Commit graph

2 commits

Author SHA1 Message Date
638f36e945 fix(services): convert fiat menu prices to sat via exchange rates
Before this fix `_to_msat(item.price)` blindly did `price * 1000`,
treating any menu price as sat-denominated regardless of the item's
`currency` field. Quote and bolt11 were internally consistent but
charged ~0.1% of the real price for fiat-priced menus.

  Big Jay's seeded with GTQ:
    2× Tacos (Maíz, +Brisket, +Chicken)
    pre-fix:  170 GTQ → 170000 msat → 170 sat invoice (~$0.14)
    post-fix: 170 GTQ → 26968000 msat → 26968 sat invoice (~$22)

services.py:
  - Drop `_to_msat` in favor of a `_price_to_msat(amount, currency)`
    helper. Sat-aliased currencies ("sat", "sats", "satoshi",
    "msat", …) take the flat ×1000 path; everything else round-
    trips through lnbits.utils.exchange_rates.fiat_amount_as_satoshis
    (same pool the events extension uses).
  - Update _price_line_item: item.price AND each modifier.price_delta
    are converted using item.currency. Modifier deltas inherit the
    parent item's currency since we don't carry a per-modifier
    currency field.
  - Update quote_balance_required: same conversion via the item's
    currency.

Verified live against the seeded "Big Jay's Bustaurant":
  GTQ → sat conversion matches LNbits's bitcoin-price aggregate
  (Binance / Blockchain / Bitfinex / Bitstamp / Coinbase / yadio).
  Quote returns 26968 sats for 170 GTQ — within ~2% of expected
  rate from external sources.
2026-05-11 19:18:16 +02:00
201c387722 feat(services,tasks): order placement, settlement, invoice listener
services.py
- place_order: validates against live menu, prices line items
  authoritatively from DB (modifier ids resolved server-side, not
  trusted from input), creates LNbits invoice, persists order +
  items. Order id := payment_hash for zero-metadata listener
  lookups.
- mark_order_paid: idempotent paid -> [accepted if auto-accept] +
  stock decrement + queues a print job.
- transition_order: explicit state-machine guard for accept/ready/
  complete/cancel/refund.
- quote_balance_required: pre-flight total for the webapp's
  multi-restaurant balance check (per the user's requirement to
  verify funds before opening any per-restaurant invoice).

tasks.py
- Single invoice listener filtered on extra.tag == 'restaurant',
  looks up order by payment_hash, delegates to mark_order_paid.
  Wrapped in try/except so one bad payment doesn't kill the loop.
2026-05-09 07:11:06 +02:00