fix: retry critical writes and allow super login
This commit is contained in:
@@ -247,106 +247,110 @@ func (s *order) payWithBalance(ctx context.Context, o *models.Order) (*transacti
|
||||
|
||||
func (s *order) settleOrder(ctx context.Context, o *models.Order, method, externalID string) error {
|
||||
var tenantOwnerID int64
|
||||
err := models.Q.Transaction(func(tx *models.Query) error {
|
||||
// 1. Handle Balance Updates
|
||||
if o.Type == consts.OrderTypeRecharge {
|
||||
// Income: Recharge (Credit User 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
|
||||
// 关键结算事务:遇到数据库冲突/死锁时短暂退避重试,避免支付状态卡死。
|
||||
err := retryCriticalWrite(ctx, func() error {
|
||||
tenantOwnerID = 0
|
||||
return models.Q.Transaction(func(tx *models.Query) error {
|
||||
// 1. Handle Balance Updates
|
||||
if o.Type == consts.OrderTypeRecharge {
|
||||
// Income: Recharge (Credit User 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
|
||||
}
|
||||
} else if method == "balance" {
|
||||
// Expense: Purchase with Balance (Deduct User Balance)
|
||||
info, err := tx.User.WithContext(ctx).
|
||||
Where(tx.User.ID.Eq(o.UserID), 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("余额不足")
|
||||
}
|
||||
}
|
||||
} else if method == "balance" {
|
||||
// Expense: Purchase with Balance (Deduct User Balance)
|
||||
info, err := tx.User.WithContext(ctx).
|
||||
Where(tx.User.ID.Eq(o.UserID), 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. Update Order Status
|
||||
now := time.Now()
|
||||
// snapshot := o.Snapshot // Preserve existing snapshot or update it with external ID
|
||||
// TODO: Update snapshot with payment info
|
||||
_, err := tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(o.ID)).Updates(&models.Order{
|
||||
Status: consts.OrderStatusPaid,
|
||||
PaidAt: now,
|
||||
UpdatedAt: now,
|
||||
// 2. Update Order Status
|
||||
now := time.Now()
|
||||
// snapshot := o.Snapshot // Preserve existing snapshot or update it with external ID
|
||||
// TODO: Update snapshot with payment info
|
||||
_, err := tx.Order.WithContext(ctx).Where(tx.Order.ID.Eq(o.ID)).Updates(&models.Order{
|
||||
Status: consts.OrderStatusPaid,
|
||||
PaidAt: now,
|
||||
UpdatedAt: now,
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Grant Content Access
|
||||
items, _ := tx.OrderItem.WithContext(ctx).Where(tx.OrderItem.OrderID.Eq(o.ID)).Find()
|
||||
for _, item := range items {
|
||||
// Check if access already exists (idempotency)
|
||||
exists, _ := tx.ContentAccess.WithContext(ctx).
|
||||
Where(tx.ContentAccess.UserID.Eq(o.UserID), tx.ContentAccess.ContentID.Eq(item.ContentID)).
|
||||
Exists()
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
|
||||
access := &models.ContentAccess{
|
||||
TenantID: item.TenantID,
|
||||
UserID: o.UserID,
|
||||
ContentID: item.ContentID,
|
||||
OrderID: o.ID,
|
||||
Status: consts.ContentAccessStatusActive,
|
||||
}
|
||||
if err := tx.ContentAccess.WithContext(ctx).Save(access); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Create Tenant Ledger (Revenue) - Only for Content Purchase
|
||||
if o.Type == consts.OrderTypeContentPurchase {
|
||||
t, err := tx.Tenant.WithContext(ctx).Where(tx.Tenant.ID.Eq(o.TenantID)).First()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tenantOwnerID = t.UserID
|
||||
|
||||
// Calculate Commission
|
||||
amount := o.AmountPaid
|
||||
fee := int64(float64(amount) * 0.10)
|
||||
creatorIncome := amount - fee
|
||||
|
||||
// Credit Tenant Owner Balance (Net Income)
|
||||
_, err = tx.User.WithContext(ctx).
|
||||
Where(tx.User.ID.Eq(tenantOwnerID)).
|
||||
Update(tx.User.Balance, gorm.Expr("balance + ?", creatorIncome))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ledger := &models.TenantLedger{
|
||||
TenantID: o.TenantID,
|
||||
UserID: t.UserID, // Owner
|
||||
OrderID: o.ID,
|
||||
Type: consts.TenantLedgerTypeDebitPurchase, // Income from purchase
|
||||
Amount: creatorIncome,
|
||||
BalanceBefore: 0, // TODO
|
||||
BalanceAfter: 0, // TODO
|
||||
FrozenBefore: 0,
|
||||
FrozenAfter: 0,
|
||||
IdempotencyKey: uuid.NewString(),
|
||||
Remark: "内容销售收入 (扣除平台费)",
|
||||
OperatorUserID: o.UserID,
|
||||
}
|
||||
if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 3. Grant Content Access
|
||||
items, _ := tx.OrderItem.WithContext(ctx).Where(tx.OrderItem.OrderID.Eq(o.ID)).Find()
|
||||
for _, item := range items {
|
||||
// Check if access already exists (idempotency)
|
||||
exists, _ := tx.ContentAccess.WithContext(ctx).
|
||||
Where(tx.ContentAccess.UserID.Eq(o.UserID), tx.ContentAccess.ContentID.Eq(item.ContentID)).
|
||||
Exists()
|
||||
if exists {
|
||||
continue
|
||||
}
|
||||
|
||||
access := &models.ContentAccess{
|
||||
TenantID: item.TenantID,
|
||||
UserID: o.UserID,
|
||||
ContentID: item.ContentID,
|
||||
OrderID: o.ID,
|
||||
Status: consts.ContentAccessStatusActive,
|
||||
}
|
||||
if err := tx.ContentAccess.WithContext(ctx).Save(access); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
// 4. Create Tenant Ledger (Revenue) - Only for Content Purchase
|
||||
if o.Type == consts.OrderTypeContentPurchase {
|
||||
t, err := tx.Tenant.WithContext(ctx).Where(tx.Tenant.ID.Eq(o.TenantID)).First()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tenantOwnerID = t.UserID
|
||||
|
||||
// Calculate Commission
|
||||
amount := o.AmountPaid
|
||||
fee := int64(float64(amount) * 0.10)
|
||||
creatorIncome := amount - fee
|
||||
|
||||
// Credit Tenant Owner Balance (Net Income)
|
||||
_, err = tx.User.WithContext(ctx).
|
||||
Where(tx.User.ID.Eq(tenantOwnerID)).
|
||||
Update(tx.User.Balance, gorm.Expr("balance + ?", creatorIncome))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
ledger := &models.TenantLedger{
|
||||
TenantID: o.TenantID,
|
||||
UserID: t.UserID, // Owner
|
||||
OrderID: o.ID,
|
||||
Type: consts.TenantLedgerTypeDebitPurchase, // Income from purchase
|
||||
Amount: creatorIncome,
|
||||
BalanceBefore: 0, // TODO
|
||||
BalanceAfter: 0, // TODO
|
||||
FrozenBefore: 0,
|
||||
FrozenAfter: 0,
|
||||
IdempotencyKey: uuid.NewString(),
|
||||
Remark: "内容销售收入 (扣除平台费)",
|
||||
OperatorUserID: o.UserID,
|
||||
}
|
||||
if err := tx.TenantLedger.WithContext(ctx).Create(ledger); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return err
|
||||
|
||||
Reference in New Issue
Block a user