feat: 添加租户成员充值功能及相关文档
This commit is contained in:
@@ -56,6 +56,107 @@ type order struct {
|
||||
ledger *ledger
|
||||
}
|
||||
|
||||
// AdminTopupUser credits tenant balance to a tenant member (tenant-admin action).
|
||||
func (s *order) AdminTopupUser(ctx context.Context, tenantID, operatorUserID, targetUserID, amount int64, idempotencyKey, reason string, now time.Time) (*models.Order, error) {
|
||||
if tenantID <= 0 || operatorUserID <= 0 || targetUserID <= 0 {
|
||||
return nil, errorx.ErrInvalidParameter.WithMsg("tenant_id/operator_user_id/target_user_id must be > 0")
|
||||
}
|
||||
if amount <= 0 {
|
||||
return nil, errorx.ErrInvalidParameter.WithMsg("amount must be > 0")
|
||||
}
|
||||
if now.IsZero() {
|
||||
now = time.Now()
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"tenant_id": tenantID,
|
||||
"operator_user": operatorUserID,
|
||||
"target_user": targetUserID,
|
||||
"amount": amount,
|
||||
"idempotency_key": idempotencyKey,
|
||||
}).Info("services.order.admin.topup_user")
|
||||
|
||||
var out models.Order
|
||||
|
||||
err := s.db.WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||
// Ensure target user is a tenant member.
|
||||
var tu models.TenantUser
|
||||
if err := tx.
|
||||
Clauses(clause.Locking{Strength: "UPDATE"}).
|
||||
Where("tenant_id = ? AND user_id = ?", tenantID, targetUserID).
|
||||
First(&tu).Error; err != nil {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return errorx.ErrPreconditionFailed.WithMsg("目标用户不属于该租户")
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
// Idempotent by (tenant_id, user_id, idempotency_key) on orders.
|
||||
if idempotencyKey != "" {
|
||||
var existing models.Order
|
||||
if err := tx.Where(
|
||||
"tenant_id = ? AND user_id = ? AND idempotency_key = ?",
|
||||
tenantID, targetUserID, idempotencyKey,
|
||||
).First(&existing).Error; err == nil {
|
||||
out = existing
|
||||
return nil
|
||||
} else if !errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
orderModel := models.Order{
|
||||
TenantID: tenantID,
|
||||
UserID: targetUserID,
|
||||
Type: consts.OrderTypeTopup,
|
||||
Status: consts.OrderStatusPaid,
|
||||
Currency: consts.CurrencyCNY,
|
||||
AmountOriginal: amount,
|
||||
AmountDiscount: 0,
|
||||
AmountPaid: amount,
|
||||
Snapshot: types.JSON([]byte("{}")),
|
||||
IdempotencyKey: idempotencyKey,
|
||||
PaidAt: now,
|
||||
CreatedAt: now,
|
||||
UpdatedAt: now,
|
||||
}
|
||||
if err := tx.Create(&orderModel).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ledgerKey := fmt.Sprintf("topup:%d", orderModel.ID)
|
||||
remark := reason
|
||||
if remark == "" {
|
||||
remark = fmt.Sprintf("topup by tenant_admin:%d", operatorUserID)
|
||||
}
|
||||
if _, err := s.ledger.CreditTopupTx(ctx, tx, tenantID, targetUserID, orderModel.ID, amount, ledgerKey, remark, now); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
out = orderModel
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"tenant_id": tenantID,
|
||||
"operator_user": operatorUserID,
|
||||
"target_user": targetUserID,
|
||||
"amount": amount,
|
||||
"idempotency_key": idempotencyKey,
|
||||
}).WithError(err).Warn("services.order.admin.topup_user.failed")
|
||||
return nil, err
|
||||
}
|
||||
|
||||
logrus.WithFields(logrus.Fields{
|
||||
"tenant_id": tenantID,
|
||||
"target_user": targetUserID,
|
||||
"order_id": out.ID,
|
||||
"amount": amount,
|
||||
}).Info("services.order.admin.topup_user.ok")
|
||||
|
||||
return &out, nil
|
||||
}
|
||||
|
||||
// MyOrderPage lists orders for current user within a tenant.
|
||||
func (s *order) MyOrderPage(ctx context.Context, tenantID, userID int64, filter *dto.MyOrderListFilter) (*requests.Pager, error) {
|
||||
if tenantID <= 0 || userID <= 0 {
|
||||
|
||||
Reference in New Issue
Block a user