first commit

This commit is contained in:
2026-09-11 01:41:52 +08:00
commit 9f22e6ccae
263 changed files with 28935 additions and 0 deletions
+191
View File
@@ -0,0 +1,191 @@
package wechat
import (
"crypto/sha1"
"encoding/hex"
"encoding/json"
"sort"
"strings"
"testing"
)
// TestHMACSHA256Hex 用 RFC 4231 测试向量校验 HMAC-SHA256 实现。
func TestHMACSHA256Hex(t *testing.T) {
key := strings.Repeat("\x0b", 20) // 0x0b * 20
data := "Hi There"
want := "b0344c61d8db38535ca8afceaf0bf12b881dc200c9833da726e9376c2e32cff7"
if got := hmacSHA256Hex(key, data); got != want {
t.Fatalf("hmacSHA256Hex mismatch: got %s, want %s", got, want)
}
}
// TestBuildPaymentSign 校验 signData 字典序、paySig 与 signature 的内部一致性。
func TestBuildPaymentSign(t *testing.T) {
const (
appKey = "test-app-key"
sessionKey = "test-session-key"
offerID = "wx1234567890"
productID = "monthly_vip"
outTradeNo = "OD20260908120000abcdef"
attach = "uid=42"
)
virtualPayMu.Lock()
defaultVirtualPayClient = &virtualPayClient{appKey: appKey, env: XPayEnvProd}
virtualPayMu.Unlock()
defer func() {
virtualPayMu.Lock()
defaultVirtualPayClient = nil
virtualPayMu.Unlock()
}()
sign, err := BuildPaymentSign(offerID, productID, outTradeNo, attach, sessionKey, 1900)
if err != nil {
t.Fatalf("BuildPaymentSign error: %v", err)
}
// signData 字段必须按字典序排列(微信侧按字典序重组验签)。
wantOrder := []string{"attach", "buyQuantity", "currencyType", "env", "goodsPrice", "offerId", "outTradeNo", "productId"}
last := -1
for _, key := range wantOrder {
idx := strings.Index(sign.SignData, `"`+key+`"`)
if idx < 0 {
t.Fatalf("signData missing key %q: %s", key, sign.SignData)
}
if idx < last {
t.Fatalf("signData key order wrong at %q: %s", key, sign.SignData)
}
last = idx
}
// signData 必须是合法 JSON,且 offerId 为字符串。
var m map[string]any
if err := json.Unmarshal([]byte(sign.SignData), &m); err != nil {
t.Fatalf("signData is not valid JSON: %v", err)
}
if _, ok := m["offerId"].(string); !ok {
t.Fatalf("offerId must be string, got %T", m["offerId"])
}
if v, ok := m["goodsPrice"].(float64); !ok || v != 1900 {
t.Fatalf("goodsPrice mismatch: %v", m["goodsPrice"])
}
// paySig = hmac(appKey, "requestVirtualPayment&"+signData);signature = hmac(sessionKey, signData)。
wantPaySig := hmacSHA256Hex(appKey, requestVirtualPaymentURI+"&"+sign.SignData)
wantSignature := hmacSHA256Hex(sessionKey, sign.SignData)
if sign.PaySig != wantPaySig {
t.Fatalf("paySig mismatch: got %s, want %s", sign.PaySig, wantPaySig)
}
if sign.Signature != wantSignature {
t.Fatalf("signature mismatch: got %s, want %s", sign.Signature, wantSignature)
}
}
// TestCheckURLSignature 校验消息推送 URL 验签(SHA1 字典序拼接)。
func TestCheckURLSignature(t *testing.T) {
token := "my-token"
timestamp := "1756000000"
nonce := "abc123"
// 独立计算期望值:["1756000000","abc123","my-token"] 字典序拼接后做 SHA1。
arr := []string{token, timestamp, nonce}
sort.Strings(arr)
sum := sha1.Sum([]byte(arr[0] + arr[1] + arr[2]))
wantSig := hex.EncodeToString(sum[:])
if !CheckURLSignature(token, timestamp, nonce, wantSig) {
t.Fatal("CheckURLSignature should pass with correct signature")
}
if CheckURLSignature(token, timestamp, nonce, "bad") {
t.Fatal("CheckURLSignature should fail with wrong signature")
}
if CheckURLSignature("", timestamp, nonce, wantSig) {
t.Fatal("CheckURLSignature should fail with empty token")
}
}
// TestParseNotifyAndResponse 校验发货推送解析与应答构造。
func TestParseNotifyAndResponse(t *testing.T) {
raw := []byte(`{
"ToUserName":"gh_test",
"FromUserName":"official_openid",
"CreateTime":1756000000,
"MsgType":"event",
"Event":"xpay_goods_deliver_notify",
"OpenId":"o_user",
"OutTradeNo":"OD20260908120000abcdef",
"Env":0,
"WeChatPayInfo":{"MchOrderNo":"mch_123","TransactionId":"wx_123","PaidTime":1756000000},
"GoodsInfo":{"ProductId":"monthly_vip","Quantity":1,"OrigPrice":1900,"ActualPrice":1900,"Attach":"uid=42"}
}`)
base, err := ParseNotify(raw)
if err != nil {
t.Fatalf("ParseNotify error: %v", err)
}
if base.Event != EventGoodsDeliverNotify {
t.Fatalf("unexpected event: %s", base.Event)
}
if base.OutTradeNo != "OD20260908120000abcdef" {
t.Fatalf("unexpected out_trade_no: %s", base.OutTradeNo)
}
var deliver XPayGoodsDeliverNotify
if err := json.Unmarshal(raw, &deliver); err != nil {
t.Fatalf("unmarshal deliver notify: %v", err)
}
if deliver.GoodsInfo.ProductID != "monthly_vip" || deliver.GoodsInfo.ActualPrice != 1900 {
t.Fatalf("deliver goods info mismatch: %+v", deliver.GoodsInfo)
}
okBody := BuildNotifyResponse(true)
if !strings.Contains(string(okBody), `"ErrCode":0`) {
t.Fatalf("success response missing ErrCode 0: %s", okBody)
}
failBody := BuildNotifyResponse(false)
if !strings.Contains(string(failBody), `"ErrCode":1`) {
t.Fatalf("fail response missing ErrCode 1: %s", failBody)
}
}
// TestIsOrderPaidStatus 校验订单支付状态白名单判断:仅已知成功枚举为已支付,未知/空串按未支付。
func TestIsOrderPaidStatus(t *testing.T) {
cases := []struct {
status string
want bool
}{
{XPayOrderStatusPaid, true},
{"NOTPAY", false},
{"CLOSED", false},
{"UNKNOWN", false},
{"", false},
}
for _, c := range cases {
if got := IsOrderPaidStatus(c.status); got != c.want {
t.Fatalf("IsOrderPaidStatus(%q) = %v, want %v", c.status, got, c.want)
}
}
}
// TestResolveEnvKey 校验环境自动选择规则:只填现网/只填沙箱/两把都填/两把都空。
func TestResolveEnvKey(t *testing.T) {
// 只填现网 -> 现网
key, env, err := resolveEnvKey("prod-key", "")
if err != nil || key != "prod-key" || env != XPayEnvProd {
t.Fatalf("only env key: key=%q env=%d err=%v", key, env, err)
}
// 只填沙箱 -> 沙箱
key, env, err = resolveEnvKey("", "sandbox-key")
if err != nil || key != "sandbox-key" || env != XPayEnvSandbox {
t.Fatalf("only sandbox key: key=%q env=%d err=%v", key, env, err)
}
// 两把都填 -> 现网优先
key, env, err = resolveEnvKey("prod-key", "sandbox-key")
if err != nil || key != "prod-key" || env != XPayEnvProd {
t.Fatalf("both keys: key=%q env=%d err=%v", key, env, err)
}
// 两把都空 -> 报错
if _, _, err := resolveEnvKey("", ""); err == nil {
t.Fatal("empty keys should return error")
}
}