Skip to content

Commit 47a2f99

Browse files
committed
fix: use available balance for debits
1 parent d7679a2 commit 47a2f99

6 files changed

Lines changed: 70 additions & 71 deletions

File tree

internal/api/send.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -161,16 +161,16 @@ func (s Service) Send(w http.ResponseWriter, r *http.Request) {
161161
return
162162
}
163163

164-
// Check sender's balance
165-
balance, err := s.Bot.GetUserBalance(fromUser)
164+
// Check sender's available balance (wallet balance - pot balance)
165+
balance, err := s.Bot.GetUserAvailableBalance(fromUser)
166166
if err != nil {
167-
log.Errorf("[api/send] Could not get balance for %s: %v", fromUsername, err)
167+
log.Errorf("[api/send] Could not get available balance for %s: %v", fromUsername, err)
168168
RespondError(w, "Could not check sender balance")
169169
return
170170
}
171171

172172
if balance < req.Amount {
173-
log.Warnf("[api/send] Insufficient balance for %s: %d < %d", fromUsername, balance, req.Amount)
173+
log.Warnf("[api/send] Insufficient available balance for %s: %d < %d", fromUsername, balance, req.Amount)
174174
RespondError(w, fmt.Sprintf("Insufficient balance: %s available, %s required", thirdparty.FormatSatsWithLKR(balance), thirdparty.FormatSatsWithLKR(req.Amount)))
175175
return
176176
}

internal/telegram/balance.go

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,10 +36,14 @@ func (bot *TipBot) balanceHandler(ctx intercept.Context) (intercept.Context, err
3636
}
3737

3838
usrStr := GetUserStr(ctx.Sender())
39-
// Use database balance (in msat) to reflect pot transfers
40-
balance := user.Wallet.Balance / 1000
39+
// Get available balance (wallet balance - pot balance)
40+
availableBalance, err := bot.GetUserAvailableBalance(user)
41+
if err != nil {
42+
log.Errorf("[/balance] Error fetching %s's available balance: %s", usrStr, err)
43+
availableBalance = 0
44+
}
4145

42-
log.Infof("[/balance] %s's balance: %s\n", usrStr, utils.FormatSats(balance))
46+
log.Infof("[/balance] %s's available balance: %s\n", usrStr, utils.FormatSats(availableBalance))
4347

4448
LKRPerSat, USDPerSat, err := thirdparty.GetSatPrice()
4549
if err != nil {
@@ -52,16 +56,16 @@ func (bot *TipBot) balanceHandler(ctx intercept.Context) (intercept.Context, err
5256
potBalance = 0
5357
}
5458

55-
totalBalance := balance + potBalance
56-
mainUSDValue := USDPerSat * float64(balance)
57-
mainLKRValue := LKRPerSat * float64(balance)
59+
totalBalance := availableBalance + potBalance
60+
mainUSDValue := USDPerSat * float64(availableBalance)
61+
mainLKRValue := LKRPerSat * float64(availableBalance)
5862
totalUSDValue := USDPerSat * float64(totalBalance)
5963
totalLKRValue := LKRPerSat * float64(totalBalance)
6064
potUSDValue := USDPerSat * float64(potBalance)
6165
potLKRValue := LKRPerSat * float64(potBalance)
6266

6367
message := fmt.Sprintf(Translate(ctx, "balanceMessage"),
64-
utils.FormatSats(balance),
68+
utils.FormatSats(availableBalance),
6569
utils.FormatFloatWithCommas(mainUSDValue),
6670
utils.FormatFloatWithCommas(mainLKRValue))
6771

internal/telegram/inline_send.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -71,15 +71,15 @@ func (bot TipBot) handleInlineSendQuery(ctx intercept.Context) (intercept.Contex
7171
}
7272
fromUser := LoadUser(ctx)
7373
fromUserStr := GetUserStr(q.Sender)
74-
balance, err := bot.GetUserBalanceCached(fromUser)
74+
balance, err := bot.GetUserAvailableBalance(fromUser)
7575
if err != nil {
76-
errmsg := fmt.Sprintf("could not get balance of user %s", fromUserStr)
76+
errmsg := fmt.Sprintf("could not get available balance of user %s", fromUserStr)
7777
log.Errorln(errmsg)
7878
return ctx, err
7979
}
80-
// check if fromUser has balance
80+
// check if fromUser has sufficient available balance
8181
if balance < amount {
82-
log.Errorf("Balance of user %s too low", fromUserStr)
82+
log.Errorf("Available balance of user %s too low", fromUserStr)
8383
bot.inlineQueryReplyWithError(ctx, TranslateUser(ctx, "inlineSendBalanceLowMessage"), fmt.Sprintf(TranslateUser(ctx, "inlineQuerySendDescription"), bot.Telegram.Me.Username))
8484
return ctx, errors.Create(errors.InvalidAmountError)
8585
}

internal/telegram/pots.go

Lines changed: 23 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -95,18 +95,17 @@ func (bot *TipBot) TransferToPot(user *lnbits.User, potName string, amount int64
9595
return fmt.Errorf("amount must be positive")
9696
}
9797

98-
return bot.DB.Users.Transaction(func(tx *gorm.DB) error {
99-
// Get current user balance (within transaction)
100-
var userWallet lnbits.User
101-
if err := tx.Where("id = ?", user.ID).First(&userWallet).Error; err != nil {
102-
return fmt.Errorf("failed to get user: %w", err)
103-
}
98+
// Check sender's available balance (excluding money already in pots)
99+
balance, err := bot.GetUserAvailableBalance(user)
100+
if err != nil {
101+
return fmt.Errorf("could not get user available balance: %w", err)
102+
}
104103

105-
balance := userWallet.Wallet.Balance
106-
// Check if sufficient funds
107-
if balance < amount {
108-
return fmt.Errorf("insufficient balance. Available: %d sats, Requested: %d sats", balance, amount)
109-
}
104+
if balance < amount {
105+
return fmt.Errorf("insufficient available balance. Available: %d sats, Requested: %d sats", balance, amount)
106+
}
107+
108+
return bot.DB.Users.Transaction(func(tx *gorm.DB) error {
110109

111110
// Verify the pot exists
112111
var pot lnbits.SavingsPot
@@ -117,19 +116,8 @@ func (bot *TipBot) TransferToPot(user *lnbits.User, potName string, amount int64
117116
return err
118117
}
119118

120-
// Atomically deduct from user balance
121-
result := tx.Model(&lnbits.User{}).Where("id = ? AND wallet_balance >= ?", user.ID, amount).
122-
UpdateColumn("wallet_balance", gorm.Expr("wallet_balance - ?", amount))
123-
if result.Error != nil {
124-
return fmt.Errorf("failed to update user balance: %w", result.Error)
125-
}
126-
if result.RowsAffected == 0 {
127-
return fmt.Errorf("insufficient balance or user not found")
128-
}
129-
130-
// Atomically add to pot balance
131-
if err := tx.Model(&lnbits.SavingsPot{}).Where("user_id = ? AND name = ?", user.ID, strings.TrimSpace(potName)).
132-
UpdateColumn("balance", gorm.Expr("balance + ?", amount)).Error; err != nil {
119+
// Atomically add to pot balance (no need to update wallet_balance as it's handled by LNbits)
120+
if err := tx.Model(&lnbits.SavingsPot{}).Where("user_id = ? AND name = ?", user.ID, strings.TrimSpace(potName)).UpdateColumn("balance", gorm.Expr("balance + ?", amount)).Error; err != nil {
133121
return fmt.Errorf("failed to update pot balance: %w", err)
134122
}
135123

@@ -145,21 +133,19 @@ func (bot *TipBot) WithdrawFromPot(user *lnbits.User, potName string, amount int
145133
return fmt.Errorf("amount must be positive")
146134
}
147135

148-
return bot.DB.Users.Transaction(func(tx *gorm.DB) error {
149-
// Verify the pot exists and has sufficient balance
150-
var pot lnbits.SavingsPot
151-
if err := tx.Where("user_id = ? AND name = ?", user.ID, strings.TrimSpace(potName)).First(&pot).Error; err != nil {
152-
if err == gorm.ErrRecordNotFound {
153-
return fmt.Errorf("pot '%s' not found", potName)
154-
}
155-
return err
156-
}
136+
// Pre-check that the pot exists and has sufficient balance
137+
pot, err := bot.GetPot(user, potName)
138+
if err != nil {
139+
return err
140+
}
157141

158-
if pot.Balance < amount {
159-
return fmt.Errorf("insufficient pot balance. Available: %d sats, Requested: %d sats", pot.Balance, amount)
160-
}
142+
if pot.Balance < amount {
143+
return fmt.Errorf("insufficient pot balance. Available: %d sats, Requested: %d sats", pot.Balance, amount)
144+
}
161145

162-
// Atomically deduct from pot balance
146+
return bot.DB.Users.Transaction(func(tx *gorm.DB) error {
147+
148+
// Atomically deduct from pot balance (no need to update wallet_balance as it's handled by LNbits)
163149
result := tx.Model(&lnbits.SavingsPot{}).Where("user_id = ? AND name = ? AND balance >= ?", user.ID, strings.TrimSpace(potName), amount).
164150
UpdateColumn("balance", gorm.Expr("balance - ?", amount))
165151
if result.Error != nil {
@@ -169,19 +155,6 @@ func (bot *TipBot) WithdrawFromPot(user *lnbits.User, potName string, amount int
169155
return fmt.Errorf("insufficient pot balance or pot not found")
170156
}
171157

172-
// Atomically add to user wallet balance
173-
result = tx.Model(&lnbits.User{}).Where("id = ?", user.ID).
174-
UpdateColumn("wallet_balance", gorm.Expr("wallet_balance + ?", amount))
175-
if result.Error != nil {
176-
return fmt.Errorf("failed to update user balance: %w", result.Error)
177-
}
178-
if result.RowsAffected == 0 {
179-
return fmt.Errorf("user not found")
180-
}
181-
182-
// Update in-memory user balance
183-
user.Wallet.Balance += amount
184-
185158
return nil
186159
})
187160
}

internal/telegram/transaction.go

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -91,17 +91,17 @@ func (t *Transaction) SendTransaction(bot *TipBot, from *lnbits.User, to *lnbits
9191
t.FromWallet = from.Wallet.ID
9292
t.FromLNbitsID = from.ID
9393

94-
// check if fromUser has balance
95-
balance, err := bot.GetUserBalance(from)
94+
// check if fromUser has available balance (wallet balance - pot balance)
95+
balance, err := bot.GetUserAvailableBalance(from)
9696
if err != nil {
97-
errmsg := fmt.Sprintf("could not get balance of user %s", fromUserStr)
97+
errmsg := fmt.Sprintf("could not get available balance of user %s", fromUserStr)
9898
log.Errorln(errmsg)
9999
return false, err
100100
}
101-
// check if fromUser has balance
101+
// check if fromUser has sufficient available balance
102102
if balance < amount {
103-
errmsg := fmt.Sprintf("balance too low.")
104-
log.Warnf("Balance of user %s too low", fromUserStr)
103+
errmsg := fmt.Sprintf("available balance too low.")
104+
log.Warnf("Available balance of user %s too low", fromUserStr)
105105
return false, fmt.Errorf(errmsg)
106106
}
107107

internal/telegram/users.go

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,28 @@ func (bot *TipBot) GetUserBalance(user *lnbits.User) (amount int64, err error) {
9999
return
100100
}
101101

102+
func (bot *TipBot) GetUserAvailableBalance(user *lnbits.User) (amount int64, err error) {
103+
walletBalance, err := bot.GetUserBalance(user)
104+
if err != nil {
105+
return 0, err
106+
}
107+
108+
potBalance, err := bot.GetUserTotalPotBalance(user)
109+
if err != nil {
110+
return 0, fmt.Errorf("could not get pot balance: %w", err)
111+
}
112+
113+
availableBalance := walletBalance - potBalance
114+
if availableBalance < 0 {
115+
availableBalance = 0
116+
}
117+
118+
log.Debugf("[GetUserAvailableBalance] %s's available balance: %d sat (wallet: %d, pots: %d)\n",
119+
GetUserStr(user.Telegram), availableBalance, walletBalance, potBalance)
120+
121+
return availableBalance, nil
122+
}
123+
102124
func (bot *TipBot) CreateWalletForTelegramUser(tbUser *tb.User) (*lnbits.User, error) {
103125
// failsafe: do not create wallet for existing user
104126
if _, exists := bot.UserExists(tbUser); exists {

0 commit comments

Comments
 (0)