fix: add optimistic lock retry for wallet topup and withdraw
This commit is contained in:
@@ -7,6 +7,7 @@ import (
|
|||||||
"context"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"juwan-backend/app/wallet/rpc/pb"
|
"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
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
currentBalance := decimal.Zero
|
|
||||||
frozenBalance := decimal.Zero
|
|
||||||
expectedVersion := int64(0)
|
|
||||||
if walletResp.GetWallets() == nil {
|
if walletResp.GetWallets() == nil {
|
||||||
zeroAmount := "0"
|
zeroAmount := "0"
|
||||||
_, err = l.svcCtx.WalletRpc.AddWallets(l.ctx, &pb.AddWalletsReq{
|
_, 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 {
|
if walletResp.GetWallets() == nil {
|
||||||
return nil, errors.New("wallet not found")
|
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")
|
return nil, errors.New("invalid wallet balance")
|
||||||
}
|
}
|
||||||
frozenBalance, err = decimal.NewFromString(walletResp.Wallets.FrozenBalance)
|
frozenBalance, perr := decimal.NewFromString(walletResp.Wallets.FrozenBalance)
|
||||||
if err != nil {
|
if perr != nil {
|
||||||
return nil, errors.New("invalid wallet frozen balance")
|
return nil, errors.New("invalid wallet frozen balance")
|
||||||
}
|
}
|
||||||
expectedVersion = walletResp.Wallets.Version
|
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
|
|
||||||
}
|
|
||||||
|
|
||||||
newBalance := currentBalance.Add(amount)
|
newBalance := currentBalance.Add(amount)
|
||||||
newBalanceStr := newBalance.String()
|
newBalanceStr := newBalance.String()
|
||||||
frozenBalanceStr := frozenBalance.String()
|
frozenBalanceStr := frozenBalance.String()
|
||||||
updatedAt := time.Now().Unix()
|
updatedAt := time.Now().Unix()
|
||||||
_, err = l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
|
_, uerr := l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
|
||||||
UserId: userID,
|
UserId: userID,
|
||||||
Balance: &newBalanceStr,
|
Balance: &newBalanceStr,
|
||||||
FrozenBalance: &frozenBalanceStr,
|
FrozenBalance: &frozenBalanceStr,
|
||||||
UpdatedAt: &updatedAt,
|
UpdatedAt: &updatedAt,
|
||||||
Version: &expectedVersion,
|
Version: &expectedVersion,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if uerr == nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
desc := fmt.Sprintf("topup via %s", req.Method)
|
desc := fmt.Sprintf("topup via %s", req.Method)
|
||||||
_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
|
_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
|
||||||
UserId: userID,
|
UserId: userID,
|
||||||
@@ -122,6 +110,16 @@ func (l *TopupLogic) Topup(req *types.TopupReq) (resp *types.EmptyResp, err erro
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.EmptyResp{}, nil
|
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"
|
"context"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"juwan-backend/app/wallet/rpc/pb"
|
"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")
|
return nil, errors.New("wallet not found")
|
||||||
}
|
}
|
||||||
|
|
||||||
currentBalance, err := decimal.NewFromString(walletResp.Wallets.Balance)
|
const maxRetries = 3
|
||||||
if err != nil {
|
for i := 0; i < maxRetries; i++ {
|
||||||
|
currentBalance, perr := decimal.NewFromString(walletResp.Wallets.Balance)
|
||||||
|
if perr != nil {
|
||||||
return nil, errors.New("invalid wallet balance")
|
return nil, errors.New("invalid wallet balance")
|
||||||
}
|
}
|
||||||
frozenBalance, err := decimal.NewFromString(walletResp.Wallets.FrozenBalance)
|
frozenBalance, perr := decimal.NewFromString(walletResp.Wallets.FrozenBalance)
|
||||||
if err != nil {
|
if perr != nil {
|
||||||
return nil, errors.New("invalid wallet frozen balance")
|
return nil, errors.New("invalid wallet frozen balance")
|
||||||
}
|
}
|
||||||
expectedVersion := walletResp.Wallets.Version
|
expectedVersion := walletResp.Wallets.Version
|
||||||
@@ -73,17 +76,14 @@ func (l *WithdrawLogic) Withdraw(req *types.TopupReq) (resp *types.EmptyResp, er
|
|||||||
newBalanceStr := newBalance.String()
|
newBalanceStr := newBalance.String()
|
||||||
frozenBalanceStr := frozenBalance.String()
|
frozenBalanceStr := frozenBalance.String()
|
||||||
updatedAt := time.Now().Unix()
|
updatedAt := time.Now().Unix()
|
||||||
_, err = l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
|
_, uerr := l.svcCtx.WalletRpc.UpdateWallets(l.ctx, &pb.UpdateWalletsReq{
|
||||||
UserId: userID,
|
UserId: userID,
|
||||||
Balance: &newBalanceStr,
|
Balance: &newBalanceStr,
|
||||||
FrozenBalance: &frozenBalanceStr,
|
FrozenBalance: &frozenBalanceStr,
|
||||||
UpdatedAt: &updatedAt,
|
UpdatedAt: &updatedAt,
|
||||||
Version: &expectedVersion,
|
Version: &expectedVersion,
|
||||||
})
|
})
|
||||||
if err != nil {
|
if uerr == nil {
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
|
|
||||||
desc := fmt.Sprintf("withdraw via %s", req.Method)
|
desc := fmt.Sprintf("withdraw via %s", req.Method)
|
||||||
_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
|
_, err = l.svcCtx.WalletRpc.AddWalletTransactions(l.ctx, &pb.AddWalletTransactionsReq{
|
||||||
UserId: userID,
|
UserId: userID,
|
||||||
@@ -96,6 +96,16 @@ func (l *WithdrawLogic) Withdraw(req *types.TopupReq) (resp *types.EmptyResp, er
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
return &types.EmptyResp{}, nil
|
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")
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user