feat: refactor ViettelFFM partner API integration
- Add the `const.go` file defining `ENV` type and constants `EnvStaging` and `EnvProd` - Update `go.mod` to use `go 1.20` instead of `go 1.17` - Introduce `model.go` file for ViettelFFM partner API structs - Implement client methods for creating outbound requests and updating logistics information - Implement an authentication method for the client with `AuthRes` struct - Define constants for base URLs and authentication paths in ViettelFFM client - Implement helper methods for getting base URL and making HTTP requests via NATS - Update `util/httputil/const.go` to include `HeaderKeyAuthorization` constant Signed-off-by: Sinh <luuvansinh555@gmail.com>
This commit is contained in:
parent
756740a2d7
commit
1034d2e077
|
@ -0,0 +1,9 @@
|
||||||
|
package tpl
|
||||||
|
|
||||||
|
// ENV ...
|
||||||
|
type ENV string
|
||||||
|
|
||||||
|
const (
|
||||||
|
EnvStaging ENV = "STAGING"
|
||||||
|
EnvProd ENV = "PROD"
|
||||||
|
)
|
4
go.mod
4
go.mod
|
@ -1,10 +1,11 @@
|
||||||
module git.selly.red/Selly-Modules/3pl
|
module git.selly.red/Selly-Modules/3pl
|
||||||
|
|
||||||
go 1.17
|
go 1.20
|
||||||
|
|
||||||
require (
|
require (
|
||||||
git.selly.red/Selly-Modules/logger v0.0.2-0.20221010053254-567df039afdb
|
git.selly.red/Selly-Modules/logger v0.0.2-0.20221010053254-567df039afdb
|
||||||
git.selly.red/Selly-Modules/natsio v1.0.3-0.20231020090841-5edec97ee393
|
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/nats-io/nats.go v1.17.0
|
||||||
github.com/thoas/go-funk v0.9.2
|
github.com/thoas/go-funk v0.9.2
|
||||||
)
|
)
|
||||||
|
@ -14,7 +15,6 @@ require (
|
||||||
github.com/elastic/go-licenser v0.4.1 // indirect
|
github.com/elastic/go-licenser v0.4.1 // indirect
|
||||||
github.com/elastic/go-sysinfo v1.1.1 // indirect
|
github.com/elastic/go-sysinfo v1.1.1 // indirect
|
||||||
github.com/elastic/go-windows v1.0.1 // indirect
|
github.com/elastic/go-windows v1.0.1 // indirect
|
||||||
github.com/go-resty/resty/v2 v2.7.0 // indirect
|
|
||||||
github.com/golang/snappy v0.0.3 // indirect
|
github.com/golang/snappy v0.0.3 // indirect
|
||||||
github.com/jcchavezs/porto v0.4.0 // indirect
|
github.com/jcchavezs/porto v0.4.0 // indirect
|
||||||
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
|
github.com/joeshaw/multierror v0.0.0-20140124173710-69b34d4ec901 // indirect
|
||||||
|
|
4
go.sum
4
go.sum
|
@ -1,9 +1,5 @@
|
||||||
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 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/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=
|
|
||||||
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 h1:43kE03FW3NONfE6hXlghafS1d233dfc7grlFqd+15SA=
|
||||||
git.selly.red/Selly-Modules/natsio v1.0.3-0.20231020090841-5edec97ee393/go.mod h1:KNODhfeBqxRmHHQHHU+p3JfH42t8s5aNxfgr6X8fr6g=
|
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 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||||
|
|
|
@ -0,0 +1,65 @@
|
||||||
|
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"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
Warehouse ORWarehouse `json:"warehouse"`
|
||||||
|
}
|
||||||
|
|
||||||
|
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"`
|
||||||
|
ProductId int `json:"product_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ProdCondType struct {
|
||||||
|
Name string `json:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ORWarehouse struct {
|
||||||
|
WarehouseId int `json:"warehouse_id"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ORResult struct {
|
||||||
|
OrCode string `json:"or_code"`
|
||||||
|
OrId int `json:"or_id"`
|
||||||
|
Status string `json:"status"`
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
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"
|
||||||
|
|
||||||
|
// TODO: update prod url
|
||||||
|
baseURLProd = ""
|
||||||
|
baseURLAuthProd = ""
|
||||||
|
|
||||||
|
pathAuth = "/realms/wms/protocol/openid-connect/token"
|
||||||
|
pathUpdateORLogisticInfo = "/wms-core/api/v1/obms/outbound-request/outbound-request-partner/%s/bill"
|
||||||
|
pathCreateOR = "/wms-core/api/v1/obms/outbound-request/outbound-request-partner"
|
||||||
|
|
||||||
|
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) UpdateOutboundRequestLogisticInfo(p UpdateLogisticInfoPayload) error {
|
||||||
|
apiURL := c.getBaseURL() + pathUpdateORLogisticInfo
|
||||||
|
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.UpdateOutboundRequestLogisticInfo - 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
|
||||||
|
}
|
|
@ -2,6 +2,7 @@ package httputil
|
||||||
|
|
||||||
const (
|
const (
|
||||||
HeaderKeyContentType = "Content-Type"
|
HeaderKeyContentType = "Content-Type"
|
||||||
|
HeaderKeyAuthorization = "Authorization"
|
||||||
|
|
||||||
HeaderValueApplicationJSON = "application/json"
|
HeaderValueApplicationJSON = "application/json"
|
||||||
HeaderValueApplicationURLEncoded = "application/x-www-form-urlencoded"
|
HeaderValueApplicationURLEncoded = "application/x-www-form-urlencoded"
|
||||||
|
|
Loading…
Reference in New Issue