diff options
| author | Adam Soltys <[email protected]> | 2026-07-14 22:06:25 -0700 |
|---|---|---|
| committer | Adam Soltys <[email protected]> | 2026-07-14 22:06:25 -0700 |
| commit | 81c3593f1eec7f94dc9cbc22edc3456fa57b95cb (patch) | |
| tree | 731ca6c880d7a6dc4d29ad4f28d156fb69720b72 /lib | |
| parent | 0d55c0ccceb378a9c1992f6e7d82a6786cdd19b8 (diff) | |
| download | coinos-server-81c3593f1eec7f94dc9cbc22edc3456fa57b95cb.tar.gz coinos-server-81c3593f1eec7f94dc9cbc22edc3456fa57b95cb.zip | |
preimages: support fiat prices, converted at invoice-creation time
POST /preimages now accepts fiat + optional currency (defaults to the
merchant's) as an alternative to a sat price. The fiat amount is stored
per entry and converted to sats at the moment a usePreimage invoice is
created, so the price tracks the exchange rate instead of freezing at
upload time. The invoice also takes on the entry's currency and rate so
receipts display the fiat price exactly as the merchant set it. If no
rate is available for the entry's currency, the preimage is pushed back
onto the queue and creation fails rather than issuing an any-amount
invoice.
Co-Authored-By: Claude Fable 5 <[email protected]>
Diffstat (limited to 'lib')
| -rw-r--r-- | lib/invoices.ts | 30 |
1 files changed, 24 insertions, 6 deletions
diff --git a/lib/invoices.ts b/lib/invoices.ts index c3722ef5..613287a3 100644 --- a/lib/invoices.ts +++ b/lib/invoices.ts @@ -13,14 +13,16 @@ import { PaymentType } from "$lib/types"; const bc = rpc(config.bitcoin); const lq = rpc(config.liquid); -// Preimage queue entries are {preimage, price} JSON, but entries queued -// before prices existed are bare hex strings — treat those as priceless +// Preimage queue entries are {preimage, price, fiat, currency} JSON, but +// entries queued before prices existed are bare hex strings — treat those +// as priceless export const parseEntry = (e) => { try { const parsed = JSON.parse(e); - if (parsed?.preimage) return { price: null, ...parsed }; + if (parsed?.preimage) + return { price: null, fiat: null, currency: null, ...parsed }; } catch {} - return { preimage: e, price: null }; + return { preimage: e, price: null, fiat: null, currency: null }; }; export const generate = async ({ invoice, user }) => { @@ -114,11 +116,27 @@ export const generate = async ({ invoice, user }) => { const queue = `${user.id}:preimages`; let entry; while ((entry = await db.lPop(queue))) { - const { preimage, price } = parseEntry(entry); + const { + preimage, + price, + fiat: entryFiat, + currency: entryCurrency, + } = parseEntry(entry); // The invoice creator is unauthenticated, so a merchant-set price // must win over the amount in the request — otherwise a buyer - // could name their own price for the secret + // could name their own price for the secret. Fiat prices convert + // at the current rate, and the invoice takes on the merchant's + // currency so receipts show the fiat price as set. if (price) amount = price; + else if (entryFiat) { + if (!rates[entryCurrency]) { + await db.lPush(queue, entry); + fail(`no rate available for ${entryCurrency}`); + } + currency = entryCurrency; + rate = rates[currency]; + amount = Math.round((SATS * entryFiat) / rate); + } try { r = await ln.invoice({ ...args, |
