package main /* #include */ import "C" import ( "bytes" "crypto/md5" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io" "mime/multipart" "net/http" "net/url" "sort" "strconv" "strings" "time" "unsafe" "github.com/parnurzeal/gorequest" "github.com/tjfoc/gmsm/sm4" ) // 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) { 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) } // 创建请求实例 req := gorequest.New() // 发送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() // 按文档要求的格式 signString := sortedJSON + appSecret // 计算MD5 md5Sum := md5.Sum([]byte(signString)) // Base64编码 sign := base64.StdEncoding.EncodeToString(md5Sum[:]) return sign, nil } // 中通快递--取消订单接口 func ztoOpenCancelPreOrder(requestJSON, appKey, appSecret string) (string, error) { if appKey == "" || appSecret == "" { return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") } // 正式环境 ztoUrl := "https://japi.zto.com/zto.open.cancelPreOrder" // 测试环境 //ztoUrl := "https://japi-test.zto.com/zto.open.cancelPreOrder" // 生成签名 dataDigest, err := ztoGenerateSign(requestJSON, appSecret) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 创建请求实例 req := gorequest.New() // 发送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 ztoOpenGetOrderInfo(requestJSON, appKey, appSecret string) (string, error) { if appKey == "" || appSecret == "" { return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") } // 正式环境 ztoUrl := "https://japi.zto.com/zto.open.getOrderInfo" // 测试环境 //ztoUrl := "https://japi-test.zto.com/zto.open.getOrderInfo" // 生成签名 dataDigest, err := ztoGenerateSign(requestJSON, appSecret) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 创建请求实例 req := gorequest.New() // 发送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 ztoOpenQueryAvailableBalanceNew(requestJSON, appKey, appSecret string) (string, error) { if appKey == "" || appSecret == "" { return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") } // 正式环境 ztoUrl := "https://japi.zto.com/zto.open.queryAvailableBalanceNew" // 测试环境 //ztoUrl := "https://japi-test.zto.com/zto.open.queryAvailableBalanceNew" // 生成签名 dataDigest, err := ztoGenerateSign(requestJSON, appSecret) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 创建请求实例 req := gorequest.New() // 发送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 } // 中通快递--请求生成面单图片/PDF func ztoOpenOrderPrint(requestJSON, appKey, appSecret string) (string, error) { if appKey == "" || appSecret == "" { return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") } // 正式环境 ztoUrl := "https://japi.zto.com/zto.open.order.print" // 测试环境 //ztoUrl := "https://japi-test.zto.com/zto.open.order.print" // 生成签名 dataDigest, err := ztoGenerateSign(requestJSON, appSecret) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 创建请求实例 req := gorequest.New() // 发送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 ztoOpenBindingEaccount(requestJSON, appKey, appSecret string) (string, error) { if appKey == "" || appSecret == "" { return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") } // 正式环境 ztoUrl := "https://japi.zto.com/zto.open.bindingEaccount" // 测试环境 //ztoUrl := "https://japi-test.zto.com/zto.open.bindingEaccount" // 生成签名 dataDigest, err := ztoGenerateSign(requestJSON, appSecret) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 创建请求实例 req := gorequest.New() // 发送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 } // 中通快递--网点code查询网点信息 func ztoVipQuerySiteInfoByCode(requestJSON, appKey, appSecret string) (string, error) { if appKey == "" || appSecret == "" { return "", fmt.Errorf("中通API配置错误: appKey或appSecret未设置") } // 正式环境 ztoUrl := "https://japi.zto.com/zto.vip.querySiteInfoByCode" // 测试环境 //ztoUrl := "https://japi-test.zto.com/zto.vip.querySiteInfoByCode" // 生成签名 dataDigest, err := ztoGenerateSign(requestJSON, appSecret) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 创建请求实例 req := gorequest.New() // 发送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 jtOrderAddOrder(bizContentJSONStr, apiAccount, privateKey string) (string, error) { // 正式环境 jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder" // 测试环境 // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/addOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" // 生成签名(传入bizContent的JSON字符串) digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 当前时间戳 timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 构造请求体 requestBody := map[string]string{ "bizContent": bizContentJSONStr, } // 创建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). Send(requestBody). 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, 响应: %s", resp.StatusCode, body) } return body, nil } // 极兔快递--创建订单(带运单号) func jtOrderV2AddOrder(bizContentJSONStr, apiAccount, privateKey string) (string, error) { // 正式环境 jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/v2/addOrder" // 测试环境 // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/v2/addOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" // 生成签名(传入bizContent的JSON字符串) digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 当前时间戳 timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 构造请求体 requestBody := map[string]string{ "bizContent": bizContentJSONStr, } // 创建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). Send(requestBody). 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, 响应: %s", resp.StatusCode, body) } return body, nil } // 极兔快递--查询订单 func jtOrderGetOrders(bizContentJSONStr, apiAccount, privateKey string) (string, error) { // 正式环境 jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/getOrders" // 测试环境 // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/getOrders?uuid=c71ba79a80fc4307ae81f09e65b89af8" // 生成签名(传入bizContent的JSON字符串) digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 当前时间戳 timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 构造请求体 requestBody := map[string]string{ "bizContent": bizContentJSONStr, } // 创建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). Send(requestBody). 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, 响应: %s", resp.StatusCode, body) } return body, nil } // 极兔快递--取消订单 func jtOrderCancelOrder(bizContentJSONStr, apiAccount, privateKey string) (string, error) { // 正式环境 jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/cancelOrder" // 测试环境 // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/cancelOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" // 生成签名(传入bizContent的JSON字符串) digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 当前时间戳 timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 构造请求体 requestBody := map[string]string{ "bizContent": bizContentJSONStr, } // 创建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). Send(requestBody). 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, 响应: %s", resp.StatusCode, body) } return body, nil } // 极兔快递--电子面单账号检验 func jtVipCheckCusPwd(bizContentJSONStr, apiAccount, privateKey string) (string, error) { // 正式环境 jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/vip/checkCusPwd" // 测试环境 // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/vip/checkCusPwd?uuid=c71ba79a80fc4307ae81f09e65b89af8" // 生成签名(传入bizContent的JSON字符串) digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 当前时间戳 timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 构造请求体 requestBody := map[string]string{ "bizContent": bizContentJSONStr, } // 创建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). Send(requestBody). 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, 响应: %s", resp.StatusCode, body) } return body, nil } // 极兔快递--电子面单余额查询 func jtEssBalance(bizContentJSONStr, apiAccount, privateKey string) (string, error) { // 正式环境 jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/ess/balance" // 测试环境 // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/ess/balance?uuid=c71ba79a80fc4307ae81f09e65b89af8" // 生成签名(传入bizContent的JSON字符串) digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 当前时间戳 timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 构造请求体 requestBody := map[string]string{ "bizContent": bizContentJSONStr, } // 创建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). Send(requestBody). 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, 响应: %s", resp.StatusCode, body) } return body, nil } // 极兔快递--获取电子面单 func jtBillCodeGetBatchBillCode(bizContentJSONStr, apiAccount, privateKey string) (string, error) { // 正式环境 jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/billCode/getBatchBillCode" // 测试环境 // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/billCode/getBatchBillCode?uuid=c71ba79a80fc4307ae81f09e65b89af8" // 生成签名(传入bizContent的JSON字符串) digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 当前时间戳 timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 构造请求体 requestBody := map[string]string{ "bizContent": bizContentJSONStr, } // 创建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). Send(requestBody). 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, 响应: %s", resp.StatusCode, body) } return body, nil } // 极兔快递--面单打印 func jtOrderPrintOrder(bizContentJSONStr, apiAccount, privateKey string) (string, error) { // 正式环境 jtUrl := "https://openapi.bxexpress.com.cn/webopenplatformapi/api/order/printOrder" // 测试环境 // jtUrl := "https://uat-openapi.bxexpress.com.cn/webopenplatformapi/api/order/printOrder?uuid=c71ba79a80fc4307ae81f09e65b89af8" // 生成签名(传入bizContent的JSON字符串) digest, err := jtGenerateDigest(bizContentJSONStr, privateKey) if err != nil { return "", fmt.Errorf("生成签名失败: %w", err) } // 当前时间戳 timestamp := fmt.Sprintf("%d", time.Now().Unix()) // 构造请求体 requestBody := map[string]string{ "bizContent": bizContentJSONStr, } // 创建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). Send(requestBody). 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, 响应: %s", resp.StatusCode, body) } return body, nil } // jtGenerateDigest 极兔生成Headers中的digest签名 // bizContentJSONStr: 业务参数的JSON字符串 // privateKey: 平台分配的私钥 func jtGenerateDigest(bizContentJSONStr, privateKey string) (string, error) { // 1. 拼接JSON字符串和私钥 signStr := bizContentJSONStr + privateKey // 2. 计算MD5 hash := md5.Sum([]byte(signStr)) // 3. Base64编码 digest := base64.StdEncoding.EncodeToString(hash[:]) return digest, nil } // GenerateBusinessDigest 极兔生成业务参数中的digest签名(用于bizContent内部) // customerCode: 客户编码,如"J0086474299" // plainPassword: 明文密码,如"H5CD3zE6" // businessKey: 业务密钥(固定为"jadada236t2") // privateKey: 平台分配的私钥 // 返回:加密后的密码和digest签名 func GenerateBusinessDigest(customerCode, plainPassword, businessKey, privateKey string) (encryptedPwd, digest string, err error) { // 1. 计算加密后的密码 pwd = MD5(明文密码 + 业务密钥),32位大写 pwdStr := plainPassword + businessKey pwdHash := md5.Sum([]byte(pwdStr)) encryptedPwd = strings.ToUpper(hex.EncodeToString(pwdHash[:])) // 2. 拼接 customerCode + pwd + privateKey signStr := customerCode + encryptedPwd + privateKey // 3. 计算MD5 hash := md5.Sum([]byte(signStr)) // 4. Base64编码 digest = base64.StdEncoding.EncodeToString(hash[:]) return encryptedPwd, digest, 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[:]) } // 韵达快递-电子面单取消 func ydCancelBmOrder(requestJSON, appKey, appSecret string) (string, error) { // 确定环境 baseURL := "https://openapi.yundaex.com/openapi-api/v1/accountOrder/cancelBmOrder" // 测试环境 //baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/cancelBmOrder" // 1. 构建基础参数(用于签名和请求头) timestamp := time.Now().UnixMilli() reqTime := fmt.Sprintf("%d", timestamp) // 针对JSON字符串进行升序排序 key, err := reorderJSONFieldsByKey(requestJSON, true) if err != nil { return "", fmt.Errorf("排序失败: %v", err) } fmt.Println("排序后的JSON字符串:", key) sign := ydGenerateSign(key, appSecret) // 3. 构建 Headers(根据文档) headers := map[string]string{ "Content-Type": "application/json; charset=UTF-8", "app-key": appKey, "req-time": reqTime, "sign": sign, // 需要实现签名方法 } // 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 } // 韵达快递--电子面单下单 // 参数:requestJSON:业务参数JSON字符串 appid:合作商appid(等同app-key) appSecret:签名 partnerID func ydCreateBmOrder(requestJSON, appKey, appSecret string) (string, error) { // 确定环境 baseURL := "https://openapi.yundaex.com/openapi-api/v1/accountOrder/createBmOrder" // 测试环境 //baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/createBmOrder" // 1. 构建基础参数(用于签名和请求头) timestamp := time.Now().UnixMilli() reqTime := fmt.Sprintf("%d", timestamp) // 针对JSON字符串进行升序排序 key, err := reorderJSONFieldsByKey(requestJSON, true) if err != nil { return "", fmt.Errorf("排序失败: %v", err) } fmt.Println("排序后的JSON字符串:", key) sign := ydGenerateSign(key, appSecret) // 3. 构建 Headers(根据文档) headers := map[string]string{ "Content-Type": "application/json; charset=UTF-8", "app-key": appKey, "req-time": reqTime, "sign": sign, // 需要实现签名方法 } // 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 } // 按键名排序JSON字段 func reorderJSONFieldsByKey(requestJSON string, sortAscending bool) (string, error) { // 解析JSON var obj map[string]interface{} if err := json.Unmarshal([]byte(requestJSON), &obj); err != nil { return "", fmt.Errorf("JSON解析失败: %v", err) } // 获取所有键并排序 keys := make([]string, 0, len(obj)) for k := range obj { keys = append(keys, k) } if sortAscending { sort.Strings(keys) // 升序 } else { sort.Sort(sort.Reverse(sort.StringSlice(keys))) // 降序 } // 创建有序结果 orderedObj := make(map[string]interface{}) for _, key := range keys { orderedObj[key] = obj[key] } marshal, _ := json.Marshal(orderedObj) return string(marshal), nil } // 韵达快递计算签名 func ydGenerateSign(content, secretKey string) string { text := content + "_" + secretKey // 计算 MD5 hash := md5.Sum([]byte(text)) return fmt.Sprintf("%x", hash[:]) } // 韵达快递-电子面单余量查询接口 func ydSearchCount(requestJSON, appKey, appSecret string) (string, error) { // 确定环境 baseURL := "https://openapi.yundaex.com/openapi-api/v1/accountOrder/searchCount" // 测试环境 //baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/searchCount" // 排序 key, err := reorderJSONFieldsByKey(requestJSON, true) if err != nil { return "", fmt.Errorf("排序失败: %v", err) } sign := ydGenerateSign(key, appSecret) fmt.Println("appKey=", appKey, " appSecret=", appSecret, " requestJSON=", requestJSON, " sign=", sign) // 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": sign, // 需要实现签名方法 } // 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 ydBmGetPdfInfo(requestJSON, appKey, appSecret string) (string, error) { // 确定环境 baseURL := "https://openapi.yundaex.com/openapi/outer/v1/bm/getPdfInfo" // 测试环境 //baseURL := "https://u-openapi.yundasys.com/openapi/outer/v1/bm/getPdfInfo" // 排序 key, err := reorderJSONFieldsByKey(requestJSON, true) if err != nil { return "", fmt.Errorf("排序失败: %v", err) } fmt.Println("排序后的JSON字符串:", key) // 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(key, 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 ydUpdateBmOrder(requestJSON, appKey, appSecret string) (string, error) { // 确定环境 baseURL := "https://openapi.yundaex.com/openapi-api/v1/accountOrder/updateBmOrder" // 测试环境 //baseURL := "https://u-openapi.yundasys.com/openapi-api/v1/accountOrder/updateBmOrder" // 1. 构建基础参数(用于签名和请求头) timestamp := time.Now().UnixMilli() reqTime := fmt.Sprintf("%d", timestamp) // 针对JSON字符串进行升序排序 key, err := reorderJSONFieldsByKey(requestJSON, true) if err != nil { return "", fmt.Errorf("排序失败: %v", err) } fmt.Println("排序后的JSON字符串:", key) sign := ydGenerateSign(key, appSecret) // 3. 构建 Headers(根据文档) headers := map[string]string{ "Content-Type": "application/json; charset=UTF-8", "app-key": appKey, "req-time": reqTime, "sign": sign, // 需要实现签名方法 } // 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 } // ========================== 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) } // ZtoOpenCancelPreOrder 中通快递--取消订单接口 // //export ZtoOpenCancelPreOrder func ZtoOpenCancelPreOrder(requestJSON, appKey, appSecret *C.char) *C.char { requestJSONStr := C.GoString(requestJSON) appKeyStr := C.GoString(appKey) appSecretStr := C.GoString(appSecret) info, err := ztoOpenCancelPreOrder(requestJSONStr, appKeyStr, appSecretStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // ZtoOpenGetOrderInfo 中通快递--查询订单接口 // //export ZtoOpenGetOrderInfo func ZtoOpenGetOrderInfo(requestJSON, appKey, appSecret *C.char) *C.char { requestJSONStr := C.GoString(requestJSON) appKeyStr := C.GoString(appKey) appSecretStr := C.GoString(appSecret) info, err := ztoOpenGetOrderInfo(requestJSONStr, appKeyStr, appSecretStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // ZtoOpenQueryAvailableBalanceNew 中通快递--获取打单余额 // //export ZtoOpenQueryAvailableBalanceNew func ZtoOpenQueryAvailableBalanceNew(requestJSON, appKey, appSecret *C.char) *C.char { requestJSONStr := C.GoString(requestJSON) appKeyStr := C.GoString(appKey) appSecretStr := C.GoString(appSecret) info, err := ztoOpenQueryAvailableBalanceNew(requestJSONStr, appKeyStr, appSecretStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // ZtoOpenOrderPrint 中通快递--请求生成面单图片/PDF // //export ZtoOpenOrderPrint func ZtoOpenOrderPrint(requestJSON, appKey, appSecret *C.char) *C.char { requestJSONStr := C.GoString(requestJSON) appKeyStr := C.GoString(appKey) appSecretStr := C.GoString(appSecret) info, err := ztoOpenOrderPrint(requestJSONStr, appKeyStr, appSecretStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // ZtoOpenBindingEaccount 中通快递--绑定电子面单 // //export ZtoOpenBindingEaccount func ZtoOpenBindingEaccount(requestJSON, appKey, appSecret *C.char) *C.char { requestJSONStr := C.GoString(requestJSON) appKeyStr := C.GoString(appKey) appSecretStr := C.GoString(appSecret) info, err := ztoOpenBindingEaccount(requestJSONStr, appKeyStr, appSecretStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // ZtoVipQuerySiteInfoByCode 中通快递--网点code查询网点信息 // //export ZtoVipQuerySiteInfoByCode func ZtoVipQuerySiteInfoByCode(requestJSON, appKey, appSecret *C.char) *C.char { requestJSONStr := C.GoString(requestJSON) appKeyStr := C.GoString(appKey) appSecretStr := C.GoString(appSecret) info, err := ztoVipQuerySiteInfoByCode(requestJSONStr, appKeyStr, appSecretStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // JtOrderAddOrder 极兔快递--创建订单接口 // //export JtOrderAddOrder func JtOrderAddOrder(bizContentJSON, apiAccount, privateKey *C.char) *C.char { bizContentJSONStr := C.GoString(bizContentJSON) apiAccountStr := C.GoString(apiAccount) privateKeyStr := C.GoString(privateKey) info, err := jtOrderAddOrder(bizContentJSONStr, apiAccountStr, privateKeyStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // JtOrderV2AddOrder 极兔快递--创建订单(带运单号) // //export JtOrderV2AddOrder func JtOrderV2AddOrder(bizContentJSON, apiAccount, privateKey *C.char) *C.char { bizContentJSONStr := C.GoString(bizContentJSON) apiAccountStr := C.GoString(apiAccount) privateKeyStr := C.GoString(privateKey) info, err := jtOrderV2AddOrder(bizContentJSONStr, apiAccountStr, privateKeyStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // JtOrderGetOrders 极兔快递--查询订单 // //export JtOrderGetOrders func JtOrderGetOrders(bizContentJSON, apiAccount, privateKey *C.char) *C.char { bizContentJSONStr := C.GoString(bizContentJSON) apiAccountStr := C.GoString(apiAccount) privateKeyStr := C.GoString(privateKey) info, err := jtOrderGetOrders(bizContentJSONStr, apiAccountStr, privateKeyStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // JtOrderCancelOrder 极兔快递--取消订单 // //export JtOrderCancelOrder func JtOrderCancelOrder(bizContentJSON, apiAccount, privateKey *C.char) *C.char { bizContentJSONStr := C.GoString(bizContentJSON) apiAccountStr := C.GoString(apiAccount) privateKeyStr := C.GoString(privateKey) info, err := jtOrderCancelOrder(bizContentJSONStr, apiAccountStr, privateKeyStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // JtVipCheckCusPwd 极兔快递--电子面单账号检验 // //export JtVipCheckCusPwd func JtVipCheckCusPwd(bizContentJSON, apiAccount, privateKey *C.char) *C.char { bizContentJSONStr := C.GoString(bizContentJSON) apiAccountStr := C.GoString(apiAccount) privateKeyStr := C.GoString(privateKey) info, err := jtVipCheckCusPwd(bizContentJSONStr, apiAccountStr, privateKeyStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // JtEssBalance 极兔快递--电子面单余额查询 // //export JtEssBalance func JtEssBalance(bizContentJSON, apiAccount, privateKey *C.char) *C.char { bizContentJSONStr := C.GoString(bizContentJSON) apiAccountStr := C.GoString(apiAccount) privateKeyStr := C.GoString(privateKey) info, err := jtEssBalance(bizContentJSONStr, apiAccountStr, privateKeyStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // JtBillCodeGetBatchBillCode 极兔快递--获取电子面单 // //export JtBillCodeGetBatchBillCode func JtBillCodeGetBatchBillCode(bizContentJSON, apiAccount, privateKey *C.char) *C.char { bizContentJSONStr := C.GoString(bizContentJSON) apiAccountStr := C.GoString(apiAccount) privateKeyStr := C.GoString(privateKey) info, err := jtBillCodeGetBatchBillCode(bizContentJSONStr, apiAccountStr, privateKeyStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // JtOrderPrintOrder 极兔快递--面单打印 // //export JtOrderPrintOrder func JtOrderPrintOrder(bizContentJSON, apiAccount, privateKey *C.char) *C.char { bizContentJSONStr := C.GoString(bizContentJSON) apiAccountStr := C.GoString(apiAccount) privateKeyStr := C.GoString(privateKey) info, err := jtOrderPrintOrder(bizContentJSONStr, apiAccountStr, privateKeyStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } // EmsAmpApiOpen 邮政快递--订单接入接口 // //export EmsAmpApiOpen func EmsAmpApiOpen(apiCode, senderNo, authorization, secretKey, requestJSON *C.char) *C.char { apiCodeStr := C.GoString(apiCode) senderNoStr := C.GoString(senderNo) authorizationStr := C.GoString(authorization) secretKeyStr := C.GoString(secretKey) requestJSONStr := C.GoString(requestJSON) info, err := emsAmpApiOpen(apiCodeStr, senderNoStr, authorizationStr, secretKeyStr, requestJSONStr) 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) } // YdCancelBmOrder 韵达快递-电子面单取消 // //export YdCancelBmOrder func YdCancelBmOrder(requestJSON, appKey, appSecret *C.char) *C.char { requestJSONStr := C.GoString(requestJSON) appKeyStr := C.GoString(appKey) appSecretStr := C.GoString(appSecret) info, err := ydCancelBmOrder(requestJSONStr, appKeyStr, appSecretStr) 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) } // YdSearchCount 韵达快递-电子面单余量查询接口 // //export YdSearchCount func YdSearchCount(requestJSON, appKey, appSecret *C.char) *C.char { requestJSONStr := C.GoString(requestJSON) appKeyStr := C.GoString(appKey) appSecretStr := C.GoString(appSecret) info, err := ydSearchCount(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) } // YdUpdateBmOrder 韵达快递-电子面单更新接口 // //export YdUpdateBmOrder func YdUpdateBmOrder(requestJSON, appKey, appSecret *C.char) *C.char { requestJSONStr := C.GoString(requestJSON) appKeyStr := C.GoString(appKey) appSecretStr := C.GoString(appSecret) info, err := ydUpdateBmOrder(requestJSONStr, appKeyStr, appSecretStr) 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)) } ///**********************************************************邮政开始***************************************************************// // MultipartFormData 存储multipart表单数据 type MultipartFormData struct { fields map[string]string files map[string]FileInfo } // FileInfo 文件信息结构 type FileInfo struct { FieldName string FileName string FileData []byte } // NewMultipartFormData 创建新的multipart表单数据对象 func NewMultipartFormData() *MultipartFormData { return &MultipartFormData{ fields: make(map[string]string), files: make(map[string]FileInfo), } } /* 邮政快递--订单接入接口 请求参数: apiCode 接口代码 senderNo 生产协议客户号 authorization 授权码 secretKey 密钥 requestJSON 业务报文JSON字符串 */ func emsAmpApiOpen(apiCode, senderNo, authorization, secretKey, requestJSON string) (string, error) { url := "https://api.ems.com.cn/amp-prod-api/f/amp/api/open" contentToEncrypt := requestJSON + secretKey signature, err := GenerateSM4Signature(contentToEncrypt, secretKey) if err != nil { return "", err } formData := NewMultipartFormData() formData.AddField("apiCode", apiCode) formData.AddField("senderNo", senderNo) formData.AddField("authorization", authorization) formData.AddField("timeStamp", time.Now().Format("2006-01-02 15:04:05")) formData.AddField("logitcsInterface", signature) headers := map[string]string{ "User-Agent": "ZTO-API-Client/1.0", } response, err := PostMultipartRequest(url, formData, headers, 30*time.Second) if err != nil { return "", err } return string(response), 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 } // PostMultipartRequest POST multipart/form-data请求 func PostMultipartRequest(url string, formData *MultipartFormData, headers map[string]string, timeout time.Duration) ([]byte, error) { // 创建payload缓冲区 payload := &bytes.Buffer{} writer := multipart.NewWriter(payload) // 添加表单字段 for key, value := range formData.fields { if err := writer.WriteField(key, value); err != nil { return nil, fmt.Errorf("写入字段 %s 失败: %v", key, err) } } // 添加文件 for _, fileInfo := range formData.files { part, err := writer.CreateFormFile(fileInfo.FieldName, fileInfo.FileName) if err != nil { return nil, fmt.Errorf("创建文件字段 %s 失败: %v", fileInfo.FieldName, err) } if _, err := part.Write(fileInfo.FileData); err != nil { return nil, fmt.Errorf("写入文件数据失败: %v", err) } } // 关闭writer if err := writer.Close(); err != nil { return nil, fmt.Errorf("关闭writer失败: %v", err) } // 创建HTTP客户端 client := &http.Client{ Timeout: timeout, } // 创建请求 req, err := http.NewRequest("POST", url, payload) if err != nil { return nil, fmt.Errorf("创建请求失败: %v", err) } // 设置Content-Type req.Header.Set("Content-Type", writer.FormDataContentType()) // 设置自定义请求头 for key, value := range headers { req.Header.Set(key, value) } // 发送请求 resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("发送请求失败: %v", err) } defer resp.Body.Close() // 读取响应体 body, err := io.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应失败: %v", err) } // 检查HTTP状态码 if resp.StatusCode != http.StatusOK { return body, fmt.Errorf("请求返回非200状态码: %d, 响应: %s", resp.StatusCode, string(body)) } return body, nil } // AddField 添加表单字段 func (m *MultipartFormData) AddField(key, value string) { m.fields[key] = value } // AddFile 添加文件 func (m *MultipartFormData) AddFile(fieldName, fileName string, fileData []byte) { m.files[fieldName] = FileInfo{ FieldName: fieldName, FileName: fileName, FileData: fileData, } } ///**********************************************************邮政结束***************************************************************// ///**********************************************************申通开始***************************************************************// // 构建请求数据 func getStoHeader(apiName string, appkey string, code string, secretKey string, toKey string, content string) map[string]string { dataDigest := getStoSignature(content, secretKey) var param = map[string]string{ "api_name": apiName, "content": content, "from_appkey": appkey, "from_code": code, "to_appkey": toKey, "to_code": toKey, "data_digest": dataDigest, } return param } func requestSto(apiName string, appkey string, code string, secretKey string, toKey string, content string) (string, error) { //var url = "http://cloudinter-linkgatewaytest.sto.cn/gateway/link.do" url := "https://cloudinter-linkgateway.sto.cn/gateway/link.do" // 获取请求参数 params := getStoHeader(apiName, appkey, code, secretKey, toKey, content) // 创建 multipart form-data 请求体 body := &bytes.Buffer{} writer := multipart.NewWriter(body) // 添加所有参数到 form-data for key, value := range params { err := writer.WriteField(key, value) if err != nil { return "", err } } // 关闭 writer 以完成 multipart 数据 err := writer.Close() if err != nil { return "", err } // 创建 HTTP 请求 req, err := http.NewRequest("POST", url, body) if err != nil { return "", err } // 设置 Content-Type 头,包含 boundary req.Header.Set("Content-Type", writer.FormDataContentType()) // 发送请求 client := &http.Client{} resp, err := client.Do(req) if err != nil { return "", err } defer resp.Body.Close() // 读取响应 responseBody, err := io.ReadAll(resp.Body) if err != nil { return "", err } return string(responseBody), nil } // 生成签名 func getStoSignature(content, secretKey string) string { text := content + secretKey hash := md5.Sum([]byte(text)) return base64.StdEncoding.EncodeToString(hash[:]) } // RequestSto 申通快递统一请求为入口 // //export RequestSto func RequestSto(apiName, appkey, code, secretKey, toKey, requestJSON *C.char) *C.char { apiNameStr := C.GoString(apiName) appkeyStr := C.GoString(appkey) codeStr := C.GoString(code) secretKeyStr := C.GoString(secretKey) toKeyStr := C.GoString(toKey) requestJSONStr := C.GoString(requestJSON) info, err := requestSto(apiNameStr, appkeyStr, codeStr, secretKeyStr, toKeyStr, requestJSONStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } ///**********************************************************申通结束***************************************************************// ///**********************************************************圆通开始***************************************************************// func requestYto(apiName, customerCode, secretKey, requestJSON string) (string, error) { // 构建URL //urlStr := fmt.Sprintf("https://openuat.yto56test.com:6443/open/%s/v1/zc0IzW/%s", apiName, customerCode) urlStr := fmt.Sprintf("https://openapi.yto.net.cn:11443/open/%s/v1/zc0IzW/%s", apiName, customerCode) // 当前毫秒级时间戳(13位) timestamp := strconv.FormatInt(time.Now().UnixMilli(), 10) // 构建待签名字符串: param + method + v // 注意:method应该是apiName,版本号根据URL中的v1确定 dataToSign := requestJSON + apiName + "v1" // 生成签名 sign := EncryptSignForOpen(dataToSign, secretKey) // 构建请求参数 params := url.Values{} params.Set("timestamp", timestamp) params.Set("param", requestJSON) params.Set("sign", sign) params.Set("format", "JSON") // 发送POST请求 resp, err := http.PostForm(urlStr, params) if err != nil { return "", err } defer resp.Body.Close() // 读取响应 body, err := io.ReadAll(resp.Body) if err != nil { return "", err } return string(body), nil } // EncryptSignForOpen 开放平台公共加密方法 func EncryptSignForOpen(data string, secret string) string { combined := data + secret hash := md5.Sum([]byte(combined)) sign := base64.StdEncoding.EncodeToString(hash[:]) return sign } // RequestYto 申通快递统一请求为入口 // //export RequestYto func RequestYto(apiName, customerCode, secretKey, requestJSON *C.char) *C.char { apiNameStr := C.GoString(apiName) customerCodeStr := C.GoString(customerCode) secretKeytr := C.GoString(secretKey) requestJSONStr := C.GoString(requestJSON) info, err := requestYto(apiNameStr, customerCodeStr, secretKeytr, requestJSONStr) if err != nil { return C.CString(err.Error()) } return C.CString(info) } ///**********************************************************圆通结束***************************************************************// // 主函数 func main() { //apiName := "OMS_EXPRESS_ORDER_CREATE" //appkey := "CAKgwDceADOybmU" //code := "CAKgwDceADOybmU" //secretKey := "2kLMr75pURbVh3FnWzY6SCr7SM6qsoO3" //content := "{\"orderNo\":\"8885452262\",\"orderSource\":\"****\",\"billType\":\"00\",\"orderType\":\"01\",\"sender\":{\"name\":\"测试名称\",\"tel\":\"0558-45778586\",\"mobile\":\"18775487548\",\"postCode\":\"100001\",\"country\":\"中国\",\"province\":\"安徽\",\"city\":\"合肥\",\"area\":\"泸州\",\"town\":\"测试镇\",\"address\":\"XX街道XX小区XX楼888\"},\"receiver\":{\"name\":\"测试名称\",\"tel\":\"0556-45778586\",\"mobile\":\"15575487548\",\"postCode\":\"100001\",\"country\":\"中国\",\"province\":\"河北\",\"city\":\"湖州\",\"area\":\"江汉\",\"town\":\"收件镇\",\"address\":\"XX街道XX小区XX楼666\",\"safeNo\":\"13466666632-0011\"},\"cargo\":{\"battery\":\"10\",\"goodsType\":\"大件\",\"goodsName\":\"XX物\",\"goodsCount\":10,\"spaceX\":10,\"spaceY\":10,\"spaceZ\":10,\"weight\":10,\"goodsAmount\":\"100\",\"cargoItemList\":[{\"serialNumber\":\"8451234\",\"referenceNumber\":\"88838783634\",\"productId\":\"001\",\"name\":\"小商品\",\"qty\":10,\"unitPrice\":1,\"amount\":10,\"currency\":\"美元\",\"weight\":10,\"remark\":\"无\"}]},\"customer\":{\"siteCode\":\"666666\",\"customerName\":\"666666000001\",\"sitePwd\":\"***\",\"monthCustomerCode\":\"9000000\"},\"internationalAnnex\":{\"internationalProductType\":\"01\",\"customsDeclaration\":false,\"senderCountry\":\"中国\",\"receiverCountry\":\"俄罗斯\"},\"waybillNo\":\"59635456632\",\"assignAnnex\":{\"takeCompanyCode\":\"862456565466\",\"takeUserCode\":\"9000000007\"},\"codValue\":\"2000\",\"freightCollectValue\":\"20\",\"timelessType\":\"01\",\"productType\":\"01\",\"serviceTypeList\":[\"***\"],\"extendFieldMap\":{\"mapValue\":\"***\"},\"remark\":\"无备注\",\"expressDirection\":\"01\",\"createChannel\":\"01\",\"regionType\":\"01\",\"insuredAnnex\":{\"insuredValue\":\"6.66\",\"goodsValue\":\"6.66\"},\"expectValue\":\"10\",\"payModel\":\"1\"}" //apiName := "BILL_CODE_QUERY_USER_STOCK" //appkey := "CAKDVcNVcjgqoKH" //code := "CAKDVcNVcjgqoKH" //secretKey := "3ySS5owwn6ENOSespB2c9fROojSAllA0" //toKey := "billcode_open" //content := "{\"siteCode\":\"130370\",\"userCode\":\"130370000295\",\"timestamp\":\"1781318163\",\"userType\":\"0\",\"password\":\"123456\",\"billSource\":\"0\"}" // //info, err := requestSto(apiName, appkey, code, secretKey, toKey, content) //if err != nil { // fmt.Println(err) // return //} //fmt.Println(info) //apiName := "privacy_create_adapter" //customerCode := "K24079264" //secretKey := "XII4ecRl" //requestJSON := "{\"logisticsNo\":\"o362rAIY6DO\",\"senderName\":\"测试1\",\"senderProvinceName\":\"上海\",\"senderCityName\":\"上海市\",\"senderCountyName\":\"青浦区\",\"senderAddress\":\"汇金路100号\",\"senderMobile\":\"15900521555\",\"recipientName\":\"测试\",\"recipientProvinceName\":\"重庆\",\"recipientCityName\":\"重庆市\",\"recipientCountyName\":\"万州区\",\"recipientAddress\":\"汇金路100好\",\"recipientMobile\":\"021-59815121\",\"remark\":\"remark-test\",\"gotCode\":\"123\",\"increments\":[{\"type\":4,\"amount\":888}],\"goods\":[{\"name\":\"mobile\",\"weight\":5,\"length\":10,\"width\":20,\"height\":5,\"price\":100,\"quantity\":1},{\"name\":\"mobile1\",\"weight\":1,\"length\":1,\"width\":1,\"height\":1,\"price\":1,\"quantity\":1}],\"startTime\":\"2022-09-07 17:25:01\",\"endTime\":\"2022-09-07 17:25:01\",\"cstOrderNo\":\"csorderno\",\"weight\":5,\"productCode\":\"PK\"}" //yto, err := requestYto(apiName, customerCode, secretKey, requestJSON) //if err != nil { // fmt.Println(err) // return //} //fmt.Println(yto) }