feat(server): pay webhook 接收器(验签/时间窗/nonce + out_trade_no 幂等开通,回 SUCCESS)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013nMthbVEmQquxBRKb9Fj8u
This commit is contained in:
wangjia
2026-07-10 22:38:01 +08:00
parent 932953bafe
commit 16b8823f1f
3 changed files with 398 additions and 0 deletions
+6
View File
@@ -302,12 +302,15 @@ func mountV1(r chi.Router, sqlDB *sql.DB, rdb *redis.Client, nodeSvc *nodes.Serv
// ── Pay(pay v2 统一支付网关;PAY_BASE_URL 未配则整组不挂载)──────────────
var payHandler *pay.Handler
var payWebhook *pay.WebhookHandler
if payBase := os.Getenv("PAY_BASE_URL"); payBase != "" {
paySystem := getenvDefault("PAY_BIZ_SYSTEM", "pangolin")
paySecret := os.Getenv("PAY_BIZ_SECRET")
payClient := pay.NewClient(payBase, paySystem, paySecret)
payStore := pay.NewStore(sqlDB)
payHandler = pay.NewHandler(payClient, payStore, sqlDB)
payWebhook = pay.NewWebhookHandler(payStore, codesSvc, sqlDB, rdb,
paySystem, paySecret, 5*time.Minute, 15*time.Minute)
} else {
log.Printf("PAY_BASE_URL 未配置 — /v1/pay 支付端点不挂载")
}
@@ -374,6 +377,9 @@ func mountV1(r chi.Router, sqlDB *sql.DB, rdb *redis.Client, nodeSvc *nodes.Serv
// Webhook: HMAC-authenticated, no JWT.
v1.Post("/webhook/store/codes", webhookHandler.ServeHTTP)
if payWebhook != nil {
v1.Post("/webhook/pay", payWebhook.ServeHTTP)
}
// Protected: all routes that require a valid Bearer JWT.
if tm != nil {