tenant: admin batch topup

This commit is contained in:
2025-12-19 13:55:20 +08:00
parent 86a1a0a2cc
commit 17d51d5ed2
11 changed files with 771 additions and 3 deletions

View File

@@ -0,0 +1,65 @@
package dto
// AdminBatchTopupItem 批量充值的单条明细。
type AdminBatchTopupItem struct {
// UserID 目标用户ID必须属于当前租户否则该条充值失败。
UserID int64 `json:"user_id"`
// Amount 充值金额:单位分;必须 > 0。
Amount int64 `json:"amount"`
// Reason 充值原因(可选):用于审计与追溯。
Reason string `json:"reason,omitempty"`
// IdempotencyKey 幂等键(可选):为空时后端会用 batch_idempotency_key 派生生成;
// 建议前端/调用方提供稳定值,便于重试时保持结果一致。
IdempotencyKey string `json:"idempotency_key,omitempty"`
}
// AdminBatchTopupForm 租户管理员批量充值请求参数。
type AdminBatchTopupForm struct {
// BatchIdempotencyKey 批次幂等键:必须填写;用于重试同一批次时保证不会重复入账。
BatchIdempotencyKey string `json:"batch_idempotency_key"`
// Items 充值明细列表:至少 1 条;单批次条数在业务侧限制(避免拖垮系统)。
Items []*AdminBatchTopupItem `json:"items"`
}
// AdminBatchTopupResultItem 批量充值的单条处理结果。
type AdminBatchTopupResultItem struct {
// UserID 目标用户ID。
UserID int64 `json:"user_id"`
// Amount 充值金额(单位分)。
Amount int64 `json:"amount"`
// IdempotencyKey 实际使用的幂等键:可能为客户端传入,也可能为后端派生生成。
IdempotencyKey string `json:"idempotency_key"`
// OrderID 生成的订单ID成功时返回失败时为 0。
OrderID int64 `json:"order_id,omitempty"`
// OK 是否成功true 表示该条充值已成功入账或命中幂等成功结果。
OK bool `json:"ok"`
// ErrorCode 错误码:失败时返回;成功时为 0。
ErrorCode int `json:"error_code,omitempty"`
// ErrorMessage 错误信息:失败时返回;成功时为空。
ErrorMessage string `json:"error_message,omitempty"`
}
// AdminBatchTopupResponse 批量充值的汇总结果。
type AdminBatchTopupResponse struct {
// Total 总条数:等于 items 长度。
Total int `json:"total"`
// Success 成功条数。
Success int `json:"success"`
// Failed 失败条数。
Failed int `json:"failed"`
// Items 明细结果列表:与请求 items 顺序一致,便于前端逐条展示。
Items []*AdminBatchTopupResultItem `json:"items"`
}

View File

@@ -234,3 +234,39 @@ func (*orderAdmin) adminTopupUser(
time.Now(),
)
}
// adminBatchTopupUsers
//
// @Summary 批量为租户成员充值(租户管理)
// @Tags Tenant
// @Accept json
// @Produce json
// @Param tenantCode path string true "Tenant Code"
// @Param form body dto.AdminBatchTopupForm true "Form"
// @Success 200 {object} dto.AdminBatchTopupResponse
//
// @Router /t/:tenantCode/v1/admin/users/topup/batch [post]
// @Bind tenant local key(tenant)
// @Bind tenantUser local key(tenant_user)
// @Bind form body
func (*orderAdmin) adminBatchTopupUsers(
ctx fiber.Ctx,
tenant *models.Tenant,
tenantUser *models.TenantUser,
form *dto.AdminBatchTopupForm,
) (*dto.AdminBatchTopupResponse, error) {
if err := requireTenantAdmin(tenantUser); err != nil {
return nil, err
}
if form == nil {
return nil, errorx.ErrInvalidParameter
}
log.WithFields(log.Fields{
"tenant_id": tenant.ID,
"user_id": tenantUser.UserID,
"total": len(form.Items),
}).Info("tenant.admin.users.topup.batch")
return services.Order.AdminBatchTopupUsers(ctx, tenant.ID, tenantUser.UserID, form, time.Now())
}

View File

@@ -179,6 +179,13 @@ func (r *Routes) Register(router fiber.Router) {
PathParam[int64]("userID"),
Body[dto.AdminTopupForm]("form"),
))
r.log.Debugf("Registering route: Post /t/:tenantCode/v1/admin/users/topup/batch -> orderAdmin.adminBatchTopupUsers")
router.Post("/t/:tenantCode/v1/admin/users/topup/batch"[len(r.Path()):], DataFunc3(
r.orderAdmin.adminBatchTopupUsers,
Local[*models.Tenant]("tenant"),
Local[*models.TenantUser]("tenant_user"),
Body[dto.AdminBatchTopupForm]("form"),
))
// Register routes for controller: orderMe
r.log.Debugf("Registering route: Get /t/:tenantCode/v1/orders -> orderMe.myOrders")
router.Get("/t/:tenantCode/v1/orders"[len(r.Path()):], DataFunc3(