|
|
@@ -0,0 +1,165 @@
|
|
|
+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)
|
|
|
+}
|