fix: retry critical writes and allow super login
This commit is contained in:
@@ -665,70 +665,73 @@ func (s *creator) ProcessRefund(ctx context.Context, userID, id int64, form *cre
|
||||
}
|
||||
|
||||
if form.Action == "accept" {
|
||||
return models.Q.Transaction(func(tx *models.Query) error {
|
||||
// 1. Deduct Creator Balance
|
||||
// We credited Creator User Balance in Order.Pay. Now deduct it.
|
||||
info, err := tx.User.WithContext(ctx).
|
||||
Where(tx.User.ID.Eq(uid), tx.User.Balance.Gte(o.AmountPaid)).
|
||||
Update(tx.User.Balance, gorm.Expr("balance - ?", o.AmountPaid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if info.RowsAffected == 0 {
|
||||
return errorx.ErrQuotaExceeded.WithMsg("余额不足,无法退款")
|
||||
}
|
||||
|
||||
// 2. Credit Buyer Balance
|
||||
_, err = tx.User.WithContext(ctx).
|
||||
Where(tx.User.ID.Eq(o.UserID)).
|
||||
Update(tx.User.Balance, gorm.Expr("balance + ?", o.AmountPaid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Update Order Status
|
||||
_, err = tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(id)).Updates(&models.Order{
|
||||
Status: consts.OrderStatusRefunded,
|
||||
RefundedAt: time.Now(),
|
||||
RefundOperatorUserID: uid,
|
||||
RefundReason: form.Reason,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. Revoke Content Access
|
||||
// Fetch order items to get content IDs
|
||||
items, _ := tx.OrderItem.WithContext(ctx).Where(tx.OrderItem.OrderID.Eq(o.ID)).Find()
|
||||
contentIDs := make([]int64, len(items))
|
||||
for i, item := range items {
|
||||
contentIDs[i] = item.ContentID
|
||||
}
|
||||
if len(contentIDs) > 0 {
|
||||
_, err = tx.ContentAccess.WithContext(ctx).
|
||||
Where(tx.ContentAccess.UserID.Eq(o.UserID), tx.ContentAccess.ContentID.In(contentIDs...)).
|
||||
UpdateSimple(tx.ContentAccess.Status.Value(consts.ContentAccessStatusRevoked))
|
||||
// 关键退款事务:遇到数据库冲突/死锁时短暂退避重试,避免退款卡住。
|
||||
return retryCriticalWrite(ctx, func() error {
|
||||
return models.Q.Transaction(func(tx *models.Query) error {
|
||||
// 1. Deduct Creator Balance
|
||||
// We credited Creator User Balance in Order.Pay. Now deduct it.
|
||||
info, err := tx.User.WithContext(ctx).
|
||||
Where(tx.User.ID.Eq(uid), tx.User.Balance.Gte(o.AmountPaid)).
|
||||
Update(tx.User.Balance, gorm.Expr("balance - ?", o.AmountPaid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if info.RowsAffected == 0 {
|
||||
return errorx.ErrQuotaExceeded.WithMsg("余额不足,无法退款")
|
||||
}
|
||||
|
||||
// 5. Create Tenant Ledger
|
||||
ledger := &models.TenantLedger{
|
||||
TenantID: tid,
|
||||
UserID: uid,
|
||||
OrderID: o.ID,
|
||||
Type: consts.TenantLedgerTypeCreditRefund,
|
||||
Amount: o.AmountPaid,
|
||||
Remark: "退款: " + form.Reason,
|
||||
OperatorUserID: uid,
|
||||
IdempotencyKey: uuid.NewString(),
|
||||
}
|
||||
if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
|
||||
return err
|
||||
}
|
||||
// 2. Credit Buyer Balance
|
||||
_, err = tx.User.WithContext(ctx).
|
||||
Where(tx.User.ID.Eq(o.UserID)).
|
||||
Update(tx.User.Balance, gorm.Expr("balance + ?", o.AmountPaid))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
// 3. Update Order Status
|
||||
_, err = tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(id)).Updates(&models.Order{
|
||||
Status: consts.OrderStatusRefunded,
|
||||
RefundedAt: time.Now(),
|
||||
RefundOperatorUserID: uid,
|
||||
RefundReason: form.Reason,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 4. Revoke Content Access
|
||||
// Fetch order items to get content IDs
|
||||
items, _ := tx.OrderItem.WithContext(ctx).Where(tx.OrderItem.OrderID.Eq(o.ID)).Find()
|
||||
contentIDs := make([]int64, len(items))
|
||||
for i, item := range items {
|
||||
contentIDs[i] = item.ContentID
|
||||
}
|
||||
if len(contentIDs) > 0 {
|
||||
_, err = tx.ContentAccess.WithContext(ctx).
|
||||
Where(tx.ContentAccess.UserID.Eq(o.UserID), tx.ContentAccess.ContentID.In(contentIDs...)).
|
||||
UpdateSimple(tx.ContentAccess.Status.Value(consts.ContentAccessStatusRevoked))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 5. Create Tenant Ledger
|
||||
ledger := &models.TenantLedger{
|
||||
TenantID: tid,
|
||||
UserID: uid,
|
||||
OrderID: o.ID,
|
||||
Type: consts.TenantLedgerTypeCreditRefund,
|
||||
Amount: o.AmountPaid,
|
||||
Remark: "退款: " + form.Reason,
|
||||
OperatorUserID: uid,
|
||||
IdempotencyKey: uuid.NewString(),
|
||||
}
|
||||
if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user