Compare commits
53 Commits
integrate-
...
master
Author | SHA1 | Date |
---|---|---|
sinhluu | 5c20536492 | |
Sinh | 4b621b9b41 | |
sinhluu | 91e69993af | |
Sinh | b7195b364f | |
Sinh | 624cb96239 | |
Sinh | e5619ae68c | |
Sinh | 00ec7e1f2f | |
Sinh | fa6198d7e3 | |
Sinh | d70f298734 | |
Sinh | ee60175a4d | |
Sinh | baf7be0820 | |
Sinh | 178652fcef | |
Sinh | 1034d2e077 | |
sinhluu | 756740a2d7 | |
sinhluu | 9fb4f0c6f3 | |
Sinh | ac8aa89d70 | |
sinhluu | d4915875e9 | |
Sinh | a33809e092 | |
sinhluu | d549cc5103 | |
Sinh | 1c6be73d04 | |
Sinh | a9b7f39b09 | |
Sinh | 71e2f0b842 | |
sinhluu | aa202e8fcb | |
Sinh | 51afbc9c0e | |
Sinh | 83201d37af | |
sinhluu | 007796e668 | |
Sinh | fb417551f4 | |
Sinh | efd91a81dd | |
Sinh | 8a0e7c2cb7 | |
Sinh | 89d2f1c8fb | |
Sinh | 6bf4914ade | |
Sinh | 844b01604a | |
Sinh | 03b68fd7e4 | |
Sinh | cd317aa9a2 | |
Sinh | fc532e5b0c | |
Sinh | 1f4c6890eb | |
Sinh | 5257cd21bc | |
Sinh | a929e52324 | |
Sinh | 9e15bfc101 | |
Sinh | b15d883542 | |
sinhluu | 94d6e0c606 | |
Sinh | 7d1d63039d | |
Sinh | 5b1b55ee78 | |
sinhluu | ba6851fe19 | |
Sinh | 4b8e6c4178 | |
sinhluu | 13eec58c2b | |
Sinh | 04c8804c9d | |
sinhluu | 01a693f36a | |
Sinh | 1e16cae1e3 | |
Sinh | 83b1969941 | |
Sinh | 2d7b0b5662 | |
trunglam | 93651891ab | |
trunglt251292 | ff3264f0d3 |
|
@ -1,4 +1,4 @@
|
|||
package shiip
|
||||
package tpl
|
||||
|
||||
// ENV ...
|
||||
type ENV string
|
|
@ -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))
|
||||
}
|
6
go.mod
6
go.mod
|
@ -1,10 +1,11 @@
|
|||
module git.selly.red/Selly-Modules/3pl
|
||||
|
||||
go 1.17
|
||||
go 1.20
|
||||
|
||||
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.20231020090841-5edec97ee393
|
||||
github.com/go-resty/resty/v2 v2.7.0
|
||||
github.com/nats-io/nats.go v1.17.0
|
||||
github.com/thoas/go-funk v0.9.2
|
||||
)
|
||||
|
@ -34,6 +35,7 @@ require (
|
|||
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect
|
||||
golang.org/x/lint v0.0.0-20210508222113-6edffad5e616 // indirect
|
||||
golang.org/x/mod v0.5.1 // indirect
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 // indirect
|
||||
golang.org/x/sys v0.0.0-20211102192858-4dd72447c267 // indirect
|
||||
golang.org/x/tools v0.1.7 // indirect
|
||||
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
|
||||
|
|
8
go.sum
8
go.sum
|
@ -1,7 +1,7 @@
|
|||
git.selly.red/Selly-Modules/logger v0.0.2-0.20221010053254-567df039afdb h1:AmcYd88IcdSkH+NEvKyJLT7psidSkjJQT/nAg/KuzFk=
|
||||
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.20231020090841-5edec97ee393 h1:43kE03FW3NONfE6hXlghafS1d233dfc7grlFqd+15SA=
|
||||
git.selly.red/Selly-Modules/natsio v1.0.3-0.20231020090841-5edec97ee393/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=
|
||||
|
@ -17,6 +17,8 @@ github.com/elastic/go-sysinfo v1.1.1/go.mod h1:i1ZYdU10oLNfRzq4vq62BEwD2fH8KaWh6
|
|||
github.com/elastic/go-windows v1.0.0/go.mod h1:TsU0Nrp7/y3+VwE82FoZF8gC/XFg/Elz6CcloAxnPgU=
|
||||
github.com/elastic/go-windows v1.0.1 h1:AlYZOldA+UJ0/2nBuqWdo90GFCgG9xuyw9SYzGUtJm0=
|
||||
github.com/elastic/go-windows v1.0.1/go.mod h1:FoVvqWSun28vaDQPbj2Elfc0JahhPB7WQEGa3c814Ss=
|
||||
github.com/go-resty/resty/v2 v2.7.0 h1:me+K9p3uhSmXtrBZ4k9jcEAfJmuC8IivWHwaLZwPrFY=
|
||||
github.com/go-resty/resty/v2 v2.7.0/go.mod h1:9PWDzw47qPphMRFfhsyk0NnSgvluHcljSMVIq3w7q0I=
|
||||
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
|
||||
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
|
||||
github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs=
|
||||
|
@ -142,6 +144,8 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL
|
|||
golang.org/x/net v0.0.0-20200226121028-0de0cce0169b/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
|
||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
|
||||
golang.org/x/net v0.0.0-20210805182204-aaa1db679c0d/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211029224645-99673261e6eb/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2 h1:CIJ76btIcR3eFI5EgSo6k1qKw9KJexJuRLI9G7Hp5wE=
|
||||
golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
|
||||
golang.org/x/sync v0.0.0-20181221193216-37e7f081c4d4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM=
|
||||
|
|
|
@ -167,7 +167,7 @@ func (c *Client) GetOrder(orderCode string) (*GetOrderResponseDecoded, error) {
|
|||
}
|
||||
|
||||
func (c *Client) requestNats(subject string, data interface{}) (*nats.Msg, error) {
|
||||
b := pjson.ToBytes(data)
|
||||
b := toBytes(data)
|
||||
return c.natsClient.Request(subject, b)
|
||||
}
|
||||
|
||||
|
|
|
@ -3,10 +3,27 @@ package globalcare
|
|||
import (
|
||||
"crypto/rsa"
|
||||
"crypto/x509"
|
||||
"encoding/json"
|
||||
"encoding/pem"
|
||||
"fmt"
|
||||
|
||||
"git.selly.red/Selly-Modules/logger"
|
||||
)
|
||||
|
||||
// toBytes ...
|
||||
func toBytes(data interface{}) []byte {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
logger.Error("pjson.toBytes", logger.LogData{"payload": data})
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// toJSONString ...
|
||||
func toJSONString(data interface{}) string {
|
||||
return string(toBytes(data))
|
||||
}
|
||||
|
||||
// GeneratePublicKeyFromBytes ...
|
||||
func generatePublicKeyFromBytes(b []byte) (*rsa.PublicKey, error) {
|
||||
pubPem, _ := pem.Decode(b)
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package jtexpress
|
||||
|
||||
const (
|
||||
apiHostProd = "https://ylstandard.jtexpress.vn"
|
||||
apiHostDev = "https://demo-ylstandard.jtexpress.vn"
|
||||
|
||||
apiPathEstimateFee = "/yuenan-interface-web/jtpos/inquiry!freight.action"
|
||||
apiPathCreateOrder = "/yuenan-interface-web/order/orderAction!createOrder.action"
|
||||
apiPathCancelOrder = "/yuenan-interface-web/order/orderAction!createOrder.action"
|
||||
apiPathTrackingOrder = ""
|
||||
)
|
||||
|
||||
const (
|
||||
statusSuccess = "true"
|
||||
)
|
||||
|
||||
const (
|
||||
msgTypeEstimateFee = "FREIGHTQUERY"
|
||||
msgTypeCreateOrder = "ORDERCREATE"
|
||||
msgTypeCancelOrder = "UPDATE"
|
||||
)
|
|
@ -0,0 +1,120 @@
|
|||
package jtexpress
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"fmt"
|
||||
"io"
|
||||
|
||||
"github.com/go-resty/resty/v2"
|
||||
|
||||
"git.selly.red/Selly-Modules/3pl/util/base64"
|
||||
"git.selly.red/Selly-Modules/3pl/util/pjson"
|
||||
)
|
||||
|
||||
func New(digestKey, companyID string, isProd, debug bool) *Client {
|
||||
host := apiHostDev
|
||||
if isProd {
|
||||
host = apiHostProd
|
||||
}
|
||||
c := &Client{
|
||||
DigestKey: digestKey,
|
||||
EccompanyID: companyID,
|
||||
IsProduction: isProd,
|
||||
Debug: debug,
|
||||
host: host,
|
||||
httpClient: resty.New().SetDebug(debug),
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
type Client struct {
|
||||
DigestKey string
|
||||
EccompanyID string
|
||||
IsProduction bool
|
||||
Debug bool
|
||||
host string
|
||||
httpClient *resty.Client
|
||||
}
|
||||
|
||||
func (c *Client) EstimateFee(req *EstimateFeeReq) (r Response) {
|
||||
path := c.host + apiPathEstimateFee
|
||||
data := pjson.ToJSONString(req)
|
||||
body := map[string]string{
|
||||
"logistics_interface": data,
|
||||
"data_digest": c.getDigest(data),
|
||||
"msg_type": msgTypeEstimateFee,
|
||||
"eccompanyid": c.EccompanyID,
|
||||
}
|
||||
r.Request.Body = pjson.ToBytes(body)
|
||||
r.Request.URL = path
|
||||
resp, err := c.httpClient.R().
|
||||
SetMultipartFormData(body).
|
||||
Post(path)
|
||||
if err != nil {
|
||||
r.Error = fmt.Errorf("jtepxress: request %s, err %v", path, err)
|
||||
return r
|
||||
}
|
||||
r.Response.StatusCode = resp.StatusCode()
|
||||
r.Response.Body = resp.Body()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *Client) CancelOrder(req *CancelOrderReq) (r Response) {
|
||||
path := c.host + apiPathCancelOrder
|
||||
data := pjson.ToJSONString(req)
|
||||
body := map[string]string{
|
||||
"logistics_interface": data,
|
||||
"data_digest": c.getDigest(data),
|
||||
"msg_type": msgTypeCancelOrder,
|
||||
"eccompanyid": c.EccompanyID,
|
||||
}
|
||||
r.Request.Body = pjson.ToBytes(body)
|
||||
r.Request.URL = path
|
||||
resp, err := c.httpClient.R().
|
||||
SetMultipartFormData(body).
|
||||
Post(path)
|
||||
if err != nil {
|
||||
r.Error = fmt.Errorf("jtepxress: request %s, err %v", path, err)
|
||||
return r
|
||||
}
|
||||
|
||||
r.Response.StatusCode = resp.StatusCode()
|
||||
r.Response.Body = resp.Body()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *Client) CreateOrder(req *CreateOrderReq) (r Response) {
|
||||
path := c.host + apiPathCreateOrder
|
||||
data := pjson.ToJSONString(req)
|
||||
body := map[string]string{
|
||||
"logistics_interface": data,
|
||||
"data_digest": c.getDigest(data),
|
||||
"msg_type": msgTypeCreateOrder,
|
||||
"eccompanyid": c.EccompanyID,
|
||||
}
|
||||
r.Request.Body = pjson.ToBytes(body)
|
||||
r.Request.URL = path
|
||||
|
||||
resp, err := c.httpClient.R().
|
||||
SetMultipartFormData(body).
|
||||
Post(path)
|
||||
if err != nil {
|
||||
r.Error = fmt.Errorf("jtepxress: request %s, err %v", path, err)
|
||||
return r
|
||||
}
|
||||
|
||||
r.Response.StatusCode = resp.StatusCode()
|
||||
r.Response.Body = resp.Body()
|
||||
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *Client) getDigest(data string) string {
|
||||
s := data + c.DigestKey
|
||||
h := md5.New()
|
||||
|
||||
io.WriteString(h, s)
|
||||
return base64.Encode([]byte(fmt.Sprintf("%x", h.Sum(nil))))
|
||||
}
|
|
@ -0,0 +1,76 @@
|
|||
package jtexpress
|
||||
|
||||
type EstimateFeeLocation struct {
|
||||
Prov string `json:"prov"`
|
||||
City string `json:"city"`
|
||||
Area string `json:"area"`
|
||||
}
|
||||
|
||||
type EstimateFeeReq struct {
|
||||
Cusname string `json:"cusname"` // = customerid
|
||||
SelfAddress int `json:"selfAddress"`
|
||||
ProductType string `json:"producttype"`
|
||||
GoodsValue string `json:"goodsvalue"`
|
||||
ItemsValue string `json:"itemsvalue"`
|
||||
Weight string `json:"weight"`
|
||||
Sender EstimateFeeLocation `json:"sender"`
|
||||
Receiver EstimateFeeLocation `json:"receiver"`
|
||||
Decs string `json:"decs"`
|
||||
FeeType string `json:"feetype"`
|
||||
}
|
||||
|
||||
type CreateOrderReq struct {
|
||||
Eccompanyid string `json:"eccompanyid"`
|
||||
Customerid string `json:"customerid"`
|
||||
Txlogisticid string `json:"txlogisticid"`
|
||||
Ordertype int `json:"ordertype"`
|
||||
Servicetype int `json:"servicetype"`
|
||||
SelfAddress int `json:"selfAddress"`
|
||||
Special string `json:"special"`
|
||||
Partsign string `json:"partsign"`
|
||||
Sender CreateOrderLocation `json:"sender"`
|
||||
Receiver CreateOrderLocation `json:"receiver"`
|
||||
Createordertime string `json:"createordertime"`
|
||||
Sendstarttime string `json:"sendstarttime"`
|
||||
Sendendtime string `json:"sendendtime"`
|
||||
Paytype string `json:"paytype"`
|
||||
Itemsvalue string `json:"itemsvalue"`
|
||||
Goodsvalue string `json:"goodsvalue"`
|
||||
IsInsured string `json:"isInsured"`
|
||||
Items []OrderItem `json:"items"`
|
||||
Weight string `json:"weight"`
|
||||
Volume string `json:"volume"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
||||
|
||||
type CreateOrderLocation struct {
|
||||
Name string `json:"name"`
|
||||
Phone string `json:"phone"`
|
||||
Mobile string `json:"mobile"`
|
||||
Prov string `json:"prov"`
|
||||
City string `json:"city"`
|
||||
Area string `json:"area"`
|
||||
Address string `json:"address"`
|
||||
}
|
||||
|
||||
type OrderItem struct {
|
||||
Itemname string `json:"itemname"`
|
||||
EnglishName string `json:"englishName"`
|
||||
Number string `json:"number"`
|
||||
Itemvalue string `json:"itemvalue"`
|
||||
Desc string `json:"desc"`
|
||||
}
|
||||
|
||||
type CancelOrderReq struct {
|
||||
Eccompanyid string `json:"eccompanyid"`
|
||||
Customerid string `json:"customerid"`
|
||||
Logisticproviderid string `json:"logisticproviderid"`
|
||||
Fieldlist []CancelOrderFieldList `json:"fieldlist"`
|
||||
}
|
||||
|
||||
type CancelOrderFieldList struct {
|
||||
Txlogisticid string `json:"txlogisticid"`
|
||||
Fieldname string `json:"fieldname"`
|
||||
Fieldvalue string `json:"fieldvalue"`
|
||||
Remark string `json:"remark"`
|
||||
}
|
|
@ -0,0 +1,62 @@
|
|||
package jtexpress
|
||||
|
||||
type EstimateFeeItemRes struct {
|
||||
ProductType string `json:"producttype"`
|
||||
Price string `json:"price"`
|
||||
CodFee string `json:"codfee"`
|
||||
InsuranceFee string `json:"insurancefee"`
|
||||
DiscountFee string `json:"discountFee"`
|
||||
Success string `json:"success"`
|
||||
Reason string `json:"reason"`
|
||||
}
|
||||
|
||||
type EstimateFeeRes struct {
|
||||
LogisticProviderID string `json:"logisticproviderid"`
|
||||
ResponseItems []*EstimateFeeItemRes `json:"responseitems"`
|
||||
}
|
||||
|
||||
type CreateOrderRes struct {
|
||||
LogisticProviderID string `json:"logisticproviderid"`
|
||||
ResponseItems []*CreateOrderItemRes `json:"responseitems"`
|
||||
}
|
||||
|
||||
type CreateOrderItemRes struct {
|
||||
Billcode string `json:"billcode"`
|
||||
CodFee string `json:"codFee"`
|
||||
Code string `json:"code"`
|
||||
DiscountFee string `json:"discountFee"`
|
||||
DispatchSite string `json:"dispatchSite"`
|
||||
InquiryFee string `json:"inquiryFee"`
|
||||
Insurancefee string `json:"insurancefee"`
|
||||
Reason string `json:"reason"`
|
||||
Reportnewurl string `json:"reportnewurl"`
|
||||
Reporturl string `json:"reporturl"`
|
||||
ReporturlJT string `json:"reporturlJT"`
|
||||
Success string `json:"success"`
|
||||
Transport string `json:"transport"`
|
||||
Txlogisticid string `json:"txlogisticid"`
|
||||
SortLine string `json:"sortLine"`
|
||||
}
|
||||
|
||||
type CancelOrderRes struct {
|
||||
LogisticProviderID string `json:"logisticproviderid"`
|
||||
ResponseItems []*CreateOrderItemRes `json:"responseitems"`
|
||||
}
|
||||
|
||||
type Response struct {
|
||||
Request RequestInfo
|
||||
Response ResponseInfo
|
||||
Error error
|
||||
}
|
||||
|
||||
type ResponseInfo struct {
|
||||
StatusCode int
|
||||
Body []byte
|
||||
}
|
||||
|
||||
type RequestInfo struct {
|
||||
Method string
|
||||
URL string
|
||||
Headers map[string]string
|
||||
Body []byte
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package kiotviet
|
||||
|
||||
const (
|
||||
apiPathListBranches = "/branches"
|
||||
apiPathListProductOnHands = "/productOnHands"
|
||||
apiPathListWebhook = "/webhooks"
|
||||
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,300 @@
|
|||
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) ListWebhooks(req ListWebhookReq) (*ListWebhookRes, error) {
|
||||
apiURL := c.getURL(apiPathListWebhook)
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodGet,
|
||||
Header: c.getRequestHeader(),
|
||||
},
|
||||
LogTarget: logTarget,
|
||||
}
|
||||
|
||||
r, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("kiotviet.Client.ListWebhooks - requestHttpViaNats: %v, %s\n", err, apiURL)
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("kiotviet.Client.ListWebhooks - requestHttpViaNats bad request %s", res.Body)
|
||||
}
|
||||
var data ListWebhookRes
|
||||
if err = r.ParseResponseData(&data); err != nil {
|
||||
return nil, fmt.Errorf("kiotviet.Client.ListWebhooks - 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.Success); 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,63 @@
|
|||
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 ListWebhookReq struct {
|
||||
}
|
||||
|
||||
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,85 @@
|
|||
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"`
|
||||
}
|
||||
|
||||
type Webhook struct {
|
||||
Id int `json:"id"`
|
||||
Type string `json:"type"`
|
||||
Url string `json:"url"`
|
||||
IsActive bool `json:"isActive"`
|
||||
RetailerId int `json:"retailerId"`
|
||||
ModifiedDate time.Time `json:"modifiedDate"`
|
||||
}
|
||||
|
||||
type ListWebhookRes struct {
|
||||
Total int `json:"total"`
|
||||
PageSize int `json:"pageSize"`
|
||||
Data []Webhook `json:"data"`
|
||||
Timestamp time.Time `json:"timestamp"`
|
||||
}
|
|
@ -4,6 +4,7 @@ const (
|
|||
PaymentMethodCOD = "cod"
|
||||
PaymentMethodBankTransfer = "bank_transfer"
|
||||
PaymentMethodWallet = "wallet"
|
||||
PaymentMethodFundiin = "fundiin"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -20,13 +21,14 @@ const (
|
|||
apiPathUpdateDelivery = "/v1/orders/update_delivery"
|
||||
apiPathCancelOrder = "/v1/orders/cancel"
|
||||
apiPathGetChannels = "/v1/channels"
|
||||
apiPathGetInventories = "/v1/inventories"
|
||||
|
||||
headerXAPIKey = "x-api-key"
|
||||
headerXTimestamp = "x-timestamp"
|
||||
headerXSignature = "x-signature"
|
||||
|
||||
webhookEventUpdateOrderStatus = "UPDATE_ORDER_STATUS"
|
||||
webhookEventUpdateInventory = "update_inventory"
|
||||
webhookEventUpdateInventory = "UPDATE_INVENTORY"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
|
@ -12,6 +12,8 @@ import (
|
|||
|
||||
// CreateOrderRequest ...
|
||||
type CreateOrderRequest struct {
|
||||
StoreCode string `json:"store_code"`
|
||||
DeliveryPlatform string `json:"delivery_platform"`
|
||||
OrderCode string `json:"order_code"`
|
||||
OrderDate time.Time `json:"order_date"`
|
||||
PickupLocationCode string `json:"pickup_location_code"`
|
||||
|
@ -25,13 +27,13 @@ type CreateOrderRequest struct {
|
|||
|
||||
// OrderItem ...
|
||||
type OrderItem struct {
|
||||
SellingPrice int `json:"selling_price"`
|
||||
Quantity int `json:"quantity"`
|
||||
Uom string `json:"uom"`
|
||||
Amount int `json:"amount"`
|
||||
Name string `json:"name"`
|
||||
PartnerSku string `json:"sku"`
|
||||
DiscountPrice int `json:"discount_price"`
|
||||
SellingPrice int `json:"selling_price"`
|
||||
Quantity int `json:"quantity"`
|
||||
Uom string `json:"uom"`
|
||||
Amount int `json:"amount"`
|
||||
Name string `json:"name"`
|
||||
PartnerSku string `json:"sku"`
|
||||
DiscountedPrice int `json:"discounted_price"`
|
||||
}
|
||||
|
||||
// UpdateOrderDeliveryRequest ...
|
||||
|
@ -48,19 +50,24 @@ type CancelOrderRequest struct {
|
|||
OrderNo string `json:"order_code"`
|
||||
}
|
||||
|
||||
type ListInventoriesReq struct {
|
||||
UpdatedFrom time.Time
|
||||
UpdatedTo time.Time
|
||||
SKUList []string
|
||||
Size int
|
||||
Page int
|
||||
}
|
||||
|
||||
/**
|
||||
* WEBHOOK ONPOINT
|
||||
*/
|
||||
|
||||
// WebhookDataUpdateInventory ...
|
||||
type WebhookDataUpdateInventory struct {
|
||||
Sku string `json:"sku"`
|
||||
PartnerSku string `json:"partner_sku"`
|
||||
WarehouseCode string `json:"warehouse_code"`
|
||||
AvailableQuantity int `json:"available_quantity"`
|
||||
CommittedQuantity int `json:"committed_quantity"`
|
||||
TotalQuantity int `json:"total_quantity"`
|
||||
UpdatedAt string `json:"updated_at"`
|
||||
Sku string `json:"sku"`
|
||||
AvailableQuantity int `json:"available_quantity"`
|
||||
PickupLocationCode string `json:"pickup_location_code"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// WebhookDataUpdateOrderStatus ...
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package onpoint
|
||||
|
||||
import "time"
|
||||
|
||||
// CreateOrderResponse ...
|
||||
type CreateOrderResponse struct {
|
||||
OrderCode string `json:"order_code"`
|
||||
|
@ -34,3 +36,34 @@ type ChannelResponse struct {
|
|||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ListInventoriesRes struct {
|
||||
Code string `json:"code"`
|
||||
Data struct {
|
||||
Entries []InventoryEntry `json:"entries"`
|
||||
Page int `json:"page"`
|
||||
Size int `json:"size"`
|
||||
TotalEntries int `json:"total_entries"`
|
||||
TotalPages int `json:"total_pages"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
type InventoryEntry struct {
|
||||
AvailableQuantity int `json:"available_quantity"`
|
||||
PickupLocation Pickup `json:"pickup_location"`
|
||||
Product Product `json:"product"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
type Product struct {
|
||||
Name string `json:"name"`
|
||||
OriginalPrice int `json:"original_price"`
|
||||
SellingPrice int `json:"selling_price"`
|
||||
Sku string `json:"sku"`
|
||||
Uom interface{} `json:"uom"`
|
||||
}
|
||||
|
||||
type Pickup struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
@ -152,19 +153,77 @@ func (c *Client) CancelOrder(p CancelOrderRequest) (*CancelOrderResponse, error)
|
|||
return &dataRes.Data, nil
|
||||
}
|
||||
|
||||
// GetInventories ...
|
||||
func (c *Client) GetInventories(req ListInventoriesReq) (*ListInventoriesRes, error) {
|
||||
url := c.getBaseURL() + apiPathGetInventories
|
||||
q := map[string]string{}
|
||||
if !req.UpdatedFrom.IsZero() {
|
||||
q["updated_from"] = req.UpdatedFrom.Format(TimeLayout)
|
||||
}
|
||||
if !req.UpdatedTo.IsZero() {
|
||||
q["updated_to"] = req.UpdatedTo.Format(TimeLayout)
|
||||
}
|
||||
if len(req.SKUList) > 0 {
|
||||
q["sku_list"] = strings.Join(req.SKUList, ",")
|
||||
}
|
||||
if req.Page > 0 {
|
||||
q["page"] = strconv.Itoa(req.Page)
|
||||
}
|
||||
if req.Size > 0 {
|
||||
q["size"] = strconv.Itoa(req.Size)
|
||||
}
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: url,
|
||||
Method: http.MethodGet,
|
||||
Query: q,
|
||||
},
|
||||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
dataRes ListInventoriesRes
|
||||
)
|
||||
if err := c.requestHttpViaNats(natsPayload, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.GetInventories: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
if err := r.ParseResponseData(&errRes); err != nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.GetInventories: parse_response_err: %v", err)
|
||||
}
|
||||
return nil, errRes
|
||||
}
|
||||
if err := r.ParseResponseData(&dataRes); err != nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.GetInventories: parse_response_data: %v", err)
|
||||
}
|
||||
|
||||
return &dataRes, nil
|
||||
}
|
||||
|
||||
func (c *Client) requestHttpViaNats(data model.CommunicationRequestHttp, res interface{}) error {
|
||||
ec, err := c.natsClient.NewJSONEncodedConn()
|
||||
if err != nil {
|
||||
return fmt.Errorf("onpoint: request via nats %v", err)
|
||||
}
|
||||
qs := ""
|
||||
for k, v := range data.Payload.Query {
|
||||
qs += k + "=" + v
|
||||
u, err := url.ParseRequestURI(data.Payload.URL)
|
||||
if err != nil {
|
||||
return fmt.Errorf("onpoint: request via nats %v", err)
|
||||
}
|
||||
q := u.Query()
|
||||
for k, v := range data.Payload.Query {
|
||||
q.Set(k, v)
|
||||
}
|
||||
u.RawQuery = q.Encode()
|
||||
|
||||
now := time.Now().Unix()
|
||||
ts := strconv.FormatInt(now, 10)
|
||||
arr := []string{
|
||||
qs,
|
||||
u.RawQuery,
|
||||
data.Payload.Data,
|
||||
ts,
|
||||
}
|
||||
|
@ -177,6 +236,8 @@ func (c *Client) requestHttpViaNats(data model.CommunicationRequestHttp, res int
|
|||
headerXTimestamp: ts,
|
||||
httputil.HeaderKeyContentType: httputil.HeaderValueApplicationJSON,
|
||||
}
|
||||
data.Payload.Query = map[string]string{}
|
||||
data.Payload.URL = u.String()
|
||||
|
||||
return ec.Request(subject.Communication.RequestHTTP, data, res)
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ package onpoint
|
|||
const (
|
||||
OrderStatusNew = "new"
|
||||
OrderStatusPendingWarehouse = "pending_warehouse"
|
||||
OrderStatusWhPending = "wh_pending"
|
||||
OrderStatusWhProcessing = "wh_processing"
|
||||
OrderStatusWhCompleted = "wh_completed"
|
||||
OrderStatusDlPending = "dl_pending"
|
||||
|
|
|
@ -1,54 +0,0 @@
|
|||
package shiip
|
||||
|
||||
const (
|
||||
TimeLayout = "2006-01-02 15:04:05"
|
||||
|
||||
apiPathCreateOutboundRequest = "/v1/api/external/vietful/outbound/requests"
|
||||
apiPathGetOutboundRequest = "/v1/api/external/vietful/outbound/requests/%d"
|
||||
apiPathCancelOutboundRequest = "/v1/api/external/vietful/outbound/requests/%d/cancel"
|
||||
apiPathUpdateLogisticInfoOutboundRequest = "/v1/api/external/vietful/outbound/requests/%d/logistic-info"
|
||||
apiPathAuth = "/v1/api/external/vietful/auth/access-token"
|
||||
|
||||
PriorityUrgent = 3
|
||||
PriorityHigh = 2
|
||||
PriorityNormal = 1
|
||||
|
||||
TPLCodeGHN = "GHN"
|
||||
TPLCodeGHTK = "GHTK"
|
||||
TPLCodeBest = "BEST"
|
||||
TPLCodeSnappy = "SPY"
|
||||
TPLCodeViettelPost = "VTP"
|
||||
TPLCodeSellyExpress = "SE"
|
||||
TPLCodeJTExpress = "JTE"
|
||||
|
||||
ShippingServiceCodeSTD = "STD"
|
||||
ORTypeOrder = 1
|
||||
ShippingTypeSelfShip = 1
|
||||
PackTypeNormal = 1
|
||||
BizTypeB2C = 1
|
||||
ConditionTypeCodeNew = "NEW"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURLAuthStaging = "https://api.shiip.vn"
|
||||
baseURLStaging = "https://api.shiip.vn"
|
||||
|
||||
// TODO: add base URL
|
||||
baseURLAuthProd = ""
|
||||
baseURLProd = ""
|
||||
)
|
||||
|
||||
const (
|
||||
ErrCodeExistPartnerCode = "exist_partner_code"
|
||||
)
|
||||
|
||||
var (
|
||||
baseURLENVMapping = map[ENV]string{
|
||||
EnvProd: baseURLProd,
|
||||
EnvStaging: baseURLStaging,
|
||||
}
|
||||
baseURLAuthENVMapping = map[ENV]string{
|
||||
EnvProd: baseURLAuthProd,
|
||||
EnvStaging: baseURLAuthStaging,
|
||||
}
|
||||
)
|
|
@ -1,22 +0,0 @@
|
|||
package shiip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
)
|
||||
|
||||
// Error ...
|
||||
type Error struct {
|
||||
Code string `json:"code"`
|
||||
Message string `json:"errorMessage"`
|
||||
}
|
||||
|
||||
// Error ...
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("tnc_err: code %s, messsage %s", e.Code, e.Message)
|
||||
}
|
||||
|
||||
// IsErrExistPartnerCode ...
|
||||
func IsErrExistPartnerCode(err error) bool {
|
||||
e, ok := err.(Error)
|
||||
return ok && e.Code == ErrCodeExistPartnerCode
|
||||
}
|
|
@ -1,62 +0,0 @@
|
|||
package shiip
|
||||
|
||||
// Product ...
|
||||
type Product struct {
|
||||
PartnerSKU string `json:"partnerSKU"`
|
||||
UnitCode string `json:"unitCode"`
|
||||
ConditionTypeCode string `json:"conditionTypeCode"`
|
||||
Quantity int64 `json:"quantity"`
|
||||
}
|
||||
|
||||
// Address ...
|
||||
type Address struct {
|
||||
AddressNo string `json:"addressNo"`
|
||||
ProvinceCode string `json:"provinceCode,omitempty"`
|
||||
DistrictCode string `json:"districtCode,omitempty"`
|
||||
WardCode string `json:"wardCode,omitempty"`
|
||||
}
|
||||
|
||||
// OutboundRequestPayload ...
|
||||
type OutboundRequestPayload struct {
|
||||
WarehouseCode string `json:"warehouseCode"`
|
||||
ShippingServiceCode string `json:"shippingServiceCode"`
|
||||
PartnerORCode string `json:"partnerORCode"`
|
||||
PartnerRefId string `json:"partnerRefId"`
|
||||
RefCode string `json:"refCode"`
|
||||
CodAmount float64 `json:"codAmount"`
|
||||
PriorityType int `json:"priorityType"`
|
||||
CustomerName string `json:"customerName"`
|
||||
CustomerPhoneNumber string `json:"customerPhoneNumber"`
|
||||
Type int `json:"type"`
|
||||
ShippingType int `json:"shippingType"`
|
||||
VehicleNumber string `json:"vehicleNumber"`
|
||||
ContainerNumber string `json:"containerNumber"`
|
||||
PackType int `json:"packType"`
|
||||
PackingNote string `json:"packingNote"`
|
||||
CustomLabel bool `json:"customLabel"`
|
||||
BizType int `json:"bizType"`
|
||||
Note string `json:"note"`
|
||||
ShippingAddress Address `json:"shippingAddress"`
|
||||
Products []Product `json:"products"`
|
||||
PartnerCreationTime string `json:"partnerCreationTime"`
|
||||
TPLCode string `json:"tplCode"`
|
||||
TrackingCode string `json:"trackingCode"`
|
||||
}
|
||||
|
||||
// UpdateORLogisticInfoPayload ...
|
||||
type UpdateORLogisticInfoPayload struct {
|
||||
OrID int `json:"orId"`
|
||||
TrackingCode string `json:"trackingCode"`
|
||||
ShippingLabels []Label `json:"shippingLabels"`
|
||||
TPLCode string `json:"tplCode"`
|
||||
}
|
||||
|
||||
type Label struct {
|
||||
Caption string `json:"caption"`
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
||||
type AuthPayload struct {
|
||||
ClientID string `json:"clientId"`
|
||||
ClientSecret string `json:"clientSecret"`
|
||||
}
|
|
@ -1,57 +0,0 @@
|
|||
package shiip
|
||||
|
||||
// OutboundRequestRes ...
|
||||
type OutboundRequestRes struct {
|
||||
OrID int `json:"orId"`
|
||||
OrCode string `json:"orCode"`
|
||||
PartnerORCode string `json:"partnerORCode"`
|
||||
Error *Error `json:"error"`
|
||||
}
|
||||
|
||||
type authRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshExpiresIn int `json:"refresh_expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
NotBeforePolicy int `json:"not-before-policy"`
|
||||
SessionState string `json:"session_state"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
// Webhook ...
|
||||
type Webhook struct {
|
||||
OrId int `json:"orId"`
|
||||
PartnerORCode string `json:"partnerORCode"`
|
||||
ErrorCode string `json:"errorCode"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
Event string `json:"event"`
|
||||
Id string `json:"id"`
|
||||
Timestamp int64 `json:"timestamp"`
|
||||
Note string `json:"note"`
|
||||
OrCode string `json:"orCode"`
|
||||
}
|
||||
|
||||
// OutboundRequestInfo ...
|
||||
type OutboundRequestInfo struct {
|
||||
OrId int `json:"orId"`
|
||||
OrCode string `json:"orCode"`
|
||||
PartnerORCode string `json:"partnerORCode"`
|
||||
OriginalPartnerOrCode string `json:"originalPartnerOrCode"`
|
||||
PartnerRefId string `json:"partnerRefId"`
|
||||
RefCode string `json:"refCode"`
|
||||
WarehouseCode string `json:"warehouseCode"`
|
||||
Status string `json:"status"`
|
||||
Note string `json:"note"`
|
||||
ShippingType int `json:"shippingType"`
|
||||
PriorityType int `json:"priorityType"`
|
||||
PackType int `json:"packType"`
|
||||
BizType int `json:"bizType"`
|
||||
CustomerName string `json:"customerName"`
|
||||
CustomerPhoneNumber string `json:"customerPhoneNumber"`
|
||||
ShippingFullAddress string `json:"shippingFullAddress"`
|
||||
CodAmount float64 `json:"codAmount"`
|
||||
ExpectedDeliveryTime string `json:"expectedDeliveryTime"`
|
||||
CreatedDate string `json:"createdDate"`
|
||||
ErrorMessage string `json:"errorMessage"`
|
||||
}
|
|
@ -1,295 +0,0 @@
|
|||
package shiip
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
"git.selly.red/Selly-Modules/natsio"
|
||||
"git.selly.red/Selly-Modules/natsio/model"
|
||||
"git.selly.red/Selly-Modules/natsio/subject"
|
||||
"github.com/nats-io/nats.go"
|
||||
|
||||
"git.selly.red/Selly-Modules/3pl/util/httputil"
|
||||
"git.selly.red/Selly-Modules/3pl/util/pjson"
|
||||
)
|
||||
|
||||
// Client ...
|
||||
type Client struct {
|
||||
realm string
|
||||
clientID string
|
||||
clientSecret string
|
||||
|
||||
env ENV
|
||||
natsClient natsio.Server
|
||||
|
||||
token string
|
||||
tokenExpireAt time.Time
|
||||
}
|
||||
|
||||
// NewClient ...
|
||||
func NewClient(env ENV, clientID, clientSecret, realm string, natsClient natsio.Server) (*Client, error) {
|
||||
if env != EnvProd && env != EnvStaging {
|
||||
return nil, fmt.Errorf("shiip.NewClient: invalid_env %s", env)
|
||||
}
|
||||
return &Client{
|
||||
realm: realm,
|
||||
clientID: clientID,
|
||||
clientSecret: clientSecret,
|
||||
token: "",
|
||||
env: env,
|
||||
natsClient: natsClient,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateOutboundRequest ...
|
||||
func (c *Client) CreateOutboundRequest(p OutboundRequestPayload) (*OutboundRequestRes, error) {
|
||||
apiURL := c.getBaseURL() + apiPathCreateOutboundRequest
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodPost,
|
||||
Data: pjson.ToJSONString([]OutboundRequestPayload{p}),
|
||||
Header: c.getRequestHeader(),
|
||||
},
|
||||
}
|
||||
msg, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("shiip.Client.CreateOutboundRequest - requestHttpViaNats: %v, %+v\n", err, natsPayload)
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
dataRes []OutboundRequestRes
|
||||
)
|
||||
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
|
||||
return nil, fmt.Errorf("shiip.Client.CreateOutboundRequest: parse_data %v", err)
|
||||
}
|
||||
res := r.Response
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("shiip.Client.CreateOutboundRequest: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
if err = r.ParseResponseData(&errRes); err != nil {
|
||||
return nil, fmt.Errorf("shiip.Client.CreateOutboundRequest: parse_response_err: %v", err)
|
||||
}
|
||||
return nil, errRes
|
||||
}
|
||||
if err = r.ParseResponseData(&dataRes); err != nil {
|
||||
return nil, fmt.Errorf("shiip.Client.CreateOutboundRequest: parse_response_data: %v", err)
|
||||
}
|
||||
if len(dataRes) == 0 {
|
||||
return nil, fmt.Errorf("shiip.Client.CreateOutboundRequest: empty_result")
|
||||
}
|
||||
item := &dataRes[0]
|
||||
e := item.Error
|
||||
if e != nil {
|
||||
return nil, errRes
|
||||
}
|
||||
|
||||
return item, err
|
||||
}
|
||||
|
||||
// UpdateOutboundRequestLogisticInfo ...
|
||||
func (c *Client) UpdateOutboundRequestLogisticInfo(p UpdateORLogisticInfoPayload) error {
|
||||
apiURL := c.getBaseURL() + fmt.Sprintf(apiPathUpdateLogisticInfoOutboundRequest, p.OrID)
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodPost,
|
||||
Header: c.getRequestHeader(),
|
||||
Data: pjson.ToJSONString(p),
|
||||
},
|
||||
}
|
||||
msg, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("shiip.Client.UpdateOutboundRequestLogisticInfo - requestHttpViaNats: %v, %+v\n", err, natsPayload)
|
||||
return err
|
||||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
)
|
||||
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
|
||||
return fmt.Errorf("shiip.Client.UpdateOutboundRequestLogisticInfo: parse_data %v", err)
|
||||
}
|
||||
res := r.Response
|
||||
if res == nil {
|
||||
return fmt.Errorf("shiip.Client.UpdateOutboundRequestLogisticInfo: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
if err = r.ParseResponseData(&errRes); err != nil {
|
||||
return fmt.Errorf("shiip.Client.UpdateOutboundRequestLogisticInfo: parse_response_err: %v", err)
|
||||
}
|
||||
return errRes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetOutboundRequestByID ...
|
||||
func (c *Client) GetOutboundRequestByID(requestID int) (*OutboundRequestInfo, error) {
|
||||
apiURL := c.getBaseURL() + fmt.Sprintf(apiPathGetOutboundRequest, requestID)
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodGet,
|
||||
Header: c.getRequestHeader(),
|
||||
},
|
||||
}
|
||||
msg, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("shiip.Client.GetOutboundRequestByID - requestHttpViaNats: %v, %+v\n", err, natsPayload)
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
outboundRequest OutboundRequestInfo
|
||||
)
|
||||
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
|
||||
return nil, fmt.Errorf("shiip.Client.GetOutboundRequestByID: parse_data %v", err)
|
||||
}
|
||||
res := r.Response
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("shiip.Client.GetOutboundRequestByID: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
if err = r.ParseResponseData(&errRes); err != nil {
|
||||
return nil, fmt.Errorf("shiip.Client.GetOutboundRequestByID: parse_response_err: %v", err)
|
||||
}
|
||||
return nil, errRes
|
||||
}
|
||||
if err = r.ParseResponseData(&outboundRequest); err != nil {
|
||||
return nil, fmt.Errorf("shiip.Client.GetOutboundRequestByID: parse_response_data: %v", err)
|
||||
}
|
||||
return &outboundRequest, nil
|
||||
}
|
||||
|
||||
// CancelOutboundRequest ...
|
||||
func (c *Client) CancelOutboundRequest(requestID int, note string) error {
|
||||
apiURL := c.getBaseURL() + fmt.Sprintf(apiPathCancelOutboundRequest, requestID)
|
||||
data := map[string]string{"note": note}
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodPost,
|
||||
Header: c.getRequestHeader(),
|
||||
Data: pjson.ToJSONString(data),
|
||||
},
|
||||
}
|
||||
msg, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("shiip.Client.CancelOutboundRequest - requestHttpViaNats: %v, %+v\n", err, natsPayload)
|
||||
return err
|
||||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
)
|
||||
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
|
||||
return fmt.Errorf("shiip.Client.CancelOutboundRequest: parse_data %v", err)
|
||||
}
|
||||
res := r.Response
|
||||
if res == nil {
|
||||
return fmt.Errorf("shiip.Client.CancelOutboundRequest: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
if err = r.ParseResponseData(&errRes); err != nil {
|
||||
return fmt.Errorf("shiip.Client.CancelOutboundRequest: parse_response_err: %v", err)
|
||||
}
|
||||
return errRes
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) auth() (*authRes, error) {
|
||||
b := AuthPayload{
|
||||
ClientID: c.clientID,
|
||||
ClientSecret: c.clientSecret,
|
||||
}
|
||||
header := map[string]string{
|
||||
httputil.HeaderKeyContentType: httputil.HeaderValueApplicationJSON,
|
||||
}
|
||||
apiURL := baseURLAuthENVMapping[c.env] + apiPathAuth
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodPost,
|
||||
Data: pjson.ToJSONString(b),
|
||||
Header: header,
|
||||
},
|
||||
}
|
||||
msg, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
log.Printf("shiip.Client.auth - requestHttpViaNats: %v, %+v\n", err, natsPayload)
|
||||
return nil, err
|
||||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
data authRes
|
||||
)
|
||||
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
|
||||
return nil, fmt.Errorf("shiip.Client.auth: parse_data %v", err)
|
||||
}
|
||||
res := r.Response
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("shiip.Client.auth: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
if err = r.ParseResponseData(&errRes); err != nil {
|
||||
return nil, fmt.Errorf("shiip.Client.auth: parse_response_err: %v", err)
|
||||
}
|
||||
return nil, errRes
|
||||
}
|
||||
if err = r.ParseResponseData(&data); err != nil {
|
||||
return nil, fmt.Errorf("shiip.Client.auth: parse_response_data: %v", err)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (c *Client) getRequestHeader() map[string]string {
|
||||
m := map[string]string{
|
||||
httputil.HeaderKeyContentType: httputil.HeaderValueApplicationJSON,
|
||||
}
|
||||
token, err := c.getToken()
|
||||
if err != nil {
|
||||
log.Printf("shiip.Client.getToken: %v\n", err)
|
||||
} else {
|
||||
m["Authorization"] = fmt.Sprintf("Bearer %s", token)
|
||||
}
|
||||
return m
|
||||
}
|
||||
|
||||
func (c *Client) requestHttpViaNats(data model.CommunicationRequestHttp) (*nats.Msg, error) {
|
||||
b := pjson.ToBytes(data)
|
||||
return c.natsClient.Request(subject.Communication.RequestHTTP, b)
|
||||
}
|
||||
|
||||
func (c *Client) getBaseURL() string {
|
||||
return baseURLENVMapping[c.env]
|
||||
}
|
||||
|
||||
func (c *Client) getToken() (string, error) {
|
||||
if c.token != "" || c.tokenExpireAt.After(time.Now()) {
|
||||
return c.token, nil
|
||||
}
|
||||
data, err := c.auth()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c.token = data.AccessToken
|
||||
d := time.Duration(data.ExpiresIn) * time.Second
|
||||
if d.Minutes() > 30 {
|
||||
d -= 30 * time.Minute
|
||||
}
|
||||
c.tokenExpireAt = time.Now().Add(d)
|
||||
return c.token, nil
|
||||
}
|
|
@ -15,7 +15,7 @@ const (
|
|||
|
||||
TPLCodeGHN = "GHN"
|
||||
TPLCodeGHTK = "GHTK"
|
||||
TPLCodeBest = "BEST"
|
||||
TPLCodeBest = "BEX"
|
||||
TPLCodeSnappy = "SPY"
|
||||
TPLCodeViettelPost = "VTP"
|
||||
TPLCodeSellyExpress = "SE"
|
||||
|
@ -30,11 +30,11 @@ const (
|
|||
)
|
||||
|
||||
const (
|
||||
baseURLAuthStaging = "https://auth.stg.tnclog.vn"
|
||||
baseURLStaging = "https://ext-api.stg.tnclog.vn"
|
||||
baseURLAuthStaging = "https://auth.stg.vnfai.com"
|
||||
baseURLStaging = "https://ext.stg.vnfai.com"
|
||||
|
||||
baseURLAuthProd = "https://auth.tnclog.vn"
|
||||
baseURLProd = "https://ext-api.tnclog.vn"
|
||||
baseURLAuthProd = "https://auth.vnfai.com"
|
||||
baseURLProd = "https://ext-api.vnfai.com"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
@ -45,8 +45,14 @@ type OutboundRequestPayload struct {
|
|||
|
||||
// UpdateORLogisticInfoPayload ...
|
||||
type UpdateORLogisticInfoPayload struct {
|
||||
OrID int `json:"orId"`
|
||||
TrackingCode string `json:"trackingCode"`
|
||||
ShippingLabel string `json:"shippingLabel"`
|
||||
SlaShipDate string `json:"slaShipDate"`
|
||||
OrID int `json:"orId"`
|
||||
|
||||
TPLCode string `json:"tplCode"`
|
||||
TrackingCode string `json:"trackingCode"`
|
||||
ShippingLabels []LogisticInfoLabel `json:"shippingLabels"`
|
||||
}
|
||||
|
||||
type LogisticInfoLabel struct {
|
||||
Caption string `json:"caption"`
|
||||
URI string `json:"uri"`
|
||||
}
|
||||
|
|
|
@ -67,7 +67,6 @@ func (c *Client) CreateOutboundRequest(p OutboundRequestPayload) (*OutboundReque
|
|||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
dataRes []OutboundRequestRes
|
||||
)
|
||||
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
|
||||
|
@ -78,6 +77,7 @@ func (c *Client) CreateOutboundRequest(p OutboundRequestPayload) (*OutboundReque
|
|||
return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
var errRes Error
|
||||
if err = r.ParseResponseData(&errRes); err != nil {
|
||||
return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: parse_response_err: %v", err)
|
||||
}
|
||||
|
@ -92,7 +92,7 @@ func (c *Client) CreateOutboundRequest(p OutboundRequestPayload) (*OutboundReque
|
|||
item := &dataRes[0]
|
||||
e := item.Error
|
||||
if e != nil {
|
||||
return nil, errRes
|
||||
return nil, e
|
||||
}
|
||||
|
||||
return item, err
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
package viettelffm
|
||||
|
||||
type AuthRes struct {
|
||||
AccessToken string `json:"access_token"`
|
||||
ExpiresIn int `json:"expires_in"`
|
||||
RefreshExpiresIn int `json:"refresh_expires_in"`
|
||||
RefreshToken string `json:"refresh_token"`
|
||||
TokenType string `json:"token_type"`
|
||||
NotBeforePolicy int `json:"not-before-policy"`
|
||||
SessionState string `json:"session_state"`
|
||||
Scope string `json:"scope"`
|
||||
}
|
||||
|
||||
type UpdateLogisticInfoPayload struct {
|
||||
PrintLabelLink string `json:"print_label_link"`
|
||||
TrackingCode string `json:"tracking_code"`
|
||||
OrCode string `json:"or_code,omitempty"`
|
||||
}
|
||||
|
||||
type CancelORPayload struct {
|
||||
OrID int `json:"or_id"`
|
||||
}
|
||||
|
||||
type ORPayload struct {
|
||||
OrProductLines []ORProductLine `json:"or_product_lines"`
|
||||
AmountPaid float64 `json:"amount_paid"`
|
||||
CodAmount float64 `json:"cod_amount"`
|
||||
CodType string `json:"cod_type"`
|
||||
Note string `json:"note"`
|
||||
OrCode string `json:"or_code"`
|
||||
OrType string `json:"or_type"`
|
||||
ShippingType string `json:"shipping_type"`
|
||||
CustomerName string `json:"customer_name"`
|
||||
CustomerEmail string `json:"customer_email"`
|
||||
PackType string `json:"pack_type"`
|
||||
PriorityType string `json:"priority_type"`
|
||||
WarehouseCode string `json:"warehouse_code"`
|
||||
}
|
||||
|
||||
type ORProductLine struct {
|
||||
Discount float64 `json:"discount"`
|
||||
Price float64 `json:"price"`
|
||||
Product Product `json:"product"`
|
||||
Quantity int `json:"quantity"`
|
||||
SpecifiedProductLine SpecifiedProductLine `json:"specified_product_line"`
|
||||
Unit string `json:"unit"`
|
||||
}
|
||||
|
||||
type SpecifiedProductLine struct {
|
||||
ProductConditionType ProdCondType `json:"product_condition_type"`
|
||||
}
|
||||
|
||||
type Product struct {
|
||||
PartnerSku string `json:"partner_sku"`
|
||||
SKU string `json:"sku"`
|
||||
}
|
||||
|
||||
type ProdCondType struct {
|
||||
Name string `json:"name"`
|
||||
}
|
||||
|
||||
type ORResult struct {
|
||||
OrCode string `json:"or_code"`
|
||||
OrId int `json:"or_id"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
type Webhook struct {
|
||||
Data WebhookData `json:"data"`
|
||||
}
|
||||
|
||||
type WebhookData struct {
|
||||
OrId int `json:"or_id"`
|
||||
OrCode string `json:"or_code"`
|
||||
PartnerOrCode string `json:"partner_or_code"`
|
||||
Status string `json:"status"`
|
||||
TotalPrice float64 `json:"total_price"`
|
||||
ShippingFee int `json:"shipping_fee"`
|
||||
}
|
|
@ -0,0 +1,232 @@
|
|||
package viettelffm
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"time"
|
||||
|
||||
"git.selly.red/Selly-Modules/natsio"
|
||||
"git.selly.red/Selly-Modules/natsio/model"
|
||||
"git.selly.red/Selly-Modules/natsio/subject"
|
||||
|
||||
tpl "git.selly.red/Selly-Modules/3pl"
|
||||
"git.selly.red/Selly-Modules/3pl/util/httputil"
|
||||
"git.selly.red/Selly-Modules/3pl/util/pjson"
|
||||
)
|
||||
|
||||
const (
|
||||
baseURLStag = "https://stg-gw.viettelpost.vn"
|
||||
baseURLAuthStag = "https://stg-keycloak.viettelpost.vn"
|
||||
|
||||
baseURLProd = "https://gw.viettelpost.vn"
|
||||
baseURLAuthProd = "https://dws-sso.viettelpost.vn"
|
||||
|
||||
pathAuth = "/realms/wms/protocol/openid-connect/token"
|
||||
pathUpdateORLogisticInfo = "/wms-core/api/v1/obms/outbound-request/outbound-request-partner/%s"
|
||||
pathCreateOR = "/wms-core/api/v1/obms/outbound-request/outbound-request-partner/hab"
|
||||
pathCancelOR = "/wms-core/api/v1/obms/outbound-request/cancel"
|
||||
|
||||
logTarget = "viettel-ffm"
|
||||
)
|
||||
|
||||
var (
|
||||
baseURLENVMapping = map[tpl.ENV]string{
|
||||
tpl.EnvProd: baseURLProd,
|
||||
tpl.EnvStaging: baseURLStag,
|
||||
}
|
||||
)
|
||||
|
||||
type Client struct {
|
||||
env tpl.ENV
|
||||
username string
|
||||
password string
|
||||
|
||||
natsClient natsio.Server
|
||||
authInfo *AuthRes
|
||||
authTokenExpireTime time.Time
|
||||
}
|
||||
|
||||
// NewClient generate OnPoint client
|
||||
func NewClient(env tpl.ENV, user, pwd string, nc natsio.Server) (*Client, error) {
|
||||
if user == "" || pwd == "" {
|
||||
return nil, errors.New("viettelffm: cannot init with empty api key")
|
||||
}
|
||||
return &Client{
|
||||
env: env,
|
||||
username: user,
|
||||
password: pwd,
|
||||
natsClient: nc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *Client) CreateOR(p ORPayload) (*ORResult, error) {
|
||||
apiURL := c.getBaseURL() + pathCreateOR
|
||||
token, err := c.getToken()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodPost,
|
||||
Data: pjson.ToJSONString(p),
|
||||
Header: map[string]string{
|
||||
httputil.HeaderKeyAuthorization: fmt.Sprintf("Bearer %s", token),
|
||||
httputil.HeaderKeyContentType: httputil.HeaderValueApplicationJSON,
|
||||
},
|
||||
},
|
||||
LogTarget: logTarget,
|
||||
}
|
||||
r, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("viettelffm.Client.CreateOR - requestHttpViaNats %v", err)
|
||||
}
|
||||
|
||||
res := r.Response
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("viettelffm.Client.CreateOR - requestHttpViaNats %s", res.Body)
|
||||
}
|
||||
var data ORResult
|
||||
if err = r.ParseResponseData(&data); err != nil {
|
||||
return nil, fmt.Errorf("viettelffm.Client.CreateOR - requestHttpViaNats parse response %v, %s", err, res.Body)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (c *Client) UpdateORLogisticInfo(p UpdateLogisticInfoPayload) error {
|
||||
apiURL := c.getBaseURL() + fmt.Sprintf(pathUpdateORLogisticInfo, p.OrCode)
|
||||
p.OrCode = ""
|
||||
token, err := c.getToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodPut,
|
||||
Data: pjson.ToJSONString(p),
|
||||
Header: map[string]string{
|
||||
httputil.HeaderKeyAuthorization: fmt.Sprintf("Bearer %s", token),
|
||||
httputil.HeaderKeyContentType: httputil.HeaderValueApplicationJSON,
|
||||
},
|
||||
},
|
||||
LogTarget: logTarget,
|
||||
}
|
||||
r, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := r.Response
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
return fmt.Errorf("viettelffm.Client.UpdateOutboundRequestLogisticInfo - requestHttpViaNats %s", res.Body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *Client) CancelOR(p CancelORPayload) error {
|
||||
apiURL := c.getBaseURL() + pathCancelOR
|
||||
token, err := c.getToken()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: apiURL,
|
||||
Method: http.MethodPost,
|
||||
Data: pjson.ToJSONString(p),
|
||||
Header: map[string]string{
|
||||
httputil.HeaderKeyAuthorization: fmt.Sprintf("Bearer %s", token),
|
||||
httputil.HeaderKeyContentType: httputil.HeaderValueApplicationJSON,
|
||||
},
|
||||
},
|
||||
LogTarget: logTarget,
|
||||
}
|
||||
r, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
res := r.Response
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
return fmt.Errorf("viettelffm.Client.CancelOR - requestHttpViaNats %s", res.Body)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Auth ...
|
||||
func (c *Client) Auth() (*AuthRes, error) {
|
||||
v := url.Values{}
|
||||
v.Set("username", c.username)
|
||||
v.Set("password", c.password)
|
||||
v.Set("grant_type", "password")
|
||||
v.Set("client_id", "wms_account")
|
||||
b := v.Encode()
|
||||
header := map[string]string{
|
||||
httputil.HeaderKeyContentType: httputil.HeaderValueApplicationURLEncoded,
|
||||
}
|
||||
bURL := baseURLAuthStag
|
||||
if c.env == tpl.EnvProd {
|
||||
bURL = baseURLAuthProd
|
||||
}
|
||||
api := bURL + pathAuth
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: api,
|
||||
Method: http.MethodPost,
|
||||
Data: b,
|
||||
Header: header,
|
||||
},
|
||||
LogTarget: logTarget,
|
||||
}
|
||||
r, err := c.requestHttpViaNats(natsPayload)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("viettelffm.Client.Auth - requestHttpViaNats: payload %+v, err %v\n", natsPayload, err)
|
||||
}
|
||||
|
||||
res := r.Response
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
return nil, fmt.Errorf("viettelffm.Client.Auth - requestHttpViaNats %s", res.Body)
|
||||
}
|
||||
var data AuthRes
|
||||
if err = r.ParseResponseData(&data); err != nil {
|
||||
return nil, fmt.Errorf("viettelffm.Client.Auth - requestHttpViaNats parse response %v, %s", err, res.Body)
|
||||
}
|
||||
return &data, nil
|
||||
}
|
||||
|
||||
func (c *Client) getToken() (string, error) {
|
||||
if c.authInfo == nil || time.Now().After(c.authTokenExpireTime) {
|
||||
auth, err := c.Auth()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
c.authInfo = auth
|
||||
c.authTokenExpireTime = time.Now().Add(time.Duration(auth.ExpiresIn) * time.Second).Add(-time.Minute)
|
||||
}
|
||||
return c.authInfo.AccessToken, nil
|
||||
}
|
||||
|
||||
func (c *Client) getBaseURL() string {
|
||||
return baseURLENVMapping[c.env]
|
||||
}
|
||||
|
||||
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("viettelffm.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("viettelffm.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("viettelffm.Client.requestHttpViaNats empty reponse")
|
||||
}
|
||||
return &r, nil
|
||||
}
|
|
@ -1,7 +1,8 @@
|
|||
package httputil
|
||||
|
||||
const (
|
||||
HeaderKeyContentType = "Content-Type"
|
||||
HeaderKeyContentType = "Content-Type"
|
||||
HeaderKeyAuthorization = "Authorization"
|
||||
|
||||
HeaderValueApplicationJSON = "application/json"
|
||||
HeaderValueApplicationURLEncoded = "application/x-www-form-urlencoded"
|
||||
|
|
|
@ -2,14 +2,15 @@ package pjson
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
|
||||
"git.selly.red/Selly-Modules/logger"
|
||||
)
|
||||
|
||||
// ToBytes ...
|
||||
func ToBytes(data interface{}) []byte {
|
||||
b, err := json.Marshal(data)
|
||||
if err != nil {
|
||||
log.Printf("3pl/util/pjson.ToBytes.Marshal: %v\n", err)
|
||||
logger.Error("pjson.ToBytes", logger.LogData{"payload": data})
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue