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
This commit is contained in:
@@ -8,6 +8,7 @@ import (
|
||||
"github.com/wangjia/pay/internal/channel"
|
||||
"github.com/wangjia/pay/internal/gateway"
|
||||
"github.com/wangjia/pay/internal/handler"
|
||||
"github.com/wangjia/pay/internal/middleware"
|
||||
"github.com/wangjia/pay/internal/service"
|
||||
)
|
||||
|
||||
@@ -44,10 +45,16 @@ func SetupV2(r *gin.Engine, g *gateway.Gateway) {
|
||||
h := handler.NewGatewayHandler(g)
|
||||
v2 := r.Group("/api/v2")
|
||||
{
|
||||
v2.POST("/orders", h.CreateOrder)
|
||||
// per-IP 限流(jiu 反馈波,P3 限流纵深):只挂改状态端点(下单/重试/取消)——
|
||||
// - callback 不限:来源是渠道服务器,不是终端用户,限了只会误伤渠道重投。
|
||||
// - GET 查单/查退款不限:只读、幂等,是结果页轮询的正常用法,风险远低于
|
||||
// 下单类写操作;真要限也该给比写操作更宽松的档,这里选择直接不限。
|
||||
// - /refunds(创建退款)/admin 组不在本次改动范围内(见 config.RateLimit 注释)。
|
||||
stateLimit := stateChangeRateLimit()
|
||||
v2.POST("/orders", stateLimit, h.CreateOrder)
|
||||
v2.GET("/orders/:order_no", h.GetStatus)
|
||||
v2.POST("/orders/:order_no/retry", h.Retry)
|
||||
v2.POST("/orders/:order_no/cancel", h.Cancel)
|
||||
v2.POST("/orders/:order_no/retry", stateLimit, h.Retry)
|
||||
v2.POST("/orders/:order_no/cancel", stateLimit, h.Cancel)
|
||||
v2.POST("/callback/:method", h.Callback)
|
||||
v2.POST("/refunds", h.CreateRefund)
|
||||
v2.GET("/refunds/:refund_id", h.GetRefund)
|
||||
@@ -59,3 +66,14 @@ func SetupV2(r *gin.Engine, g *gateway.Gateway) {
|
||||
admin.POST("/refunds/:refund_id/complete", h.CompleteRefund)
|
||||
}
|
||||
}
|
||||
|
||||
// stateChangeRateLimit 按 config.C.RateLimit 装配限流中间件;disabled=true 时放行一切
|
||||
// (no-op 中间件,不是跳过注册——保持路由树形状稳定,便于测试/观测)。每次 SetupV2 调用
|
||||
// 都构造一个新的 IPRateLimiter 实例(桶状态不跨多次 SetupV2 共享),这也让测试里反复
|
||||
// 建引擎彼此互不影响。
|
||||
func stateChangeRateLimit() gin.HandlerFunc {
|
||||
if config.C.RateLimit.Disabled {
|
||||
return func(c *gin.Context) { c.Next() }
|
||||
}
|
||||
return middleware.NewIPRateLimiter(config.C.RateLimit.RequestsPerMin).Gin()
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user