Skip to content

feat: pay bolt11 Lightning invoices via /api/v1/send#66

Open
xbuddhi wants to merge 1 commit into
mainfrom
feat/api-invoice-payments
Open

feat: pay bolt11 Lightning invoices via /api/v1/send#66
xbuddhi wants to merge 1 commit into
mainfrom
feat/api-invoice-payments

Conversation

@xbuddhi

@xbuddhi xbuddhi commented Jul 20, 2026

Copy link
Copy Markdown

Summary

Adds external bolt11 Lightning invoice payments to the existing /api/v1/send endpoint. When the to field is a lnbc... payment request (optionally lightning:-prefixed), the bot pays it externally via lnbits instead of doing an internal transfer.

What changed

  • Invoice detection in Send()to matching a bolt11 invoice routes to the new sendToInvoice handler.
  • Amount from the invoice — amountless invoices are rejected (they must encode an amount).
  • Same guardrails as internal sends — min/max limits and per-wallet admin-approval threshold are enforced.
  • Idempotency — a SetNX lock + dedup keyed on the invoice payment hash prevents double-payment from concurrent/retried requests.
  • Fee handling — balance check reserves ~2% for routing fees; the response reports the real fee (read back from the settled payment, converted msat→sat).
  • Large invoices reuse the existing Telegram admin-approval flow — a pending transaction is created and an approve/pay button is sent; on approval the invoice is paid and /api/v1/send/status reflects the outcome.
  • Response fields — adds payment_hash and fee (sats) for invoice payments.

API contract

POST /api/v1/send

{ "from": "BiccoindeepaDSA", "to": "lnbc10u1p...", "memo": "optional" }
  • Success 200{ "success": true, "payment_hash": "...", "fee": 3, "amount": 1000, ... }
  • Requires approval 202{ "success": false, "message": "... Transaction ID: ..." }

Docs

  • New provider-facing guide: docs/invoice-payments-api.md
  • Updated docs/referral-api.md and README.md for invoice support.

Testing

  • go build ./... — clean
  • go vet ./internal/api/... — clean

Note: this is a separate feature from the /api/v1/usertransactions work on feat/api-user-transactions; that endpoint is not part of this PR.

Summary by CodeRabbit

  • New Features
    • Added support for sending payments to BOLT11 Lightning invoices.
    • Invoice payments can proceed immediately or through an approval workflow when required.
    • Invoice amounts are validated automatically, with routing-fee reserves and duplicate-payment protection.
    • Successful invoice payments now report the payment hash and routing fee.
    • Added provider integration documentation covering authentication, API usage, statuses, errors, limits, and implementation examples.
    • Updated API reference documentation with invoice request and response examples.

Allow the API send endpoint to pay an external bolt11 invoice when the
`to` field is a `lnbc...` payment request (optionally `lightning:`-prefixed).

- Decode the invoice and take the amount from it; reject amountless invoices
- Enforce the same min/max and admin-approval thresholds as internal sends
- Idempotency lock + dedup keyed on the invoice payment hash
- Balance check reserves ~2% for routing fees; response reports the real fee
- Large invoices reuse the existing Telegram admin-approval flow
- Response adds `payment_hash` and `fee` (sats) for invoice payments

Docs: add provider-facing docs/invoice-payments-api.md and update
referral-api.md / README for invoice support.
@coderabbitai

coderabbitai Bot commented Jul 20, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Bolt11 invoices can now be submitted to the Send API, validated and paid externally through LNBits, or routed through Telegram admin approval. Pending transactions and responses persist invoice-specific data, while documentation covers authentication, endpoints, constraints, and integration flows.

Changes

Bolt11 invoice payments

Layer / File(s) Summary
Invoice payment contracts and integration guide
README.md, docs/invoice-payments-api.md, docs/referral-api.md
Documents Bolt11 recipients, HMAC authentication, endpoint contracts, validation rules, approval polling, errors, and invoice payment responses.
Pending invoice transaction state
internal/api/pending_transaction.go
Adds invoice transaction fields and construction/loading behavior that avoids treating invoices as Telegram recipients.
Invoice detection and immediate payment
internal/api/send.go
Detects, decodes, validates, deduplicates, and pays Bolt11 invoices while returning the payment hash and routing fee.
Approval-based invoice execution
internal/telegram/api_approval.go
Adds invoice approval persistence, approval routing, LNBits execution, pending-status updates, and Telegram result messages.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Provider
  participant SendAPI
  participant InvoicePayment
  participant LNBits
  participant TelegramApproval
  Provider->>SendAPI: submit Bolt11 invoice
  SendAPI->>InvoicePayment: decode and validate invoice
  InvoicePayment->>LNBits: pay immediately or create approval
  LNBits-->>InvoicePayment: payment hash and routing fee
  InvoicePayment->>TelegramApproval: process approval when required
  TelegramApproval-->>Provider: return or publish payment status
Loading

Possibly related PRs

  • CeyLabs/BitcoinDeepaBot#16: Provides the existing pending-transaction and admin-approval flow extended here for invoice payments.
  • CeyLabs/BitcoinDeepaBot#19: Introduces the Telegram API approval structures and handlers extended with invoice-specific execution.

Suggested labels: Review effort 4/5, Possible security concern

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding bolt11 Lightning invoice payments through /api/v1/send.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/api-invoice-payments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@xbuddhi xbuddhi assigned cmnisal and unassigned cmnisal Jul 20, 2026
@xbuddhi
xbuddhi requested a review from cmnisal July 20, 2026 13:44

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (1)
docs/invoice-payments-api.md (1)

30-32: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Specify a language for fenced code blocks. Fenced code blocks without a language specifier trigger markdown lint warnings. Add text to resolve them.

  • docs/invoice-payments-api.md#L30-L32: change to ```text.
  • docs/invoice-payments-api.md#L41-L43: change to ```text.
  • docs/invoice-payments-api.md#L310-L328: change to ```text.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/invoice-payments-api.md` around lines 30 - 32, Specify the text language
for all three fenced code blocks in docs/invoice-payments-api.md: update the
fences at lines 30-32, 41-43, and 310-328 from untyped fences to text fences,
preserving their contents.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@internal/api/send.go`:
- Around line 538-543: Update the payment failure handling in the Invoice
payment flow to keep logging the detailed err internally, but pass a generic
payment-failed message to RespondError instead of formatting err into the API
response. Preserve the existing ErrorLogger.LogPaymentError call and response
status behavior.
- Around line 559-563: Update the sender confirmation message construction near
senderConfirmationMsg so the provided API memo is included in the Telegram
notification, alongside the existing invoice description when present. Reuse the
existing formatting and escaping conventions, while preserving the current
amount and send behavior.
- Around line 487-488: In the payment flow around GetUserAvailableBalance and
the subsequent invoice payment, acquire an exclusive per-user lock using the
existing MemoCache before checking balance, and hold it through the payment
execution. Ensure the lock is released on every success and error path so
concurrent requests for the same user are serialized while different users
remain independent.

In `@internal/telegram/api_approval.go`:
- Around line 245-292: Update CreateAPIInvoiceApprovalRequest to construct a
fresh local ReplyMarkup for each approval request instead of mutating the shared
apiApprovalConfirmationMenu. Build the approve and cancel buttons and their row
on this per-call markup, then pass it to bot.Telegram.Send so concurrent
requests retain their own callback data.

---

Nitpick comments:
In `@docs/invoice-payments-api.md`:
- Around line 30-32: Specify the text language for all three fenced code blocks
in docs/invoice-payments-api.md: update the fences at lines 30-32, 41-43, and
310-328 from untyped fences to text fences, preserving their contents.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: a679854e-370f-4d24-bcb9-55bda467d90b

📥 Commits

Reviewing files that changed from the base of the PR and between f26fb22 and 9cdc43c.

📒 Files selected for processing (6)
  • README.md
  • docs/invoice-payments-api.md
  • docs/referral-api.md
  • internal/api/pending_transaction.go
  • internal/api/send.go
  • internal/telegram/api_approval.go

Comment thread internal/api/send.go
Comment on lines +487 to +488
// Check available balance with a ~2% routing fee reserve
balance, err := s.Bot.GetUserAvailableBalance(fromUser)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Prevent concurrent payments from bypassing pot reserves.

Currently, concurrent API requests for the same user can all pass the availableBalance check because the LNBits Wallet.Pay backend does not enforce local "pot" limits. This TOCTOU (Time-of-Check to Time-of-Use) race condition allows a user to overdraw their available balance and inadvertently spend funds that were locked in pots.

Consider acquiring a temporary user-level lock (e.g., leveraging the existing MemoCache) before checking the balance and executing the invoice payment.

🔒️ Proposed fix to prevent concurrent balance bypass
 	// Check available balance with a ~2% routing fee reserve
+
+	// Prevent concurrent balance-draining API calls for the same user
+	userLockKey := fmt.Sprintf("api_send_user_%s", fromUsername)
+	if success := s.MemoCache.SetNX(userLockKey, "locked"); !success {
+		RespondError(w, "Another payment is currently processing. Please wait.")
+		return
+	}
+	defer s.MemoCache.Delete(userLockKey)
+
 	balance, err := s.Bot.GetUserAvailableBalance(fromUser)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/send.go` around lines 487 - 488, In the payment flow around
GetUserAvailableBalance and the subsequent invoice payment, acquire an exclusive
per-user lock using the existing MemoCache before checking balance, and hold it
through the payment execution. Ensure the lock is released on every success and
error path so concurrent requests for the same user are serialized while
different users remain independent.

Comment thread internal/api/send.go
Comment thread internal/api/send.go
Comment on lines +559 to +563
senderConfirmationMsg := fmt.Sprintf("✅ Invoice paid successfully!\n\n💸 Amount: %s", thirdparty.FormatSatsWithLKR(amount))
if bolt11.Description != "" {
senderConfirmationMsg += fmt.Sprintf("\n✉️ %s", str.MarkdownEscape(bolt11.Description))
}
if _, err := s.Bot.Telegram.Send(fromUser.Telegram, senderConfirmationMsg); err != nil {

@coderabbitai coderabbitai Bot Jul 20, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Include the memo in the sender's Telegram confirmation.

The API documentation states that the memo is used for "sender confirmation / logs". However, the Telegram confirmation message currently only includes the invoice description, omitting the provided API memo.

📝 Proposed fix to include the memo
 	// Send confirmation to sender
 	senderConfirmationMsg := fmt.Sprintf("✅ Invoice paid successfully!\n\n💸 Amount: %s", thirdparty.FormatSatsWithLKR(amount))
 	if bolt11.Description != "" {
-		senderConfirmationMsg += fmt.Sprintf("\n✉️ %s", str.MarkdownEscape(bolt11.Description))
+		senderConfirmationMsg += fmt.Sprintf("\n📄 Invoice: %s", str.MarkdownEscape(bolt11.Description))
 	}
+	if memo != "" {
+		senderConfirmationMsg += fmt.Sprintf("\n✉️ Memo: %s", str.MarkdownEscape(memo))
+	}
 	if _, err := s.Bot.Telegram.Send(fromUser.Telegram, senderConfirmationMsg); err != nil {
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
senderConfirmationMsg := fmt.Sprintf("✅ Invoice paid successfully!\n\n💸 Amount: %s", thirdparty.FormatSatsWithLKR(amount))
if bolt11.Description != "" {
senderConfirmationMsg += fmt.Sprintf("\n✉️ %s", str.MarkdownEscape(bolt11.Description))
}
if _, err := s.Bot.Telegram.Send(fromUser.Telegram, senderConfirmationMsg); err != nil {
senderConfirmationMsg := fmt.Sprintf("✅ Invoice paid successfully!\n\n💸 Amount: %s", thirdparty.FormatSatsWithLKR(amount))
if bolt11.Description != "" {
senderConfirmationMsg += fmt.Sprintf("\n📄 Invoice: %s", str.MarkdownEscape(bolt11.Description))
}
if memo != "" {
senderConfirmationMsg += fmt.Sprintf("\n✉️ Memo: %s", str.MarkdownEscape(memo))
}
if _, err := s.Bot.Telegram.Send(fromUser.Telegram, senderConfirmationMsg); err != nil {
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@internal/api/send.go` around lines 559 - 563, Update the sender confirmation
message construction near senderConfirmationMsg so the provided API memo is
included in the Telegram notification, alongside the existing invoice
description when present. Reuse the existing formatting and escaping
conventions, while preserving the current amount and send behavior.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is a good suggestion. Add memo?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@cmnisal, agreed. The sender confirmation should show both the invoice description (when present) and the caller-provided API memo.

  • Update internal/api/send.go in Service.sendToInvoice.
  • Keep the existing amount and invoice-description behavior.
  • Append a separately labeled, Markdown-escaped Memo: line when memo is non-empty.

🐇✨


Failed to handle agent chat message. Please try again.

Comment thread internal/telegram/api_approval.go
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants