PaymentCenter/app/third/paymentService/psbc/client.go

357 lines
9.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package payment
import (
"bytes"
"crypto/hmac"
"crypto/rand"
"crypto/sha256"
"encoding/base64"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"math/big"
"net/http"
"sort"
"strings"
"time"
"PaymentCenter/app/third/paymentService/psbc/internal/sm2"
psbcutil "PaymentCenter/app/third/paymentService/psbc/internal/util"
"github.com/ZZMarquis/gm/sm4"
)
// 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 流程youchu_api.go/config.go
func (c *Client) doPost(url string, partnerTxSriNo string, method string, encryptedBody []byte) ([]byte, error) {
req, err := http.NewRequest("POST", url, bytes.NewReader(encryptedBody))
if err != nil {
return nil, fmt.Errorf("创建请求失败: %v", err)
}
// 构建 HTTP Header对齐 YouChuKoffee 的 doPost 方式
req.Header.Set("partnerTxSriNo", partnerTxSriNo)
req.Header.Set("reqTime", time.Now().Format("20060102150405"))
req.Header.Set("method", method)
req.Header.Set("version", "1")
req.Header.Set("merchantId", c.cfg.MerchantId)
req.Header.Set("appID", c.cfg.AppID)
req.Header.Set("accessType", "API")
req.Header.Set("reserve", "")
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
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)
// 使用 YouChuKoffee 加密包psbc/internal/sm2 + psbc/internal/util
func (c *Client) EncryptMobile(inputJson string, signStr string) (string, string, error) {
defer func() {
if err := recover(); err != nil {
fmt.Println("加密失败", err)
}
}()
sm4Key := psbcutil.GenerateSM4Key()
// SM2 加密 SM4 密钥(使用银行公钥,使用 YouChuKoffee 的 Sm2Encrypt
bankPubKey, err := sm2.ReadPublicKeyFromHex(c.cfg.BankKey)
if err != nil {
return "", "", fmt.Errorf("读取银行公钥失败: %v", err)
}
encryptKeyBytes, err := psbcutil.Sm2Encrypt(bankPubKey, sm4Key)
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 的 Sm2SignUserID 传 nil默认值 1234567812345678
merchantPrivKey, err := sm2.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 加密
// 对齐 YouChuKoffee: sm43.ECBEncrypt(sm4Key, sm43.PKCS5Padding([]byte(param), 16))
// sm4ECBEncrypt 内部已处理 padding调用方不需要再 pad
param := fmt.Sprintf("%s&sign=%s", inputJson, sig)
tmp, err := sm4ECBEncrypt(sm4Key, []byte(param))
if err != nil {
return "", "", fmt.Errorf("sm4加密失败: %v", err)
}
return encodeBase64(tmp), encodeBase64(encryptKeyBytes), nil
}
// DecryptResponse 解密银行响应
func (c *Client) DecryptResponse(response string, isRequest bool) (Rsponse string, err error) {
var MerchantId, PrivateKey, PublicKey string
if isRequest {
MerchantId = c.cfg.MerchantId
PrivateKey = c.cfg.PrivateKeyCallback
PublicKey = c.cfg.SopPublicKey
} else {
MerchantId = c.cfg.MerchantId
PrivateKey = c.cfg.PrivateKeyCallback
PublicKey = c.cfg.SopPublicKey
}
encrypt, err := Decrypt(MerchantId, PrivateKey, PublicKey, response, isRequest)
if err != nil {
return
}
var RsponseData map[string]interface{}
err = json.Unmarshal([]byte(encrypt), &RsponseData)
if err != nil {
return
} else {
if RsponseData["body"] != nil {
if body, ok := RsponseData["body"].(string); ok {
Rsponse = body
} else {
notify, _ := json.Marshal(RsponseData["body"].(map[string]interface{}))
Rsponse = string(notify)
}
}
}
return
}
func Decrypt(merchantId, privateKey, sopPublicKey, respJson string, isRequest bool) (string, error) {
var reqData map[string]string
err := json.Unmarshal([]byte(respJson), &reqData)
if err != nil {
return "", err
}
reqData["accessToken"] = ""
keys := [4]string{}
if isRequest {
keys = [4]string{"request", "signature", "encryptKey", "accessToken"}
} else {
keys = [4]string{"response", "signature", "encryptKey", "accessToken"}
}
var inEncryptKey, inAccessToken, inData, inSignature string
for i := 0; i < 4; i++ {
data, err := checkInData(reqData, keys[i])
if err != nil {
return "", err
}
switch keys[i] {
case "request", "response":
inData = data
case "signature":
inSignature = data
case "encryptKey":
inEncryptKey = data
case "accessToken":
inAccessToken = data
}
}
checked := verify(fmt.Sprintf("%s%s%s", inData, inEncryptKey, inAccessToken), inSignature, sopPublicKey, merchantId)
if !checked {
return "", errors.New("签名验证失败")
}
priKey, err := sm2.ReadPrivateKeyFromHex(privateKey)
if err != nil {
return "", errors.New("读取私钥失败")
}
hexEncryptKey, err := hex.DecodeString(inEncryptKey)
if err != nil {
return "", errors.New("解密sm4key失败")
}
sm4Key, err := psbcutil.Sm2Decrypt(priKey, hexEncryptKey)
if err != nil {
return "", errors.New("解密sm2key失败")
}
request, _ := base64.StdEncoding.DecodeString(inData)
encryptedSm4Key, err := sm4.CBCDecrypt(sm4Key, psbcutil.GetSM4IV(), request)
return string(psbcutil.Padding(encryptedSm4Key, 0)), nil
}
func checkInData(reqData map[string]string, key string) (string, error) {
data, ok := reqData[key]
if !ok {
return "", errors.New("请求数据中不存在" + key)
}
return data, nil
}
func verify(content string, signature string, publicKeyStr string, merchantId string) bool {
pubKey, err := sm2.ReadPublicKeyFromHex(publicKeyStr)
//content = ""
if err != nil {
panic(fmt.Sprintf("pubKeyBytes sm2 ReadPublicKeyFromHex err: %v", err))
}
r, s := signToRS(signature)
return sm2.Sm2Verify(pubKey, []byte(content), []byte(merchantId), r, s)
}
// VerifySignature 验签
func (c *Client) VerifySignature(content, signature string) bool {
return c.verify(content, signature)
}
func (c *Client) verify(content, signature string) bool {
pubKey, err := sm2.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
}