daShangDao_kfzgw-info/expressDeliveryOrder/expressDeliveryOrder.go
2026-02-27 11:46:40 +08:00

1294 lines
41 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package main
/*
#include <stdlib.h>
*/
import "C"
import (
"bytes"
"crypto/md5"
"encoding/base64"
"encoding/hex"
"encoding/json"
"fmt"
"github.com/parnurzeal/gorequest"
"github.com/tjfoc/gmsm/sm4"
"net/http"
"sort"
"strings"
"time"
"unsafe"
)
// ZtoOrderRequest 中通订单请求结构体
type ZtoOrderRequest struct {
PartnerType string `json:"partnerType"` // 合作模式 1集团客户2非集团客户
OrderType string `json:"orderType"` // partnerType为1时orderType1全网件 2预约件。partnerType为2时orderType1全网件 2预约件返回运单号 3预约件不返回运单号 4星联全网件
PartnerOrderCode string `json:"partnerOrderCode"` // 合作商订单号
AccountInfo AccountInfo `json:"accountInfo"` // 账号信息
BillCode string `json:"billCode"` // 运单号
SenderInfo SenderInfo `json:"senderInfo"` // 发件人信息
ReceiveInfo ReceiveInfo `json:"receiveInfo"` // 收件人信息
OrderVasList []OrderVas `json:"orderVasList"` // 增值服务信息
HallCode string `json:"hallCode"` // 门店/仓库编码partnerType为1时可使用
SiteCode string `json:"siteCode"` // 网点codeorderVasList.vasType为receiveReturnService必填
SiteName string `json:"siteName"` // 网点名称orderVasList.vasType为receiveReturnService必填
SummaryInfo SummaryInfo `json:"summaryInfo"` // 汇总信息
Remark string `json:"remark"` // 备注
OrderItems []OrderItem `json:"orderItems"` // 物品信息
Cabinet Cabinet `json:"cabinet"` // 机柜信息
}
// AccountInfo 账号信息
type AccountInfo struct {
AccountId string `json:"accountId"` // 电子面单账号partnerType为2orderType传1,2,4时必传
AccountPassword string `json:"accountPassword"` // 电子面单密码测试环境传ZTO123
Type int `json:"type"` // 单号类型:1.普通电子面单74.星联电子面单默认是1
CustomerId string `json:"customerId"` // 集团客户编码partnerType传1时必传
}
// SenderInfo 发件人信息
type SenderInfo struct {
SenderId string `json:"senderId"` // 发件人ID
SenderName string `json:"senderName"` // 发件人姓名
SenderPhone string `json:"senderPhone"` // 发件人座机与senderMobile二者不能同时为空
SenderMobile string `json:"senderMobile"` // 发件人手机号与senderPhone二者不能同时为空
SenderProvince string `json:"senderProvince"` // 发件人省
SenderCity string `json:"senderCity"` // 发件人市
SenderDistrict string `json:"senderDistrict"` // 发件人区
SenderAddress string `json:"senderAddress"` // 发件人详细地址
}
// ReceiveInfo 收件人信息
type ReceiveInfo struct {
ReceiverName string `json:"receiverName"` // 收件人姓名
ReceiverPhone string `json:"receiverPhone"` // 收件人座机与receiverMobile二者不能同时为空
ReceiverMobile string `json:"receiverMobile"` // 收件人手机号(与 receiverPhone二者不能同时为空
ReceiverProvince string `json:"receiverProvince"` // 收件人省
ReceiverCity string `json:"receiverCity"` // 收件人市
ReceiverDistrict string `json:"receiverDistrict"` // 收件人区
ReceiverAddress string `json:"receiverAddress"` // 收件人详细地址
}
// OrderVas 增值服务
type OrderVas struct {
VasType string `json:"vasType"` // 增值类型COD代收vip中通标快insured保价receiveReturnService签单返回twoHour两小时standardExpress中通好快
VasAmount int64 `json:"vasAmount"` // 增值价格如果增值类型涉及金额会校验vasType为COD、insured时不能为空单位
VasPrice int64 `json:"vasPrice"` // 增值价格(暂时不用)
VasDetail string `json:"vasDetail"` // 增值详情
AccountNo string `json:"accountNo"` // 代收账号(有代收货款增值时必填)
}
// SummaryInfo 汇总信息
type SummaryInfo struct {
Size string `json:"size"` // 订单包裹大小(单位:厘米、格式:"长,宽,高",用半角的逗号来分隔)
Quantity int `json:"quantity"` // 订单包裹内货物总数量
Price int `json:"price"` // 商品总价值(单位:元)
Freight int `json:"freight"` // 运输费(单位:元)
Premium int `json:"premium"` // 险费(单位:元)
StartTime string `json:"startTime"` // 取件开始时间传值此字段则需必传vasTypetwoHour
EndTime string `json:"endTime"` // 取件截止时间传值此字段则需必传vasTypetwoHour
}
// OrderItem 物品信息
type OrderItem struct {
Name string `json:"name"` // 货品名称
Category string `json:"category"` // 商品分类
Material string `json:"material"` // 商品材质
Size string `json:"size"` // 大小(长,宽,高)(单位:厘米), 用半角的逗号来分隔长宽高
Weight int64 `json:"weight"` // 重量(单位:克)
Unitprice int `json:"unitprice"` // 单价(单位:元)
Quantity int `json:"quantity"` // 货品数量
Remark string `json:"remark"` // 货品备注
}
// Cabinet 机柜信息
type Cabinet struct {
Address string `json:"address"` // 地址
Specification int `json:"specification"` // 格口规格 格口大小(1 大 2 中 3 小)
Code string `json:"code"` // 开箱码
}
/*
中通快递--创建订单接口
请求参数:
requestJSON 创建订单参数JSON字符串
appKey app值
appSecret 密钥
*/
func ztoOpenCreateOrder(requestJSON, appKey, appSecret string) (string, error) {
fmt.Printf("进入中通订单创建接口: %v \n", requestJSON)
if appKey == "" || appSecret == "" {
return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置")
}
// 正式环境
ztoUrl := "https://japi.zto.com/zto.open.createOrder"
// 测试环境
//ztoUrl := "https://japi-test.zto.com/zto.open.createOrder"
// 生成签名
dataDigest, err := ztoGenerateSign(requestJSON, appSecret)
if err != nil {
return "", fmt.Errorf("生成签名失败: %w", err)
}
fmt.Println(dataDigest)
// 创建gorequest实例
req := gorequest.New()
// 使用gorequest发送POST请求
resp, body, errs := req.Post(ztoUrl).
Set("Content-Type", "application/json").
Set("x-appKey", appKey).
Set("x-datadigest", dataDigest).
Set("x-timestamp", fmt.Sprintf("%d", time.Now().Unix())).
Send(requestJSON).
End()
// 处理错误
if len(errs) > 0 {
errStr := ""
for _, e := range errs {
errStr += e.Error() + "; "
}
return "", fmt.Errorf("发送HTTP请求失败: %s", errStr)
}
// 检查HTTP状态码
if resp.StatusCode >= 400 {
return "", fmt.Errorf("HTTP请求失败状态码: %d", resp.StatusCode)
}
// 解析响应体
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// 中通签名生成函数
func ztoGenerateSign(data, appSecret string) (string, error) {
// 中通签名生成函数(修正版)
// 根据文档,签名流程应该是:
// 1. 请求参数json字符串 + appSecret
// 2. 进行MD5计算
// 3. 对MD5结果进行Base64编码
// 注意:中通可能要求特定的格式,比如参数需要排序
var requestData map[string]interface{}
if err := json.Unmarshal([]byte(data), &requestData); err != nil {
return "", fmt.Errorf("解析请求参数失败: %w", err)
}
// 将map转换为排序后的JSON字符串中通可能要求按key排序
keys := make([]string, 0, len(requestData))
for k := range requestData {
keys = append(keys, k)
}
sort.Strings(keys)
var buf strings.Builder
buf.WriteString("{")
for i, k := range keys {
if i > 0 {
buf.WriteString(",")
}
buf.WriteString(fmt.Sprintf(`"%s":`, k))
// 根据值的类型进行格式化
switch v := requestData[k].(type) {
case string:
buf.WriteString(fmt.Sprintf(`"%s"`, v))
case float64:
buf.WriteString(fmt.Sprintf(`%.0f`, v)) // 整数可能以float64形式存在
default:
// 其他类型转换为JSON字符串
valBytes, _ := json.Marshal(v)
buf.Write(valBytes)
}
}
buf.WriteString("}")
sortedJSON := buf.String()
fmt.Printf("排序后的JSON: %s\n", sortedJSON)
// 按文档要求的格式
signString := sortedJSON + appSecret
// 计算MD5
md5Sum := md5.Sum([]byte(signString))
// Base64编码
sign := base64.StdEncoding.EncodeToString(md5Sum[:])
fmt.Printf("Base64签名: %s\n", sign)
return sign, nil
}
/*
极兔快递--创建订单接口
请求参数:
requestJSON 创建订单参数JSON字符串
appKey app值
appSecret 密钥
*/
func jtOrderAddOrder(requestJSON, apiAccount, privateKey string) (string, error) {
fmt.Printf("进入极兔订单创建接口: %v \n", requestJSON)
// 正式环境
jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder"
// 测试环境
//jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8"
// 生成签名
digest, err := jtGenerateDigest(requestJSON, privateKey)
if err != nil {
return "", fmt.Errorf("生成签名失败: %w", err)
}
// 当前时间戳
timestamp := fmt.Sprintf("%d", time.Now().Unix())
// 创建gorequest实例
req := gorequest.New()
// 使用gorequest发送POST请求
resp, body, errs := req.Post(jtUrl).
Type("form").
Set("Content-Type", "application/x-www-form-urlencoded").
Set("apiAccount", apiAccount).
Set("digest", digest).
Set("timestamp", timestamp).
Set("User-Agent", "ZTO-API-Client/1.0").
Send(requestJSON).
End()
// 处理错误
if len(errs) > 0 {
errStr := ""
for _, e := range errs {
errStr += e.Error() + "; "
}
return "", fmt.Errorf("发送HTTP请求失败: %s", errStr)
}
// 检查HTTP状态码
if resp.StatusCode >= 400 {
return "", fmt.Errorf("HTTP请求失败状态码: %d", resp.StatusCode)
}
// 解析响应体
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// GenerateHeaderDigest 极兔生成Headers中的digest签名
// bizContent: 业务参数的JSON对象如map[string]interface{}或结构体
// privateKey: 平台分配的私钥
func jtGenerateDigest(bizContent interface{}, privateKey string) (string, error) {
// 1. 将业务参数转换为JSON字符串
jsonBytes, err := json.Marshal(bizContent)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
jsonStr := string(jsonBytes)
// 2. 拼接JSON字符串和私钥
signStr := jsonStr + privateKey
// 3. 计算MD5
hash := md5.Sum([]byte(signStr))
// 4. Base64编码
digest := base64.StdEncoding.EncodeToString(hash[:])
return digest, nil
}
// GenerateBusinessDigest 极兔生成业务参数中的digest签名
// customerCode: 客户编码,如"J0086474299"
// plainPassword: 明文密码,如"H5CD3zE6"
// privateKey: 平台分配的私钥
func GenerateBusinessDigest(customerCode, plainPassword, privateKey string) (string, error) {
// 1. 计算加密后的密码 pwd = MD5(明文密码 + "jadada236t2")32位大写
pwdStr := plainPassword + "jadada236t2"
pwdHash := md5.Sum([]byte(pwdStr))
pwd := strings.ToUpper(hex.EncodeToString(pwdHash[:]))
// 2. 拼接 customerCode + pwd + privateKey
signStr := customerCode + pwd + privateKey
// 3. 计算MD5
hash := md5.Sum([]byte(signStr))
// 4. Base64编码
digest := base64.StdEncoding.EncodeToString(hash[:])
return digest, nil
}
/*
邮政快递--订单接入接口
请求参数:
requestJSON 创建订单参数JSON字符串
appKey app值
appSecret 密钥
*/
func emsAmpApiOpen(requestJSON, secretKey string) (string, error) {
// 正式环境
emsUrl := "https://api.ems.com.cn/amp-prod-api/f/amp/api/open"
// 测试环境
//emsUrl := "https://api.ems.com.cn/amp-prod-api/f/amp/api/test"
// 当前时间戳
timestamp := fmt.Sprintf("%d", time.Now().Unix())
contentToEncrypt := requestJSON + secretKey
signature, err := GenerateSM4Signature(contentToEncrypt, secretKey)
if err != nil {
return "", fmt.Errorf("生成签名失败: %v", err)
}
// 准备请求参数
params := map[string]interface{}{
"logisticsInterface": string(requestJSON),
}
// 创建gorequest实例
req := gorequest.New()
// 设置超时和重试策略
req = req.Timeout(30*time.Second).
Retry(3, 5*time.Second, http.StatusInternalServerError, http.StatusBadGateway)
// 使用gorequest发送POST请求
resp, body, errs := req.Post(emsUrl).
Set("Content-Type", "application/json").
Set("apiCode", "020003").
Set("senderNo", "EMS").
Set("timestamp", timestamp).
Set("authorization", signature).
Set("User-Agent", "ZTO-API-Client/1.0").
Send(params).
End()
// 处理错误
if len(errs) > 0 {
errStr := ""
for _, e := range errs {
errStr += e.Error() + "; "
}
return "", fmt.Errorf("发送HTTP请求失败: %s", errStr)
}
// 检查HTTP状态码
if resp.StatusCode >= 400 {
return "", fmt.Errorf("HTTP请求失败状态码: %d", resp.StatusCode)
}
// 解析响应体
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
const (
CIPHERTEXT_PREFIX_THIRD_SM4ECB = "|$4|"
)
// GenerateSM4Signature 生成SM4签名
// params: 业务报文JSON字符串
// key: 从页面生成的秘钥base64格式
// 返回值: 签名后的字符串,格式为 |$4| + base64(SM4加密结果)
func GenerateSM4Signature(params, key string) (string, error) {
// 检查输入参数
if params == "" || key == "" {
return params, nil
}
// 检查是否已经加密
if strings.HasPrefix(params, CIPHERTEXT_PREFIX_THIRD_SM4ECB) {
return params, nil
}
// 拼接待签名的内容:业务报文 + key
content := params + key
// 解码base64格式的密钥
keyBytes, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return "", fmt.Errorf("failed to decode base64 key: %v", err)
}
// SM4 ECB模式加密
encryptedData, err := sm4ECBEncrypt([]byte(content), keyBytes)
if err != nil {
return "", fmt.Errorf("failed to encrypt with SM4: %v", err)
}
// 对加密结果进行base64编码
encryptedBase64 := base64.StdEncoding.EncodeToString(encryptedData)
// 添加前缀
result := CIPHERTEXT_PREFIX_THIRD_SM4ECB + encryptedBase64
return result, nil
}
// SM4ECBDecrypt 验证签名(解密)
func SM4ECBDecrypt(ciphertext, key string) (string, error) {
if ciphertext == "" || key == "" {
return ciphertext, nil
}
// 检查是否包含SM4前缀
if !strings.HasPrefix(ciphertext, CIPHERTEXT_PREFIX_THIRD_SM4ECB) {
return ciphertext, nil
}
// 移除前缀
encryptedBase64 := strings.TrimPrefix(ciphertext, CIPHERTEXT_PREFIX_THIRD_SM4ECB)
// 解码base64密文
encryptedData, err := base64.StdEncoding.DecodeString(encryptedBase64)
if err != nil {
return "", fmt.Errorf("failed to decode base64 ciphertext: %v", err)
}
// 解码base64密钥
keyBytes, err := base64.StdEncoding.DecodeString(key)
if err != nil {
return "", fmt.Errorf("failed to decode base64 key: %v", err)
}
// SM4 ECB模式解密
decryptedData, err := sm4ECBDecrypt(encryptedData, keyBytes)
if err != nil {
return "", fmt.Errorf("failed to decrypt with SM4: %v", err)
}
return string(decryptedData), nil
}
// sm4ECBEncrypt SM4 ECB模式加密
func sm4ECBEncrypt(plaintext, key []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
// 使用PKCS5Padding填充
plaintext = pkcs5Padding(plaintext, block.BlockSize())
// ECB模式不需要IV
blockSize := block.BlockSize()
ciphertext := make([]byte, len(plaintext))
// 分块加密
for start := 0; start < len(plaintext); start += blockSize {
end := start + blockSize
block.Encrypt(ciphertext[start:end], plaintext[start:end])
}
return ciphertext, nil
}
// sm4ECBDecrypt SM4 ECB模式解密
func sm4ECBDecrypt(ciphertext, key []byte) ([]byte, error) {
block, err := sm4.NewCipher(key)
if err != nil {
return nil, err
}
// 检查密文长度
if len(ciphertext)%block.BlockSize() != 0 {
return nil, fmt.Errorf("ciphertext length is not a multiple of block size")
}
plaintext := make([]byte, len(ciphertext))
blockSize := block.BlockSize()
// 分块解密
for start := 0; start < len(ciphertext); start += blockSize {
end := start + blockSize
block.Decrypt(plaintext[start:end], ciphertext[start:end])
}
// 去除PKCS5Padding填充
plaintext = pkcs5UnPadding(plaintext)
return plaintext, nil
}
// pkcs5Padding PKCS5填充
func pkcs5Padding(src []byte, blockSize int) []byte {
padding := blockSize - len(src)%blockSize
padtext := bytes.Repeat([]byte{byte(padding)}, padding)
return append(src, padtext...)
}
// pkcs5UnPadding 去除PKCS5填充
func pkcs5UnPadding(src []byte) []byte {
length := len(src)
if length == 0 {
return src
}
unpadding := int(src[length-1])
if unpadding > length {
return src
}
return src[:(length - unpadding)]
}
// VerifySignature 验证签名是否正确
func VerifySignature(ciphertext, params, key string) (bool, error) {
// 解密签名
decrypted, err := SM4ECBDecrypt(ciphertext, key)
if err != nil {
return false, err
}
// 验证解密后的内容是否等于 params + key
expected := params + key
return decrypted == expected, nil
}
/*
申通快递--订单创建接口
请求参数:
requestJSON content参数JSON字符串
fromAppkey 订阅方/请求发起方的应用key
secretKey 密钥
fromCode 订阅方/请求发起方的应用资源code
*/
func stoOmsExpressOrderCreate(requestJSON, fromAppkey, secretKey, fromCode string) (string, error) {
// 正式环境
//ztoUrl := "https://cloudinter-linkgateway.sto.cn/gateway/link.do"
// 测试环境
stoUrl := "http://cloudinter-linkgatewaytest.sto.cn/gateway/link.do"
signature := stoGenerateSign(requestJSON, secretKey)
// 准备请求参数
params := map[string]string{
"api_name": "OMS_EXPRESS_ORDER_CREATE",
"content": requestJSON,
"from_appkey": fromAppkey,
"from_code": fromCode,
"to_appkey": "sto_oms",
"to_code": "sto_oms",
"data_digest": signature,
}
// 创建gorequest实例
req := gorequest.New()
// 设置超时和重试策略
req = req.Timeout(30*time.Second).
Retry(3, 5*time.Second, http.StatusInternalServerError, http.StatusBadGateway)
// 使用gorequest发送POST请求
req.Post(stoUrl).
Set("Content-Type", "application/x-www-form-urlencoded;charset=utf-8")
// 添加表单参数
for key, value := range params {
req.Send(fmt.Sprintf("%s=%s", key, value))
}
resp, body, errs := req.End()
// 处理错误
if len(errs) > 0 {
errStr := ""
for _, e := range errs {
errStr += e.Error() + "; "
}
return "", fmt.Errorf("发送HTTP请求失败: %s", errStr)
}
// 检查HTTP状态码
if resp.StatusCode >= 400 {
return "", fmt.Errorf("HTTP请求失败状态码: %d", resp.StatusCode)
}
// 解析响应体
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// 申通快递计算签名
func stoGenerateSign(content, secretKey string) string {
text := content + secretKey
// 计算 MD5
hash := md5.Sum([]byte(text))
// Base64 编码
return base64.StdEncoding.EncodeToString(hash[:])
}
// 韵达快递--电子面单下单
// 参数requestJSON:业务参数JSON字符串 appid合作商appid等同app-key appSecret签名 partnerID
func ydCreateBmOrder(requestJSON, appKey, appSecret string) (string, error) {
// 确定环境
//baseURL := "https://openapi.yundax.com/openapi-api/v1/accountOrder/createBmOrder"
// 测试环境
baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/createBmOrder"
// 3. 构建 Headers根据文档
timestamp := time.Now().UnixMilli()
headers := map[string]string{
"Content-Type": "application/json; charset=UTF-8",
"app-key": appKey,
"req-time": fmt.Sprintf("%d", timestamp),
"sign": ydGenerateSign(requestJSON, appSecret), // 需要实现签名方法
}
// 4. 发送请求
request := gorequest.New()
resp, body, errs := request.Post(baseURL).
Set("Content-Type", headers["Content-Type"]).
Set("app-key", headers["app-key"]).
Set("req-time", headers["req-time"]).
Set("sign", headers["sign"]).
Send(requestJSON).
End()
if len(errs) > 0 {
errStr := ""
for _, e := range errs {
errStr += e.Error() + "; "
}
return "", fmt.Errorf("发送HTTP请求失败: %s", errStr)
}
// 检查HTTP状态码
if resp.StatusCode >= 400 {
return "", fmt.Errorf("HTTP请求失败状态码: %d", resp.StatusCode)
}
// 解析响应体
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
// 韵达快递计算签名
func ydGenerateSign(content, secretKey string) string {
text := content + "_" + secretKey
// 计算 MD5
hash := md5.Sum([]byte(text))
// Base64 编码
return base64.StdEncoding.EncodeToString(hash[:])
}
// 韵达快递--电子面单打印
func ydBmGetPdfInfo(requestJSON, appKey, appSecret string) (string, error) {
// 确定环境
//baseURL := "https://openapi.yundax.com/openapi-api/v1/accountOrder/createBmOrder"
// 测试环境
baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/createBmOrder"
// 3. 构建 Headers根据文档
timestamp := time.Now().UnixMilli()
headers := map[string]string{
"Content-Type": "application/json; charset=UTF-8",
"app-key": appKey,
"req-time": fmt.Sprintf("%d", timestamp),
"sign": ydGenerateSign(requestJSON, appSecret), // 需要实现签名方法
}
// 4. 发送请求
request := gorequest.New()
resp, body, errs := request.Post(baseURL).
Set("Content-Type", headers["Content-Type"]).
Set("app-key", headers["app-key"]).
Set("req-time", headers["req-time"]).
Set("sign", headers["sign"]).
Send(requestJSON).
End()
if len(errs) > 0 {
errStr := ""
for _, e := range errs {
errStr += e.Error() + "; "
}
return "", fmt.Errorf("发送HTTP请求失败: %s", errStr)
}
// 检查HTTP状态码
if resp.StatusCode >= 400 {
return "", fmt.Errorf("HTTP请求失败状态码: %d", resp.StatusCode)
}
// 解析响应体
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return "", fmt.Errorf("解析响应失败: %v, 响应内容: %s", err, body)
}
// 转换成json字符串
responseJSON, err := json.Marshal(response)
if err != nil {
return "", fmt.Errorf("JSON序列化失败: %v", err)
}
return string(responseJSON), nil
}
const (
ZTO_ORDER_TYPE = "ZTO"
JT_ORDER_TYPE = "JT"
EMS_ORDER_TYPE = "EMS"
STO_ORDER_TYPE = "STO"
YD_ORDER_TYPE = "YD"
)
/*
整合所有快递--订单接口
请求参数:
orderType 快递类型
requestJSON 创建订单参数JSON字符串
*/
func integrationOrderCreate(orderType, requestJSON, key, secret, fromCode string) (string, error) {
switch orderType {
// 中通
case ZTO_ORDER_TYPE:
return ztoOpenCreateOrder(requestJSON, key, secret)
// 极兔
case JT_ORDER_TYPE:
return jtOrderAddOrder(requestJSON, key, secret)
// 邮政
case EMS_ORDER_TYPE:
return emsAmpApiOpen(requestJSON, key)
// 申通
case STO_ORDER_TYPE:
return stoOmsExpressOrderCreate(requestJSON, key, secret, fromCode)
// 韵达
case YD_ORDER_TYPE:
return ydCreateBmOrder(requestJSON, key, secret)
}
return "", fmt.Errorf("快递类型不匹配: %s", orderType)
}
// ========================== C 导入函数 ===================
// ZtoOpenCreateOrder 中通快递--创建订单接口
//
//export ZtoOpenCreateOrder
func ZtoOpenCreateOrder(requestJSON, appKey, appSecret *C.char) *C.char {
requestJSONStr := C.GoString(requestJSON)
appKeyStr := C.GoString(appKey)
appSecretStr := C.GoString(appSecret)
info, err := ztoOpenCreateOrder(requestJSONStr, appKeyStr, appSecretStr)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// JtOrderAddOrder 极兔快递--创建订单接口
//
//export JtOrderAddOrder
func JtOrderAddOrder(requestJSON, apiAccount, privateKey *C.char) *C.char {
requestJSONStr := C.GoString(requestJSON)
apiAccountStr := C.GoString(apiAccount)
privateKeyStr := C.GoString(privateKey)
info, err := jtOrderAddOrder(requestJSONStr, apiAccountStr, privateKeyStr)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// EmsAmpApiOpen 邮政快递--订单接入接口
//
//export EmsAmpApiOpen
func EmsAmpApiOpen(requestJSON, secretKey *C.char) *C.char {
requestJSONStr := C.GoString(requestJSON)
secretKeyStr := C.GoString(secretKey)
info, err := emsAmpApiOpen(requestJSONStr, secretKeyStr)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// StoOmsExpressOrderCreate 申通快递--订单接入接口
//
//export StoOmsExpressOrderCreate
func StoOmsExpressOrderCreate(requestJSON, fromAppkey, secretKey, fromCode *C.char) *C.char {
requestJSONStr := C.GoString(requestJSON)
fromAppkeyStr := C.GoString(fromAppkey)
secretKeyStr := C.GoString(secretKey)
fromCodeStr := C.GoString(fromCode)
info, err := stoOmsExpressOrderCreate(requestJSONStr, fromAppkeyStr, secretKeyStr, fromCodeStr)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// YdCreateBmOrder 韵达快递--电子面单下单
//
//export YdCreateBmOrder
func YdCreateBmOrder(requestJSON, appKey, appSecret *C.char) *C.char {
requestJSONStr := C.GoString(requestJSON)
appKeyStr := C.GoString(appKey)
appSecretStr := C.GoString(appSecret)
info, err := ydCreateBmOrder(requestJSONStr, appKeyStr, appSecretStr)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// YdBmGetPdfInfo 韵达快递--电子面单打印
//
//export YdBmGetPdfInfo
func YdBmGetPdfInfo(requestJSON, appKey, appSecret *C.char) *C.char {
requestJSONStr := C.GoString(requestJSON)
appKeyStr := C.GoString(appKey)
appSecretStr := C.GoString(appSecret)
info, err := ydBmGetPdfInfo(requestJSONStr, appKeyStr, appSecretStr)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// IntegrationOrderCreate 整合所有快递--订单接口
//
//export IntegrationOrderCreate
func IntegrationOrderCreate(orderType, requestJSON, key, secret, fromCode *C.char) *C.char {
orderTypeStr := C.GoString(orderType)
requestJSONStr := C.GoString(requestJSON)
keyStr := C.GoString(key)
secretStr := C.GoString(secret)
fromCodeStr := C.GoString(fromCode)
info, err := integrationOrderCreate(orderTypeStr, requestJSONStr, keyStr, secretStr, fromCodeStr)
if err != nil {
return C.CString(err.Error())
}
return C.CString(info)
}
// FreeCString 释放C字符串内存
//
//export FreeCString
func FreeCString(str *C.char) {
C.free(unsafe.Pointer(str))
}
func getRequestParamter(orderType string) {
switch orderType {
case ZTO_ORDER_TYPE:
case JT_ORDER_TYPE:
case EMS_ORDER_TYPE:
case STO_ORDER_TYPE:
var stoOrder StoOrder
stoOrder.OrderNo = "OrderNo string 订单号(客户系统自己生成,唯一)"
}
}
// StoOrder 订单信息
type StoOrder struct {
// OrderNo 订单号(客户系统自己生成,唯一)
OrderNo string `json:"orderNo"`
// OrderSource 订单来源(订阅服务时填写的来源编码)
OrderSource string `json:"orderSource"`
// BillType 获取面单的类型00-普通、03-国际、01-代收、02-到付、04-生鲜),默认普通业务,如果有其他业务先与业务方沟通清楚
BillType string `json:"billType"`
// OrderType 订单类型01-普通订单、02-调度订单默认01-普通订单,如果有散单业务需先业务方沟通清楚
OrderType string `json:"orderType"`
// Sender 寄件人信息
Sender StoSenderInfo `json:"sender"`
// Receiver 收件人信息
Receiver ReceiverInfo `json:"receiver"`
// Cargo 包裹信息
Cargo CargoInfo `json:"cargo"`
// Customer 客户信息,在线下单取运单号必填,代单号下单不需要填写,测试账号传值如下,生产账号联系合作业务方提供
Customer CustomerInfo `json:"customer"`
// InternationalAnnex 国际订单附属信息(国际业务订单必填,其他业务不要填写)
InternationalAnnex InternationalInfo `json:"internationalAnnex"`
// WaybillNo 运单号下单前已获取运单号时必传否则不传或传NULL
WaybillNo string `json:"waybillNo"`
// AssignAnnex 指定网点揽收(调度散单业务订单需要传)其他业务不需要
AssignAnnex AssignInfo `json:"assignAnnex"`
// CodValue 代收货款金额,单位:元(代收货款业务时必填)
CodValue string `json:"codValue"`
// FreightCollectValue 到付运费金额,单位:元(到付业务时必填)
FreightCollectValue string `json:"freightCollectValue"`
// TimelessType 时效类型01-普通)
TimelessType string `json:"timelessType"`
// ProductType 产品类型01-普通、02-冷链、03-生鲜)
ProductType string `json:"productType"`
// ServiceTypeList 增值服务TRACE_PUSH-轨迹回传;PRIVACY_SURFACE_SINGLE-隐私面单标;STO_MARKET_SDD-申咚咚按需派送STO_HOME_DELIVERY_OP 送货上门开城判断)
ServiceTypeList []string `json:"serviceTypeList"`
// ExtendFieldMap 拓展字段 注意事项:属性值有逗号等于号需过滤掉
ExtendFieldMap map[string]string `json:"extendFieldMap"`
// Remark 备注
Remark string `json:"remark"`
// ExpressDirection 快递流向01-正向订单默认01
ExpressDirection string `json:"expressDirection"`
// CreateChannel 创建原因01-客户创建默认01
CreateChannel string `json:"createChannel"`
// RegionType 区域类型01-国内默认01
RegionType string `json:"regionType"`
// InsuredAnnex 保价模型(保价服务必填)
InsuredAnnex InsuredInfo `json:"insuredAnnex"`
// ExpectValue 预估费用(散单业务使用)
ExpectValue string `json:"expectValue"`
// PayModel 支付方式1-现付2-到付3-月结)
PayModel string `json:"payModel"`
}
// StoSenderInfo 寄件人信息
type StoSenderInfo struct {
// Name 寄件人名称
Name string `json:"name"`
// Tel 寄件人固定电话
Tel string `json:"tel"`
// Mobile 寄件人手机号码
Mobile string `json:"mobile"`
// PostCode 邮编
PostCode string `json:"postCode"`
// Country 国家
Country string `json:"country"`
// Province 省
Province string `json:"province"`
// City 市
City string `json:"city"`
// Area 区
Area string `json:"area"`
// Town 镇
Town string `json:"town"`
// Address 详细地址
Address string `json:"address"`
}
// ReceiverInfo 收件人信息
type ReceiverInfo struct {
// Name 收件人名称
Name string `json:"name"`
// Tel 收件人固定电话
Tel string `json:"tel"`
// Mobile 收件人手机号码
Mobile string `json:"mobile"`
// PostCode 邮编
PostCode string `json:"postCode"`
// Country 国家
Country string `json:"country"`
// Province 省
Province string `json:"province"`
// City 市
City string `json:"city"`
// Area 区
Area string `json:"area"`
// Town 镇
Town string `json:"town"`
// Address 详细地址
Address string `json:"address"`
// SafeNo 安全号码
SafeNo string `json:"safeNo"`
}
// CargoInfo 包裹信息
type CargoInfo struct {
// Battery 带电标识10/未知 20/带电 30/不带电)
Battery string `json:"battery"`
// GoodsType 物品类型(大件、小件、扁平件\文件)
GoodsType string `json:"goodsType"`
// GoodsName 物品名称
GoodsName string `json:"goodsName"`
// GoodsCount 物品数量
GoodsCount int `json:"goodsCount"`
// SpaceX 长cm
SpaceX float64 `json:"spaceX"`
// SpaceY 宽cm
SpaceY float64 `json:"spaceY"`
// SpaceZ 高cm
SpaceZ float64 `json:"spaceZ"`
// Weight 重量kg
Weight float64 `json:"weight"`
// GoodsAmount 商品金额
GoodsAmount string `json:"goodsAmount"`
// CargoItemList 小包信息(国际业务专用,其他业务不需要填写)
CargoItemList []CargoItem `json:"cargoItemList"`
}
// CargoItem 小包信息
type CargoItem struct {
// SerialNumber 小包号
SerialNumber string `json:"serialNumber"`
// ReferenceNumber 关联单号
ReferenceNumber string `json:"referenceNumber"`
// ProductId 商品ID
ProductId string `json:"productId"`
// Name 名称
Name string `json:"name"`
// Qty 数量
Qty int `json:"qty"`
// UnitPrice 单价
UnitPrice float64 `json:"unitPrice"`
// Amount 总价
Amount float64 `json:"amount"`
// Currency 币种
Currency string `json:"currency"`
// Weight 重量kg
Weight float64 `json:"weight"`
// Remark 备注
Remark string `json:"remark"`
}
// CustomerInfo 客户信息
type CustomerInfo struct {
// SiteCode 网点编码必传,测试传值"siteCode":"666666"
SiteCode string `json:"siteCode"`
// CustomerName 客户编码,测试传值"customerName":"666666000001"
CustomerName string `json:"customerName"`
// SitePwd 电子面单密码,测试传值"sitePwd":"abc123"
SitePwd string `json:"sitePwd"`
// MonthCustomerCode 月结客户编码(不传单号需调度才传月结编号)如果填写一般和客户编号值相同
MonthCustomerCode string `json:"monthCustomerCode"`
}
// InternationalInfo 国际订单附属信息
type InternationalInfo struct {
// InternationalProductType 国际业务类型01-国际进口02-国际保税03-国际直邮)
InternationalProductType string `json:"internationalProductType"`
// CustomsDeclaration 是否报关,默认为否
CustomsDeclaration bool `json:"customsDeclaration"`
// SenderCountry 发件人所在国家,国际件为必填字段
SenderCountry string `json:"senderCountry"`
// ReceiverCountry 收件人所在国家,国际件为必填字段
ReceiverCountry string `json:"receiverCountry"`
}
// AssignInfo 指定网点揽收信息
type AssignInfo struct {
// TakeCompanyCode 指定取件的网点编号
TakeCompanyCode string `json:"takeCompanyCode"`
// TakeUserCode 指定取件的业务员编号指定业务员时takeCompanyCode可传可不传若传必须传正确举例寄件地址是上海只能是指定上海业务员取件
TakeUserCode string `json:"takeUserCode"`
}
// InsuredInfo 保价信息
type InsuredInfo struct {
// InsuredValue 保价金额,单位:元(保价服务时必填,精确到小数点后两位)
InsuredValue string `json:"insuredValue"`
// GoodsValue 物品价值,单位:元(保价服务时必填,精确到小数点后两位)
GoodsValue string `json:"goodsValue"`
}
// 极兔快递配置结构体
type JituConfig struct {
APIAccount string // 接入方在平台的api账户标识
APIKey string // 用于生成digest的密钥
BaseURL string
CustomerCode string
}
// 请求头结构体
type JituHeader struct {
APIAccount string `json:"apiAccount"`
Digest string `json:"digest"`
Timestamp string `json:"timestamp"`
}
// 地址结构体
type Address struct {
Name string `json:"name"`
Company string `json:"company,omitempty"`
PostCode string `json:"postCode,omitempty"`
MailBox string `json:"mailBox,omitempty"`
Mobile string `json:"mobile"`
Phone string `json:"phone,omitempty"`
CountryCode string `json:"countryCode"`
Prov string `json:"prov"`
City string `json:"city"`
Area string `json:"area"`
Town string `json:"town,omitempty"`
Street string `json:"street,omitempty"`
Address string `json:"address"`
}
// 商品项结构体
type Item struct {
ItemType string `json:"itemType,omitempty"`
ItemName string `json:"itemName"`
ChineseName string `json:"chineseName,omitempty"`
EnglishName string `json:"englishName,omitempty"`
Number int `json:"number"`
ItemValue string `json:"itemValue,omitempty"`
PriceCurrency string `json:"priceCurrency,omitempty"`
Desc string `json:"desc,omitempty"`
ItemURL string `json:"itemUrl,omitempty"`
}
// 扩展信息结构体
type ExtendInfo struct {
IsFourLevelAddress string `json:"isFourLevelAddress,omitempty"`
}
// 业务内容结构体
type BizContent struct {
CustomerCode string `json:"customerCode"`
Digest string `json:"digest"`
Network *string `json:"network,omitempty"`
TxlogisticId string `json:"txlogisticId"`
ExpressType string `json:"expressType"`
OrderType string `json:"orderType"`
ServiceType string `json:"serviceType"`
DeliveryType string `json:"deliveryType"`
PayType string `json:"payType"`
Sender Address `json:"sender"`
Receiver Address `json:"receiver"`
SendStartTime string `json:"sendStartTime"`
SendEndTime string `json:"sendEndTime"`
GoodsType string `json:"goodsType"`
Length float64 `json:"length"`
Width float64 `json:"width"`
Height float64 `json:"height"`
Weight string `json:"weight"`
TotalQuantity int `json:"totalQuantity"`
ItemsValue *string `json:"itemsValue,omitempty"`
PriceCurrency *string `json:"priceCurrency,omitempty"`
OfferFee *string `json:"offerFee,omitempty"`
Remark *string `json:"remark,omitempty"`
Items []Item `json:"items"`
CustomsInfo *string `json:"customsInfo,omitempty"`
PostSiteCode *string `json:"postSiteCode,omitempty"`
PostSiteName *string `json:"postSiteName,omitempty"`
PostSiteAddress *string `json:"postSiteAddress,omitempty"`
RealName *string `json:"realName,omitempty"`
ExtendInfo ExtendInfo `json:"extendInfo,omitempty"`
}
// 生成digest
func generateDigest(data string) string {
hash := md5.Sum([]byte(data))
return base64.StdEncoding.EncodeToString(hash[:])
}
// 生成请求头
func generateHeaders(config *JituConfig) JituHeader {
timestamp := fmt.Sprintf("%d", time.Now().UnixNano()/int64(time.Millisecond))
// 根据极兔API文档header中的digest通常是apiAccount + apiKey + timestamp的MD5
headerData := config.APIAccount + config.APIKey + timestamp
headerDigest := generateDigest(headerData)
return JituHeader{
APIAccount: config.APIAccount,
Digest: headerDigest,
Timestamp: timestamp,
}
}
// 创建订单请求
func CreateJituOrder(config *JituConfig, bizContent *BizContent) (map[string]interface{}, error) {
// 设置请求URL--测试地址
jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8"
// 设置请求URL--正式地址
//jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder"
// 生成请求头
headers := generateHeaders(config)
// 设置业务内容中的customerCode
bizContent.CustomerCode = config.CustomerCode
// 生成业务内容的digest这里需要根据极兔API的实际要求生成
// 通常是对bizContent的某些字段进行签名
bizContentDigest := generateDigest(bizContent.TxlogisticId + config.APIKey)
bizContent.Digest = bizContentDigest
// 将bizContent转换为JSON字符串
bizContentJSON, err := json.Marshal(bizContent)
if err != nil {
return nil, fmt.Errorf("序列化bizContent失败: %v", err)
}
// 构建请求体
requestBody := map[string]interface{}{
"bizContent": string(bizContentJSON),
}
// 使用gorequest发送请求
request := gorequest.New()
// 设置请求头
request.Set("apiAccount", headers.APIAccount)
request.Set("digest", headers.Digest)
request.Set("timestamp", headers.Timestamp)
request.Set("Content-Type", "application/x-www-form-urlencoded")
// 发送POST请求
resp, body, errs := request.Post(jtUrl).
Type("form").
Send(requestBody).
End()
if len(errs) > 0 {
return nil, fmt.Errorf("请求失败: %v", errs)
}
if resp.StatusCode != 200 {
return nil, fmt.Errorf("请求返回非200状态码: %d, 响应: %s", resp.StatusCode, body)
}
// 解析响应
var response map[string]interface{}
if err := json.Unmarshal([]byte(body), &response); err != nil {
return nil, fmt.Errorf("解析响应失败: %v, 原始响应: %s", err, body)
}
return response, nil
}
// 主函数
func main() {
}