Adds fiat settlement entry formatting
Introduces a function to format fiat settlement entries for Beancount, handling cash, bank transfers, and other non-lightning payments. This allows for recording transactions in fiat currency with sats as metadata. Updates the API endpoint to use the new function when settling receivables with fiat currencies.
This commit is contained in:
parent
472c4e2164
commit
490b361268
2 changed files with 148 additions and 27 deletions
|
|
@ -528,6 +528,112 @@ def format_payment_entry(
|
|||
)
|
||||
|
||||
|
||||
def format_fiat_settlement_entry(
|
||||
user_id: str,
|
||||
payment_account: str,
|
||||
payable_or_receivable_account: str,
|
||||
fiat_amount: Decimal,
|
||||
fiat_currency: str,
|
||||
amount_sats: int,
|
||||
description: str,
|
||||
entry_date: date,
|
||||
is_payable: bool = True,
|
||||
payment_method: str = "cash",
|
||||
reference: Optional[str] = None
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Format a fiat (cash/bank) settlement entry.
|
||||
|
||||
Unlike Lightning payments, fiat settlements use fiat currency as the primary amount
|
||||
with SATS stored as metadata for reference.
|
||||
|
||||
Args:
|
||||
user_id: User ID
|
||||
payment_account: Payment method account (e.g., "Assets:Cash", "Assets:Bank")
|
||||
payable_or_receivable_account: User's account being settled
|
||||
fiat_amount: Amount in fiat currency (unsigned)
|
||||
fiat_currency: Fiat currency code (EUR, USD, etc.)
|
||||
amount_sats: Equivalent amount in satoshis (for metadata only)
|
||||
description: Payment description
|
||||
entry_date: Date of settlement
|
||||
is_payable: True if castle paying user (payable), False if user paying castle (receivable)
|
||||
payment_method: Payment method (cash, bank_transfer, check, etc.)
|
||||
reference: Optional reference
|
||||
|
||||
Returns:
|
||||
Fava API entry dict
|
||||
"""
|
||||
fiat_amount_abs = abs(fiat_amount)
|
||||
amount_sats_abs = abs(amount_sats)
|
||||
|
||||
if is_payable:
|
||||
# Castle paying user: DR Payable, CR Cash/Bank
|
||||
postings = [
|
||||
{
|
||||
"account": payable_or_receivable_account,
|
||||
"amount": f"{fiat_amount_abs:.2f} {fiat_currency}",
|
||||
"meta": {
|
||||
"sats-equivalent": str(amount_sats_abs)
|
||||
}
|
||||
},
|
||||
{
|
||||
"account": payment_account,
|
||||
"amount": f"-{fiat_amount_abs:.2f} {fiat_currency}",
|
||||
"meta": {
|
||||
"sats-equivalent": str(amount_sats_abs)
|
||||
}
|
||||
}
|
||||
]
|
||||
else:
|
||||
# User paying castle: DR Cash/Bank, CR Receivable
|
||||
postings = [
|
||||
{
|
||||
"account": payment_account,
|
||||
"amount": f"{fiat_amount_abs:.2f} {fiat_currency}",
|
||||
"meta": {
|
||||
"sats-equivalent": str(amount_sats_abs)
|
||||
}
|
||||
},
|
||||
{
|
||||
"account": payable_or_receivable_account,
|
||||
"amount": f"-{fiat_amount_abs:.2f} {fiat_currency}",
|
||||
"meta": {
|
||||
"sats-equivalent": str(amount_sats_abs)
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
# Map payment method to appropriate source and tag
|
||||
payment_method_map = {
|
||||
"cash": ("cash_settlement", "cash-payment"),
|
||||
"bank_transfer": ("bank_settlement", "bank-transfer"),
|
||||
"check": ("check_settlement", "check-payment"),
|
||||
"btc_onchain": ("onchain_settlement", "onchain-payment"),
|
||||
"other": ("manual_settlement", "manual-payment")
|
||||
}
|
||||
|
||||
source, tag = payment_method_map.get(payment_method.lower(), ("manual_settlement", "manual-payment"))
|
||||
|
||||
entry_meta = {
|
||||
"user-id": user_id,
|
||||
"source": source
|
||||
}
|
||||
|
||||
links = []
|
||||
if reference:
|
||||
links.append(reference)
|
||||
|
||||
return format_transaction(
|
||||
date_val=entry_date,
|
||||
flag="*", # Cleared (payment already happened)
|
||||
narration=description,
|
||||
postings=postings,
|
||||
tags=[tag],
|
||||
links=links,
|
||||
meta=entry_meta
|
||||
)
|
||||
|
||||
|
||||
def format_net_settlement_entry(
|
||||
user_id: str,
|
||||
payment_account: str,
|
||||
|
|
|
|||
69
views_api.py
69
views_api.py
|
|
@ -1569,43 +1569,58 @@ async def api_settle_receivable(
|
|||
# DR Cash/Bank (asset increased), CR Accounts Receivable (asset decreased)
|
||||
# This records that user paid their debt
|
||||
from .fava_client import get_fava_client
|
||||
from .beancount_format import format_payment_entry
|
||||
from .beancount_format import format_payment_entry, format_fiat_settlement_entry
|
||||
from decimal import Decimal
|
||||
|
||||
fava = get_fava_client()
|
||||
|
||||
# Determine amount and currency
|
||||
if data.currency:
|
||||
# Fiat currency payment (e.g., EUR, USD)
|
||||
# Use the sats equivalent for the journal entry to match the receivable
|
||||
# Determine if this is a fiat or lightning payment
|
||||
is_fiat_payment = data.currency and data.payment_method.lower() in [
|
||||
"cash", "bank_transfer", "check", "other"
|
||||
]
|
||||
|
||||
if is_fiat_payment:
|
||||
# Fiat currency payment (cash, bank transfer, etc.)
|
||||
# Record in fiat currency with sats as metadata
|
||||
if not data.amount_sats:
|
||||
raise HTTPException(
|
||||
status_code=HTTPStatus.BAD_REQUEST,
|
||||
detail="amount_sats is required when settling with fiat currency"
|
||||
)
|
||||
amount_in_sats = data.amount_sats
|
||||
fiat_currency = data.currency.upper()
|
||||
fiat_amount = data.amount
|
||||
else:
|
||||
# Satoshi payment
|
||||
amount_in_sats = int(data.amount)
|
||||
fiat_currency = None
|
||||
fiat_amount = None
|
||||
|
||||
# Format payment entry
|
||||
entry = format_payment_entry(
|
||||
user_id=data.user_id,
|
||||
payment_account=payment_account.name,
|
||||
payable_or_receivable_account=user_receivable.name,
|
||||
amount_sats=amount_in_sats,
|
||||
description=data.description,
|
||||
entry_date=datetime.now().date(),
|
||||
is_payable=False, # User paying castle (receivable settlement)
|
||||
fiat_currency=fiat_currency,
|
||||
fiat_amount=fiat_amount,
|
||||
payment_hash=data.payment_hash,
|
||||
reference=data.reference or f"MANUAL-{data.user_id[:8]}"
|
||||
)
|
||||
entry = format_fiat_settlement_entry(
|
||||
user_id=data.user_id,
|
||||
payment_account=payment_account.name,
|
||||
payable_or_receivable_account=user_receivable.name,
|
||||
fiat_amount=Decimal(str(data.amount)),
|
||||
fiat_currency=data.currency.upper(),
|
||||
amount_sats=data.amount_sats,
|
||||
description=data.description,
|
||||
entry_date=datetime.now().date(),
|
||||
is_payable=False, # User paying castle (receivable settlement)
|
||||
payment_method=data.payment_method,
|
||||
reference=data.reference or f"MANUAL-{data.user_id[:8]}"
|
||||
)
|
||||
else:
|
||||
# Lightning or BTC onchain payment
|
||||
# Record in SATS with optional fiat metadata
|
||||
amount_in_sats = data.amount_sats if data.amount_sats else int(data.amount)
|
||||
fiat_currency = data.currency.upper() if data.currency else None
|
||||
fiat_amount = Decimal(str(data.amount)) if data.currency else None
|
||||
|
||||
entry = format_payment_entry(
|
||||
user_id=data.user_id,
|
||||
payment_account=payment_account.name,
|
||||
payable_or_receivable_account=user_receivable.name,
|
||||
amount_sats=amount_in_sats,
|
||||
description=data.description,
|
||||
entry_date=datetime.now().date(),
|
||||
is_payable=False, # User paying castle (receivable settlement)
|
||||
fiat_currency=fiat_currency,
|
||||
fiat_amount=fiat_amount,
|
||||
payment_hash=data.payment_hash,
|
||||
reference=data.reference or f"MANUAL-{data.user_id[:8]}"
|
||||
)
|
||||
|
||||
# Add additional metadata to entry
|
||||
if "meta" not in entry:
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue