All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 7m5s
- WechatPayCallback 移除对永远为 nil 的单例 wechatPayment 的依赖, 改为动态按 payment_config_id 加载创建订单时所用的精确配置 - PaymentV2Service 新增 VerifyCallback()(v2 XML 验签)和 PeekOrderNo()(不验签预解析) - FuiouPayCallback 由 GetActiveConfig 改为 GetConfigForCallback, 通过 ParseNotify 预解析订单号后精确加载配置,防止切换配置后旧订单验签失败 - wechat_config.Service 注入 assetRechargeStore/agentRechargeStore, 新增 GetConfigForCallback():按订单号前缀查 payment_config_id, GetByIDUnscoped 加载配置(含已停用/软删除记录),找不到则回退激活配置 - 修复微信 v2 回调响应格式:从 JSON 改为 XML,避免微信持续重试
108 lines
3.0 KiB
Go
108 lines
3.0 KiB
Go
package fuiou
|
||
|
||
import (
|
||
"encoding/xml"
|
||
"fmt"
|
||
"net/url"
|
||
"strings"
|
||
)
|
||
|
||
// ParseNotify 解析回调通知(不验签)
|
||
// 用于在验签前提取订单号以确定使用哪套支付配置
|
||
func ParseNotify(rawBody []byte) (*NotifyRequest, error) {
|
||
content := string(rawBody)
|
||
if strings.HasPrefix(content, "req=") {
|
||
content = content[4:]
|
||
}
|
||
decoded, err := url.QueryUnescape(content)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("URL 解码回调数据失败: %w", err)
|
||
}
|
||
utf8Bytes, err := GBKToUTF8([]byte(decoded))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("GBK 转 UTF-8 失败: %w", err)
|
||
}
|
||
utf8Str := strings.Replace(string(utf8Bytes), `encoding="GBK"`, `encoding="UTF-8"`, 1)
|
||
var notify NotifyRequest
|
||
if err := xml.Unmarshal([]byte(utf8Str), ¬ify); err != nil {
|
||
return nil, fmt.Errorf("XML 解析回调数据失败: %w", err)
|
||
}
|
||
return ¬ify, nil
|
||
}
|
||
|
||
// VerifyNotify 验证回调通知并解析数据
|
||
// rawBody: 原始请求 body(可能包含 req= 前缀的 URL 编码 GBK XML)
|
||
func (c *Client) VerifyNotify(rawBody []byte) (*NotifyRequest, error) {
|
||
content := string(rawBody)
|
||
|
||
// 处理 req= 前缀(富友回调可能以 form 格式发送)
|
||
if strings.HasPrefix(content, "req=") {
|
||
content = content[4:]
|
||
}
|
||
|
||
// URL 解码
|
||
decoded, err := url.QueryUnescape(content)
|
||
if err != nil {
|
||
return nil, fmt.Errorf("URL 解码回调数据失败: %w", err)
|
||
}
|
||
|
||
// GBK → UTF-8
|
||
utf8Bytes, err := GBKToUTF8([]byte(decoded))
|
||
if err != nil {
|
||
return nil, fmt.Errorf("GBK 转 UTF-8 失败: %w", err)
|
||
}
|
||
|
||
// 替换编码声明
|
||
utf8Str := strings.Replace(string(utf8Bytes), `encoding="GBK"`, `encoding="UTF-8"`, 1)
|
||
|
||
// XML 解析
|
||
var notify NotifyRequest
|
||
if err := xml.Unmarshal([]byte(utf8Str), ¬ify); err != nil {
|
||
return nil, fmt.Errorf("XML 解析回调数据失败: %w", err)
|
||
}
|
||
|
||
// 验证签名
|
||
if err := c.Verify(¬ify, notify.Sign); err != nil {
|
||
return nil, fmt.Errorf("回调签名验证失败: %w", err)
|
||
}
|
||
|
||
// 检查结果码
|
||
if notify.ResultCode != "000000" {
|
||
return ¬ify, fmt.Errorf("回调结果非成功: %s - %s", notify.ResultCode, notify.ResultMsg)
|
||
}
|
||
|
||
return ¬ify, nil
|
||
}
|
||
|
||
// BuildNotifySuccessResponse 构建成功响应(GBK 编码的 XML)
|
||
func BuildNotifySuccessResponse() []byte {
|
||
return buildNotifyResponse("000000", "success")
|
||
}
|
||
|
||
// BuildNotifyFailResponse 构建失败响应(GBK 编码的 XML)
|
||
func BuildNotifyFailResponse(msg string) []byte {
|
||
return buildNotifyResponse("999999", msg)
|
||
}
|
||
|
||
// buildNotifyResponse 构建回调响应 XML(GBK 编码)
|
||
func buildNotifyResponse(code, msg string) []byte {
|
||
resp := NotifyResponse{
|
||
ResultCode: code,
|
||
ResultMsg: msg,
|
||
}
|
||
|
||
xmlBytes, err := xml.Marshal(resp)
|
||
if err != nil {
|
||
return []byte(`<?xml version="1.0" encoding="GBK"?><xml><result_code>999999</result_code><result_msg>internal error</result_msg></xml>`)
|
||
}
|
||
|
||
xmlStr := `<?xml version="1.0" encoding="GBK"?>` + string(xmlBytes)
|
||
|
||
gbkBytes, err := utf8ToGBK([]byte(xmlStr))
|
||
if err != nil {
|
||
return []byte(xmlStr)
|
||
}
|
||
|
||
return gbkBytes
|
||
}
|