积分充值全流程落地:申请审核入账、凭证上传压缩转webp、管理员调账收权

- 新增积分充值申请与审核:商户提交充值申请(凭证图片必填,1元=100积分),
  管理员审核通过后事务内自动入账,幂等键防重复
- 新增通用图片上传组件 ImageUploader:canvas 压缩转 webp、预览、删除,
  后端按魔数校验图片格式并持久化到 data/uploads
- 管理员钱包调整改为按商户维度:移除商户侧调账接口,新增管理员
  平台商户积分调整与钱包流水查看
- 低余额 Webhook 告警配置持久化,充值入账/调账后自动检查推送
- 平台商户操作列移除进入/成员,积分充值页接入真实接口并修复金额单位换算
This commit is contained in:
yml2213
2026-08-04 13:03:29 +08:00
parent 54f05c15a4
commit 941e7ad79a
22 changed files with 1658 additions and 18 deletions
+15
View File
@@ -20,6 +20,7 @@ type Handlers struct {
Open *handler.OpenV1Handler
SourceOpen *handler.OpenHandler
Merchant *handler.MerchantHandler
Recharge *handler.RechargeHandler
JWT *jwt.Manager
Tenant *service.TenantService
OpenDB *gorm.DB
@@ -28,6 +29,7 @@ type Handlers struct {
OpenAPISecret string
OpenSignSkew int64
OpenAPIDebug bool
UploadDir string
}
// requestLogger 访问日志中间件:跳过 /health 健康检查(每 30 秒一次,避免刷屏)。
@@ -58,6 +60,11 @@ func Setup(h *Handlers) *gin.Engine {
c.JSON(200, gin.H{"status": "ok"})
})
// 上传的凭证等静态文件(公开访问,文件名不可枚举)
if h.UploadDir != "" {
r.Static("/uploads", h.UploadDir)
}
api := r.Group("/api")
{
api.POST("/auth/login", h.Auth.Login)
@@ -126,6 +133,10 @@ func Setup(h *Handlers) *gin.Engine {
merchant.POST("/orders/:order_no/delivery-link/restore", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureOrders), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.RestoreDeliveryLink)
merchant.GET("/wallet", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator, model.MemberRoleFinance), h.Merchant.GetWallet)
merchant.GET("/wallet/ledger", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Merchant.ListWalletLedger)
merchant.POST("/recharge/applications", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Recharge.CreateApplication)
merchant.GET("/recharge/applications", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator, model.MemberRoleFinance), h.Recharge.ListApplications)
merchant.GET("/recharge/alert-config", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Recharge.GetAlertConfig)
merchant.PUT("/recharge/alert-config", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureWallet), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleFinance), h.Recharge.SaveAlertConfig)
merchant.GET("/api-clients", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), h.Merchant.ListAPIClients)
merchant.POST("/api-clients", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.CreateAPIClient)
merchant.PATCH("/api-clients/:id/status", middleware.RequireMerchantFeature(h.OpenDB, model.MerchantFeatureAPI), middleware.RequireMerchantRole(model.MemberRoleOwner, model.MemberRoleOperator), h.Merchant.UpdateAPIClientStatus)
@@ -153,7 +164,11 @@ func Setup(h *Handlers) *gin.Engine {
admin.GET("/platform/merchants/:id/wallet", h.Merchant.AdminGetWallet)
admin.GET("/platform/merchants/:id/wallet/ledger", h.Merchant.AdminListWalletLedger)
admin.POST("/platform/merchants/:id/wallet/adjust", h.Merchant.AdminAdjustWallet)
admin.GET("/platform/recharge/applications", h.Recharge.AdminListApplications)
admin.POST("/platform/recharge/applications/:id/review", h.Recharge.AdminReviewApplication)
}
auth.POST("/upload", h.Recharge.Upload)
}
}
+3 -1
View File
@@ -24,6 +24,7 @@ func TestSetupDoesNotPanic(t *testing.T) {
callbackSvc := service.NewCallbackService(db, codec)
fulfillmentSvc := service.NewFulfillmentService(db, callbackSvc)
merchantSvc := service.NewMerchantService(db, codec, tenantSvc)
rechargeSvc := service.NewRechargeService(db, fulfillmentSvc, "")
deliverySvc := service.NewDeliveryService(fulfillmentSvc, "", "", "", "", 0)
defer func() {
@@ -37,7 +38,8 @@ func TestSetupDoesNotPanic(t *testing.T) {
User: handler.NewUserHandler(userSvc),
Open: handler.NewOpenV1Handler(merchantSvc, fulfillmentSvc, deliverySvc),
SourceOpen: handler.NewOpenHandler(fulfillmentSvc),
Merchant: handler.NewMerchantHandler(merchantSvc, fulfillmentSvc, callbackSvc, deliverySvc),
Merchant: handler.NewMerchantHandler(merchantSvc, fulfillmentSvc, callbackSvc, deliverySvc, rechargeSvc),
Recharge: handler.NewRechargeHandler(rechargeSvc),
JWT: jwt.NewManager("test-jwt"),
Tenant: tenantSvc,
OpenDB: db,