From 3add13075c95a0855d42d6c66c1002ae972f8f8c Mon Sep 17 00:00:00 2001 From: padreug Date: Sun, 2 Nov 2025 02:52:41 +0100 Subject: [PATCH] Adds fiat currency metadata to payments Adds fiat currency information to payment invoices and ledger entries. This allows for tracking the fiat value of transactions and provides a more complete financial picture. Calculates the fiat amount proportionally based on the user's balance and includes the fiat currency, amount, and exchange rates in the invoice's extra data. This data is then extracted and added to the ledger entry's metadata when recording the payment. --- tasks.py | 19 +++++++++++++++++ views_api.py | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 77 insertions(+), 1 deletion(-) diff --git a/tasks.py b/tasks.py index d0a31a9..32333e1 100644 --- a/tasks.py +++ b/tasks.py @@ -161,6 +161,23 @@ async def on_invoice_paid(payment: Payment) -> None: # Convert amount from millisatoshis to satoshis amount_sats = payment.amount // 1000 + # Extract fiat metadata from invoice (if present) + from decimal import Decimal + line_metadata = {} + if payment.extra: + fiat_currency = payment.extra.get("fiat_currency") + fiat_amount = payment.extra.get("fiat_amount") + fiat_rate = payment.extra.get("fiat_rate") + btc_rate = payment.extra.get("btc_rate") + + if fiat_currency and fiat_amount: + line_metadata = { + "fiat_currency": fiat_currency, + "fiat_amount": str(fiat_amount), + "fiat_rate": fiat_rate, + "btc_rate": btc_rate, + } + # Get user's receivable account (what user owes) user_receivable = await get_or_create_user_account( user_id, AccountType.ASSET, "Accounts Receivable" @@ -193,12 +210,14 @@ async def on_invoice_paid(payment: Payment) -> None: debit=amount_sats, credit=0, description="Lightning payment received", + metadata=line_metadata, ), CreateEntryLine( account_id=user_receivable.id, debit=0, credit=amount_sats, description="Payment applied to balance", + metadata=line_metadata, ), ], ) diff --git a/views_api.py b/views_api.py index 3753f85..f8bc5eb 100644 --- a/views_api.py +++ b/views_api.py @@ -585,13 +585,52 @@ async def api_generate_payment_invoice( # Get castle wallet ID castle_wallet_id = await check_castle_wallet_configured() + # Get user's balance to calculate fiat metadata + user_balance = await get_user_balance(target_user_id) + + # Calculate proportional fiat amount for this invoice + invoice_extra = {"tag": "castle", "user_id": target_user_id} + + if user_balance.fiat_balances: + # Simple single-currency solution: use the first (and should be only) currency + currencies = list(user_balance.fiat_balances.keys()) + + if len(currencies) > 1: + raise HTTPException( + status_code=HTTPStatus.BAD_REQUEST, + detail=f"User has multiple currencies ({', '.join(currencies)}). Please settle to a single currency first.", + ) + + if len(currencies) == 1: + fiat_currency = currencies[0] + total_fiat_balance = user_balance.fiat_balances[fiat_currency] + total_sat_balance = abs(user_balance.balance) # Use absolute value + + if total_sat_balance > 0: + # Calculate proportional fiat amount for this invoice + # fiat_amount = (invoice_amount / total_sats) * total_fiat + from decimal import Decimal + proportion = Decimal(data.amount) / Decimal(total_sat_balance) + invoice_fiat_amount = abs(total_fiat_balance) * proportion + + # Calculate fiat rate (sats per fiat unit) + fiat_rate = float(data.amount) / float(invoice_fiat_amount) if invoice_fiat_amount > 0 else 0 + btc_rate = float(invoice_fiat_amount) / float(data.amount) * 100_000_000 if data.amount > 0 else 0 + + invoice_extra.update({ + "fiat_currency": fiat_currency, + "fiat_amount": str(invoice_fiat_amount.quantize(Decimal("0.001"))), + "fiat_rate": fiat_rate, + "btc_rate": btc_rate, + }) + # Create invoice on castle wallet invoice_data = CreateInvoice( out=False, amount=data.amount, memo=f"Payment from user {target_user_id[:8]} to Castle", unit="sat", - extra={"tag": "castle", "user_id": target_user_id}, + extra=invoice_extra, ) payment = await create_payment_request(castle_wallet_id, invoice_data) @@ -663,6 +702,22 @@ async def api_record_payment( # Convert amount from millisatoshis to satoshis amount_sats = payment.amount // 1000 + # Extract fiat metadata from invoice (if present) + line_metadata = {} + if payment.extra and isinstance(payment.extra, dict): + fiat_currency = payment.extra.get("fiat_currency") + fiat_amount = payment.extra.get("fiat_amount") + fiat_rate = payment.extra.get("fiat_rate") + btc_rate = payment.extra.get("btc_rate") + + if fiat_currency and fiat_amount: + line_metadata = { + "fiat_currency": fiat_currency, + "fiat_amount": str(fiat_amount), + "fiat_rate": fiat_rate, + "btc_rate": btc_rate, + } + # Get user's receivable account (what user owes) user_receivable = await get_or_create_user_account( target_user_id, AccountType.ASSET, "Accounts Receivable" @@ -698,12 +753,14 @@ async def api_record_payment( debit=amount_sats, credit=0, description="Lightning payment received", + metadata=line_metadata, ), CreateEntryLine( account_id=user_receivable.id, debit=0, credit=amount_sats, description="Payment applied to balance", + metadata=line_metadata, ), ], )