325 lines
8.8 KiB
Go
325 lines
8.8 KiB
Go
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
|
||
}
|