feat: 新增富友支付 SDK(RSA 签名、GBK 编解码、XML 协议、回调验签)

- pkg/fuiou/types.go: WxPreCreateRequest/Response、NotifyRequest 等 XML 结构体
- pkg/fuiou/client.go: Client 结构体、NewClient、字典序+GBK+MD5+RSA 签名/验签、HTTP 请求
- pkg/fuiou/wxprecreate.go: WxPreCreate 方法,支持公众号 JSAPI(JSAPI)和小程序(LETPAY)
- pkg/fuiou/notify.go: VerifyNotify(GBK→UTF-8+XML 解析+RSA 验签)、BuildNotifyResponse

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-opencode)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-03-16 23:28:42 +08:00
parent b0da71bd25
commit a308ee228b
4 changed files with 518 additions and 0 deletions

84
pkg/fuiou/notify.go Normal file
View File

@@ -0,0 +1,84 @@
package fuiou
import (
"encoding/xml"
"fmt"
"net/url"
"strings"
)
// 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), &notify); err != nil {
return nil, fmt.Errorf("XML 解析回调数据失败: %w", err)
}
// 验证签名
if err := c.Verify(&notify, notify.Sign); err != nil {
return nil, fmt.Errorf("回调签名验证失败: %w", err)
}
// 检查结果码
if notify.ResultCode != "000000" {
return &notify, fmt.Errorf("回调结果非成功: %s - %s", notify.ResultCode, notify.ResultMsg)
}
return &notify, nil
}
// BuildNotifySuccessResponse 构建成功响应GBK 编码的 XML
func BuildNotifySuccessResponse() []byte {
return buildNotifyResponse("000000", "success")
}
// BuildNotifyFailResponse 构建失败响应GBK 编码的 XML
func BuildNotifyFailResponse(msg string) []byte {
return buildNotifyResponse("999999", msg)
}
// buildNotifyResponse 构建回调响应 XMLGBK 编码)
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
}