fix(wallet): enforce consistent withdrawal accounting

This commit is contained in:
david
2026-07-31 09:54:17 +08:00
parent 36a5ced1c0
commit 42a2322c8e
23 changed files with 1405 additions and 250 deletions

View File

@@ -311,14 +311,34 @@ func reviewWalletApplyCash(ctx *gin.Context, targetStatus int) {
}
now := time.Now()
if targetStatus == common.StatusRejected {
result := tx.Model(&models.WalletBasic{}).
Where("id = ? AND withdrawal_balance <= ?", application.WalletBasicID, math.MaxInt64-application.Amount).
Update("withdrawal_balance", gorm.Expr("withdrawal_balance + ?", application.Amount))
if result.Error != nil {
return result.Error
wallet, err := common.LockWalletForUpdate(tx, application.WalletBasicID, false)
if err != nil {
return err
}
if result.RowsAffected != 1 {
return errors.New("withdrawal balance overflow")
released, err := common.ReleaseWithdrawalApplication(&wallet, application)
if err != nil {
return err
}
if released {
if err := common.SaveWalletBalances(tx, wallet); err != nil {
return err
}
record := common.NewWalletBalanceRecord(
wallet,
"withdrawal-reject:"+application.Identity,
"income",
"withdrawal_release",
application.Amount,
application.CashNo,
"",
application.Channel,
operatorIdentity,
operatorName,
request.Reason,
)
if err := tx.Create(&record).Error; err != nil {
return err
}
}
}
return tx.Model(&application).Updates(map[string]any{
@@ -359,10 +379,43 @@ func CompleteWalletApplyCash(ctx *gin.Context) {
if application.ApplyStatus != common.StatusApproved {
return errors.New("cash application is not approved")
}
if !application.BalanceReserved {
wallet, err := common.LockWalletForUpdate(tx, application.WalletBasicID, true)
if err != nil {
return err
}
changed, err := common.SettleLegacyWithdrawal(&wallet, application)
if err != nil {
return err
}
if !changed {
return errors.New("legacy withdrawal settlement did not change wallet")
}
if err := common.SaveWalletBalances(tx, wallet); err != nil {
return err
}
operatorIdentity, operatorName := common.PlatformOperator(ctx)
record := common.NewWalletBalanceRecord(
wallet,
"withdrawal-complete:"+application.Identity,
"expense",
"withdrawal_complete",
application.Amount,
"",
request.TradeNo,
application.Channel,
operatorIdentity,
operatorName,
"历史提现申请完成时补记总余额",
)
if err := tx.Create(&record).Error; err != nil {
return err
}
}
now := time.Now()
return tx.Model(&application).Updates(map[string]any{
"apply_status": common.StatusCompleted, "trade_no": strings.TrimSpace(request.TradeNo),
"callback_msg": request.CallbackMsg, "completed_at": &now,
"callback_msg": request.CallbackMsg, "completed_at": &now, "balance_reserved": true,
}).Error
})
if err != nil {