@openreceive/react 0.2.4 → 0.3.1

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.
package/dist/index.js CHANGED
@@ -30,16 +30,23 @@ function useCheckoutSession(options) {
30
30
  reference: () => optionsRef.current.reference(),
31
31
  requestCheckout: (reference) => optionsRef.current.requestCheckout?.(reference),
32
32
  onSnapshot: (snapshot) => optionsRef.current.onSnapshot?.(snapshot),
33
- swapPrefix: () => optionsRef.current.swapPrefix?.(),
34
- fetch: () => optionsRef.current.fetch?.(),
35
- swapSelection: {
36
- started: () => optionsRef.current.swapSelection?.started(),
37
- setStarted: (invoice) => optionsRef.current.swapSelection?.setStarted(invoice),
38
- dismissedInvoiceId: () => optionsRef.current.swapSelection?.dismissedInvoiceId() ?? null,
39
- setDismissedInvoiceId: (invoiceId) => optionsRef.current.swapSelection?.setDismissedInvoiceId(invoiceId),
40
- setSelectedAsset: (payInAsset) => optionsRef.current.swapSelection?.setSelectedAsset(payInAsset)
33
+ // The inner `swap` is always present because the session is built once,
34
+ // on the first render, and the host may only gain swap options later.
35
+ // Every accessor reads through `optionsRef`, so a host with no `swap`
36
+ // makes `prefix()` answer undefined and the session reports the missing
37
+ // wiring instead of silently doing nothing.
38
+ swap: {
39
+ selection: {
40
+ started: () => optionsRef.current.swap?.selection.started(),
41
+ setStarted: (invoice) => optionsRef.current.swap?.selection.setStarted(invoice),
42
+ dismissedInvoiceId: () => optionsRef.current.swap?.selection.dismissedInvoiceId() ?? null,
43
+ setDismissedInvoiceId: (invoiceId) => optionsRef.current.swap?.selection.setDismissedInvoiceId(invoiceId),
44
+ setSelectedAsset: (payInAsset) => optionsRef.current.swap?.selection.setSelectedAsset(payInAsset)
45
+ },
46
+ prefix: () => optionsRef.current.swap?.prefix(),
47
+ fetch: () => optionsRef.current.swap?.fetch(),
48
+ onStarted: (invoice) => optionsRef.current.swap?.onStarted?.(invoice)
41
49
  },
42
- onSwapStarted: (invoice) => optionsRef.current.onSwapStarted?.(invoice),
43
50
  get logger() {
44
51
  return optionsRef.current.logger;
45
52
  },
@@ -56,8 +63,7 @@ import {
56
63
  OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES,
57
64
  copyInvoice as copyInvoiceHelper,
58
65
  createCheckoutStatusModel,
59
- createPaymentDataEntries,
60
- createQrSvg,
66
+ createQrSvgController,
61
67
  formatAmountCaption,
62
68
  checkoutLabels,
63
69
  openWallet as openWalletHelper,
@@ -186,14 +192,15 @@ function QRCode(props) {
186
192
  const onErrorRef = React3.useRef(onError);
187
193
  onErrorRef.current = onError;
188
194
  React3.useEffect(() => {
189
- let cancelled = false;
190
- createQrSvg(invoice, { encoder, width }).then((nextSvg) => {
191
- if (!cancelled) setSvg(nextSvg);
192
- }).catch((error) => {
193
- if (!cancelled) onErrorRef.current?.(error);
195
+ const controller = createQrSvgController({
196
+ onValue: setSvg,
197
+ onError: (error) => onErrorRef.current?.(error),
198
+ ...encoder === void 0 ? {} : { encoder },
199
+ width
194
200
  });
201
+ controller.show(invoice);
195
202
  return () => {
196
- cancelled = true;
203
+ controller.stop();
197
204
  };
198
205
  }, [invoice, encoder, width]);
199
206
  return React3.createElement("div", {
@@ -320,31 +327,6 @@ function SettledCheckIcon() {
320
327
  })
321
328
  );
322
329
  }
323
- function PaymentData(props) {
324
- const entries = createPaymentDataEntries(props.source);
325
- if (entries.length === 0) return null;
326
- return React3.createElement(
327
- "details",
328
- { className: joinClassNames(orClasses.paymentData, props.className) },
329
- React3.createElement(
330
- "summary",
331
- { className: orClasses.paymentDataTitle },
332
- checkoutLabels.viewPaymentData
333
- ),
334
- React3.createElement(
335
- "div",
336
- { className: orClasses.paymentDataBody },
337
- entries.map(
338
- (entry) => React3.createElement(
339
- "div",
340
- { key: entry.label, className: orClasses.paymentDataRow },
341
- React3.createElement("span", { className: orClasses.paymentDataKey }, entry.label),
342
- React3.createElement("p", { className: orClasses.paymentDataValue }, entry.value)
343
- )
344
- )
345
- )
346
- );
347
- }
348
330
  function WaitingState(props) {
349
331
  const status = props.status ?? createCheckoutStatusModel({
350
332
  phase: props.phase,
@@ -872,6 +854,38 @@ function useCheckout(options) {
872
854
  const next = controllerRef.current?.cancel();
873
855
  if (next !== void 0) setState(next);
874
856
  }, []);
857
+ const swapRefundController = React6.useCallback(() => {
858
+ const controller = controllerRef.current;
859
+ if (controller === null) {
860
+ throw new Error("Swap refunds need a started checkout controller.");
861
+ }
862
+ return controller;
863
+ }, []);
864
+ const stageSwapRefund = React6.useCallback(
865
+ async (refund) => {
866
+ try {
867
+ return await swapRefundController().stageSwapRefund(refund);
868
+ } catch (error) {
869
+ onErrorRef.current?.(error);
870
+ throw error;
871
+ }
872
+ },
873
+ [swapRefundController]
874
+ );
875
+ const confirmSwapRefund = React6.useCallback(
876
+ async (refund) => {
877
+ try {
878
+ return await swapRefundController().confirmSwapRefund(refund);
879
+ } catch (error) {
880
+ onErrorRef.current?.(error);
881
+ throw error;
882
+ }
883
+ },
884
+ [swapRefundController]
885
+ );
886
+ const clearSwapRefundStaging = React6.useCallback(() => {
887
+ controllerRef.current?.clearSwapRefundStaging();
888
+ }, []);
875
889
  return {
876
890
  // The state IS the view model now: one derivation, one set of display
877
891
  // fields. `status` stays on top of it because the component's terminal
@@ -890,7 +904,10 @@ function useCheckout(options) {
890
904
  retry,
891
905
  cancel,
892
906
  copyInvoice,
893
- openWallet
907
+ openWallet,
908
+ stageSwapRefund,
909
+ confirmSwapRefund,
910
+ clearSwapRefundStaging
894
911
  };
895
912
  }
896
913
  var CheckoutContext = React6.createContext(null);
@@ -914,12 +931,12 @@ import {
914
931
  buildMethodGridEntries,
915
932
  checkoutLabels as checkoutLabels5,
916
933
  copyInvoice as copyInvoiceHelper3,
934
+ createAssetBaseUrlResolver,
935
+ createMethodGridDisplay,
917
936
  createPaymentWizardController,
918
937
  createPaymentWizardModel,
919
938
  createWizardRouteAssetDisplays,
920
939
  createWizardRouteDisplays,
921
- findSwapGridGroup,
922
- formatChooseNetworkHeading,
923
940
  formatNetworkSummary,
924
941
  getNetworkIcon,
925
942
  getPaymentMethodIcon,
@@ -930,16 +947,12 @@ import {
930
947
  networkMobileRevealClasses,
931
948
  networkSummaryIconClasses,
932
949
  orClasses as orClasses6,
933
- overlaySwapRefundStaging,
934
- paymentAccentId,
935
950
  paymentMethods,
951
+ resolveWizardSelection,
936
952
  requestSwapRefund,
953
+ selectCurrentSwapInvoice,
937
954
  swapAssetMatchesRoute,
938
- swapGroupLimitOption,
939
- swapOptionLimitMessage as swapOptionLimitMessage2,
940
- swapPickerKey,
941
- updateSelectedSwapNetworks,
942
- wizardNetworkGroupIds
955
+ swapOptionLimitMessage as swapOptionLimitMessage2
943
956
  } from "@openreceive/browser/headless";
944
957
  import * as React9 from "react";
945
958
 
@@ -1189,7 +1202,7 @@ import {
1189
1202
  createDetailExternalLink,
1190
1203
  createSwapDisplayModel,
1191
1204
  createTransactionDetails,
1192
- createQrPayloadSvg,
1205
+ createQrSvgController as createQrSvgController2,
1193
1206
  getSwapRefundFormError,
1194
1207
  checkoutLabels as checkoutLabels4,
1195
1208
  createSwapUnavailableModel,
@@ -1296,12 +1309,11 @@ function renderSwapUnavailable(quote, checkout) {
1296
1309
  );
1297
1310
  }
1298
1311
  function renderSwapDepositPanel(options) {
1299
- const display = createSwapDisplayModel(
1300
- options.invoice,
1301
- options.now === void 0 ? {} : { now: options.now }
1302
- );
1312
+ const display = createSwapDisplayModel(options.invoice, {
1313
+ ...options.now === void 0 ? {} : { now: options.now },
1314
+ ...options.resumable === void 0 ? {} : { resumable: options.resumable }
1315
+ });
1303
1316
  if (display === void 0) return null;
1304
- const memo = display.depositMemo;
1305
1317
  const backButton = React8.createElement(
1306
1318
  "button",
1307
1319
  {
@@ -1450,7 +1462,7 @@ function renderSwapDepositPanel(options) {
1450
1462
  onError: options.onError
1451
1463
  }),
1452
1464
  renderSwapSupportDetails(display, options),
1453
- renderSwapRefundReturnWarning()
1465
+ renderSwapRefundReturnWarning(display)
1454
1466
  );
1455
1467
  }
1456
1468
  if (display.state === "refund_pending" || display.state === "refunded") {
@@ -1478,7 +1490,7 @@ function renderSwapDepositPanel(options) {
1478
1490
  })
1479
1491
  ),
1480
1492
  renderSwapSupportDetails(display, options),
1481
- renderSwapRefundReturnWarning()
1493
+ renderSwapRefundReturnWarning(display)
1482
1494
  );
1483
1495
  }
1484
1496
  if (display.state === "deposit") {
@@ -1517,16 +1529,14 @@ function renderSwapDepositPanel(options) {
1517
1529
  {
1518
1530
  className: orClasses5.swapDetails
1519
1531
  },
1520
- // Explorer stays hidden until payment completes (tx ids / settled details).
1521
- // No explorer link on the LIVE deposit row: the address has nothing on
1522
- // chain yet, and sending the payer to a third-party explorer mid-payment
1523
- // is not something this pane does. The settled/refund panes link theirs.
1524
- renderSwapCopyRow("Address", display.depositAddress, {
1525
- ...options,
1526
- selectable: true
1527
- }),
1528
- memo === void 0 ? null : renderSwapCopyRow("Memo", memo, options),
1529
- renderSwapCopyRow("Amount", display.depositAmount, { ...options, selectable: true })
1532
+ ...display.copyRows.flatMap(
1533
+ (row) => renderSwapCopyRow(
1534
+ row.label,
1535
+ row.copyValue ?? row.value,
1536
+ { ...options, selectable: row.selectable },
1537
+ row.value
1538
+ )
1539
+ )
1530
1540
  ),
1531
1541
  waitingCard(display.countdownLabel),
1532
1542
  renderSwapFeeBreakdown(display.feeBreakdown)
@@ -1740,13 +1750,13 @@ function renderSwapSupportDetails(display, options) {
1740
1750
  )
1741
1751
  );
1742
1752
  }
1743
- function renderSwapRefundReturnWarning() {
1753
+ function renderSwapRefundReturnWarning(display) {
1744
1754
  return React8.createElement(
1745
1755
  "p",
1746
1756
  {
1747
1757
  className: orClasses5.swapWarning
1748
1758
  },
1749
- checkoutLabels4.refundReturnWarning
1759
+ display.refundReturnLabel
1750
1760
  );
1751
1761
  }
1752
1762
  var REFUND_ADDRESS_DRAFT_LIMIT = 8;
@@ -1843,17 +1853,19 @@ function SwapPayloadQRCode(props) {
1843
1853
  const [svg, setSvg] = React8.useState("");
1844
1854
  const onErrorRef = React8.useRef(props.onError);
1845
1855
  onErrorRef.current = props.onError;
1856
+ const { payload, encoder } = props;
1846
1857
  React8.useEffect(() => {
1847
- let cancelled = false;
1848
- createQrPayloadSvg(props.payload, { encoder: props.encoder, width: 220 }).then((nextSvg) => {
1849
- if (!cancelled) setSvg(nextSvg);
1850
- }).catch((error) => {
1851
- if (!cancelled) onErrorRef.current?.(error);
1858
+ const controller = createQrSvgController2({
1859
+ onValue: setSvg,
1860
+ onError: (error) => onErrorRef.current?.(error),
1861
+ ...encoder === void 0 ? {} : { encoder },
1862
+ width: 220
1852
1863
  });
1864
+ controller.showPayload(payload);
1853
1865
  return () => {
1854
- cancelled = true;
1866
+ controller.stop();
1855
1867
  };
1856
- }, [props.payload, props.encoder]);
1868
+ }, [payload, encoder]);
1857
1869
  const imageSource = svg.length === 0 ? void 0 : `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
1858
1870
  return React8.createElement("img", {
1859
1871
  alt: "",
@@ -1871,9 +1883,7 @@ function PaymentWizard(props) {
1871
1883
  const [startedSwapInvoice, setStartedSwapInvoice] = React9.useState(null);
1872
1884
  const [dismissedSwapInvoiceId, setDismissedSwapInvoiceId] = React9.useState(null);
1873
1885
  const [selectedSwapAsset, setSelectedSwapAsset] = React9.useState(null);
1874
- const [selectedSwapNetworks, setSelectedSwapNetworks] = React9.useState(
1875
- {}
1876
- );
1886
+ const [selectedSwapAssetByGroup, setSelectedSwapAssetByGroup] = React9.useState({});
1877
1887
  const [selectedPickerKey, setSelectedPickerKey] = React9.useState(null);
1878
1888
  const autoSwapAttemptedRef = React9.useRef(/* @__PURE__ */ new Set());
1879
1889
  const fetcher = props.fetch ?? globalThis.fetch;
@@ -1882,16 +1892,18 @@ function PaymentWizard(props) {
1882
1892
  const session = useCheckoutSession({
1883
1893
  snapshot: () => checkout,
1884
1894
  reference: () => reference,
1885
- swapPrefix: () => props.prefix,
1886
- fetch: () => fetcher,
1887
- swapSelection: {
1888
- started: () => startedSwapInvoice ?? void 0,
1889
- setStarted: setStartedSwapInvoice,
1890
- dismissedInvoiceId: () => dismissedSwapInvoiceId,
1891
- setDismissedInvoiceId: setDismissedSwapInvoiceId,
1892
- setSelectedAsset: setSelectedSwapAsset
1895
+ swap: {
1896
+ selection: {
1897
+ started: () => startedSwapInvoice ?? void 0,
1898
+ setStarted: setStartedSwapInvoice,
1899
+ dismissedInvoiceId: () => dismissedSwapInvoiceId,
1900
+ setDismissedInvoiceId: setDismissedSwapInvoiceId,
1901
+ setSelectedAsset: setSelectedSwapAsset
1902
+ },
1903
+ prefix: () => props.prefix,
1904
+ fetch: () => fetcher,
1905
+ onStarted: (invoice) => props.onSwapStarted?.(invoice)
1893
1906
  },
1894
- onSwapStarted: (invoice) => props.onSwapStarted?.(invoice),
1895
1907
  ...props.logger === void 0 ? {} : { logger: props.logger },
1896
1908
  onError: (error) => props.onError?.(error)
1897
1909
  });
@@ -1899,9 +1911,10 @@ function PaymentWizard(props) {
1899
1911
  const clearSwapFocus = React9.useCallback(() => {
1900
1912
  setSelectedSwapAsset(null);
1901
1913
  setSelectedPickerKey(null);
1902
- setSelectedSwapNetworks({});
1914
+ setSelectedSwapAssetByGroup({});
1903
1915
  session.clearSwapStartError();
1904
- }, [session]);
1916
+ props.swapRefund?.clearSwapRefundStaging();
1917
+ }, [session, props.swapRefund]);
1905
1918
  const onSwapFocusChange = props.onSwapFocusChange;
1906
1919
  React9.useEffect(() => {
1907
1920
  onSwapFocusChange?.(selectedSwapAsset !== null);
@@ -1912,7 +1925,10 @@ function PaymentWizard(props) {
1912
1925
  return { enabled: methods.length > 0, options: methods };
1913
1926
  }, [checkout]);
1914
1927
  const currentSwapInvoice = React9.useMemo(
1915
- () => selectCurrentSwapInvoice(checkout, startedSwapInvoice, dismissedSwapInvoiceId),
1928
+ () => selectCurrentSwapInvoice(checkout, {
1929
+ started: startedSwapInvoice,
1930
+ dismissedInvoiceId: dismissedSwapInvoiceId
1931
+ }),
1916
1932
  [checkout, startedSwapInvoice, dismissedSwapInvoiceId]
1917
1933
  );
1918
1934
  const now = useTickingUnixSeconds(currentSwapInvoice !== void 0);
@@ -1925,6 +1941,7 @@ function PaymentWizard(props) {
1925
1941
  },
1926
1942
  [props.prefix, session]
1927
1943
  );
1944
+ const swapRefund = props.swapRefund;
1928
1945
  const refundSwap = React9.useCallback(
1929
1946
  async (attemptId, refundAddress, confirm) => {
1930
1947
  const prefix = props.prefix;
@@ -1932,12 +1949,17 @@ function PaymentWizard(props) {
1932
1949
  return;
1933
1950
  }
1934
1951
  try {
1935
- const invoice = await requestSwapRefund({
1952
+ const invoice = swapRefund !== void 0 ? await (confirm ? swapRefund.confirmSwapRefund : swapRefund.stageSwapRefund).call(
1953
+ swapRefund,
1954
+ { attemptId, refundAddress }
1955
+ ) : await requestSwapRefund({
1936
1956
  fetch: fetcher,
1937
1957
  prefix,
1938
1958
  reference,
1939
- invoices: [startedSwapInvoice, ...checkout?.invoices ?? []],
1940
- attemptId,
1959
+ paymentHash: resolveAttemptPaymentHash(
1960
+ [startedSwapInvoice, ...checkout?.invoices ?? []],
1961
+ attemptId
1962
+ ),
1941
1963
  refundAddress,
1942
1964
  confirm,
1943
1965
  ...props.logger === void 0 ? {} : { logger: props.logger }
@@ -1954,6 +1976,7 @@ function PaymentWizard(props) {
1954
1976
  fetcher,
1955
1977
  props.onError,
1956
1978
  props.logger,
1979
+ swapRefund,
1957
1980
  startedSwapInvoice,
1958
1981
  checkout?.invoices
1959
1982
  ]
@@ -1966,12 +1989,13 @@ function PaymentWizard(props) {
1966
1989
  );
1967
1990
  const model = createPaymentWizardModel(selection);
1968
1991
  const { wizard } = model;
1992
+ const resolveAssetUrl = props.resolveAssetUrl ?? (props.assetBaseUrl === void 0 || props.assetBaseUrl.trim() === "" ? void 0 : createAssetBaseUrlResolver(props.assetBaseUrl));
1969
1993
  const routeAssetDisplays = createWizardRouteAssetDisplays(model.routeAssets, {
1970
1994
  selectedRoute: model.selectedRoute,
1971
- ...props.resolveAssetUrl === void 0 ? {} : { resolveAssetUrl: props.resolveAssetUrl }
1995
+ ...resolveAssetUrl === void 0 ? {} : { resolveAssetUrl }
1972
1996
  });
1973
1997
  const routeDisplays = createWizardRouteDisplays(wizard.routes, {
1974
- ...props.resolveAssetUrl === void 0 ? {} : { resolveAssetUrl: props.resolveAssetUrl }
1998
+ ...resolveAssetUrl === void 0 ? {} : { resolveAssetUrl }
1975
1999
  });
1976
2000
  const showRoutePicker = routeAssetDisplays.length > 0 && (model.selectedRoute === null || routeDisplays.length === 0);
1977
2001
  const activeTutorialProvider = activeTutorial === null ? void 0 : routeDisplays.flatMap((route) => route.providers).find((provider) => provider.id === activeTutorial.providerId);
@@ -2024,6 +2048,7 @@ function PaymentWizard(props) {
2024
2048
  logger: props.logger,
2025
2049
  onError: props.onError,
2026
2050
  onRefund: refundSwap,
2051
+ ...props.resumable === void 0 ? {} : { resumable: props.resumable },
2027
2052
  onBackToLightning: () => {
2028
2053
  setDismissedSwapInvoiceId(activeSwapForAsset.invoice_id);
2029
2054
  clearSwapFocus();
@@ -2047,21 +2072,37 @@ function PaymentWizard(props) {
2047
2072
  checkout: checkout ?? void 0,
2048
2073
  selectedPickerKey,
2049
2074
  startingAsset: swapStartingAsset,
2050
- selectedSwapNetworks,
2075
+ selectedSwapAssetByGroup,
2076
+ // One resolver decides what a tile click MEANS, so the "ask the
2077
+ // network question only when it is a real question" rule is not
2078
+ // re-derived here: a single-network group comes back as start_swap
2079
+ // and the wizard starts it instead of opening a one-answer step.
2051
2080
  onSelectPicker: (key, previousKey) => {
2081
+ const resolved = resolveWizardSelection({
2082
+ pickerKey: key,
2083
+ previousKey,
2084
+ entries: buildMethodGridEntries(paymentMethods, swapAssetOptions),
2085
+ selectedAssetByGroup: selectedSwapAssetByGroup
2086
+ });
2087
+ if (resolved.kind === "none") return;
2088
+ if (resolved.kind === "select_method") {
2089
+ setSelectedPickerKey(null);
2090
+ updateWizardSelection(
2091
+ (controller) => controller.selectMethod(resolved.methodId)
2092
+ );
2093
+ if (resolved.methodId === "bitcoin") void props.onRequestLightning?.();
2094
+ return;
2095
+ }
2096
+ if (resolved.kind === "start_swap") {
2097
+ setSelectedPickerKey(null);
2098
+ selectSwapAsset(resolved.payInAsset);
2099
+ return;
2100
+ }
2052
2101
  setSelectedPickerKey(key);
2053
- const entries = buildMethodGridEntries(paymentMethods, swapAssetOptions);
2054
- setSelectedSwapNetworks(
2055
- (current) => updateSelectedSwapNetworks({
2056
- entries,
2057
- nextKey: key,
2058
- previousKey,
2059
- selectedNetworks: current
2060
- })
2061
- );
2102
+ setSelectedSwapAssetByGroup(resolved.selectedAssetByGroup);
2062
2103
  },
2063
2104
  onSelectNetwork: (groupKey, payInAsset) => {
2064
- setSelectedSwapNetworks((current) => ({
2105
+ setSelectedSwapAssetByGroup((current) => ({
2065
2106
  ...current,
2066
2107
  [groupKey]: payInAsset
2067
2108
  }));
@@ -2075,7 +2116,7 @@ function PaymentWizard(props) {
2075
2116
  }
2076
2117
  },
2077
2118
  onContinueSwap: selectSwapAsset,
2078
- ...props.resolveAssetUrl === void 0 ? {} : { resolveAssetUrl: props.resolveAssetUrl }
2119
+ ...resolveAssetUrl === void 0 ? {} : { resolveAssetUrl }
2079
2120
  }) : null,
2080
2121
  selection.selectedMethod === null ? null : React9.createElement(
2081
2122
  "div",
@@ -2144,6 +2185,7 @@ function PaymentWizard(props) {
2144
2185
  logger: props.logger,
2145
2186
  onError: props.onError,
2146
2187
  onRefund: refundSwap,
2188
+ ...props.resumable === void 0 ? {} : { resumable: props.resumable },
2147
2189
  onBackToLightning: () => {
2148
2190
  setDismissedSwapInvoiceId(activeSwapForRoute.invoice_id);
2149
2191
  clearSwapFocus();
@@ -2242,23 +2284,16 @@ function PaymentWizard(props) {
2242
2284
  );
2243
2285
  }
2244
2286
  function renderCompactPaymentMethodSelector(options) {
2245
- const entries = buildMethodGridEntries(paymentMethods, options.swapAssetOptions);
2287
+ const display = createMethodGridDisplay({
2288
+ entries: buildMethodGridEntries(paymentMethods, options.swapAssetOptions),
2289
+ selectedPickerKey: options.selectedPickerKey,
2290
+ selectedAssetByGroup: options.selectedSwapAssetByGroup,
2291
+ startingAsset: options.startingAsset,
2292
+ ...options.checkout === void 0 ? {} : { checkout: options.checkout }
2293
+ });
2246
2294
  const currenciesLoading = options.currenciesLoading === true && options.swapAssetOptions.length === 0;
2247
- const selectedKey = options.selectedPickerKey;
2248
- const selectedGroup = findSwapGridGroup(entries, selectedKey);
2249
- const networkRequired = selectedGroup !== void 0 && selectedGroup.options.length > 1;
2250
- const selectedGroupKey = selectedGroup?.label.trim().toUpperCase();
2251
- const selectedNetworkAsset = selectedGroupKey === void 0 ? void 0 : options.selectedSwapNetworks[selectedGroupKey];
2252
- const selectedNetworkOption = selectedGroup === void 0 || selectedNetworkAsset === void 0 ? void 0 : selectedGroup.options.find((option) => option.pay_in_asset === selectedNetworkAsset);
2253
- const continueTarget = selectedNetworkOption !== void 0 ? {
2254
- payInAsset: selectedNetworkOption.pay_in_asset,
2255
- disabled: selectedNetworkOption.available === false,
2256
- limitMessage: swapOptionLimitMessage2(selectedNetworkOption, options.checkout)
2257
- } : null;
2258
- const startingAsset = options.startingAsset;
2259
- const gridBusy = startingAsset !== null;
2260
- const continueStarting = continueTarget !== null && continueTarget.payInAsset === startingAsset;
2261
- const canContinue = continueTarget !== null && !continueTarget.disabled && selectedNetworkOption !== void 0 && !gridBusy;
2295
+ const { gridBusy, networkRequired, selectedGroup, continueTarget, canContinue } = display;
2296
+ const continueStarting = continueTarget?.starting === true;
2262
2297
  const continueButton = (className) => React9.createElement(
2263
2298
  "button",
2264
2299
  {
@@ -2268,7 +2303,7 @@ function renderCompactPaymentMethodSelector(options) {
2268
2303
  "aria-disabled": canContinue ? void 0 : "true",
2269
2304
  "aria-busy": continueStarting ? "true" : void 0,
2270
2305
  onClick: !canContinue ? void 0 : () => {
2271
- if (continueTarget === null) return;
2306
+ if (continueTarget === void 0) return;
2272
2307
  options.onContinueSwap(continueTarget.payInAsset);
2273
2308
  }
2274
2309
  },
@@ -2280,14 +2315,10 @@ function renderCompactPaymentMethodSelector(options) {
2280
2315
  "aria-hidden": "true"
2281
2316
  }),
2282
2317
  checkoutLabels5.preparingPayment
2283
- ) : continueTarget?.disabled && continueTarget.limitMessage !== void 0 ? continueTarget.limitMessage : checkoutLabels5.continue
2318
+ ) : continueTarget?.label ?? checkoutLabels5.continue
2284
2319
  );
2285
2320
  const renderNetworkSelector = (group, mobile) => {
2286
- const accent = paymentAccentId(group.label);
2287
- const groupKey = group.label.trim().toUpperCase();
2288
- const selectedAsset = options.selectedSwapNetworks[groupKey];
2289
- const selectedOption = selectedAsset === void 0 ? void 0 : group.options.find((option) => option.pay_in_asset === selectedAsset);
2290
- const { panelId, headingId } = wizardNetworkGroupIds(groupKey);
2321
+ const { accent, groupKey, panelId, headingId, selectedOption } = group;
2291
2322
  return React9.createElement(
2292
2323
  "div",
2293
2324
  {
@@ -2308,7 +2339,7 @@ function renderCompactPaymentMethodSelector(options) {
2308
2339
  id: headingId,
2309
2340
  className: orClasses6.methodNetworkHeading
2310
2341
  },
2311
- formatChooseNetworkHeading(group.label)
2342
+ group.heading
2312
2343
  ),
2313
2344
  React9.createElement(
2314
2345
  "p",
@@ -2422,23 +2453,22 @@ function renderCompactPaymentMethodSelector(options) {
2422
2453
  "aria-label": checkoutLabels5.paymentMethod,
2423
2454
  className: orClasses6.methodGrid
2424
2455
  },
2425
- ...entries.map((entry) => {
2456
+ ...display.entries.map((entry) => {
2426
2457
  if (entry.kind === "method") {
2427
2458
  const method = entry.method;
2428
- const accent2 = paymentAccentId(method.id);
2429
2459
  return React9.createElement(
2430
2460
  "button",
2431
2461
  {
2432
2462
  key: method.id,
2433
2463
  type: "button",
2434
2464
  className: assetButtonClasses({
2435
- accent: accent2,
2465
+ accent: entry.accent,
2436
2466
  selected: false,
2437
- disabled: gridBusy
2467
+ disabled: entry.disabled
2438
2468
  }),
2439
- disabled: gridBusy,
2440
- "aria-disabled": gridBusy ? "true" : void 0,
2441
- onClick: gridBusy ? void 0 : () => options.onContinueMethod(method.id)
2469
+ disabled: entry.disabled,
2470
+ "aria-disabled": entry.disabled ? "true" : void 0,
2471
+ onClick: entry.disabled ? void 0 : () => options.onContinueMethod(method.id)
2442
2472
  },
2443
2473
  React9.createElement(
2444
2474
  "span",
@@ -2457,21 +2487,18 @@ function renderCompactPaymentMethodSelector(options) {
2457
2487
  );
2458
2488
  }
2459
2489
  const group = entry.group;
2460
- const groupKey = group.label.trim().toUpperCase();
2461
- const pickerKey = swapPickerKey(group.label);
2462
- const selected = selectedKey === pickerKey;
2463
- const multiNetwork = group.options.length > 1;
2464
- const displayOption = group.options.find((option) => option.available !== false) ?? group.options[0];
2465
- if (displayOption === void 0) return null;
2466
- const selectedAsset = options.selectedSwapNetworks[groupKey];
2467
- const selectedOption = selectedAsset === void 0 ? void 0 : group.options.find((option) => option.pay_in_asset === selectedAsset);
2468
- const activeOption = selectedOption ?? displayOption;
2469
- const starting = group.options.some((option) => option.pay_in_asset === startingAsset);
2470
- const disabled = group.options.every((option) => option.available === false);
2471
- const accent = paymentAccentId(group.label);
2472
- const limitOption = disabled ? swapGroupLimitOption(group.options) ?? activeOption : activeOption;
2473
- const limitMessage = swapOptionLimitMessage2(limitOption, options.checkout);
2474
- const { panelId } = wizardNetworkGroupIds(groupKey);
2490
+ const {
2491
+ pickerKey,
2492
+ selected,
2493
+ multiNetwork,
2494
+ displayOption,
2495
+ selectedOption,
2496
+ starting,
2497
+ disabled,
2498
+ accent,
2499
+ limitMessage,
2500
+ panelId
2501
+ } = group;
2475
2502
  return React9.createElement(
2476
2503
  "div",
2477
2504
  { key: pickerKey, className: orClasses6.methodTile },
@@ -2490,7 +2517,14 @@ function renderCompactPaymentMethodSelector(options) {
2490
2517
  selected: starting || multiNetwork && selected,
2491
2518
  disabled: disabled || gridBusy && !starting
2492
2519
  }),
2493
- onClick: disabled || gridBusy ? void 0 : multiNetwork ? () => options.onSelectPicker(pickerKey, selectedKey) : () => options.onContinueSwap(displayOption.pay_in_asset)
2520
+ onClick: disabled || gridBusy ? void 0 : (
2521
+ // `needsNetworkStep` / `startPayInAsset` are the
2522
+ // options.length > 1 rule as DATA, so neither renderer
2523
+ // re-derives which of the two a tile click means.
2524
+ group.needsNetworkStep ? () => options.onSelectPicker(pickerKey, options.selectedPickerKey) : () => options.onContinueSwap(
2525
+ group.startPayInAsset ?? displayOption.pay_in_asset
2526
+ )
2527
+ )
2494
2528
  },
2495
2529
  React9.createElement(
2496
2530
  "span",
@@ -2647,13 +2681,13 @@ function renderWizardBreadcrumbs(options) {
2647
2681
  function swapOptionsForRoute(routeKey, options) {
2648
2682
  return options.filter((option) => swapAssetMatchesRoute(routeKey, option.pay_in_asset));
2649
2683
  }
2650
- function selectCurrentSwapInvoice(checkout, local, dismissedInvoiceId) {
2651
- const fromCheckout = checkout?.invoices.find(
2652
- (invoice) => invoice.rail === "swap" && invoice.swap !== void 0 && invoice.invoice_id !== dismissedInvoiceId
2653
- );
2654
- if (local === null || local.invoice_id === dismissedInvoiceId) return fromCheckout;
2655
- const matched = checkout?.invoices.find((invoice) => invoice.invoice_id === local.invoice_id) ?? local;
2656
- return overlaySwapRefundStaging(matched, local);
2684
+ function resolveAttemptPaymentHash(invoices, attemptId) {
2685
+ for (const invoice of invoices) {
2686
+ if (invoice == null) continue;
2687
+ if ((invoice.swap?.attempt_id ?? invoice.invoice_id) !== attemptId) continue;
2688
+ if (invoice.payment_hash !== void 0) return invoice.payment_hash;
2689
+ }
2690
+ throw new Error(`No swap attempt ${attemptId} in this checkout.`);
2657
2691
  }
2658
2692
  function renderRoutePicker(options) {
2659
2693
  return React9.createElement(
@@ -2868,9 +2902,13 @@ function CheckoutView(props) {
2868
2902
  prefix = OPENRECEIVE_DEFAULT_PREFIX,
2869
2903
  metadata: _metadata,
2870
2904
  createFetch: _createFetch,
2871
- syncUrl: _syncUrl,
2905
+ // Not `_`-prefixed like its neighbours: the view reads it, to infer
2906
+ // `resumable` for the refund screen. Still destructured out so it never
2907
+ // reaches the rendered <section>.
2908
+ syncUrl,
2872
2909
  resumePathPrefix: _resumePathPrefix,
2873
- routeReference: _routeReference,
2910
+ routeReference,
2911
+ resumable,
2874
2912
  onRequestLightning,
2875
2913
  onSwapStarted,
2876
2914
  mintingLightning = false,
@@ -2895,6 +2933,7 @@ function CheckoutView(props) {
2895
2933
  classNames,
2896
2934
  children,
2897
2935
  resolveAssetUrl,
2936
+ assetBaseUrl,
2898
2937
  className,
2899
2938
  ...sectionProps
2900
2939
  } = props;
@@ -2965,6 +3004,21 @@ function CheckoutView(props) {
2965
3004
  ...ownsTheme ? theme.attributes : {}
2966
3005
  },
2967
3006
  customChildren === void 0 ? [
3007
+ // Above the amount and OUTSIDE the Lightning pane, which drops out on
3008
+ // the swap deposit panel, on expiry and on the receipt — the payer
3009
+ // needs to know what they are buying on all four screens.
3010
+ checkoutModel.checkout.description === void 0 || checkoutModel.checkout.description === "" ? null : React10.createElement(
3011
+ "p",
3012
+ {
3013
+ key: "order-description",
3014
+ className: joinClassNames(
3015
+ orClasses7.orderDescription,
3016
+ classNames?.orderDescription
3017
+ ),
3018
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES3.orderDescription]: ""
3019
+ },
3020
+ checkoutModel.checkout.description
3021
+ ),
2968
3022
  ownsTheme ? React10.createElement(ThemeToggle, {
2969
3023
  key: "theme",
2970
3024
  className: classNames?.themeToggle,
@@ -3135,7 +3189,16 @@ function CheckoutView(props) {
3135
3189
  onCopy,
3136
3190
  onRequestLightning,
3137
3191
  onSwapStarted,
3138
- resolveAssetUrl
3192
+ resolveAssetUrl,
3193
+ assetBaseUrl,
3194
+ // Whether a payer who closes the tab has a URL to come back
3195
+ // to. Explicit wins — only the host knows about a per-order
3196
+ // route of its own; otherwise infer it from the two props that
3197
+ // put the reference in the URL.
3198
+ resumable: resumable ?? (syncUrl === true || routeReference !== void 0),
3199
+ // The engine's staged refund address rides the controller this
3200
+ // model owns, so a poll cannot wipe a review in progress.
3201
+ swapRefund: checkoutModel
3139
3202
  }) : null
3140
3203
  ] : customChildren
3141
3204
  );
@@ -3168,7 +3231,6 @@ export {
3168
3231
  CopyInvoiceButton,
3169
3232
  InvoiceSummary,
3170
3233
  OpenWalletButton,
3171
- PaymentData,
3172
3234
  PaymentState,
3173
3235
  PaymentWizard,
3174
3236
  QRCode,