chinaumsServer.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165
  1. package chinaums
  2. import (
  3. "crypto/hmac"
  4. "crypto/sha256"
  5. "encoding/base64"
  6. "encoding/hex"
  7. "encoding/json"
  8. "fmt"
  9. "github.com/shopspring/decimal"
  10. "github.com/spf13/viper"
  11. "go_zh/model"
  12. "go_zh/pkg/utils"
  13. "sort"
  14. "strings"
  15. "sync"
  16. "time"
  17. )
  18. type ChinaumsServer struct {
  19. appId string
  20. appKey string
  21. appletId string //小程序appid
  22. wxAppId string //微信公众号appid
  23. signKey string //解密使用密文
  24. }
  25. var (
  26. chinaumsServerInstance *ChinaumsServer
  27. chinaumsServerOnce sync.Once
  28. )
  29. // GetOrderSnServerInstance 获取单例实例
  30. func GetOrderSnServerInstance() *ChinaumsServer {
  31. chinaumsServerOnce.Do(func() {
  32. chinaumsServerInstance = &ChinaumsServer{
  33. appId: viper.GetString("chinaums.appId"),
  34. appKey: viper.GetString("chinaums.appKey"),
  35. appletId: viper.GetString("chinaums.appletId"),
  36. wxAppId: viper.GetString("chinaums.wxAppId"),
  37. signKey: viper.GetString("chinaums.signKey"),
  38. }
  39. })
  40. return chinaumsServerInstance
  41. }
  42. // ShopOrderWxH5Pay 微信公众号店铺收款码支付
  43. func (cs *ChinaumsServer) ShopOrderWxH5Pay(shopMchInfo model.XShopMchid, orderSn string, payMoney decimal.Decimal, notifyUrl string, wxOpenid string) string {
  44. url := "https://api-mop.chinaums.com/v1/netpay/webpay/pay"
  45. result := make(map[string]interface{})
  46. result["requestTimestamp"] = time.Now().Format("2006-01-02 15:04:05")
  47. result["mid"] = shopMchInfo.SubMchid
  48. result["tid"] = shopMchInfo.Tid
  49. result["instMid"] = "YUEDANDEFAULT" // 业务类型 YUEDANDEFAULT
  50. result["merOrderId"] = orderSn
  51. result["notifyUrl"] = notifyUrl
  52. result["returnUrl"] = "https://shoppay.jhcs888.com/#/?order_sn=" + orderSn // 网页跳转地址
  53. result["totalAmount"] = payMoney.Mul(decimal.NewFromInt(100)).IntPart()
  54. result["subAppId"] = cs.wxAppId // 微信必填
  55. result["subOpenId"] = wxOpenid
  56. body, _ := json.Marshal(result)
  57. strUrl := cs.curlget(url, orderSn, string(body))
  58. return strUrl
  59. }
  60. // ShopOrderAliH5Pay 银联发起支付宝H5支付
  61. func (cs *ChinaumsServer) ShopOrderAliH5Pay(shopMchInfo model.XShopMchid, orderSn string, payMoney decimal.Decimal, notifyUrl string) string {
  62. url := "https://api-mop.chinaums.com/v1/netpay/trade/h5-pay"
  63. result := make(map[string]interface{})
  64. result["requestTimestamp"] = time.Now().Format("2006-01-02 15:04:05")
  65. result["mid"] = shopMchInfo.SubMchid
  66. result["tid"] = shopMchInfo.Tid
  67. result["merOrderId"] = orderSn
  68. result["instMid"] = "H5DEFAULT"
  69. result["notifyUrl"] = notifyUrl
  70. result["totalAmount"] = payMoney.Mul(decimal.NewFromInt(100)).IntPart()
  71. body, _ := json.Marshal(result)
  72. strUrl := cs.curlget(url, orderSn, string(body))
  73. return strUrl
  74. }
  75. // curlget 拼接请求地址
  76. func (cs *ChinaumsServer) curlget(url, orderSn, body string) string {
  77. timestamp := time.Now().Format("20060102150405")
  78. nonce := utils.Md5Sign(orderSn)
  79. signature := utils.UrlEncode(cs.getSignature(timestamp, nonce, body))
  80. reqUrl := fmt.Sprintf("%s?authorization=OPEN-FORM-PARAM&appId=%s&timestamp=%s&nonce=%s&content=%s&signature=%s", url, cs.appId, timestamp, nonce, utils.UrlEncode(body), signature)
  81. return reqUrl
  82. }
  83. // buildSignStr 生成签名原始字符串,规则如下:
  84. // 1. 排除键名为 "sign" 的项;
  85. // 2. 排除值为 nil 的项;
  86. // 3. 剩余参数按键名升序排序;
  87. // 4. 值如果是 map 或 slice,则进行 JSON 编码;否则直接转换为字符串;
  88. // 5. 拼接成 key=value 形式,用 & 连接,末尾不加 &。
  89. func buildSignStr(params map[string]interface{}) string {
  90. // 收集所有非 "sign" 且值不为 nil 的键
  91. keys := make([]string, 0, len(params))
  92. for k, v := range params {
  93. if k == "sign" || v == nil {
  94. continue
  95. }
  96. keys = append(keys, k)
  97. }
  98. // 键名排序
  99. sort.Strings(keys)
  100. var builder strings.Builder
  101. for i, key := range keys {
  102. val := params[key]
  103. // 将值转换为字符串
  104. var valStr string
  105. switch v := val.(type) {
  106. case map[string]interface{}: // 关联数组 -> JSON 对象
  107. b, _ := json.Marshal(v)
  108. valStr = string(b)
  109. case []interface{}: // 索引数组 -> JSON 数组
  110. b, _ := json.Marshal(v)
  111. valStr = string(b)
  112. default:
  113. // 基本类型(string, int, float, bool 等)使用默认字符串表示
  114. valStr = fmt.Sprint(v)
  115. }
  116. builder.WriteString(key)
  117. builder.WriteString("=")
  118. builder.WriteString(valStr)
  119. // 除最后一个元素外,添加 & 分隔符
  120. if i < len(keys)-1 {
  121. builder.WriteString("&")
  122. }
  123. }
  124. return builder.String()
  125. }
  126. // getSignature 生成签名,
  127. // 参数 timestamp, nonce, body 均为字符串类型
  128. func (cs *ChinaumsServer) getSignature(timestamp, nonce, body string) string {
  129. // 1. 计算 body 的 SHA256 哈希(原始二进制)并转换为十六进制字符串
  130. bodyHash := sha256.Sum256([]byte(body))
  131. bodyHex := hex.EncodeToString(bodyHash[:])
  132. // 2. 拼接 appid + timestamp + nonce + bodyHex
  133. message := cs.appId + timestamp + nonce + bodyHex
  134. // 3. 使用 HMAC-SHA256 对消息进行签名,密钥为 appkey
  135. h := hmac.New(sha256.New, []byte(cs.appKey))
  136. h.Write([]byte(message))
  137. signature := h.Sum(nil) // 原始二进制签名
  138. // 4. 对签名结果进行 Base64 编码
  139. return base64.StdEncoding.EncodeToString(signature)
  140. }