feat:邮储支付代码完善补充缺失逻辑

This commit is contained in:
wolter 2026-07-29 16:47:49 +08:00
parent a2202ea200
commit 80bdf8b00a
8 changed files with 148 additions and 68 deletions

View File

@ -54,14 +54,18 @@ func (c *Client) BillQuery(date time.Time) (*BillQueryResponse, error) {
return nil, fmt.Errorf("读取响应失败: %v", err) return nil, fmt.Errorf("读取响应失败: %v", err)
} }
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP请求失败状态码: %d响应: %s", resp.StatusCode, string(body))
}
responseData, err := c.DecryptResponse(string(body), false) responseData, err := c.DecryptResponse(string(body), false)
if err != nil { if err != nil {
return nil, fmt.Errorf("解密响应失败: %v", err) return nil, fmt.Errorf("解密响应失败: %v,原始响应: %s", err, string(body))
} }
var result BillQueryResponse var result BillQueryResponse
if err := json.Unmarshal([]byte(responseData), &result); err != nil { if err := json.Unmarshal([]byte(responseData), &result); err != nil {
return nil, fmt.Errorf("解析响应失败: %v", err) return nil, fmt.Errorf("解析响应失败: %v,响应内容: %s", err, responseData)
} }
return &result, nil return &result, nil
@ -109,5 +113,9 @@ func (c *Client) BillDownload(fileId string, date time.Time) ([]byte, error) {
return nil, fmt.Errorf("读取响应失败: %v", err) 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 return body, nil
} }

View File

@ -118,32 +118,47 @@ func (c *Client) EncryptMobile(inputJson string, signStr string) (string, string
func (c *Client) DecryptResponse(respJson string, isRequest bool) (string, error) { func (c *Client) DecryptResponse(respJson string, isRequest bool) (string, error) {
var reqData map[string]string var reqData map[string]string
if err := json.Unmarshal([]byte(respJson), &reqData); err != nil { if err := json.Unmarshal([]byte(respJson), &reqData); err != nil {
return "", err 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"] = "" reqData["accessToken"] = ""
var keys []string inData := reqData[dataKey]
if isRequest { inSignature := reqData["signature"]
keys = []string{"request", "signature", "encryptKey", "accessToken"} inEncryptKey := reqData["encryptKey"]
} else {
keys = []string{"response", "signature", "encryptKey", "accessToken"}
}
var inEncryptKey, inData, inSignature string
for _, key := range keys {
data, ok := reqData[key]
if !ok {
return "", fmt.Errorf("请求数据中不存在%s", key)
}
switch key {
case "request", "response":
inData = data
case "signature":
inSignature = data
case "encryptKey":
inEncryptKey = data
}
}
// 验签 // 验签
checked := c.verify(fmt.Sprintf("%s%s%s", inData, inEncryptKey, ""), inSignature) checked := c.verify(fmt.Sprintf("%s%s%s", inData, inEncryptKey, ""), inSignature)
@ -213,7 +228,12 @@ func getSM4IV() []byte {
} }
func generateSM4Key() []byte { func generateSM4Key() []byte {
return make([]byte, 16) 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 { func pkcs5Padding(ciphertext []byte, blockSize int) []byte {

View File

@ -25,7 +25,7 @@ func (c *Client) ParseNotify(rawJson string) (*NotifyRequest, error) {
} }
// VerifyAndParseNotify 验签并解析回调通知 // VerifyAndParseNotify 验签并解析回调通知
func (c *Client) VerifyAndParseNotify(rawJson string) (map[string]interface{}, error) { func (c *Client) VerifyAndParseNotify(rawJson string) (*YouChuOrderNotifyRequest, error) {
var reqData map[string]string var reqData map[string]string
if err := json.Unmarshal([]byte(rawJson), &reqData); err != nil { if err := json.Unmarshal([]byte(rawJson), &reqData); err != nil {
return nil, fmt.Errorf("解析通知失败: %v", err) return nil, fmt.Errorf("解析通知失败: %v", err)
@ -44,10 +44,10 @@ func (c *Client) VerifyAndParseNotify(rawJson string) (map[string]interface{}, e
return nil, fmt.Errorf("解密通知失败: %v", err) return nil, fmt.Errorf("解密通知失败: %v", err)
} }
var result map[string]interface{} var result YouChuOrderNotifyRequest
if err := json.Unmarshal([]byte(decrypted), &result); err != nil { if err := json.Unmarshal([]byte(decrypted), &result); err != nil {
return nil, fmt.Errorf("解析解密后数据失败: %v", err) return nil, fmt.Errorf("解析解密后数据失败: %v", err)
} }
return result, nil return &result, nil
} }

View File

@ -58,14 +58,22 @@ func (c *Client) OrderQuery(orderNo string) (*OrderQueryResponse, error) {
return nil, fmt.Errorf("读取响应失败: %v", err) return nil, fmt.Errorf("读取响应失败: %v", err)
} }
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP请求失败状态码: %d响应: %s", resp.StatusCode, string(body))
}
responseData, err := c.DecryptResponse(string(body), false) responseData, err := c.DecryptResponse(string(body), false)
if err != nil { if err != nil {
return nil, fmt.Errorf("解密响应失败: %v", err) return nil, fmt.Errorf("解密响应失败: %v,原始响应: %s", err, string(body))
} }
var result OrderQueryResponse var result OrderQueryResponse
if err := json.Unmarshal([]byte(responseData), &result); err != nil { if err := json.Unmarshal([]byte(responseData), &result); err != nil {
return nil, fmt.Errorf("解析响应失败: %v", err) return nil, fmt.Errorf("解析响应失败: %v响应内容: %s", err, responseData)
}
if result.RespCode != "" && result.RespCode != "0000" && result.RespCode != "00" {
return nil, fmt.Errorf("订单查询失败,错误码: %s错误信息: %s", result.RespCode, result.RespMsg)
} }
return &result, nil return &result, nil

View File

@ -4,20 +4,26 @@ import (
"encoding/hex" "encoding/hex"
"fmt" "fmt"
"net/url" "net/url"
"strings"
) )
// CreatePaymentLink 创建支付链接 // CreatePaymentLink 创建支付链接
func (c *Client) CreatePaymentLink(req PaymentLinkRequest) (*PaymentLinkResponse, error) { func (c *Client) CreatePaymentLink(req PaymentLinkRequest) (*PaymentLinkResponse, error) {
payUrlTemplate := "%s&fromId=include_sb&tysdPayParams=%s&tysdShopid=%s&tysdEncryptKey=%s&returnUrl=%s&showTitleBar=%s" var payUrlTemplate string
if len(req.BackUrl) > 0 && strings.Contains(req.BackUrl, "?") {
payUrlTemplate = "%s&fromId=include_sb&tysdPayParams=%s&tysdShopid=%s&tysdEncryptKey=%s&returnUrl=%s&showTitleBar=%s"
} else {
payUrlTemplate = "%s?fromId=include_sb&tysdPayParams=%s&tysdShopid=%s&tysdEncryptKey=%s&returnUrl=%s&showTitleBar=%s"
}
orderData := map[string]string{ orderData := map[string]string{
"merchantNo": c.cfg.MchtNo, "merchantNo": c.cfg.MchtNo,
"orderInfos": req.ProductName, "orderInfos": req.ProductName,
"orderNo": req.OrderNo, "orderNo": req.OrderNo,
"amount": req.Price, "amount": req.Price,
"successUrl": hex.EncodeToString([]byte(c.cfg.SuccessUrl + req.OrderNo)), "successUrl": hex.EncodeToString([]byte(c.cfg.SuccessUrl)),
"acctType": "2", "acctType": "2",
"notifyUrl": hex.EncodeToString([]byte(c.cfg.SuccessUrl + req.OrderNo)), "notifyUrl": hex.EncodeToString([]byte(c.cfg.NotifyUrl)),
} }
signData := map[string]string{ signData := map[string]string{
@ -55,10 +61,21 @@ func (c *Client) CreatePaymentLink(req PaymentLinkRequest) (*PaymentLinkResponse
// CreatePaymentLinkWithBackUrl 创建支付链接(带 fromId 判断) // CreatePaymentLinkWithBackUrl 创建支付链接(带 fromId 判断)
func (c *Client) CreatePaymentLinkWithBackUrl(req PaymentLinkRequest) (*PaymentLinkResponse, error) { func (c *Client) CreatePaymentLinkWithBackUrl(req PaymentLinkRequest) (*PaymentLinkResponse, error) {
var payUrlTemplate string var payUrlTemplate string
if len(req.BackUrl) > 0 && (contains(req.BackUrl, "fromId")) { hasQuery := len(req.BackUrl) > 0 && strings.Contains(req.BackUrl, "?")
hasFromId := len(req.BackUrl) > 0 && contains(req.BackUrl, "fromId")
if hasFromId {
if hasQuery {
payUrlTemplate = "%s&tysdPayParams=%s&tysdShopid=%s&tysdEncryptKey=%s&returnUrl=%s&showTitleBar=%s" payUrlTemplate = "%s&tysdPayParams=%s&tysdShopid=%s&tysdEncryptKey=%s&returnUrl=%s&showTitleBar=%s"
} else { } else {
payUrlTemplate = "%s?tysdPayParams=%s&tysdShopid=%s&tysdEncryptKey=%s&returnUrl=%s&showTitleBar=%s"
}
} else {
if hasQuery {
payUrlTemplate = "%s&fromId=include_sb&tysdPayParams=%s&tysdShopid=%s&tysdEncryptKey=%s&returnUrl=%s&showTitleBar=%s" payUrlTemplate = "%s&fromId=include_sb&tysdPayParams=%s&tysdShopid=%s&tysdEncryptKey=%s&returnUrl=%s&showTitleBar=%s"
} else {
payUrlTemplate = "%s?fromId=include_sb&tysdPayParams=%s&tysdShopid=%s&tysdEncryptKey=%s&returnUrl=%s&showTitleBar=%s"
}
} }
orderData := map[string]string{ orderData := map[string]string{
@ -66,9 +83,9 @@ func (c *Client) CreatePaymentLinkWithBackUrl(req PaymentLinkRequest) (*PaymentL
"orderInfos": req.ProductName, "orderInfos": req.ProductName,
"orderNo": req.OrderNo, "orderNo": req.OrderNo,
"amount": req.Price, "amount": req.Price,
"successUrl": hex.EncodeToString([]byte(c.cfg.SuccessUrl + req.OrderNo)), "successUrl": hex.EncodeToString([]byte(c.cfg.SuccessUrl)),
"acctType": "2", "acctType": "2",
"notifyUrl": hex.EncodeToString([]byte(c.cfg.SuccessUrl + req.OrderNo)), "notifyUrl": hex.EncodeToString([]byte(c.cfg.NotifyUrl)),
} }
signData := map[string]string{ signData := map[string]string{
@ -104,5 +121,5 @@ func (c *Client) CreatePaymentLinkWithBackUrl(req PaymentLinkRequest) (*PaymentL
} }
func contains(s, substr string) bool { func contains(s, substr string) bool {
return len(s) > 0 && len(substr) > 0 && (s == substr || len(s) > len(substr) && s[:len(substr)] == substr) return len(s) > 0 && len(substr) > 0 && strings.Contains(s, substr)
} }

View File

@ -61,14 +61,22 @@ func (c *Client) Refund(req RefundRequest) (*RefundResponse, error) {
return nil, fmt.Errorf("读取响应失败: %v", err) return nil, fmt.Errorf("读取响应失败: %v", err)
} }
if resp.StatusCode != http.StatusOK {
return nil, fmt.Errorf("HTTP请求失败状态码: %d响应: %s", resp.StatusCode, string(body))
}
responseData, err := c.DecryptResponse(string(body), false) responseData, err := c.DecryptResponse(string(body), false)
if err != nil { if err != nil {
return nil, fmt.Errorf("解密响应失败: %v", err) return nil, fmt.Errorf("解密响应失败: %v,原始响应: %s", err, string(body))
} }
var result RefundResponse var result RefundResponse
if err := json.Unmarshal([]byte(responseData), &result); err != nil { if err := json.Unmarshal([]byte(responseData), &result); err != nil {
return nil, fmt.Errorf("解析响应失败: %v", err) return nil, fmt.Errorf("解析响应失败: %v响应内容: %s", err, responseData)
}
if result.RespCode != "" && result.RespCode != "0000" && result.RespCode != "00" {
return nil, fmt.Errorf("退款申请失败,错误码: %s错误信息: %s", result.RespCode, result.RespMsg)
} }
return &result, nil return &result, nil

View File

@ -48,10 +48,14 @@ type OrderQueryRequest struct {
// OrderQueryResponse 订单查询响应 // OrderQueryResponse 订单查询响应
type OrderQueryResponse struct { type OrderQueryResponse struct {
OrderNo string `json:"orderNo"`
OrderSta string `json:"orderSta"`
RespCode string `json:"code"` RespCode string `json:"code"`
RespMsg string `json:"respMsg"` RespMsg string `json:"respMsg"`
RespCd string `json:"respCd"`
ReqTraceId string `json:"reqTraceId"` //请求方流水号或者订单号,
OrderNo string `json:"orderNo"` //统一收单订单号
OrderSta string `json:"orderSta"` //03-支付成功 04-支付失败 05-检查失败
TxnFg string `json:"txnFg"` //0-正常 2-已部分退货 3- 已全部退货
TxnAmt string `json:"txnAmt"` // 交易金额 单位为元精确到小数点后2位
} }
// RefundRequest 退款请求 // RefundRequest 退款请求
@ -66,8 +70,13 @@ type RefundRequest struct {
type RefundResponse struct { type RefundResponse struct {
RespCode string `json:"code"` RespCode string `json:"code"`
RespMsg string `json:"respMsg"` RespMsg string `json:"respMsg"`
RespCd string `json:"respCd"`
TxnCode string `json:"txnCode"`
SourceId string `json:"sourceId"`
ReqTraceId string `json:"ReqTraceId"`
RefundOrderNo string `json:"refundOrderNo"` RefundOrderNo string `json:"refundOrderNo"`
RefundOrderSta string `json:"refundOrderSta"` RefundOrderSta string `json:"refundOrderSta"`
MchtNo string `json:"mchtNo"`
TxnAmt string `json:"txnAmt"` TxnAmt string `json:"txnAmt"`
} }
@ -103,6 +112,20 @@ type NotifyResponse struct {
LinkUrl string `json:"linkUrl"` LinkUrl string `json:"linkUrl"`
} }
type YouChuOrderNotifyRequest struct {
TxnCode string `json:"txnCode"`
SourceId string `json:"sourceId"`
ReqDate string `json:"reqDate"`
ReqTraceId string `json:"reqTraceId"` // 商户提交的订单号
OrderNo string `json:"orderNo"` // 银行的订单号
TxnAmt string `json:"txnAmt"`
OrderSta string `json:"orderSta"` //03-支付成功 04-支付失败05-检查失败
MchtNo string `json:"mchtNo"`
TermId string `json:"termId"`
TxnFlag string `json:"txnFlag"` //01-消费 03-退货
TxnKind string `json:"txnKind"` //01-银行卡 02-微信 03-支付宝
}
// OrderStatus 订单状态 // OrderStatus 订单状态
type OrderStatus string type OrderStatus string

View File

@ -5,7 +5,6 @@ import (
psbc "PaymentCenter/app/third/paymentService/psbc" psbc "PaymentCenter/app/third/paymentService/psbc"
"PaymentCenter/config" "PaymentCenter/config"
"context" "context"
"errors"
"fmt" "fmt"
"github.com/qit-team/snow-core/log/logger" "github.com/qit-team/snow-core/log/logger"
"strconv" "strconv"
@ -101,17 +100,26 @@ func PsbcOrderQuery(ctx context.Context, psbcConfig PsbcPay, orderNo string) (Pa
tradeStateDesc = "未支付" tradeStateDesc = "未支付"
} }
amountTotal := int64(0) var amountTotal int64
payerTotal := int64(0) var payerTotal int64
if resp.TxnAmt != "" {
amountFloat, err := strconv.ParseFloat(resp.TxnAmt, 64)
if err == nil {
amountTotal = int64(amountFloat * 100)
payerTotal = int64(amountFloat * 100)
}
}
successTime := ""
outTradeNo, _ := strconv.ParseInt(orderNo, 10, 64) outTradeNo, _ := strconv.ParseInt(orderNo, 10, 64)
return PayOrderQueryInfo{ return PayOrderQueryInfo{
AppId: psbcConfig.AppID, AppId: psbcConfig.AppID,
OutTradeNo: outTradeNo, OutTradeNo: outTradeNo,
TransactionId: "", TransactionId: resp.OrderNo,
TradeState: tradeState, TradeState: tradeState,
TradeStateDesc: tradeStateDesc, TradeStateDesc: tradeStateDesc,
SuccessTime: "", SuccessTime: successTime,
AmountTotal: amountTotal, AmountTotal: amountTotal,
PayerTotal: payerTotal, PayerTotal: payerTotal,
}, nil }, nil
@ -177,26 +185,14 @@ func PsbcVerifyAndParseNotify(rawBody string, psbcConfig PsbcPay) (orderId int64
return 0, 0, 0, "", err return 0, 0, 0, "", err
} }
orderNoStr := "" orderId, err = strconv.ParseInt(result.ReqTraceId, 10, 64)
if orderNo, ok := result["orderNo"].(string); ok {
orderNoStr = orderNo
}
if orderNoStr == "" {
return 0, 0, 0, "", errors.New("回调数据中缺少订单号")
}
orderId, err = strconv.ParseInt(orderNoStr, 10, 64)
if err != nil { if err != nil {
logger.Error(context.Background(), "PsbcVerifyAndParseNotify 发生错误", fmt.Sprintf("订单号转换失败,错误信息:%s", err.Error())) logger.Error(context.Background(), "PsbcVerifyAndParseNotify 发生错误", fmt.Sprintf("订单号转换失败,错误信息:%s", err.Error()))
return 0, 0, 0, "", err return 0, 0, 0, "", err
} }
amountStr := "" if result.TxnAmt != "" {
if amount, ok := result["amount"].(string); ok { amountFloat, err := strconv.ParseFloat(result.TxnAmt, 64)
amountStr = amount
}
if amountStr != "" {
amountFloat, err := strconv.ParseFloat(amountStr, 64)
if err == nil { if err == nil {
amountTotal = int64(amountFloat * 100) amountTotal = int64(amountFloat * 100)
payerTotal = amountTotal payerTotal = amountTotal
@ -204,8 +200,8 @@ func PsbcVerifyAndParseNotify(rawBody string, psbcConfig PsbcPay) (orderId int64
} }
orderSta := "" orderSta := ""
if sta, ok := result["orderSta"].(string); ok { if result.OrderSta != "" {
orderSta = sta orderSta = result.OrderSta
} }
switch orderSta { switch orderSta {