integrate-onpoint #5
2
go.mod
2
go.mod
|
@ -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
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/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=
|
||||||
|
|
|
@ -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