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, ), ], )