From af424109f1c5c4e5c87150ac671cef3557ccd5a3 Mon Sep 17 00:00:00 2001 From: padreug Date: Thu, 23 Oct 2025 03:07:47 +0200 Subject: [PATCH] Enables admin to generate invoices for users Allows administrators to generate payment invoices on behalf of specific users. This is useful for handling settlement invoices in certain scenarios. The changes include: - Adding a `user_id` field to the generate payment invoice request model - Updating the API endpoint to accept the `user_id` parameter - Implementing checks to ensure only superusers can generate invoices for other users - Updating the memo and extra data to reflect the target user --- models.py | 1 + static/js/index.js | 3 ++- views_api.py | 40 +++++++++++++++++++++++++++++++++------- 3 files changed, 36 insertions(+), 8 deletions(-) diff --git a/models.py b/models.py index 391b8ac..abd508f 100644 --- a/models.py +++ b/models.py @@ -173,6 +173,7 @@ class GeneratePaymentInvoice(BaseModel): """Generate payment invoice request""" amount: int + user_id: Optional[str] = None # For admin-generated settlement invoices class RecordPayment(BaseModel): diff --git a/static/js/index.js b/static/js/index.js index c13c628..ff91f69 100644 --- a/static/js/index.js +++ b/static/js/index.js @@ -915,7 +915,8 @@ window.app = Vue.createApp({ '/castle/api/v1/generate-payment-invoice', this.g.user.wallets[0].adminkey, { - amount: this.settleReceivableDialog.amount + amount: this.settleReceivableDialog.amount, + user_id: this.settleReceivableDialog.user_id // Specify which user this invoice is for } ) diff --git a/views_api.py b/views_api.py index e54478b..b134e29 100644 --- a/views_api.py +++ b/views_api.py @@ -559,10 +559,26 @@ async def api_generate_payment_invoice( """ Generate an invoice on the Castle wallet for user to pay their balance. User can then pay this invoice to settle their debt. + + If user_id is provided (admin only), the invoice is generated for that specific user. """ from lnbits.core.crud.wallets import get_wallet from lnbits.core.models import CreateInvoice from lnbits.core.services import create_payment_request + from lnbits.settings import settings as lnbits_settings + + # Determine which user this invoice is for + if data.user_id: + # Admin generating invoice for a specific user + if wallet.wallet.user != lnbits_settings.super_user: + raise HTTPException( + status_code=HTTPStatus.FORBIDDEN, + detail="Only super user can generate invoices for other users", + ) + target_user_id = data.user_id + else: + # User generating invoice for themselves + target_user_id = wallet.wallet.user # Get castle wallet ID castle_wallet_id = await check_castle_wallet_configured() @@ -571,9 +587,9 @@ async def api_generate_payment_invoice( invoice_data = CreateInvoice( out=False, amount=data.amount, - memo=f"Payment from user {wallet.wallet.user[:8]} to Castle", + memo=f"Payment from user {target_user_id[:8]} to Castle", unit="sat", - extra={"user_id": wallet.wallet.user, "type": "castle_payment"}, + extra={"user_id": target_user_id, "type": "castle_payment"}, ) payment = await create_payment_request(castle_wallet_id, invoice_data) @@ -602,6 +618,8 @@ async def api_record_payment( """ Record a lightning payment in accounting after invoice is paid. This reduces what the user owes to the castle. + + The user_id is extracted from the payment metadata (set during invoice generation). """ from lnbits.core.crud.payments import get_standalone_payment @@ -617,9 +635,17 @@ async def api_record_payment( status_code=HTTPStatus.BAD_REQUEST, detail="Payment not yet paid" ) + # Get user_id from payment metadata (set during invoice generation) + # This allows admins to record payments on behalf of users + target_user_id = wallet.wallet.user # Default to wallet user + if payment.extra and isinstance(payment.extra, dict): + metadata_user_id = payment.extra.get("user_id") + if metadata_user_id: + target_user_id = metadata_user_id + # Get user's receivable account (what user owes) user_receivable = await get_or_create_user_account( - wallet.wallet.user, AccountType.ASSET, "Accounts Receivable" + target_user_id, AccountType.ASSET, "Accounts Receivable" ) # Get lightning account @@ -638,11 +664,11 @@ async def api_record_payment( "source": "lightning_payment", "created_via": "record_payment", "payment_hash": data.payment_hash, - "payer_user_id": wallet.wallet.user, + "payer_user_id": target_user_id, } entry_data = CreateJournalEntry( - description=f"Lightning payment from user {wallet.wallet.user[:8]}", + description=f"Lightning payment from user {target_user_id[:8]}", reference=data.payment_hash, flag=JournalEntryFlag.CLEARED, # Payment is immediately cleared meta=entry_meta, @@ -662,10 +688,10 @@ async def api_record_payment( ], ) - entry = await create_journal_entry(entry_data, wallet.wallet.user) + entry = await create_journal_entry(entry_data, target_user_id) # Get updated balance - balance = await get_user_balance(wallet.wallet.user) + balance = await get_user_balance(target_user_id) return { "journal_entry_id": entry.id,