Integrate onpoint #3
2
go.mod
2
go.mod
|
@ -4,7 +4,7 @@ go 1.17
|
|||
|
||||
require (
|
||||
github.com/Selly-Modules/logger v0.0.1
|
||||
github.com/Selly-Modules/natsio v1.0.2-0.20220826163751-df340fefda0a
|
||||
github.com/Selly-Modules/natsio v1.0.2-0.20220913022818-738089755ab0
|
||||
github.com/nats-io/nats.go v1.13.0
|
||||
github.com/thoas/go-funk v0.9.1
|
||||
)
|
||||
|
|
2
go.sum
2
go.sum
|
@ -2,6 +2,8 @@ github.com/Selly-Modules/logger v0.0.1 h1:dwLLtW53FfVBlklhdtFRB63eP0ofIh0IUQ/Gjg
|
|||
github.com/Selly-Modules/logger v0.0.1/go.mod h1:RWhSQ3F01an8KD00VjzRBZOMcE5eV2Cy0/l4ZkeieyU=
|
||||
github.com/Selly-Modules/natsio v1.0.2-0.20220826163751-df340fefda0a h1:QmJ8iqksbU+1vUa2SijsAMSLnw1C5X4R8PYjuNPNCCE=
|
||||
github.com/Selly-Modules/natsio v1.0.2-0.20220826163751-df340fefda0a/go.mod h1:q9dqmiMyl9MUVYZsvAWDI85083rnLEGAEFfYajLOLUU=
|
||||
github.com/Selly-Modules/natsio v1.0.2-0.20220913022818-738089755ab0 h1:viwbSgCFHHsiWIR2aOr6f0fuKgkWs9DR8A+xQgyoCDs=
|
||||
github.com/Selly-Modules/natsio v1.0.2-0.20220913022818-738089755ab0/go.mod h1:q9dqmiMyl9MUVYZsvAWDI85083rnLEGAEFfYajLOLUU=
|
||||
github.com/armon/go-radix v1.0.0 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
|
||||
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
|
||||
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
package onpoint
|
||||
|
||||
const (
|
||||
baseURLStaging = "https://dev-api.onpoint.vn"
|
||||
|
||||
apiPathCreateOrder = "/v1/orders"
|
||||
apiPathUpdateDelivery = "/v1/orders/delivery/update"
|
||||
apiPathCancelOrder = "/v1/orders/cancel"
|
||||
apiPathGetChannels = "/v1/channels"
|
||||
|
||||
headerXAPIKey = "x-api-key"
|
||||
headerXTimestamp = "x-timestamp"
|
||||
headerXSignature = "x-signature"
|
||||
)
|
||||
|
||||
var (
|
||||
baseURLENVMapping = map[ENV]string{
|
||||
// EnvProd: baseURLProd,
|
||||
EnvStaging: baseURLStaging,
|
||||
}
|
||||
)
|
|
@ -0,0 +1,9 @@
|
|||
package onpoint
|
||||
|
||||
// ENV ...
|
||||
type ENV string
|
||||
|
||||
const (
|
||||
EnvStaging ENV = "STAGING"
|
||||
EnvProd ENV = "PROD"
|
||||
)
|
|
@ -0,0 +1,14 @@
|
|||
package onpoint
|
||||
|
||||
import "fmt"
|
||||
|
||||
// Error ...
|
||||
type Error struct {
|
||||
Message string `json:"message"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// Error ...
|
||||
func (e Error) Error() string {
|
||||
return fmt.Sprintf("onpoint_err: status %s, message: %s", e.Status, e.Message)
|
||||
}
|
|
@ -0,0 +1,83 @@
|
|||
package onpoint
|
||||
|
||||
import "time"
|
||||
|
||||
/*
|
||||
* Request payload
|
||||
*/
|
||||
|
||||
// CreateOrderRequest ...
|
||||
type CreateOrderRequest struct {
|
||||
PartnerOrderCode string `json:"partner_order_code"`
|
||||
OrderDate time.Time `json:"order_date"`
|
||||
ChannelCode string `json:"channel_code"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
DistrictCode string `json:"district_code"`
|
||||
WardCode string `json:"ward_code"`
|
||||
ProvinceCode string `json:"province_code"`
|
||||
Note string `json:"note"`
|
||||
SubtotalPrice int `json:"subtotal_price"`
|
||||
ShippingFee int `json:"shipping_fee"`
|
||||
TotalDiscounts int `json:"total_discounts"`
|
||||
TotalPrice int `json:"total_price"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
DeliveryPlatform string `json:"delivery_platform"`
|
||||
Items []OrderItem `json:"items"`
|
||||
}
|
||||
|
||||
// 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:"partner_sku"`
|
||||
}
|
||||
|
||||
// UpdateOrderDeliveryRequest ...
|
||||
type UpdateOrderDeliveryRequest struct {
|
||||
OrderNo string `json:"order_no"` // required
|
||||
DeliveryPlatform string `json:"delivery_platform"` // required
|
||||
DeliveryTrackingNumber string `json:"delivery_tracking_number"`
|
||||
ShippingLabel string `json:"shipping_label"`
|
||||
}
|
||||
|
||||
// CancelOrderRequest ...
|
||||
type CancelOrderRequest struct {
|
||||
OrderNo string `json:"order_no"`
|
||||
}
|
||||
|
||||
/**
|
||||
* WEBHOOK ONPOINT
|
||||
*/
|
||||
|
||||
// WebhookPayload ...
|
||||
type WebhookPayload struct {
|
||||
Event string `json:"event"`
|
||||
RequestedAt time.Time `json:"requested_at"`
|
||||
Data interface{} `json:"data"`
|
||||
}
|
||||
|
||||
// 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 time.Time `json:"updated_at"`
|
||||
}
|
||||
|
||||
// WebhookDataUpdateOrderStatus ...
|
||||
type WebhookDataUpdateOrderStatus struct {
|
||||
PartnerOrderCode string `json:"partner_order_code"`
|
||||
OrderNo string `json:"order_no"`
|
||||
Status string `json:"status"`
|
||||
DeliveryStatus string `json:"delivery_status"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package onpoint
|
||||
|
||||
import "time"
|
||||
|
||||
// CreateOrderResponse ...
|
||||
type CreateOrderResponse struct {
|
||||
PartnerOrderCode string `json:"partner_order_code"`
|
||||
OrderNo string `json:"order_no"`
|
||||
OrderDate time.Time `json:"order_date"`
|
||||
ChannelCode string `json:"channel_code"`
|
||||
FullName string `json:"full_name"`
|
||||
Email string `json:"email"`
|
||||
Phone string `json:"phone"`
|
||||
Address string `json:"address"`
|
||||
FullAddress string `json:"full_address"`
|
||||
District string `json:"district"`
|
||||
Ward string `json:"ward"`
|
||||
Province string `json:"province"`
|
||||
DistrictCode string `json:"district_code"`
|
||||
WardCode string `json:"ward_code"`
|
||||
ProvinceCode string `json:"province_code"`
|
||||
Note string `json:"note"`
|
||||
SubtotalPrice int `json:"subtotal_price"`
|
||||
ShippingFee int `json:"shipping_fee"`
|
||||
TotalDiscounts int `json:"total_discounts"`
|
||||
TotalPrice int `json:"total_price"`
|
||||
PaymentMethod string `json:"payment_method"`
|
||||
DeliveryPlatform string `json:"delivery_platform"`
|
||||
Status string `json:"status"`
|
||||
UpdatedAt time.Time `json:"updated_at"`
|
||||
InsertedAt time.Time `json:"inserted_at"`
|
||||
Items []OrderItem `json:"items"`
|
||||
}
|
||||
|
||||
// UpdateOrderDeliveryResponse ...
|
||||
type UpdateOrderDeliveryResponse struct {
|
||||
DeliveryPlatform string `json:"delivery_platform"`
|
||||
DeliveryTrackingNumber string `json:"delivery_tracking_number"`
|
||||
ShippingLabel string `json:"shipping_label"`
|
||||
}
|
||||
|
||||
// CancelOrderResponse ...
|
||||
type CancelOrderResponse struct {
|
||||
OrderNo string `json:"order_no"`
|
||||
Status string `json:"status"`
|
||||
}
|
||||
|
||||
// ChannelResponse ...
|
||||
type ChannelResponse struct {
|
||||
Code string `json:"code"`
|
||||
Name string `json:"name"`
|
||||
}
|
|
@ -0,0 +1,220 @@
|
|||
package onpoint
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/Selly-Modules/natsio"
|
||||
"github.com/Selly-Modules/natsio/model"
|
||||
"github.com/Selly-Modules/natsio/subject"
|
||||
|
||||
"github.com/Selly-Modules/3pl/util/pjson"
|
||||
)
|
||||
|
||||
// Client ...
|
||||
type Client struct {
|
||||
env ENV
|
||||
apiKey string
|
||||
secretKey string
|
||||
natsClient natsio.Server
|
||||
}
|
||||
|
||||
// NewClient generate OnPoint client
|
||||
func NewClient(env ENV, apiKey, secretKey string, nc natsio.Server) (*Client, error) {
|
||||
if apiKey == "" {
|
||||
return nil, errors.New("onpoint: cannot init with empty api key")
|
||||
}
|
||||
return &Client{
|
||||
env: env,
|
||||
apiKey: apiKey,
|
||||
secretKey: secretKey,
|
||||
natsClient: nc,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// CreateOrder ...
|
||||
func (c *Client) CreateOrder(p CreateOrderRequest) (*CreateOrderResponse, error) {
|
||||
url := c.getBaseURL() + apiPathCreateOrder
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: url,
|
||||
Method: http.MethodPost,
|
||||
Data: pjson.ToJSONString(p),
|
||||
},
|
||||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
dataRes struct {
|
||||
Data CreateOrderResponse `json:"data"`
|
||||
}
|
||||
)
|
||||
if err := c.requestHttpViaNats(natsPayload, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.CreateOrder: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
if err := r.ParseResponseData(&errRes); err != nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.CreateOrder: parse_response_err: %v", err)
|
||||
}
|
||||
return nil, errRes
|
||||
}
|
||||
if err := r.ParseResponseData(&dataRes); err != nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.CreateOrder: parse_response_data: %v", err)
|
||||
}
|
||||
|
||||
return &dataRes.Data, nil
|
||||
}
|
||||
|
||||
// UpdateDelivery ...
|
||||
func (c *Client) UpdateDelivery(p UpdateOrderDeliveryRequest) (*UpdateOrderDeliveryResponse, error) {
|
||||
url := c.getBaseURL() + apiPathUpdateDelivery
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: url,
|
||||
Method: http.MethodPost,
|
||||
Data: pjson.ToJSONString(p),
|
||||
},
|
||||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
dataRes struct {
|
||||
Data UpdateOrderDeliveryResponse `json:"data"`
|
||||
}
|
||||
)
|
||||
if err := c.requestHttpViaNats(natsPayload, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.UpdateDelivery: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
if err := r.ParseResponseData(&errRes); err != nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.UpdateDelivery: parse_response_err: %v", err)
|
||||
}
|
||||
return nil, errRes
|
||||
}
|
||||
if err := r.ParseResponseData(&dataRes); err != nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.UpdateDelivery: parse_response_data: %v", err)
|
||||
}
|
||||
|
||||
return &dataRes.Data, nil
|
||||
}
|
||||
|
||||
// CancelOrder ...
|
||||
func (c *Client) CancelOrder(p CancelOrderRequest) (*CancelOrderResponse, error) {
|
||||
url := c.getBaseURL() + apiPathCancelOrder
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: url,
|
||||
Method: http.MethodPost,
|
||||
Data: pjson.ToJSONString(p),
|
||||
},
|
||||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
dataRes struct {
|
||||
Data CancelOrderResponse `json:"data"`
|
||||
}
|
||||
)
|
||||
if err := c.requestHttpViaNats(natsPayload, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.CancelOrder: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
if err := r.ParseResponseData(&errRes); err != nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.CancelOrder: parse_response_err: %v", err)
|
||||
}
|
||||
return nil, errRes
|
||||
}
|
||||
if err := r.ParseResponseData(&dataRes); err != nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.CancelOrder: parse_response_data: %v", err)
|
||||
}
|
||||
|
||||
return &dataRes.Data, nil
|
||||
}
|
||||
|
||||
// GetChannels ...
|
||||
func (c *Client) GetChannels() ([]ChannelResponse, error) {
|
||||
url := c.getBaseURL() + apiPathGetChannels
|
||||
natsPayload := model.CommunicationRequestHttp{
|
||||
ResponseImmediately: true,
|
||||
Payload: model.HttpRequest{
|
||||
URL: url,
|
||||
Method: http.MethodGet,
|
||||
},
|
||||
}
|
||||
var (
|
||||
r model.CommunicationHttpResponse
|
||||
errRes Error
|
||||
dataRes struct {
|
||||
Data []ChannelResponse `json:"data"`
|
||||
}
|
||||
)
|
||||
if err := c.requestHttpViaNats(natsPayload, &r); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := r.Response
|
||||
if res == nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.GetChannels: empty_response")
|
||||
}
|
||||
if res.StatusCode >= http.StatusBadRequest {
|
||||
if err := r.ParseResponseData(&errRes); err != nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.GetChannels: parse_response_err: %v", err)
|
||||
}
|
||||
return nil, errRes
|
||||
}
|
||||
if err := r.ParseResponseData(&dataRes); err != nil {
|
||||
return nil, fmt.Errorf("onpoint.Client.GetChannels: parse_response_data: %v", err)
|
||||
}
|
||||
|
||||
return dataRes.Data, 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
|
||||
}
|
||||
now := time.Now().Unix()
|
||||
ts := strconv.FormatInt(now, 10)
|
||||
arr := []string{
|
||||
qs,
|
||||
data.Payload.Data,
|
||||
ts,
|
||||
}
|
||||
s := strings.Join(arr, ".")
|
||||
// sign data
|
||||
sign := hashSHA256AndUppercase(s, c.secretKey)
|
||||
data.Payload.Header = map[string]string{
|
||||
headerXAPIKey: c.apiKey,
|
||||
headerXSignature: sign,
|
||||
headerXTimestamp: ts,
|
||||
}
|
||||
|
||||
return ec.Request(subject.Communication.RequestHTTP, data, res)
|
||||
}
|
||||
|
||||
func (c *Client) getBaseURL() string {
|
||||
return baseURLENVMapping[c.env]
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package onpoint
|
||||
|
||||
const (
|
||||
OrderStatusNew = "new"
|
||||
OrderStatusPendingWarehouse = "pending_warehouse"
|
||||
OrderStatusWhProcessing = "wh_processing"
|
||||
OrderStatusWhCompleted = "wh_completed"
|
||||
OrderStatusDlPending = "dl_pending"
|
||||
OrderStatusDlIntransit = "dl_intransit"
|
||||
OrderStatusDLDelivered = "dl_delivered"
|
||||
OrderStatusDLReturning = "dl_returning"
|
||||
OrderStatusReturned = "returned"
|
||||
OrderStatusPartialCancelled = "partial_cancelled"
|
||||
OrderStatusCancelled = "cancelled"
|
||||
OrderStatusCompleted = "completed"
|
||||
OrderStatusUnknown = "unknown"
|
||||
)
|
|
@ -0,0 +1,18 @@
|
|||
package onpoint
|
||||
|
||||
import (
|
||||
"crypto/hmac"
|
||||
"crypto/sha256"
|
||||
"encoding/hex"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func hashSHA256(data, key string) string {
|
||||
h := hmac.New(sha256.New, []byte(key))
|
||||
h.Write([]byte(data))
|
||||
return hex.EncodeToString(h.Sum(nil))
|
||||
}
|
||||
|
||||
func hashSHA256AndUppercase(data, key string) string {
|
||||
return strings.ToUpper(hashSHA256(data, key))
|
||||
}
|
Loading…
Reference in New Issue