@openreceive/browser 0.4.9 → 0.4.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -864,7 +864,8 @@ import {
864
864
  formatDecimal as formatDecimal2,
865
865
  parseDecimal as parseDecimal2,
866
866
  payInAssetNetwork,
867
- swapAddressNetworkForPayInAsset
867
+ swapAddressNetworkForPayInAsset,
868
+ swapPayInAssetPeggedTo
868
869
  } from "@openreceive/core";
869
870
 
870
871
  // src/internal/unix-seconds.ts
@@ -881,34 +882,56 @@ function resolveNow(now) {
881
882
  }
882
883
 
883
884
  // src/internal/checkout-swap-view.ts
884
- function createSwapFeeBreakdown(fee) {
885
+ function swapFeeInTokenUnits(fee, payInAsset) {
886
+ return swapPayInAssetPeggedTo(payInAsset) === fee.currency;
887
+ }
888
+ function createSwapFeeBreakdown(fee, swap) {
885
889
  if (fee === void 0) return void 0;
886
- let payIn;
887
- let payout;
888
- try {
889
- payIn = parseDecimal2(fee.pay_in_fiat);
890
- payout = parseDecimal2(fee.payout_fiat);
891
- } catch {
892
- return void 0;
893
- }
894
- if (payout.units <= 0n) return void 0;
895
- const scale = Math.max(payIn.scale, payout.scale);
896
- const payInUnits = payIn.units * 10n ** BigInt(scale - payIn.scale);
897
- const payoutUnits = payout.units * 10n ** BigInt(scale - payout.scale);
898
- const feeUnits = payInUnits > payoutUnits ? payInUnits - payoutUnits : 0n;
899
- const format = (units) => {
890
+ const payout = parseFeeDecimal(fee.payout_fiat);
891
+ if (payout === void 0 || payout.units <= 0n) return void 0;
892
+ const fiat = (units, scale) => {
900
893
  const value = formatDecimal2(rescaleHalfUp(units, scale, 2), 2);
901
894
  return formatFiatAmount({ currency: fee.currency, value }) ?? `${value} ${fee.currency}`;
902
895
  };
903
- const percentTenths = roundedDiv(feeUnits * 1000n, payoutUnits);
904
- const feePercent = `${(percentTenths / 10n).toString()}.${(percentTenths % 10n).toString()}%`;
896
+ if (swap !== void 0 && swapFeeInTokenUnits(fee, swap.pay_in_asset)) {
897
+ const deposit = parseFeeDecimal(swap.deposit_amount);
898
+ if (deposit === void 0) return void 0;
899
+ const { assetLabel } = getSwapAssetDisplay(swap.pay_in_asset);
900
+ const gap2 = decimalGap(deposit, payout);
901
+ return {
902
+ cartTotal: fiat(gap2.base, gap2.scale),
903
+ youSend: `${formatDepositAmount(swap.deposit_amount)} ${assetLabel}`,
904
+ fee: `${formatDecimal2(rescaleHalfUp(gap2.fee, gap2.scale, 2), 2)} ${assetLabel}`,
905
+ feePercent: percentOf(gap2.fee, gap2.base)
906
+ };
907
+ }
908
+ const payIn = parseFeeDecimal(fee.pay_in_fiat);
909
+ if (payIn === void 0) return void 0;
910
+ const gap = decimalGap(payIn, payout);
905
911
  return {
906
- cartTotal: format(payoutUnits),
907
- youSend: format(payInUnits),
908
- fee: format(feeUnits),
909
- feePercent
912
+ cartTotal: fiat(gap.base, gap.scale),
913
+ youSend: fiat(gap.base + gap.fee, gap.scale),
914
+ fee: fiat(gap.fee, gap.scale),
915
+ feePercent: percentOf(gap.fee, gap.base)
910
916
  };
911
917
  }
918
+ function parseFeeDecimal(value) {
919
+ try {
920
+ return parseDecimal2(value);
921
+ } catch {
922
+ return void 0;
923
+ }
924
+ }
925
+ function decimalGap(paid, base) {
926
+ const scale = Math.max(paid.scale, base.scale);
927
+ const paidUnits = paid.units * 10n ** BigInt(scale - paid.scale);
928
+ const baseUnits = base.units * 10n ** BigInt(scale - base.scale);
929
+ return { fee: paidUnits > baseUnits ? paidUnits - baseUnits : 0n, base: baseUnits, scale };
930
+ }
931
+ function percentOf(fee, base) {
932
+ const tenths = roundedDiv(fee * 1000n, base);
933
+ return `${(tenths / 10n).toString()}.${(tenths % 10n).toString()}%`;
934
+ }
912
935
  function swapDepositRisk(payInAsset) {
913
936
  const network = swapAddressNetworkForPayInAsset(payInAsset);
914
937
  if (network === void 0 || network === "ETH") return "chain_ambiguous";
@@ -929,7 +952,7 @@ function createSwapDisplayModel(invoice, options = {}) {
929
952
  const depositRisk = swapDepositRisk(swap.pay_in_asset);
930
953
  const doubleSpendWarning = `Pay with one method only \u2014 if you already sent ${asset.assetLabel}, do not also pay the Lightning invoice.`;
931
954
  const settled = invoice.transaction_state === "settled";
932
- const feeBreakdown = createSwapFeeBreakdown(swap.fee);
955
+ const feeBreakdown = createSwapFeeBreakdown(swap.fee, swap);
933
956
  return {
934
957
  provider: swap.provider,
935
958
  attemptId: swap.attempt_id ?? invoice.invoice_id,
@@ -1291,7 +1314,7 @@ function createTransactionDetails(input) {
1291
1314
  push("Lightning payout", swap.payout_tx_id);
1292
1315
  push("Refund address", swap.refund_address, void 0, "address");
1293
1316
  push("Refund transaction", swap.refund_tx_id, void 0, "tx");
1294
- const feeBreakdown = createSwapFeeBreakdown(swap.fee);
1317
+ const feeBreakdown = createSwapFeeBreakdown(swap.fee, swap);
1295
1318
  if (feeBreakdown !== void 0) {
1296
1319
  push("Cart total", feeBreakdown.cartTotal);
1297
1320
  push("You send", feeBreakdown.youSend);
@@ -1301,7 +1324,9 @@ function createTransactionDetails(input) {
1301
1324
  );
1302
1325
  } else if (swap.fee !== void 0) {
1303
1326
  push("Fee currency", swap.fee.currency);
1304
- push("Pay-in fiat", swap.fee.pay_in_fiat);
1327
+ if (!swapFeeInTokenUnits(swap.fee, swap.pay_in_asset)) {
1328
+ push("Pay-in fiat", swap.fee.pay_in_fiat);
1329
+ }
1305
1330
  push("Payout fiat", swap.fee.payout_fiat);
1306
1331
  }
1307
1332
  }
@@ -1,7 +1,7 @@
1
1
  import { AssetIndexEntry, PaymentWizardRoute } from '@openreceive/provider-data';
2
2
  export { PaymentWizardRoute, PaymentWizardRouteRequest, getPaymentWizardRoutes, loadPayTutorialImages, payTutorialImage } from '@openreceive/provider-data';
3
- import { P as PaymentIconId, a as ParseOpenReceiveOptionalIntegerOptions, b as PaymentMethod, R as ResolvedTheme, T as ThemePreference, c as PaymentMethodOption, d as TickingValueOptions, e as TickingValueController, f as TransientFeedbackOptions, g as TransientFeedbackController, C as CheckoutInvoiceSnapshot, U as UnixSeconds, S as SwapDisplayModel, h as CheckoutSnapshot, i as SwapDepositRisk, j as CheckoutState, k as TransactionDetailsInput, l as TransactionDetailRow, m as CreateCheckoutStateOptions, n as CheckoutStatusModelInput, o as CheckoutStatusModel, p as CheckoutPaymentMethod, B as BrowserLoggerOption, q as ThemeAttributeTarget, r as CheckoutElementAttributes, s as CheckoutElementAttributeOptions, t as CheckoutElementEventHandlers, u as CheckoutElementListeners, v as CreateCheckoutShellOptions, w as CheckoutShellElements, x as CheckoutShellOptions, y as CheckoutShellModel, z as CreateOpenReceiveThemeToggleElementOptions, A as ThemeToggleElementAttributeOptions, D as ThemeToggleElementAttributes, E as StoredThemeModelOptions, F as ThemeModel, G as ThemeModelOptions, H as ReadThemePreferenceOptions, I as ThemeControlTargets, J as ThemeStorageOptions, K as PaymentWizardControllerOptions, L as PaymentWizardController, M as PaymentWizardSelection, N as PaymentWizardModel, W as WizardRouteAssetDisplay, O as WizardRouteDisplay, Q as PaymentWizardSelectionAction } from './status-Doh8Wz4D.js';
4
- export { V as BrowserLogContext, X as BrowserLogger, Y as BrowserRequestError, Z as CheckoutController, _ as CheckoutControllerOptions, $ as CheckoutPhase, a0 as CheckoutShellRootAttributes, a1 as CheckoutStatusRefresh, a2 as OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES, a3 as OPENRECEIVE_CHECKOUT_DATA_SELECTORS, a4 as OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES, a5 as OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS, a6 as OPENRECEIVE_CHECKOUT_ELEMENT_PARTS, a7 as OPENRECEIVE_CHECKOUT_ELEMENT_PART_SELECTORS, a8 as OPENRECEIVE_CHECKOUT_ELEMENT_SLOTS, a9 as OPENRECEIVE_CHECKOUT_ELEMENT_TAG_NAME, aa as OPENRECEIVE_COPY_FEEDBACK_MS, ab as OPENRECEIVE_DEFAULT_POLL_INTERVAL_MS, ac as OPENRECEIVE_DEFAULT_PREFIX, ad as OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES, ae as OPENRECEIVE_PAYMENT_WIZARD_SELECTORS, af as OPENRECEIVE_PROVIDER_PREVIEW_LIMIT, ag as OPENRECEIVE_STYLE_ROOT_ATTRIBUTE, ah as OPENRECEIVE_STYLE_ROOT_SELECTOR, ai as OPENRECEIVE_THEME_STORAGE_KEY, aj as OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES, ak as OPENRECEIVE_THEME_TOGGLE_ELEMENT_EVENTS, al as OPENRECEIVE_THEME_TOGGLE_ELEMENT_PARTS, am as OPENRECEIVE_THEME_TOGGLE_ELEMENT_PART_SELECTORS, an as OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME, ao as QrEncoder, ap as QrSvgController, aq as QrSvgControllerOptions, ar as Status, as as SwapCopyRow, at as SwapRefundStaging, au as WizardProviderDisplay, av as assertDisplayInvoice, aw as copyInvoice, ax as createCheckoutActionEvent, ay as createCheckoutController, az as createCheckoutErrorEvent, aA as createCheckoutProviderCopyEvent, aB as createCheckoutStateEvent, aC as createQrPayloadSvg, aD as createQrSvg, aE as createQrSvgController, aF as createStatusFetcher, aG as createThemeChangeEvent, aH as currentCheckoutUrl, aI as deriveCheckoutStateLabels, aJ as deriveStatus, aK as enterCheckoutResumePath, aL as escapeHtml, aM as formatAmountCaption, aN as formatDepositAmount, aO as formatFiatAmount, aP as formatMsats, aQ as formatSwapLimit, aR as formatUnixTime, aS as openWallet, aT as paymentIconSvgs, aU as prepareCheckout, aV as requestCheckout } from './status-Doh8Wz4D.js';
3
+ import { P as PaymentIconId, a as ParseOpenReceiveOptionalIntegerOptions, b as PaymentMethod, R as ResolvedTheme, T as ThemePreference, c as PaymentMethodOption, d as TickingValueOptions, e as TickingValueController, f as TransientFeedbackOptions, g as TransientFeedbackController, C as CheckoutInvoiceSnapshot, U as UnixSeconds, S as SwapDisplayModel, h as CheckoutSnapshot, i as SwapDepositRisk, j as CheckoutState, k as TransactionDetailsInput, l as TransactionDetailRow, m as CreateCheckoutStateOptions, n as CheckoutStatusModelInput, o as CheckoutStatusModel, p as CheckoutPaymentMethod, B as BrowserLoggerOption, q as ThemeAttributeTarget, r as CheckoutElementAttributes, s as CheckoutElementAttributeOptions, t as CheckoutElementEventHandlers, u as CheckoutElementListeners, v as CreateCheckoutShellOptions, w as CheckoutShellElements, x as CheckoutShellOptions, y as CheckoutShellModel, z as CreateOpenReceiveThemeToggleElementOptions, A as ThemeToggleElementAttributeOptions, D as ThemeToggleElementAttributes, E as StoredThemeModelOptions, F as ThemeModel, G as ThemeModelOptions, H as ReadThemePreferenceOptions, I as ThemeControlTargets, J as ThemeStorageOptions, K as PaymentWizardControllerOptions, L as PaymentWizardController, M as PaymentWizardSelection, N as PaymentWizardModel, W as WizardRouteAssetDisplay, O as WizardRouteDisplay, Q as PaymentWizardSelectionAction } from './status-Cnx1Vv-w.js';
4
+ export { V as BrowserLogContext, X as BrowserLogger, Y as BrowserRequestError, Z as CheckoutController, _ as CheckoutControllerOptions, $ as CheckoutPhase, a0 as CheckoutShellRootAttributes, a1 as CheckoutStatusRefresh, a2 as OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES, a3 as OPENRECEIVE_CHECKOUT_DATA_SELECTORS, a4 as OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES, a5 as OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS, a6 as OPENRECEIVE_CHECKOUT_ELEMENT_PARTS, a7 as OPENRECEIVE_CHECKOUT_ELEMENT_PART_SELECTORS, a8 as OPENRECEIVE_CHECKOUT_ELEMENT_SLOTS, a9 as OPENRECEIVE_CHECKOUT_ELEMENT_TAG_NAME, aa as OPENRECEIVE_COPY_FEEDBACK_MS, ab as OPENRECEIVE_DEFAULT_POLL_INTERVAL_MS, ac as OPENRECEIVE_DEFAULT_PREFIX, ad as OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES, ae as OPENRECEIVE_PAYMENT_WIZARD_SELECTORS, af as OPENRECEIVE_PROVIDER_PREVIEW_LIMIT, ag as OPENRECEIVE_STYLE_ROOT_ATTRIBUTE, ah as OPENRECEIVE_STYLE_ROOT_SELECTOR, ai as OPENRECEIVE_THEME_STORAGE_KEY, aj as OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES, ak as OPENRECEIVE_THEME_TOGGLE_ELEMENT_EVENTS, al as OPENRECEIVE_THEME_TOGGLE_ELEMENT_PARTS, am as OPENRECEIVE_THEME_TOGGLE_ELEMENT_PART_SELECTORS, an as OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME, ao as QrEncoder, ap as QrSvgController, aq as QrSvgControllerOptions, ar as Status, as as SwapCopyRow, at as SwapRefundStaging, au as WizardProviderDisplay, av as assertDisplayInvoice, aw as copyInvoice, ax as createCheckoutActionEvent, ay as createCheckoutController, az as createCheckoutErrorEvent, aA as createCheckoutProviderCopyEvent, aB as createCheckoutStateEvent, aC as createQrPayloadSvg, aD as createQrSvg, aE as createQrSvgController, aF as createStatusFetcher, aG as createThemeChangeEvent, aH as currentCheckoutUrl, aI as deriveCheckoutStateLabels, aJ as deriveStatus, aK as enterCheckoutResumePath, aL as escapeHtml, aM as formatAmountCaption, aN as formatDepositAmount, aO as formatFiatAmount, aP as formatMsats, aQ as formatSwapLimit, aR as formatUnixTime, aS as openWallet, aT as paymentIconSvgs, aU as prepareCheckout, aV as requestCheckout } from './status-Cnx1Vv-w.js';
5
5
  import '@openreceive/core';
6
6
 
7
7
  /** Each packaged payment icon as a `data:image/svg+xml` URI, keyed by id. */
package/dist/headless.js CHANGED
@@ -119,7 +119,7 @@ import {
119
119
  swapOptionLimitSentence,
120
120
  swapPickerKey,
121
121
  updatePaymentWizardSelection
122
- } from "./chunk-CPVYEWVM.js";
122
+ } from "./chunk-FIAYCDZO.js";
123
123
 
124
124
  // src/headless.ts
125
125
  import {
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { aW as BrowserLogLevel } from './status-Doh8Wz4D.js';
2
- export { aX as BrowserLogEntry, X as BrowserLogger, B as BrowserLoggerOption, Y as BrowserRequestError, Z as CheckoutController, _ as CheckoutControllerOptions, h as CheckoutSnapshot, j as CheckoutState, aY as CopyInvoiceOptions, aZ as GuestCheckoutResumeController, a_ as GuestCheckoutResumeOptions, a$ as OpenWalletOptions, b0 as PrepareCheckoutOptions, b1 as QrOptions, b2 as RequestCheckoutOptions, ar as Status, b3 as StatusInvoiceLike, U as UnixSeconds, aw as copyInvoice, ay as createCheckoutController, b4 as createGuestCheckoutResume, b5 as createGuestOrderFetcher, b6 as createLightningUri, b7 as createQrPngDataUrl, aD as createQrSvg, aJ as deriveStatus, aK as enterCheckoutResumePath, aS as openWallet, aU as prepareCheckout, aV as requestCheckout } from './status-Doh8Wz4D.js';
1
+ import { aW as BrowserLogLevel } from './status-Cnx1Vv-w.js';
2
+ export { aX as BrowserLogEntry, X as BrowserLogger, B as BrowserLoggerOption, Y as BrowserRequestError, Z as CheckoutController, _ as CheckoutControllerOptions, h as CheckoutSnapshot, j as CheckoutState, aY as CopyInvoiceOptions, aZ as GuestCheckoutResumeController, a_ as GuestCheckoutResumeOptions, a$ as OpenWalletOptions, b0 as PrepareCheckoutOptions, b1 as QrOptions, b2 as RequestCheckoutOptions, ar as Status, b3 as StatusInvoiceLike, U as UnixSeconds, aw as copyInvoice, ay as createCheckoutController, b4 as createGuestCheckoutResume, b5 as createGuestOrderFetcher, b6 as createLightningUri, b7 as createQrPngDataUrl, aD as createQrSvg, aJ as deriveStatus, aK as enterCheckoutResumePath, aS as openWallet, aU as prepareCheckout, aV as requestCheckout } from './status-Cnx1Vv-w.js';
3
3
  import '@openreceive/provider-data';
4
4
  import '@openreceive/core';
5
5
 
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ import {
13
13
  openWallet,
14
14
  prepareCheckout,
15
15
  requestCheckout
16
- } from "./chunk-CPVYEWVM.js";
16
+ } from "./chunk-FIAYCDZO.js";
17
17
  export {
18
18
  BrowserRequestError,
19
19
  copyInvoice,
@@ -431,14 +431,17 @@ interface CheckoutInvoiceSwapSnapshot {
431
431
  }
432
432
  /**
433
433
  * Formatted fee breakout for the deposit panel, explaining why the payer sends more
434
- * than the cart total. All figures are display-ready fiat strings.
434
+ * than the cart total. All figures are display-ready strings. `cartTotal` is
435
+ * always fiat; `youSend` and `fee` are fiat for a floating asset and token
436
+ * amounts ("50.05 USDC") for a stablecoin pegged to the fee currency, where a
437
+ * fiat valuation would read as the deposit amount with a typo.
435
438
  */
436
439
  interface SwapFeeBreakdown {
437
440
  /** Cart total delivered to the merchant, e.g. "$10.00". */
438
441
  readonly cartTotal: string;
439
- /** Fiat value of the crypto the payer sends, e.g. "$10.59". */
442
+ /** What the payer sends: its fiat value ("$10.59") or, for a pegged stablecoin, the deposit amount itself ("10.59 USDC"). */
440
443
  readonly youSend: string;
441
- /** The swap fee absorbed by the payer (exchange spread + network fees), e.g. "$0.59". */
444
+ /** The swap fee absorbed by the payer (exchange spread + network fees), e.g. "$0.59" or "0.59 USDC". */
442
445
  readonly fee: string;
443
446
  /** The fee as a percentage of the cart total, e.g. "5.9%", when computable. */
444
447
  readonly feePercent?: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openreceive/browser",
3
- "version": "0.4.9",
3
+ "version": "0.4.10",
4
4
  "description": "Browser helpers and a headless UI engine for Bitcoin Lightning checkout and optional USDT, USDC, SOL and ETH swaps.",
5
5
  "keywords": [
6
6
  "bitcoin",
@@ -30,8 +30,8 @@
30
30
  "./styles.css": "./dist/styles.css"
31
31
  },
32
32
  "dependencies": {
33
- "@openreceive/core": "0.4.9",
34
- "@openreceive/provider-data": "0.4.9",
33
+ "@openreceive/core": "0.4.10",
34
+ "@openreceive/provider-data": "0.4.10",
35
35
  "qrcode": "^1.5.4"
36
36
  },
37
37
  "devDependencies": {
@@ -70,6 +70,14 @@ same diagnostics redacted, always exit 0 — safe to share.
70
70
  - Refunds exist only for swap deposits from `refund_required`. There is **no
71
71
  Lightning refund** — the wallet cannot spend. Do not chase one.
72
72
  https://openreceive.org/guides/swap-refunds.md
73
+ - "Payer reports two different amounts on a stablecoin checkout" (50.05 or
74
+ 50.03?): the deposit amount is a token quantity, `fee.pay_in_fiat` is its
75
+ fiat valuation. Only `swap.deposit_amount` is an instruction. From 0.4.10 the
76
+ packaged checkout renders a USD stablecoin's breakdown in the token and never
77
+ shows `pay_in_fiat`; on an older bundle, upgrade `@openreceive/*`. To verify,
78
+ read the row's `deposit_amount` and `fee` and confirm the UI shows only the
79
+ deposit amount. A custom UI must call `createSwapFeeBreakdown(fee, swap)`
80
+ with the swap, not the fee alone.
73
81
 
74
82
  ## 6. Checkout UI shows nothing
75
83
 
@@ -86,6 +86,17 @@ state; do not retry-loop it, and do not build an idempotency store around it —
86
86
  that serialization is the library's job. (A hook failure while persisting an
87
87
  attempt is a **503 retryable**, deliberately distinct.)
88
88
 
89
+ ## Amounts on the deposit panel
90
+
91
+ `swap.deposit_amount` is the ONLY amount a payer is ever told to send, in the
92
+ pay-in token. `swap.fee.pay_in_fiat` / `payout_fiat` are fiat valuations that
93
+ explain the spread (why the deposit exceeds the cart total); they are not
94
+ instructions. For a stablecoin pegged to the fee currency (USDT, USDC) the
95
+ packaged checkout expresses the breakdown in the token and never renders
96
+ `pay_in_fiat` — "$50.03" under "50.05 USDC" reads as the same number with a
97
+ typo. A custom UI gets the same rule from `createSwapFeeBreakdown(fee, swap)`;
98
+ pass the swap, not just the fee.
99
+
89
100
  ## Secrets
90
101
 
91
102
  `NWC_URI` and `LSC_URI_*` are server-only. Never put them in browser code,
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (BTCPay Server)
2
2
 
3
- These directions describe OpenReceive 0.4.9.
3
+ These directions describe OpenReceive 0.4.10.
4
4
 
5
5
  Connect a BTCPay Server store to a receive-only NWC wallet with the OpenReceive
6
6
  plugin, and optionally let payers pay BTCPay invoices with USDT, USDC, ETH or
@@ -32,7 +32,7 @@ refund path on the same checkout screen.
32
32
 
33
33
  ## Step 0 — check the deployment before you change anything
34
34
 
35
- 1. Confirm the BTCPay Server version is 2.4.2 or later (Server Settings →
35
+ 1. Confirm the BTCPay Server version is 2.4.4 or later (Server Settings →
36
36
  About, or `GET /api/v1/server/info`). The plugin declares that minimum and
37
37
  BTCPay refuses to load it below.
38
38
  2. Check whether the plugin is installed (Server Settings → Plugins, or the
@@ -128,7 +128,7 @@ passes. The page it comes from is https://openreceive.org/guides/quickstart-btcp
128
128
 
129
129
  ## BTCPay Server quickstart
130
130
 
131
- Requires BTCPay Server ≥ 2.4.2.
131
+ Requires BTCPay Server ≥ 2.4.4.
132
132
 
133
133
  The OpenReceive plugin makes a receive-only NWC wallet the Lightning node of a
134
134
  BTCPay store. BTCPay mints every Lightning invoice in that wallet and records
@@ -143,7 +143,7 @@ invoices, checkout, webhooks and Greenfield API are the host.
143
143
 
144
144
  ### 1. Prerequisites
145
145
 
146
- - A BTCPay Server, version 2.4.2 or later, on any network (mainnet, testnet,
146
+ - A BTCPay Server, version 2.4.4 or later, on any network (mainnet, testnet,
147
147
  signet, regtest). The wallet must be on the same network.
148
148
  - A receive-only NWC code for the wallet you want to receive into
149
149
  ([get one here](https://openreceive.org/get_a_nwc_code_to_receive_payments)).
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (Django)
2
2
 
3
- These directions describe OpenReceive 0.4.9.
3
+ These directions describe OpenReceive 0.4.10.
4
4
 
5
5
  Add OpenReceive to a Django project — the app you are already working in. You
6
6
  do not need a copy of the OpenReceive source: the Python package is on PyPI
@@ -231,6 +231,13 @@ built on `@openreceive/browser/headless`. Read that before writing components.
231
231
  - `createSwapDisplayModel` → `display.copyRows` for deposits: address, memo,
232
232
  and the bare amount each get a copy row. Render `swap.networkWarning*` as
233
233
  the model gives it.
234
+ - `swap.deposit_amount` is the only amount a payer is told to send. Never put
235
+ a fiat valuation of it (`swap.fee.pay_in_fiat`) next to a stablecoin amount:
236
+ "$50.03" under "50.05 USDC" reads as a typo, and the payer asks which one to
237
+ send. The one fiat figure on a USDT/USDC deposit panel is the cart total
238
+ (`payout_fiat`); express "you send" and the fee in the token. Use
239
+ `createSwapFeeBreakdown(fee, swap)` — with the swap, not the fee alone — and
240
+ it applies this for you; SOL and ETH keep a fiat breakdown.
234
241
  - `createCheckoutSession` owns mint and swap start. To start swaps, pass its
235
242
  `swap` option (`selection`, `prefix`, `fetch`) together. Without it
236
243
  `startSwap` reports through `onError`.
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (FastAPI)
2
2
 
3
- These directions describe OpenReceive 0.4.9.
3
+ These directions describe OpenReceive 0.4.10.
4
4
 
5
5
  Add OpenReceive to a FastAPI application — the app you are already working in.
6
6
  You do not need a copy of the OpenReceive source: the engine is on PyPI
@@ -228,6 +228,13 @@ components.
228
228
  - `createSwapDisplayModel` → `display.copyRows` for deposits: address, memo,
229
229
  and the bare amount each get a copy row. Render `swap.networkWarning*` as
230
230
  the model gives it.
231
+ - `swap.deposit_amount` is the only amount a payer is told to send. Never put
232
+ a fiat valuation of it (`swap.fee.pay_in_fiat`) next to a stablecoin amount:
233
+ "$50.03" under "50.05 USDC" reads as a typo, and the payer asks which one to
234
+ send. The one fiat figure on a USDT/USDC deposit panel is the cart total
235
+ (`payout_fiat`); express "you send" and the fee in the token. Use
236
+ `createSwapFeeBreakdown(fee, swap)` — with the swap, not the fee alone — and
237
+ it applies this for you; SOL and ETH keep a fiat breakdown.
231
238
  - `createCheckoutSession` owns mint and swap start. To start swaps, pass its
232
239
  `swap` option (`selection`, `prefix`, `fetch`) together. Without it
233
240
  `startSwap` reports through `onError`.
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (Fastify)
2
2
 
3
- These directions describe OpenReceive 0.4.9.
3
+ These directions describe OpenReceive 0.4.10.
4
4
 
5
5
  Add OpenReceive to a Fastify application — the app you are already working in.
6
6
  You do not need a copy of the OpenReceive source: the packages are on npm, and
@@ -211,6 +211,13 @@ components.
211
211
  - `createSwapDisplayModel` → `display.copyRows` for deposits: address, memo,
212
212
  and the bare amount each get a copy row. Render `swap.networkWarning*` as
213
213
  the model gives it.
214
+ - `swap.deposit_amount` is the only amount a payer is told to send. Never put
215
+ a fiat valuation of it (`swap.fee.pay_in_fiat`) next to a stablecoin amount:
216
+ "$50.03" under "50.05 USDC" reads as a typo, and the payer asks which one to
217
+ send. The one fiat figure on a USDT/USDC deposit panel is the cart total
218
+ (`payout_fiat`); express "you send" and the fee in the token. Use
219
+ `createSwapFeeBreakdown(fee, swap)` — with the swap, not the fee alone — and
220
+ it applies this for you; SOL and ETH keep a fiat breakdown.
214
221
  - `createCheckoutSession` owns mint and swap start. To start swaps, pass its
215
222
  `swap` option (`selection`, `prefix`, `fetch`) together. Without it
216
223
  `startSwap` reports through `onError`.
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (Laravel)
2
2
 
3
- These directions describe OpenReceive 0.4.9.
3
+ These directions describe OpenReceive 0.4.10.
4
4
 
5
5
  Add OpenReceive to a Laravel application — the app you are already working in.
6
6
  You do not need a copy of the OpenReceive source: the package is on Packagist
@@ -216,6 +216,13 @@ built on `@openreceive/browser/headless`. Read that before writing components.
216
216
  - `createSwapDisplayModel` → `display.copyRows` for deposits: address, memo,
217
217
  and the bare amount each get a copy row. Render `swap.networkWarning*` as
218
218
  the model gives it.
219
+ - `swap.deposit_amount` is the only amount a payer is told to send. Never put
220
+ a fiat valuation of it (`swap.fee.pay_in_fiat`) next to a stablecoin amount:
221
+ "$50.03" under "50.05 USDC" reads as a typo, and the payer asks which one to
222
+ send. The one fiat figure on a USDT/USDC deposit panel is the cart total
223
+ (`payout_fiat`); express "you send" and the fee in the token. Use
224
+ `createSwapFeeBreakdown(fee, swap)` — with the swap, not the fee alone — and
225
+ it applies this for you; SOL and ETH keep a fiat breakdown.
219
226
  - `createCheckoutSession` owns mint and swap start. To start swaps, pass its
220
227
  `swap` option (`selection`, `prefix`, `fetch`) together. Without it
221
228
  `startSwap` reports through `onError`.
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (Next.js)
2
2
 
3
- These directions describe OpenReceive 0.4.9.
3
+ These directions describe OpenReceive 0.4.10.
4
4
 
5
5
  Add OpenReceive to a Next.js App Router application — the app you are already
6
6
  working in. You do not need a copy of the OpenReceive source: the packages are
@@ -217,6 +217,13 @@ components.
217
217
  - `createSwapDisplayModel` → `display.copyRows` for deposits: address, memo,
218
218
  and the bare amount each get a copy row. Render `swap.networkWarning*` as
219
219
  the model gives it.
220
+ - `swap.deposit_amount` is the only amount a payer is told to send. Never put
221
+ a fiat valuation of it (`swap.fee.pay_in_fiat`) next to a stablecoin amount:
222
+ "$50.03" under "50.05 USDC" reads as a typo, and the payer asks which one to
223
+ send. The one fiat figure on a USDT/USDC deposit panel is the cart total
224
+ (`payout_fiat`); express "you send" and the fee in the token. Use
225
+ `createSwapFeeBreakdown(fee, swap)` — with the swap, not the fee alone — and
226
+ it applies this for you; SOL and ETH keep a fiat breakdown.
220
227
  - `createCheckoutSession` owns mint and swap start. To start swaps, pass its
221
228
  `swap` option (`selection`, `prefix`, `fetch`) together. Without it
222
229
  `startSwap` reports through `onError`.
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (Node.js)
2
2
 
3
- These directions describe OpenReceive 0.4.9.
3
+ These directions describe OpenReceive 0.4.10.
4
4
 
5
5
  Add OpenReceive to a Node application — the app you are already working in. You
6
6
  do not need a copy of the OpenReceive source: the packages are on npm, and the
@@ -203,6 +203,13 @@ components.
203
203
  - `createSwapDisplayModel` → `display.copyRows` for deposits: address, memo,
204
204
  and the bare amount each get a copy row. Render `swap.networkWarning*` as
205
205
  the model gives it.
206
+ - `swap.deposit_amount` is the only amount a payer is told to send. Never put
207
+ a fiat valuation of it (`swap.fee.pay_in_fiat`) next to a stablecoin amount:
208
+ "$50.03" under "50.05 USDC" reads as a typo, and the payer asks which one to
209
+ send. The one fiat figure on a USDT/USDC deposit panel is the cart total
210
+ (`payout_fiat`); express "you send" and the fee in the token. Use
211
+ `createSwapFeeBreakdown(fee, swap)` — with the swap, not the fee alone — and
212
+ it applies this for you; SOL and ETH keep a fiat breakdown.
206
213
  - `createCheckoutSession` owns mint and swap start. To start swaps, pass its
207
214
  `swap` option (`selection`, `prefix`, `fetch`) together. Without it
208
215
  `startSwap` reports through `onError`.
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (PHP)
2
2
 
3
- These directions describe OpenReceive 0.4.9.
3
+ These directions describe OpenReceive 0.4.10.
4
4
 
5
5
  Add OpenReceive to a PHP application — the app you are already working in. You
6
6
  do not need a copy of the OpenReceive source: the engine is on Packagist
@@ -229,6 +229,13 @@ of https://openreceive.org/guides/checkout-ux.md, for a UI built on
229
229
  - `createSwapDisplayModel` → `display.copyRows` for deposits: address, memo,
230
230
  and the bare amount each get a copy row. Render `swap.networkWarning*` as
231
231
  the model gives it.
232
+ - `swap.deposit_amount` is the only amount a payer is told to send. Never put
233
+ a fiat valuation of it (`swap.fee.pay_in_fiat`) next to a stablecoin amount:
234
+ "$50.03" under "50.05 USDC" reads as a typo, and the payer asks which one to
235
+ send. The one fiat figure on a USDT/USDC deposit panel is the cart total
236
+ (`payout_fiat`); express "you send" and the fee in the token. Use
237
+ `createSwapFeeBreakdown(fee, swap)` — with the swap, not the fee alone — and
238
+ it applies this for you; SOL and ETH keep a fiat breakdown.
232
239
  - `createCheckoutSession` owns mint and swap start. To start swaps, pass its
233
240
  `swap` option (`selection`, `prefix`, `fetch`) together. Without it
234
241
  `startSwap` reports through `onError`.
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (Rails)
2
2
 
3
- These directions describe OpenReceive 0.4.9.
3
+ These directions describe OpenReceive 0.4.10.
4
4
 
5
5
  Add OpenReceive to a Rails application — the app you are already working in. You
6
6
  do not need a copy of the OpenReceive source: the gem is on RubyGems, the
@@ -210,6 +210,13 @@ built on `@openreceive/browser/headless`. Read that before writing components.
210
210
  - `createSwapDisplayModel` → `display.copyRows` for deposits: address, memo,
211
211
  and the bare amount each get a copy row. Render `swap.networkWarning*` as
212
212
  the model gives it.
213
+ - `swap.deposit_amount` is the only amount a payer is told to send. Never put
214
+ a fiat valuation of it (`swap.fee.pay_in_fiat`) next to a stablecoin amount:
215
+ "$50.03" under "50.05 USDC" reads as a typo, and the payer asks which one to
216
+ send. The one fiat figure on a USDT/USDC deposit panel is the cart total
217
+ (`payout_fiat`); express "you send" and the fee in the token. Use
218
+ `createSwapFeeBreakdown(fee, swap)` — with the swap, not the fee alone — and
219
+ it applies this for you; SOL and ETH keep a fiat breakdown.
213
220
  - `createCheckoutSession` owns mint and swap start. To start swaps, pass its
214
221
  `swap` option (`selection`, `prefix`, `fetch`) together. Without it
215
222
  `startSwap` reports through `onError`.
@@ -1,6 +1,6 @@
1
1
  # OpenReceive agent directions (WordPress + WooCommerce)
2
2
 
3
- These directions describe OpenReceive 0.4.9.
3
+ These directions describe OpenReceive 0.4.10.
4
4
 
5
5
  Install and configure the OpenReceive gateway in the existing WooCommerce
6
6
  store. Preserve its theme, checkout, customer accounts, order model and prices.