new create admin order,express add company
This commit is contained in:
@@ -0,0 +1,459 @@
|
||||
package transit
|
||||
|
||||
import (
|
||||
"crypto/md5"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"io"
|
||||
"lone-services/pkg/utils"
|
||||
"lone-services/services/express/internal/dao"
|
||||
"math/rand"
|
||||
"net/http"
|
||||
"net/url"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
jsoniter "github.com/json-iterator/go"
|
||||
)
|
||||
|
||||
// SfClient 顺丰快递客户端
|
||||
type SfClient struct {
|
||||
config dao.ExpressSend
|
||||
}
|
||||
|
||||
const (
|
||||
SfServiceOrder = "EXP_RECE_CREATE_ORDER" //下订单
|
||||
SfServiceOrderConfirm = "EXP_RECE_UPDATE_ORDER" //取消订单
|
||||
SfServiceOrderQueryByMailNo = "EXP_RECE_SEARCH_ROUTES" //路由查询-通过运单号-通过订单号
|
||||
SfServiceOrderEstimated = "EXP_RECE_SEARCH_PROMITM" //预计派送时间
|
||||
SfServiceOrderWaybills = "COM_RECE_CLOUD_PRINT_WAYBILLS" //快递单下载
|
||||
|
||||
SfContactTypeA = 1 //发货方
|
||||
SfContactTypeB = 2 //收货方
|
||||
SfCN = "CN"
|
||||
SfLanguage = "zh-CN"
|
||||
SfWaybillTypeMaster = 1 //母订单
|
||||
SfWaybillTypeSublevel = 2 //子订单
|
||||
SfTrackingType = 2 //顾客订单号
|
||||
|
||||
SfServiceOrderConfirmKey = 2 //取消订单
|
||||
SfServiceCheckTypeCard = 2 //月结卡
|
||||
|
||||
// 以下是签收code
|
||||
sfStatusThirdPartySign = "980"
|
||||
sfStatusArriveHoldLost = "8000"
|
||||
sfStatusExternalSign = "935"
|
||||
sfStatusMultiPieceTakePhoto = "701"
|
||||
sfStatusPartnerDelivered = "658"
|
||||
sfStatusCustomerPickUp = "128"
|
||||
sfStatusSigned = "80"
|
||||
)
|
||||
|
||||
// SfSingedCodeMap 已签收CODE https://open.sf-express.com/developSupport/734349?activeIndex=589678
|
||||
var SfSingedCodeMap = map[string]bool{
|
||||
sfStatusThirdPartySign: true,
|
||||
sfStatusArriveHoldLost: true,
|
||||
sfStatusExternalSign: true,
|
||||
sfStatusMultiPieceTakePhoto: true,
|
||||
sfStatusPartnerDelivered: true,
|
||||
sfStatusCustomerPickUp: true,
|
||||
sfStatusSigned: true,
|
||||
}
|
||||
|
||||
// NewSfClient 初始化顺丰客户端
|
||||
func NewSfClient(info dao.ExpressSend) *SfClient {
|
||||
return &SfClient{info}
|
||||
}
|
||||
|
||||
func (c *SfClient) Create(order dao.DeliveryOrderPrint) (TransitResult, error) {
|
||||
//生成map对象
|
||||
info := c.getCreateData(order)
|
||||
//生成需要提交的数据
|
||||
postData := c.getPostData(SfServiceOrder, info)
|
||||
//发送请求
|
||||
result, err := c.send(postData)
|
||||
if err != nil {
|
||||
utils.Logger.Error("SF Create Order:", err)
|
||||
return result, nil
|
||||
}
|
||||
result.PostInfo = info
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Query 查询物流
|
||||
func (c *SfClient) Query(orderId string) (TransitResult, error) {
|
||||
data := map[string]interface{}{
|
||||
//1:根据顺丰运单号查询,trackingNumber将被当作顺丰运单号处理
|
||||
//2:根据客户订单号查询,trackingNumber将被当作客户订单号处理
|
||||
"trackingType": SfTrackingType,
|
||||
"trackingNumber": orderId,
|
||||
}
|
||||
bytes, _ := jsoniter.Marshal(data)
|
||||
//生成需要提交的数据
|
||||
postData := c.getPostData(SfServiceOrderQueryByMailNo, string(bytes))
|
||||
//发送请求
|
||||
result, err := c.send(postData)
|
||||
if err != nil {
|
||||
utils.Logger.Error("SF Query Order:", err)
|
||||
return result, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DownSheet 下载面单
|
||||
func (c *SfClient) DownSheet(orders []string, info dao.DeliveryOrderPrint) (TransitResult, error) {
|
||||
var documents []map[string]interface{}
|
||||
sum := len(orders)
|
||||
for key, order := range orders {
|
||||
tmp := make(map[string]interface{})
|
||||
tmp["masterWaybillNo"] = order
|
||||
if sum > 1 {
|
||||
tmp["branchWaybillNo"] = order
|
||||
tmp["sum"] = sum
|
||||
tmp["seq"] = key + 1
|
||||
}
|
||||
documents = append(documents, tmp)
|
||||
if utils.GetConfigBool("transit.sf_test") {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
data := map[string]interface{}{
|
||||
"templateCode": c.config.PdfCode,
|
||||
"documents": documents,
|
||||
"version": "2.0",
|
||||
"sync": true,
|
||||
}
|
||||
bytes, _ := jsoniter.Marshal(data)
|
||||
//生成需要提交的数据
|
||||
postData := c.getPostData(SfServiceOrderWaybills, string(bytes))
|
||||
//发送请求
|
||||
result, err := c.sendDown(postData)
|
||||
if err != nil {
|
||||
utils.Logger.Error("SF Create Order:", err)
|
||||
return result, err
|
||||
}
|
||||
result.PostInfo = string(bytes)
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Estimated 获取预计时间
|
||||
func (c *SfClient) Estimated(orderId, card string) (TransitResult, error) {
|
||||
data := map[string]interface{}{
|
||||
"checkType": SfServiceCheckTypeCard,
|
||||
"searchNo": orderId,
|
||||
"checkNos": []string{card},
|
||||
}
|
||||
bytes, _ := jsoniter.Marshal(data)
|
||||
//生成需要提交的数据
|
||||
postData := c.getPostData(SfServiceOrderEstimated, string(bytes))
|
||||
//发送请求
|
||||
result, err := c.send(postData)
|
||||
if err != nil {
|
||||
utils.Logger.Error("SF estimated Order:", err)
|
||||
return result, err
|
||||
}
|
||||
result.PostInfo = string(bytes)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// Cancel 取消订单
|
||||
func (c *SfClient) Cancel(orderId string) (TransitResult, error) {
|
||||
data := map[string]interface{}{
|
||||
"dealType": SfServiceOrderConfirmKey,
|
||||
"orderId": orderId,
|
||||
}
|
||||
bytes, _ := jsoniter.Marshal(data)
|
||||
//生成需要提交的数据
|
||||
postData := c.getPostData(SfServiceOrderConfirm, string(bytes))
|
||||
//发送请求
|
||||
result, err := c.send(postData)
|
||||
if err != nil {
|
||||
utils.Logger.Error("SF cancel Order:", err)
|
||||
return result, err
|
||||
}
|
||||
result.PostInfo = string(bytes)
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (c *SfClient) createUUID() string {
|
||||
r := rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
uniq := fmt.Sprintf("%d%d", r.Int(), time.Now().UnixNano())
|
||||
chars := fmt.Sprintf("%x", md5.Sum([]byte(uniq)))
|
||||
|
||||
// 格式:8-4-4-4-12
|
||||
return fmt.Sprintf("%s-%s-%s-%s-%s",
|
||||
chars[0:8],
|
||||
chars[8:12],
|
||||
chars[12:16],
|
||||
chars[16:20],
|
||||
chars[20:32],
|
||||
)
|
||||
}
|
||||
|
||||
func (c *SfClient) sendPost(apiUrl string, postData map[string]interface{}) (string, error) {
|
||||
values := make(url.Values)
|
||||
for k, v := range postData {
|
||||
values.Set(k, fmt.Sprintf("%v", v))
|
||||
}
|
||||
|
||||
client := &http.Client{Timeout: time.Duration(15*60) * time.Second}
|
||||
req, _ := http.NewRequest("POST", apiUrl, strings.NewReader(values.Encode()))
|
||||
req.Header.Set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
utils.Logger.Error("Sf:", err)
|
||||
return "", err
|
||||
}
|
||||
defer func(Body io.ReadCloser) {
|
||||
err := Body.Close()
|
||||
if err != nil {
|
||||
utils.Logger.Error(err)
|
||||
}
|
||||
}(resp.Body)
|
||||
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
return string(body), nil
|
||||
}
|
||||
|
||||
func (c *SfClient) getCreateData(order dao.DeliveryOrderPrint) string {
|
||||
name := utils.GetConfigString("transit.name")
|
||||
if name == "" {
|
||||
name = "化妆品"
|
||||
}
|
||||
// 生成顺丰标准请求参数
|
||||
data := map[string]interface{}{
|
||||
"cargoDesc": name,
|
||||
"expressTypeId": utils.NumberTwo, //顺丰顺丰标快
|
||||
"monthlyCard": c.config.CardNumber, // 你的月结卡号
|
||||
"orderId": order.OrderSn, // 你的订单号
|
||||
"language": SfLanguage,
|
||||
"parcelQty": order.Number,
|
||||
"isDocall": utils.GetConfigInt("transit.sf_call_type"),
|
||||
"isReturnRoutelabel": utils.NumberOne, //是否返回路由标签: 默认1, 1:返回路由标签, 0:不返回;除部分特殊用户外,其余用户都默认返回
|
||||
"payMethod": utils.NumberOne, //付款方式,支持以下值: 1:寄方付 2:收方付 3:第三方付
|
||||
"waybillNoInfoList": []map[string]interface{}{},
|
||||
"cargoDetails": []map[string]interface{}{},
|
||||
// 寄件 + 收件
|
||||
"contactInfoList": []map[string]interface{}{
|
||||
// 寄件方 contactType=1
|
||||
{
|
||||
"country": SfCN,
|
||||
"company": c.config.CompanyName,
|
||||
"province": c.config.Province,
|
||||
"city": c.config.City,
|
||||
"county": c.config.County,
|
||||
"address": c.config.Address,
|
||||
"contact": c.config.Contact,
|
||||
"mobile": c.config.Mobile,
|
||||
"contactType": SfContactTypeA,
|
||||
},
|
||||
// 收件方 contactType=2
|
||||
{
|
||||
"country": SfCN,
|
||||
"province": order.Province,
|
||||
"city": order.City,
|
||||
"county": order.County,
|
||||
"address": order.Address,
|
||||
"contact": order.Addressee,
|
||||
"mobile": order.Mobile,
|
||||
"contactType": SfContactTypeB,
|
||||
},
|
||||
},
|
||||
"totalWeight": order.Weight, // 用你的真实重量
|
||||
}
|
||||
var nameInfo []string
|
||||
if order.Number > utils.NumberZero {
|
||||
num := utils.NumberZero
|
||||
for _, item := range order.ProductItems {
|
||||
nameInfo = append(nameInfo, fmt.Sprintf("%s*%d", item.ProductName, item.ProductNumber))
|
||||
for i := utils.NumberZero; i < item.ProductNumber; i++ {
|
||||
Type := SfWaybillTypeMaster
|
||||
if num > utils.NumberZero {
|
||||
Type = SfWaybillTypeSublevel
|
||||
}
|
||||
info := make(map[string]interface{})
|
||||
info["waybillType"] = Type
|
||||
if utils.GetConfigBool("transit.sf_test") {
|
||||
info["waybillNo"] = "SF3191177193750"
|
||||
}
|
||||
info["boxNo"] = "Box" + fmt.Sprintf("%03d", num+1)
|
||||
info["width"] = item.ProductWidth
|
||||
info["height"] = item.ProductHeight
|
||||
info["weight"] = item.ProductWeight
|
||||
info["volume"] = item.ProductCubage
|
||||
info["length"] = item.ProductLength
|
||||
data["waybillNoInfoList"] = append(data["waybillNoInfoList"].([]map[string]interface{}), info)
|
||||
num++
|
||||
if utils.GetConfigBool("transit.sf_test") {
|
||||
break
|
||||
}
|
||||
}
|
||||
if utils.GetConfigBool("transit.sf_test") {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
cargoDetails := make(map[string]interface{})
|
||||
cargoDetails["name"] = name
|
||||
if len(nameInfo) > utils.NumberZero {
|
||||
cargoDetails["name"] = strings.Join(nameInfo, ",")
|
||||
data["remark"] = strings.Join(nameInfo, ",")
|
||||
}
|
||||
cargoDetails["count"] = order.Number
|
||||
data["cargoDetails"] = append(data["cargoDetails"].([]map[string]interface{}), cargoDetails)
|
||||
|
||||
bytes, _ := jsoniter.Marshal(data)
|
||||
return string(bytes)
|
||||
}
|
||||
|
||||
func (c *SfClient) getPostData(serviceCode string, msgData string) map[string]interface{} {
|
||||
requestID := c.createUUID()
|
||||
timestamp := fmt.Sprintf("%d", time.Now().Unix())
|
||||
|
||||
// 签名逻辑:md5( urlencode(msgData + timestamp + code ) ) → base64
|
||||
raw := url.QueryEscape(msgData + timestamp + c.config.AppSecretKey)
|
||||
md5Val := md5.Sum([]byte(raw))
|
||||
msgDigest := base64.StdEncoding.EncodeToString(md5Val[:])
|
||||
|
||||
return map[string]interface{}{
|
||||
"partnerID": c.config.AppCode,
|
||||
"requestID": requestID,
|
||||
"serviceCode": serviceCode,
|
||||
"timestamp": timestamp,
|
||||
"msgDigest": msgDigest,
|
||||
"msgData": msgData,
|
||||
}
|
||||
}
|
||||
|
||||
func (c *SfClient) send(postData map[string]interface{}) (TransitResult, error) {
|
||||
res := TransitResult{}
|
||||
// 发送请求
|
||||
resultStr, err := c.sendPost(c.config.AppUrl, postData)
|
||||
if err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// 解析第一层JSON
|
||||
var result map[string]interface{}
|
||||
if err := jsoniter.Unmarshal([]byte(resultStr), &result); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.RequestID = postData["requestID"].(string)
|
||||
res.RequestInfo = resultStr
|
||||
// 读取 apiResultData
|
||||
if apiResultData, ok := result["apiResultData"].(string); ok && apiResultData != "" {
|
||||
var dataMap map[string]interface{}
|
||||
if err := jsoniter.Unmarshal([]byte(apiResultData), &dataMap); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
res.ErrorCode = dataMap["errorCode"].(string)
|
||||
res.ApiErrorMsg = utils.GetStringByMap(dataMap, "errorMsg")
|
||||
res.ApiResponseID = utils.GetStringByMap(result, "apiResponseID")
|
||||
// 成功 → 提取运单号
|
||||
if res.ErrorCode == "S0000" {
|
||||
res.ErrorCode = utils.StringStatusOk
|
||||
if msgData, ok := dataMap["msgData"].(map[string]interface{}); ok {
|
||||
res.RequestQuery = msgData
|
||||
if list, ok := msgData["waybillNoInfoList"].([]interface{}); ok {
|
||||
for _, item := range list {
|
||||
if m, ok := item.(map[string]interface{}); ok {
|
||||
no := utils.GetStringByMap(m, "waybillNo")
|
||||
if no != "" {
|
||||
res.WaybillNo = append(res.WaybillNo, no)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// 错误情况
|
||||
res.ErrorCode = utils.GetStringByMap(result, "apiResultCode")
|
||||
if res.ErrorCode == "" {
|
||||
res.ErrorCode = "A11111"
|
||||
}
|
||||
res.ApiErrorMsg = utils.GetStringByMap(result, "apiErrorMsg")
|
||||
res.ApiResponseID = utils.GetStringByMap(result, "apiResponseID")
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
|
||||
func (c *SfClient) sendDown(postData map[string]interface{}) (TransitResult, error) {
|
||||
res := TransitResult{}
|
||||
// 发送请求
|
||||
resultStr, err := c.sendPost(c.config.AppUrl, 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.RequestID = utils.GetStringByMap(postData, "requestID")
|
||||
res.RequestInfo = resultStr
|
||||
res.ApiResponseID = utils.GetStringByMap(result, "apiResponseID")
|
||||
res.ApiErrorMsg = utils.GetStringByMap(result, "apiErrorMsg")
|
||||
|
||||
// 外层状态
|
||||
apiResultCode := utils.GetStringByMap(result, "apiResultCode")
|
||||
if apiResultCode == "A1000" {
|
||||
// 接口调用成功
|
||||
res.ErrorCode = utils.StringStatusOk
|
||||
} else {
|
||||
res.ErrorCode = apiResultCode
|
||||
return res, nil
|
||||
}
|
||||
|
||||
apiResultData := utils.GetStringByMap(result, "apiResultData")
|
||||
if apiResultData == "" {
|
||||
return res, nil
|
||||
}
|
||||
|
||||
var dataMap map[string]interface{}
|
||||
if err := jsoniter.Unmarshal([]byte(apiResultData), &dataMap); err != nil {
|
||||
return res, err
|
||||
}
|
||||
|
||||
// ======================
|
||||
// 正确解析顺丰云打印结构
|
||||
// ======================
|
||||
success := false
|
||||
if val, ok := dataMap["success"].(bool); ok {
|
||||
success = val
|
||||
}
|
||||
if success {
|
||||
res.ErrorCode = utils.StringStatusOk
|
||||
// 读取 obj -> files
|
||||
if obj, ok := dataMap["obj"].(map[string]interface{}); ok {
|
||||
if files, ok := obj["files"].([]interface{}); ok {
|
||||
for _, item := range files {
|
||||
if m, ok := item.(map[string]interface{}); ok {
|
||||
|
||||
waybillNo := utils.GetStringByMap(m, "waybillNo")
|
||||
res.Files = append(res.Files, TransitFiles{
|
||||
File: utils.GetStringByMap(m, "waybillNo"),
|
||||
Url: utils.GetStringByMap(m, "url"),
|
||||
Token: utils.GetStringByMap(m, "token"),
|
||||
WaybillNo: waybillNo,
|
||||
})
|
||||
|
||||
res.WaybillNo = append(res.WaybillNo, waybillNo)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
res.ErrorCode = "FAIL"
|
||||
}
|
||||
|
||||
return res, nil
|
||||
}
|
||||
Reference in New Issue
Block a user