应对管理员访问凭证失守导致的数据外泄风险,新增三层防护: 审计日志(admin-only 可见,用户不可见) - 新增 append-only audit_logs 表(migration 180)+ 异步批量写入 + 保留期清理 - 审计中间件挂在 admin/user/auth/admin-payment 组认证之后:记录所有变更类 请求 + 白名单敏感读取(账号/代理导出、备份下载、admin/user API key 读取) - 请求头凭证首尾掩码;请求体 JSON 递归脱敏(api_key/password 等擦除,base_url 保留以便追责);非 JSON body 不入库 - 无单条删除;全量清空需现场 TOTP 校验、拒绝 admin API key、未启用 2FA 不允许, 清空后同步写入留痕记录 会话 IP/UA 绑定(默认开启,可在系统设置关闭) - JWT 携带 session id + IP/UA 指纹哈希;IP 或 UA 任一变化即撤销会话家族并要求 重新登录;旧 token 无指纹时放行以平滑升级 敏感操作 step-up 2FA(sudo 窗口 15 分钟) - 账号/代理导出、DB 备份创建/下载、S3 目标修改要求近期 TOTP 二次验证;admin API key 一律拒绝;前端 useStepUp 组合式 + TotpStepUpDialog 弹码后自动重试 - API key 查看按需求暂不加强管控 前端:新增 /admin/audit-logs 操作日志页面(筛选/详情/2FA 清空)、侧边栏入口、 step-up 弹窗接入导出与备份流程、安全设置项(绑定开关 + 日志保留天数)、zh/en i18n
111 lines
4.0 KiB
Go
111 lines
4.0 KiB
Go
package routes
|
|
|
|
import (
|
|
"github.com/Wei-Shaw/sub2api/internal/handler"
|
|
"github.com/Wei-Shaw/sub2api/internal/handler/admin"
|
|
"github.com/Wei-Shaw/sub2api/internal/server/middleware"
|
|
"github.com/Wei-Shaw/sub2api/internal/service"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
)
|
|
|
|
// RegisterPaymentRoutes registers all payment-related routes:
|
|
// user-facing endpoints, webhook endpoints, and admin endpoints.
|
|
func RegisterPaymentRoutes(
|
|
v1 *gin.RouterGroup,
|
|
paymentHandler *handler.PaymentHandler,
|
|
webhookHandler *handler.PaymentWebhookHandler,
|
|
adminPaymentHandler *admin.PaymentHandler,
|
|
jwtAuth middleware.JWTAuthMiddleware,
|
|
adminAuth middleware.AdminAuthMiddleware,
|
|
auditLog middleware.AuditLogMiddleware,
|
|
settingService *service.SettingService,
|
|
) {
|
|
// --- User-facing payment endpoints (authenticated) ---
|
|
authenticated := v1.Group("/payment")
|
|
authenticated.Use(gin.HandlerFunc(jwtAuth))
|
|
authenticated.Use(middleware.BackendModeUserGuard(settingService))
|
|
{
|
|
authenticated.GET("/config", paymentHandler.GetPaymentConfig)
|
|
authenticated.GET("/checkout-info", paymentHandler.GetCheckoutInfo)
|
|
authenticated.GET("/plans", paymentHandler.GetPlans)
|
|
authenticated.GET("/limits", paymentHandler.GetLimits)
|
|
|
|
orders := authenticated.Group("/orders")
|
|
{
|
|
orders.POST("", paymentHandler.CreateOrder)
|
|
orders.POST("/verify", paymentHandler.VerifyOrder)
|
|
orders.GET("/my", paymentHandler.GetMyOrders)
|
|
orders.GET("/:id", paymentHandler.GetOrder)
|
|
orders.POST("/:id/cancel", paymentHandler.CancelOrder)
|
|
orders.POST("/:id/refund-request", paymentHandler.RequestRefund)
|
|
orders.GET("/refund-eligible-providers", paymentHandler.GetRefundEligibleProviders)
|
|
}
|
|
}
|
|
|
|
// --- Public payment endpoints (no auth) ---
|
|
// Signed resume-token recovery is the preferred public lookup path.
|
|
// The legacy anonymous out_trade_no verify endpoint remains available as a
|
|
// persisted-state compatibility path for staggered upgrades.
|
|
public := v1.Group("/payment/public")
|
|
{
|
|
public.POST("/orders/verify", paymentHandler.VerifyOrderPublic)
|
|
public.POST("/orders/resolve", paymentHandler.ResolveOrderPublicByResumeToken)
|
|
}
|
|
|
|
// --- Webhook endpoints (no auth) ---
|
|
webhook := v1.Group("/payment/webhook")
|
|
{
|
|
// EasyPay sends GET callbacks with query params
|
|
webhook.GET("/easypay", webhookHandler.EasyPayNotify)
|
|
webhook.POST("/easypay", webhookHandler.EasyPayNotify)
|
|
webhook.POST("/alipay", webhookHandler.AlipayNotify)
|
|
webhook.POST("/wxpay", webhookHandler.WxpayNotify)
|
|
webhook.POST("/stripe", webhookHandler.StripeWebhook)
|
|
webhook.POST("/airwallex", webhookHandler.AirwallexWebhook)
|
|
}
|
|
|
|
// --- Admin payment endpoints (admin auth) ---
|
|
adminGroup := v1.Group("/admin/payment")
|
|
adminGroup.Use(gin.HandlerFunc(adminAuth))
|
|
adminGroup.Use(gin.HandlerFunc(auditLog))
|
|
adminGroup.Use(middleware.AdminComplianceGuard(settingService))
|
|
{
|
|
// Dashboard
|
|
adminGroup.GET("/dashboard", adminPaymentHandler.GetDashboard)
|
|
|
|
// Config
|
|
adminGroup.GET("/config", adminPaymentHandler.GetConfig)
|
|
adminGroup.PUT("/config", adminPaymentHandler.UpdateConfig)
|
|
|
|
// Orders
|
|
adminOrders := adminGroup.Group("/orders")
|
|
{
|
|
adminOrders.GET("", adminPaymentHandler.ListOrders)
|
|
adminOrders.GET("/:id", adminPaymentHandler.GetOrderDetail)
|
|
adminOrders.POST("/:id/cancel", adminPaymentHandler.CancelOrder)
|
|
adminOrders.POST("/:id/retry", adminPaymentHandler.RetryFulfillment)
|
|
adminOrders.POST("/:id/refund", adminPaymentHandler.ProcessRefund)
|
|
adminOrders.POST("/:id/refund/query", adminPaymentHandler.QueryAndFinalizeRefund)
|
|
}
|
|
|
|
// Subscription Plans
|
|
plans := adminGroup.Group("/plans")
|
|
{
|
|
plans.GET("", adminPaymentHandler.ListPlans)
|
|
plans.POST("", adminPaymentHandler.CreatePlan)
|
|
plans.PUT("/:id", adminPaymentHandler.UpdatePlan)
|
|
plans.DELETE("/:id", adminPaymentHandler.DeletePlan)
|
|
}
|
|
|
|
// Provider Instances
|
|
providers := adminGroup.Group("/providers")
|
|
{
|
|
providers.GET("", adminPaymentHandler.ListProviders)
|
|
providers.POST("", adminPaymentHandler.CreateProvider)
|
|
providers.PUT("/:id", adminPaymentHandler.UpdateProvider)
|
|
providers.DELETE("/:id", adminPaymentHandler.DeleteProvider)
|
|
}
|
|
}
|
|
}
|