Files
NianAIGC/backend/internal/payments/wechat_test.go
T
2026-10-12 13:57:23 +08:00

84 lines
3.1 KiB
Go

package payments
import (
"bytes"
"context"
"crypto"
"crypto/aes"
"crypto/cipher"
"crypto/rand"
"crypto/rsa"
"crypto/sha256"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"fmt"
"net/http"
"strconv"
"testing"
"time"
)
func TestVerifiedNotificationRejectsReplayAndTampering(t *testing.T) {
key, err := rsa.GenerateKey(rand.Reader, 2048)
if err != nil {
t.Fatal(err)
}
pubDER, err := x509.MarshalPKIXPublicKey(&key.PublicKey)
if err != nil {
t.Fatal(err)
}
privDER, err := x509.MarshalPKCS8PrivateKey(key)
if err != nil {
t.Fatal(err)
}
config := Config{MchID: "mch", AppID: "app", NotifyURL: "https://example.com/notify", MerchantSerial: "serial",
PrivateKeyPEM: string(pem.EncodeToMemory(&pem.Block{Type: "PRIVATE KEY", Bytes: privDER})),
WechatPublicKeyID: "PUB_KEY_ID_123", WechatPublicKeyPEM: string(pem.EncodeToMemory(&pem.Block{Type: "PUBLIC KEY", Bytes: pubDER})),
APIv3Key: "12345678901234567890123456789012"}
provider, err := NewWeChatProvider(config)
if err != nil {
t.Fatal(err)
}
wp := provider.(*wechatProvider)
txn := map[string]any{"out_trade_no": "order1", "mchid": "mch", "appid": "app", "transaction_id": "wx1",
"trade_state": "SUCCESS", "amount": map[string]any{"total": 10000, "currency": "CNY"}, "success_time": time.Now().UTC().Format(time.RFC3339)}
plain, _ := json.Marshal(txn)
block, _ := aes.NewCipher([]byte(config.APIv3Key))
aead, _ := cipher.NewGCM(block)
nonce := "123456789012"
ciphertext := aead.Seal(nil, []byte(nonce), plain, []byte("associated"))
body, _ := json.Marshal(map[string]any{"id": "event1", "event_type": "TRANSACTION.SUCCESS", "resource_type": "encrypt-resource",
"resource": map[string]any{"algorithm": "AEAD_AES_256_GCM", "ciphertext": base64.StdEncoding.EncodeToString(ciphertext), "associated_data": "associated", "nonce": nonce}})
makeRequest := func(timestamp int64, tamper bool) *http.Request {
ts := strconv.FormatInt(timestamp, 10)
signed := fmt.Sprintf("%s\n%s\n%s\n", ts, "nonce1", body)
hash := sha256.Sum256([]byte(signed))
signature, err := rsa.SignPKCS1v15(rand.Reader, key, crypto.SHA256, hash[:])
if err != nil {
t.Fatal(err)
}
actualBody := body
if tamper {
actualBody = append(bytes.Clone(body), ' ')
}
req, _ := http.NewRequest(http.MethodPost, "https://example.com/notify", bytes.NewReader(actualBody))
req.Header.Set("Wechatpay-Serial", config.WechatPublicKeyID)
req.Header.Set("Wechatpay-Timestamp", ts)
req.Header.Set("Wechatpay-Nonce", "nonce1")
req.Header.Set("Wechatpay-Signature", base64.StdEncoding.EncodeToString(signature))
return req
}
valid, err := wp.ParseNotification(context.Background(), makeRequest(time.Now().Unix(), false))
if err != nil || valid.AmountFen != 10000 || valid.OrderID != "order1" {
t.Fatalf("valid notification: %v %+v", err, valid)
}
if _, err := wp.ParseNotification(context.Background(), makeRequest(time.Now().Add(-10*time.Minute).Unix(), false)); err == nil {
t.Fatal("accepted stale timestamp")
}
if _, err := wp.ParseNotification(context.Background(), makeRequest(time.Now().Unix(), true)); err == nil {
t.Fatal("accepted tampered body")
}
}