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
This commit is contained in:
padreug 2025-10-23 03:07:47 +02:00
parent a2a58d323b
commit af424109f1
3 changed files with 36 additions and 8 deletions

View file

@ -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):

View file

@ -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
}
)

View file

@ -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,