feat:邮储支付fix

This commit is contained in:
wolter 2026-08-10 11:31:00 +08:00
parent 7f2aceb5a7
commit 2543a0fd4d
11 changed files with 244 additions and 200 deletions

View File

@ -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

View File

@ -62,10 +62,10 @@ type CloseReqs struct {
// api 接口返回数据, 统一返回结构, order数据会进行加密 // api 接口返回数据, 统一返回结构, order数据会进行加密
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 {

View File

@ -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
} }

View File

@ -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
} }

View File

@ -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 的 Sm2SignUserID 传 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 func checkInData(reqData map[string]string, key string) (string, error) {
} data, ok := reqData[key]
bodyBytes, marshalErr := json.Marshal(bodyVal) if !ok {
if marshalErr == nil { return "", errors.New("请求数据中不存在" + key)
return string(bodyBytes), nil
}
}
} }
return data, nil
}
return decryptedStr, 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))
} }

View File

@ -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

View File

@ -34,8 +34,8 @@ func (c *Client) OrderQuery(orderNo string) (*OrderQueryResponse, error) {
"txnCode": "1004", "txnCode": "1004",
"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
} }

View File

@ -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
} }

View File

@ -6,23 +6,24 @@ import (
// Config 支付配置 // Config 支付配置
type Config struct { type Config struct {
MerchantId string MerchantId string
MchtNo string MchtNo string
AppID string AppID string
SopPublicKey string SopPublicKey string
PrivateKey string PrivateKey string
Pubkey string Pubkey string
BankKey string BankKey string
Sha string Sha string
ReturnUrl string ReturnUrl string
SuccessUrl string SuccessUrl string
NotifyUrl string NotifyUrl string
ShowTitleBar string ShowTitleBar string
LoginHost string LoginHost string
OrderHost string OrderHost string
FileHost string FileHost string
ShopId string ShopId string
Psbcmcc string // 三方服务商标识,如 LSXD、JBZ Psbcmcc string // 三方服务商标识,如 LSXD、JBZ
PrivateKeyCallback string // 邮储响应解密的私钥
} }
// PaymentLinkRequest 生成支付链接请求 // PaymentLinkRequest 生成支付链接请求

View File

@ -12,44 +12,46 @@ import (
) )
type PsbcPay struct { type PsbcPay struct {
MerchantId string `json:"merchant_id"` // 商户ID MerchantId string `json:"merchant_id"` // 商户ID
MchtNo string `json:"mcht_no"` // 商户号 MchtNo string `json:"mcht_no"` // 商户号
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"` // 商户私钥
Pubkey string `json:"pubkey"` // 商户公钥 PrivateKeyCallback string `json:"private_key_callback"` // 邮储响应解密的私钥
BankKey string `json:"bank_key"` // 银行公钥 Pubkey string `json:"pubkey"` // 商户公钥
Sha string `json:"sha"` // SHA密钥 BankKey string `json:"bank_key"` // 银行公钥
ReturnUrl string `json:"return_url"` // 支付返回地址 Sha string `json:"sha"` // SHA密钥
SuccessUrl string `json:"success_url"` // 支付成功地址 ReturnUrl string `json:"return_url"` // 支付返回地址
NotifyUrl string `json:"notify_url"` // 回调通知地址 SuccessUrl string `json:"success_url"` // 支付成功地址
ShowTitleBar string `json:"show_title_bar"` // 是否显示标题栏 NotifyUrl string `json:"notify_url"` // 回调通知地址
LoginHost string `json:"login_host"` // 登录服务地址 ShowTitleBar string `json:"show_title_bar"` // 是否显示标题栏
OrderHost string `json:"order_host"` // 订单服务地址 LoginHost string `json:"login_host"` // 登录服务地址
FileHost string `json:"file_host"` // 文件服务地址 OrderHost string `json:"order_host"` // 订单服务地址
ShopId string `json:"shop_id"` // 店铺ID FileHost string `json:"file_host"` // 文件服务地址
Psbcmcc string `json:"psbcmcc"` // 三方服务商标识 ShopId string `json:"shop_id"` // 店铺ID
Psbcmcc string `json:"psbcmcc"` // 三方服务商标识
} }
func PsbcInitClient(psbcConfig PsbcPay) *psbc.Client { func PsbcInitClient(psbcConfig PsbcPay) *psbc.Client {
cfg := psbc.Config{ cfg := psbc.Config{
MerchantId: psbcConfig.MerchantId, MerchantId: psbcConfig.MerchantId,
MchtNo: psbcConfig.MchtNo, MchtNo: psbcConfig.MchtNo,
AppID: psbcConfig.AppID, AppID: psbcConfig.AppID,
SopPublicKey: psbcConfig.SopPublicKey, SopPublicKey: psbcConfig.SopPublicKey,
PrivateKey: psbcConfig.PrivateKey, PrivateKey: psbcConfig.PrivateKey,
Pubkey: psbcConfig.Pubkey, Pubkey: psbcConfig.Pubkey,
BankKey: psbcConfig.BankKey, BankKey: psbcConfig.BankKey,
Sha: psbcConfig.Sha, Sha: psbcConfig.Sha,
ReturnUrl: psbcConfig.ReturnUrl, ReturnUrl: psbcConfig.ReturnUrl,
SuccessUrl: psbcConfig.SuccessUrl, SuccessUrl: psbcConfig.SuccessUrl,
NotifyUrl: psbcConfig.NotifyUrl, NotifyUrl: psbcConfig.NotifyUrl,
ShowTitleBar: psbcConfig.ShowTitleBar, ShowTitleBar: psbcConfig.ShowTitleBar,
LoginHost: psbcConfig.LoginHost, LoginHost: psbcConfig.LoginHost,
OrderHost: psbcConfig.OrderHost, OrderHost: psbcConfig.OrderHost,
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)
} }

View File

@ -35,22 +35,23 @@ func TestPsbcPay(t *testing.T) {
//data := `{"merchant_id": "your_merchant_id", "mcht_no": "your_mcht_no", "app_id": "your_app_id", "sop_public_key": "your_sop_public_key", "private_key": "your_private_key", "pubkey": "your_pubkey", "bank_key": "your_bank_key", "sha": "your_sha_key", "return_url": "https://example.com/return", "success_url": "https://example.com/success", "notify_url": "https://example.com/notify", "show_title_bar": "1", "login_host": "https://login.example.com", "order_host": "https://order.example.com", "file_host": "https://file.example.com", "shop_id": "your_shop_id"}` //data := `{"merchant_id": "your_merchant_id", "mcht_no": "your_mcht_no", "app_id": "your_app_id", "sop_public_key": "your_sop_public_key", "private_key": "your_private_key", "pubkey": "your_pubkey", "bank_key": "your_bank_key", "sha": "your_sha_key", "return_url": "https://example.com/return", "success_url": "https://example.com/success", "notify_url": "https://example.com/notify", "show_title_bar": "1", "login_host": "https://login.example.com", "order_host": "https://order.example.com", "file_host": "https://file.example.com", "shop_id": "your_shop_id"}`
psbc := PsbcPay{ psbc := PsbcPay{
MerchantId: "testMerchant001", MerchantId: "testMerchant001",
MchtNo: "100610100019042", MchtNo: "100610100019042",
AppID: "961925472724332544001", AppID: "961925472724332544001",
SopPublicKey: "04CABE03249C94BDC8A6A4440DA1B2ADFACF73F4340E5F1B9A76463694B44C2E5600A9BEAA035739383C292CF9F1C4695FAAC7963CD5033D5D647A6B1EBE78EC6A", SopPublicKey: "04CABE03249C94BDC8A6A4440DA1B2ADFACF73F4340E5F1B9A76463694B44C2E5600A9BEAA035739383C292CF9F1C4695FAAC7963CD5033D5D647A6B1EBE78EC6A",
PrivateKey: "140c5c68da1829b79d8e4629a8b9548bd694ccc7061fbacb1079aed0e5ac33fa", PrivateKey: "140c5c68da1829b79d8e4629a8b9548bd694ccc7061fbacb1079aed0e5ac33fa",
Pubkey: "0447fe9ed13aac6200c92b893ad1289a98437c8404e64b1d2f7c755698a8b8e8acb35c1db2bedc5213c930bdfc10efb0e12269cf6a06877679f114fe5ae9dd4469", PrivateKeyCallback: "1F0E2F085955461A9B87820AFBD513712CAEA89687BE657DE4EC91613BE62D32",
BankKey: "04E0E2E37F11901483CDFBC47F489D87D5D78C55DD7F919B73DEA83007748668B7871A1BA9608F156E25B7D64C7821379BAC1E2C591D5A50FF311D1AAE026C1DAE", Pubkey: "0447fe9ed13aac6200c92b893ad1289a98437c8404e64b1d2f7c755698a8b8e8acb35c1db2bedc5213c930bdfc10efb0e12269cf6a06877679f114fe5ae9dd4469",
Sha: "636879vrt96t57w6f59", BankKey: "04E0E2E37F11901483CDFBC47F489D87D5D78C55DD7F919B73DEA83007748668B7871A1BA9608F156E25B7D64C7821379BAC1E2C591D5A50FF311D1AAE026C1DAE",
ReturnUrl: "http://milk.h5.test.86698.cn/#/pages/ycnc/order", Sha: "636879vrt96t57w6f59",
SuccessUrl: "http://milk.h5.test.86698.cn/#/pages/ycnc/orderDetail?isPayBack=true&order_no=", ReturnUrl: "http://milk.h5.test.86698.cn/#/pages/ycnc/order",
NotifyUrl: "http://milk.test.api.cdlsxd.cn/api/v1/order/notify", SuccessUrl: "http://milk.h5.test.86698.cn/#/pages/ycnc/orderDetail?isPayBack=true&order_no=",
ShowTitleBar: "true", NotifyUrl: "http://milk.test.api.cdlsxd.cn/api/v1/order/notify",
LoginHost: "http://wap.dev.psbc.com/sop-h5/biz/crecard/", ShowTitleBar: "true",
OrderHost: "http://wap.dev.psbc.com/sop-h5/biz/unionpay/", LoginHost: "http://wap.dev.psbc.com/sop-h5/biz/crecard/",
FileHost: "", OrderHost: "http://wap.dev.psbc.com/sop-h5/biz/unionpay/",
ShopId: "LSXD0001", FileHost: "",
ShopId: "LSXD0001",
} }
data, _ := json.Marshal(psbc) data, _ := json.Marshal(psbc)
fmt.Println(string(data)) fmt.Println(string(data))
@ -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{