feat:邮储支付fix
This commit is contained in:
parent
7f2aceb5a7
commit
2543a0fd4d
|
|
@ -46,7 +46,7 @@ func PayUrl(c *gin.Context) {
|
||||||
|
|
||||||
res.Order = thirdpay.NewOrdersResp(pay.Order)
|
res.Order = thirdpay.NewOrdersResp(pay.Order)
|
||||||
res.Url = pay.Url
|
res.Url = pay.Url
|
||||||
res.ThirdMsg = pay.ThirdMsg
|
res.PayInfo = pay.PayInfo
|
||||||
res.JsInfo = pay.JsApiInfo
|
res.JsInfo = pay.JsApiInfo
|
||||||
controllers.ApiRes(c, res, pay.PayCode)
|
controllers.ApiRes(c, res, pay.PayCode)
|
||||||
return
|
return
|
||||||
|
|
|
||||||
|
|
@ -64,8 +64,8 @@ type CloseReqs struct {
|
||||||
type ApiResponse struct {
|
type ApiResponse struct {
|
||||||
Order interface{} `json:"order,omitempty"`
|
Order interface{} `json:"order,omitempty"`
|
||||||
Url string `json:"url,omitempty"`
|
Url string `json:"url,omitempty"`
|
||||||
ThirdMsg string `json:"third_msg,omitempty"`
|
|
||||||
JsInfo *WxMiniPayResponse `json:"js_info,omitempty"`
|
JsInfo *WxMiniPayResponse `json:"js_info,omitempty"`
|
||||||
|
PayInfo string `json:"pay_info,omitempty"` // 支付参数,邮储app支付参数
|
||||||
}
|
}
|
||||||
|
|
||||||
type PayChannelListRequest struct {
|
type PayChannelListRequest struct {
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@ type Pay struct {
|
||||||
Url string // 支付链接
|
Url string // 支付链接
|
||||||
ThirdMsg string // 第三方错误信息
|
ThirdMsg string // 第三方错误信息
|
||||||
JsApiInfo *front.WxMiniPayResponse // jsapi支付参数
|
JsApiInfo *front.WxMiniPayResponse // jsapi支付参数
|
||||||
|
PayInfo string // 支付参数,邮储app支付参数
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewPayWithPayCheck(paycheck *PayCheck) *Pay {
|
func NewPayWithPayCheck(paycheck *PayCheck) *Pay {
|
||||||
|
|
@ -217,7 +218,7 @@ func (w *Pay) PayUrl() (url string) {
|
||||||
}
|
}
|
||||||
} else if w.PayParam.Channel.ChannelType == common.PAY_CHANNEL_PSBC {
|
} else if w.PayParam.Channel.ChannelType == common.PAY_CHANNEL_PSBC {
|
||||||
// 邮储支付返回 handCodePay 参数
|
// 邮储支付返回 handCodePay 参数
|
||||||
w.ThirdMsg = res.Result
|
w.PayInfo = res.Result
|
||||||
} else {
|
} else {
|
||||||
w.Url = res.Result
|
w.Url = res.Result
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -46,9 +46,8 @@ func (c *Client) BillQuery(date time.Time) (*BillQueryResponse, error) {
|
||||||
encryptedBytes, _ := json.Marshal(encryptedReq)
|
encryptedBytes, _ := json.Marshal(encryptedReq)
|
||||||
url := c.cfg.FileHost + c.cfg.MerchantId + ".htm?partnerTxSriNo=" + BusiMainId
|
url := c.cfg.FileHost + c.cfg.MerchantId + ".htm?partnerTxSriNo=" + BusiMainId
|
||||||
|
|
||||||
// head 同时作为 HTTP Header 明文传输
|
// head 同时作为 HTTP Header 明文传输(对齐 YouChuKoffee 的 doPost 方式)
|
||||||
head := requestBody["head"].(map[string]string)
|
body, err := c.doPost(url, BusiMainId, "ufile.query.commonQuery", encryptedBytes)
|
||||||
body, err := c.doPost(url, head, encryptedBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
@ -103,9 +102,8 @@ func (c *Client) BillDownload(fileId string, date time.Time) ([]byte, error) {
|
||||||
encryptedBytes, _ := json.Marshal(encryptedReq)
|
encryptedBytes, _ := json.Marshal(encryptedReq)
|
||||||
url := c.cfg.FileHost + c.cfg.MerchantId + ".htm?partnerTxSriNo=" + BusiMainId
|
url := c.cfg.FileHost + c.cfg.MerchantId + ".htm?partnerTxSriNo=" + BusiMainId
|
||||||
|
|
||||||
// head 同时作为 HTTP Header 明文传输
|
// head 同时作为 HTTP Header 明文传输(对齐 YouChuKoffee 的 doPost 方式)
|
||||||
head := requestBody["head"].(map[string]string)
|
body, err := c.doPost(url, BusiMainId, "ufile.download.commonDownload", encryptedBytes)
|
||||||
body, err := c.doPost(url, head, encryptedBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,7 @@ import (
|
||||||
"encoding/base64"
|
"encoding/base64"
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
"math/big"
|
"math/big"
|
||||||
|
|
@ -16,9 +17,9 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"PaymentCenter/app/third/paymentService/psbc/internal/sm2"
|
||||||
|
psbcutil "PaymentCenter/app/third/paymentService/psbc/internal/util"
|
||||||
"github.com/ZZMarquis/gm/sm4"
|
"github.com/ZZMarquis/gm/sm4"
|
||||||
"github.com/tjfoc/gmsm/sm2"
|
|
||||||
"github.com/tjfoc/gmsm/x509"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Client 支付客户端
|
// Client 支付客户端
|
||||||
|
|
@ -33,17 +34,23 @@ func NewClient(cfg Config) *Client {
|
||||||
|
|
||||||
// doPost 发送 POST 请求到邮储银行
|
// doPost 发送 POST 请求到邮储银行
|
||||||
// head 信息通过 HTTP Header 明文传输,encryptedBody 作为 HTTP POST Body
|
// head 信息通过 HTTP Header 明文传输,encryptedBody 作为 HTTP POST Body
|
||||||
// 对齐 YouChuKoffee 的 postbank.doPost 流程
|
// 对齐 YouChuKoffee 的 postbank.doPost 流程(youchu_api.go/config.go)
|
||||||
func (c *Client) doPost(url string, head map[string]string, encryptedBody []byte) ([]byte, error) {
|
func (c *Client) doPost(url string, partnerTxSriNo string, method string, encryptedBody []byte) ([]byte, error) {
|
||||||
req, err := http.NewRequest("POST", url, bytes.NewReader(encryptedBody))
|
req, err := http.NewRequest("POST", url, bytes.NewReader(encryptedBody))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("创建请求失败: %v", err)
|
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")
|
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}
|
client := &http.Client{Timeout: 60 * time.Second}
|
||||||
resp, err := client.Do(req)
|
resp, err := client.Do(req)
|
||||||
|
|
@ -106,6 +113,7 @@ func RandomNumber(n int) string {
|
||||||
|
|
||||||
// EncryptMobile 手机银行支付参数加密
|
// EncryptMobile 手机银行支付参数加密
|
||||||
// 返回: tysdPayParams(base64), tysdEncryptKey(base64)
|
// 返回: tysdPayParams(base64), tysdEncryptKey(base64)
|
||||||
|
// 使用 YouChuKoffee 加密包(psbc/internal/sm2 + psbc/internal/util)
|
||||||
func (c *Client) EncryptMobile(inputJson string, signStr string) (string, string, error) {
|
func (c *Client) EncryptMobile(inputJson string, signStr string) (string, string, error) {
|
||||||
defer func() {
|
defer func() {
|
||||||
if err := recover(); err != nil {
|
if err := recover(); err != nil {
|
||||||
|
|
@ -113,14 +121,14 @@ func (c *Client) EncryptMobile(inputJson string, signStr string) (string, string
|
||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
|
|
||||||
sm4Key := generateSM4Key()
|
sm4Key := psbcutil.GenerateSM4Key()
|
||||||
|
|
||||||
// SM2 加密 SM4 密钥(使用银行公钥)
|
// SM2 加密 SM4 密钥(使用银行公钥,使用 YouChuKoffee 的 Sm2Encrypt)
|
||||||
bankPubKey, err := x509.ReadPublicKeyFromHex(c.cfg.BankKey)
|
bankPubKey, err := sm2.ReadPublicKeyFromHex(c.cfg.BankKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("读取银行公钥失败: %v", err)
|
return "", "", fmt.Errorf("读取银行公钥失败: %v", err)
|
||||||
}
|
}
|
||||||
encryptKeyBytes, err := sm2.Encrypt(bankPubKey, sm4Key, nil, sm2.C1C3C2)
|
encryptKeyBytes, err := psbcutil.Sm2Encrypt(bankPubKey, sm4Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("sm2加密失败: %v", err)
|
return "", "", fmt.Errorf("sm2加密失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -129,8 +137,8 @@ func (c *Client) EncryptMobile(inputJson string, signStr string) (string, string
|
||||||
encryptKeyBytes = encryptKeyBytes[1:]
|
encryptKeyBytes = encryptKeyBytes[1:]
|
||||||
}
|
}
|
||||||
|
|
||||||
// SM2 签名(与 YouChuKoffee 对齐:UserID 传 nil,默认值 1234567812345678)
|
// SM2 签名(使用 YouChuKoffee 的 Sm2Sign,UserID 传 nil,默认值 1234567812345678)
|
||||||
merchantPrivKey, err := x509.ReadPrivateKeyFromHex(c.cfg.PrivateKey)
|
merchantPrivKey, err := sm2.ReadPrivateKeyFromHex(c.cfg.PrivateKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("读取商户私钥失败: %v", err)
|
return "", "", fmt.Errorf("读取商户私钥失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -141,8 +149,10 @@ func (c *Client) EncryptMobile(inputJson string, signStr string) (string, string
|
||||||
sig := encodeBase64(rsToBytes(r, s))
|
sig := encodeBase64(rsToBytes(r, s))
|
||||||
|
|
||||||
// 拼接参数并 SM4 ECB 加密
|
// 拼接参数并 SM4 ECB 加密
|
||||||
|
// 对齐 YouChuKoffee: sm43.ECBEncrypt(sm4Key, sm43.PKCS5Padding([]byte(param), 16))
|
||||||
|
// sm4ECBEncrypt 内部已处理 padding,调用方不需要再 pad
|
||||||
param := fmt.Sprintf("%s&sign=%s", inputJson, sig)
|
param := fmt.Sprintf("%s&sign=%s", inputJson, sig)
|
||||||
tmp, err := sm4ECBEncrypt(sm4Key, pkcs5Padding([]byte(param), 16))
|
tmp, err := sm4ECBEncrypt(sm4Key, []byte(param))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", "", fmt.Errorf("sm4加密失败: %v", err)
|
return "", "", fmt.Errorf("sm4加密失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -151,91 +161,113 @@ func (c *Client) EncryptMobile(inputJson string, signStr string) (string, string
|
||||||
}
|
}
|
||||||
|
|
||||||
// DecryptResponse 解密银行响应
|
// DecryptResponse 解密银行响应
|
||||||
func (c *Client) DecryptResponse(respJson string, isRequest bool) (string, error) {
|
func (c *Client) DecryptResponse(response string, isRequest bool) (Rsponse string, err error) {
|
||||||
var reqData map[string]string
|
var MerchantId, PrivateKey, PublicKey string
|
||||||
if err := json.Unmarshal([]byte(respJson), &reqData); err != nil {
|
|
||||||
return "", fmt.Errorf("解析响应JSON失败: %v,响应内容: %s", err, respJson)
|
|
||||||
}
|
|
||||||
|
|
||||||
dataKey := "response"
|
|
||||||
if isRequest {
|
if isRequest {
|
||||||
dataKey = "request"
|
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
|
||||||
}
|
}
|
||||||
|
|
||||||
_, hasData := reqData[dataKey]
|
encrypt, err := Decrypt(MerchantId, PrivateKey, PublicKey, response, isRequest)
|
||||||
_, 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 {
|
if err != nil {
|
||||||
return "", fmt.Errorf("读取私钥失败: %v", err)
|
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)
|
hexEncryptKey, err := hex.DecodeString(inEncryptKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("解密sm4key失败: %v", err)
|
return "", errors.New("解密sm4key失败")
|
||||||
}
|
}
|
||||||
sm4Key, err := sm2.Decrypt(privKey, hexEncryptKey, sm2.C1C3C2)
|
sm4Key, err := psbcutil.Sm2Decrypt(priKey, hexEncryptKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("解密sm2key失败: %v", err)
|
return "", errors.New("解密sm2key失败")
|
||||||
}
|
}
|
||||||
|
|
||||||
// 解密数据
|
|
||||||
request, _ := base64.StdEncoding.DecodeString(inData)
|
request, _ := base64.StdEncoding.DecodeString(inData)
|
||||||
encryptedSm4Key, err := sm4.CBCDecrypt(sm4Key, getSM4IV(), request)
|
|
||||||
decryptedStr := string(pkcs5UnPadding(encryptedSm4Key))
|
|
||||||
|
|
||||||
// 提取 body 字段(银行解密后的结构为 {head, body},业务数据在 body 中)
|
encryptedSm4Key, err := sm4.CBCDecrypt(sm4Key, psbcutil.GetSM4IV(), request)
|
||||||
var decryptedMap map[string]interface{}
|
|
||||||
if jsonErr := json.Unmarshal([]byte(decryptedStr), &decryptedMap); jsonErr == nil {
|
return string(psbcutil.Padding(encryptedSm4Key, 0)), 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
|
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 验签
|
// VerifySignature 验签
|
||||||
|
|
@ -244,7 +276,7 @@ func (c *Client) VerifySignature(content, signature string) bool {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Client) verify(content, signature string) bool {
|
func (c *Client) verify(content, signature string) bool {
|
||||||
pubKey, err := x509.ReadPublicKeyFromHex(c.cfg.SopPublicKey)
|
pubKey, err := sm2.ReadPublicKeyFromHex(c.cfg.SopPublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
panic(fmt.Sprintf("pubKeyBytes sm2 ReadPublicKeyFromHex err: %v", err))
|
panic(fmt.Sprintf("pubKeyBytes sm2 ReadPublicKeyFromHex err: %v", err))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,12 @@ import (
|
||||||
"encoding/hex"
|
"encoding/hex"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
|
"PaymentCenter/app/third/paymentService/psbc/internal/sm2"
|
||||||
|
psbcutil "PaymentCenter/app/third/paymentService/psbc/internal/util"
|
||||||
"github.com/ZZMarquis/gm/sm4"
|
"github.com/ZZMarquis/gm/sm4"
|
||||||
"github.com/tjfoc/gmsm/sm2"
|
|
||||||
"github.com/tjfoc/gmsm/x509"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// EncryptRequest 加密请求
|
// EncryptRequest 加密请求
|
||||||
|
|
@ -30,11 +31,11 @@ func EncryptRequest(request interface{}, cfg Config) (map[string]string, error)
|
||||||
return nil, fmt.Errorf("序列化请求失败: %v", err)
|
return nil, fmt.Errorf("序列化请求失败: %v", err)
|
||||||
}
|
}
|
||||||
inputJson := string(inputBytes)
|
inputJson := string(inputBytes)
|
||||||
|
|
||||||
// Step 2: SM4 CBC 加密
|
// Step 2: SM4 CBC 加密
|
||||||
sm4Key := generateSM4Key()
|
sm4Key := psbcutil.GenerateSM4Key()
|
||||||
iv := getSM4IV()
|
|
||||||
paddedData := pkcs5Padding([]byte(inputJson), sm4.BlockSize)
|
iv := psbcutil.GetSM4IV() // UISwD9fW6cFh9SNS
|
||||||
|
paddedData := psbcutil.Padding([]byte(inputJson), 1)
|
||||||
tmp, err := sm4.CBCEncrypt(sm4Key, iv, paddedData)
|
tmp, err := sm4.CBCEncrypt(sm4Key, iv, paddedData)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("SM4加密失败: %v", err)
|
return nil, fmt.Errorf("SM4加密失败: %v", err)
|
||||||
|
|
@ -42,12 +43,12 @@ func EncryptRequest(request interface{}, cfg Config) (map[string]string, error)
|
||||||
responseMsg := base64.StdEncoding.EncodeToString(tmp)
|
responseMsg := base64.StdEncoding.EncodeToString(tmp)
|
||||||
responseMsg = addNewline(responseMsg) // 对齐 YouChuKoffee,每76字符加 \r\n
|
responseMsg = addNewline(responseMsg) // 对齐 YouChuKoffee,每76字符加 \r\n
|
||||||
|
|
||||||
// Step 3: SM2 加密 SM4 密钥
|
// Step 3: SM2 加密 SM4 密钥(使用 copied sm2 包,对齐 YouChuKoffee 的 Sm2Encrypt)
|
||||||
sopPubKey, err := x509.ReadPublicKeyFromHex(cfg.SopPublicKey)
|
sopPubKey, err := sm2.ReadPublicKeyFromHex(cfg.SopPublicKey)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("读取SOP公钥失败: %v", err)
|
return nil, fmt.Errorf("读取SOP公钥失败: %v", err)
|
||||||
}
|
}
|
||||||
encryptKeyBytes, err := sm2.Encrypt(sopPubKey, sm4Key, rand.Reader, sm2.C1C3C2)
|
encryptKeyBytes, err := psbcutil.Sm2Encrypt(sopPubKey, sm4Key)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("SM2加密SM4密钥失败: %v", err)
|
return nil, fmt.Errorf("SM2加密SM4密钥失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -55,7 +56,8 @@ func EncryptRequest(request interface{}, cfg Config) (map[string]string, error)
|
||||||
|
|
||||||
// Step 4: 签名(对齐 YouChuKoffee sign():UserID = MerchantId)
|
// Step 4: 签名(对齐 YouChuKoffee sign():UserID = MerchantId)
|
||||||
signContent := fmt.Sprintf("%s%s%s", responseMsg, encryptKey, accessToken)
|
signContent := fmt.Sprintf("%s%s%s", responseMsg, encryptKey, accessToken)
|
||||||
signature, err := client.signWithUserID(signContent, []byte(cfg.MerchantId))
|
|
||||||
|
signature, err := sign(cfg.MerchantId, client.cfg.PrivateKeyCallback, signContent)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("生成签名失败: %v", err)
|
return nil, fmt.Errorf("生成签名失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
@ -71,16 +73,23 @@ func EncryptRequest(request interface{}, cfg Config) (map[string]string, error)
|
||||||
|
|
||||||
// signWithUserID 使用指定 UserID 对内容进行 SM2 签名
|
// signWithUserID 使用指定 UserID 对内容进行 SM2 签名
|
||||||
// 对齐 YouChuKoffee 的 sign() + rSToSign():直接返回 r.Text(16) + "#" + s.Text(16),不做 base64 编码
|
// 对齐 YouChuKoffee 的 sign() + rSToSign():直接返回 r.Text(16) + "#" + s.Text(16),不做 base64 编码
|
||||||
func (c *Client) signWithUserID(content string, userId []byte) (string, error) {
|
func sign(merchantId string, privateKeyHex string, signContent string) (string, error) {
|
||||||
privKey, err := x509.ReadPrivateKeyFromHex(c.cfg.PrivateKey)
|
privateKey, err := sm2.ReadPrivateKeyFromHex(privateKeyHex)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("读取私钥失败: %v", err)
|
return "", err
|
||||||
}
|
}
|
||||||
r, s, err := sm2.Sm2Sign(privKey, []byte(content), userId, rand.Reader)
|
|
||||||
|
r, s, err := sm2.Sm2Sign(privateKey, []byte(signContent), []byte(merchantId), rand.Reader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return "", fmt.Errorf("SM2签名失败: %v", err)
|
return "", err
|
||||||
}
|
}
|
||||||
return r.Text(16) + "#" + s.Text(16), nil
|
return rSToSign(r, s), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func rSToSign(r *big.Int, s *big.Int) string {
|
||||||
|
rStr := r.Text(16)
|
||||||
|
sStr := s.Text(16)
|
||||||
|
return fmt.Sprintf("%s#%s", rStr, sStr)
|
||||||
}
|
}
|
||||||
|
|
||||||
// addNewline 每76个字符添加 \r\n,对齐 YouChuKoffee 的 postbank.addNewline
|
// addNewline 每76个字符添加 \r\n,对齐 YouChuKoffee 的 postbank.addNewline
|
||||||
|
|
|
||||||
|
|
@ -35,7 +35,7 @@ func (c *Client) OrderQuery(orderNo string) (*OrderQueryResponse, error) {
|
||||||
"sourceId": "16",
|
"sourceId": "16",
|
||||||
"reqTraceId": now + RandomNumber(10),
|
"reqTraceId": now + RandomNumber(10),
|
||||||
"reqDate": now, // 14 位 YYYYMMDDHHmmss
|
"reqDate": now, // 14 位 YYYYMMDDHHmmss
|
||||||
"txnDt": now,
|
"txnDt": time.Now().Format("20060102"), // 8 位 YYYYMMDD,对齐 YouChuKoffee
|
||||||
"mchtNo": c.cfg.MchtNo,
|
"mchtNo": c.cfg.MchtNo,
|
||||||
"oldSeqNo": orderNo,
|
"oldSeqNo": orderNo,
|
||||||
},
|
},
|
||||||
|
|
@ -48,12 +48,13 @@ func (c *Client) OrderQuery(orderNo string) (*OrderQueryResponse, error) {
|
||||||
return nil, fmt.Errorf("加密请求失败: %v", err)
|
return nil, fmt.Errorf("加密请求失败: %v", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
BusiMainId = "202607301754241710448735"
|
||||||
|
|
||||||
encryptedBytes, _ := json.Marshal(encryptedReq)
|
encryptedBytes, _ := json.Marshal(encryptedReq)
|
||||||
url := c.cfg.OrderHost + c.cfg.MerchantId + ".htm?partnerTxSriNo=" + BusiMainId
|
url := c.cfg.OrderHost + c.cfg.MerchantId + ".htm?partnerTxSriNo=" + BusiMainId
|
||||||
|
|
||||||
// head 同时作为 HTTP Header 明文传输
|
// head 同时作为 HTTP Header 明文传输(对齐 YouChuKoffee 的 doPost 方式)
|
||||||
head := requestBody["head"].(map[string]string)
|
body, err := c.doPost(url, BusiMainId, "b2c.gatewaypay.orderQuery", encryptedBytes)
|
||||||
body, err := c.doPost(url, head, encryptedBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -53,9 +53,8 @@ func (c *Client) Refund(req RefundRequest) (*RefundResponse, error) {
|
||||||
encryptedBytes, _ := json.Marshal(encryptedReq)
|
encryptedBytes, _ := json.Marshal(encryptedReq)
|
||||||
url := c.cfg.OrderHost + c.cfg.MerchantId + ".htm?partnerTxSriNo=" + BusiMainId
|
url := c.cfg.OrderHost + c.cfg.MerchantId + ".htm?partnerTxSriNo=" + BusiMainId
|
||||||
|
|
||||||
// head 同时作为 HTTP Header 明文传输
|
// head 同时作为 HTTP Header 明文传输(对齐 YouChuKoffee 的 doPost 方式)
|
||||||
head := requestBody["head"].(map[string]string)
|
body, err := c.doPost(url, BusiMainId, "b2c.gatewaypay.orderRefund", encryptedBytes)
|
||||||
body, err := c.doPost(url, head, encryptedBytes)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@ type Config struct {
|
||||||
FileHost string
|
FileHost string
|
||||||
ShopId string
|
ShopId string
|
||||||
Psbcmcc string // 三方服务商标识,如 LSXD、JBZ
|
Psbcmcc string // 三方服务商标识,如 LSXD、JBZ
|
||||||
|
PrivateKeyCallback string // 邮储响应解密的私钥
|
||||||
}
|
}
|
||||||
|
|
||||||
// PaymentLinkRequest 生成支付链接请求
|
// PaymentLinkRequest 生成支付链接请求
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@ type PsbcPay struct {
|
||||||
AppID string `json:"app_id"` // 应用ID
|
AppID string `json:"app_id"` // 应用ID
|
||||||
SopPublicKey string `json:"sop_public_key"` // SOP公钥
|
SopPublicKey string `json:"sop_public_key"` // SOP公钥
|
||||||
PrivateKey string `json:"private_key"` // 商户私钥
|
PrivateKey string `json:"private_key"` // 商户私钥
|
||||||
|
PrivateKeyCallback string `json:"private_key_callback"` // 邮储响应解密的私钥
|
||||||
Pubkey string `json:"pubkey"` // 商户公钥
|
Pubkey string `json:"pubkey"` // 商户公钥
|
||||||
BankKey string `json:"bank_key"` // 银行公钥
|
BankKey string `json:"bank_key"` // 银行公钥
|
||||||
Sha string `json:"sha"` // SHA密钥
|
Sha string `json:"sha"` // SHA密钥
|
||||||
|
|
@ -50,6 +51,7 @@ func PsbcInitClient(psbcConfig PsbcPay) *psbc.Client {
|
||||||
FileHost: psbcConfig.FileHost,
|
FileHost: psbcConfig.FileHost,
|
||||||
ShopId: psbcConfig.ShopId,
|
ShopId: psbcConfig.ShopId,
|
||||||
Psbcmcc: psbcConfig.Psbcmcc,
|
Psbcmcc: psbcConfig.Psbcmcc,
|
||||||
|
PrivateKeyCallback: psbcConfig.PrivateKeyCallback,
|
||||||
}
|
}
|
||||||
return psbc.NewClient(cfg)
|
return psbc.NewClient(cfg)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ func TestPsbcPay(t *testing.T) {
|
||||||
AppID: "961925472724332544001",
|
AppID: "961925472724332544001",
|
||||||
SopPublicKey: "04CABE03249C94BDC8A6A4440DA1B2ADFACF73F4340E5F1B9A76463694B44C2E5600A9BEAA035739383C292CF9F1C4695FAAC7963CD5033D5D647A6B1EBE78EC6A",
|
SopPublicKey: "04CABE03249C94BDC8A6A4440DA1B2ADFACF73F4340E5F1B9A76463694B44C2E5600A9BEAA035739383C292CF9F1C4695FAAC7963CD5033D5D647A6B1EBE78EC6A",
|
||||||
PrivateKey: "140c5c68da1829b79d8e4629a8b9548bd694ccc7061fbacb1079aed0e5ac33fa",
|
PrivateKey: "140c5c68da1829b79d8e4629a8b9548bd694ccc7061fbacb1079aed0e5ac33fa",
|
||||||
|
PrivateKeyCallback: "1F0E2F085955461A9B87820AFBD513712CAEA89687BE657DE4EC91613BE62D32",
|
||||||
Pubkey: "0447fe9ed13aac6200c92b893ad1289a98437c8404e64b1d2f7c755698a8b8e8acb35c1db2bedc5213c930bdfc10efb0e12269cf6a06877679f114fe5ae9dd4469",
|
Pubkey: "0447fe9ed13aac6200c92b893ad1289a98437c8404e64b1d2f7c755698a8b8e8acb35c1db2bedc5213c930bdfc10efb0e12269cf6a06877679f114fe5ae9dd4469",
|
||||||
BankKey: "04E0E2E37F11901483CDFBC47F489D87D5D78C55DD7F919B73DEA83007748668B7871A1BA9608F156E25B7D64C7821379BAC1E2C591D5A50FF311D1AAE026C1DAE",
|
BankKey: "04E0E2E37F11901483CDFBC47F489D87D5D78C55DD7F919B73DEA83007748668B7871A1BA9608F156E25B7D64C7821379BAC1E2C591D5A50FF311D1AAE026C1DAE",
|
||||||
Sha: "636879vrt96t57w6f59",
|
Sha: "636879vrt96t57w6f59",
|
||||||
|
|
@ -69,18 +70,18 @@ func TestPsbcPay(t *testing.T) {
|
||||||
PayerClientIp: "192.168.110.235",
|
PayerClientIp: "192.168.110.235",
|
||||||
Psbc: psbc,
|
Psbc: psbc,
|
||||||
}
|
}
|
||||||
//支付
|
////支付
|
||||||
orderResult := PaymentService(c, request)
|
//orderResult := PaymentService(c, request)
|
||||||
t.Log(orderResult)
|
//t.Log(orderResult)
|
||||||
|
|
||||||
//// 查询订单
|
// 查询订单
|
||||||
//qreq := PayOrderQueryRequest{
|
qreq := PayOrderQueryRequest{
|
||||||
// OrderId: request.OrderId,
|
OrderId: request.OrderId,
|
||||||
// PayChannel: 3,
|
PayChannel: 3,
|
||||||
// Psbc: request.Psbc,
|
Psbc: request.Psbc,
|
||||||
//}
|
}
|
||||||
//queryResult := PayOrderQuery(c, qreq)
|
queryResult := PayOrderQuery(c, qreq)
|
||||||
//t.Log(queryResult)
|
t.Log(queryResult)
|
||||||
|
|
||||||
//// 退款
|
//// 退款
|
||||||
//refund := OrderRefundRequest{
|
//refund := OrderRefundRequest{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue