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,