package main /* #include */ 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时,orderType:1:全网件 2:预约件。partnerType为2时,orderType:1:全网件 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"` // 网点code(orderVasList.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为2,orderType传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"` // 取件开始时间(传值此字段则需必传vasType:twoHour) EndTime string `json:"endTime"` // 取件截止时间(传值此字段则需必传vasType:twoHour) } // 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() { }