Adds balance payment feature

Implements a feature that allows users to pay their outstanding balance via Lightning.

The changes include:
- Adds the UI elements for invoice generation and display, including QR code.
- Integrates backend endpoints to generate and record payments.
- Adds polling mechanism to track payments and update balance.
- Creates new database models to support manual payment requests.
This commit is contained in:
padreug 2025-10-22 16:46:46 +02:00
parent eb9a3c1600
commit ef3e2d9e0d
4 changed files with 232 additions and 49 deletions

View file

@ -444,20 +444,66 @@ async def api_get_all_balances(
# ===== PAYMENT ENDPOINTS =====
@castle_api_router.post("/api/v1/pay-balance")
async def api_pay_balance(
@castle_api_router.post("/api/v1/generate-payment-invoice")
async def api_generate_payment_invoice(
amount: int,
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> dict:
"""
Record a payment from user to castle (reduces what user owes or what castle owes user).
This should be called after an invoice is paid.
Generate an invoice on the Castle wallet for user to pay their balance.
User can then pay this invoice to settle their debt.
"""
wallet_id = wallet.wallet.id
from lnbits.core.models import CreateInvoice
from lnbits.core.services import create_payment_request
# Get castle wallet ID
castle_wallet_id = await check_castle_wallet_configured()
# Create invoice on castle wallet
invoice_data = CreateInvoice(
out=False,
amount=amount,
memo=f"Payment from user {wallet.wallet.user[:8]} to Castle",
unit="sat",
extra={"user_id": wallet.wallet.user, "type": "castle_payment"},
)
payment = await create_payment_request(castle_wallet_id, invoice_data)
return {
"payment_hash": payment.payment_hash,
"payment_request": payment.bolt11,
"amount": amount,
"memo": invoice_data.memo,
}
@castle_api_router.post("/api/v1/record-payment")
async def api_record_payment(
payment_hash: str,
wallet: WalletTypeInfo = Depends(require_invoice_key),
) -> dict:
"""
Record a lightning payment in accounting after invoice is paid.
This reduces what the user owes to the castle.
"""
from lnbits.core.crud.payments import get_standalone_payment
# Get the payment details
payment = await get_standalone_payment(payment_hash)
if not payment:
raise HTTPException(
status_code=HTTPStatus.NOT_FOUND, detail="Payment not found"
)
if not payment.paid:
raise HTTPException(
status_code=HTTPStatus.BAD_REQUEST, detail="Payment not yet paid"
)
# Get user's receivable account (what user owes)
user_receivable = await get_or_create_user_account(
wallet_id, AccountType.ASSET, "Accounts Receivable"
wallet.wallet.user, AccountType.ASSET, "Accounts Receivable"
)
# Get lightning account
@ -467,33 +513,35 @@ async def api_pay_balance(
status_code=HTTPStatus.NOT_FOUND, detail="Lightning account not found"
)
# Create journal entry
# Create journal entry to record payment
# DR Lightning Balance, CR Accounts Receivable (User)
# This reduces what the user owes
entry_data = CreateJournalEntry(
description=f"Payment received from user {wallet_id[:8]}",
description=f"Lightning payment from user {wallet.wallet.user[:8]}",
reference=payment_hash,
lines=[
CreateEntryLine(
account_id=lightning_account.id,
debit=amount,
debit=payment.amount,
credit=0,
description="Lightning payment received",
),
CreateEntryLine(
account_id=user_receivable.id,
debit=0,
credit=amount,
credit=payment.amount,
description="Payment applied to balance",
),
],
)
entry = await create_journal_entry(entry_data, wallet_id)
entry = await create_journal_entry(entry_data, wallet.wallet.user)
# Get updated balance
balance = await get_user_balance(wallet_id)
balance = await get_user_balance(wallet.wallet.user)
return {
"journal_entry": entry.dict(),
"journal_entry_id": entry.id,
"new_balance": balance.balance,
"message": "Payment recorded successfully",
}