package chinaums import ( "crypto/hmac" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "github.com/shopspring/decimal" "github.com/spf13/viper" "go_zh/model" "go_zh/pkg/utils" "sort" "strings" "sync" "time" ) type ChinaumsServer struct { appId string appKey string appletId string //小程序appid wxAppId string //微信公众号appid signKey string //解密使用密文 } var ( chinaumsServerInstance *ChinaumsServer chinaumsServerOnce sync.Once ) // GetOrderSnServerInstance 获取单例实例 func GetOrderSnServerInstance() *ChinaumsServer { chinaumsServerOnce.Do(func() { chinaumsServerInstance = &ChinaumsServer{ appId: viper.GetString("chinaums.appId"), appKey: viper.GetString("chinaums.appKey"), appletId: viper.GetString("chinaums.appletId"), wxAppId: viper.GetString("chinaums.wxAppId"), signKey: viper.GetString("chinaums.signKey"), } }) return chinaumsServerInstance } // ShopOrderWxH5Pay 微信公众号店铺收款码支付 func (cs *ChinaumsServer) ShopOrderWxH5Pay(shopMchInfo model.XShopMchid, orderSn string, payMoney decimal.Decimal, notifyUrl string, wxOpenid string) string { url := "https://api-mop.chinaums.com/v1/netpay/webpay/pay" result := make(map[string]interface{}) result["requestTimestamp"] = time.Now().Format("2006-01-02 15:04:05") result["mid"] = shopMchInfo.SubMchid result["tid"] = shopMchInfo.Tid result["instMid"] = "YUEDANDEFAULT" // 业务类型 YUEDANDEFAULT result["merOrderId"] = orderSn result["notifyUrl"] = notifyUrl result["returnUrl"] = "https://shoppay.jhcs888.com/#/?order_sn=" + orderSn // 网页跳转地址 result["totalAmount"] = payMoney.Mul(decimal.NewFromInt(100)).IntPart() result["subAppId"] = cs.wxAppId // 微信必填 result["subOpenId"] = wxOpenid body, _ := json.Marshal(result) strUrl := cs.curlget(url, orderSn, string(body)) return strUrl } // ShopOrderAliH5Pay 银联发起支付宝H5支付 func (cs *ChinaumsServer) ShopOrderAliH5Pay(shopMchInfo model.XShopMchid, orderSn string, payMoney decimal.Decimal, notifyUrl string) string { url := "https://api-mop.chinaums.com/v1/netpay/trade/h5-pay" result := make(map[string]interface{}) result["requestTimestamp"] = time.Now().Format("2006-01-02 15:04:05") result["mid"] = shopMchInfo.SubMchid result["tid"] = shopMchInfo.Tid result["merOrderId"] = orderSn result["instMid"] = "H5DEFAULT" result["notifyUrl"] = notifyUrl result["totalAmount"] = payMoney.Mul(decimal.NewFromInt(100)).IntPart() body, _ := json.Marshal(result) strUrl := cs.curlget(url, orderSn, string(body)) return strUrl } // curlget 拼接请求地址 func (cs *ChinaumsServer) curlget(url, orderSn, body string) string { timestamp := time.Now().Format("20060102150405") nonce := utils.Md5Sign(orderSn) signature := utils.UrlEncode(cs.getSignature(timestamp, nonce, body)) reqUrl := fmt.Sprintf("%s?authorization=OPEN-FORM-PARAM&appId=%s×tamp=%s&nonce=%s&content=%s&signature=%s", url, cs.appId, timestamp, nonce, utils.UrlEncode(body), signature) return reqUrl } // buildSignStr 生成签名原始字符串,规则如下: // 1. 排除键名为 "sign" 的项; // 2. 排除值为 nil 的项; // 3. 剩余参数按键名升序排序; // 4. 值如果是 map 或 slice,则进行 JSON 编码;否则直接转换为字符串; // 5. 拼接成 key=value 形式,用 & 连接,末尾不加 &。 func buildSignStr(params map[string]interface{}) string { // 收集所有非 "sign" 且值不为 nil 的键 keys := make([]string, 0, len(params)) for k, v := range params { if k == "sign" || v == nil { continue } keys = append(keys, k) } // 键名排序 sort.Strings(keys) var builder strings.Builder for i, key := range keys { val := params[key] // 将值转换为字符串 var valStr string switch v := val.(type) { case map[string]interface{}: // 关联数组 -> JSON 对象 b, _ := json.Marshal(v) valStr = string(b) case []interface{}: // 索引数组 -> JSON 数组 b, _ := json.Marshal(v) valStr = string(b) default: // 基本类型(string, int, float, bool 等)使用默认字符串表示 valStr = fmt.Sprint(v) } builder.WriteString(key) builder.WriteString("=") builder.WriteString(valStr) // 除最后一个元素外,添加 & 分隔符 if i < len(keys)-1 { builder.WriteString("&") } } return builder.String() } // getSignature 生成签名, // 参数 timestamp, nonce, body 均为字符串类型 func (cs *ChinaumsServer) getSignature(timestamp, nonce, body string) string { // 1. 计算 body 的 SHA256 哈希(原始二进制)并转换为十六进制字符串 bodyHash := sha256.Sum256([]byte(body)) bodyHex := hex.EncodeToString(bodyHash[:]) // 2. 拼接 appid + timestamp + nonce + bodyHex message := cs.appId + timestamp + nonce + bodyHex // 3. 使用 HMAC-SHA256 对消息进行签名,密钥为 appkey h := hmac.New(sha256.New, []byte(cs.appKey)) h.Write([]byte(message)) signature := h.Sum(nil) // 原始二进制签名 // 4. 对签名结果进行 Base64 编码 return base64.StdEncoding.EncodeToString(signature) }