@openreceive/react 0.1.1 → 0.2.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
@@ -1,919 +1,3171 @@
1
+ // src/checkout.ts
2
+ import {
3
+ createLightningInvoiceDecodeUrl as createLightningInvoiceDecodeUrl2,
4
+ enterCheckoutResumePath,
5
+ mergeAttemptIntoCheckout,
6
+ mergeAttemptIntoSnapshot,
7
+ OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES as OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES3,
8
+ OPENRECEIVE_DEFAULT_PREFIX,
9
+ checkoutLabels as checkoutLabels6,
10
+ orClasses as orClasses7,
11
+ prepareCheckout,
12
+ requestCheckout,
13
+ validateCheckoutProps
14
+ } from "@openreceive/browser/headless";
15
+ import * as React10 from "react";
16
+
17
+ // src/checkout-session.ts
18
+ import {
19
+ createCheckoutSession
20
+ } from "@openreceive/browser/headless";
1
21
  import * as React from "react";
2
- import { OPENRECEIVE_COPY_FEEDBACK_MS, OPENRECEIVE_COUNTRY_STORAGE_KEY, OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES, OPENRECEIVE_THEME_STORAGE_KEY, copyInvoice as copyInvoiceHelper, createCheckoutController, createCheckoutDisplayModel, createCheckoutSnapshotFromDisplayData, createCheckoutStatusModel, createCheckoutStateFromDisplayData, createOpenReceivePaymentWizardModel, createOpenReceivePaymentWizardController, createCheckoutProviderCopyEvent, createOpenReceiveWizardRouteAssetDisplays, createOpenReceiveWizardRouteDisplays, createQrSvg, createOpenReceiveThemeModel, createOpenReceiveTransientFeedbackController, getOpenReceiveDefaultCountryCode, getOpenReceivePaymentMethodIcon, getOpenReceiveWizardEmptyMessage, openReceiveCheckoutLabels, openReceivePaymentMethods, openWallet as openWalletHelper, readOpenReceiveThemePreference, status as deriveStatus, writeOpenReceiveThemePreference } from "@openreceive/browser/internal";
3
- const DEFAULT_LOOKUP_URL = "/openreceive/v1/invoices/lookup";
4
- function useOpenReceiveTransientValue(resetValue, delayMs = OPENRECEIVE_COPY_FEEDBACK_MS) {
5
- const [value, setValue] = React.useState(resetValue);
6
- const controller = React.useRef(null);
7
- React.useEffect(() => {
8
- controller.current?.clear();
9
- controller.current = createOpenReceiveTransientFeedbackController({
10
- resetValue,
11
- delayMs,
12
- onValue: setValue
13
- });
14
- return () => controller.current?.clear();
15
- }, [resetValue, delayMs]);
16
- const showValue = React.useCallback((nextValue) => {
17
- if (controller.current === null) {
18
- setValue(nextValue);
19
- return;
20
- }
21
- controller.current.show(nextValue);
22
- }, []);
23
- return [value, showValue];
24
- }
25
- function toCheckoutDisplayData(invoice) {
26
- return {
27
- invoice_id: invoice.invoice_id,
28
- invoice: invoice.invoice,
29
- ...(invoice.payment_hash === undefined ? {} : { payment_hash: invoice.payment_hash }),
30
- ...(invoice.amount_msats === undefined ? {} : { amount_msats: invoice.amount_msats }),
31
- ...(invoice.fiat_quote === undefined ? {} : { fiat_quote: invoice.fiat_quote }),
32
- ...(invoice.transaction_state === undefined
33
- ? {}
34
- : { transaction_state: invoice.transaction_state }),
35
- ...(invoice.workflow_state === undefined ? {} : { workflow_state: invoice.workflow_state }),
36
- ...(invoice.expires_at === undefined ? {} : { expires_at: invoice.expires_at }),
37
- ...(invoice.settled_at === undefined ? {} : { settled_at: invoice.settled_at })
38
- };
22
+ function useCheckoutSession(options) {
23
+ const [, rerender] = React.useReducer((count) => count + 1, 0);
24
+ const optionsRef = React.useRef(options);
25
+ optionsRef.current = options;
26
+ const sessionRef = React.useRef(void 0);
27
+ if (sessionRef.current === void 0) {
28
+ sessionRef.current = createCheckoutSession({
29
+ snapshot: () => optionsRef.current.snapshot(),
30
+ reference: () => optionsRef.current.reference(),
31
+ requestCheckout: (reference) => optionsRef.current.requestCheckout?.(reference),
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)
41
+ },
42
+ onSwapStarted: (invoice) => optionsRef.current.onSwapStarted?.(invoice),
43
+ get logger() {
44
+ return optionsRef.current.logger;
45
+ },
46
+ onError: (error) => optionsRef.current.onError(error),
47
+ onChange: rerender
48
+ });
49
+ }
50
+ return sessionRef.current;
39
51
  }
40
- function toCheckoutViewModel(display, currentStatus) {
41
- return {
42
- invoice_id: display.invoice_id,
43
- invoice: display.invoice,
44
- ...(display.payment_hash === undefined ? {} : { payment_hash: display.payment_hash }),
45
- ...(display.amount_msats === undefined ? {} : { amount_msats: display.amount_msats }),
46
- ...(display.fiat_quote === undefined ? {} : { fiat_quote: display.fiat_quote }),
47
- ...(display.expires_at === undefined ? {} : { expires_at: display.expires_at }),
48
- ...(display.settled_at === undefined ? {} : { settled_at: display.settled_at }),
49
- lightningUri: display.lightningUri,
50
- ...(display.amountLabel === undefined ? {} : { amountLabel: display.amountLabel }),
51
- ...(display.fiatLabel === undefined ? {} : { fiatLabel: display.fiatLabel }),
52
- ...(display.paymentHashLabel === undefined ? {} : { paymentHashLabel: display.paymentHashLabel }),
53
- status: currentStatus
54
- };
52
+
53
+ // src/components.ts
54
+ import * as React3 from "react";
55
+ import {
56
+ OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES,
57
+ copyInvoice as copyInvoiceHelper,
58
+ createCheckoutStatusModel,
59
+ createPaymentDataEntries,
60
+ createQrSvg,
61
+ formatAmountCaption,
62
+ checkoutLabels,
63
+ openWallet as openWalletHelper,
64
+ orClasses
65
+ } from "@openreceive/browser/headless";
66
+
67
+ // src/hooks.ts
68
+ import * as React2 from "react";
69
+ import {
70
+ OPENRECEIVE_COPY_FEEDBACK_MS,
71
+ createTickingValueController,
72
+ createTransientFeedbackController
73
+ } from "@openreceive/browser/headless";
74
+ function useTransientValue(resetValue, delayMs = OPENRECEIVE_COPY_FEEDBACK_MS) {
75
+ const [value, setValue] = React2.useState(resetValue);
76
+ const controller = React2.useRef(null);
77
+ React2.useEffect(() => {
78
+ controller.current?.clear();
79
+ controller.current = createTransientFeedbackController({
80
+ resetValue,
81
+ delayMs,
82
+ onValue: setValue
83
+ });
84
+ return () => controller.current?.clear();
85
+ }, [resetValue, delayMs]);
86
+ const showValue = React2.useCallback((nextValue) => {
87
+ if (controller.current === null) {
88
+ setValue(nextValue);
89
+ return;
90
+ }
91
+ controller.current.show(nextValue);
92
+ }, []);
93
+ return [value, showValue];
55
94
  }
56
- export function createCheckoutViewModel(data) {
57
- return toCheckoutViewModel(createCheckoutDisplayModel(toCheckoutDisplayData(data.invoice)), deriveStatus(data.invoice));
58
- }
59
- function resolveCheckoutLookupUrl(options) {
60
- if (options.polling === false || options.lookupUrl === false)
61
- return undefined;
62
- return options.lookupUrl ?? DEFAULT_LOOKUP_URL;
63
- }
64
- export function useCheckout(options) {
65
- const [copied, showCopied] = useOpenReceiveTransientValue(false);
66
- const displayData = React.useMemo(() => toCheckoutDisplayData(options.invoice), [options.invoice]);
67
- const model = React.useMemo(() => toCheckoutViewModel(createCheckoutDisplayModel(displayData), deriveStatus(options.invoice)), [
68
- displayData,
69
- options.invoice
70
- ]);
71
- const snapshot = React.useMemo(() => createCheckoutSnapshotFromDisplayData(displayData), [
72
- displayData
73
- ]);
74
- const [state, setState] = React.useState(() => createCheckoutStateFromDisplayData(displayData, {
75
- logger: options.logger
76
- }));
77
- const controllerRef = React.useRef(null);
78
- const onStateRef = React.useRef(options.onState);
79
- onStateRef.current = options.onState;
80
- const onPaidRef = React.useRef(options.onPaid);
81
- onPaidRef.current = options.onPaid;
82
- const paidAnnouncementRef = React.useRef({
83
- invoiceId: snapshot.invoice_id,
84
- fired: false
95
+ function useTickingUnixSeconds(active) {
96
+ const [now, setNow] = React2.useState(void 0);
97
+ React2.useEffect(() => {
98
+ if (!active) {
99
+ setNow(void 0);
100
+ return;
101
+ }
102
+ const controller = createTickingValueController({
103
+ onValue: setNow
85
104
  });
86
- const logContext = React.useMemo(() => getCheckoutLogContext(displayData), [
87
- displayData
88
- ]);
89
- React.useEffect(() => {
90
- const lookupInvoice = options.polling === false ? undefined : options.lookupInvoice;
91
- const lookupUrl = resolveCheckoutLookupUrl(options);
92
- const controller = createCheckoutController({
93
- snapshot,
94
- ...(lookupInvoice === undefined ? {} : { lookupInvoice }),
95
- ...(lookupUrl === undefined ? {} : { lookupUrl }),
96
- ...(options.refreshInvoice === undefined ? {} : { refreshInvoice: options.refreshInvoice }),
97
- ...(options.refreshUrl === undefined ? {} : { refreshUrl: options.refreshUrl }),
98
- ...(options.refreshHeaders === undefined ? {} : { refreshHeaders: options.refreshHeaders }),
99
- ...(options.refreshIdempotencyKey === undefined
100
- ? {}
101
- : { refreshIdempotencyKey: options.refreshIdempotencyKey }),
102
- ...(options.refreshReason === undefined ? {} : { refreshReason: options.refreshReason }),
103
- pollIntervalMs: options.pollIntervalMs,
104
- logger: options.logger,
105
- onError: options.onError,
106
- clipboard: options.clipboard,
107
- open: options.open,
108
- onState: (nextState) => {
109
- setState(nextState);
110
- onStateRef.current?.(nextState);
111
- }
112
- });
113
- controllerRef.current = controller;
114
- controller.start();
115
- return () => {
116
- controller.stop();
117
- if (controllerRef.current === controller)
118
- controllerRef.current = null;
119
- };
120
- }, [
121
- snapshot,
122
- options.lookupInvoice,
123
- options.lookupUrl,
124
- options.polling,
125
- options.refreshInvoice,
126
- options.refreshUrl,
127
- options.refreshHeaders,
128
- options.refreshIdempotencyKey,
129
- options.refreshReason,
130
- options.pollIntervalMs,
131
- options.logger,
132
- options.onError,
133
- options.clipboard,
134
- options.open
135
- ]);
136
- const publicStatus = deriveStatus(state);
137
- const richStatus = createCheckoutStatusModel(state);
138
- React.useEffect(() => {
139
- const announced = paidAnnouncementRef.current;
140
- if (announced.invoiceId !== snapshot.invoice_id) {
141
- paidAnnouncementRef.current = {
142
- invoiceId: snapshot.invoice_id,
143
- fired: false
144
- };
145
- }
146
- }, [snapshot.invoice_id]);
147
- React.useEffect(() => {
148
- const announced = paidAnnouncementRef.current;
149
- if (publicStatus !== "paid" || announced.fired)
150
- return;
151
- paidAnnouncementRef.current = {
152
- invoiceId: snapshot.invoice_id,
153
- fired: true
154
- };
155
- // UI hint only; server-side fulfillment must use the backend onPaid hook.
156
- onPaidRef.current?.();
157
- }, [publicStatus, snapshot.invoice_id]);
158
- const copyInvoice = React.useCallback(async () => {
159
- try {
160
- const controller = controllerRef.current;
161
- if (controller === null) {
162
- await copyInvoiceHelper({
163
- invoice: displayData.invoice,
164
- clipboard: options.clipboard,
165
- logger: options.logger,
166
- logContext
167
- });
168
- }
169
- else {
170
- await controller.copyInvoice();
171
- }
172
- showCopied(true);
173
- }
174
- catch (error) {
175
- options.onError?.(error);
176
- throw error;
177
- }
178
- }, [logContext, displayData.invoice, options.clipboard, options.logger, options.onError, showCopied]);
179
- const openWallet = React.useCallback(() => {
180
- try {
181
- const controller = controllerRef.current;
182
- return controller === null
183
- ? openWalletHelper({
184
- invoice: displayData.invoice,
185
- open: options.open,
186
- logger: options.logger,
187
- logContext
188
- })
189
- : controller.openWallet();
190
- }
191
- catch (error) {
192
- options.onError?.(error);
193
- throw error;
194
- }
195
- }, [logContext, displayData.invoice, options.open, options.logger, options.onError]);
196
- const reloadState = React.useCallback(async () => {
197
- try {
198
- const next = await controllerRef.current?.reloadState();
199
- if (next !== undefined)
200
- setState(next);
201
- }
202
- catch (error) {
203
- options.onError?.(error);
204
- throw error;
205
- }
206
- }, [options.onError]);
207
- const retry = React.useCallback(async () => {
208
- try {
209
- const next = await controllerRef.current?.retry();
210
- if (next !== undefined)
211
- setState(next);
212
- }
213
- catch (error) {
214
- options.onError?.(error);
215
- throw error;
216
- }
217
- }, [options.onError]);
218
- const refreshExpiredInvoice = React.useCallback(async () => {
219
- try {
220
- const next = await controllerRef.current?.refreshExpiredInvoice();
221
- if (next !== undefined)
222
- setState(next);
223
- }
224
- catch (error) {
225
- options.onError?.(error);
226
- throw error;
227
- }
228
- }, [options.onError]);
229
- const cancel = React.useCallback(() => {
230
- const next = controllerRef.current?.cancel();
231
- if (next !== undefined)
232
- setState(next);
233
- }, []);
234
- return {
235
- ...model,
236
- copied,
237
- status: publicStatus,
238
- expiresInSeconds: richStatus.expiresInSeconds,
239
- countdownLabel: richStatus.countdownLabel,
240
- countdownPrefix: richStatus.countdownPrefix,
241
- statusTitle: richStatus.title,
242
- statusDetail: richStatus.detail,
243
- waiting: richStatus.waiting,
244
- reloadState,
245
- retry,
246
- refreshExpiredInvoice,
247
- cancel,
248
- copyInvoice,
249
- openWallet
250
- };
105
+ controller.start();
106
+ return () => controller.stop();
107
+ }, [active]);
108
+ return now;
251
109
  }
252
- const CheckoutContext = React.createContext(null);
253
- export function useCheckoutContext() {
254
- const checkout = React.useContext(CheckoutContext);
255
- if (checkout === null) {
256
- throw new Error("useCheckoutContext must be used within CheckoutProvider.");
110
+
111
+ // src/utils.ts
112
+ function joinClassNames(...values) {
113
+ const joined = values.filter(Boolean).join(" ");
114
+ return joined === "" ? void 0 : joined;
115
+ }
116
+ function selectAllInputHandlers() {
117
+ const selectAll = (input) => {
118
+ input.select();
119
+ };
120
+ return {
121
+ onFocus: (event) => {
122
+ selectAll(event.currentTarget);
123
+ },
124
+ onClick: (event) => {
125
+ selectAll(event.currentTarget);
126
+ },
127
+ onMouseUp: (event) => {
128
+ event.preventDefault();
129
+ },
130
+ onSelect: (event) => {
131
+ const input = event.currentTarget;
132
+ if (input.selectionStart !== 0 || input.selectionEnd !== input.value.length) {
133
+ input.setSelectionRange(0, input.value.length);
134
+ }
257
135
  }
258
- return checkout;
259
- }
260
- export function CheckoutProvider(props) {
261
- const { children, ...options } = props;
262
- const checkout = useCheckout(options);
263
- const content = typeof children === "function" ? children(checkout) : children;
264
- return React.createElement(CheckoutContext.Provider, { value: checkout }, content);
265
- }
266
- export function QRCode(props) {
267
- const { invoice, encoder, width = 256, onError, ...divProps } = props;
268
- const [svg, setSvg] = React.useState("");
269
- React.useEffect(() => {
270
- let cancelled = false;
271
- createQrSvg(invoice, { encoder, width })
272
- .then((nextSvg) => {
273
- if (!cancelled)
274
- setSvg(nextSvg);
275
- })
276
- .catch((error) => {
277
- if (!cancelled)
278
- onError?.(error);
279
- });
280
- return () => {
281
- cancelled = true;
282
- };
283
- }, [invoice, encoder, width, onError]);
284
- return React.createElement("div", {
285
- ...divProps,
286
- [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.qr]: "",
287
- dangerouslySetInnerHTML: {
288
- __html: svg
289
- }
136
+ };
137
+ }
138
+ async function copyText(text, clipboard) {
139
+ const target = clipboard ?? globalThis.navigator?.clipboard;
140
+ if (target === void 0) throw new Error("Clipboard API is unavailable.");
141
+ await target.writeText(text);
142
+ }
143
+ function getCheckoutLogContext(data) {
144
+ return {
145
+ ...data.invoice_id === void 0 ? {} : { invoice_id: data.invoice_id },
146
+ ...data.payment_hash === void 0 ? {} : { payment_hash: data.payment_hash },
147
+ ...data.amount_msats === void 0 ? {} : { amount_msats: data.amount_msats },
148
+ ...data.transaction_state === void 0 ? {} : { transaction_state: data.transaction_state },
149
+ ...data.workflow_state === void 0 ? {} : { workflow_state: data.workflow_state }
150
+ };
151
+ }
152
+
153
+ // src/components.ts
154
+ function ClipboardIcon() {
155
+ return React3.createElement(
156
+ "svg",
157
+ {
158
+ className: orClasses.copyIcon,
159
+ width: 16,
160
+ height: 16,
161
+ viewBox: "0 0 16 16",
162
+ fill: "none",
163
+ "aria-hidden": "true",
164
+ focusable: "false"
165
+ },
166
+ React3.createElement("rect", {
167
+ x: 5,
168
+ y: 5,
169
+ width: 8,
170
+ height: 9,
171
+ rx: 1.5,
172
+ stroke: "currentColor",
173
+ strokeWidth: 1.5
174
+ }),
175
+ React3.createElement("path", {
176
+ d: "M3.5 11V3.5A1.5 1.5 0 0 1 5 2h5.5",
177
+ stroke: "currentColor",
178
+ strokeWidth: 1.5,
179
+ strokeLinecap: "round"
180
+ })
181
+ );
182
+ }
183
+ function QRCode(props) {
184
+ const { invoice, encoder, width = 256, onError, ...divProps } = props;
185
+ const [svg, setSvg] = React3.useState("");
186
+ const onErrorRef = React3.useRef(onError);
187
+ onErrorRef.current = onError;
188
+ 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);
290
194
  });
195
+ return () => {
196
+ cancelled = true;
197
+ };
198
+ }, [invoice, encoder, width]);
199
+ return React3.createElement("div", {
200
+ ...divProps,
201
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.qr]: "",
202
+ dangerouslySetInnerHTML: {
203
+ __html: svg
204
+ }
205
+ });
291
206
  }
292
- export function CopyInvoiceButton(props) {
293
- const { invoice, copyInvoice, clipboard, logger, onCopied, onError, onClick, copiedLabel = openReceiveCheckoutLabels.copied, ButtonComponent = "button", children, type = "button", ...buttonProps } = props;
294
- const [copied, showCopied] = useOpenReceiveTransientValue(false);
295
- return React.createElement(ButtonComponent, {
296
- ...buttonProps,
297
- type,
298
- onClick: async (event) => {
299
- onClick?.(event);
300
- if (event.defaultPrevented)
301
- return;
302
- try {
303
- if (copyInvoice === undefined) {
304
- await copyInvoiceHelper({ invoice, clipboard, logger });
305
- }
306
- else {
307
- await copyInvoice();
308
- }
309
- showCopied(true);
310
- onCopied?.();
311
- }
312
- catch (error) {
313
- onError?.(error);
314
- }
207
+ function CopyInvoiceButton(props) {
208
+ const {
209
+ invoice,
210
+ copyInvoice,
211
+ clipboard,
212
+ logger,
213
+ onCopied,
214
+ onError,
215
+ onClick,
216
+ copiedLabel = checkoutLabels.copied,
217
+ ButtonComponent = "button",
218
+ children,
219
+ type = "button",
220
+ className,
221
+ ...buttonProps
222
+ } = props;
223
+ const [copied, showCopied] = useTransientValue(false);
224
+ return React3.createElement(
225
+ ButtonComponent,
226
+ {
227
+ ...buttonProps,
228
+ className: joinClassNames(orClasses.btn, className),
229
+ type,
230
+ onClick: async (event) => {
231
+ onClick?.(event);
232
+ if (event.defaultPrevented) return;
233
+ try {
234
+ if (copyInvoice === void 0) {
235
+ await copyInvoiceHelper({ invoice, clipboard, logger });
236
+ } else {
237
+ await copyInvoice();
238
+ }
239
+ showCopied(true);
240
+ onCopied?.();
241
+ } catch (error) {
242
+ onError?.(error);
315
243
  }
316
- }, children ?? (copied ? copiedLabel : openReceiveCheckoutLabels.copyInvoice));
317
- }
318
- export function OpenWalletButton(props) {
319
- const { invoice, openWallet, open, logger, onOpened, onError, onClick, ButtonComponent = "button", children = openReceiveCheckoutLabels.openWallet, type = "button", ...buttonProps } = props;
320
- return React.createElement(ButtonComponent, {
321
- ...buttonProps,
322
- type,
323
- onClick: (event) => {
324
- onClick?.(event);
325
- if (event.defaultPrevented)
326
- return;
327
- try {
328
- const uri = openWallet === undefined
329
- ? openWalletHelper({ invoice, open, logger })
330
- : openWallet();
331
- onOpened?.(uri);
332
- }
333
- catch (error) {
334
- onError?.(error);
335
- }
244
+ }
245
+ },
246
+ children ?? React3.createElement(
247
+ React3.Fragment,
248
+ null,
249
+ React3.createElement(ClipboardIcon),
250
+ copied ? copiedLabel : checkoutLabels.copyInvoice
251
+ )
252
+ );
253
+ }
254
+ function OpenWalletButton(props) {
255
+ const {
256
+ invoice,
257
+ openWallet,
258
+ open,
259
+ logger,
260
+ onOpened,
261
+ onError,
262
+ onClick,
263
+ ButtonComponent = "button",
264
+ children = checkoutLabels.openWallet,
265
+ type = "button",
266
+ className,
267
+ ...buttonProps
268
+ } = props;
269
+ return React3.createElement(
270
+ ButtonComponent,
271
+ {
272
+ ...buttonProps,
273
+ className: joinClassNames(orClasses.btn, className),
274
+ type,
275
+ onClick: (event) => {
276
+ onClick?.(event);
277
+ if (event.defaultPrevented) return;
278
+ try {
279
+ const uri = openWallet === void 0 ? openWalletHelper({ invoice, open, logger }) : openWallet();
280
+ onOpened?.(uri);
281
+ } catch (error) {
282
+ onError?.(error);
336
283
  }
337
- }, children);
338
- }
339
- export function PaymentState(props) {
340
- const { state = "pending", ...spanProps } = props;
341
- return React.createElement("span", {
342
- ...spanProps,
343
- [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.state]: state
344
- }, state);
345
- }
346
- export function useTheme(options = {}) {
347
- const storageKey = options.storageKey ?? OPENRECEIVE_THEME_STORAGE_KEY;
348
- const [theme, setThemeState] = React.useState(() => readOpenReceiveThemePreference({
349
- storage: options.storage,
284
+ }
285
+ },
286
+ children
287
+ );
288
+ }
289
+ function PaymentState(props) {
290
+ const { state = "pending", className, ...spanProps } = props;
291
+ return React3.createElement(
292
+ "span",
293
+ {
294
+ ...spanProps,
295
+ className: joinClassNames(
296
+ state === "settled" ? orClasses.stateSettled : orClasses.statePending,
297
+ className
298
+ ),
299
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.state]: state
300
+ },
301
+ state
302
+ );
303
+ }
304
+ function SettledCheckIcon() {
305
+ return React3.createElement(
306
+ "svg",
307
+ {
308
+ className: orClasses.settledIcon,
309
+ viewBox: "0 0 24 24",
310
+ fill: "none",
311
+ stroke: "currentColor",
312
+ strokeWidth: 1.8,
313
+ "aria-hidden": "true",
314
+ focusable: "false"
315
+ },
316
+ React3.createElement("path", {
317
+ strokeLinecap: "round",
318
+ strokeLinejoin: "round",
319
+ d: "M9 12.75 11.25 15 15 9.75M21 12a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"
320
+ })
321
+ );
322
+ }
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
+ function WaitingState(props) {
349
+ const status = props.status ?? createCheckoutStatusModel({
350
+ phase: props.phase,
351
+ waiting: props.waiting ?? false
352
+ });
353
+ const title = props.statusTitle ?? status.title;
354
+ const titleRow = props.countdownLabel === void 0 ? React3.createElement("strong", { className: orClasses.paymentStatusTitle }, title) : React3.createElement(
355
+ "div",
356
+ { className: orClasses.swapWaitingTitle },
357
+ React3.createElement("strong", { className: orClasses.paymentStatusTitle }, title),
358
+ React3.createElement(
359
+ "strong",
360
+ { className: orClasses.swapCountdown },
361
+ props.countdownLabel
362
+ )
363
+ );
364
+ return React3.createElement(
365
+ "div",
366
+ {
367
+ // Settlement and status transitions are announced to assistive tech.
368
+ role: "status",
369
+ "aria-live": "polite",
370
+ className: joinClassNames(orClasses.paymentStatus, props.className)
371
+ },
372
+ status.waiting ? React3.createElement("span", {
373
+ className: orClasses.spinner,
374
+ "aria-hidden": "true"
375
+ }) : null,
376
+ props.settled ?? status.phase === "settled" ? React3.createElement(SettledCheckIcon, { key: "settled-icon" }) : null,
377
+ React3.createElement(
378
+ "div",
379
+ {
380
+ className: orClasses.paymentStatusBody
381
+ },
382
+ titleRow,
383
+ React3.createElement(
384
+ "span",
385
+ { className: orClasses.paymentStatusDetail },
386
+ props.statusDetail ?? status.detail
387
+ )
388
+ )
389
+ );
390
+ }
391
+ function InvoiceSummary(props) {
392
+ const {
393
+ amountLabel,
394
+ fiatLabel,
395
+ paymentHashLabel,
396
+ status,
397
+ PaymentStateComponent = PaymentState,
398
+ classNames,
399
+ className,
400
+ ...divProps
401
+ } = props;
402
+ return React3.createElement(
403
+ "div",
404
+ {
405
+ ...divProps,
406
+ className: joinClassNames(orClasses.meta, className),
407
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.meta]: ""
408
+ },
409
+ amountLabel === void 0 ? null : React3.createElement(
410
+ "span",
411
+ {
412
+ className: joinClassNames(orClasses.metaItem, classNames?.amount)
413
+ },
414
+ amountLabel
415
+ ),
416
+ fiatLabel === void 0 ? null : React3.createElement(
417
+ "span",
418
+ {
419
+ className: joinClassNames(orClasses.metaItem, classNames?.fiat)
420
+ },
421
+ fiatLabel
422
+ ),
423
+ paymentHashLabel === void 0 ? null : React3.createElement(
424
+ "span",
425
+ {
426
+ className: joinClassNames(orClasses.metaItem, classNames?.paymentHash)
427
+ },
428
+ paymentHashLabel
429
+ ),
430
+ status === void 0 ? null : React3.createElement(PaymentStateComponent, {
431
+ state: status,
432
+ className: classNames?.paymentState
433
+ })
434
+ );
435
+ }
436
+ function SatsDetail(props) {
437
+ const { amountLabel, fiatLabel, fiatCurrency, className, ...divProps } = props;
438
+ const caption = formatAmountCaption({ amountLabel, fiatLabel, fiatCurrency });
439
+ if (caption === void 0) return null;
440
+ return React3.createElement(
441
+ "div",
442
+ {
443
+ ...divProps,
444
+ className: joinClassNames(orClasses.satsDetail, className)
445
+ },
446
+ caption
447
+ );
448
+ }
449
+
450
+ // src/theme.ts
451
+ import * as React4 from "react";
452
+ import {
453
+ OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES as OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES2,
454
+ OPENRECEIVE_THEME_STORAGE_KEY,
455
+ createThemeModel,
456
+ orClasses as orClasses2,
457
+ readThemePreference,
458
+ writeThemePreference
459
+ } from "@openreceive/browser/headless";
460
+ var ThemeContext = React4.createContext(null);
461
+ var storedThemeListeners = /* @__PURE__ */ new Set();
462
+ var lastSetTheme = /* @__PURE__ */ new Map();
463
+ function notifyStoredThemeChange() {
464
+ for (const listener of storedThemeListeners) listener();
465
+ }
466
+ function getSystemDark() {
467
+ return globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false;
468
+ }
469
+ function getServerSystemDark() {
470
+ return false;
471
+ }
472
+ function subscribeStoredTheme(onStoreChange) {
473
+ storedThemeListeners.add(onStoreChange);
474
+ globalThis.addEventListener?.("storage", onStoreChange);
475
+ return () => {
476
+ storedThemeListeners.delete(onStoreChange);
477
+ globalThis.removeEventListener?.("storage", onStoreChange);
478
+ };
479
+ }
480
+ function subscribeSystemDark(onStoreChange) {
481
+ const media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
482
+ media?.addEventListener?.("change", onStoreChange);
483
+ return () => media?.removeEventListener?.("change", onStoreChange);
484
+ }
485
+ function useLocalTheme(options = {}) {
486
+ const storageKey = options.storageKey ?? OPENRECEIVE_THEME_STORAGE_KEY;
487
+ const storage = options.storage;
488
+ const defaultTheme = options.defaultTheme;
489
+ const theme = React4.useSyncExternalStore(
490
+ subscribeStoredTheme,
491
+ React4.useCallback(
492
+ () => readThemePreference({
493
+ storage,
350
494
  storageKey,
351
- defaultTheme: options.defaultTheme
352
- }));
353
- const [systemDark, setSystemDark] = React.useState(() => globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false);
354
- React.useEffect(() => {
355
- const media = globalThis.matchMedia?.("(prefers-color-scheme: dark)");
356
- if (media === undefined)
357
- return undefined;
358
- const update = () => setSystemDark(media.matches);
359
- media.addEventListener?.("change", update);
360
- return () => media.removeEventListener?.("change", update);
361
- }, []);
362
- const themeModel = createOpenReceiveThemeModel(theme, { systemDark });
363
- const setTheme = React.useCallback((nextTheme) => {
364
- setThemeState(nextTheme);
365
- writeOpenReceiveThemePreference(nextTheme, {
366
- storage: options.storage,
367
- storageKey
368
- });
369
- }, [options.storage, storageKey]);
370
- const toggleTheme = React.useCallback(() => {
371
- setTheme(themeModel.nextTheme);
372
- }, [setTheme, themeModel.nextTheme]);
373
- return {
374
- theme,
375
- resolvedTheme: themeModel.resolvedTheme,
376
- model: themeModel,
377
- nextTheme: themeModel.nextTheme,
378
- toggleLabel: themeModel.toggleLabel,
379
- attributes: themeModel.attributes,
380
- checkoutElementAttributes: themeModel.checkoutElementAttributes,
381
- setTheme,
382
- toggleTheme
383
- };
495
+ defaultTheme: lastSetTheme.get(storageKey) ?? defaultTheme
496
+ }),
497
+ [storage, storageKey, defaultTheme]
498
+ ),
499
+ React4.useCallback(
500
+ () => (
501
+ // A host-supplied storage is readable on the server (that is how SSR theming is
502
+ // wired); the implicit browser localStorage is not, so fall back to the default.
503
+ storage === void 0 ? defaultTheme ?? "system" : readThemePreference({ storage, storageKey, defaultTheme })
504
+ ),
505
+ [storage, storageKey, defaultTheme]
506
+ )
507
+ );
508
+ const systemDark = React4.useSyncExternalStore(
509
+ subscribeSystemDark,
510
+ getSystemDark,
511
+ getServerSystemDark
512
+ );
513
+ const themeModel = createThemeModel(theme, { systemDark });
514
+ const setTheme = React4.useCallback(
515
+ (nextTheme) => {
516
+ lastSetTheme.set(storageKey, nextTheme);
517
+ writeThemePreference(nextTheme, {
518
+ storage,
519
+ storageKey
520
+ });
521
+ notifyStoredThemeChange();
522
+ },
523
+ [storage, storageKey]
524
+ );
525
+ const toggleTheme = React4.useCallback(() => {
526
+ setTheme(themeModel.nextTheme);
527
+ }, [setTheme, themeModel.nextTheme]);
528
+ return {
529
+ theme,
530
+ resolvedTheme: themeModel.resolvedTheme,
531
+ model: themeModel,
532
+ nextTheme: themeModel.nextTheme,
533
+ toggleLabel: themeModel.toggleLabel,
534
+ attributes: themeModel.attributes,
535
+ checkoutElementAttributes: themeModel.checkoutElementAttributes,
536
+ fromScope: false,
537
+ setTheme,
538
+ toggleTheme
539
+ };
384
540
  }
385
- export function ThemeToggle(props) {
386
- const { theme, resolvedTheme, onThemeChange, ButtonComponent = "button", children, type = "button", onClick, ...buttonProps } = props;
387
- const fallback = useTheme({
388
- defaultTheme: theme
389
- });
390
- const activeTheme = resolvedTheme ?? fallback.resolvedTheme;
391
- const themeModel = createOpenReceiveThemeModel(activeTheme);
392
- const componentProps = {
393
- ...buttonProps,
394
- "aria-label": themeModel.toggleLabel,
395
- className: joinClassNames("or-theme-toggle-button", `or-theme-toggle-${themeModel.resolvedTheme}`, buttonProps.className),
396
- title: themeModel.toggleLabel,
397
- type,
398
- [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.themeToggle]: "",
399
- onClick: (event) => {
400
- onClick?.(event);
401
- if (event.defaultPrevented)
402
- return;
403
- onThemeChange?.(themeModel.nextTheme);
404
- if (onThemeChange === undefined)
405
- fallback.setTheme(themeModel.nextTheme);
406
- }
407
- };
408
- const defaultChildren = React.createElement(React.Fragment, null, React.createElement("span", {
409
- "aria-hidden": true,
410
- className: "or-theme-toggle-track"
411
- }, React.createElement("span", {
412
- className: "or-theme-toggle-icon or-theme-toggle-icon-light"
413
- })), React.createElement("span", {
414
- className: "or-theme-toggle-label"
415
- }, themeModel.toggleLabel));
416
- return React.createElement(ButtonComponent, componentProps, children ?? defaultChildren);
417
- }
418
- export function ThemeScope(props) {
419
- const { as: Element = "div", defaultTheme, themeStorageKey, storage, themeToggle = false, topbarClassName, themeToggleClassName, ButtonComponent, children, ...elementProps } = props;
420
- const theme = useTheme({
421
- defaultTheme,
422
- storageKey: themeStorageKey,
423
- storage
424
- });
425
- const scopedChildren = typeof children === "function" ? children(theme) : children;
426
- return React.createElement(Element, {
541
+ function useTheme(options = {}) {
542
+ const scoped = React4.useContext(ThemeContext);
543
+ const local = useLocalTheme(options);
544
+ const storageKey = options.storageKey ?? OPENRECEIVE_THEME_STORAGE_KEY;
545
+ if (scoped !== null && options.storage === void 0 && (options.storageKey === void 0 || options.storageKey === storageKey) && options.defaultTheme === void 0) {
546
+ return { ...scoped, fromScope: true };
547
+ }
548
+ return local;
549
+ }
550
+ function ThemeToggle(props) {
551
+ const {
552
+ theme,
553
+ resolvedTheme,
554
+ onThemeChange,
555
+ ButtonComponent = "button",
556
+ children,
557
+ type = "button",
558
+ onClick,
559
+ ...buttonProps
560
+ } = props;
561
+ const fallback = useTheme({
562
+ defaultTheme: theme
563
+ });
564
+ const activeTheme = resolvedTheme ?? fallback.resolvedTheme;
565
+ const themeModel = createThemeModel(activeTheme);
566
+ const componentProps = {
567
+ ...buttonProps,
568
+ "aria-label": themeModel.toggleLabel,
569
+ className: joinClassNames(orClasses2.themeToggle, buttonProps.className),
570
+ title: themeModel.toggleLabel,
571
+ type,
572
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES2.themeToggle]: "",
573
+ onClick: (event) => {
574
+ onClick?.(event);
575
+ if (event.defaultPrevented) return;
576
+ onThemeChange?.(themeModel.nextTheme);
577
+ if (onThemeChange === void 0) fallback.setTheme(themeModel.nextTheme);
578
+ }
579
+ };
580
+ return React4.createElement(ButtonComponent, componentProps, children ?? themeModel.toggleLabel);
581
+ }
582
+ function ThemeScope(props) {
583
+ const {
584
+ as: Element = "div",
585
+ defaultTheme,
586
+ storageKey,
587
+ storage,
588
+ themeToggle = false,
589
+ topbarClassName,
590
+ themeToggleClassName,
591
+ ButtonComponent,
592
+ children,
593
+ ...elementProps
594
+ } = props;
595
+ const theme = useLocalTheme({
596
+ defaultTheme,
597
+ storageKey,
598
+ storage
599
+ });
600
+ const scopedChildren = typeof children === "function" ? children(theme) : children;
601
+ return React4.createElement(
602
+ ThemeContext.Provider,
603
+ { value: theme },
604
+ // Variadic children, not an array: an array child needs a key on every entry.
605
+ React4.createElement(
606
+ Element,
607
+ {
427
608
  ...elementProps,
428
609
  ...theme.attributes
429
- }, [
430
- themeToggle
431
- ? React.createElement("div", {
432
- className: topbarClassName,
433
- key: "openreceive-theme-scope-toggle"
434
- }, React.createElement(ThemeToggle, {
435
- className: themeToggleClassName,
436
- theme: theme.theme,
437
- resolvedTheme: theme.resolvedTheme,
438
- onThemeChange: theme.setTheme,
439
- ButtonComponent
440
- }))
441
- : null,
442
- scopedChildren
443
- ]);
444
- }
445
- export function WaitingState(props) {
446
- const status = props.status ??
447
- createCheckoutStatusModel({
448
- phase: props.phase,
449
- waiting: props.waiting ?? false
450
- });
451
- return React.createElement("div", {
452
- className: joinClassNames("or-payment-status", props.className)
453
- }, status.waiting
454
- ? React.createElement("span", {
455
- className: "or-spinner",
456
- "aria-hidden": "true"
610
+ },
611
+ themeToggle ? React4.createElement(
612
+ "div",
613
+ {
614
+ className: topbarClassName
615
+ },
616
+ React4.createElement(ThemeToggle, {
617
+ className: themeToggleClassName,
618
+ theme: theme.theme,
619
+ resolvedTheme: theme.resolvedTheme,
620
+ onThemeChange: theme.setTheme,
621
+ ButtonComponent
457
622
  })
458
- : null, React.createElement("div", null, React.createElement("strong", null, props.statusTitle ?? status.title), React.createElement("span", null, props.statusDetail ?? status.detail)));
459
- }
460
- export function PaymentWizard(props) {
461
- const countryStorageKey = props.countryStorageKey ?? OPENRECEIVE_COUNTRY_STORAGE_KEY;
462
- const [selection, setSelection] = React.useState(() => createOpenReceivePaymentWizardController({
463
- storageKey: countryStorageKey,
464
- defaultCountryCode: getOpenReceiveDefaultCountryCode()
465
- }).getSelection());
466
- const [activeTutorial, setActiveTutorial] = React.useState(null);
467
- const updateWizardSelection = React.useCallback((apply) => {
468
- setSelection((current) => apply(createOpenReceivePaymentWizardController({
469
- selection: current,
470
- storageKey: countryStorageKey
471
- })));
472
- }, [countryStorageKey]);
473
- const model = createOpenReceivePaymentWizardModel(selection);
474
- const { wizard } = model;
475
- const routeAssetDisplays = createOpenReceiveWizardRouteAssetDisplays(model.routeAssets, {
476
- selectedRoute: model.selectedRoute
623
+ ) : null,
624
+ scopedChildren
625
+ )
626
+ );
627
+ }
628
+
629
+ // src/use-checkout.ts
630
+ import * as React5 from "react";
631
+ import {
632
+ copyInvoice as copyInvoiceHelper2,
633
+ createCheckoutController,
634
+ createCheckoutState,
635
+ createCheckoutStatusModel as createCheckoutStatusModel2,
636
+ openWallet as openWalletHelper2,
637
+ deriveStatus
638
+ } from "@openreceive/browser/headless";
639
+ function useCheckout(options) {
640
+ const checkout = options.checkout;
641
+ const [copied, showCopied] = useTransientValue(false);
642
+ const [latestSnapshot, setLatestSnapshot] = React5.useState(checkout);
643
+ const incomingIdentity = `${checkout.checkout_id} ${checkout.reference}`;
644
+ const checkoutRef = React5.useRef(checkout);
645
+ checkoutRef.current = checkout;
646
+ React5.useEffect(() => {
647
+ setLatestSnapshot(checkoutRef.current);
648
+ }, [incomingIdentity]);
649
+ const snapshot = latestSnapshot;
650
+ const [state, setState] = React5.useState(
651
+ () => createCheckoutState(snapshot, {
652
+ logger: options.logger
653
+ })
654
+ );
655
+ const controllerRef = React5.useRef(null);
656
+ const onStateRef = React5.useRef(options.onState);
657
+ onStateRef.current = options.onState;
658
+ const onSettledRef = React5.useRef(options.onSettled);
659
+ onSettledRef.current = options.onSettled;
660
+ const loggerRef = React5.useRef(options.logger);
661
+ loggerRef.current = options.logger;
662
+ const onErrorRef = React5.useRef(options.onError);
663
+ onErrorRef.current = options.onError;
664
+ const onCopyRef = React5.useRef(options.onCopy);
665
+ onCopyRef.current = options.onCopy;
666
+ const onOpenWalletRef = React5.useRef(options.onOpenWallet);
667
+ onOpenWalletRef.current = options.onOpenWallet;
668
+ const settledAnnouncementRef = React5.useRef({
669
+ reference: snapshot.reference,
670
+ fired: false
671
+ });
672
+ const logContext = React5.useMemo(() => getCheckoutLogContext(state), [state]);
673
+ const refreshStatus = options.polling === false ? void 0 : options.refreshStatus;
674
+ const refreshStatusRef = React5.useRef(refreshStatus);
675
+ refreshStatusRef.current = refreshStatus;
676
+ const polls = refreshStatus !== void 0;
677
+ const prefix = options.polling === false ? void 0 : options.prefix;
678
+ const snapshotRef = React5.useRef(snapshot);
679
+ snapshotRef.current = snapshot;
680
+ const checkoutIdentity = `${snapshot.checkout_id} ${snapshot.reference}`;
681
+ React5.useEffect(() => {
682
+ const controller = createCheckoutController({
683
+ snapshot: snapshotRef.current,
684
+ ...polls ? {
685
+ refreshStatus: async (reference) => await refreshStatusRef.current?.(reference) ?? null
686
+ } : {},
687
+ ...prefix === void 0 ? {} : { prefix },
688
+ pollIntervalMs: options.pollIntervalMs,
689
+ // Omit logger when unset so @openreceive/browser attaches its default console sink.
690
+ // Pass `false` through to disable; wrap custom sinks so inline host callbacks stay stable.
691
+ ...loggerRef.current === false ? { logger: false } : loggerRef.current !== void 0 ? {
692
+ logger: (entry) => {
693
+ const current = loggerRef.current;
694
+ if (typeof current === "function") current(entry);
695
+ }
696
+ } : {},
697
+ onError: (error) => {
698
+ onErrorRef.current?.(error);
699
+ },
700
+ clipboard: options.clipboard,
701
+ open: options.open,
702
+ onState: (nextState) => {
703
+ setState(nextState);
704
+ onStateRef.current?.(nextState);
705
+ },
706
+ onSnapshot: setLatestSnapshot
477
707
  });
478
- const routeDisplays = createOpenReceiveWizardRouteDisplays(wizard.routes);
479
- const showRoutePicker = routeAssetDisplays.length > 0 &&
480
- (model.selectedRoute === null || routeDisplays.length === 0);
481
- const activeTutorialProvider = activeTutorial === null
482
- ? undefined
483
- : routeDisplays
484
- .flatMap((route) => route.providers)
485
- .find((provider) => provider.id === activeTutorial.providerId);
486
- return React.createElement("div", {
487
- className: joinClassNames("or-wizard", props.className)
488
- }, selection.selectedMethod === null
489
- ? React.createElement(React.Fragment, null, React.createElement("div", {
490
- className: "or-wizard-header"
491
- }, React.createElement("div", null, React.createElement("h2", null, openReceiveCheckoutLabels.wizardTitle), React.createElement("p", null, openReceiveCheckoutLabels.wizardSubtitle))), React.createElement("div", {
492
- className: "or-method-grid"
493
- }, openReceivePaymentMethods.map((method) => React.createElement("button", {
494
- key: method.id,
495
- onClick: () => {
496
- updateWizardSelection((controller) => controller.selectMethod(method.id));
497
- },
498
- type: "button"
499
- }, React.createElement("img", { alt: "", src: getOpenReceivePaymentMethodIcon(method.id) }), React.createElement("span", null, method.title), React.createElement("small", null, method.detail)))))
500
- : null, selection.selectedMethod === null
501
- ? null
502
- : renderWizardBreadcrumbs({
503
- method: selection.selectedMethod,
504
- selectedRoute: model.selectedRoute,
505
- routeAssets: routeAssetDisplays,
506
- onChangeMethod: () => {
507
- updateWizardSelection((controller) => controller.changeMethod());
508
- },
509
- onChangeRoute: () => {
510
- updateWizardSelection((controller) => controller.update({ type: "change_route" }));
511
- }
512
- }), showRoutePicker && selection.selectedMethod === "bitcoin"
513
- ? renderRoutePicker({
514
- assets: routeAssetDisplays,
515
- method: "bitcoin",
516
- onSelectRoute: (route) => {
517
- updateWizardSelection((controller) => controller.selectRoute(route));
518
- }
519
- })
520
- : null, showRoutePicker && selection.selectedMethod === "crypto"
521
- ? renderRoutePicker({
522
- assets: routeAssetDisplays,
523
- method: "crypto",
524
- onSelectRoute: (route) => {
525
- updateWizardSelection((controller) => controller.selectRoute(route));
526
- }
527
- })
528
- : null, selection.selectedMethod === null
529
- ? null
530
- : React.createElement("div", {
531
- className: "or-wizard-results"
532
- }, routeDisplays.length === 0
533
- ? React.createElement("p", {
534
- className: "or-wizard-empty"
535
- }, getOpenReceiveWizardEmptyMessage(selection.selectedMethod))
536
- : routeDisplays.map((route) => React.createElement("section", {
537
- className: "or-wizard-route",
538
- key: route.key
539
- }, React.createElement("div", {
540
- className: "or-wizard-route-heading"
541
- }, React.createElement("div", null, React.createElement("h3", null, route.title, wizard.selectedRail === null
542
- ? null
543
- : renderCountrySelect({
544
- countries: model.countryDisplays,
545
- selectedCountryCode: selection.selectedCountryCode,
546
- onSelectCountry: (countryCode) => {
547
- updateWizardSelection((controller) => controller.selectCountry(countryCode));
548
- }
549
- })))), React.createElement("div", {
550
- className: "or-provider-grid"
551
- }, route.providers.map((provider) => React.createElement("article", {
552
- className: provider.recommended
553
- ? "or-provider-card recommended"
554
- : "or-provider-card",
555
- key: provider.id
556
- }, React.createElement("div", {
557
- className: "or-provider-heading"
558
- }, React.createElement("img", {
559
- alt: "",
560
- src: provider.icon
561
- }), React.createElement("h4", null, provider.name), provider.recommendedLabel === null
562
- ? null
563
- : React.createElement("span", null, provider.recommendedLabel)), React.createElement("p", {
564
- className: "or-provider-kind"
565
- }, provider.kind), React.createElement("div", {
566
- className: "or-provider-actions"
567
- }, renderProviderOpenAction(provider, () => setActiveTutorial({
568
- providerId: provider.id,
569
- index: 0,
570
- copied: false
571
- }))))))))), activeTutorialProvider === undefined || activeTutorial === null
572
- ? null
573
- : renderProviderTutorialModal({
574
- provider: activeTutorialProvider,
575
- index: activeTutorial.index,
576
- copied: activeTutorial.copied,
577
- onClose: () => setActiveTutorial(null),
578
- onCopy: async () => {
579
- try {
580
- await copyInvoiceHelper({
581
- invoice: props.invoice,
582
- logger: props.logger,
583
- logContext: props.logContext
584
- });
585
- globalThis.dispatchEvent?.(createCheckoutProviderCopyEvent(activeTutorialProvider.id));
586
- setActiveTutorial({
587
- providerId: activeTutorialProvider.id,
588
- index: 0,
589
- copied: true
590
- });
591
- }
592
- catch (error) {
593
- props.onError?.(error);
594
- }
595
- },
596
- onStep: (index) => setActiveTutorial({
597
- providerId: activeTutorialProvider.id,
598
- index,
599
- copied: activeTutorial.copied
600
- })
601
- }));
708
+ controllerRef.current = controller;
709
+ controller.start();
710
+ void controller.reloadState().catch(() => void 0);
711
+ return () => {
712
+ controller.stop();
713
+ if (controllerRef.current === controller) controllerRef.current = null;
714
+ };
715
+ }, [checkoutIdentity, polls, prefix, options.pollIntervalMs]);
716
+ const publicStatus = deriveStatus(state);
717
+ const richStatus = createCheckoutStatusModel2(state);
718
+ React5.useEffect(() => {
719
+ const announced = settledAnnouncementRef.current;
720
+ if (announced.reference !== snapshot.reference) {
721
+ settledAnnouncementRef.current = {
722
+ reference: snapshot.reference,
723
+ fired: false
724
+ };
725
+ }
726
+ }, [snapshot.reference]);
727
+ React5.useEffect(() => {
728
+ const announced = settledAnnouncementRef.current;
729
+ if (publicStatus !== "settled" || announced.fired) return;
730
+ settledAnnouncementRef.current = {
731
+ reference: snapshot.reference,
732
+ fired: true
733
+ };
734
+ onSettledRef.current?.();
735
+ }, [publicStatus, snapshot.reference]);
736
+ const copyInvoice = React5.useCallback(async () => {
737
+ try {
738
+ const controller = controllerRef.current;
739
+ if (controller === null) {
740
+ await copyInvoiceHelper2({
741
+ invoice: state.invoice,
742
+ clipboard: options.clipboard,
743
+ logger: options.logger,
744
+ logContext
745
+ });
746
+ } else {
747
+ await controller.copyInvoice();
748
+ }
749
+ showCopied(true);
750
+ onCopyRef.current?.();
751
+ } catch (error) {
752
+ onErrorRef.current?.(error);
753
+ throw error;
754
+ }
755
+ }, [logContext, state.invoice, options.clipboard, options.logger, showCopied]);
756
+ const openWallet = React5.useCallback(() => {
757
+ try {
758
+ const controller = controllerRef.current;
759
+ const uri = controller === null ? openWalletHelper2({
760
+ invoice: state.invoice,
761
+ open: options.open,
762
+ logger: options.logger,
763
+ logContext
764
+ }) : controller.openWallet();
765
+ onOpenWalletRef.current?.(uri);
766
+ return uri;
767
+ } catch (error) {
768
+ onErrorRef.current?.(error);
769
+ throw error;
770
+ }
771
+ }, [logContext, state.invoice, options.open, options.logger]);
772
+ const reloadState = React5.useCallback(async () => {
773
+ try {
774
+ const next = await controllerRef.current?.reloadState();
775
+ if (next !== void 0) setState(next);
776
+ } catch (error) {
777
+ onErrorRef.current?.(error);
778
+ throw error;
779
+ }
780
+ }, []);
781
+ const retry = reloadState;
782
+ const cancel = React5.useCallback(() => {
783
+ const next = controllerRef.current?.cancel();
784
+ if (next !== void 0) setState(next);
785
+ }, []);
786
+ return {
787
+ // The state IS the view model now: one derivation, one set of display
788
+ // fields. `status` stays on top of it because the component's terminal
789
+ // branches read the checkout's coarse status, not the attempt's phase.
790
+ ...state,
791
+ checkout: latestSnapshot,
792
+ copied,
793
+ status: publicStatus,
794
+ expires_in_seconds: richStatus.expires_in_seconds,
795
+ countdownLabel: richStatus.countdownLabel,
796
+ countdownPrefix: richStatus.countdownPrefix,
797
+ statusTitle: richStatus.title,
798
+ statusDetail: richStatus.detail,
799
+ waiting: richStatus.waiting,
800
+ reloadState,
801
+ retry,
802
+ cancel,
803
+ copyInvoice,
804
+ openWallet
805
+ };
602
806
  }
603
- function renderWizardBreadcrumbs(options) {
604
- const method = openReceivePaymentMethods.find((candidate) => candidate.id === options.method);
605
- const methodLabel = method?.title ?? openReceiveCheckoutLabels.paymentMethod;
606
- const routeLabel = options.selectedRoute === null || options.routeAssets.length <= 1
607
- ? null
608
- : options.routeAssets.find((asset) => asset.id === options.selectedRoute)?.label ?? options.selectedRoute;
609
- return React.createElement("nav", {
610
- "aria-label": "Payment path",
611
- className: "or-wizard-breadcrumbs"
612
- }, React.createElement("button", {
613
- className: "or-wizard-breadcrumb",
614
- onClick: options.onChangeMethod,
615
- type: "button"
616
- }, openReceiveCheckoutLabels.paymentMethod), React.createElement("span", { "aria-hidden": "true" }, "/"), routeLabel === null
617
- ? React.createElement("span", { className: "or-wizard-breadcrumb-current" }, methodLabel)
618
- : React.createElement(React.Fragment, null, React.createElement("button", {
619
- className: "or-wizard-breadcrumb",
620
- onClick: options.onChangeRoute,
621
- type: "button"
622
- }, methodLabel), React.createElement("span", { "aria-hidden": "true" }, "/"), React.createElement("span", { className: "or-wizard-breadcrumb-current" }, routeLabel)));
807
+ var CheckoutContext = React5.createContext(null);
808
+ function useCheckoutContext() {
809
+ const checkout = React5.useContext(CheckoutContext);
810
+ if (checkout === null) {
811
+ throw new Error("useCheckoutContext must be used within CheckoutProvider.");
812
+ }
813
+ return checkout;
814
+ }
815
+ function CheckoutProvider(props) {
816
+ const { children, ...options } = props;
817
+ const checkout = useCheckout(options);
818
+ const content = typeof children === "function" ? children(checkout) : children;
819
+ return React5.createElement(CheckoutContext.Provider, { value: checkout }, content);
623
820
  }
821
+
822
+ // src/wizard.ts
823
+ import {
824
+ assetButtonClasses,
825
+ buildMethodGridEntries,
826
+ checkoutLabels as checkoutLabels5,
827
+ copyInvoice as copyInvoiceHelper3,
828
+ createPaymentWizardController,
829
+ createPaymentWizardModel,
830
+ createWizardRouteAssetDisplays,
831
+ createWizardRouteDisplays,
832
+ findSwapGridGroup,
833
+ formatChooseNetworkHeading,
834
+ formatNetworkSummary,
835
+ getNetworkIcon,
836
+ getPaymentMethodIcon,
837
+ getSwapOptionIcon,
838
+ getWizardEmptyMessage,
839
+ networkButtonClasses,
840
+ networkCheckClasses,
841
+ networkMobileRevealClasses,
842
+ networkSummaryIconClasses,
843
+ orClasses as orClasses6,
844
+ overlaySwapRefundStaging,
845
+ paymentAccentId,
846
+ paymentMethods,
847
+ requestSwapRefund,
848
+ swapAssetMatchesRoute,
849
+ swapGroupLimitOption,
850
+ swapOptionLimitMessage as swapOptionLimitMessage2,
851
+ swapPickerKey,
852
+ updateSelectedSwapNetworks,
853
+ wizardNetworkGroupIds
854
+ } from "@openreceive/browser/headless";
855
+ import * as React9 from "react";
856
+
857
+ // src/provider-tutorial.ts
858
+ import {
859
+ createLightningInvoiceDecodeUrl,
860
+ checkoutLabels as checkoutLabels2,
861
+ orClasses as orClasses3
862
+ } from "@openreceive/browser/headless";
863
+ import * as React6 from "react";
624
864
  function renderProviderOpenAction(provider, onOpenTutorial) {
625
- if (provider.tutorials.length === 0) {
626
- return React.createElement("a", {
627
- href: provider.url,
628
- rel: "noreferrer",
629
- target: "_blank"
630
- }, provider.openLabel);
865
+ if (provider.tutorials.length === 0) {
866
+ return React6.createElement(
867
+ "a",
868
+ {
869
+ className: orClasses3.providerOpen,
870
+ href: provider.url,
871
+ rel: "noreferrer",
872
+ target: "_blank"
873
+ },
874
+ provider.openLabel
875
+ );
876
+ }
877
+ return React6.createElement(
878
+ "button",
879
+ {
880
+ className: orClasses3.providerOpen,
881
+ onClick: onOpenTutorial,
882
+ type: "button"
883
+ },
884
+ provider.openLabel
885
+ );
886
+ }
887
+ function ProviderTutorialModal(options) {
888
+ const { provider } = options;
889
+ const dialogRef = React6.useRef(null);
890
+ React6.useEffect(() => {
891
+ const dialog = dialogRef.current;
892
+ if (dialog === null) return;
893
+ const opener = dialog.ownerDocument.activeElement instanceof HTMLElement ? dialog.ownerDocument.activeElement : null;
894
+ dialog.focus();
895
+ return () => {
896
+ opener?.focus();
897
+ };
898
+ }, []);
899
+ const trapTab = (event) => {
900
+ if (event.key !== "Tab") return;
901
+ const dialog = dialogRef.current;
902
+ if (dialog === null) return;
903
+ const focusables = Array.from(
904
+ dialog.querySelectorAll(
905
+ 'button, [href], input, select, textarea, [tabindex]:not([tabindex="-1"])'
906
+ )
907
+ ).filter((element) => !element.hasAttribute("disabled"));
908
+ if (focusables.length === 0) return;
909
+ const first = focusables[0];
910
+ const last = focusables[focusables.length - 1];
911
+ const active = dialog.ownerDocument.activeElement;
912
+ if (event.shiftKey && (active === first || active === dialog)) {
913
+ event.preventDefault();
914
+ last.focus();
915
+ } else if (!event.shiftKey && active === last) {
916
+ event.preventDefault();
917
+ first.focus();
631
918
  }
632
- return React.createElement("button", {
633
- className: "or-provider-open",
634
- onClick: onOpenTutorial,
635
- type: "button"
636
- }, provider.openLabel);
637
- }
638
- function renderProviderTutorialModal(options) {
639
- const { provider } = options;
640
- if (provider.tutorials.length === 0)
641
- return null;
642
- const totalSteps = provider.tutorials.length + 1;
643
- const stepIndex = Math.max(0, Math.min(provider.tutorials.length, options.index));
644
- const tutorial = stepIndex === 0 ? undefined : provider.tutorials[stepIndex - 1];
645
- const previousIndex = Math.max(0, stepIndex - 1);
646
- const nextIndex = Math.min(provider.tutorials.length, stepIndex + 1);
647
- const isFinalStep = stepIndex === provider.tutorials.length;
648
- return React.createElement("div", {
649
- "aria-label": `${openReceiveCheckoutLabels.tutorialTitlePrefix} ${provider.name}`,
650
- "aria-modal": true,
651
- className: "or-tutorial-modal",
652
- onClick: (event) => {
653
- if (event.target === event.currentTarget)
654
- options.onClose();
919
+ };
920
+ if (provider.tutorials.length === 0) return null;
921
+ const totalSteps = provider.tutorials.length + 1;
922
+ const stepIndex = Math.max(0, Math.min(provider.tutorials.length, options.index));
923
+ const tutorial = stepIndex === 0 ? void 0 : provider.tutorials[stepIndex - 1];
924
+ const previousIndex = Math.max(0, stepIndex - 1);
925
+ const nextIndex = Math.min(provider.tutorials.length, stepIndex + 1);
926
+ const isFinalStep = stepIndex === provider.tutorials.length;
927
+ const decodeHref = createLightningInvoiceDecodeUrl(options.invoice, options.decodeLinkUrl);
928
+ return React6.createElement(
929
+ "div",
930
+ {
931
+ ref: dialogRef,
932
+ "aria-label": `${checkoutLabels2.tutorialTitlePrefix} ${provider.name}`,
933
+ "aria-modal": true,
934
+ className: orClasses3.tutorialModal,
935
+ onClick: (event) => {
936
+ if (event.target === event.currentTarget) options.onClose();
937
+ },
938
+ onKeyDown: (event) => {
939
+ if (event.key === "Escape") options.onClose();
940
+ trapTab(event);
941
+ },
942
+ role: "dialog",
943
+ tabIndex: -1
944
+ },
945
+ React6.createElement(
946
+ "div",
947
+ {
948
+ className: orClasses3.tutorialBox
949
+ },
950
+ React6.createElement(
951
+ "div",
952
+ {
953
+ className: orClasses3.tutorialHeader
655
954
  },
656
- onKeyDown: (event) => {
657
- if (event.key === "Escape")
658
- options.onClose();
659
- },
660
- role: "dialog",
661
- tabIndex: -1
662
- }, React.createElement("div", {
663
- className: "or-tutorial-dialog"
664
- }, React.createElement("div", {
665
- className: "or-tutorial-header"
666
- }, React.createElement("div", {
667
- className: "or-tutorial-title"
668
- }, React.createElement("img", {
669
- alt: "",
670
- className: "or-tutorial-header-logo",
671
- src: provider.icon
672
- }), React.createElement("h3", null, `${openReceiveCheckoutLabels.tutorialTitlePrefix} ${provider.name}`)), React.createElement("button", {
673
- "aria-label": "Close",
674
- className: "or-tutorial-close",
675
- onClick: options.onClose,
676
- type: "button"
677
- }, "X")), stepIndex === 0
678
- ? React.createElement("div", {
679
- className: "or-tutorial-intro"
680
- }, React.createElement("img", {
955
+ React6.createElement(
956
+ "div",
957
+ {
958
+ className: orClasses3.tutorialTitle
959
+ },
960
+ React6.createElement("img", {
681
961
  alt: "",
682
- className: "or-tutorial-provider-logo",
962
+ className: orClasses3.tutorialHeaderLogo,
683
963
  src: provider.icon
684
- }), React.createElement("p", null, `${openReceiveCheckoutLabels.tutorialIntroPrefix} ${provider.name}.`), React.createElement("p", null, openReceiveCheckoutLabels.tutorialIntroCopy), React.createElement("button", {
685
- className: "or-tutorial-copy",
964
+ }),
965
+ React6.createElement("h3", null, `${checkoutLabels2.tutorialTitlePrefix} ${provider.name}`)
966
+ ),
967
+ React6.createElement(
968
+ "button",
969
+ {
970
+ "aria-label": checkoutLabels2.tutorialClose,
971
+ className: orClasses3.tutorialClose,
972
+ onClick: options.onClose,
973
+ type: "button"
974
+ },
975
+ "X"
976
+ )
977
+ ),
978
+ stepIndex === 0 ? React6.createElement(
979
+ "div",
980
+ {
981
+ className: orClasses3.tutorialIntro
982
+ },
983
+ React6.createElement("img", {
984
+ alt: "",
985
+ className: orClasses3.tutorialProviderLogo,
986
+ src: provider.icon
987
+ }),
988
+ React6.createElement(
989
+ "p",
990
+ null,
991
+ `${checkoutLabels2.tutorialIntroPrefix} ${provider.name}.`
992
+ ),
993
+ React6.createElement("p", null, checkoutLabels2.tutorialIntroCopy),
994
+ React6.createElement(
995
+ "button",
996
+ {
997
+ className: orClasses3.tutorialCopy,
686
998
  onClick: () => void options.onCopy(),
687
999
  type: "button"
688
- }, openReceiveCheckoutLabels.copyInvoice), options.copied
689
- ? React.createElement("p", {
690
- className: "or-tutorial-copy-message"
691
- }, openReceiveCheckoutLabels.tutorialCopiedContinue)
692
- : null)
693
- : React.createElement(React.Fragment, null, React.createElement("div", {
694
- className: "or-tutorial-frame"
695
- }, React.createElement("img", {
1000
+ },
1001
+ checkoutLabels2.copyInvoice
1002
+ ),
1003
+ decodeHref === void 0 ? null : React6.createElement(
1004
+ "a",
1005
+ {
1006
+ className: orClasses3.tutorialCopy,
1007
+ href: decodeHref,
1008
+ rel: "noreferrer",
1009
+ target: "_blank"
1010
+ },
1011
+ checkoutLabels2.decodeInvoice
1012
+ ),
1013
+ options.copied ? React6.createElement(
1014
+ "p",
1015
+ {
1016
+ className: orClasses3.tutorialCopyMessage
1017
+ },
1018
+ checkoutLabels2.tutorialCopiedContinue
1019
+ ) : null
1020
+ ) : React6.createElement(
1021
+ React6.Fragment,
1022
+ null,
1023
+ React6.createElement(
1024
+ "div",
1025
+ {
1026
+ className: orClasses3.tutorialFrame
1027
+ },
1028
+ React6.createElement("img", {
696
1029
  alt: tutorial?.caption ?? "",
697
- className: "or-tutorial-image",
1030
+ className: orClasses3.tutorialImage,
698
1031
  src: tutorial?.image ?? ""
699
- })), React.createElement("p", {
700
- className: "or-tutorial-caption"
701
- }, tutorial?.caption ?? "")), React.createElement("div", {
702
- "aria-hidden": "true",
703
- className: "or-tutorial-steps"
704
- }, Array.from({ length: totalSteps }, (_, index) => React.createElement("span", {
705
- className: index === stepIndex
706
- ? "or-tutorial-step active"
707
- : "or-tutorial-step",
708
- key: index
709
- }))), React.createElement("p", {
710
- className: "or-tutorial-progress"
711
- }, `Step ${stepIndex + 1} of ${totalSteps}`), React.createElement("div", {
712
- className: "or-tutorial-controls"
713
- }, React.createElement("button", {
714
- disabled: stepIndex === 0,
715
- onClick: () => options.onStep(previousIndex),
716
- type: "button"
717
- }, "Back"), React.createElement("button", {
718
- onClick: () => {
719
- if (isFinalStep) {
1032
+ })
1033
+ ),
1034
+ React6.createElement(
1035
+ "p",
1036
+ {
1037
+ className: orClasses3.tutorialCaption
1038
+ },
1039
+ tutorial?.caption ?? ""
1040
+ )
1041
+ ),
1042
+ React6.createElement(
1043
+ "div",
1044
+ {
1045
+ "aria-hidden": "true",
1046
+ className: orClasses3.tutorialSteps
1047
+ },
1048
+ Array.from(
1049
+ { length: totalSteps },
1050
+ (_, index) => React6.createElement("span", {
1051
+ className: index === stepIndex ? orClasses3.tutorialStepActive : orClasses3.tutorialStep,
1052
+ key: index
1053
+ })
1054
+ )
1055
+ ),
1056
+ React6.createElement(
1057
+ "p",
1058
+ {
1059
+ className: orClasses3.tutorialProgress
1060
+ },
1061
+ `Step ${stepIndex + 1} of ${totalSteps}`
1062
+ ),
1063
+ React6.createElement(
1064
+ "div",
1065
+ {
1066
+ className: orClasses3.tutorialControls
1067
+ },
1068
+ React6.createElement(
1069
+ "button",
1070
+ {
1071
+ className: orClasses3.btn,
1072
+ disabled: stepIndex === 0,
1073
+ onClick: () => options.onStep(previousIndex),
1074
+ type: "button"
1075
+ },
1076
+ checkoutLabels2.tutorialBack
1077
+ ),
1078
+ React6.createElement(
1079
+ "button",
1080
+ {
1081
+ className: orClasses3.btn,
1082
+ onClick: () => {
1083
+ if (isFinalStep) {
720
1084
  options.onClose();
721
1085
  return;
722
- }
723
- options.onStep(nextIndex);
1086
+ }
1087
+ options.onStep(nextIndex);
1088
+ },
1089
+ type: "button"
1090
+ },
1091
+ isFinalStep ? checkoutLabels2.tutorialExit : checkoutLabels2.tutorialNext
1092
+ )
1093
+ )
1094
+ )
1095
+ );
1096
+ }
1097
+
1098
+ // src/swap.ts
1099
+ import {
1100
+ createDetailExternalLink,
1101
+ createSwapDisplayModel,
1102
+ createTransactionDetails,
1103
+ createQrPayloadSvg,
1104
+ getSwapRefundFormError,
1105
+ checkoutLabels as checkoutLabels4,
1106
+ createSwapUnavailableModel,
1107
+ swapOptionLimitMessage,
1108
+ orClasses as orClasses5,
1109
+ selectCheckoutDisplayInvoice
1110
+ } from "@openreceive/browser/headless";
1111
+ import * as React8 from "react";
1112
+
1113
+ // src/transaction-details.ts
1114
+ import {
1115
+ checkoutLabels as checkoutLabels3,
1116
+ orClasses as orClasses4,
1117
+ resolveTransactionDetailRows
1118
+ } from "@openreceive/browser/headless";
1119
+ import * as React7 from "react";
1120
+ function TransactionDetails(props) {
1121
+ const rows = resolveTransactionDetailRows(props.state);
1122
+ if (rows.length === 0) return null;
1123
+ return React7.createElement(
1124
+ "details",
1125
+ {
1126
+ className: joinClassNames(orClasses4.transactionDetails, props.className),
1127
+ open: props.open === true ? true : void 0
1128
+ },
1129
+ React7.createElement(
1130
+ "summary",
1131
+ { className: orClasses4.transactionDetailsTitle },
1132
+ checkoutLabels3.transactionDetails
1133
+ ),
1134
+ React7.createElement(
1135
+ "div",
1136
+ { className: orClasses4.transactionDetailsContent },
1137
+ React7.createElement(
1138
+ "dl",
1139
+ { className: orClasses4.swapDetails },
1140
+ rows.flatMap(
1141
+ (row) => renderTransactionDetailRow(row, {
1142
+ clipboard: props.clipboard,
1143
+ onError: props.onError
1144
+ })
1145
+ )
1146
+ )
1147
+ )
1148
+ );
1149
+ }
1150
+ function renderTransactionDetailRow(row, options) {
1151
+ const copyValue = row.copyValue ?? row.value;
1152
+ return [
1153
+ React7.createElement(
1154
+ "dt",
1155
+ { key: `${row.label}-label`, className: orClasses4.swapDetailsDt },
1156
+ row.label
1157
+ ),
1158
+ React7.createElement(
1159
+ "dd",
1160
+ { key: `${row.label}-value`, className: orClasses4.swapDetailsDd },
1161
+ React7.createElement("code", { className: orClasses4.swapDetailsCode }, row.value),
1162
+ React7.createElement(
1163
+ "div",
1164
+ { className: orClasses4.swapDetailsActions },
1165
+ React7.createElement(TransactionDetailCopyButton, {
1166
+ value: copyValue,
1167
+ clipboard: options.clipboard,
1168
+ onError: options.onError
1169
+ }),
1170
+ row.href === void 0 ? null : React7.createElement(
1171
+ "a",
1172
+ {
1173
+ className: orClasses4.swapDetailsLink,
1174
+ href: row.href,
1175
+ rel: "noreferrer",
1176
+ target: "_blank"
1177
+ },
1178
+ row.hrefLabel ?? checkoutLabels3.viewOnExplorer
1179
+ )
1180
+ )
1181
+ )
1182
+ ];
1183
+ }
1184
+ function TransactionDetailCopyButton(props) {
1185
+ const [copied, setCopied] = useTransientValue(false);
1186
+ const label = copied ? checkoutLabels3.copied : "Copy";
1187
+ return React7.createElement(
1188
+ "button",
1189
+ {
1190
+ className: orClasses4.detailCopy,
1191
+ type: "button",
1192
+ "aria-label": label,
1193
+ title: label,
1194
+ onClick: () => {
1195
+ void copyText(props.value, props.clipboard).then(() => setCopied(true)).catch((error) => props.onError?.(error));
1196
+ }
1197
+ },
1198
+ React7.createElement(ClipboardIcon)
1199
+ );
1200
+ }
1201
+
1202
+ // src/swap.ts
1203
+ function renderSwapActions(options) {
1204
+ const shown = options.options.filter((option) => option.provider.length > 0);
1205
+ if (!options.enabled || shown.length === 0) return null;
1206
+ return React8.createElement(
1207
+ "div",
1208
+ {
1209
+ className: orClasses5.swapActions
1210
+ },
1211
+ shown.map((option) => {
1212
+ const disabled = option.available === false;
1213
+ const limitMessage = swapOptionLimitMessage(option, options.checkout);
1214
+ return React8.createElement(
1215
+ "div",
1216
+ {
1217
+ className: orClasses5.swapAction,
1218
+ key: option.pay_in_asset
724
1219
  },
725
- type: "button"
726
- }, isFinalStep ? openReceiveCheckoutLabels.tutorialExit : "Next"))));
1220
+ disabled ? limitMessage === void 0 ? null : React8.createElement("p", { className: orClasses5.swapWarning }, limitMessage) : option.pay_amount === void 0 ? null : React8.createElement(
1221
+ "p",
1222
+ {
1223
+ className: orClasses5.swapEstimate
1224
+ },
1225
+ `Estimated ${option.pay_amount} ${option.label} to settle this checkout.`
1226
+ ),
1227
+ React8.createElement(
1228
+ "button",
1229
+ {
1230
+ className: orClasses5.swapStart,
1231
+ disabled: disabled || options.startingAsset !== null,
1232
+ onClick: disabled ? void 0 : () => options.onStart(option.pay_in_asset),
1233
+ type: "button"
1234
+ },
1235
+ options.startingAsset === option.pay_in_asset ? checkoutLabels4.preparingPayment : checkoutLabels4.createPaymentAddress.replace("{asset}", option.label).replace("{network}", option.network_label)
1236
+ )
1237
+ );
1238
+ })
1239
+ );
727
1240
  }
728
- export function InvoiceSummary(props) {
729
- const { amountLabel, fiatLabel, status, PaymentStateComponent = PaymentState, classNames, className, ...divProps } = props;
730
- return React.createElement("div", {
731
- ...divProps,
732
- className,
733
- [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.meta]: ""
734
- }, amountLabel === undefined
735
- ? null
736
- : React.createElement("span", {
737
- className: classNames?.amount
738
- }, amountLabel), fiatLabel === undefined
739
- ? null
740
- : React.createElement("span", {
741
- className: classNames?.fiat
742
- }, fiatLabel), status === undefined
743
- ? null
744
- : React.createElement(PaymentStateComponent, {
745
- state: status,
746
- className: classNames?.paymentState
747
- }));
1241
+ function renderSwapStartError(message, onRetry) {
1242
+ return React8.createElement(
1243
+ "section",
1244
+ {
1245
+ className: orClasses5.swapPanel
1246
+ },
1247
+ React8.createElement(
1248
+ "div",
1249
+ { role: "alert" },
1250
+ React8.createElement(
1251
+ "strong",
1252
+ { className: orClasses5.swapHeadingTitle },
1253
+ checkoutLabels4.swapStartFailedTitle
1254
+ ),
1255
+ React8.createElement("p", { className: orClasses5.swapWarning }, message),
1256
+ React8.createElement(
1257
+ "button",
1258
+ {
1259
+ className: orClasses5.btn,
1260
+ type: "button",
1261
+ onClick: onRetry
1262
+ },
1263
+ checkoutLabels4.tryAgain
1264
+ )
1265
+ )
1266
+ );
748
1267
  }
749
- export function Checkout(props) {
750
- const { invoice, qrEncoder, logger, onError, lookupInvoice, lookupUrl, refreshInvoice, refreshUrl, refreshHeaders, refreshIdempotencyKey, refreshReason, onState, onPaid, onStartOver, polling, paymentWizard = true, themeSwitcher = false, defaultTheme, themeStorageKey, countryStorageKey, components, classNames, children, className, ...sectionProps } = props;
751
- const checkoutModel = useCheckout({
752
- invoice,
753
- logger,
754
- onError,
755
- lookupInvoice,
756
- lookupUrl,
757
- refreshInvoice,
758
- refreshUrl,
759
- refreshHeaders,
760
- refreshIdempotencyKey,
761
- refreshReason,
762
- onState,
763
- onPaid,
764
- polling
765
- });
766
- const theme = useTheme({
767
- defaultTheme,
768
- storageKey: themeStorageKey
769
- });
770
- const QRCodeComponent = components?.QRCode ?? QRCode;
771
- const InvoiceSummaryComponent = components?.InvoiceSummary ?? InvoiceSummary;
772
- const CopyButton = components?.CopyButton ?? CopyInvoiceButton;
773
- const ButtonComponent = components?.Button;
774
- const PaymentStateComponent = components?.PaymentState ?? PaymentState;
775
- const customChildren = typeof children === "function" ? children(checkoutModel) : children;
776
- const expired = checkoutModel.status === "expired";
777
- const startOver = () => {
778
- if (onStartOver !== undefined) {
779
- onStartOver();
780
- return;
1268
+ function renderSwapPreparing(assetLabel) {
1269
+ return React8.createElement(
1270
+ "section",
1271
+ {
1272
+ className: orClasses5.swapPanel
1273
+ },
1274
+ React8.createElement(WaitingState, {
1275
+ waiting: true,
1276
+ statusTitle: checkoutLabels4.preparingPaymentAddress,
1277
+ statusDetail: checkoutLabels4.preparingPaymentAddressDetail.replace("{asset}", assetLabel)
1278
+ })
1279
+ );
1280
+ }
1281
+ function renderSwapUnavailable(quote, checkout) {
1282
+ const model = createSwapUnavailableModel(quote, checkout);
1283
+ return React8.createElement(
1284
+ "section",
1285
+ {
1286
+ className: orClasses5.swapPanel
1287
+ },
1288
+ React8.createElement(
1289
+ "div",
1290
+ {
1291
+ className: orClasses5.swapHeading
1292
+ },
1293
+ React8.createElement("strong", { className: orClasses5.swapHeadingTitle }, model.title)
1294
+ ),
1295
+ React8.createElement("p", { className: orClasses5.swapWarning }, model.detail),
1296
+ model.range === void 0 ? null : React8.createElement("p", { className: orClasses5.swapWarning }, model.range),
1297
+ React8.createElement("p", { className: orClasses5.swapProgress }, model.hint)
1298
+ );
1299
+ }
1300
+ function renderSwapDepositPanel(options) {
1301
+ const display = createSwapDisplayModel(
1302
+ options.invoice,
1303
+ options.now === void 0 ? {} : { now: options.now }
1304
+ );
1305
+ if (display === void 0) return null;
1306
+ const memo = display.depositMemo;
1307
+ const backButton = React8.createElement(
1308
+ "button",
1309
+ {
1310
+ className: orClasses5.swapBack,
1311
+ onClick: options.onBackToLightning,
1312
+ type: "button"
1313
+ },
1314
+ checkoutLabels4.payWithLightningInstead
1315
+ );
1316
+ const heading = React8.createElement(
1317
+ "div",
1318
+ {
1319
+ className: orClasses5.swapHeading
1320
+ },
1321
+ React8.createElement(
1322
+ "strong",
1323
+ { className: orClasses5.swapHeadingTitle },
1324
+ display.providerStateLabel
1325
+ ),
1326
+ React8.createElement(
1327
+ "span",
1328
+ { className: orClasses5.swapHeadingDetail },
1329
+ display.providerStateDetail
1330
+ )
1331
+ );
1332
+ const waitingCard = (countdownLabel) => React8.createElement(WaitingState, {
1333
+ waiting: true,
1334
+ statusTitle: display.providerStateLabel,
1335
+ statusDetail: display.providerStateDetail,
1336
+ ...countdownLabel === void 0 ? {} : { countdownLabel }
1337
+ });
1338
+ if (display.state === "creating") {
1339
+ return React8.createElement(
1340
+ "section",
1341
+ {
1342
+ className: orClasses5.swapPanel
1343
+ },
1344
+ waitingCard(),
1345
+ backButton
1346
+ );
1347
+ }
1348
+ if (display.state === "settled") {
1349
+ const highlightRows = [
1350
+ ...display.depositTxId === void 0 ? [] : renderSwapCopyRow("Deposit transaction", display.depositTxId, {
1351
+ ...options,
1352
+ kind: "tx",
1353
+ payInAsset: display.payInAsset
1354
+ }),
1355
+ ...display.payoutTxId === void 0 ? [] : renderSwapCopyRow("Lightning payout", display.payoutTxId, options),
1356
+ ...display.providerOrderId === void 0 ? [] : renderSwapCopyRow("Provider order", display.providerOrderId, options)
1357
+ ];
1358
+ const displayInvoice = options.checkout === void 0 ? void 0 : selectCheckoutDisplayInvoice(options.checkout);
1359
+ const bolt11 = typeof options.invoice.invoice === "string" ? options.invoice.invoice : typeof displayInvoice?.invoice === "string" ? displayInvoice.invoice : void 0;
1360
+ return React8.createElement(
1361
+ "section",
1362
+ {
1363
+ className: orClasses5.swapPanel
1364
+ },
1365
+ React8.createElement(WaitingState, {
1366
+ waiting: false,
1367
+ statusTitle: display.providerStateLabel,
1368
+ statusDetail: display.providerStateDetail
1369
+ }),
1370
+ highlightRows.length === 0 ? null : React8.createElement(
1371
+ "dl",
1372
+ {
1373
+ className: orClasses5.swapDetails
1374
+ },
1375
+ highlightRows
1376
+ ),
1377
+ React8.createElement(TransactionDetails, {
1378
+ state: createTransactionDetails({
1379
+ ...options.checkout === void 0 ? {} : {
1380
+ reference: options.checkout.reference,
1381
+ checkout_id: options.checkout.checkout_id,
1382
+ ...options.checkout.fiat === void 0 ? {} : { fiat_quote: { fiat: options.checkout.fiat } },
1383
+ ...options.checkout.amount_msats === void 0 ? {} : { amount_msats: options.checkout.amount_msats }
1384
+ },
1385
+ invoice_id: options.invoice.invoice_id,
1386
+ ...bolt11 === void 0 ? {} : { invoice: bolt11 },
1387
+ rail: options.invoice.rail,
1388
+ ...options.invoice.payment_hash === void 0 ? displayInvoice?.payment_hash === void 0 ? {} : { payment_hash: displayInvoice.payment_hash } : { payment_hash: options.invoice.payment_hash },
1389
+ ...options.invoice.amount_msats === void 0 ? {} : { amount_msats: options.invoice.amount_msats },
1390
+ ...options.invoice.fiat_quote === void 0 ? {} : { fiat_quote: options.invoice.fiat_quote },
1391
+ ...options.invoice.transaction_state === void 0 ? {} : { transaction_state: options.invoice.transaction_state },
1392
+ ...options.invoice.workflow_state === void 0 ? {} : { workflow_state: options.invoice.workflow_state },
1393
+ ...options.invoice.expires_at === void 0 ? {} : { expires_at: options.invoice.expires_at },
1394
+ ...(options.checkout?.paid_at ?? options.invoice.settled_at) === void 0 ? {} : { settled_at: options.checkout?.paid_at ?? options.invoice.settled_at },
1395
+ ...options.invoice.swap === void 0 ? {} : { swap: options.invoice.swap }
1396
+ }),
1397
+ clipboard: options.clipboard,
1398
+ onError: options.onError
1399
+ })
1400
+ );
1401
+ }
1402
+ if (display.state === "progress") {
1403
+ return React8.createElement(
1404
+ "section",
1405
+ {
1406
+ className: orClasses5.swapPanel
1407
+ },
1408
+ waitingCard(),
1409
+ renderSwapSupportDetails(display, options)
1410
+ );
1411
+ }
1412
+ if (display.state === "expired") {
1413
+ return React8.createElement(
1414
+ "section",
1415
+ {
1416
+ className: orClasses5.swapPanel
1417
+ },
1418
+ heading,
1419
+ React8.createElement(
1420
+ "p",
1421
+ {
1422
+ className: orClasses5.swapWarning
1423
+ },
1424
+ "This payment address expired without a detected payment. Create a new payment address to try again."
1425
+ ),
1426
+ renderSwapSupportDetails(display, options),
1427
+ backButton
1428
+ );
1429
+ }
1430
+ if (display.state === "refund_required") {
1431
+ return React8.createElement(
1432
+ "section",
1433
+ {
1434
+ className: orClasses5.swapPanel
1435
+ },
1436
+ heading,
1437
+ renderSwapRefundFacts(display, options),
1438
+ React8.createElement(
1439
+ "p",
1440
+ {
1441
+ className: orClasses5.swapWarning
1442
+ },
1443
+ `Use a ${display.networkLabel} address you control. Do not paste the deposit address.`
1444
+ ),
1445
+ React8.createElement(SwapRefundForm, {
1446
+ attemptId: display.attemptId,
1447
+ payInAsset: display.payInAsset,
1448
+ networkLabel: display.networkLabel,
1449
+ submittedRefundAddress: display.refundAddress,
1450
+ refundAllowed: display.refundAllowed,
1451
+ onRefund: options.onRefund,
1452
+ onError: options.onError
1453
+ }),
1454
+ renderSwapSupportDetails(display, options),
1455
+ renderSwapRefundReturnWarning()
1456
+ );
1457
+ }
1458
+ if (display.state === "refund_pending" || display.state === "refunded") {
1459
+ return React8.createElement(
1460
+ "section",
1461
+ {
1462
+ className: orClasses5.swapPanel
1463
+ },
1464
+ heading,
1465
+ renderSwapRefundFacts(display, options),
1466
+ React8.createElement(
1467
+ "dl",
1468
+ {
1469
+ className: orClasses5.swapDetails
1470
+ },
1471
+ display.refundAddress === void 0 ? null : renderSwapCopyRow("Refund address", display.refundAddress, {
1472
+ ...options,
1473
+ kind: "address",
1474
+ payInAsset: display.payInAsset
1475
+ }),
1476
+ display.refundTxId === void 0 ? null : renderSwapCopyRow("Refund transaction", display.refundTxId, {
1477
+ ...options,
1478
+ kind: "tx",
1479
+ payInAsset: display.payInAsset
1480
+ })
1481
+ ),
1482
+ renderSwapSupportDetails(display, options),
1483
+ renderSwapRefundReturnWarning()
1484
+ );
1485
+ }
1486
+ if (display.state === "deposit") {
1487
+ return React8.createElement(
1488
+ "section",
1489
+ {
1490
+ className: orClasses5.swapPanel
1491
+ },
1492
+ React8.createElement(
1493
+ "p",
1494
+ {
1495
+ className: orClasses5.swapInstruction
1496
+ },
1497
+ "Pay ",
1498
+ React8.createElement("strong", null, `${display.depositAmount} ${display.assetLabel}`),
1499
+ " to this address"
1500
+ ),
1501
+ renderSwapNetworkWarning(display),
1502
+ React8.createElement(
1503
+ "div",
1504
+ {
1505
+ className: orClasses5.swapDepositLayout
1506
+ },
1507
+ React8.createElement(SwapPayloadQRCode, {
1508
+ payload: display.qrPayload,
1509
+ encoder: options.encoder,
1510
+ onError: options.onError
1511
+ }),
1512
+ React8.createElement(
1513
+ "div",
1514
+ {
1515
+ className: orClasses5.swapDepositSide
1516
+ },
1517
+ React8.createElement(
1518
+ "dl",
1519
+ {
1520
+ className: orClasses5.swapDetails
1521
+ },
1522
+ // Explorer stays hidden until payment completes (tx ids / settled details).
1523
+ // No explorer link on the LIVE deposit row: the address has nothing on
1524
+ // chain yet, and sending the payer to a third-party explorer mid-payment
1525
+ // is not something this pane does. The settled/refund panes link theirs.
1526
+ renderSwapCopyRow("Address", display.depositAddress, {
1527
+ ...options,
1528
+ selectable: true
1529
+ }),
1530
+ memo === void 0 ? null : renderSwapCopyRow("Memo", memo, options),
1531
+ renderSwapCopyRow("Amount", display.depositAmount, { ...options, selectable: true })
1532
+ ),
1533
+ waitingCard(display.countdownLabel),
1534
+ renderSwapFeeBreakdown(display.feeBreakdown)
1535
+ )
1536
+ ),
1537
+ backButton
1538
+ );
1539
+ }
1540
+ return React8.createElement(
1541
+ "section",
1542
+ {
1543
+ className: orClasses5.swapPanel
1544
+ },
1545
+ heading,
1546
+ React8.createElement(
1547
+ "p",
1548
+ { className: orClasses5.swapWarning },
1549
+ checkoutLabels4.supportReviewNeeded
1550
+ ),
1551
+ renderSwapSupportDetails(display, options),
1552
+ backButton
1553
+ );
1554
+ }
1555
+ function renderSwapNetworkWarning(display) {
1556
+ const emphasisStart = display.networkWarning.indexOf(display.networkWarningEmphasis);
1557
+ const before = emphasisStart === -1 ? display.networkWarning : display.networkWarning.slice(0, emphasisStart);
1558
+ const after = emphasisStart === -1 ? "" : display.networkWarning.slice(emphasisStart + display.networkWarningEmphasis.length);
1559
+ return React8.createElement(
1560
+ "div",
1561
+ {
1562
+ role: "alert",
1563
+ className: orClasses5.swapNetworkWarning
1564
+ },
1565
+ React8.createElement(
1566
+ "svg",
1567
+ {
1568
+ xmlns: "http://www.w3.org/2000/svg",
1569
+ fill: "none",
1570
+ viewBox: "0 0 24 24",
1571
+ className: orClasses5.swapNetworkWarningIcon,
1572
+ "aria-hidden": "true"
1573
+ },
1574
+ React8.createElement("path", {
1575
+ strokeLinecap: "round",
1576
+ strokeLinejoin: "round",
1577
+ strokeWidth: 2,
1578
+ d: "M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z"
1579
+ })
1580
+ ),
1581
+ React8.createElement(
1582
+ "div",
1583
+ { className: orClasses5.swapNetworkWarningContent },
1584
+ React8.createElement(
1585
+ "p",
1586
+ { className: orClasses5.swapNetworkWarningTitle },
1587
+ display.networkWarningTitle
1588
+ ),
1589
+ React8.createElement(
1590
+ "p",
1591
+ { className: orClasses5.swapNetworkWarningBody },
1592
+ before,
1593
+ emphasisStart === -1 ? null : React8.createElement(
1594
+ "strong",
1595
+ { className: orClasses5.swapNetworkWarningEmphasis },
1596
+ display.networkWarningEmphasis
1597
+ ),
1598
+ after
1599
+ )
1600
+ )
1601
+ );
1602
+ }
1603
+ function renderSwapRefundFacts(display, options) {
1604
+ const rows = [
1605
+ ...display.depositReceivedAmount === void 0 ? [] : renderSwapCopyRow(
1606
+ "Amount received",
1607
+ `${display.depositReceivedAmount} ${display.assetLabel}`,
1608
+ options
1609
+ ),
1610
+ ...display.depositReceivedAmount === void 0 ? [] : renderSwapCopyRow(
1611
+ "Amount required",
1612
+ `${display.depositAmount} ${display.assetLabel}`,
1613
+ options
1614
+ ),
1615
+ ...display.refundAmount === void 0 ? [] : renderSwapCopyRow(
1616
+ "Estimated refund",
1617
+ `${display.refundAmount} ${display.assetLabel}`,
1618
+ options
1619
+ )
1620
+ ];
1621
+ if (rows.length === 0) return null;
1622
+ return React8.createElement("dl", { className: orClasses5.swapDetails }, rows);
1623
+ }
1624
+ function renderSwapFeeBreakdown(breakdown) {
1625
+ if (breakdown === void 0) return null;
1626
+ const feeValue = breakdown.feePercent === void 0 ? breakdown.fee : `${breakdown.fee} (${breakdown.feePercent})`;
1627
+ return React8.createElement(
1628
+ "div",
1629
+ { className: orClasses5.swapBreakdown },
1630
+ React8.createElement(
1631
+ "p",
1632
+ { className: orClasses5.swapBreakdownTitle },
1633
+ checkoutLabels4.paymentBreakdown
1634
+ ),
1635
+ React8.createElement(
1636
+ "dl",
1637
+ { className: joinClassNames(orClasses5.swapDetails, orClasses5.swapBreakdownRows) },
1638
+ React8.createElement(
1639
+ "dt",
1640
+ { key: "cart-label", className: orClasses5.swapDetailsDt },
1641
+ checkoutLabels4.cartTotal
1642
+ ),
1643
+ React8.createElement("dd", { key: "cart-value" }, breakdown.cartTotal),
1644
+ React8.createElement(
1645
+ "dt",
1646
+ { key: "send-label", className: orClasses5.swapDetailsDt },
1647
+ checkoutLabels4.youSend
1648
+ ),
1649
+ React8.createElement("dd", { key: "send-value" }, breakdown.youSend),
1650
+ React8.createElement(
1651
+ "dt",
1652
+ { key: "fee-label", className: orClasses5.swapDetailsDt },
1653
+ checkoutLabels4.swapAndNetworkFees
1654
+ ),
1655
+ React8.createElement("dd", { key: "fee-value" }, feeValue)
1656
+ )
1657
+ );
1658
+ }
1659
+ function renderSwapCopyRow(label, value, options, displayValue = value) {
1660
+ const link = options.href === void 0 ? options.kind === void 0 ? void 0 : createDetailExternalLink({
1661
+ kind: options.kind,
1662
+ value,
1663
+ ...options.payInAsset === void 0 ? {} : { payInAsset: options.payInAsset }
1664
+ }) : {
1665
+ href: options.href,
1666
+ hrefLabel: options.hrefLabel ?? checkoutLabels4.viewOnExplorer
1667
+ };
1668
+ const valueField = options.selectable === true ? React8.createElement("input", {
1669
+ className: orClasses5.swapDetailsInput,
1670
+ type: "text",
1671
+ readOnly: true,
1672
+ value: displayValue,
1673
+ "aria-label": label,
1674
+ ...selectAllInputHandlers()
1675
+ }) : React8.createElement("code", { className: orClasses5.swapDetailsCode }, displayValue);
1676
+ return [
1677
+ React8.createElement("dt", { key: `${label}-label`, className: orClasses5.swapDetailsDt }, label),
1678
+ React8.createElement(
1679
+ "dd",
1680
+ { key: `${label}-value`, className: orClasses5.swapDetailsDd },
1681
+ valueField,
1682
+ React8.createElement(
1683
+ "div",
1684
+ { className: orClasses5.swapDetailsActions },
1685
+ React8.createElement(SwapCopyButton, {
1686
+ value,
1687
+ clipboard: options.clipboard,
1688
+ onError: options.onError
1689
+ }),
1690
+ link === void 0 ? null : React8.createElement(
1691
+ "a",
1692
+ {
1693
+ className: orClasses5.swapDetailsLink,
1694
+ href: link.href,
1695
+ rel: "noreferrer",
1696
+ target: "_blank"
1697
+ },
1698
+ link.hrefLabel
1699
+ )
1700
+ )
1701
+ )
1702
+ ];
1703
+ }
1704
+ function SwapCopyButton(props) {
1705
+ const [copied, showCopied] = useTransientValue(false);
1706
+ const label = copied ? checkoutLabels4.copied : "Copy";
1707
+ return React8.createElement(
1708
+ "button",
1709
+ {
1710
+ className: orClasses5.detailCopy,
1711
+ onClick: () => {
1712
+ void copyText(props.value, props.clipboard).then(() => showCopied(true)).catch(props.onError);
1713
+ },
1714
+ type: "button",
1715
+ "aria-label": label,
1716
+ title: label
1717
+ },
1718
+ React8.createElement(ClipboardIcon)
1719
+ );
1720
+ }
1721
+ function renderSwapSupportDetails(display, options) {
1722
+ const rowOptions = { ...options, payInAsset: display.payInAsset };
1723
+ const rows = [
1724
+ ...display.depositTxId === void 0 ? [] : renderSwapCopyRow("Deposit transaction", display.depositTxId, rowOptions),
1725
+ ...display.payoutTxId === void 0 ? [] : renderSwapCopyRow("Lightning payout", display.payoutTxId, options),
1726
+ ...display.refundTxId === void 0 ? [] : renderSwapCopyRow("Refund transaction", display.refundTxId, rowOptions),
1727
+ ...display.providerOrderId === void 0 ? [] : renderSwapCopyRow("Provider order", display.providerOrderId, options)
1728
+ ];
1729
+ if (rows.length === 0) return null;
1730
+ return React8.createElement(
1731
+ "details",
1732
+ {
1733
+ className: orClasses5.swapSupport
1734
+ },
1735
+ React8.createElement("summary", { className: orClasses5.swapSupportTitle }, "Payment details"),
1736
+ React8.createElement(
1737
+ "div",
1738
+ { className: orClasses5.swapSupportContent },
1739
+ React8.createElement("dl", { className: orClasses5.swapDetails }, rows)
1740
+ )
1741
+ );
1742
+ }
1743
+ function renderSwapRefundReturnWarning() {
1744
+ return React8.createElement(
1745
+ "p",
1746
+ {
1747
+ className: orClasses5.swapWarning
1748
+ },
1749
+ checkoutLabels4.refundReturnWarning
1750
+ );
1751
+ }
1752
+ var REFUND_ADDRESS_DRAFT_LIMIT = 8;
1753
+ var refundAddressDraftByAttempt = /* @__PURE__ */ new Map();
1754
+ function setRefundAddressDraft(attemptId, value) {
1755
+ refundAddressDraftByAttempt.delete(attemptId);
1756
+ refundAddressDraftByAttempt.set(attemptId, value);
1757
+ while (refundAddressDraftByAttempt.size > REFUND_ADDRESS_DRAFT_LIMIT) {
1758
+ const oldest = refundAddressDraftByAttempt.keys().next().value;
1759
+ if (oldest === void 0) break;
1760
+ refundAddressDraftByAttempt.delete(oldest);
1761
+ }
1762
+ }
1763
+ function SwapRefundForm(props) {
1764
+ const [refundAddress, setRefundAddress] = React8.useState(
1765
+ () => refundAddressDraftByAttempt.get(props.attemptId) ?? ""
1766
+ );
1767
+ React8.useEffect(() => {
1768
+ setRefundAddress(refundAddressDraftByAttempt.get(props.attemptId) ?? "");
1769
+ }, [props.attemptId]);
1770
+ const [submitting, setSubmitting] = React8.useState(false);
1771
+ const [showAddressError, setShowAddressError] = React8.useState(false);
1772
+ const address = refundAddress.trim();
1773
+ const addressError = getSwapRefundFormError(props.payInAsset, address, props.networkLabel);
1774
+ const confirm = address.length > 0 && props.submittedRefundAddress !== void 0 && props.submittedRefundAddress === address;
1775
+ const disabled = submitting || !props.refundAllowed;
1776
+ const showError = showAddressError && addressError !== void 0;
1777
+ return React8.createElement(
1778
+ "form",
1779
+ {
1780
+ className: orClasses5.swapRefund,
1781
+ noValidate: true,
1782
+ onSubmit: (event) => {
1783
+ event.preventDefault();
1784
+ if (addressError !== void 0 || !props.refundAllowed) {
1785
+ setShowAddressError(true);
1786
+ return;
781
1787
  }
782
- void checkoutModel.refreshExpiredInvoice().catch((error) => {
783
- onError?.(error);
784
- });
1788
+ setSubmitting(true);
1789
+ void props.onRefund(props.attemptId, address, confirm).catch(props.onError).finally(() => setSubmitting(false));
1790
+ }
1791
+ },
1792
+ props.submittedRefundAddress === void 0 ? null : React8.createElement(
1793
+ "p",
1794
+ {
1795
+ className: orClasses5.swapWarning
1796
+ },
1797
+ checkoutLabels4.confirmRefundTo.replace("{address}", props.submittedRefundAddress)
1798
+ ),
1799
+ React8.createElement("input", {
1800
+ autoComplete: "off",
1801
+ "aria-invalid": showError ? true : void 0,
1802
+ className: showError ? orClasses5.swapRefundInputInvalid : orClasses5.swapRefundInput,
1803
+ onChange: (event) => {
1804
+ const value = event.currentTarget.value;
1805
+ setRefundAddressDraft(props.attemptId, value);
1806
+ setRefundAddress(value);
1807
+ },
1808
+ onBlur: () => {
1809
+ if (refundAddress.trim().length > 0) setShowAddressError(true);
1810
+ },
1811
+ placeholder: checkoutLabels4.refundAddressPlaceholder.replace("{network}", props.networkLabel),
1812
+ required: true,
1813
+ type: "text",
1814
+ value: refundAddress
1815
+ }),
1816
+ showError ? React8.createElement(
1817
+ "p",
1818
+ {
1819
+ className: orClasses5.swapRefundError,
1820
+ role: "alert"
1821
+ },
1822
+ addressError
1823
+ ) : null,
1824
+ props.submittedRefundAddress === void 0 ? React8.createElement(
1825
+ "p",
1826
+ {
1827
+ className: orClasses5.swapRefundHint
1828
+ },
1829
+ `Make sure you control this ${props.networkLabel} address. Refunds sent to the wrong address usually cannot be recovered.`
1830
+ ) : null,
1831
+ React8.createElement(
1832
+ "button",
1833
+ {
1834
+ className: orClasses5.btn,
1835
+ disabled,
1836
+ type: "submit"
1837
+ },
1838
+ submitting ? checkoutLabels4.submitting : confirm ? checkoutLabels4.confirmRefund : checkoutLabels4.reviewRefundAddress
1839
+ )
1840
+ );
1841
+ }
1842
+ function SwapPayloadQRCode(props) {
1843
+ const [svg, setSvg] = React8.useState("");
1844
+ const onErrorRef = React8.useRef(props.onError);
1845
+ onErrorRef.current = props.onError;
1846
+ 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);
1852
+ });
1853
+ return () => {
1854
+ cancelled = true;
785
1855
  };
786
- return React.createElement("section", {
787
- ...sectionProps,
788
- className: joinClassNames(className, classNames?.root),
789
- [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.root]: "",
790
- ...theme.attributes
791
- }, customChildren === undefined
792
- ? [
793
- themeSwitcher
794
- ? React.createElement(ThemeToggle, {
795
- key: "theme",
796
- className: classNames?.themeToggle,
797
- theme: theme.theme,
798
- resolvedTheme: theme.resolvedTheme,
799
- onThemeChange: theme.setTheme,
800
- ButtonComponent
1856
+ }, [props.payload, props.encoder]);
1857
+ const imageSource = svg.length === 0 ? void 0 : `data:image/svg+xml;charset=utf-8,${encodeURIComponent(svg)}`;
1858
+ return React8.createElement("img", {
1859
+ alt: "",
1860
+ className: orClasses5.swapQr,
1861
+ src: imageSource
1862
+ });
1863
+ }
1864
+
1865
+ // src/wizard.ts
1866
+ function PaymentWizard(props) {
1867
+ const [selection, setSelection] = React9.useState(
1868
+ () => createPaymentWizardController().getSelection()
1869
+ );
1870
+ const [activeTutorial, setActiveTutorial] = React9.useState(null);
1871
+ const [startedSwapInvoice, setStartedSwapInvoice] = React9.useState(null);
1872
+ const [dismissedSwapInvoiceId, setDismissedSwapInvoiceId] = React9.useState(null);
1873
+ const [selectedSwapAsset, setSelectedSwapAsset] = React9.useState(null);
1874
+ const [selectedSwapNetworks, setSelectedSwapNetworks] = React9.useState(
1875
+ {}
1876
+ );
1877
+ const [selectedPickerKey, setSelectedPickerKey] = React9.useState(null);
1878
+ const autoSwapAttemptedRef = React9.useRef(/* @__PURE__ */ new Set());
1879
+ const fetcher = props.fetch ?? globalThis.fetch;
1880
+ const checkout = props.checkout;
1881
+ const reference = checkout?.reference;
1882
+ const session = useCheckoutSession({
1883
+ snapshot: () => checkout,
1884
+ 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
1893
+ },
1894
+ onSwapStarted: (invoice) => props.onSwapStarted?.(invoice),
1895
+ ...props.logger === void 0 ? {} : { logger: props.logger },
1896
+ onError: (error) => props.onError?.(error)
1897
+ });
1898
+ const { startSwap, swapStartError, startingSwapAsset: swapStartingAsset } = session;
1899
+ const clearSwapFocus = React9.useCallback(() => {
1900
+ setSelectedSwapAsset(null);
1901
+ setSelectedPickerKey(null);
1902
+ setSelectedSwapNetworks({});
1903
+ session.clearSwapStartError();
1904
+ }, [session]);
1905
+ const onSwapFocusChange = props.onSwapFocusChange;
1906
+ React9.useEffect(() => {
1907
+ onSwapFocusChange?.(selectedSwapAsset !== null);
1908
+ return () => onSwapFocusChange?.(false);
1909
+ }, [selectedSwapAsset, onSwapFocusChange]);
1910
+ const swapOptions = React9.useMemo(() => {
1911
+ const methods = checkout?.payment_methods ?? [];
1912
+ return { enabled: methods.length > 0, options: methods };
1913
+ }, [checkout]);
1914
+ const currentSwapInvoice = React9.useMemo(
1915
+ () => selectCurrentSwapInvoice(checkout, startedSwapInvoice, dismissedSwapInvoiceId),
1916
+ [checkout, startedSwapInvoice, dismissedSwapInvoiceId]
1917
+ );
1918
+ const now = useTickingUnixSeconds(currentSwapInvoice !== void 0);
1919
+ const selectSwapAsset = React9.useCallback(
1920
+ (payInAsset) => {
1921
+ if (props.prefix === void 0) return;
1922
+ autoSwapAttemptedRef.current.delete(payInAsset);
1923
+ session.clearSwapStartError();
1924
+ setSelectedSwapAsset(payInAsset);
1925
+ },
1926
+ [props.prefix, session]
1927
+ );
1928
+ const refundSwap = React9.useCallback(
1929
+ async (attemptId, refundAddress, confirm) => {
1930
+ const prefix = props.prefix;
1931
+ if (prefix === void 0 || reference === void 0 || fetcher === void 0) {
1932
+ return;
1933
+ }
1934
+ try {
1935
+ const invoice = await requestSwapRefund({
1936
+ fetch: fetcher,
1937
+ prefix,
1938
+ reference,
1939
+ invoices: [startedSwapInvoice, ...checkout?.invoices ?? []],
1940
+ attemptId,
1941
+ refundAddress,
1942
+ confirm,
1943
+ ...props.logger === void 0 ? {} : { logger: props.logger }
1944
+ });
1945
+ setStartedSwapInvoice(invoice);
1946
+ setDismissedSwapInvoiceId(null);
1947
+ } catch (error) {
1948
+ props.onError?.(error);
1949
+ }
1950
+ },
1951
+ [
1952
+ props.prefix,
1953
+ reference,
1954
+ fetcher,
1955
+ props.onError,
1956
+ props.logger,
1957
+ startedSwapInvoice,
1958
+ checkout?.invoices
1959
+ ]
1960
+ );
1961
+ const updateWizardSelection = React9.useCallback(
1962
+ (apply) => {
1963
+ setSelection((current) => apply(createPaymentWizardController({ selection: current })));
1964
+ },
1965
+ []
1966
+ );
1967
+ const model = createPaymentWizardModel(selection);
1968
+ const { wizard } = model;
1969
+ const routeAssetDisplays = createWizardRouteAssetDisplays(model.routeAssets, {
1970
+ selectedRoute: model.selectedRoute
1971
+ });
1972
+ const routeDisplays = createWizardRouteDisplays(wizard.routes);
1973
+ const showRoutePicker = routeAssetDisplays.length > 0 && (model.selectedRoute === null || routeDisplays.length === 0);
1974
+ const activeTutorialProvider = activeTutorial === null ? void 0 : routeDisplays.flatMap((route) => route.providers).find((provider) => provider.id === activeTutorial.providerId);
1975
+ const swapAssetOptions = swapOptions.enabled ? swapOptions.options.filter((option) => option.provider.length > 0) : [];
1976
+ const stickySwapInvoiceRef = React9.useRef(void 0);
1977
+ if (currentSwapInvoice !== void 0 && currentSwapInvoice.invoice_id !== dismissedSwapInvoiceId) {
1978
+ stickySwapInvoiceRef.current = currentSwapInvoice;
1979
+ } else if (stickySwapInvoiceRef.current?.invoice_id === dismissedSwapInvoiceId) {
1980
+ stickySwapInvoiceRef.current = void 0;
1981
+ }
1982
+ const activeSwapForAsset = selectedSwapAsset === null ? void 0 : currentSwapInvoice !== void 0 && currentSwapInvoice.swap?.pay_in_asset === selectedSwapAsset ? currentSwapInvoice : stickySwapInvoiceRef.current?.swap?.pay_in_asset === selectedSwapAsset ? stickySwapInvoiceRef.current : void 0;
1983
+ React9.useEffect(() => {
1984
+ if (selectedSwapAsset === null) return;
1985
+ if (props.prefix === void 0) return;
1986
+ if (activeSwapForAsset !== void 0) return;
1987
+ if (autoSwapAttemptedRef.current.has(selectedSwapAsset)) return;
1988
+ autoSwapAttemptedRef.current.add(selectedSwapAsset);
1989
+ void startSwap(selectedSwapAsset);
1990
+ }, [selectedSwapAsset, activeSwapForAsset, props.prefix, startSwap]);
1991
+ const selectedSwapOption = selectedSwapAsset === null ? void 0 : swapAssetOptions.find((option) => option.pay_in_asset === selectedSwapAsset) ?? session.swapQuotes[selectedSwapAsset];
1992
+ const selectedSwapQuote = selectedSwapAsset === null ? void 0 : session.swapQuotes[selectedSwapAsset];
1993
+ const selectedSwapLabel = selectedSwapOption?.label ?? "this coin";
1994
+ if (selectedSwapAsset !== null) {
1995
+ return React9.createElement(
1996
+ "div",
1997
+ {
1998
+ className: joinClassNames(orClasses6.wizard, props.className)
1999
+ },
2000
+ React9.createElement(
2001
+ "div",
2002
+ { className: orClasses6.wizardBody },
2003
+ renderWizardBackBreadcrumb(
2004
+ selectedSwapOption === void 0 ? selectedSwapLabel : `${selectedSwapOption.label} \xB7 ${selectedSwapOption.network_label}`,
2005
+ clearSwapFocus
2006
+ ),
2007
+ React9.createElement(
2008
+ "div",
2009
+ {
2010
+ className: orClasses6.wizardResults
2011
+ },
2012
+ swapStartError !== void 0 && activeSwapForAsset === void 0 ? renderSwapStartError(swapStartError, () => {
2013
+ autoSwapAttemptedRef.current.delete(selectedSwapAsset);
2014
+ void startSwap(selectedSwapAsset);
2015
+ }) : activeSwapForAsset !== void 0 ? renderSwapDepositPanel({
2016
+ invoice: activeSwapForAsset,
2017
+ checkout,
2018
+ now,
2019
+ encoder: props.qrEncoder,
2020
+ clipboard: props.clipboard,
2021
+ logger: props.logger,
2022
+ onError: props.onError,
2023
+ onRefund: refundSwap,
2024
+ onBackToLightning: () => {
2025
+ setDismissedSwapInvoiceId(activeSwapForAsset.invoice_id);
2026
+ clearSwapFocus();
2027
+ void props.onRequestLightning?.();
2028
+ }
2029
+ }) : selectedSwapQuote !== void 0 && !selectedSwapQuote.available ? renderSwapUnavailable(selectedSwapQuote, checkout) : renderSwapPreparing(selectedSwapLabel)
2030
+ )
2031
+ )
2032
+ );
2033
+ }
2034
+ return React9.createElement(
2035
+ "div",
2036
+ {
2037
+ className: joinClassNames(orClasses6.wizard, props.className)
2038
+ },
2039
+ selection.selectedMethod === null ? renderCompactPaymentMethodSelector({
2040
+ swapAssetOptions,
2041
+ // Create-checkout snapshot has no payment_methods yet; order status fills them
2042
+ // after the provider catalog (ccies) warms. Show a loader instead of Crypto.
2043
+ currenciesLoading: checkout?.payment_methods === void 0,
2044
+ checkout: checkout ?? void 0,
2045
+ selectedPickerKey,
2046
+ startingAsset: swapStartingAsset,
2047
+ selectedSwapNetworks,
2048
+ onSelectPicker: (key, previousKey) => {
2049
+ setSelectedPickerKey(key);
2050
+ const entries = buildMethodGridEntries(paymentMethods, swapAssetOptions);
2051
+ setSelectedSwapNetworks(
2052
+ (current) => updateSelectedSwapNetworks({
2053
+ entries,
2054
+ nextKey: key,
2055
+ previousKey,
2056
+ selectedNetworks: current
2057
+ })
2058
+ );
2059
+ },
2060
+ onSelectNetwork: (groupKey, payInAsset) => {
2061
+ setSelectedSwapNetworks((current) => ({
2062
+ ...current,
2063
+ [groupKey]: payInAsset
2064
+ }));
2065
+ },
2066
+ onContinueMethod: (methodId) => {
2067
+ updateWizardSelection(
2068
+ (controller) => controller.selectMethod(methodId)
2069
+ );
2070
+ if (methodId === "bitcoin") {
2071
+ void props.onRequestLightning?.();
2072
+ }
2073
+ },
2074
+ onContinueSwap: selectSwapAsset
2075
+ }) : null,
2076
+ selection.selectedMethod === null ? null : React9.createElement(
2077
+ "div",
2078
+ { className: orClasses6.wizardBody },
2079
+ renderWizardBreadcrumbs({
2080
+ method: selection.selectedMethod,
2081
+ selectedRoute: model.selectedRoute,
2082
+ routeAssets: routeAssetDisplays,
2083
+ onChangeMethod: () => {
2084
+ updateWizardSelection((controller) => controller.changeMethod());
2085
+ },
2086
+ onChangeRoute: () => {
2087
+ updateWizardSelection((controller) => controller.update({ type: "change_route" }));
2088
+ }
2089
+ }),
2090
+ showRoutePicker && selection.selectedMethod === "bitcoin" ? renderRoutePicker({
2091
+ assets: routeAssetDisplays,
2092
+ method: "bitcoin",
2093
+ onSelectRoute: (route) => {
2094
+ updateWizardSelection((controller) => controller.selectRoute(route));
2095
+ }
2096
+ }) : null,
2097
+ React9.createElement(
2098
+ "div",
2099
+ {
2100
+ className: orClasses6.wizardResults
2101
+ },
2102
+ routeDisplays.length === 0 ? React9.createElement(
2103
+ "p",
2104
+ {
2105
+ className: orClasses6.wizardEmpty
2106
+ },
2107
+ getWizardEmptyMessage()
2108
+ ) : routeDisplays.map((route) => {
2109
+ const routeSwapOptions = swapOptionsForRoute(route.key, swapOptions.options);
2110
+ const activeSwapForRoute = currentSwapInvoice !== void 0 && swapAssetMatchesRoute(route.key, currentSwapInvoice.swap?.pay_in_asset) ? currentSwapInvoice : void 0;
2111
+ return React9.createElement(
2112
+ "section",
2113
+ {
2114
+ className: orClasses6.wizardRoute,
2115
+ key: route.key
2116
+ },
2117
+ React9.createElement(
2118
+ "div",
2119
+ {
2120
+ className: orClasses6.wizardRouteHeading
2121
+ },
2122
+ React9.createElement(
2123
+ "div",
2124
+ null,
2125
+ React9.createElement("h3", null, route.title)
2126
+ )
2127
+ ),
2128
+ activeSwapForRoute === void 0 ? renderSwapActions({
2129
+ options: routeSwapOptions,
2130
+ enabled: swapOptions.enabled,
2131
+ startingAsset: swapStartingAsset,
2132
+ onStart: selectSwapAsset,
2133
+ checkout
2134
+ }) : renderSwapDepositPanel({
2135
+ invoice: activeSwapForRoute,
2136
+ checkout,
2137
+ now,
2138
+ encoder: props.qrEncoder,
2139
+ clipboard: props.clipboard,
2140
+ logger: props.logger,
2141
+ onError: props.onError,
2142
+ onRefund: refundSwap,
2143
+ onBackToLightning: () => {
2144
+ setDismissedSwapInvoiceId(activeSwapForRoute.invoice_id);
2145
+ clearSwapFocus();
2146
+ void props.onRequestLightning?.();
2147
+ }
2148
+ }),
2149
+ activeSwapForRoute === void 0 ? React9.createElement(
2150
+ "div",
2151
+ {
2152
+ className: orClasses6.providerGrid
2153
+ },
2154
+ route.providers.map(
2155
+ (provider) => React9.createElement(
2156
+ "article",
2157
+ {
2158
+ className: orClasses6.providerCard,
2159
+ key: provider.id
2160
+ },
2161
+ React9.createElement(
2162
+ "div",
2163
+ {
2164
+ className: orClasses6.providerHeading
2165
+ },
2166
+ React9.createElement("img", {
2167
+ alt: "",
2168
+ className: orClasses6.providerIcon,
2169
+ src: provider.icon
2170
+ }),
2171
+ React9.createElement(
2172
+ "h4",
2173
+ { className: orClasses6.providerName },
2174
+ provider.name
2175
+ )
2176
+ ),
2177
+ React9.createElement(
2178
+ "p",
2179
+ {
2180
+ className: orClasses6.providerKind
2181
+ },
2182
+ provider.kind
2183
+ ),
2184
+ React9.createElement(
2185
+ "div",
2186
+ {
2187
+ className: orClasses6.providerActions
2188
+ },
2189
+ renderProviderOpenAction(
2190
+ provider,
2191
+ () => setActiveTutorial({
2192
+ providerId: provider.id,
2193
+ index: 0,
2194
+ copied: false
2195
+ })
2196
+ )
2197
+ )
2198
+ )
2199
+ )
2200
+ ) : null
2201
+ );
2202
+ })
2203
+ )
2204
+ ),
2205
+ activeTutorialProvider === void 0 || activeTutorial === null ? null : React9.createElement(ProviderTutorialModal, {
2206
+ key: "provider-tutorial",
2207
+ provider: activeTutorialProvider,
2208
+ index: activeTutorial.index,
2209
+ copied: activeTutorial.copied,
2210
+ invoice: props.invoice ?? "",
2211
+ ...props.decodeLinkUrl === void 0 ? {} : { decodeLinkUrl: props.decodeLinkUrl },
2212
+ onClose: () => setActiveTutorial(null),
2213
+ onCopy: async () => {
2214
+ try {
2215
+ if (!props.invoice) return;
2216
+ await copyInvoiceHelper3({
2217
+ invoice: props.invoice,
2218
+ logger: props.logger,
2219
+ logContext: props.logContext
2220
+ });
2221
+ props.onProviderCopy?.(activeTutorialProvider.id);
2222
+ props.onCopy?.();
2223
+ setActiveTutorial({
2224
+ providerId: activeTutorialProvider.id,
2225
+ index: 0,
2226
+ copied: true
2227
+ });
2228
+ } catch (error) {
2229
+ props.onError?.(error);
2230
+ }
2231
+ },
2232
+ onStep: (index) => setActiveTutorial({
2233
+ providerId: activeTutorialProvider.id,
2234
+ index,
2235
+ copied: activeTutorial.copied
2236
+ })
2237
+ })
2238
+ );
2239
+ }
2240
+ function renderCompactPaymentMethodSelector(options) {
2241
+ const entries = buildMethodGridEntries(paymentMethods, options.swapAssetOptions);
2242
+ const currenciesLoading = options.currenciesLoading === true && options.swapAssetOptions.length === 0;
2243
+ const selectedKey = options.selectedPickerKey;
2244
+ const selectedGroup = findSwapGridGroup(entries, selectedKey);
2245
+ const networkRequired = selectedGroup !== void 0 && selectedGroup.options.length > 1;
2246
+ const selectedGroupKey = selectedGroup?.label.trim().toUpperCase();
2247
+ const selectedNetworkAsset = selectedGroupKey === void 0 ? void 0 : options.selectedSwapNetworks[selectedGroupKey];
2248
+ const selectedNetworkOption = selectedGroup === void 0 || selectedNetworkAsset === void 0 ? void 0 : selectedGroup.options.find((option) => option.pay_in_asset === selectedNetworkAsset);
2249
+ const continueTarget = selectedNetworkOption !== void 0 ? {
2250
+ payInAsset: selectedNetworkOption.pay_in_asset,
2251
+ disabled: selectedNetworkOption.available === false,
2252
+ limitMessage: swapOptionLimitMessage2(selectedNetworkOption, options.checkout)
2253
+ } : null;
2254
+ const startingAsset = options.startingAsset;
2255
+ const gridBusy = startingAsset !== null;
2256
+ const continueStarting = continueTarget !== null && continueTarget.payInAsset === startingAsset;
2257
+ const canContinue = continueTarget !== null && !continueTarget.disabled && selectedNetworkOption !== void 0 && !gridBusy;
2258
+ const continueButton = (className) => React9.createElement(
2259
+ "button",
2260
+ {
2261
+ type: "button",
2262
+ className,
2263
+ disabled: !canContinue,
2264
+ "aria-disabled": canContinue ? void 0 : "true",
2265
+ "aria-busy": continueStarting ? "true" : void 0,
2266
+ onClick: !canContinue ? void 0 : () => {
2267
+ if (continueTarget === null) return;
2268
+ options.onContinueSwap(continueTarget.payInAsset);
2269
+ }
2270
+ },
2271
+ continueStarting ? React9.createElement(
2272
+ React9.Fragment,
2273
+ null,
2274
+ React9.createElement("span", {
2275
+ className: orClasses6.continueSpinner,
2276
+ "aria-hidden": "true"
2277
+ }),
2278
+ checkoutLabels5.preparingPayment
2279
+ ) : continueTarget?.disabled && continueTarget.limitMessage !== void 0 ? continueTarget.limitMessage : checkoutLabels5.continue
2280
+ );
2281
+ const renderNetworkSelector = (group, mobile) => {
2282
+ const accent = paymentAccentId(group.label);
2283
+ const groupKey = group.label.trim().toUpperCase();
2284
+ const selectedAsset = options.selectedSwapNetworks[groupKey];
2285
+ const selectedOption = selectedAsset === void 0 ? void 0 : group.options.find((option) => option.pay_in_asset === selectedAsset);
2286
+ const { panelId, headingId } = wizardNetworkGroupIds(groupKey);
2287
+ return React9.createElement(
2288
+ "div",
2289
+ {
2290
+ id: panelId,
2291
+ role: "group",
2292
+ "aria-labelledby": headingId,
2293
+ className: mobile ? networkMobileRevealClasses(accent) : orClasses6.methodNetworkReveal
2294
+ },
2295
+ React9.createElement(
2296
+ "div",
2297
+ { className: orClasses6.methodNetworkLayout },
2298
+ React9.createElement(
2299
+ "div",
2300
+ null,
2301
+ React9.createElement(
2302
+ "h3",
2303
+ {
2304
+ id: headingId,
2305
+ className: orClasses6.methodNetworkHeading
2306
+ },
2307
+ formatChooseNetworkHeading(group.label)
2308
+ ),
2309
+ React9.createElement(
2310
+ "p",
2311
+ { className: orClasses6.methodNetworkHint },
2312
+ checkoutLabels5.selectNetworkToContinue
2313
+ )
2314
+ ),
2315
+ React9.createElement(
2316
+ "div",
2317
+ {
2318
+ role: "group",
2319
+ "aria-labelledby": headingId,
2320
+ className: orClasses6.methodNetworkGrid
2321
+ },
2322
+ group.options.map((option) => {
2323
+ const optionDisabled = option.available === false;
2324
+ const optionSelected = option.pay_in_asset === selectedOption?.pay_in_asset;
2325
+ const optionLimit = swapOptionLimitMessage2(option, options.checkout);
2326
+ return React9.createElement(
2327
+ "div",
2328
+ { key: option.pay_in_asset, className: orClasses6.methodTile },
2329
+ React9.createElement(
2330
+ "button",
2331
+ {
2332
+ type: "button",
2333
+ "aria-pressed": optionSelected,
2334
+ disabled: optionDisabled,
2335
+ "aria-disabled": optionDisabled ? "true" : void 0,
2336
+ className: networkButtonClasses({
2337
+ accent,
2338
+ selected: optionSelected,
2339
+ disabled: optionDisabled
2340
+ }),
2341
+ onClick: optionDisabled ? void 0 : () => options.onSelectNetwork(groupKey, option.pay_in_asset)
2342
+ },
2343
+ React9.createElement(
2344
+ "span",
2345
+ { "aria-hidden": "true", className: "grid size-6 shrink-0 place-items-center" },
2346
+ React9.createElement("img", {
2347
+ alt: "",
2348
+ className: orClasses6.methodNetworkIcon,
2349
+ src: getNetworkIcon(option.network_label)
2350
+ })
2351
+ ),
2352
+ React9.createElement("span", { className: "truncate" }, option.network_label),
2353
+ optionSelected ? React9.createElement(
2354
+ "span",
2355
+ {
2356
+ "aria-hidden": "true",
2357
+ className: networkCheckClasses(accent)
2358
+ },
2359
+ "\u2713"
2360
+ ) : null
2361
+ ),
2362
+ optionDisabled && optionLimit !== void 0 ? React9.createElement("span", { className: orClasses6.methodLimitHint }, optionLimit) : null
2363
+ );
2364
+ })
2365
+ ),
2366
+ continueButton(orClasses6.methodConfirmDesktop)
2367
+ ),
2368
+ selectedOption !== void 0 ? React9.createElement(
2369
+ "p",
2370
+ {
2371
+ "aria-live": "polite",
2372
+ className: orClasses6.methodNetworkSummary
2373
+ },
2374
+ React9.createElement(
2375
+ "span",
2376
+ {
2377
+ "aria-hidden": "true",
2378
+ className: networkSummaryIconClasses(accent)
2379
+ },
2380
+ "\u2713"
2381
+ ),
2382
+ formatNetworkSummary(group.label, selectedOption.network_label)
2383
+ ) : null
2384
+ );
2385
+ };
2386
+ return React9.createElement(
2387
+ React9.Fragment,
2388
+ null,
2389
+ React9.createElement(
2390
+ "header",
2391
+ {
2392
+ className: orClasses6.wizardHeader
2393
+ },
2394
+ React9.createElement(
2395
+ "h2",
2396
+ {
2397
+ id: "payment-method-heading",
2398
+ className: orClasses6.wizardHeaderTitle
2399
+ },
2400
+ checkoutLabels5.wizardTitle
2401
+ ),
2402
+ React9.createElement(
2403
+ "p",
2404
+ { className: orClasses6.wizardHeaderSubtitle },
2405
+ checkoutLabels5.wizardSubtitle
2406
+ )
2407
+ ),
2408
+ React9.createElement(
2409
+ "div",
2410
+ {
2411
+ className: orClasses6.wizardBody,
2412
+ "aria-labelledby": "payment-method-heading"
2413
+ },
2414
+ React9.createElement(
2415
+ "div",
2416
+ {
2417
+ role: "group",
2418
+ "aria-label": checkoutLabels5.paymentMethod,
2419
+ className: orClasses6.methodGrid
2420
+ },
2421
+ ...entries.map((entry) => {
2422
+ if (entry.kind === "method") {
2423
+ const method = entry.method;
2424
+ const accent2 = paymentAccentId(method.id);
2425
+ return React9.createElement(
2426
+ "button",
2427
+ {
2428
+ key: method.id,
2429
+ type: "button",
2430
+ className: assetButtonClasses({
2431
+ accent: accent2,
2432
+ selected: false,
2433
+ disabled: gridBusy
2434
+ }),
2435
+ disabled: gridBusy,
2436
+ "aria-disabled": gridBusy ? "true" : void 0,
2437
+ onClick: gridBusy ? void 0 : () => options.onContinueMethod(method.id)
2438
+ },
2439
+ React9.createElement(
2440
+ "span",
2441
+ { "aria-hidden": "true", className: orClasses6.methodIconWrap },
2442
+ React9.createElement("img", {
2443
+ alt: "",
2444
+ className: orClasses6.methodIcon,
2445
+ src: getPaymentMethodIcon(method.id)
801
2446
  })
802
- : null,
803
- expired
804
- ? null
805
- : React.createElement(QRCodeComponent, {
806
- key: "qr",
807
- invoice: checkoutModel.invoice,
808
- encoder: qrEncoder,
809
- onError,
810
- className: classNames?.qr,
811
- style: {
812
- aspectRatio: "1",
813
- justifySelf: "center",
814
- maxWidth: 420,
815
- width: "min(100%, 420px)"
816
- }
2447
+ ),
2448
+ React9.createElement(
2449
+ "span",
2450
+ { className: orClasses6.methodTitleWrap },
2451
+ React9.createElement("span", { className: orClasses6.methodTitle }, method.title)
2452
+ )
2453
+ );
2454
+ }
2455
+ const group = entry.group;
2456
+ const groupKey = group.label.trim().toUpperCase();
2457
+ const pickerKey = swapPickerKey(group.label);
2458
+ const selected = selectedKey === pickerKey;
2459
+ const multiNetwork = group.options.length > 1;
2460
+ const displayOption = group.options.find((option) => option.available !== false) ?? group.options[0];
2461
+ if (displayOption === void 0) return null;
2462
+ const selectedAsset = options.selectedSwapNetworks[groupKey];
2463
+ const selectedOption = selectedAsset === void 0 ? void 0 : group.options.find((option) => option.pay_in_asset === selectedAsset);
2464
+ const activeOption = selectedOption ?? displayOption;
2465
+ const starting = group.options.some((option) => option.pay_in_asset === startingAsset);
2466
+ const disabled = group.options.every((option) => option.available === false);
2467
+ const accent = paymentAccentId(group.label);
2468
+ const limitOption = disabled ? swapGroupLimitOption(group.options) ?? activeOption : activeOption;
2469
+ const limitMessage = swapOptionLimitMessage2(limitOption, options.checkout);
2470
+ const { panelId } = wizardNetworkGroupIds(groupKey);
2471
+ return React9.createElement(
2472
+ "div",
2473
+ { key: pickerKey, className: orClasses6.methodTile },
2474
+ React9.createElement(
2475
+ "button",
2476
+ {
2477
+ type: "button",
2478
+ "aria-pressed": starting || multiNetwork && selected,
2479
+ "aria-expanded": multiNetwork ? selected : void 0,
2480
+ "aria-controls": multiNetwork ? panelId : void 0,
2481
+ "aria-busy": starting ? "true" : void 0,
2482
+ disabled: disabled || gridBusy,
2483
+ "aria-disabled": disabled || gridBusy ? "true" : void 0,
2484
+ className: assetButtonClasses({
2485
+ accent,
2486
+ selected: starting || multiNetwork && selected,
2487
+ disabled: disabled || gridBusy && !starting
817
2488
  }),
818
- React.createElement(WaitingState, {
819
- key: "waiting",
820
- waiting: checkoutModel.waiting,
821
- statusTitle: checkoutModel.statusTitle,
822
- statusDetail: checkoutModel.statusDetail,
823
- className: classNames?.waiting
824
- }),
825
- checkoutModel.countdownLabel === undefined
826
- ? null
827
- : React.createElement("div", {
828
- key: "countdown",
829
- className: joinClassNames("or-countdown", classNames?.countdown)
830
- }, checkoutModel.countdownPrefix, " ", React.createElement("strong", null, checkoutModel.countdownLabel)),
831
- React.createElement(InvoiceSummaryComponent, {
832
- key: "summary",
833
- amountLabel: checkoutModel.amountLabel,
834
- fiatLabel: checkoutModel.fiatLabel,
835
- status: checkoutModel.status,
836
- PaymentStateComponent,
837
- className: classNames?.summary,
838
- classNames
839
- }),
840
- React.createElement("div", {
841
- key: "actions",
842
- className: classNames?.actions,
843
- [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.actions]: ""
844
- }, expired
845
- ? React.createElement(ButtonComponent ?? "button", {
846
- type: "button",
847
- onClick: startOver
848
- }, openReceiveCheckoutLabels.startOver)
849
- : React.createElement(CopyButton, {
850
- invoice: checkoutModel.invoice,
851
- copyInvoice: checkoutModel.copyInvoice,
852
- onError,
853
- logger,
854
- ButtonComponent,
855
- className: classNames?.copyButton
856
- })),
857
- paymentWizard && !expired
858
- ? React.createElement(PaymentWizard, {
859
- key: "wizard",
860
- invoice: checkoutModel.invoice,
861
- className: classNames?.wizard,
862
- logger,
863
- onError,
864
- countryStorageKey,
865
- logContext: getCheckoutLogContext({
866
- invoice_id: checkoutModel.invoice_id,
867
- payment_hash: checkoutModel.payment_hash,
868
- amount_msats: checkoutModel.amount_msats
869
- })
2489
+ onClick: disabled || gridBusy ? void 0 : multiNetwork ? () => options.onSelectPicker(pickerKey, selectedKey) : () => options.onContinueSwap(displayOption.pay_in_asset)
2490
+ },
2491
+ React9.createElement(
2492
+ "span",
2493
+ { "aria-hidden": "true", className: orClasses6.methodIconWrap },
2494
+ starting ? React9.createElement("span", {
2495
+ className: orClasses6.spinner,
2496
+ "aria-hidden": "true"
2497
+ }) : React9.createElement("img", {
2498
+ alt: "",
2499
+ className: orClasses6.methodIcon,
2500
+ src: getSwapOptionIcon(displayOption)
870
2501
  })
871
- : null
872
- ]
873
- : customChildren);
2502
+ ),
2503
+ React9.createElement(
2504
+ "span",
2505
+ { className: orClasses6.methodTitleWrap },
2506
+ React9.createElement("span", { className: orClasses6.methodTitle }, group.label),
2507
+ !disabled && multiNetwork ? React9.createElement(
2508
+ "span",
2509
+ { className: orClasses6.methodDetailMobile },
2510
+ selected && selectedOption !== void 0 ? `${selectedOption.network_label} network` : checkoutLabels5.selectNetwork
2511
+ ) : null
2512
+ )
2513
+ ),
2514
+ disabled && limitMessage !== void 0 ? React9.createElement("span", { className: orClasses6.methodLimitHint }, limitMessage) : null,
2515
+ multiNetwork ? React9.createElement(
2516
+ "div",
2517
+ {
2518
+ className: joinClassNames(
2519
+ orClasses6.methodNetworkRevealAnim,
2520
+ selected ? orClasses6.methodNetworkRevealAnimOpen : orClasses6.methodNetworkRevealAnimClosed
2521
+ )
2522
+ },
2523
+ React9.createElement(
2524
+ "div",
2525
+ { className: orClasses6.methodNetworkRevealInner },
2526
+ selected ? renderNetworkSelector(group, true) : null
2527
+ )
2528
+ ) : null
2529
+ );
2530
+ }),
2531
+ currenciesLoading ? React9.createElement(
2532
+ "div",
2533
+ {
2534
+ key: "currencies-loading",
2535
+ role: "status",
2536
+ "aria-live": "polite",
2537
+ className: orClasses6.methodCurrenciesLoading
2538
+ },
2539
+ React9.createElement("span", {
2540
+ className: orClasses6.spinner,
2541
+ "aria-hidden": "true"
2542
+ }),
2543
+ React9.createElement(
2544
+ "span",
2545
+ { className: orClasses6.methodTitle },
2546
+ checkoutLabels5.loadingCurrencies
2547
+ )
2548
+ ) : null
2549
+ ),
2550
+ networkRequired && selectedGroup !== void 0 ? React9.createElement(
2551
+ "div",
2552
+ { className: orClasses6.methodNetworkRevealDesktop },
2553
+ renderNetworkSelector(selectedGroup, false)
2554
+ ) : null
2555
+ )
2556
+ );
874
2557
  }
875
- function getCheckoutLogContext(data) {
876
- return {
877
- ...(data.invoice_id === undefined ? {} : { invoice_id: data.invoice_id }),
878
- ...(data.payment_hash === undefined ? {} : { payment_hash: data.payment_hash }),
879
- ...(data.amount_msats === undefined ? {} : { amount_msats: data.amount_msats }),
880
- ...(data.transaction_state === undefined
881
- ? {}
882
- : { transaction_state: data.transaction_state }),
883
- ...(data.workflow_state === undefined
884
- ? {}
885
- : { workflow_state: data.workflow_state })
886
- };
2558
+ function renderWizardBackBreadcrumb(currentLabel, onBack) {
2559
+ return React9.createElement(
2560
+ "div",
2561
+ {
2562
+ className: orClasses6.breadcrumbs
2563
+ },
2564
+ React9.createElement(
2565
+ "ul",
2566
+ null,
2567
+ React9.createElement(
2568
+ "li",
2569
+ null,
2570
+ React9.createElement(
2571
+ "button",
2572
+ {
2573
+ className: "link link-hover",
2574
+ onClick: onBack,
2575
+ type: "button"
2576
+ },
2577
+ checkoutLabels5.switchPaymentMethod
2578
+ )
2579
+ ),
2580
+ React9.createElement(
2581
+ "li",
2582
+ null,
2583
+ React9.createElement("span", { className: orClasses6.breadcrumbCurrent }, currentLabel)
2584
+ )
2585
+ )
2586
+ );
887
2587
  }
888
- function renderCountrySelect(options) {
889
- return React.createElement("label", {
890
- className: "or-country-select"
891
- }, React.createElement("span", null, openReceiveCheckoutLabels.chooseCountry), React.createElement("select", {
892
- value: options.selectedCountryCode,
893
- onChange: (event) => {
894
- options.onSelectCountry(event.currentTarget.value);
895
- }
896
- }, options.countries.map((country) => React.createElement("option", {
897
- key: country.code,
898
- value: country.code
899
- }, country.label))));
2588
+ function renderWizardBreadcrumbs(options) {
2589
+ const method = paymentMethods.find((candidate) => candidate.id === options.method);
2590
+ const methodLabel = method?.title ?? checkoutLabels5.paymentMethod;
2591
+ const routeLabel = options.selectedRoute === null || options.routeAssets.length <= 1 ? null : options.routeAssets.find((asset) => asset.id === options.selectedRoute)?.label ?? options.selectedRoute;
2592
+ return React9.createElement(
2593
+ "nav",
2594
+ {
2595
+ "aria-label": "Payment path",
2596
+ className: orClasses6.breadcrumbs
2597
+ },
2598
+ React9.createElement(
2599
+ "ul",
2600
+ null,
2601
+ React9.createElement(
2602
+ "li",
2603
+ null,
2604
+ React9.createElement(
2605
+ "button",
2606
+ {
2607
+ className: "link link-hover",
2608
+ onClick: options.onChangeMethod,
2609
+ type: "button"
2610
+ },
2611
+ checkoutLabels5.switchPaymentMethod
2612
+ )
2613
+ ),
2614
+ routeLabel === null ? React9.createElement(
2615
+ "li",
2616
+ null,
2617
+ React9.createElement("span", { className: orClasses6.breadcrumbCurrent }, methodLabel)
2618
+ ) : React9.createElement(
2619
+ React9.Fragment,
2620
+ null,
2621
+ React9.createElement(
2622
+ "li",
2623
+ null,
2624
+ React9.createElement(
2625
+ "button",
2626
+ {
2627
+ className: "link link-hover",
2628
+ onClick: options.onChangeRoute,
2629
+ type: "button"
2630
+ },
2631
+ methodLabel
2632
+ )
2633
+ ),
2634
+ React9.createElement(
2635
+ "li",
2636
+ null,
2637
+ React9.createElement("span", { className: orClasses6.breadcrumbCurrent }, routeLabel)
2638
+ )
2639
+ )
2640
+ )
2641
+ );
2642
+ }
2643
+ function swapOptionsForRoute(routeKey, options) {
2644
+ return options.filter((option) => swapAssetMatchesRoute(routeKey, option.pay_in_asset));
2645
+ }
2646
+ function selectCurrentSwapInvoice(checkout, local, dismissedInvoiceId) {
2647
+ const fromCheckout = checkout?.invoices.find(
2648
+ (invoice) => invoice.rail === "swap" && invoice.swap !== void 0 && invoice.invoice_id !== dismissedInvoiceId
2649
+ );
2650
+ if (local === null || local.invoice_id === dismissedInvoiceId) return fromCheckout;
2651
+ const matched = checkout?.invoices.find((invoice) => invoice.invoice_id === local.invoice_id) ?? local;
2652
+ return overlaySwapRefundStaging(matched, local);
900
2653
  }
901
2654
  function renderRoutePicker(options) {
902
- return React.createElement("div", {
903
- className: `or-route-picker or-route-picker-${options.method}`
904
- }, options.assets.map((asset) => {
905
- return React.createElement("button", {
906
- className: asset.selected ? "selected" : "",
907
- key: asset.id,
908
- onClick: () => options.onSelectRoute(asset.id),
909
- type: "button"
910
- }, React.createElement("img", {
911
- alt: "",
912
- src: asset.icon
913
- }), React.createElement("span", null, asset.label), React.createElement("small", null, asset.subtitle));
914
- }));
2655
+ return React9.createElement(
2656
+ "div",
2657
+ {
2658
+ className: orClasses6.routePicker,
2659
+ "data-method": options.method
2660
+ },
2661
+ options.assets.map((asset) => {
2662
+ return React9.createElement(
2663
+ "button",
2664
+ {
2665
+ className: asset.selected ? orClasses6.routeButtonSelected : orClasses6.routeButton,
2666
+ key: asset.id,
2667
+ onClick: () => options.onSelectRoute(asset.id),
2668
+ type: "button"
2669
+ },
2670
+ React9.createElement("img", {
2671
+ alt: "",
2672
+ className: orClasses6.methodIcon,
2673
+ src: asset.icon
2674
+ }),
2675
+ React9.createElement("span", { className: orClasses6.methodTitle }, asset.label),
2676
+ React9.createElement("small", { className: orClasses6.methodDetail }, asset.subtitle)
2677
+ );
2678
+ })
2679
+ );
915
2680
  }
916
- function joinClassNames(...values) {
917
- const joined = values.filter(Boolean).join(" ");
918
- return joined === "" ? undefined : joined;
2681
+
2682
+ // src/checkout.ts
2683
+ function Checkout(props) {
2684
+ validateCheckoutProps({
2685
+ framework: "@openreceive/react",
2686
+ checkout: props.checkout,
2687
+ reference: props.reference,
2688
+ metadata: props.metadata,
2689
+ syncUrl: props.syncUrl,
2690
+ resumePathPrefix: props.resumePathPrefix,
2691
+ routeReference: props.routeReference
2692
+ });
2693
+ const { checkout } = props;
2694
+ if (checkout !== void 0) {
2695
+ return React10.createElement(CheckoutSnapshotMode, {
2696
+ ...props,
2697
+ checkout,
2698
+ prefix: props.prefix ?? OPENRECEIVE_DEFAULT_PREFIX
2699
+ });
2700
+ }
2701
+ return React10.createElement(CheckoutCreate, props);
2702
+ }
2703
+ function CheckoutSnapshotMode(props) {
2704
+ const { checkout } = props;
2705
+ const identity = `${checkout.checkout_id} ${checkout.reference}`;
2706
+ const [current, setCurrent] = React10.useState(checkout);
2707
+ const lastIdentityRef = React10.useRef(identity);
2708
+ React10.useEffect(() => {
2709
+ if (lastIdentityRef.current !== identity) {
2710
+ lastIdentityRef.current = identity;
2711
+ setCurrent(checkout);
2712
+ }
2713
+ }, [identity, checkout]);
2714
+ const onSwapStarted = React10.useCallback((invoice) => {
2715
+ setCurrent((previous) => mergeAttemptIntoSnapshot(invoice, previous));
2716
+ }, []);
2717
+ return React10.createElement(CheckoutView, {
2718
+ ...props,
2719
+ checkout: current,
2720
+ onSwapStarted
2721
+ });
2722
+ }
2723
+ function CheckoutCreate(props) {
2724
+ const reference = props.reference;
2725
+ const resolvedPrefix = props.prefix ?? OPENRECEIVE_DEFAULT_PREFIX;
2726
+ const {
2727
+ onError,
2728
+ metadata,
2729
+ createFetch,
2730
+ className,
2731
+ classNames,
2732
+ syncUrl = false,
2733
+ resumePathPrefix = "/checkout",
2734
+ routeReference
2735
+ } = props;
2736
+ const [created, setCreated] = React10.useState({ status: "pending" });
2737
+ const [attempt, setAttempt] = React10.useState(0);
2738
+ const onErrorRef = React10.useRef(onError);
2739
+ onErrorRef.current = onError;
2740
+ const metadataRef = React10.useRef(metadata);
2741
+ metadataRef.current = metadata;
2742
+ const createFetchRef = React10.useRef(createFetch);
2743
+ createFetchRef.current = createFetch;
2744
+ const createdCheckoutRef = React10.useRef(created.checkout);
2745
+ createdCheckoutRef.current = created.checkout;
2746
+ const session = useCheckoutSession({
2747
+ snapshot: () => createdCheckoutRef.current,
2748
+ reference: () => reference,
2749
+ requestCheckout: (id) => requestCheckout({
2750
+ prefix: resolvedPrefix,
2751
+ reference: id,
2752
+ ...metadataRef.current === void 0 ? {} : { metadata: metadataRef.current },
2753
+ ...createFetchRef.current === void 0 ? {} : { fetch: createFetchRef.current }
2754
+ }),
2755
+ // One stable CheckoutView across the mint: swapping the rendered shell
2756
+ // remounted PaymentWizard and wiped the payer's method selection.
2757
+ onSnapshot: (checkout) => setCreated({ status: "ready", checkout }),
2758
+ onError: (error) => onErrorRef.current?.(error)
2759
+ });
2760
+ React10.useEffect(() => {
2761
+ if (syncUrl) {
2762
+ enterCheckoutResumePath(reference, {
2763
+ pathPrefix: resumePathPrefix,
2764
+ ...routeReference === void 0 ? {} : { routeReference }
2765
+ });
2766
+ }
2767
+ }, [syncUrl, reference, resumePathPrefix, routeReference]);
2768
+ React10.useEffect(() => {
2769
+ let cancelled = false;
2770
+ setCreated({ status: "pending" });
2771
+ prepareCheckout({
2772
+ prefix: resolvedPrefix,
2773
+ reference,
2774
+ ...createFetchRef.current === void 0 ? {} : { fetch: createFetchRef.current }
2775
+ }).then((checkout) => {
2776
+ if (!cancelled) setCreated({ status: "ready", checkout });
2777
+ }).catch((error) => {
2778
+ if (cancelled) return;
2779
+ onErrorRef.current?.(error);
2780
+ setCreated({
2781
+ status: "error",
2782
+ ...error instanceof Error && error.message.length > 0 ? { errorMessage: error.message } : {}
2783
+ });
2784
+ });
2785
+ return () => {
2786
+ cancelled = true;
2787
+ };
2788
+ }, [reference, resolvedPrefix, attempt]);
2789
+ const onSwapStarted = React10.useCallback(
2790
+ (invoice) => {
2791
+ setCreated((current) => {
2792
+ if (current.status !== "ready") return current;
2793
+ return {
2794
+ status: "ready",
2795
+ checkout: mergeAttemptIntoCheckout(invoice, current.checkout, reference)
2796
+ };
2797
+ });
2798
+ },
2799
+ [reference]
2800
+ );
2801
+ if (created.status === "ready" && created.checkout !== void 0) {
2802
+ return React10.createElement(CheckoutView, {
2803
+ ...props,
2804
+ checkout: created.checkout,
2805
+ prefix: resolvedPrefix,
2806
+ mintingLightning: session.mintingLightning,
2807
+ onRequestLightning: session.ensureLightning,
2808
+ onSwapStarted
2809
+ });
2810
+ }
2811
+ if (created.status === "error") {
2812
+ return React10.createElement(
2813
+ "section",
2814
+ {
2815
+ className: joinClassNames(
2816
+ className,
2817
+ classNames?.root,
2818
+ orClasses7.creating,
2819
+ "openreceive-checkout-error"
2820
+ ),
2821
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES3.root]: ""
2822
+ },
2823
+ React10.createElement("p", { role: "alert" }, "Could not start checkout."),
2824
+ // The thrown error carries the server's payer-facing text (e.g. the
2825
+ // rate-limit message or "Exchange rates are temporarily unavailable").
2826
+ created.errorMessage === void 0 ? null : React10.createElement("p", null, created.errorMessage),
2827
+ React10.createElement(
2828
+ "button",
2829
+ {
2830
+ type: "button",
2831
+ className: orClasses7.btn,
2832
+ onClick: () => setAttempt((count) => count + 1)
2833
+ },
2834
+ "Try again"
2835
+ )
2836
+ );
2837
+ }
2838
+ return React10.createElement(
2839
+ "section",
2840
+ {
2841
+ className: joinClassNames(
2842
+ className,
2843
+ classNames?.root,
2844
+ orClasses7.creating,
2845
+ "openreceive-checkout-creating"
2846
+ ),
2847
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES3.root]: ""
2848
+ },
2849
+ React10.createElement("span", {
2850
+ className: orClasses7.spinner,
2851
+ "aria-hidden": "true"
2852
+ }),
2853
+ React10.createElement("p", null, "Creating checkout\u2026")
2854
+ );
2855
+ }
2856
+ function CheckoutView(props) {
2857
+ const {
2858
+ checkout,
2859
+ // Create-mode props are consumed by the Checkout dispatcher / CheckoutCreate wrapper; drop
2860
+ // them here so they never leak onto the rendered <section>.
2861
+ reference: _reference,
2862
+ // The dispatcher and CheckoutCreate both resolve this before rendering the
2863
+ // view, so the default below only guards a direct CheckoutView call.
2864
+ prefix = OPENRECEIVE_DEFAULT_PREFIX,
2865
+ metadata: _metadata,
2866
+ createFetch: _createFetch,
2867
+ syncUrl: _syncUrl,
2868
+ resumePathPrefix: _resumePathPrefix,
2869
+ routeReference: _routeReference,
2870
+ onRequestLightning,
2871
+ onSwapStarted,
2872
+ mintingLightning = false,
2873
+ qrEncoder,
2874
+ logger,
2875
+ decodeLinkUrl,
2876
+ onCopy,
2877
+ onOpenWallet,
2878
+ onError,
2879
+ refreshStatus,
2880
+ onState,
2881
+ onSettled,
2882
+ onProviderCopy,
2883
+ onStartOver,
2884
+ polling,
2885
+ pollIntervalMs,
2886
+ paymentWizard = true,
2887
+ themeToggle = true,
2888
+ defaultTheme,
2889
+ storageKey,
2890
+ components,
2891
+ classNames,
2892
+ children,
2893
+ className,
2894
+ ...sectionProps
2895
+ } = props;
2896
+ const checkoutModel = useCheckout({
2897
+ checkout,
2898
+ logger,
2899
+ onCopy,
2900
+ onOpenWallet,
2901
+ onError,
2902
+ refreshStatus,
2903
+ prefix,
2904
+ onState,
2905
+ onSettled,
2906
+ polling,
2907
+ ...pollIntervalMs === void 0 ? {} : { pollIntervalMs }
2908
+ });
2909
+ const theme = useTheme({
2910
+ defaultTheme,
2911
+ storageKey
2912
+ });
2913
+ const ownsTheme = themeToggle && !theme.fromScope;
2914
+ const [swapFocused, setSwapFocused] = React10.useState(false);
2915
+ const QRCodeComponent = components?.QRCode ?? QRCode;
2916
+ const InvoiceSummaryComponent = components?.InvoiceSummary ?? InvoiceSummary;
2917
+ const CopyButton = components?.CopyButton ?? CopyInvoiceButton;
2918
+ const OpenWalletButtonComponent = components?.OpenWalletButton;
2919
+ const ButtonComponent = components?.Button;
2920
+ const PaymentStateComponent = components?.PaymentState ?? PaymentState;
2921
+ const customChildren = typeof children === "function" ? children(checkoutModel) : children;
2922
+ const expired = checkoutModel.status === "expired";
2923
+ const settled = checkoutModel.status === "settled";
2924
+ const showLightning = !!checkoutModel.invoice && !swapFocused && !expired;
2925
+ const hideLightning = !showLightning && !expired && !settled;
2926
+ const showSummaryMeta = checkoutModel.status === "settled" || checkoutModel.status === "expired";
2927
+ const fiatCurrency = checkoutModel.fiat_quote?.fiat?.currency;
2928
+ const decodeInvoiceHref = createLightningInvoiceDecodeUrl2(checkoutModel.invoice, decodeLinkUrl);
2929
+ const startOver = () => {
2930
+ onStartOver?.();
2931
+ };
2932
+ const lightningPane = hideLightning || expired || settled ? null : React10.createElement(
2933
+ "div",
2934
+ {
2935
+ key: "lightning-pane",
2936
+ className: joinClassNames(orClasses7.lightningPane, classNames?.lightningPane)
2937
+ },
2938
+ React10.createElement(QRCodeComponent, {
2939
+ key: "qr",
2940
+ invoice: checkoutModel.invoice,
2941
+ encoder: qrEncoder,
2942
+ onError,
2943
+ className: joinClassNames(orClasses7.qr, classNames?.qr)
2944
+ }),
2945
+ React10.createElement(SatsDetail, {
2946
+ key: "sats-detail",
2947
+ amountLabel: checkoutModel.amountLabel,
2948
+ fiatLabel: checkoutModel.fiatLabel,
2949
+ fiatCurrency,
2950
+ className: classNames?.satsDetail
2951
+ })
2952
+ );
2953
+ return React10.createElement(
2954
+ "section",
2955
+ {
2956
+ ...sectionProps,
2957
+ className: joinClassNames(className, orClasses7.root, classNames?.root),
2958
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES3.root]: "",
2959
+ // Under ThemeScope, inherit data-theme from the page. Standalone Checkout owns it.
2960
+ ...ownsTheme ? theme.attributes : {}
2961
+ },
2962
+ customChildren === void 0 ? [
2963
+ ownsTheme ? React10.createElement(ThemeToggle, {
2964
+ key: "theme",
2965
+ className: classNames?.themeToggle,
2966
+ theme: theme.theme,
2967
+ resolvedTheme: theme.resolvedTheme,
2968
+ onThemeChange: theme.setTheme,
2969
+ ButtonComponent
2970
+ }) : null,
2971
+ mintingLightning ? React10.createElement(
2972
+ "div",
2973
+ {
2974
+ key: "minting-lightning",
2975
+ className: orClasses7.creating
2976
+ },
2977
+ React10.createElement("span", {
2978
+ className: orClasses7.spinner,
2979
+ "aria-hidden": "true"
2980
+ }),
2981
+ React10.createElement("p", null, checkoutLabels6.preparingPayment)
2982
+ ) : null,
2983
+ hideLightning ? null : React10.createElement(
2984
+ "div",
2985
+ {
2986
+ key: "payment-layout",
2987
+ className: expired || settled ? orClasses7.paymentLayoutExpired : orClasses7.paymentLayout
2988
+ },
2989
+ lightningPane,
2990
+ React10.createElement(
2991
+ "div",
2992
+ {
2993
+ key: "payment-info",
2994
+ className: orClasses7.paymentInfo
2995
+ },
2996
+ expired || settled ? null : React10.createElement(
2997
+ "p",
2998
+ {
2999
+ key: "invoice-title",
3000
+ className: joinClassNames(
3001
+ orClasses7.invoiceTitle,
3002
+ classNames?.invoiceTitle
3003
+ )
3004
+ },
3005
+ checkoutLabels6.bitcoinLightningInvoice
3006
+ ),
3007
+ React10.createElement(WaitingState, {
3008
+ key: "waiting",
3009
+ waiting: checkoutModel.waiting,
3010
+ statusTitle: checkoutModel.statusTitle,
3011
+ statusDetail: checkoutModel.statusDetail,
3012
+ settled,
3013
+ className: classNames?.waiting
3014
+ }),
3015
+ checkoutModel.countdownLabel === void 0 ? null : React10.createElement(
3016
+ "div",
3017
+ {
3018
+ key: "countdown",
3019
+ className: joinClassNames(orClasses7.countdown, classNames?.countdown)
3020
+ },
3021
+ checkoutModel.countdownPrefix,
3022
+ " ",
3023
+ React10.createElement(
3024
+ "strong",
3025
+ { className: orClasses7.countdownStrong },
3026
+ checkoutModel.countdownLabel
3027
+ )
3028
+ ),
3029
+ showSummaryMeta ? React10.createElement(InvoiceSummaryComponent, {
3030
+ key: "summary",
3031
+ status: checkoutModel.status,
3032
+ PaymentStateComponent,
3033
+ className: classNames?.summary,
3034
+ classNames
3035
+ }) : null,
3036
+ settled ? React10.createElement(PaymentData, {
3037
+ key: "payment-data",
3038
+ // The model IS the checkout state, so the payment-data
3039
+ // panel reads the same derivation the rest of the screen
3040
+ // does. It used to re-flatten the snapshot separately,
3041
+ // which reported the displayed ATTEMPT's transaction /
3042
+ // workflow state on a checkout the state already knows
3043
+ // is paid.
3044
+ source: checkoutModel,
3045
+ className: classNames?.details
3046
+ }) : expired ? React10.createElement(
3047
+ "div",
3048
+ {
3049
+ key: "expired-actions",
3050
+ className: joinClassNames(orClasses7.actions, classNames?.actions),
3051
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES3.actions]: ""
3052
+ },
3053
+ React10.createElement(
3054
+ ButtonComponent ?? "button",
3055
+ {
3056
+ type: "button",
3057
+ className: orClasses7.btn,
3058
+ onClick: startOver
3059
+ },
3060
+ checkoutLabels6.startOver
3061
+ )
3062
+ ) : React10.createElement(
3063
+ "div",
3064
+ {
3065
+ key: "actions",
3066
+ className: joinClassNames(orClasses7.actions, classNames?.actions),
3067
+ [OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES3.actions]: ""
3068
+ },
3069
+ React10.createElement(CopyButton, {
3070
+ key: "copy-invoice",
3071
+ invoice: checkoutModel.invoice,
3072
+ copyInvoice: checkoutModel.copyInvoice,
3073
+ onError,
3074
+ logger,
3075
+ ButtonComponent,
3076
+ className: classNames?.copyButton
3077
+ }),
3078
+ // Opt-in slot: the default checkout ships no wallet button, so a
3079
+ // desktop payer is never sent to a handler that does not exist.
3080
+ OpenWalletButtonComponent === void 0 ? null : React10.createElement(OpenWalletButtonComponent, {
3081
+ key: "open-wallet",
3082
+ invoice: checkoutModel.invoice,
3083
+ openWallet: checkoutModel.openWallet,
3084
+ onError,
3085
+ logger,
3086
+ ButtonComponent,
3087
+ className: classNames?.openWalletButton
3088
+ }),
3089
+ decodeInvoiceHref === void 0 ? null : React10.createElement(
3090
+ "a",
3091
+ {
3092
+ key: "decode-invoice",
3093
+ className: orClasses7.btn,
3094
+ href: decodeInvoiceHref,
3095
+ rel: "noreferrer",
3096
+ target: "_blank"
3097
+ },
3098
+ checkoutLabels6.decodeInvoice
3099
+ )
3100
+ )
3101
+ )
3102
+ ),
3103
+ paymentWizard && !settled && (!expired || swapFocused) ? React10.createElement(PaymentWizard, {
3104
+ key: "wizard",
3105
+ // Only pass invoice when it's a real bolt11 (non-empty, non-deferred).
3106
+ invoice: checkoutModel.invoice || void 0,
3107
+ checkout: checkoutModel.checkout,
3108
+ className: classNames?.wizard,
3109
+ logger,
3110
+ onError,
3111
+ onSwapFocusChange: setSwapFocused,
3112
+ prefix,
3113
+ qrEncoder,
3114
+ decodeLinkUrl,
3115
+ logContext: getCheckoutLogContext({
3116
+ invoice_id: checkoutModel.invoice_id,
3117
+ payment_hash: checkoutModel.payment_hash,
3118
+ amount_msats: checkoutModel.amount_msats
3119
+ }),
3120
+ onProviderCopy,
3121
+ onCopy,
3122
+ onRequestLightning,
3123
+ onSwapStarted
3124
+ }) : null
3125
+ ] : customChildren
3126
+ );
3127
+ }
3128
+
3129
+ // src/index.ts
3130
+ import { resolveTransactionDetailRows as resolveTransactionDetailRows2 } from "@openreceive/browser/headless";
3131
+
3132
+ // src/view-model.ts
3133
+ import {
3134
+ createCheckoutState as createCheckoutState2,
3135
+ selectCheckoutDisplayInvoice as selectCheckoutDisplayInvoice2,
3136
+ deriveStatus as deriveStatus2
3137
+ } from "@openreceive/browser/headless";
3138
+ function deriveCheckoutOrderStatus(snapshot) {
3139
+ if (snapshot.status === "paid") return "settled";
3140
+ if (snapshot.status === "expired") return "expired";
3141
+ const invoice = selectCheckoutDisplayInvoice2(snapshot);
3142
+ return invoice === void 0 ? "pending" : deriveStatus2(invoice);
3143
+ }
3144
+ function createCheckoutViewModel(data) {
3145
+ return {
3146
+ ...createCheckoutState2(data.checkout, { logger: false }),
3147
+ status: deriveCheckoutOrderStatus(data.checkout)
3148
+ };
919
3149
  }
3150
+ export {
3151
+ Checkout,
3152
+ CheckoutProvider,
3153
+ CopyInvoiceButton,
3154
+ InvoiceSummary,
3155
+ OpenWalletButton,
3156
+ PaymentData,
3157
+ PaymentState,
3158
+ PaymentWizard,
3159
+ QRCode,
3160
+ SatsDetail,
3161
+ ThemeScope,
3162
+ ThemeToggle,
3163
+ TransactionDetails,
3164
+ WaitingState,
3165
+ createCheckoutViewModel,
3166
+ renderSwapDepositPanel,
3167
+ resolveTransactionDetailRows2 as resolveTransactionDetailRows,
3168
+ useCheckout,
3169
+ useCheckoutContext,
3170
+ useTheme
3171
+ };