integrate-onpoint #5

Merged
sinhluu merged 15 commits from integrate-onpoint into master 2022-10-31 09:36:25 +00:00
10 changed files with 437 additions and 1 deletions
Showing only changes of commit 5f58965975 - Show all commits

2
go.mod
View File

@ -4,7 +4,7 @@ go 1.17
require ( require (
github.com/Selly-Modules/logger v0.0.1 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/nats-io/nats.go v1.13.0
github.com/thoas/go-funk v0.9.1 github.com/thoas/go-funk v0.9.1
) )

2
go.sum
View File

@ -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/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 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.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 h1:F4z6KzEeeQIMeLFa97iZU6vupzoecKdU5TX24SNppXI=
github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8= github.com/armon/go-radix v1.0.0/go.mod h1:ufUuZ+zHj4x4TnLV4JWEpy2hxWSpsRywHrMgIH9cCH8=
github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8= github.com/benbjohnson/clock v1.1.0 h1:Q92kusRqC1XV2MjkWETPvjJVqKetz1OzxZB7mHJLju8=

View File

@ -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,
}
)

View File

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

View File

@ -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)
}

View File

@ -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"`
}

View File

@ -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"`
}

View File

@ -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]
}

View File

@ -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"
)

View File

@ -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))
}