Merge branch 'kiotviet' into develop
This commit is contained in:
commit
61d7b5850d
|
@ -0,0 +1,39 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"git.selly.red/Selly-Modules/natsio"
|
||||
|
||||
"git.selly.red/Selly-Modules/3pl/partnerapi/kiotviet"
|
||||
"git.selly.red/Selly-Modules/3pl/util/pjson"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var (
|
||||
client = "45575d3e-5785-***"
|
||||
secret = "65ACE104EC4232***"
|
||||
retailer = "****"
|
||||
)
|
||||
if err := natsio.Connect(natsio.Config{URL: "localhost:4222"}); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c, err := kiotviet.New(client, secret, retailer, natsio.GetServer())
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
data, err := c.GetBranches(kiotviet.ListBranchesReq{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
fmt.Println(pjson.ToJSONString(data))
|
||||
|
||||
prod, err := c.GetProductOnHands(kiotviet.ListProductOnHandsReq{
|
||||
PageSize: 10,
|
||||
})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
fmt.Println(pjson.ToJSONString(prod))
|
||||
}
|
2
go.mod
2
go.mod
|
@ -4,7 +4,7 @@ go 1.17
|
|||
|
||||
require (
|
||||
git.selly.red/Selly-Modules/logger v0.0.2-0.20221010053254-567df039afdb
|
||||
git.selly.red/Selly-Modules/natsio v1.0.2-0.20221010041139-c11419a3ad33
|
||||
git.selly.red/Selly-Modules/natsio v1.0.3-0.20231006093940-b3bde5cd0960
|
||||
github.com/nats-io/nats.go v1.17.0
|
||||
github.com/thoas/go-funk v0.9.2
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -2,6 +2,8 @@ git.selly.red/Selly-Modules/logger v0.0.2-0.20221010053254-567df039afdb h1:AmcYd
|
|||
git.selly.red/Selly-Modules/logger v0.0.2-0.20221010053254-567df039afdb/go.mod h1:Q1//Z6HRmfa7VyjH2J6YyT0YV2jT8+K6SIgwnYuS4II=
|
||||
git.selly.red/Selly-Modules/natsio v1.0.2-0.20221010041139-c11419a3ad33 h1:GvQjelaV4XZm++AOihYAKOD6k9510aMAr6B6MGnrXPs=
|
||||
git.selly.red/Selly-Modules/natsio v1.0.2-0.20221010041139-c11419a3ad33/go.mod h1:KNODhfeBqxRmHHQHHU+p3JfH42t8s5aNxfgr6X8fr6g=
|
||||
git.selly.red/Selly-Modules/natsio v1.0.3-0.20231006093940-b3bde5cd0960 h1:wL/BW1xGoB/EXeA2HtxT6Nr/cXpPJYVNfToV3aFtGls=
|
||||
git.selly.red/Selly-Modules/natsio v1.0.3-0.20231006093940-b3bde5cd0960/go.mod h1:KNODhfeBqxRmHHQHHU+p3JfH42t8s5aNxfgr6X8fr6g=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
|
|
|
@ -0,0 +1,25 @@
|
|||
package kiotviet
|
||||
|
||||
const (
|
||||
apiPathListBranches = "/branches"
|
||||
apiPathListProductOnHands = "/productOnHands"
|
||||
apiPathRegisterWebhook = "/webhooks"
|
||||
apiPathUnregisterWebhook = "/webhooks/%d" // %s -> webhook id
|
||||
|
||||
apiPathAuth = "/connect/token"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURLProd = "https://public.kiotapi.com"
|
||||
baseURLTokenProd = "https://id.kiotviet.vn"
|
||||
)
|
||||
|
||||
const (
|
||||
WebhookTypeCustomerUpdate = "customer.update"
|
||||
WebhookTypeCustomerDelete = "customer.delete"
|
||||
WebhookTypeProductUpdate = "product.update"
|
||||
WebhookTypeProductDelete = "product.delete"
|
||||
WebhookTypeStockUpdate = "stock.update"
|
||||
WebhookTypeOrderUpdate = "order.update"
|
||||
WebhookTypeInvoiceUpdate = "invoice.update"
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package kiotviet
|
||||
|
||||
// ENV ...
|
||||
type ENV string
|
||||
|
||||
const (
|
||||
// EnvStaging ENV = "STAGING"
|
||||
EnvProd ENV = "PROD"
|
||||
)
|
|
@ -0,0 +1 @@
|
|||
package kiotviet
|
|
@ -0,0 +1,272 @@
|
|||
package kiotviet
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.selly.red/Selly-Modules/natsio"
|
||||
"git.selly.red/Selly-Modules/natsio/model"
|
||||
"git.selly.red/Selly-Modules/natsio/subject"
|
||||
|
||||
"git.selly.red/Selly-Modules/3pl/util/httputil"
|
||||
"git.selly.red/Selly-Modules/3pl/util/pjson"
|
||||
)
|
||||
|
||||
const (
|
||||
logTarget = "kiotviet"
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
clientID string
|
||||
secretKey string
|
||||
retailer string
|
||||
|
||||
natsClient natsio.Server
|
||||
|
||||
auth *AuthRes
|
||||
authExpireTime time.Time
|
||||
}
|
||||
|
||||
func New(clientID, secretKey, retailer string, natsClient natsio.Server) (*Client, error) {
|
||||
if clientID == "" || secretKey == "" || retailer == "" {
|
||||
return nil, fmt.Errorf("kiotviet: cannot create client with empty info")
|
||||
}
|
||||
return &Client{
|
||||
clientID: clientID,
|
||||
secretKey: secretKey,
|
||||
retailer: retailer,
|
||||
natsClient: natsClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetProductOnHands(req ListProductOnHandsReq) (*ListProductOnHandsRes, error) {
|
||||
apiURL := c.getURL(apiPathListProductOnHands)
|
||||
query := map[string]string{
|
||||
"orderBy": req.OrderBy,
|
||||
"lastModifiedFrom": req.LastModifiedFrom,
|
||||
}
|
||||
if req.PageSize > 0 {
|
||||
query["pageSize"] = strconv.Itoa(req.PageSize)
|
||||
}
|
||||
if req.CurrentItem > 0 {
|
||||
query["currentItem"] = strconv.Itoa(req.CurrentItem)
|
||||
}
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodGet,
|
||||
Query: query,
|
||||
Header: c.getRequestHeader(),
|
||||
},
|
||||
LogTarget: logTarget,
|
||||
}
|
||||
r, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("kiotviet.Client.GetProductOnHands - requestHttpViaNats: %v, %s\n", err, apiURL)
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("kiotviet.Client.GetProductOnHands - requestHttpViaNats bad request %s", res.Body)
|
||||
}
|
||||
var data ListProductOnHandsRes
|
||||
if err = r.ParseResponseData(&data); err != nil {
|
||||
return nil, fmt.Errorf("kiotviet.Client.GetProductOnHands - requestHttpViaNats parse response %v, %s", err, res.Body)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (c *Client) GetBranches(req ListBranchesReq) (*ListBranchesRes, error) {
|
||||
apiURL := c.getURL(apiPathListBranches)
|
||||
query := map[string]string{
|
||||
"orderBy": req.OrderBy,
|
||||
"lastModifiedFrom": req.LastModifiedFrom,
|
||||
"orderDirection": req.OrderDirection,
|
||||
"includeRemoveIds": req.IncludeRemoveIDs,
|
||||
}
|
||||
if req.PageSize > 0 {
|
||||
query["pageSize"] = strconv.Itoa(req.PageSize)
|
||||
}
|
||||
if req.CurrentItem > 0 {
|
||||
query["currentItem"] = strconv.Itoa(req.CurrentItem)
|
||||
}
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodGet,
|
||||
Query: query,
|
||||
Header: c.getRequestHeader(),
|
||||
},
|
||||
LogTarget: logTarget,
|
||||
}
|
||||
|
||||
r, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("kiotviet.Client.GetBranches - requestHttpViaNats: %v, %s\n", err, apiURL)
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("kiotviet.Client.GetBranches - requestHttpViaNats bad request %s", res.Body)
|
||||
}
|
||||
var data ListBranchesRes
|
||||
if err = r.ParseResponseData(&data); err != nil {
|
||||
return nil, fmt.Errorf("kiotviet.Client.GetBranches - requestHttpViaNats parse response %v, %s", err, res.Body)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (c *Client) RegisterWebhook(req RegisterWebhookReq) (*RegisterWebhookRes, error) {
|
||||
apiURL := c.getURL(apiPathRegisterWebhook)
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodPost,
|
||||
Data: pjson.ToJSONString(req),
|
||||
Header: c.getRequestHeader(),
|
||||
},
|
||||
LogTarget: logTarget,
|
||||
}
|
||||
|
||||
r, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("kiotviet.Client.RegisterWebhook - requestHttpViaNats: %v, %s\n", err, apiURL)
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("kiotviet.Client.RegisterWebhook - requestHttpViaNats bad request %s", res.Body)
|
||||
}
|
||||
var data RegisterWebhookRes
|
||||
if err = r.ParseResponseData(&data); err != nil {
|
||||
return nil, fmt.Errorf("kiotviet.Client.RegisterWebhook - requestHttpViaNats parse response %v, %s", err, res.Body)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (c *Client) UnregisterWebhook(req UnregisterWebhookReq) (*UnregisterWebhookRes, error) {
|
||||
apiURL := c.getURL(fmt.Sprintf(apiPathUnregisterWebhook, req.ID))
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodDelete,
|
||||
Header: c.getRequestHeader(),
|
||||
},
|
||||
LogTarget: logTarget,
|
||||
}
|
||||
|
||||
r, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("kiotviet.Client.UnregisterWebhook - requestHttpViaNats: %v, %s\n", err, apiURL)
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("kiotviet.Client.UnregisterWebhook - requestHttpViaNats bad request %s", res.Body)
|
||||
}
|
||||
var data UnregisterWebhookRes
|
||||
if err = r.ParseResponseData(&data); err != nil {
|
||||
return nil, fmt.Errorf("kiotviet.Client.UnregisterWebhook - requestHttpViaNats parse response %v, %s", err, res.Body)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (c *Client) Auth() (*AuthRes, error) {
|
||||
v := url.Values{}
|
||||
v.Add("scopes", "PublicApi.Access")
|
||||
v.Add("grant_type", "client_credentials")
|
||||
v.Add("client_id", c.clientID)
|
||||
v.Add("client_secret", c.secretKey)
|
||||
|
||||
body := v.Encode()
|
||||
header := map[string]string{
|
||||
httputil.HeaderKeyContentType: httputil.HeaderValueApplicationURLEncoded,
|
||||
}
|
||||
apiURL := baseURLTokenProd + apiPathAuth
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodPost,
|
||||
Data: body,
|
||||
Header: header,
|
||||
},
|
||||
LogTarget: logTarget,
|
||||
}
|
||||
r, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("kiotviet.Client.Auth - requestHttpViaNats: %v, %s\n", err, apiURL)
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("kiotviet.Client.Auth - requestHttpViaNats %s", res.Body)
|
||||
}
|
||||
var data AuthRes
|
||||
if err = r.ParseResponseData(&data); err != nil {
|
||||
return nil, fmt.Errorf("kiotviet.Client.Auth - requestHttpViaNats parse response %v, %s", err, res.Body)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (c *Client) getURL(path string) string {
|
||||
return baseURLProd + path
|
||||
}
|
||||
|
||||
func (c *Client) requestHttpViaNats(data model.CommunicationRequestHttp) (*model.CommunicationHttpResponse, error) {
|
||||
b := pjson.ToBytes(data)
|
||||
msg, err := c.natsClient.Request(subject.Communication.RequestHTTP, b)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("kiotviet.Client.requestHttpViaNats err: %v, url %s", err, data.Payload.URL)
|
||||
}
|
||||
var r model.CommunicationHttpResponse
|
||||
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
|
||||
return nil, fmt.Errorf("kiotviet.Client.requestHttpViaNats parse data err: %v, url %s, data %s", err, data.Payload.URL, string(msg.Data))
|
||||
}
|
||||
if r.Response == nil {
|
||||
return nil, fmt.Errorf("kiotviet.Client.requestHttpViaNats empty reponse")
|
||||
}
|
||||
return &r, nil
|
||||
}
|
||||
|
||||
func (c *Client) getRequestHeader() map[string]string {
|
||||
m := map[string]string{
|
||||
httputil.HeaderKeyContentType: httputil.HeaderValueApplicationJSON,
|
||||
"Retailer": c.retailer,
|
||||
}
|
||||
|
||||
token, err := c.getToken()
|
||||
if err != nil {
|
||||
log.Printf("kiotviet.Client.getToken err %v\n", err)
|
||||
} else {
|
||||
m["Authorization"] = fmt.Sprintf("Bearer %s", token)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *Client) getToken() (string, error) {
|
||||
auth := c.auth
|
||||
if auth != nil && c.authExpireTime.After(time.Now()) {
|
||||
return auth.AccessToken, nil
|
||||
}
|
||||
data, err := c.Auth()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c.auth = data
|
||||
d := time.Duration(data.ExpiresIn) * time.Second
|
||||
if d.Minutes() > 30 {
|
||||
d -= 30 * time.Minute
|
||||
}
|
||||
c.authExpireTime = time.Now().Add(d)
|
||||
|
||||
return data.AccessToken, nil
|
||||
}
|
|
@ -0,0 +1,60 @@
|
|||
package kiotviet
|
||||
|
||||
type AuthReq struct {
|
||||
ClientID string
|
||||
ClientSecret string
|
||||
GrantType string
|
||||
Scopes string
|
||||
}
|
||||
|
||||
type ListProductOnHandsReq struct {
|
||||
OrderBy string `json:"orderBy,omitempty"`
|
||||
LastModifiedFrom string `json:"lastModifiedFrom,omitempty"`
|
||||
PageSize int `json:"pageSize,omitempty"`
|
||||
CurrentItem int `json:"currentItem,omitempty"`
|
||||
}
|
||||
|
||||
type ListBranchesReq struct {
|
||||
OrderBy string `json:"orderBy,omitempty"`
|
||||
LastModifiedFrom string `json:"lastModifiedFrom,omitempty"`
|
||||
PageSize int `json:"pageSize,omitempty"`
|
||||
CurrentItem int `json:"currentItem,omitempty"`
|
||||
OrderDirection string `json:"orderDirection,omitempty"` // Asc/ Desc. Default Desc
|
||||
IncludeRemoveIDs string `json:"includeRemoveIds,omitempty"` // true/ false
|
||||
}
|
||||
|
||||
type WebhookReq struct {
|
||||
Type string `json:"Type"`
|
||||
Url string `json:"Url"`
|
||||
IsActive bool `json:"IsActive"`
|
||||
}
|
||||
|
||||
type RegisterWebhookReq struct {
|
||||
Webhook WebhookReq `json:"webhook"`
|
||||
}
|
||||
|
||||
type UnregisterWebhookReq struct {
|
||||
ID int
|
||||
}
|
||||
|
||||
type WebhookBody struct {
|
||||
ID string `json:"Id"`
|
||||
Attempt int `json:"Attempt"`
|
||||
Notifications []WebhookNotification `json:"Notifications"`
|
||||
}
|
||||
|
||||
type WebhookNotification struct {
|
||||
Action string `json:"Action"`
|
||||
Data []WebhookStockUpdateData `json:"data"`
|
||||
}
|
||||
|
||||
type WebhookStockUpdateData struct {
|
||||
ProductID int64 `json:"ProductId"`
|
||||
ProductCode string `json:"ProductCode"`
|
||||
ProductName string `json:"ProductName"`
|
||||
BranchID int `json:"BranchId"`
|
||||
BranchName string `json:"BranchName"`
|
||||
Cost int64 `json:"Cost"`
|
||||
OnHand int `json:"OnHand"`
|
||||
Reserved int `json:"Reserved"`
|
||||
}
|
|
@ -0,0 +1,69 @@
|
|||
package kiotviet
|
||||
|
||||
import "time"
|
||||
|
||||
type AuthRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
TokenType string `json:"token_type"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
type ListProductOnHandsRes struct {
|
||||
Total int `json:"total"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Data []ProductOnHand `json:"data"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
type ProductOnHand struct {
|
||||
ID int `json:"id"`
|
||||
Code string `json:"code"`
|
||||
CreatedDate string `json:"createdDate"`
|
||||
ModifiedDate string `json:"modifiedDate"`
|
||||
Inventories []ProductOnHandInventory `json:"inventories"`
|
||||
}
|
||||
|
||||
type ProductOnHandInventory struct {
|
||||
BranchID int `json:"branchId"`
|
||||
OnHand int `json:"onHand"`
|
||||
Reserved int `json:"reserved"`
|
||||
}
|
||||
|
||||
type ListBranchesRes struct {
|
||||
Total int `json:"total"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Data []Branch `json:"data"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
||||
|
||||
type Branch struct {
|
||||
ID int `json:"id"`
|
||||
BranchName string `json:"branchName"`
|
||||
Address string `json:"address"`
|
||||
LocationName string `json:"locationName"`
|
||||
ContactNumber string `json:"contactNumber"`
|
||||
RetailerID int `json:"retailerId"`
|
||||
CreatedDate string `json:"createdDate"`
|
||||
}
|
||||
|
||||
type RegisterWebhookRes struct {
|
||||
ID int `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Url string `json:"url"`
|
||||
IsActive bool `json:"isActive"`
|
||||
RetailerID int `json:"retailerId"`
|
||||
}
|
||||
|
||||
type UnregisterWebhookRes struct {
|
||||
Success bool
|
||||
}
|
||||
|
||||
type ResponseError struct {
|
||||
ResponseStatus RError `json:"responseStatus"`
|
||||
}
|
||||
|
||||
type RError struct {
|
||||
ErrorCode string `json:"errorCode"`
|
||||
Message string `json:"message"`
|
||||
}
|
Loading…
Reference in New Issue