package payment import ( "bytes" "crypto/hmac" "crypto/rand" "crypto/sha256" "encoding/base64" "encoding/hex" "encoding/json" "fmt" "io/ioutil" "math/big" "net/http" "sort" "strings" "time" "github.com/ZZMarquis/gm/sm4" "github.com/tjfoc/gmsm/sm2" "github.com/tjfoc/gmsm/x509" ) // Client 支付客户端 type Client struct { cfg Config } // NewClient 创建支付客户端 func NewClient(cfg Config) *Client { return &Client{cfg: cfg} } // doPost 发送 POST 请求到邮储银行 // head 信息通过 HTTP Header 明文传输,encryptedBody 作为 HTTP POST Body // 对齐 YouChuKoffee 的 postbank.doPost 流程 func (c *Client) doPost(url string, head map[string]string, encryptedBody []byte) ([]byte, error) { req, err := http.NewRequest("POST", url, bytes.NewReader(encryptedBody)) if err != nil { return nil, fmt.Errorf("创建请求失败: %v", err) } req.Header.Set("Content-Type", "application/json;charset=UTF-8") for k, v := range head { req.Header.Set(k, v) } client := &http.Client{Timeout: 60 * time.Second} resp, err := client.Do(req) if err != nil { return nil, fmt.Errorf("请求失败: %v", err) } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) if err != nil { return nil, fmt.Errorf("读取响应失败: %v", err) } if resp.StatusCode != http.StatusOK { return nil, fmt.Errorf("HTTP请求失败,状态码: %d,响应: %s", resp.StatusCode, string(body)) } return body, nil } // MapToString 将 map 转换为排序后的 key=value&key=value 格式 func MapToString(m map[string]string) string { keys := make([]string, 0, len(m)) for k := range m { keys = append(keys, k) } sort.Strings(keys) var result []string for _, k := range keys { result = append(result, fmt.Sprintf("%s=%s", k, m[k])) } return strings.Join(result, "&") } // HmacSha256To16 生成 HMAC-SHA256 签名(16进制) func HmacSha256To16(key string, data string) string { mac := hmac.New(sha256.New, []byte(key)) mac.Write([]byte(data)) encode := mac.Sum(nil) return hex.EncodeToString(encode) } // GenerateOrderNumber 生成订单号 func GenerateOrderNumber() string { datePart := time.Now().Format("20060102150405") randomPart := fmt.Sprintf("%06d", time.Now().Nanosecond()%1000000) return "SJ" + datePart + randomPart } // RandomNumber 生成指定长度的随机数字字符串 func RandomNumber(n int) string { result := make([]byte, n) for i := 0; i < n; i++ { result[i] = byte(time.Now().Nanosecond()%10 + '0') time.Sleep(1) } return string(result) } // EncryptMobile 手机银行支付参数加密 // 返回: tysdPayParams(base64), tysdEncryptKey(base64) func (c *Client) EncryptMobile(inputJson string, signStr string) (string, string, error) { defer func() { if err := recover(); err != nil { fmt.Println("加密失败", err) } }() sm4Key := generateSM4Key() // SM2 加密 SM4 密钥(使用银行公钥) bankPubKey, err := x509.ReadPublicKeyFromHex(c.cfg.BankKey) if err != nil { return "", "", fmt.Errorf("读取银行公钥失败: %v", err) } encryptKeyBytes, err := sm2.Encrypt(bankPubKey, sm4Key, nil, sm2.C1C3C2) if err != nil { return "", "", fmt.Errorf("sm2加密失败: %v", err) } encryptKey := hex.EncodeToString(encryptKeyBytes) if len(encryptKey) > 2 && encryptKey[0:2] == "04" { encryptKeyBytes = encryptKeyBytes[1:] } // SM2 签名(与 YouChuKoffee 对齐:UserID 传 nil,默认值 1234567812345678) merchantPrivKey, err := x509.ReadPrivateKeyFromHex(c.cfg.PrivateKey) if err != nil { return "", "", fmt.Errorf("读取商户私钥失败: %v", err) } r, s, err := sm2.Sm2Sign(merchantPrivKey, []byte(signStr), nil, rand.Reader) if err != nil { return "", "", fmt.Errorf("签名失败: %v", err) } sig := encodeBase64(rsToBytes(r, s)) // 拼接参数并 SM4 ECB 加密 param := fmt.Sprintf("%s&sign=%s", inputJson, sig) tmp, err := sm4ECBEncrypt(sm4Key, pkcs5Padding([]byte(param), 16)) if err != nil { return "", "", fmt.Errorf("sm4加密失败: %v", err) } return encodeBase64(tmp), encodeBase64(encryptKeyBytes), nil } // DecryptResponse 解密银行响应 func (c *Client) DecryptResponse(respJson string, isRequest bool) (string, error) { var reqData map[string]string if err := json.Unmarshal([]byte(respJson), &reqData); err != nil { return "", fmt.Errorf("解析响应JSON失败: %v,响应内容: %s", err, respJson) } dataKey := "response" if isRequest { dataKey = "request" } _, hasData := reqData[dataKey] _, hasSignature := reqData["signature"] _, hasEncryptKey := reqData["encryptKey"] if !hasData || !hasSignature || !hasEncryptKey { if code, ok := reqData["code"]; ok { msg := reqData["msg"] if msg == "" { msg = reqData["message"] } return "", fmt.Errorf("银行返回错误,错误码: %s,错误信息: %s", code, msg) } if respCode, ok := reqData["respCode"]; ok { respMsg := reqData["respMsg"] return "", fmt.Errorf("银行返回错误,错误码: %s,错误信息: %s", respCode, respMsg) } missingFields := []string{} if !hasData { missingFields = append(missingFields, dataKey) } if !hasSignature { missingFields = append(missingFields, "signature") } if !hasEncryptKey { missingFields = append(missingFields, "encryptKey") } return "", fmt.Errorf("响应格式不正确,缺少字段: %v,响应内容: %s", missingFields, respJson) } reqData["accessToken"] = "" inData := reqData[dataKey] inSignature := reqData["signature"] inEncryptKey := reqData["encryptKey"] // 验签 checked := c.verify(fmt.Sprintf("%s%s%s", inData, inEncryptKey, ""), inSignature) if !checked { return "", fmt.Errorf("签名验证失败") } // 解密 SM4 密钥 privKey, err := x509.ReadPrivateKeyFromHex(c.cfg.PrivateKey) if err != nil { return "", fmt.Errorf("读取私钥失败: %v", err) } hexEncryptKey, err := hex.DecodeString(inEncryptKey) if err != nil { return "", fmt.Errorf("解密sm4key失败: %v", err) } sm4Key, err := sm2.Decrypt(privKey, hexEncryptKey, sm2.C1C3C2) if err != nil { return "", fmt.Errorf("解密sm2key失败: %v", err) } // 解密数据 request, _ := base64.StdEncoding.DecodeString(inData) encryptedSm4Key, err := sm4.CBCDecrypt(sm4Key, getSM4IV(), request) decryptedStr := string(pkcs5UnPadding(encryptedSm4Key)) // 提取 body 字段(银行解密后的结构为 {head, body},业务数据在 body 中) var decryptedMap map[string]interface{} if jsonErr := json.Unmarshal([]byte(decryptedStr), &decryptedMap); jsonErr == nil { if bodyVal, bodyOk := decryptedMap["body"]; bodyOk && bodyVal != nil { if bodyStr, isStr := bodyVal.(string); isStr { return bodyStr, nil } bodyBytes, marshalErr := json.Marshal(bodyVal) if marshalErr == nil { return string(bodyBytes), nil } } } return decryptedStr, nil } // VerifySignature 验签 func (c *Client) VerifySignature(content, signature string) bool { return c.verify(content, signature) } func (c *Client) verify(content, signature string) bool { pubKey, err := x509.ReadPublicKeyFromHex(c.cfg.SopPublicKey) if err != nil { panic(fmt.Sprintf("pubKeyBytes sm2 ReadPublicKeyFromHex err: %v", err)) } r, s := signToRS(signature) return sm2.Sm2Verify(pubKey, []byte(content), []byte(c.cfg.MerchantId), r, s) } func signToRS(signStr string) (*big.Int, *big.Int) { sub := strings.Split(signStr, "#") if len(sub) != 2 { panic(fmt.Sprintf("err rs: %x", sub)) } r, _ := new(big.Int).SetString(sub[0], 16) s, _ := new(big.Int).SetString(sub[1], 16) return r, s } func rsToBytes(r, s *big.Int) []byte { rBytes := r.Bytes() sBytes := s.Bytes() result := make([]byte, 0, len(rBytes)+len(sBytes)) result = append(result, rBytes...) result = append(result, sBytes...) return result } func encodeBase64(data []byte) string { return base64.StdEncoding.EncodeToString(data) } func getSM4IV() []byte { return []byte("UISwD9fW6cFh9SNS") } func generateSM4Key() []byte { key := make([]byte, 16) _, err := rand.Read(key) if err != nil { panic(fmt.Sprintf("生成SM4密钥失败: %v", err)) } return key } func pkcs5Padding(ciphertext []byte, blockSize int) []byte { padding := blockSize - len(ciphertext)%blockSize padtext := make([]byte, padding) for i := 0; i < padding; i++ { padtext[i] = byte(padding) } return append(ciphertext, padtext...) } func pkcs5UnPadding(origData []byte) []byte { length := len(origData) if length == 0 { return origData } unpadding := int(origData[length-1]) if unpadding > length { return origData } return origData[:(length - unpadding)] } func sm4ECBEncrypt(key, plaintext []byte) ([]byte, error) { block, err := sm4.NewCipher(key) if err != nil { return nil, err } blockSize := block.BlockSize() plaintext = pkcs5Padding(plaintext, blockSize) ciphertext := make([]byte, len(plaintext)) for start := 0; start < len(plaintext); start += blockSize { block.Encrypt(ciphertext[start:start+blockSize], plaintext[start:start+blockSize]) } return ciphertext, nil }