fix: add optimistic lock retry for wallet topup and withdraw
This commit is contained in:
@@ -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,54 +72,54 @@ 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")
|
||||
expectedVersion := walletResp.Wallets.Version
|
||||
|
||||
newBalance := currentBalance.Add(amount)
|
||||
newBalanceStr := newBalance.String()
|
||||
frozenBalanceStr := frozenBalance.String()
|
||||
updatedAt := time.Now().Unix()
|
||||
_, uerr := l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
|
||||
UserId: userID,
|
||||
Balance: &newBalanceStr,
|
||||
FrozenBalance: &frozenBalanceStr,
|
||||
UpdatedAt: &updatedAt,
|
||||
Version: &expectedVersion,
|
||||
})
|
||||
if uerr == nil {
|
||||
desc := fmt.Sprintf("topup via %s", req.Method)
|
||||
_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
|
||||
UserId: userID,
|
||||
Type: "topup",
|
||||
Amount: amount.String(),
|
||||
BalanceAfter: newBalanceStr,
|
||||
Description: desc,
|
||||
CreatedAt: updatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &types.EmptyResp{}, nil
|
||||
}
|
||||
frozenBalance, err = decimal.NewFromString(walletResp.Wallets.FrozenBalance)
|
||||
if err != nil {
|
||||
return nil, errors.New("invalid wallet frozen balance")
|
||||
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
|
||||
}
|
||||
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{
|
||||
UserId: userID,
|
||||
Balance: &newBalanceStr,
|
||||
FrozenBalance: &frozenBalanceStr,
|
||||
UpdatedAt: &updatedAt,
|
||||
Version: &expectedVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
desc := fmt.Sprintf("topup via %s", req.Method)
|
||||
_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
|
||||
UserId: userID,
|
||||
Type: "topup",
|
||||
Amount: amount.String(),
|
||||
BalanceAfter: newBalanceStr,
|
||||
Description: desc,
|
||||
CreatedAt: updatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.EmptyResp{}, nil
|
||||
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,46 +57,55 @@ 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 {
|
||||
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
|
||||
if currentBalance.LessThan(amount) {
|
||||
return nil, errors.New("insufficient balance")
|
||||
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, perr := decimal.NewFromString(walletResp.Wallets.FrozenBalance)
|
||||
if perr != nil {
|
||||
return nil, errors.New("invalid wallet frozen balance")
|
||||
}
|
||||
expectedVersion := walletResp.Wallets.Version
|
||||
if currentBalance.LessThan(amount) {
|
||||
return nil, errors.New("insufficient balance")
|
||||
}
|
||||
|
||||
newBalance := currentBalance.Sub(amount)
|
||||
newBalanceStr := newBalance.String()
|
||||
frozenBalanceStr := frozenBalance.String()
|
||||
updatedAt := time.Now().Unix()
|
||||
_, uerr := l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
|
||||
UserId: userID,
|
||||
Balance: &newBalanceStr,
|
||||
FrozenBalance: &frozenBalanceStr,
|
||||
UpdatedAt: &updatedAt,
|
||||
Version: &expectedVersion,
|
||||
})
|
||||
if uerr == nil {
|
||||
desc := fmt.Sprintf("withdraw via %s", req.Method)
|
||||
_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
|
||||
UserId: userID,
|
||||
Type: "withdrawal",
|
||||
Amount: amount.String(),
|
||||
BalanceAfter: newBalanceStr,
|
||||
Description: desc,
|
||||
CreatedAt: updatedAt,
|
||||
})
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
newBalance := currentBalance.Sub(amount)
|
||||
newBalanceStr := newBalance.String()
|
||||
frozenBalanceStr := frozenBalance.String()
|
||||
updatedAt := time.Now().Unix()
|
||||
_, err = l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
|
||||
UserId: userID,
|
||||
Balance: &newBalanceStr,
|
||||
FrozenBalance: &frozenBalanceStr,
|
||||
UpdatedAt: &updatedAt,
|
||||
Version: &expectedVersion,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
desc := fmt.Sprintf("withdraw via %s", req.Method)
|
||||
_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
|
||||
UserId: userID,
|
||||
Type: "withdrawal",
|
||||
Amount: amount.String(),
|
||||
BalanceAfter: newBalanceStr,
|
||||
Description: desc,
|
||||
CreatedAt: updatedAt,
|
||||
})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &types.EmptyResp{}, nil
|
||||
return nil, errors.New("wallet update conflict, please retry")
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user