feat(backend): 在线购买/续费对接 pay 收款中枢——下单/webhook 验签入账/续期叠加/查单兜底
- POST /license/purchase(仅管理员)建单并调 pay 下单,返回收银台 pay_url - POST /pay/callback 公开接收器:HMAC 验签+时间戳窗口+按 out_trade_no 幂等+金额逐分核对,同事务续期 - 续期与兑换券同口径:未过期从到期日叠加、已过期从现在起算,写入 tier/max_devices/features - 后台每 60s 查单兜底防 webhook 丢失;closed 标 failed - 新表 license_purchases(schema.sql/AutoMigrate/testutil 同步);配置 PAY_SECRET/PAY_BASE_URL/PAY_RETURN_URL - 契约真相源 ~/code/pay-contract openapi.yaml v1.0.0 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JJ1g8XV1YhhmHRzhwWEW7o
This commit is contained in:
@@ -15,11 +15,14 @@ func Setup(r *gin.Engine, db *gorm.DB) {
|
||||
authSvc := service.NewAuthService(db)
|
||||
licenseSvc := service.NewLicenseService(db)
|
||||
stockSvc := service.NewStockService(db)
|
||||
paySvc := service.NewPayService(db, config.C.Pay.BaseURL, config.C.Pay.Secret, config.C.Pay.ReturnURL)
|
||||
service.StartPayReconcile(paySvc) // 查单兜底(PAY_SECRET 未配置时空转不启动)
|
||||
|
||||
// 处理器
|
||||
authH := handler.NewAuthHandler(authSvc, licenseSvc)
|
||||
sessionH := handler.NewSessionHandler(authSvc)
|
||||
licenseH := handler.NewLicenseHandler(licenseSvc)
|
||||
payH := handler.NewPayHandler(paySvc)
|
||||
productH := handler.NewProductHandler(db)
|
||||
warehouseH := handler.NewWarehouseHandler(db)
|
||||
partnerH := handler.NewPartnerHandler(db)
|
||||
@@ -79,6 +82,9 @@ func Setup(r *gin.Engine, db *gorm.DB) {
|
||||
public.POST("/register", registerIP, authH.Register)
|
||||
}
|
||||
|
||||
// pay 支付成功 webhook(公开路由,HMAC 验签在 handler 内;按 IP 限流防噪)
|
||||
v1.POST("/pay/callback", publicReadIP, payH.Callback)
|
||||
|
||||
// 需要 JWT 的基础路由组
|
||||
api := v1.Group("")
|
||||
api.Use(middleware.JWT(db))
|
||||
@@ -104,6 +110,9 @@ func Setup(r *gin.Engine, db *gorm.DB) {
|
||||
license.GET("/verify", licenseH.Verify)
|
||||
license.POST("/deactivate", licenseH.Deactivate)
|
||||
license.GET("/devices", licenseH.Devices)
|
||||
// 在线购买/续费(走 pay 收款中枢;仅管理员,handler 内判权)
|
||||
license.POST("/purchase", payH.Purchase)
|
||||
license.GET("/purchase/:out_trade_no", payH.PurchaseStatus)
|
||||
}
|
||||
|
||||
// 业务路由:ReadOnly + LicenseGuard(过期只读/锁定拦截写操作)
|
||||
|
||||
Reference in New Issue
Block a user