8051b0fb16
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
55 lines
1.7 KiB
Go
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)
|
|
}
|
|
}
|