define tnc api

This commit is contained in:
Sinh 2022-08-15 17:28:35 +07:00
parent c0cdae1240
commit 58e830d0a0
7 changed files with 443 additions and 9 deletions

View File

@ -4,14 +4,23 @@ import "github.com/Selly-Modules/tpl/util/pjson"
// NatsResponse ... // NatsResponse ...
type NatsResponse struct { type NatsResponse struct {
Data interface{} `json:"data"` Response *HttpResponse `json:"response"`
Error bool `json:"error"` Error bool `json:"error"`
Message string `json:"message"` Message string `json:"message"`
RequestID string `json:"requestId"` RequestID string `json:"requestId"`
} }
// ParseData ... // ParseResponseData ...
func (r *NatsResponse) ParseData(result interface{}) error { func (r *NatsResponse) ParseResponseData(result interface{}) error {
b := pjson.ToBytes(r.Data) if r.Response == nil {
return nil
}
b := pjson.ToBytes(r.Response.Body)
return pjson.Unmarshal(b, result) return pjson.Unmarshal(b, result)
} }
// HttpResponse ...
type HttpResponse struct {
Body string `json:"body"`
StatusCode int `json:"statusCode"`
}

View File

@ -85,7 +85,7 @@ func (c *Client) CreateOrder(p CreateOrderPayload) (*CommonResponse, error) {
if err = pjson.Unmarshal(msg.Data, &r); err != nil { if err = pjson.Unmarshal(msg.Data, &r); err != nil {
return nil, err return nil, err
} }
err = r.ParseData(&res) err = r.ParseResponseData(&res)
return &res, err return &res, err
} }
@ -114,7 +114,7 @@ func (c *Client) GetOrder(orderCode string) (*CommonResponse, error) {
if err = pjson.Unmarshal(msg.Data, &r); err != nil { if err = pjson.Unmarshal(msg.Data, &r); err != nil {
return nil, err return nil, err
} }
err = r.ParseData(&res) err = r.ParseResponseData(&res)
return &res, err return &res, err
} }

41
partnerapi/tnc/const.go Normal file
View File

@ -0,0 +1,41 @@
package tnc
const (
TimeLayout = "2006-01-02 15:04:05"
apiPathCreateOutboundRequest = "/api/v1/ors"
apiPathGetOutboundRequest = "/api/v1/ors/%d"
apiPathCancelOutboundRequest = "/api/v1/ors/%d/cancel"
apiPathAuth = "/auth/realms/%s/protocol/openid-connect/token"
PriorityUrgent = 3
PriorityHigh = 2
PriorityNormal = 1
TPLCodeGHN = "GHN"
TPLCodeGHTK = "GHTK"
TPLCodeBest = "BEST"
TPLCodeSnappy = "SPY"
TPLCodeViettelPost = "VTP"
TPLCodeSellyExpress = "SE"
TPLCodeJTExpress = "JTE"
)
const (
baseURLAuthStaging = "https://auth.stg.tnclog.vn"
baseURLStaging = "https://ext-api.stg.tnclog.vn"
baseURLAuthProd = "https://auth.tnclog.vn"
baseURLProd = "https://ext-api.tnclog.vn"
)
var (
baseURLENVMapping = map[ENV]string{
EnvProd: baseURLProd,
EnvStaging: baseURLStaging,
}
baseURLAuthENVMapping = map[ENV]string{
EnvProd: baseURLAuthProd,
EnvStaging: baseURLAuthStaging,
}
)

9
partnerapi/tnc/env.go Normal file
View File

@ -0,0 +1,9 @@
package tnc
// ENV ...
type ENV string
const (
EnvStaging ENV = "STAGING"
EnvProd ENV = "PROD"
)

View File

@ -0,0 +1,44 @@
package tnc
// 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"`
DistrictCode string `json:"districtCode"`
WardCode string `json:"wardCode"`
}
// 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"`
}

View File

@ -0,0 +1,63 @@
package tnc
// OutboundRequestRes ...
type OutboundRequestRes struct {
OrID int `json:"orId"`
OrCode string `json:"orCode"`
PartnerORCode string `json:"partnerORCode"`
Error *ErrRes `json:"error"`
}
// ErrRes ...
type ErrRes struct {
Code string `json:"code"`
ErrorMessage string `json:"errorMessage"`
}
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"`
}

268
partnerapi/tnc/tnc.go Normal file
View File

@ -0,0 +1,268 @@
package tnc
import (
"fmt"
"net/http"
"net/url"
"time"
"github.com/Selly-Modules/logger"
"github.com/Selly-Modules/natsio"
"github.com/nats-io/nats.go"
"github.com/Selly-Modules/tpl/constant"
natsiomodel "github.com/Selly-Modules/tpl/model/natsio"
"github.com/Selly-Modules/tpl/util/pjson"
)
// Client ...
type Client struct {
// Auth info
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("tnc.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 := natsiomodel.NatsRequestHTTP{
ResponseImmediately: true,
Payload: natsiomodel.HTTPPayload{
URL: apiURL,
Method: http.MethodGet,
Data: pjson.ToJSONString(p),
Header: c.getRequestHeader(),
},
}
msg, err := c.requestHttpViaNats(natsPayload)
if err != nil {
logger.Error("tnc.Client.CreateOutboundRequest - requestHttpViaNats", logger.LogData{
"err": err.Error(),
"payload": natsPayload,
})
return nil, err
}
var (
r natsiomodel.NatsResponse
errRes ErrRes
dataRes []OutboundRequestRes
)
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: parse_data %v", err)
}
res := r.Response
if res == nil {
return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: empty_response")
}
if res.StatusCode >= http.StatusBadRequest {
if err = r.ParseResponseData(&errRes); err != nil {
return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: parse_response_err: %v", err)
}
return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: failed code %s, message %s", errRes.Code, errRes.ErrorMessage)
}
if err = r.ParseResponseData(&dataRes); err != nil {
return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: parse_response_data: %v", err)
}
if len(dataRes) == 0 {
return nil, fmt.Errorf("tnc.Client.CreateOutboundRequest: empty_result")
}
return &dataRes[0], err
}
// GetOutboundRequestByID ...
func (c *Client) GetOutboundRequestByID(requestID int) (*OutboundRequestInfo, error) {
apiURL := c.getBaseURL() + fmt.Sprintf(apiPathGetOutboundRequest, requestID)
natsPayload := natsiomodel.NatsRequestHTTP{
ResponseImmediately: true,
Payload: natsiomodel.HTTPPayload{
URL: apiURL,
Method: http.MethodGet,
Header: c.getRequestHeader(),
},
}
msg, err := c.requestHttpViaNats(natsPayload)
if err != nil {
logger.Error("tnc.Client.GetOutboundRequestByID - requestHttpViaNats", logger.LogData{
"err": err.Error(),
"payload": natsPayload,
})
return nil, err
}
var (
r natsiomodel.NatsResponse
errRes ErrRes
outboundRequest OutboundRequestInfo
)
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
return nil, fmt.Errorf("tnc.Client.GetOutboundRequestByID: parse_data %v", err)
}
res := r.Response
if res == nil {
return nil, fmt.Errorf("tnc.Client.GetOutboundRequestByID: empty_response")
}
if res.StatusCode >= http.StatusBadRequest {
if err = r.ParseResponseData(&errRes); err != nil {
return nil, fmt.Errorf("tnc.Client.GetOutboundRequestByID: parse_response_err: %v", err)
}
return nil, fmt.Errorf("tnc.Client.GetOutboundRequestByID: failed code %s, message %s", errRes.Code, errRes.ErrorMessage)
}
if err = r.ParseResponseData(outboundRequest); err != nil {
return nil, fmt.Errorf("tnc.Client.GetOutboundRequestByID: parse_response_data: %v", err)
}
return &outboundRequest, nil
}
// CancelOutboundRequest ...
func (c *Client) CancelOutboundRequest(requestID int) error {
apiURL := c.getBaseURL() + fmt.Sprintf(apiPathCancelOutboundRequest, requestID)
natsPayload := natsiomodel.NatsRequestHTTP{
ResponseImmediately: true,
Payload: natsiomodel.HTTPPayload{
URL: apiURL,
Method: http.MethodPost,
Header: c.getRequestHeader(),
},
}
msg, err := c.requestHttpViaNats(natsPayload)
if err != nil {
logger.Error("tnc.Client.CancelOutboundRequest - requestHttpViaNats", logger.LogData{
"err": err.Error(),
"payload": natsPayload,
})
return err
}
var (
r natsiomodel.NatsResponse
errRes ErrRes
)
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
return fmt.Errorf("tnc.Client.CancelOutboundRequest: parse_data %v", err)
}
res := r.Response
if res == nil {
return fmt.Errorf("tnc.Client.CancelOutboundRequest: empty_response")
}
if res.StatusCode >= http.StatusBadRequest {
if err = r.ParseResponseData(&errRes); err != nil {
return fmt.Errorf("tnc.Client.CancelOutboundRequest: parse_response_err: %v", err)
}
return fmt.Errorf("tnc.Client.CancelOutboundRequest: failed code %s, message %s", errRes.Code, errRes.ErrorMessage)
}
return nil
}
func (c *Client) auth() (*authRes, error) {
v := url.Values{}
v.Add("realm", c.realm)
v.Add("grant_type", "client_credentials")
v.Add("client_id", c.clientID)
v.Add("client_secret", c.clientSecret)
body := v.Encode()
header := map[string]string{
"Content-Type": "application/x-www-form-urlencoded",
}
apiURL := baseURLAuthENVMapping[c.env] + fmt.Sprintf(apiPathAuth, c.realm)
natsPayload := natsiomodel.NatsRequestHTTP{
ResponseImmediately: true,
Payload: natsiomodel.HTTPPayload{
URL: apiURL,
Method: http.MethodPost,
Data: body,
Header: header,
},
}
msg, err := c.requestHttpViaNats(natsPayload)
if err != nil {
logger.Error("tnc.Client.auth - requestHttpViaNats", logger.LogData{
"err": err.Error(),
"payload": natsPayload,
})
return nil, err
}
var (
r natsiomodel.NatsResponse
errRes ErrRes
data authRes
)
if err = pjson.Unmarshal(msg.Data, &r); err != nil {
return nil, fmt.Errorf("tnc.Client.auth: parse_data %v", err)
}
res := r.Response
if res == nil {
return nil, fmt.Errorf("tnc.Client.auth: empty_response")
}
if res.StatusCode >= http.StatusBadRequest {
if err = r.ParseResponseData(&errRes); err != nil {
return nil, fmt.Errorf("tnc.Client.auth: parse_response_err: %v", err)
}
return nil, fmt.Errorf("tnc.Client.auth: failed code %s, message %s", errRes.Code, errRes.ErrorMessage)
}
if err = r.ParseResponseData(data); err != nil {
return nil, fmt.Errorf("tnc.Client.auth: parse_response_data: %v", err)
}
return &data, nil
}
func (c *Client) getRequestHeader() map[string]string {
m := map[string]string{
"Content-Type": "application/json",
}
token, err := c.getToken()
if err != nil {
logger.Error("tnc.Client.getToken", logger.LogData{"err": err.Error()})
} else {
m["Authorization"] = fmt.Sprintf("Bearer %s", token)
}
return m
}
func (c *Client) requestHttpViaNats(data natsiomodel.NatsRequestHTTP) (*nats.Msg, error) {
s := constant.NatsCommunicationSubjectRequestHTTP
b := pjson.ToBytes(data)
return c.natsClient.Request(s, 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
}