fix: add optimistic lock retry for wallet topup and withdraw

This commit is contained in:
zetaloop
2026-04-22 20:29:35 +08:00
parent 17daff03bd
commit 59256897e9
2 changed files with 93 additions and 85 deletions
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"
"juwan-backend/app/wallet/rpc/pb"
@@ -53,9 +54,6 @@ func (l *TopupLogic) Topup(req *types.TopupReq) (resp *types.EmptyResp, err erro
return nil, err
}
currentBalance := decimal.Zero
frozenBalance := decimal.Zero
expectedVersion := int64(0)
if walletResp.GetWallets() == nil {
zeroAmount := "0"
_, err = l.svcCtx.WalletRpc.AddWallets(l.ctx, &pb.AddWalletsReq{
@@ -74,42 +72,32 @@ func (l *TopupLogic) Topup(req *types.TopupReq) (resp *types.EmptyResp, err erro
if walletResp.GetWallets() == nil {
return nil, errors.New("wallet not found")
}
currentBalance, err = decimal.NewFromString(walletResp.Wallets.Balance)
if err != nil {
}
const maxRetries = 3
for i := 0; i < maxRetries; i++ {
currentBalance, perr := decimal.NewFromString(walletResp.Wallets.Balance)
if perr != nil {
return nil, errors.New("invalid wallet balance")
}
frozenBalance, err = decimal.NewFromString(walletResp.Wallets.FrozenBalance)
if err != nil {
frozenBalance, perr := decimal.NewFromString(walletResp.Wallets.FrozenBalance)
if perr != nil {
return nil, errors.New("invalid wallet frozen balance")
}
expectedVersion = walletResp.Wallets.Version
} else {
currentBalance, err = decimal.NewFromString(walletResp.Wallets.Balance)
if err != nil {
return nil, errors.New("invalid wallet balance")
}
frozenBalance, err = decimal.NewFromString(walletResp.Wallets.FrozenBalance)
if err != nil {
return nil, errors.New("invalid wallet frozen balance")
}
expectedVersion = walletResp.Wallets.Version
}
expectedVersion := walletResp.Wallets.Version
newBalance := currentBalance.Add(amount)
newBalanceStr := newBalance.String()
frozenBalanceStr := frozenBalance.String()
updatedAt := time.Now().Unix()
_, err = l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
_, uerr := l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
UserId: userID,
Balance: &newBalanceStr,
FrozenBalance: &frozenBalanceStr,
UpdatedAt: &updatedAt,
Version: &expectedVersion,
})
if err != nil {
return nil, err
}
if uerr == nil {
desc := fmt.Sprintf("topup via %s", req.Method)
_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
UserId: userID,
@@ -122,6 +110,16 @@ func (l *TopupLogic) Topup(req *types.TopupReq) (resp *types.EmptyResp, err erro
if err != nil {
return nil, err
}
return &types.EmptyResp{}, nil
}
if !strings.Contains(uerr.Error(), "conflict") {
return nil, uerr
}
walletResp, err = l.svcCtx.WalletRpc.GetWalletsById(l.ctx, &pb.GetWalletsByIdReq{Id: userID})
if err != nil {
return nil, err
}
}
return nil, errors.New("wallet update conflict, please retry")
}
@@ -7,6 +7,7 @@ import (
"context"
"errors"
"fmt"
"strings"
"time"
"juwan-backend/app/wallet/rpc/pb"
@@ -56,12 +57,14 @@ func (l *WithdrawLogic) Withdraw(req *types.TopupReq) (resp *types.EmptyResp, er
return nil, errors.New("wallet not found")
}
currentBalance, err := decimal.NewFromString(walletResp.Wallets.Balance)
if err != nil {
const maxRetries = 3
for i := 0; i < maxRetries; i++ {
currentBalance, perr := decimal.NewFromString(walletResp.Wallets.Balance)
if perr != nil {
return nil, errors.New("invalid wallet balance")
}
frozenBalance, err := decimal.NewFromString(walletResp.Wallets.FrozenBalance)
if err != nil {
frozenBalance, perr := decimal.NewFromString(walletResp.Wallets.FrozenBalance)
if perr != nil {
return nil, errors.New("invalid wallet frozen balance")
}
expectedVersion := walletResp.Wallets.Version
@@ -73,17 +76,14 @@ func (l *WithdrawLogic) Withdraw(req *types.TopupReq) (resp *types.EmptyResp, er
newBalanceStr := newBalance.String()
frozenBalanceStr := frozenBalance.String()
updatedAt := time.Now().Unix()
_, err = l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
_, uerr := l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
UserId: userID,
Balance: &newBalanceStr,
FrozenBalance: &frozenBalanceStr,
UpdatedAt: &updatedAt,
Version: &expectedVersion,
})
if err != nil {
return nil, err
}
if uerr == nil {
desc := fmt.Sprintf("withdraw via %s", req.Method)
_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
UserId: userID,
@@ -96,6 +96,16 @@ func (l *WithdrawLogic) Withdraw(req *types.TopupReq) (resp *types.EmptyResp, er
if err != nil {
return nil, err
}
return &types.EmptyResp{}, nil
}
if !strings.Contains(uerr.Error(), "conflict") {
return nil, uerr
}
walletResp, err = l.svcCtx.WalletRpc.GetWalletsById(l.ctx, &pb.GetWalletsByIdReq{Id: userID})
if err != nil {
return nil, err
}
}
return nil, errors.New("wallet update conflict, please retry")
}