Files
pay/internal/handler/metadata_test.go
T
wangjia 8051b0fb16 fix(v2): jiu 反馈波——端型透传(wap/page)+ alipay qr(当面付移植)+ 单号增熵/挡板限流
1. CreateOrderInput/RetryOrder 加 Metadata 通道,handler 白名单过滤(is_mobile/render)
   后原样传给 provider.CreateRequest;alipay adapter 据 is_mobile 选 wap/page。
2. alipay adapter 移植 v1 当面付(TradePreCreate):Metadata["render"]=="qr" → 二维码
   render_type,payload={qr_content,display_amount,currency},默认 2 小时窗口。
3. NewOutTradeNo 随机部分 8→16 hex 防生日碰撞(仍 <=64 字符,合规 DB 列/支付宝上限);
   v2 改状态端点(下单/重试/取消)加 per-IP 内存令牌桶限流,默认开 30/min,
   config.rate_limit.disabled 可关;callback/GET 查询不限。

顺带修:internal/reconcile/sync_test.go 的 gateway.New 调用漏传 refunds store,
预先存在的编译期回归(与本次改动无关,但挡住 go test ./... 全绿,一并修掉)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
2026-07-10 18:40:15 +08:00

55 lines
1.7 KiB
Go

package handler_test
import (
"net/http"
"testing"
"github.com/gin-gonic/gin"
"github.com/wangjia/pay/config"
"github.com/wangjia/pay/internal/accounts"
"github.com/wangjia/pay/internal/gateway"
"github.com/wangjia/pay/internal/model"
"github.com/wangjia/pay/internal/provider"
"github.com/wangjia/pay/internal/provider/fake"
"github.com/wangjia/pay/internal/router"
"github.com/wangjia/pay/internal/store"
)
// jiu 反馈波 item 1:POST /api/v2/orders 的 metadata 字段只应放行白名单键
// (is_mobile/render),任意其它键(潜在注入 provider 内部逻辑)必须被丢弃。
func TestV2CreateOrderMetadataWhitelist(t *testing.T) {
gin.SetMode(gin.TestMode)
db := model.OpenTestDB(t)
orders := store.NewOrderStore(db)
refunds := store.NewRefundStore(db)
preg := provider.NewRegistry()
fp := fake.New()
preg.Register(fp)
areg := accounts.New([]config.AccountConfig{
{AccountID: "fake-a1", Channel: "fake", Region: "global", Enabled: true, Weight: 1},
})
picker := accounts.NewRouter(areg, nil, nil)
g := gateway.New(orders, refunds, preg, picker, oneResolver{}, nopEnqueuer{}, "global")
r := gin.New()
router.SetupV2(r, g)
w, out := do(t, r, http.MethodPost, "/api/v2/orders", map[string]any{
"sku": "pro_year", "method": "fake",
"metadata": map[string]any{
"is_mobile": "1",
"evil_key": "inject-me", // 不在白名单,必须被丢弃
},
})
if w.Code != http.StatusOK {
t.Fatalf("create code=%d body=%v", w.Code, out)
}
got := fp.LastMetadata()
if got["is_mobile"] != "1" {
t.Fatalf("白名单键 is_mobile 应透传给 provider, got %+v", got)
}
if _, ok := got["evil_key"]; ok {
t.Fatalf("非白名单键 evil_key 不应透传给 provider, got %+v", got)
}
}