new create admin order,express add company
This commit is contained in:
@@ -0,0 +1,555 @@
|
||||
package transit
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"crypto/hmac"
|
||||
"crypto/md5"
|
||||
"crypto/sha1"
|
||||
"crypto/sha256"
|
||||
"crypto/sha512"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"lone-services/pkg/utils"
|
||||
"lone-services/services/express/internal/dao"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode/utf8"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
// JdClient 顺丰快递客户端
|
||||
type JdClient struct {
|
||||
config dao.ExpressSend
|
||||
accessToken string
|
||||
customerCode string
|
||||
}
|
||||
|
||||
const (
|
||||
JdServicePrecheckOrder = "/ecap/v1/orders/precheck" //预处理创建订单
|
||||
JdServiceOrder = "/ecap/v1/orders/create" //下订单
|
||||
JdServiceOrderConfirm = "/ecap/v1/orders/cancel" //取消订单
|
||||
JdServiceOrderQueryByMailNo = "/ecap/v1/orders/trace/query" //路由查询-通过运单号-通过订单号
|
||||
JdServiceOrderEstimated = "/ecap/v1/orders/info/query" //预计派送时间
|
||||
JdServiceOrderWaybills = "/cloud/print/render" //快递单下载
|
||||
JdServiceBackUrl = "/jd/tracking/subscribe" //回调订阅地址
|
||||
|
||||
JdAlgorithm = "md5-salt" //加密方式
|
||||
JdVersion = "2.0"
|
||||
JdBtoC = 1
|
||||
JdCtoB = 2
|
||||
JdSettleType = 3 //付款方式-月结
|
||||
JdCodeFail = "fail" //失败标识
|
||||
JdMaxOrderNumber = 8 //最多箱子数
|
||||
JdProductCode = "ed-m-0001" //京东标快 支持 0-C2C;1-B2C;2-C2B
|
||||
JdRemarkMaxLength = 200 //最大备注长度
|
||||
JdSheet76x130 = "jdkd76x130"
|
||||
|
||||
// 以下是签收code
|
||||
JdStatusDeliverySign = "10034"
|
||||
JdStatusPartialDelivery = "10042"
|
||||
JdStatusDeliveryBigSign = "300015"
|
||||
|
||||
JdLopDNBackECAP = "Tracking_JD"
|
||||
JdOrderECAP = "ECAP"
|
||||
JdPrintECAP = "jdcloudprint"
|
||||
)
|
||||
|
||||
// JdSingedCodeMap 已签收CODE https://open.sf-express.com/developSupport/734349?activeIndex=589678
|
||||
var JdSingedCodeMap = map[string]bool{
|
||||
JdStatusDeliverySign: true,
|
||||
JdStatusPartialDelivery: true,
|
||||
JdStatusDeliveryBigSign: true,
|
||||
}
|
||||
|
||||
// NewJdClient 初始化顺丰客户端
|
||||
func NewJdClient(info dao.ExpressSend) *JdClient {
|
||||
other := utils.StrToStrArr(info.Other, "|")
|
||||
var accessToken, customerCode string
|
||||
for i := utils.NumberZero; i < len(other); i++ {
|
||||
tmp := strings.Split(other[i], ":")
|
||||
if len(tmp) != utils.NumberTwo {
|
||||
continue
|
||||
}
|
||||
switch tmp[0] {
|
||||
case "token":
|
||||
accessToken = tmp[1]
|
||||
break
|
||||
case "code":
|
||||
customerCode = tmp[1]
|
||||
break
|
||||
}
|
||||
}
|
||||
return &JdClient{info, accessToken, customerCode}
|
||||
}
|
||||
|
||||
func (c *JdClient) Create(order dao.DeliveryOrderPrint) (TransitResult, error) {
|
||||
result, err := c.getCheck(order)
|
||||
if err != nil {
|
||||
return result, err
|
||||
}
|
||||
if result.ErrorCode != utils.StringStatusOk {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
info := c.getCreateData(order, order.ProductItems)
|
||||
//发送请求
|
||||
result, err = c.send(JdServiceOrder, info)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Jd Create Order:", err)
|
||||
return result, err
|
||||
}
|
||||
if result.ErrorCode != utils.StringStatusOk {
|
||||
utils.Logger.Error("Jd Create Order:", err)
|
||||
return result, nil
|
||||
}
|
||||
c.subscribe(result.WaybillNo[utils.NumberZero])
|
||||
result.PostInfo = utils.StructToJson(info)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Query 查询物流
|
||||
func (c *JdClient) Query(waybillCode string) (TransitResult, error) {
|
||||
data := map[string]interface{}{
|
||||
//1:根据顺丰运单号查询,trackingNumber将被当作顺丰运单号处理
|
||||
//2:根据客户订单号查询,trackingNumber将被当作客户订单号处理
|
||||
//"trackingType": JdTrackingType,
|
||||
"waybillCode": waybillCode,
|
||||
"orderOrigin": JdBtoC,
|
||||
}
|
||||
//发送请求
|
||||
result, err := c.send(JdServiceOrderQueryByMailNo, data)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Jd Query Order:", err)
|
||||
return result, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DownSheet 下载面单
|
||||
func (c *JdClient) DownSheet(orders []string, info dao.DeliveryOrderPrint) (TransitResult, error) {
|
||||
data := map[string]interface{}{
|
||||
"customerCode": c.customerCode,
|
||||
"templateCode": JdSheet76x130,
|
||||
"operator": info.AdminName, //自定义操作人
|
||||
"taskId": strconv.FormatInt(time.Now().UnixMilli(), 10), //自定义任务ID
|
||||
"printData": map[string]interface{}{
|
||||
"orderNumber": info.OrderSn, //订单号
|
||||
"carrierCode": "JD",
|
||||
"billCodeValue": orders[0],
|
||||
"billCodeType": "waybillCode",
|
||||
"customerPrintData": map[string]interface{}{
|
||||
"productCode": JdProductCode,
|
||||
},
|
||||
},
|
||||
"outputConfig": map[string]interface{}{
|
||||
"outputType": "1", //文件
|
||||
"dataFormat": "1", //当fileFormat=1时,枚举值填1(1代表URL)
|
||||
"fileFormat": "1", //PDF
|
||||
},
|
||||
}
|
||||
//生成需要提交的数据
|
||||
//发送请求
|
||||
result, err := c.sendDown(JdServiceOrderWaybills, data, orders[0])
|
||||
if err != nil {
|
||||
utils.Logger.Error("Jd Create Order:", err)
|
||||
return result, err
|
||||
}
|
||||
result.PostInfo = utils.StructToJson(data)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Estimated 获取预计时间
|
||||
func (c *JdClient) Estimated(orderId, card string) (TransitResult, error) {
|
||||
data := map[string]interface{}{
|
||||
"customerCode": c.customerCode,
|
||||
"deliveryId": orderId,
|
||||
}
|
||||
utils.Logger.Error(data)
|
||||
//发送请求
|
||||
result, err := c.send(JdServiceOrderEstimated, data)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Jd estimated Order:", err)
|
||||
return result, err
|
||||
}
|
||||
utils.Logger.Error(result)
|
||||
result.PostInfo = utils.StructToJson(data)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Cancel 取消订单
|
||||
func (c *JdClient) Cancel(waybillCode string) (TransitResult, error) {
|
||||
data := map[string]interface{}{
|
||||
"waybillCode": waybillCode,
|
||||
"orderOrigin": JdBtoC,
|
||||
"cancelReason": "快递数据同步失败",
|
||||
"cancelReasonCode": "1",
|
||||
}
|
||||
//发送请求
|
||||
result, err := c.send(JdServiceOrderConfirm, data)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Jd cancel Order:", err)
|
||||
return result, err
|
||||
}
|
||||
result.PostInfo = utils.StructToJson(data)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Edit 修改订单
|
||||
func (c *JdClient) Edit(waybillCode string) (TransitResult, error) {
|
||||
data := map[string]interface{}{
|
||||
"waybillCode": waybillCode,
|
||||
"orderOrigin": JdBtoC,
|
||||
"cancelReason": "快递数据同步失败",
|
||||
"cancelReasonCode": "1",
|
||||
}
|
||||
//发送请求
|
||||
result, err := c.send(JdServiceOrderConfirm, data)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Jd cancel Order:", err)
|
||||
return result, err
|
||||
}
|
||||
result.PostInfo = utils.StructToJson(data)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *JdClient) sendPost(apiUri, lop string, postData map[string]interface{}) (string, error) {
|
||||
client := http.Client{}
|
||||
baseUri := c.config.AppUrl
|
||||
|
||||
bodyArr := []map[string]interface{}{
|
||||
postData,
|
||||
}
|
||||
body := utils.StructToJson(bodyArr)
|
||||
timestamp := time.Now().Format(time.DateTime)
|
||||
content := strings.Join([]string{
|
||||
c.config.AppSecretKey,
|
||||
"access_token", c.accessToken,
|
||||
"app_key", c.config.AppCode,
|
||||
"method", apiUri,
|
||||
"param_json", body,
|
||||
"timestamp", timestamp,
|
||||
"v", JdVersion,
|
||||
c.config.AppSecretKey,
|
||||
}, "")
|
||||
sign, err := c.sign(JdAlgorithm, []byte(content), c.config.AppSecretKey)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Jd:", err)
|
||||
return "", err
|
||||
}
|
||||
postUrl := baseUri + apiUri
|
||||
httpRequest, err := http.NewRequest("POST", postUrl, bytes.NewReader([]byte(body)))
|
||||
if err != nil {
|
||||
utils.Logger.Error("Jd:", err)
|
||||
return "", err
|
||||
}
|
||||
query := httpRequest.URL.Query()
|
||||
query.Add("LOP-DN", lop)
|
||||
query.Add("access_token", c.accessToken)
|
||||
query.Add("app_key", c.config.AppCode)
|
||||
query.Add("timestamp", timestamp)
|
||||
query.Add("v", JdVersion)
|
||||
query.Add("sign", sign)
|
||||
query.Add("algorithm", JdAlgorithm)
|
||||
httpRequest.URL.RawQuery = query.Encode()
|
||||
_, offset := time.Now().Zone()
|
||||
// lop-tz代表时区,为接口调用当地的时区;删去后默认为东八区
|
||||
httpRequest.Header.Add("lop-tz", fmt.Sprintf("%d", offset/3600))
|
||||
// 用于开放平台识别客户调用API方式,客户无需修改
|
||||
httpRequest.Header.Add("User-Agent", "lop-http/go")
|
||||
httpRequest.Header.Add("content-type", "application/json;charset=utf-8")
|
||||
httpResponse, err := client.Do(httpRequest)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Jd:", err)
|
||||
return "", err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
utils.Logger.Error(err)
|
||||
}
|
||||
}(httpResponse.Body)
|
||||
|
||||
b, err := io.ReadAll(httpResponse.Body)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Jd:", err)
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(b), nil
|
||||
}
|
||||
|
||||
func (c *JdClient) sign(algorithm string, data []byte, secret string) (string, error) {
|
||||
if algorithm == "md5-salt" {
|
||||
h := md5.New()
|
||||
if _, err := h.Write(data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return fmt.Sprintf("%x", h.Sum(nil)), nil
|
||||
} else if algorithm == "HMacMD5" {
|
||||
h := hmac.New(md5.New, []byte(secret))
|
||||
if _, err := h.Write(data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
|
||||
} else if algorithm == "HMacSHA1" {
|
||||
h := hmac.New(sha1.New, []byte(secret))
|
||||
if _, err := h.Write(data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
|
||||
} else if algorithm == "HMacSHA256" {
|
||||
h := hmac.New(sha256.New, []byte(secret))
|
||||
if _, err := h.Write(data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
|
||||
} else if algorithm == "HMacSHA512" {
|
||||
h := hmac.New(sha512.New, []byte(secret))
|
||||
if _, err := h.Write(data); err != nil {
|
||||
return "", err
|
||||
}
|
||||
return base64.StdEncoding.EncodeToString(h.Sum(nil)), nil
|
||||
}
|
||||
return "", fmt.Errorf("algorithm %s not supported yet", algorithm)
|
||||
}
|
||||
|
||||
func (c *JdClient) getCreateData(order dao.DeliveryOrderPrint, products []dao.DeliveryOrderProduct) map[string]interface{} {
|
||||
var weight, length, width, hight, volume float32
|
||||
var remark []string
|
||||
|
||||
for _, item := range products {
|
||||
weight += item.ProductWeight * float32(item.ProductNumber)
|
||||
length += item.ProductLength * float32(item.ProductNumber)
|
||||
width += item.ProductWidth * float32(item.ProductNumber)
|
||||
hight += item.ProductHeight * float32(item.ProductNumber)
|
||||
volume += item.ProductCubage * float32(item.ProductNumber)
|
||||
remark = append(remark, fmt.Sprintf("%s×%d", item.ProductName, item.ProductNumber))
|
||||
}
|
||||
|
||||
remarkStr := strings.Join(remark, ",")
|
||||
if utf8.RuneCountInString(remarkStr) > JdRemarkMaxLength {
|
||||
remarkStr = remarkStr[:JdRemarkMaxLength]
|
||||
}
|
||||
|
||||
name := utils.GetConfigString("transit.name")
|
||||
if name == "" {
|
||||
name = "化妆品"
|
||||
}
|
||||
|
||||
// 生成顺丰标准请求参数
|
||||
data := map[string]interface{}{
|
||||
"orderId": order.OrderSn, //订单号
|
||||
"senderContact": map[string]interface{}{ //发货人信息
|
||||
"name": c.config.Contact,
|
||||
"mobile": c.config.Mobile,
|
||||
"fullAddress": c.config.Address,
|
||||
},
|
||||
"receiverContact": map[string]interface{}{ //收货人信息
|
||||
"name": order.Addressee,
|
||||
"mobile": order.Mobile,
|
||||
"fullAddress": order.Address,
|
||||
},
|
||||
"orderOrigin": JdBtoC,
|
||||
"customerCode": c.customerCode, //
|
||||
"productsReq": map[string]interface{}{
|
||||
"productCode": JdProductCode,
|
||||
},
|
||||
"settleType": JdSettleType,
|
||||
"cargoes": []map[string]interface{}{
|
||||
{
|
||||
"name": name,
|
||||
"quantity": order.Number,
|
||||
"weight": weight,
|
||||
"length": length,
|
||||
"width": width,
|
||||
"hight": hight,
|
||||
"volume": volume,
|
||||
},
|
||||
},
|
||||
"extendProps": map[string]interface{}{
|
||||
"autoSubscribe": "1", //1为订阅推送,0为不订阅推送
|
||||
},
|
||||
"remark": remarkStr,
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (c *JdClient) getCheck(order dao.DeliveryOrderPrint) (TransitResult, error) {
|
||||
//生成map对象
|
||||
info := c.getCheckCreateData(order, utils.NumberOne, order.ProductItems[:1])
|
||||
//发送请求
|
||||
result, err := c.send(JdServicePrecheckOrder, info)
|
||||
result.PostInfo = utils.StructToJson(info)
|
||||
return result, err
|
||||
}
|
||||
|
||||
func (c *JdClient) getCheckCreateData(order dao.DeliveryOrderPrint, number int, products []dao.DeliveryOrderProduct) map[string]interface{} {
|
||||
var weight, length, width, hight, volume float32
|
||||
for _, item := range products {
|
||||
weight += item.ProductWeight
|
||||
length += item.ProductLength
|
||||
width += item.ProductWidth
|
||||
hight += item.ProductHeight
|
||||
volume += item.ProductCubage
|
||||
}
|
||||
|
||||
name := utils.GetConfigString("transit.name")
|
||||
if name == "" {
|
||||
name = "化妆品"
|
||||
}
|
||||
|
||||
// 生成顺丰标准请求参数
|
||||
data := map[string]interface{}{
|
||||
"senderContact": map[string]interface{}{
|
||||
"name": c.config.Contact,
|
||||
"mobile": c.config.Mobile,
|
||||
"fullAddress": c.config.Address,
|
||||
},
|
||||
"receiverContact": map[string]interface{}{
|
||||
"name": order.Addressee,
|
||||
"mobile": order.Mobile,
|
||||
"fullAddress": order.Address,
|
||||
},
|
||||
"orderOrigin": JdBtoC,
|
||||
"customerCode": c.customerCode, // 你的订单号
|
||||
"settleType": JdSettleType,
|
||||
"cargoes": []map[string]interface{}{
|
||||
{
|
||||
"name": name,
|
||||
"quantity": number,
|
||||
"weight": weight,
|
||||
"length": length,
|
||||
"width": width,
|
||||
"hight": hight,
|
||||
"volume": volume,
|
||||
},
|
||||
},
|
||||
}
|
||||
return data
|
||||
}
|
||||
|
||||
func (c *JdClient) send(uri string, postData map[string]interface{}) (TransitResult, error) {
|
||||
res := TransitResult{}
|
||||
// 发送请求
|
||||
resultStr, err := c.sendPost(uri, JdOrderECAP, postData)
|
||||
if err != nil {
|
||||
utils.Logger.Error(err)
|
||||
return res, err
|
||||
}
|
||||
var result map[string]interface{}
|
||||
if err = jsoniter.Unmarshal([]byte(resultStr), &result); err != nil {
|
||||
utils.Logger.Error(err)
|
||||
return res, err
|
||||
}
|
||||
res.RequestInfo = resultStr
|
||||
res.ErrorCode = JdCodeFail
|
||||
if _, ok := result["code"]; ok {
|
||||
if result["success"].(bool) {
|
||||
res.ErrorCode = utils.StringStatusOk
|
||||
}
|
||||
|
||||
res.ApiErrorMsg = utils.GetStringByMap(result, "subMsg")
|
||||
if _, ok := postData["requestId"]; ok {
|
||||
res.RequestID = postData["requestId"].(string)
|
||||
}
|
||||
|
||||
// 成功 → 提取运单号
|
||||
if res.ErrorCode == utils.StringStatusOk {
|
||||
res.WaybillNo = append(res.WaybillNo, utils.GetStringByMap(result["data"].(map[string]interface{}), "waybillCode"))
|
||||
}
|
||||
} else if _, ok := result["statusCode"]; ok {
|
||||
if result["statusCode"].(float64) == utils.NumberZero {
|
||||
res.ErrorCode = utils.StringStatusOk
|
||||
}
|
||||
res.ApiErrorMsg = utils.GetStringByMap(result, "statusMessage")
|
||||
}
|
||||
if msgData, ok := result["data"].(map[string]interface{}); ok {
|
||||
res.RequestQuery = msgData
|
||||
}
|
||||
utils.Logger.Error(res)
|
||||
utils.Logger.Error(result)
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *JdClient) subscribe(referenceNumber string) bool {
|
||||
data := map[string]interface{}{
|
||||
"referenceNumber": referenceNumber,
|
||||
"referenceType": "20000",
|
||||
}
|
||||
res := TransitResult{}
|
||||
// 发送请求
|
||||
resultStr, err := c.sendPost(JdServiceBackUrl, JdLopDNBackECAP, data)
|
||||
utils.Logger.Error(data)
|
||||
if err != nil {
|
||||
utils.Logger.Error(err)
|
||||
return false
|
||||
}
|
||||
var result map[string]interface{}
|
||||
if err = jsoniter.Unmarshal([]byte(resultStr), &result); err != nil {
|
||||
utils.Logger.Error(err)
|
||||
return false
|
||||
}
|
||||
res.RequestInfo = resultStr
|
||||
res.ErrorCode = JdCodeFail
|
||||
if _, ok := result["code"]; ok {
|
||||
if result["code"].(float64) == 1000 {
|
||||
res.ErrorCode = utils.StringStatusOk
|
||||
} else {
|
||||
utils.Logger.Error(result)
|
||||
}
|
||||
|
||||
res.ApiErrorMsg = utils.GetStringByMap(result, "msg")
|
||||
}
|
||||
utils.Logger.Error(resultStr)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (c *JdClient) sendDown(uri string, postData map[string]interface{}, billCodeValue string) (TransitResult, error) {
|
||||
res := TransitResult{}
|
||||
// 发送请求
|
||||
resultStr, err := c.sendPost(uri, JdPrintECAP, postData)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
var result map[string]interface{}
|
||||
if err = jsoniter.Unmarshal([]byte(resultStr), &result); err != nil {
|
||||
return res, err
|
||||
}
|
||||
res.RequestInfo = resultStr
|
||||
res.ErrorCode = JdCodeFail
|
||||
if _, ok := result["code"]; ok {
|
||||
if result["code"].(float64) == 1000 {
|
||||
res.ErrorCode = utils.StringStatusOk
|
||||
}
|
||||
|
||||
res.ApiErrorMsg = utils.GetStringByMap(result, "msg")
|
||||
|
||||
// 成功 → 获取内容
|
||||
if res.ErrorCode == utils.StringStatusOk {
|
||||
resultArr, ok := result["result"].([]interface{})
|
||||
if ok && len(resultArr) > utils.NumberZero {
|
||||
firstItem, ok := resultArr[utils.NumberZero].(map[string]interface{})
|
||||
if ok {
|
||||
// 3. 从第一条数据里取出 url
|
||||
url := utils.GetStringByMap(firstItem, "url")
|
||||
res.Files = append(res.Files, TransitFiles{
|
||||
Url: url,
|
||||
WaybillNo: billCodeValue,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
if msgData, ok := result["data"].(map[string]interface{}); ok {
|
||||
res.RequestQuery = msgData
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Reference in New Issue
Block a user