@openreceive/browser 0.1.0

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.
Files changed (94) hide show
  1. package/LICENSE +21 -0
  2. package/dist/assets/icons/bank.svg +12 -0
  3. package/dist/assets/icons/bnb.svg +1 -0
  4. package/dist/assets/icons/btc.svg +1 -0
  5. package/dist/assets/icons/card.svg +14 -0
  6. package/dist/assets/icons/crypto.svg +12 -0
  7. package/dist/assets/icons/doge.svg +1 -0
  8. package/dist/assets/icons/eth.svg +1 -0
  9. package/dist/assets/icons/lightning.svg +11 -0
  10. package/dist/assets/icons/ltc.svg +1 -0
  11. package/dist/assets/icons/sol.svg +1 -0
  12. package/dist/assets/icons/trx.svg +1 -0
  13. package/dist/assets/icons/usdc.svg +1 -0
  14. package/dist/assets/icons/usdt.svg +1 -0
  15. package/dist/assets/icons/xmr.svg +1 -0
  16. package/dist/assets/icons/xrp.svg +1 -0
  17. package/dist/assets/pay_tutorials/boltz-1.webp +0 -0
  18. package/dist/assets/pay_tutorials/boltz-2.webp +0 -0
  19. package/dist/assets/pay_tutorials/cashapp-1.webp +0 -0
  20. package/dist/assets/pay_tutorials/cashapp-2.webp +0 -0
  21. package/dist/assets/pay_tutorials/cashapp-3.webp +0 -0
  22. package/dist/assets/pay_tutorials/cashapp-4.webp +0 -0
  23. package/dist/assets/pay_tutorials/cashapp-5.webp +0 -0
  24. package/dist/assets/pay_tutorials/cashapp-6.webp +0 -0
  25. package/dist/assets/pay_tutorials/coinbase-1.webp +0 -0
  26. package/dist/assets/pay_tutorials/coinbase-2.webp +0 -0
  27. package/dist/assets/pay_tutorials/fixedfloat-1.webp +0 -0
  28. package/dist/assets/pay_tutorials/fixedfloat-2.webp +0 -0
  29. package/dist/assets/pay_tutorials/kraken-1.webp +0 -0
  30. package/dist/assets/pay_tutorials/kraken-2.webp +0 -0
  31. package/dist/assets/pay_tutorials/kraken-3.webp +0 -0
  32. package/dist/assets/pay_tutorials/kraken-4.webp +0 -0
  33. package/dist/assets/pay_tutorials/strike-1.webp +0 -0
  34. package/dist/assets/pay_tutorials/strike-2.webp +0 -0
  35. package/dist/assets/pay_tutorials/strike-3.webp +0 -0
  36. package/dist/assets/pay_tutorials/strike-4.webp +0 -0
  37. package/dist/assets/provider-icons/aqua.png +0 -0
  38. package/dist/assets/provider-icons/belo.png +0 -0
  39. package/dist/assets/provider-icons/binance.png +0 -0
  40. package/dist/assets/provider-icons/bipa.png +0 -0
  41. package/dist/assets/provider-icons/bitfinex.png +0 -0
  42. package/dist/assets/provider-icons/bitget.png +0 -0
  43. package/dist/assets/provider-icons/bitnob.png +0 -0
  44. package/dist/assets/provider-icons/bitso.png +0 -0
  45. package/dist/assets/provider-icons/bity.png +0 -0
  46. package/dist/assets/provider-icons/blink.png +0 -0
  47. package/dist/assets/provider-icons/bluewallet.png +0 -0
  48. package/dist/assets/provider-icons/boltz.png +0 -0
  49. package/dist/assets/provider-icons/bringin.png +0 -0
  50. package/dist/assets/provider-icons/bullbitcoin.png +0 -0
  51. package/dist/assets/provider-icons/cakewallet.png +0 -0
  52. package/dist/assets/provider-icons/cashapp.png +0 -0
  53. package/dist/assets/provider-icons/changenow.png +0 -0
  54. package/dist/assets/provider-icons/chivo.png +0 -0
  55. package/dist/assets/provider-icons/coinbase.png +0 -0
  56. package/dist/assets/provider-icons/coincorner.png +0 -0
  57. package/dist/assets/provider-icons/fixedfloat.png +0 -0
  58. package/dist/assets/provider-icons/getalby.png +0 -0
  59. package/dist/assets/provider-icons/kraken.png +0 -0
  60. package/dist/assets/provider-icons/kryptex.png +0 -0
  61. package/dist/assets/provider-icons/kucoin.png +0 -0
  62. package/dist/assets/provider-icons/manifest.json +207 -0
  63. package/dist/assets/provider-icons/mtpelerin.png +0 -0
  64. package/dist/assets/provider-icons/muun.png +0 -0
  65. package/dist/assets/provider-icons/okcoin.png +0 -0
  66. package/dist/assets/provider-icons/okx.png +0 -0
  67. package/dist/assets/provider-icons/phoenix.png +0 -0
  68. package/dist/assets/provider-icons/pouch.png +0 -0
  69. package/dist/assets/provider-icons/ripio.png +0 -0
  70. package/dist/assets/provider-icons/river.png +0 -0
  71. package/dist/assets/provider-icons/rizful.png +0 -0
  72. package/dist/assets/provider-icons/shakepay.png +0 -0
  73. package/dist/assets/provider-icons/sideshift.png +0 -0
  74. package/dist/assets/provider-icons/simpleswap.png +0 -0
  75. package/dist/assets/provider-icons/speed.png +0 -0
  76. package/dist/assets/provider-icons/strike.png +0 -0
  77. package/dist/assets/provider-icons/trocador.png +0 -0
  78. package/dist/assets/provider-icons/walletofsatoshi.png +0 -0
  79. package/dist/assets/provider-icons/zeus.png +0 -0
  80. package/dist/country-map.d.ts +5 -0
  81. package/dist/country-map.js +17 -0
  82. package/dist/index.d.ts +4 -0
  83. package/dist/index.js +2 -0
  84. package/dist/internal.d.ts +834 -0
  85. package/dist/internal.js +2617 -0
  86. package/dist/pay-tutorials.d.ts +1 -0
  87. package/dist/pay-tutorials.js +42 -0
  88. package/dist/provider-icons.d.ts +1 -0
  89. package/dist/provider-icons.js +84 -0
  90. package/dist/qrcode.d.ts +11 -0
  91. package/dist/status.d.ts +9 -0
  92. package/dist/status.js +25 -0
  93. package/dist/styles.css +759 -0
  94. package/package.json +28 -0
@@ -0,0 +1,2617 @@
1
+ import { getAssets, getCountries, getCountryRoutes, getPaymentWizardRoutes } from "@openreceive/provider-data";
2
+ import { openReceivePayTutorialUrls } from "./pay-tutorials.js";
3
+ import { openReceiveProviderIconUrls } from "./provider-icons.js";
4
+ import { status as deriveStatus } from "./status.js";
5
+ export { status } from "./status.js";
6
+ export const OPENRECEIVE_QR_QUIET_ZONE_MODULES = 4;
7
+ export const OPENRECEIVE_QR_DARK_COLOR = "#000000";
8
+ export const OPENRECEIVE_QR_LIGHT_COLOR = "#FFFFFFFF";
9
+ export const OPENRECEIVE_QR_ERROR_CORRECTION = "M";
10
+ export const OPENRECEIVE_COUNTRY_STORAGE_KEY = "openreceive.checkout.country";
11
+ export const OPENRECEIVE_LEGACY_DEMO_COUNTRY_STORAGE_KEY = "openreceive-demo.country";
12
+ export const OPENRECEIVE_THEME_STORAGE_KEY = "openreceive.theme";
13
+ export const OPENRECEIVE_DEFAULT_POLL_INTERVAL_MS = 3000;
14
+ export const OPENRECEIVE_COPY_FEEDBACK_MS = 1800;
15
+ export const OPENRECEIVE_PROVIDER_PREVIEW_LIMIT = 4;
16
+ export const OPENRECEIVE_CHECKOUT_ELEMENT_TAG_NAME = "openreceive-checkout";
17
+ export const OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME = "openreceive-theme-toggle";
18
+ export const OPENRECEIVE_COUNTRY_MAP_WIDTH = 820;
19
+ export const OPENRECEIVE_COUNTRY_MAP_HEIGHT = 420;
20
+ export const OPENRECEIVE_COUNTRY_MAP_VIEW_BOX = `0 0 ${OPENRECEIVE_COUNTRY_MAP_WIDTH} ${OPENRECEIVE_COUNTRY_MAP_HEIGHT}`;
21
+ export const OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS = {
22
+ copy: "openreceive-copy",
23
+ openWallet: "openreceive-open-wallet",
24
+ state: "openreceive-state",
25
+ settled: "openreceive-settled",
26
+ providerCopy: "openreceive-provider-copy",
27
+ startOver: "openreceive-start-over",
28
+ error: "openreceive-error"
29
+ };
30
+ export const OPENRECEIVE_THEME_TOGGLE_ELEMENT_EVENTS = {
31
+ change: "openreceive-theme-change"
32
+ };
33
+ export const OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES = {
34
+ root: "data-openreceive-wizard",
35
+ breadcrumb: "data-or-breadcrumb",
36
+ method: "data-or-method",
37
+ region: "data-or-region",
38
+ regionShape: "data-or-region-shape",
39
+ country: "data-or-country",
40
+ switchCountry: "data-or-switch-country",
41
+ route: "data-or-route",
42
+ providerCopy: "data-or-provider-copy",
43
+ providerTutorial: "data-or-provider-tutorial",
44
+ providerTutorialIndex: "data-or-provider-tutorial-index"
45
+ };
46
+ export const OPENRECEIVE_PAYMENT_WIZARD_SELECTORS = {
47
+ root: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.root}]`,
48
+ breadcrumb: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.breadcrumb}]`,
49
+ method: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.method}]`,
50
+ region: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.region}]`,
51
+ regionShape: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.regionShape}]`,
52
+ country: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.country}]`,
53
+ switchCountry: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.switchCountry}]`,
54
+ route: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.route}]`,
55
+ providerCopy: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerCopy}]`,
56
+ providerTutorial: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorial}]`,
57
+ providerTutorialIndex: `[${OPENRECEIVE_PAYMENT_WIZARD_ATTRIBUTES.providerTutorialIndex}]`
58
+ };
59
+ export const OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES = {
60
+ root: "data-openreceive-checkout",
61
+ qr: "data-openreceive-qr",
62
+ meta: "data-openreceive-meta",
63
+ state: "data-openreceive-state",
64
+ actions: "data-openreceive-actions",
65
+ theme: "data-openreceive-theme",
66
+ themeToggle: "data-openreceive-theme-toggle"
67
+ };
68
+ export const OPENRECEIVE_CHECKOUT_DATA_SELECTORS = {
69
+ root: `[${OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.root}]`,
70
+ qr: `[${OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.qr}]`,
71
+ meta: `[${OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.meta}]`,
72
+ state: `[${OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.state}]`,
73
+ actions: `[${OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.actions}]`,
74
+ theme: `[${OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.theme}]`,
75
+ themeToggle: `[${OPENRECEIVE_CHECKOUT_DATA_ATTRIBUTES.themeToggle}]`
76
+ };
77
+ export const OPENRECEIVE_CHECKOUT_ELEMENT_PARTS = {
78
+ copy: "copy",
79
+ open: "open",
80
+ startOver: "start-over"
81
+ };
82
+ export const OPENRECEIVE_CHECKOUT_ELEMENT_PART_SELECTORS = {
83
+ copy: `[part="${OPENRECEIVE_CHECKOUT_ELEMENT_PARTS.copy}"]`,
84
+ open: `[part="${OPENRECEIVE_CHECKOUT_ELEMENT_PARTS.open}"]`,
85
+ startOver: `[part="${OPENRECEIVE_CHECKOUT_ELEMENT_PARTS.startOver}"]`
86
+ };
87
+ export const OPENRECEIVE_THEME_TOGGLE_ELEMENT_PARTS = {
88
+ button: "button"
89
+ };
90
+ export const OPENRECEIVE_THEME_TOGGLE_ELEMENT_PART_SELECTORS = {
91
+ button: `[part="${OPENRECEIVE_THEME_TOGGLE_ELEMENT_PARTS.button}"]`
92
+ };
93
+ export function createCheckoutProviderCopyEvent(providerId) {
94
+ return new CustomEvent(OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.providerCopy, {
95
+ detail: {
96
+ providerId
97
+ }
98
+ });
99
+ }
100
+ export function createCheckoutActionEvent(eventName) {
101
+ return new CustomEvent(eventName);
102
+ }
103
+ export function createCheckoutStateEvent(eventName, state) {
104
+ return new CustomEvent(eventName, {
105
+ detail: {
106
+ state
107
+ }
108
+ });
109
+ }
110
+ export function createCheckoutErrorEvent(error) {
111
+ return new CustomEvent(OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.error, {
112
+ detail: {
113
+ error
114
+ }
115
+ });
116
+ }
117
+ export function createOpenReceiveThemeChangeEvent(theme) {
118
+ return new CustomEvent(OPENRECEIVE_THEME_TOGGLE_ELEMENT_EVENTS.change, {
119
+ detail: {
120
+ theme: theme.theme,
121
+ resolvedTheme: theme.resolvedTheme
122
+ }
123
+ });
124
+ }
125
+ const bankIcon = new URL("./assets/icons/bank.svg", import.meta.url).href;
126
+ const bnbIcon = new URL("./assets/icons/bnb.svg", import.meta.url).href;
127
+ const btcIcon = new URL("./assets/icons/btc.svg", import.meta.url).href;
128
+ const cardIcon = new URL("./assets/icons/card.svg", import.meta.url).href;
129
+ const cryptoIcon = new URL("./assets/icons/crypto.svg", import.meta.url).href;
130
+ const dogeIcon = new URL("./assets/icons/doge.svg", import.meta.url).href;
131
+ const ethIcon = new URL("./assets/icons/eth.svg", import.meta.url).href;
132
+ const lightningIcon = new URL("./assets/icons/lightning.svg", import.meta.url).href;
133
+ const ltcIcon = new URL("./assets/icons/ltc.svg", import.meta.url).href;
134
+ const solIcon = new URL("./assets/icons/sol.svg", import.meta.url).href;
135
+ const trxIcon = new URL("./assets/icons/trx.svg", import.meta.url).href;
136
+ const usdcIcon = new URL("./assets/icons/usdc.svg", import.meta.url).href;
137
+ const usdtIcon = new URL("./assets/icons/usdt.svg", import.meta.url).href;
138
+ const xmrIcon = new URL("./assets/icons/xmr.svg", import.meta.url).href;
139
+ const xrpIcon = new URL("./assets/icons/xrp.svg", import.meta.url).href;
140
+ export const openReceivePaymentIconUrls = {
141
+ bank: bankIcon,
142
+ bnb: bnbIcon,
143
+ btc: btcIcon,
144
+ card: cardIcon,
145
+ crypto: cryptoIcon,
146
+ doge: dogeIcon,
147
+ eth: ethIcon,
148
+ lightning: lightningIcon,
149
+ ltc: ltcIcon,
150
+ sol: solIcon,
151
+ trx: trxIcon,
152
+ usdc: usdcIcon,
153
+ usdt: usdtIcon,
154
+ xmr: xmrIcon,
155
+ xrp: xrpIcon
156
+ };
157
+ export const openReceivePaymentMethodIconIds = {
158
+ card: "card",
159
+ bank: "bank",
160
+ bitcoin: "btc",
161
+ crypto: "crypto"
162
+ };
163
+ export const openReceiveAssetIconIds = {
164
+ bnb: "bnb",
165
+ btc: "btc",
166
+ doge: "doge",
167
+ eth: "eth",
168
+ ltc: "ltc",
169
+ sol: "sol",
170
+ trx: "trx",
171
+ usdc: "usdc",
172
+ usdt: "usdt",
173
+ xmr: "xmr",
174
+ xrp: "xrp"
175
+ };
176
+ export const OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES = {
177
+ invoiceId: "invoice-id",
178
+ invoice: "invoice",
179
+ paymentHash: "payment-hash",
180
+ amountMsats: "amount-msats",
181
+ fiatCurrency: "fiat-currency",
182
+ fiatValue: "fiat-value",
183
+ status: "status",
184
+ expiresAt: "expires-at",
185
+ lookupUrl: "lookup-url",
186
+ theme: "theme",
187
+ paymentWizard: "payment-wizard"
188
+ };
189
+ export const OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES = {
190
+ rootSelector: "root-selector",
191
+ checkoutSelector: "checkout-selector",
192
+ defaultTheme: "default-theme",
193
+ storageKey: "storage-key"
194
+ };
195
+ export const openReceiveCheckoutLabels = {
196
+ copyInvoice: "Copy invoice",
197
+ copied: "Copied!",
198
+ openWallet: "Open Wallet",
199
+ paymentStatus: {
200
+ waitingTitle: "Waiting for payment",
201
+ waitingDetail: "Keep this page open while we verify settlement.",
202
+ settledTitle: "Payment received",
203
+ settledDetail: "Backend settlement verified.",
204
+ expiredTitle: "Invoice expired",
205
+ expiredDetail: "Create a fresh invoice to keep going."
206
+ },
207
+ countdownPrefix: "Invoice expires in",
208
+ startOver: "Start over",
209
+ wizardTitle: "Pay this invoice",
210
+ wizardSubtitle: "Choose how you want to pay.",
211
+ paymentMethod: "Payment method",
212
+ emptyBitcoin: "Choose Bitcoin Lightning.",
213
+ emptyCrypto: "Choose an altcoin.",
214
+ emptyFiat: "No providers found for this country yet.",
215
+ recommended: "Recommended",
216
+ openProvider: "How To Pay",
217
+ tutorialTitlePrefix: "Pay a Lightning invoice with",
218
+ tutorialIntroPrefix: "It's easy to make this payment using",
219
+ tutorialIntroCopy: "The first step is to copy the invoice to your clipboard.",
220
+ tutorialCopiedContinue: "Copied! Click next below to continue with tutorial.",
221
+ tutorialExit: "Exit",
222
+ lightningNetwork: "Lightning Network",
223
+ chooseCountry: "Choose a country"
224
+ };
225
+ export const openReceiveCheckoutElementStyles = `
226
+ :host {
227
+ --or-bg: #ffffff;
228
+ --or-bg-soft: #fafafa;
229
+ --or-text: #171717;
230
+ --or-muted: #525252;
231
+ --or-border: #d4d4d4;
232
+ --or-warm: #f97316;
233
+ --or-good-bg: #e8f5ee;
234
+ --or-good: #11613b;
235
+ color: var(--or-text);
236
+ display: block;
237
+ font: 14px/1.4 system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
238
+ }
239
+
240
+ :host([theme="dark"]),
241
+ [part="root"][data-theme="dark"] {
242
+ --or-bg: #1c2230;
243
+ --or-bg-soft: #121620;
244
+ --or-text: #f7f8fb;
245
+ --or-muted: #b7c0cf;
246
+ --or-border: #333c4d;
247
+ --or-warm: #ff9f1c;
248
+ --or-good-bg: #173828;
249
+ --or-good: #86efac;
250
+ }
251
+
252
+ [part="root"] {
253
+ background: var(--or-bg);
254
+ border: 1px solid var(--or-border);
255
+ border-radius: 8px;
256
+ display: grid;
257
+ gap: 12px;
258
+ padding: 12px;
259
+ }
260
+
261
+ [part="qr"] {
262
+ aspect-ratio: 1;
263
+ align-items: center;
264
+ background: #ffffff;
265
+ border: 1px solid var(--or-border);
266
+ border-radius: 6px;
267
+ display: flex;
268
+ justify-content: center;
269
+ justify-self: center;
270
+ min-width: 0;
271
+ overflow: hidden;
272
+ width: min(100%, 420px);
273
+ }
274
+
275
+ [part="qr"] svg {
276
+ display: block;
277
+ height: 100%;
278
+ width: 100%;
279
+ }
280
+
281
+ [part="meta"] {
282
+ align-items: center;
283
+ display: flex;
284
+ flex-wrap: wrap;
285
+ gap: 8px;
286
+ }
287
+
288
+ [part="state"] {
289
+ background: var(--or-good-bg);
290
+ border-radius: 999px;
291
+ color: var(--or-good);
292
+ padding: 2px 8px;
293
+ }
294
+
295
+ [part="payment-hash"] {
296
+ color: var(--or-muted);
297
+ font-size: 12px;
298
+ }
299
+
300
+ [part="invoice"] {
301
+ background: var(--or-bg-soft);
302
+ border: 1px solid var(--or-border);
303
+ border-radius: 6px;
304
+ box-sizing: border-box;
305
+ color: var(--or-text);
306
+ font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
307
+ min-height: 68px;
308
+ overflow-wrap: anywhere;
309
+ padding: 8px;
310
+ resize: vertical;
311
+ width: 100%;
312
+ }
313
+
314
+ [part="status"] {
315
+ align-items: center;
316
+ background: var(--or-bg-soft);
317
+ border: 1px solid var(--or-border);
318
+ border-radius: 6px;
319
+ display: flex;
320
+ gap: 10px;
321
+ padding: 8px;
322
+ }
323
+
324
+ [part="status"] p {
325
+ color: var(--or-muted);
326
+ margin: 2px 0 0;
327
+ }
328
+
329
+ [part="spinner"] {
330
+ border: 3px solid var(--or-border);
331
+ border-radius: 999px;
332
+ border-top-color: var(--or-text);
333
+ flex: 0 0 auto;
334
+ height: 22px;
335
+ width: 22px;
336
+ animation: openreceive-spin 850ms linear infinite;
337
+ }
338
+
339
+ [part="countdown"] {
340
+ color: var(--or-muted);
341
+ }
342
+
343
+ [part="actions"] {
344
+ display: grid;
345
+ gap: 8px;
346
+ }
347
+
348
+ [part="wizard"] {
349
+ border-top: 1px solid var(--or-border);
350
+ display: grid;
351
+ gap: 12px;
352
+ padding-top: 12px;
353
+ }
354
+
355
+ [part="wizard"] h2,
356
+ [part="wizard"] h3,
357
+ [part="wizard"] h4,
358
+ [part="wizard"] p {
359
+ margin: 0;
360
+ }
361
+
362
+ [part="wizard"] p,
363
+ [part="wizard"] small {
364
+ color: var(--or-muted);
365
+ }
366
+
367
+ [part="method-grid"],
368
+ [part="route-picker"],
369
+ [part="country-grid"],
370
+ [part="provider-grid"],
371
+ [part="provider-actions"] {
372
+ display: grid;
373
+ gap: 8px;
374
+ }
375
+
376
+ [part="method-grid"],
377
+ [part="route-picker"],
378
+ [part="country-grid"],
379
+ [part="provider-grid"],
380
+ [part="provider-actions"] {
381
+ grid-template-columns: 1fr 1fr;
382
+ }
383
+
384
+ [part="method"],
385
+ [part="route"],
386
+ [part="country"],
387
+ [part="provider"] {
388
+ background: var(--or-bg);
389
+ border: 1px solid var(--or-border);
390
+ border-radius: 6px;
391
+ box-sizing: border-box;
392
+ color: var(--or-text);
393
+ }
394
+
395
+ [part="method"],
396
+ [part="route"],
397
+ [part="country"],
398
+ [part="provider"] {
399
+ display: grid;
400
+ gap: 4px;
401
+ min-height: 64px;
402
+ padding: 8px;
403
+ text-align: left;
404
+ }
405
+
406
+ [part="method"] img,
407
+ [part="route"] img {
408
+ height: 28px;
409
+ width: 28px;
410
+ }
411
+
412
+ [part~="selected"] {
413
+ border-color: var(--or-text);
414
+ box-shadow: 0 0 0 1px var(--or-text);
415
+ }
416
+
417
+ [part="country-summary"] {
418
+ align-items: center;
419
+ border: 1px solid var(--or-border);
420
+ border-radius: 6px;
421
+ display: flex;
422
+ gap: 8px;
423
+ justify-content: space-between;
424
+ padding: 8px;
425
+ }
426
+
427
+ [part="wizard-results"] {
428
+ display: grid;
429
+ gap: 12px;
430
+ }
431
+
432
+ [part="wizard-breadcrumbs"] {
433
+ align-items: center;
434
+ display: flex;
435
+ flex-wrap: wrap;
436
+ gap: 6px;
437
+ }
438
+
439
+ [part="wizard-breadcrumb"] {
440
+ background: transparent;
441
+ border: 0;
442
+ color: var(--or-muted);
443
+ gap: 6px;
444
+ min-height: 32px;
445
+ padding: 0;
446
+ }
447
+
448
+ [part="wizard-breadcrumb"]:hover {
449
+ color: var(--or-text);
450
+ }
451
+
452
+ [part="wizard-breadcrumb-current"] {
453
+ color: var(--or-text);
454
+ font-weight: 700;
455
+ }
456
+
457
+ [part="wizard-breadcrumb-separator"] {
458
+ color: var(--or-muted);
459
+ }
460
+
461
+ [part="wizard-route"] {
462
+ display: grid;
463
+ gap: 18px;
464
+ }
465
+
466
+ [part="wizard-route"] h3 {
467
+ align-items: center;
468
+ display: flex;
469
+ flex-wrap: wrap;
470
+ gap: 8px;
471
+ }
472
+
473
+ [part="country-select"] {
474
+ align-items: center;
475
+ display: inline-flex;
476
+ font-size: 0.78em;
477
+ gap: 6px;
478
+ }
479
+
480
+ [part="country-select"] span {
481
+ clip: rect(0 0 0 0);
482
+ height: 1px;
483
+ overflow: hidden;
484
+ position: absolute;
485
+ white-space: nowrap;
486
+ width: 1px;
487
+ }
488
+
489
+ [part="country-select"] select {
490
+ background: var(--or-bg);
491
+ border: 1px solid var(--or-border);
492
+ border-radius: 6px;
493
+ color: var(--or-text);
494
+ font: inherit;
495
+ min-height: 34px;
496
+ padding: 0 8px;
497
+ }
498
+
499
+ [part="provider"] {
500
+ align-items: center;
501
+ column-gap: 10px;
502
+ display: grid;
503
+ grid-template-columns: minmax(0, 1fr) auto auto;
504
+ min-height: 0;
505
+ row-gap: 4px;
506
+ }
507
+
508
+ [part="provider-badges"] {
509
+ display: flex;
510
+ flex-wrap: wrap;
511
+ gap: 6px;
512
+ }
513
+
514
+ [part="provider-heading"] {
515
+ align-items: center;
516
+ display: flex;
517
+ gap: 8px;
518
+ grid-column: 1;
519
+ justify-content: start;
520
+ min-width: 0;
521
+ }
522
+
523
+ [part="provider-heading"] img {
524
+ border-radius: 6px;
525
+ flex: 0 0 auto;
526
+ height: 28px;
527
+ width: 28px;
528
+ }
529
+
530
+ [part="provider-heading"] h4 {
531
+ flex: 1 1 auto;
532
+ min-width: 0;
533
+ overflow: hidden;
534
+ text-overflow: ellipsis;
535
+ white-space: nowrap;
536
+ }
537
+
538
+ [part="provider-kind"] {
539
+ grid-column: 1;
540
+ color: var(--or-muted);
541
+ font-size: 0.9em;
542
+ line-height: 1.3;
543
+ margin: -2px 0 0;
544
+ }
545
+
546
+ [part="provider-badges"] span,
547
+ [part="recommended"] {
548
+ background: var(--or-bg-soft);
549
+ border-radius: 999px;
550
+ color: var(--or-muted);
551
+ font-size: 12px;
552
+ grid-column: 2;
553
+ justify-self: end;
554
+ padding: 2px 7px;
555
+ white-space: nowrap;
556
+ }
557
+
558
+ button,
559
+ a[part="open"],
560
+ button[part="provider-open"] {
561
+ align-items: center;
562
+ border: 1px solid var(--or-text);
563
+ border-radius: 6px;
564
+ box-sizing: border-box;
565
+ color: var(--or-text);
566
+ cursor: pointer;
567
+ display: inline-flex;
568
+ font: inherit;
569
+ justify-content: center;
570
+ min-height: 40px;
571
+ padding: 8px 10px;
572
+ text-decoration: none;
573
+ }
574
+
575
+ button {
576
+ background: var(--or-bg);
577
+ }
578
+
579
+ a[part="open"],
580
+ button[part="provider-open"] {
581
+ background: var(--or-text);
582
+ color: var(--or-bg);
583
+ }
584
+
585
+ [part="provider-actions"] {
586
+ align-self: center;
587
+ display: flex;
588
+ grid-column: 3;
589
+ grid-row: 1 / span 2;
590
+ justify-self: end;
591
+ }
592
+
593
+ button[part="provider-open"],
594
+ [part="provider-actions"] a {
595
+ background: var(--or-bg-soft);
596
+ color: var(--or-text);
597
+ min-height: 36px;
598
+ padding: 7px 12px;
599
+ white-space: nowrap;
600
+ }
601
+
602
+ [part="tutorial"] {
603
+ align-items: center;
604
+ background: rgb(0 0 0 / 0.6);
605
+ display: flex;
606
+ inset: 0;
607
+ justify-content: center;
608
+ padding: 16px;
609
+ position: fixed;
610
+ z-index: 10;
611
+ }
612
+
613
+ [part="tutorial"][hidden] {
614
+ display: none;
615
+ }
616
+
617
+ [part="tutorial-dialog"] {
618
+ background: var(--or-bg);
619
+ border: 1px solid var(--or-border);
620
+ border-radius: 8px;
621
+ box-shadow: 0 20px 70px rgb(0 0 0 / 0.32);
622
+ box-sizing: border-box;
623
+ display: grid;
624
+ gap: 12px;
625
+ max-height: min(92vh, 900px);
626
+ max-width: min(440px, 100%);
627
+ min-width: 0;
628
+ overflow: hidden;
629
+ padding: 12px;
630
+ width: 100%;
631
+ }
632
+
633
+ [part="tutorial-header"],
634
+ [part="tutorial-controls"] {
635
+ align-items: center;
636
+ display: flex;
637
+ gap: 8px;
638
+ justify-content: space-between;
639
+ }
640
+
641
+ [part="tutorial-header"] h3 {
642
+ font-size: 16px;
643
+ line-height: 1.25;
644
+ min-width: 0;
645
+ }
646
+
647
+ [part="tutorial-title"] {
648
+ align-items: center;
649
+ display: flex;
650
+ gap: 10px;
651
+ min-width: 0;
652
+ }
653
+
654
+ [part="tutorial-header-logo"] {
655
+ border-radius: 8px;
656
+ flex: 0 0 auto;
657
+ height: 36px;
658
+ width: 36px;
659
+ }
660
+
661
+ [part="tutorial-close"] {
662
+ background: var(--or-bg-soft);
663
+ border-color: var(--or-border);
664
+ color: var(--or-text);
665
+ flex: 0 0 auto;
666
+ font-size: 16px;
667
+ font-weight: 700;
668
+ height: 36px;
669
+ min-height: 36px;
670
+ padding: 0;
671
+ width: 36px;
672
+ }
673
+
674
+ [part="tutorial-frame"] {
675
+ align-items: center;
676
+ background: var(--or-bg-soft);
677
+ border: 1px solid var(--or-border);
678
+ border-radius: 8px;
679
+ display: flex;
680
+ justify-content: center;
681
+ min-height: 0;
682
+ overflow: hidden;
683
+ }
684
+
685
+ [part="tutorial-image"] {
686
+ display: block;
687
+ height: auto;
688
+ max-height: min(66vh, 720px);
689
+ max-width: 100%;
690
+ object-fit: contain;
691
+ width: auto;
692
+ }
693
+
694
+ [part="tutorial-caption"] {
695
+ color: var(--or-text);
696
+ font-size: 16px;
697
+ font-weight: 700;
698
+ text-align: center;
699
+ }
700
+
701
+ [part="tutorial-intro"] {
702
+ display: grid;
703
+ gap: 12px;
704
+ padding: 16px 8px;
705
+ text-align: center;
706
+ }
707
+
708
+ [part="tutorial-provider-logo"] {
709
+ border-radius: 8px;
710
+ height: 52px;
711
+ justify-self: center;
712
+ width: 52px;
713
+ }
714
+
715
+ [part="tutorial-intro"] p {
716
+ color: var(--or-text);
717
+ font-size: 15px;
718
+ }
719
+
720
+ [part="tutorial-copy"] {
721
+ background: var(--or-warm);
722
+ border-color: var(--or-warm);
723
+ color: #111827;
724
+ justify-self: center;
725
+ min-width: min(240px, 100%);
726
+ }
727
+
728
+ [part="tutorial-copy-message"] {
729
+ color: var(--or-good);
730
+ font-weight: 700;
731
+ }
732
+
733
+ [part="tutorial-progress"] {
734
+ color: var(--or-muted);
735
+ font-size: 12px;
736
+ text-align: center;
737
+ }
738
+
739
+ [part="tutorial-steps"] {
740
+ display: flex;
741
+ gap: 6px;
742
+ justify-content: center;
743
+ }
744
+
745
+ [part="tutorial-step"],
746
+ [part="tutorial-step-active"] {
747
+ background: var(--or-border);
748
+ border-radius: 999px;
749
+ height: 7px;
750
+ width: 7px;
751
+ }
752
+
753
+ [part="tutorial-step-active"] {
754
+ background: var(--or-text);
755
+ }
756
+
757
+ [part="tutorial-nav"] {
758
+ flex: 1 1 0;
759
+ }
760
+
761
+ [part="tutorial-controls"] [part="tutorial-nav"]:last-child:not(:disabled) {
762
+ background: var(--or-warm);
763
+ border-color: var(--or-warm);
764
+ color: #111827;
765
+ }
766
+
767
+ [part="tutorial-nav"]:disabled {
768
+ background: var(--or-bg-soft);
769
+ border-color: var(--or-border);
770
+ color: var(--or-muted);
771
+ cursor: not-allowed;
772
+ }
773
+
774
+ @media (max-width: 420px) {
775
+ [part="method-grid"],
776
+ [part="route-picker"],
777
+ [part="country-grid"],
778
+ [part="provider-grid"],
779
+ [part="provider-actions"] {
780
+ grid-template-columns: 1fr;
781
+ }
782
+
783
+ [part="tutorial"] {
784
+ align-items: stretch;
785
+ padding: 8px;
786
+ }
787
+
788
+ [part="tutorial-dialog"] {
789
+ max-height: calc(100vh - 16px);
790
+ }
791
+
792
+ [part="tutorial-controls"] {
793
+ display: grid;
794
+ grid-template-columns: 1fr 1fr;
795
+ }
796
+ }
797
+
798
+ @keyframes openreceive-spin {
799
+ to {
800
+ transform: rotate(360deg);
801
+ }
802
+ }
803
+ `;
804
+ export const openReceiveThemeToggleElementStyles = `
805
+ :host {
806
+ display: inline-block;
807
+ }
808
+
809
+ button {
810
+ align-items: center;
811
+ background: var(--or-theme-toggle-bg, transparent);
812
+ border: var(--or-theme-toggle-border, 1px solid currentColor);
813
+ border-radius: var(--or-theme-toggle-radius, 999px);
814
+ color: inherit;
815
+ cursor: pointer;
816
+ display: inline-flex;
817
+ gap: 8px;
818
+ font: inherit;
819
+ min-height: var(--or-theme-toggle-min-height, 34px);
820
+ padding: var(--or-theme-toggle-padding, 4px 12px 4px 4px);
821
+ }
822
+
823
+ .or-theme-toggle-track {
824
+ align-items: center;
825
+ background: transparent;
826
+ border: 0;
827
+ border-radius: 999px;
828
+ display: grid;
829
+ flex: 0 0 auto;
830
+ grid-template-columns: 1fr;
831
+ height: 24px;
832
+ padding: 0;
833
+ width: 24px;
834
+ }
835
+
836
+ .or-theme-toggle-icon {
837
+ border-radius: 999px;
838
+ display: block;
839
+ grid-area: 1 / 1;
840
+ height: 24px;
841
+ position: relative;
842
+ transition: background 160ms ease, box-shadow 160ms ease, opacity 160ms ease;
843
+ width: 24px;
844
+ }
845
+
846
+ .or-theme-toggle-icon-light {
847
+ opacity: 1;
848
+ }
849
+
850
+ .or-theme-toggle-dark .or-theme-toggle-icon-light {
851
+ background: #94a3b8;
852
+ box-shadow: none;
853
+ opacity: 0.72;
854
+ }
855
+
856
+ .or-theme-toggle-label {
857
+ font-size: 14px;
858
+ font-weight: 700;
859
+ line-height: 1;
860
+ white-space: nowrap;
861
+ }
862
+
863
+ .or-theme-toggle-icon-light {
864
+ background: #facc15;
865
+ box-shadow: 0 0 0 2px rgb(250 204 21 / 0.2);
866
+ }
867
+
868
+ .or-theme-toggle-icon-light::before,
869
+ .or-theme-toggle-icon-light::after {
870
+ content: "";
871
+ position: absolute;
872
+ }
873
+
874
+ .or-theme-toggle-icon-light::before {
875
+ border: 2px solid rgb(255 255 255 / 0.72);
876
+ border-radius: 999px;
877
+ inset: 5px;
878
+ }
879
+
880
+ .or-theme-toggle-icon-light::after {
881
+ background:
882
+ radial-gradient(circle, transparent 50%, #facc15 52%),
883
+ conic-gradient(
884
+ from 0deg,
885
+ transparent 0 8deg,
886
+ rgb(255 255 255 / 0.75) 8deg 14deg,
887
+ transparent 14deg 45deg,
888
+ rgb(255 255 255 / 0.75) 45deg 51deg,
889
+ transparent 51deg 82deg,
890
+ rgb(255 255 255 / 0.75) 82deg 88deg,
891
+ transparent 88deg 127deg,
892
+ rgb(255 255 255 / 0.75) 127deg 133deg,
893
+ transparent 133deg 172deg,
894
+ rgb(255 255 255 / 0.75) 172deg 178deg,
895
+ transparent 178deg 217deg,
896
+ rgb(255 255 255 / 0.75) 217deg 223deg,
897
+ transparent 223deg 262deg,
898
+ rgb(255 255 255 / 0.75) 262deg 268deg,
899
+ transparent 268deg 307deg,
900
+ rgb(255 255 255 / 0.75) 307deg 313deg,
901
+ transparent 313deg 360deg
902
+ );
903
+ border-radius: 999px;
904
+ inset: 3px;
905
+ }
906
+
907
+ `;
908
+ export const openReceivePaymentMethods = [
909
+ {
910
+ id: "card",
911
+ title: "Credit Card",
912
+ detail: "Pick your country, then use a card-friendly provider."
913
+ },
914
+ {
915
+ id: "bank",
916
+ title: "Bank Transfer",
917
+ detail: "Choose a country for local bank rails and cash apps."
918
+ },
919
+ {
920
+ id: "bitcoin",
921
+ title: "Bitcoin",
922
+ detail: "Pay from Lightning or send on-chain into a swap."
923
+ },
924
+ {
925
+ id: "crypto",
926
+ title: "Crypto",
927
+ detail: "Use stablecoins or altcoins through Lightning-capable services."
928
+ }
929
+ ];
930
+ export const openReceiveRegionLabels = {
931
+ "north-america": "North America",
932
+ "latin-america": "Latin America",
933
+ europe: "Europe",
934
+ africa: "Africa",
935
+ "middle-east": "Middle East",
936
+ "asia-pacific": "Asia Pacific"
937
+ };
938
+ export const openReceiveRegionOrder = [
939
+ "north-america",
940
+ "latin-america",
941
+ "europe",
942
+ "africa",
943
+ "middle-east",
944
+ "asia-pacific"
945
+ ];
946
+ export function parseOpenReceiveOptionalInteger(value, options = {}) {
947
+ if (value === null || value === undefined || value === "")
948
+ return undefined;
949
+ const parsed = Number(value);
950
+ if (!Number.isSafeInteger(parsed) || parsed < 0) {
951
+ throw new RangeError(`${options.label ?? "value"} must be a non-negative safe integer`);
952
+ }
953
+ return parsed;
954
+ }
955
+ export function parseOpenReceiveBooleanAttribute(value) {
956
+ if (value === null || value === undefined)
957
+ return undefined;
958
+ return value !== "false";
959
+ }
960
+ export function parseOpenReceiveResolvedTheme(value) {
961
+ return value === "light" || value === "dark" ? value : undefined;
962
+ }
963
+ export function parseOpenReceiveThemePreference(value) {
964
+ return value === "light" || value === "dark" || value === "system"
965
+ ? value
966
+ : undefined;
967
+ }
968
+ export function parseOpenReceivePaymentMethod(value) {
969
+ return value === "card" ||
970
+ value === "bank" ||
971
+ value === "bitcoin" ||
972
+ value === "crypto"
973
+ ? value
974
+ : null;
975
+ }
976
+ export function parseOpenReceiveRegion(value) {
977
+ return openReceiveRegionOrder.includes(value)
978
+ ? value
979
+ : null;
980
+ }
981
+ export const openReceiveCountryMapRegions = [
982
+ {
983
+ id: "north-america",
984
+ cx: 180,
985
+ cy: 125,
986
+ rx: 150,
987
+ ry: 78
988
+ },
989
+ {
990
+ id: "latin-america",
991
+ cx: 265,
992
+ cy: 260,
993
+ rx: 82,
994
+ ry: 130
995
+ },
996
+ {
997
+ id: "europe",
998
+ cx: 425,
999
+ cy: 145,
1000
+ rx: 105,
1001
+ ry: 64
1002
+ },
1003
+ {
1004
+ id: "africa",
1005
+ cx: 455,
1006
+ cy: 255,
1007
+ rx: 76,
1008
+ ry: 102
1009
+ },
1010
+ {
1011
+ id: "middle-east",
1012
+ cx: 535,
1013
+ cy: 213,
1014
+ rx: 78,
1015
+ ry: 58
1016
+ },
1017
+ {
1018
+ id: "asia-pacific",
1019
+ cx: 655,
1020
+ cy: 215,
1021
+ rx: 165,
1022
+ ry: 120
1023
+ }
1024
+ ];
1025
+ export const openReceiveCountryPins = {
1026
+ AR: { region: "latin-america", coordinates: [-64, -34] },
1027
+ AU: { region: "asia-pacific", coordinates: [134, -25] },
1028
+ BD: { region: "asia-pacific", coordinates: [90, 24] },
1029
+ BR: { region: "latin-america", coordinates: [-52, -10] },
1030
+ CA: { region: "north-america", coordinates: [-106, 56] },
1031
+ CH: { region: "europe", coordinates: [8, 47] },
1032
+ CL: { region: "latin-america", coordinates: [-71, -30] },
1033
+ CO: { region: "latin-america", coordinates: [-74, 4] },
1034
+ DE: { region: "europe", coordinates: [10, 51] },
1035
+ EG: { region: "africa", coordinates: [30, 27] },
1036
+ ES: { region: "europe", coordinates: [-4, 40] },
1037
+ FR: { region: "europe", coordinates: [2, 47] },
1038
+ GB: { region: "europe", coordinates: [-2, 54] },
1039
+ GH: { region: "africa", coordinates: [-1, 8] },
1040
+ IE: { region: "europe", coordinates: [-8, 53] },
1041
+ ID: { region: "asia-pacific", coordinates: [118, -2] },
1042
+ IN: { region: "asia-pacific", coordinates: [78, 22] },
1043
+ IT: { region: "europe", coordinates: [12, 43] },
1044
+ JP: { region: "asia-pacific", coordinates: [138, 37] },
1045
+ KE: { region: "africa", coordinates: [38, 0] },
1046
+ KR: { region: "asia-pacific", coordinates: [128, 36] },
1047
+ MX: { region: "latin-america", coordinates: [-102, 23] },
1048
+ NG: { region: "africa", coordinates: [8, 9] },
1049
+ NL: { region: "europe", coordinates: [5, 52] },
1050
+ PH: { region: "asia-pacific", coordinates: [122, 13] },
1051
+ PK: { region: "asia-pacific", coordinates: [70, 30] },
1052
+ PL: { region: "europe", coordinates: [19, 52] },
1053
+ PT: { region: "europe", coordinates: [-8, 39] },
1054
+ SA: { region: "middle-east", coordinates: [45, 24] },
1055
+ SG: { region: "asia-pacific", coordinates: [104, 1.3] },
1056
+ SV: { region: "latin-america", coordinates: [-89, 13.8] },
1057
+ TH: { region: "asia-pacific", coordinates: [101, 15] },
1058
+ TR: { region: "middle-east", coordinates: [35, 39] },
1059
+ UA: { region: "europe", coordinates: [31, 49] },
1060
+ US: { region: "north-america", coordinates: [-98, 39] },
1061
+ VE: { region: "latin-america", coordinates: [-66, 7] },
1062
+ VN: { region: "asia-pacific", coordinates: [108, 16] },
1063
+ ZA: { region: "africa", coordinates: [24, -29] },
1064
+ AE: { region: "middle-east", coordinates: [54, 24] }
1065
+ };
1066
+ export function getOpenReceiveDefaultCountryCode() {
1067
+ return getCountries().find((country) => country.code === "US")?.code ?? getCountries()[0]?.code ?? "";
1068
+ }
1069
+ export function getOpenReceiveBitcoinAssets() {
1070
+ return getAssets().filter((asset) => asset.symbol === "btc" && asset.route !== undefined);
1071
+ }
1072
+ function getOpenReceiveDefaultBitcoinRoute() {
1073
+ const routes = [
1074
+ ...new Set(getOpenReceiveBitcoinAssets().flatMap((asset) => asset.route === undefined ? [] : [asset.route]))
1075
+ ];
1076
+ return routes.length === 1 ? routes[0] ?? null : null;
1077
+ }
1078
+ export function getOpenReceiveAltcoinAssets() {
1079
+ return getAssets().filter((asset) => asset.route !== undefined &&
1080
+ asset.symbol !== "btc" &&
1081
+ !["usd", "eur", "gbp"].includes(asset.symbol));
1082
+ }
1083
+ export function getOpenReceiveRegionForCountry(countryCode) {
1084
+ return openReceiveCountryPins[countryCode]?.region ?? "north-america";
1085
+ }
1086
+ export function getOpenReceiveCoverageLabel(coverage) {
1087
+ void coverage;
1088
+ return "";
1089
+ }
1090
+ export function formatOpenReceiveCountryMetaLabel(country) {
1091
+ return country.currency;
1092
+ }
1093
+ export function createOpenReceiveCountryDisplay(country, options = {}) {
1094
+ return {
1095
+ country,
1096
+ code: country.code,
1097
+ label: country.name,
1098
+ metaLabel: formatOpenReceiveCountryMetaLabel(country),
1099
+ selected: country.code === options.selectedCountryCode
1100
+ };
1101
+ }
1102
+ export function getOpenReceivePaymentStatusText(phase) {
1103
+ if (phase === "settled") {
1104
+ return {
1105
+ title: openReceiveCheckoutLabels.paymentStatus.settledTitle,
1106
+ detail: openReceiveCheckoutLabels.paymentStatus.settledDetail
1107
+ };
1108
+ }
1109
+ if (phase === "expired") {
1110
+ return {
1111
+ title: openReceiveCheckoutLabels.paymentStatus.expiredTitle,
1112
+ detail: openReceiveCheckoutLabels.paymentStatus.expiredDetail
1113
+ };
1114
+ }
1115
+ return {
1116
+ title: openReceiveCheckoutLabels.paymentStatus.waitingTitle,
1117
+ detail: openReceiveCheckoutLabels.paymentStatus.waitingDetail
1118
+ };
1119
+ }
1120
+ export function getOpenReceiveWizardEmptyMessage(method) {
1121
+ if (method === "bitcoin")
1122
+ return openReceiveCheckoutLabels.emptyBitcoin;
1123
+ if (method === "crypto")
1124
+ return openReceiveCheckoutLabels.emptyCrypto;
1125
+ return openReceiveCheckoutLabels.emptyFiat;
1126
+ }
1127
+ export function getCheckoutProviderUsBadge(us) {
1128
+ void us;
1129
+ return null;
1130
+ }
1131
+ export function getCheckoutProviderOpenLabel(providerName) {
1132
+ void providerName;
1133
+ return openReceiveCheckoutLabels.openProvider;
1134
+ }
1135
+ export function getCheckoutProviderIcon(provider) {
1136
+ return openReceiveProviderIconUrls[provider.icon_path] ?? openReceivePaymentIconUrls.crypto;
1137
+ }
1138
+ export function getCheckoutProviderTutorials(provider) {
1139
+ return (provider.tutorials ?? []).map((tutorial) => ({
1140
+ index: tutorial.index,
1141
+ path: tutorial.path,
1142
+ image: openReceivePayTutorialUrls[tutorial.path] ?? tutorial.path,
1143
+ caption: tutorial.caption
1144
+ }));
1145
+ }
1146
+ export function getOpenReceiveRouteNetworkLabel(routeId) {
1147
+ return routeId === "lightning" || routeId === "btc-lightning"
1148
+ ? openReceiveCheckoutLabels.lightningNetwork
1149
+ : routeId;
1150
+ }
1151
+ export function createOpenReceiveWizardRouteAssetDisplays(assets, options = {}) {
1152
+ return assets.map((asset) => {
1153
+ const id = asset.route ?? asset.symbol;
1154
+ return {
1155
+ id,
1156
+ label: asset.label,
1157
+ subtitle: getOpenReceiveRouteNetworkLabel(id),
1158
+ icon: getOpenReceiveRouteIcon(asset),
1159
+ selected: options.selectedRoute === id
1160
+ };
1161
+ });
1162
+ }
1163
+ export function createOpenReceiveWizardRouteDisplays(routes, options = {}) {
1164
+ return routes.map((route) => ({
1165
+ key: getOpenReceiveWizardRouteDisplayKey(route),
1166
+ title: getOpenReceiveWizardRouteDisplayTitle(route),
1167
+ subtitle: getOpenReceiveWizardRouteDisplaySubtitle(route),
1168
+ providers: (options.providerPreviewLimit === undefined
1169
+ ? route.providers
1170
+ : route.providers.slice(0, options.providerPreviewLimit)).map((entry) => createOpenReceiveWizardProviderDisplay(entry))
1171
+ }));
1172
+ }
1173
+ function getOpenReceiveWizardRouteDisplayKey(route) {
1174
+ return route.kind === "crypto"
1175
+ ? route.route.id
1176
+ : `${route.rail.id}:${route.country.code}`;
1177
+ }
1178
+ function getOpenReceiveWizardRouteDisplayTitle(route) {
1179
+ return route.kind === "crypto"
1180
+ ? route.route.label
1181
+ : route.rail.label;
1182
+ }
1183
+ function getOpenReceiveWizardRouteDisplaySubtitle(route) {
1184
+ return route.kind === "crypto" ? route.route.symbol.toUpperCase() : route.country.currency;
1185
+ }
1186
+ function createOpenReceiveWizardProviderDisplay(entry) {
1187
+ return {
1188
+ id: entry.provider.id,
1189
+ name: entry.provider.name,
1190
+ kind: entry.provider.kind,
1191
+ url: entry.provider.lightning_docs_url ?? entry.provider.url,
1192
+ icon: getCheckoutProviderIcon(entry.provider),
1193
+ tutorials: getCheckoutProviderTutorials(entry.provider),
1194
+ recommended: entry.flagship,
1195
+ recommendedLabel: entry.flagship ? openReceiveCheckoutLabels.recommended : null,
1196
+ usBadge: null,
1197
+ copyLabel: openReceiveCheckoutLabels.copyInvoice,
1198
+ copiedLabel: openReceiveCheckoutLabels.copied,
1199
+ openLabel: getCheckoutProviderOpenLabel(entry.provider.name)
1200
+ };
1201
+ }
1202
+ export function getOpenReceivePaymentMethodIcon(method) {
1203
+ return openReceivePaymentIconUrls[openReceivePaymentMethodIconIds[method]];
1204
+ }
1205
+ export function getOpenReceiveAssetIcon(symbol) {
1206
+ return openReceivePaymentIconUrls[openReceiveAssetIconIds[symbol] ?? "crypto"];
1207
+ }
1208
+ export function getOpenReceiveRouteIcon(asset) {
1209
+ const routeId = asset.route ?? asset.symbol;
1210
+ if (asset.symbol === "btc" && routeId.includes("lightning")) {
1211
+ return openReceivePaymentIconUrls.lightning;
1212
+ }
1213
+ return getOpenReceiveAssetIcon(asset.symbol);
1214
+ }
1215
+ export function getOpenReceiveCountriesForRail(rail) {
1216
+ return getCountries()
1217
+ .filter((country) => openReceiveCountryPins[country.code] !== undefined &&
1218
+ getCountryRoutes(country.code).some((route) => route.rail.id === rail))
1219
+ .sort((left, right) => left.name.localeCompare(right.name, "en"));
1220
+ }
1221
+ export function projectOpenReceiveCountryMapPoint(coordinates, options = {}) {
1222
+ const width = options.width ?? OPENRECEIVE_COUNTRY_MAP_WIDTH;
1223
+ const height = options.height ?? OPENRECEIVE_COUNTRY_MAP_HEIGHT;
1224
+ const longitude = Math.max(-180, Math.min(180, coordinates[0]));
1225
+ const latitude = Math.max(-85, Math.min(85, coordinates[1]));
1226
+ return [
1227
+ ((longitude + 180) / 360) * width,
1228
+ ((85 - latitude) / 170) * height
1229
+ ];
1230
+ }
1231
+ export function createOpenReceiveCountryPickerModel(request) {
1232
+ const selectedCountry = request.countries.find((country) => country.code === request.selectedCountryCode);
1233
+ const hoveredCountry = request.hoveredCountryCode === undefined || request.hoveredCountryCode === null
1234
+ ? undefined
1235
+ : request.countries.find((country) => country.code === request.hoveredCountryCode);
1236
+ const visibleRegionCountries = request.countries.filter((country) => getOpenReceiveRegionForCountry(country.code) === request.selectedRegion);
1237
+ const selectedCountryDisplay = selectedCountry === undefined
1238
+ ? undefined
1239
+ : createOpenReceiveCountryDisplay(selectedCountry, {
1240
+ selectedCountryCode: request.selectedCountryCode
1241
+ });
1242
+ const hoveredCountryDisplay = hoveredCountry === undefined
1243
+ ? undefined
1244
+ : createOpenReceiveCountryDisplay(hoveredCountry, {
1245
+ selectedCountryCode: request.selectedCountryCode
1246
+ });
1247
+ const visibleRegionCountryDisplays = visibleRegionCountries.map((country) => createOpenReceiveCountryDisplay(country, {
1248
+ selectedCountryCode: request.selectedCountryCode
1249
+ }));
1250
+ const regions = openReceiveRegionOrder.map((region) => {
1251
+ const count = request.countries.filter((country) => getOpenReceiveRegionForCountry(country.code) === region).length;
1252
+ return {
1253
+ id: region,
1254
+ label: openReceiveRegionLabels[region],
1255
+ count,
1256
+ enabled: count > 0,
1257
+ selected: region === request.selectedRegion
1258
+ };
1259
+ });
1260
+ const mapCountries = request.countries.flatMap((country) => {
1261
+ const pin = openReceiveCountryPins[country.code];
1262
+ if (pin === undefined)
1263
+ return [];
1264
+ return [
1265
+ {
1266
+ country,
1267
+ region: pin.region,
1268
+ coordinates: pin.coordinates,
1269
+ point: projectOpenReceiveCountryMapPoint(pin.coordinates),
1270
+ selected: country.code === request.selectedCountryCode,
1271
+ hovered: country.code === request.hoveredCountryCode,
1272
+ label: country.name,
1273
+ metaLabel: formatOpenReceiveCountryMetaLabel(country)
1274
+ }
1275
+ ];
1276
+ });
1277
+ return {
1278
+ countries: request.countries,
1279
+ regions,
1280
+ selectedCountry,
1281
+ hoveredCountry,
1282
+ ...(selectedCountryDisplay === undefined ? {} : { selectedCountryDisplay }),
1283
+ ...(hoveredCountryDisplay === undefined ? {} : { hoveredCountryDisplay }),
1284
+ readoutLabel: hoveredCountryDisplay?.label ??
1285
+ selectedCountryDisplay?.label ??
1286
+ openReceiveCheckoutLabels.chooseCountry,
1287
+ ...((hoveredCountryDisplay ?? selectedCountryDisplay) === undefined
1288
+ ? {}
1289
+ : { readoutMetaLabel: (hoveredCountryDisplay ?? selectedCountryDisplay)?.metaLabel }),
1290
+ visibleRegionCountries,
1291
+ visibleRegionCountryDisplays,
1292
+ mapCountries
1293
+ };
1294
+ }
1295
+ export function createOpenReceivePaymentWizardState(request) {
1296
+ const selectedRail = getRailForPaymentMethod(request.selectedMethod);
1297
+ const railCountries = selectedRail === null ? [] : getOpenReceiveCountriesForRail(selectedRail);
1298
+ const selectedCountry = request.selectedCountryCode === undefined
1299
+ ? railCountries[0]
1300
+ : railCountries.find((country) => country.code === request.selectedCountryCode) ??
1301
+ railCountries[0];
1302
+ const selectedRouteId = request.selectedMethod === "bitcoin"
1303
+ ? request.selectedBitcoinRoute ?? getOpenReceiveDefaultBitcoinRoute()
1304
+ : request.selectedMethod === "crypto"
1305
+ ? request.selectedCryptoRoute ?? null
1306
+ : null;
1307
+ const routes = selectedRail !== null && selectedCountry !== undefined
1308
+ ? getPaymentWizardRoutes({
1309
+ country: selectedCountry.code,
1310
+ rail: selectedRail
1311
+ })
1312
+ : selectedRouteId === null
1313
+ ? []
1314
+ : getPaymentWizardRoutes({ route: selectedRouteId });
1315
+ return {
1316
+ selectedRail,
1317
+ ...(selectedCountry === undefined ? {} : { selectedCountry }),
1318
+ railCountries,
1319
+ selectedRouteId,
1320
+ routes
1321
+ };
1322
+ }
1323
+ export function createOpenReceivePaymentWizardSelection(options = {}) {
1324
+ const selectedCountryCode = options.storedCountryCode ?? options.defaultCountryCode ?? getOpenReceiveDefaultCountryCode();
1325
+ return {
1326
+ selectedMethod: null,
1327
+ selectedCountryCode,
1328
+ selectedBitcoinRoute: null,
1329
+ selectedCryptoRoute: null,
1330
+ selectedRegion: getOpenReceiveRegionForCountry(selectedCountryCode),
1331
+ countryPickerOpen: options.storedCountryCode === undefined
1332
+ ? true
1333
+ : options.storedCountryCode === null
1334
+ };
1335
+ }
1336
+ export function createOpenReceivePaymentWizardModel(selection) {
1337
+ const wizard = createOpenReceivePaymentWizardState({
1338
+ selectedMethod: selection.selectedMethod,
1339
+ selectedCountryCode: selection.selectedCountryCode,
1340
+ selectedBitcoinRoute: selection.selectedBitcoinRoute,
1341
+ selectedCryptoRoute: selection.selectedCryptoRoute
1342
+ });
1343
+ const routeAssets = selection.selectedMethod === "bitcoin"
1344
+ ? getOpenReceiveBitcoinAssets()
1345
+ : selection.selectedMethod === "crypto"
1346
+ ? getOpenReceiveAltcoinAssets()
1347
+ : [];
1348
+ const selectedRoute = wizard.selectedRouteId;
1349
+ const countryPicker = createOpenReceiveCountryPickerModel({
1350
+ countries: wizard.railCountries,
1351
+ selectedCountryCode: selection.selectedCountryCode,
1352
+ selectedRegion: selection.selectedRegion
1353
+ });
1354
+ const countryDisplays = wizard.railCountries.map((country) => createOpenReceiveCountryDisplay(country, {
1355
+ selectedCountryCode: selection.selectedCountryCode
1356
+ }));
1357
+ return {
1358
+ selection,
1359
+ wizard,
1360
+ countryPicker,
1361
+ countryDisplays,
1362
+ visibleRegionCountries: countryPicker.visibleRegionCountries,
1363
+ visibleRegionCountryDisplays: countryPicker.visibleRegionCountryDisplays,
1364
+ ...(countryPicker.selectedCountryDisplay === undefined
1365
+ ? {}
1366
+ : { selectedCountryDisplay: countryPicker.selectedCountryDisplay }),
1367
+ routeAssets,
1368
+ selectedRoute
1369
+ };
1370
+ }
1371
+ export function updateOpenReceivePaymentWizardSelection(selection, action) {
1372
+ switch (action.type) {
1373
+ case "select_method": {
1374
+ if (action.method !== "card" && action.method !== "bank") {
1375
+ return {
1376
+ ...selection,
1377
+ selectedMethod: action.method,
1378
+ selectedBitcoinRoute: action.method === "bitcoin"
1379
+ ? selection.selectedBitcoinRoute ?? getOpenReceiveDefaultBitcoinRoute()
1380
+ : selection.selectedBitcoinRoute,
1381
+ countryPickerOpen: false
1382
+ };
1383
+ }
1384
+ const selectedCountryCode = action.storedCountryCode ?? selection.selectedCountryCode;
1385
+ return {
1386
+ ...selection,
1387
+ selectedMethod: action.method,
1388
+ selectedCountryCode,
1389
+ selectedRegion: getOpenReceiveRegionForCountry(selectedCountryCode),
1390
+ countryPickerOpen: false
1391
+ };
1392
+ }
1393
+ case "change_method": {
1394
+ return {
1395
+ ...selection,
1396
+ selectedMethod: null,
1397
+ selectedBitcoinRoute: null,
1398
+ selectedCryptoRoute: null,
1399
+ countryPickerOpen: false
1400
+ };
1401
+ }
1402
+ case "change_route": {
1403
+ if (selection.selectedMethod === "bitcoin") {
1404
+ return {
1405
+ ...selection,
1406
+ selectedBitcoinRoute: null
1407
+ };
1408
+ }
1409
+ if (selection.selectedMethod === "crypto") {
1410
+ return {
1411
+ ...selection,
1412
+ selectedCryptoRoute: null
1413
+ };
1414
+ }
1415
+ return selection;
1416
+ }
1417
+ case "select_region": {
1418
+ const nextSelection = {
1419
+ ...selection,
1420
+ selectedRegion: action.region
1421
+ };
1422
+ const regionCountries = createOpenReceivePaymentWizardModel(nextSelection)
1423
+ .wizard
1424
+ .railCountries
1425
+ .filter((country) => getOpenReceiveRegionForCountry(country.code) === action.region);
1426
+ if (regionCountries.some((country) => country.code === selection.selectedCountryCode)) {
1427
+ return nextSelection;
1428
+ }
1429
+ const first = regionCountries[0];
1430
+ return first === undefined
1431
+ ? nextSelection
1432
+ : {
1433
+ ...nextSelection,
1434
+ selectedCountryCode: first.code
1435
+ };
1436
+ }
1437
+ case "select_country": {
1438
+ return {
1439
+ ...selection,
1440
+ selectedCountryCode: action.countryCode,
1441
+ selectedRegion: getOpenReceiveRegionForCountry(action.countryCode),
1442
+ countryPickerOpen: false
1443
+ };
1444
+ }
1445
+ case "open_country_picker": {
1446
+ return {
1447
+ ...selection,
1448
+ countryPickerOpen: true
1449
+ };
1450
+ }
1451
+ case "select_route": {
1452
+ if (selection.selectedMethod === "bitcoin") {
1453
+ return {
1454
+ ...selection,
1455
+ selectedBitcoinRoute: action.route
1456
+ };
1457
+ }
1458
+ if (selection.selectedMethod === "crypto") {
1459
+ return {
1460
+ ...selection,
1461
+ selectedCryptoRoute: action.route
1462
+ };
1463
+ }
1464
+ return selection;
1465
+ }
1466
+ }
1467
+ }
1468
+ export class OpenReceiveBrowserPaymentWizardController {
1469
+ options;
1470
+ selection;
1471
+ constructor(options = {}) {
1472
+ this.options = options;
1473
+ this.selection =
1474
+ options.selection ??
1475
+ createOpenReceivePaymentWizardSelection({
1476
+ storedCountryCode: options.storedCountryCode ??
1477
+ readOpenReceiveStoredCountryCode({
1478
+ storage: options.storage,
1479
+ storageKey: options.storageKey
1480
+ }),
1481
+ defaultCountryCode: options.defaultCountryCode
1482
+ });
1483
+ }
1484
+ getSelection() {
1485
+ return this.selection;
1486
+ }
1487
+ getModel() {
1488
+ return createOpenReceivePaymentWizardModel(this.selection);
1489
+ }
1490
+ update(action) {
1491
+ const normalizedAction = action.type === "select_method" &&
1492
+ (action.method === "card" || action.method === "bank") &&
1493
+ action.storedCountryCode === undefined
1494
+ ? {
1495
+ ...action,
1496
+ storedCountryCode: readOpenReceiveStoredCountryCode({
1497
+ storage: this.options.storage,
1498
+ storageKey: this.options.storageKey
1499
+ })
1500
+ }
1501
+ : action;
1502
+ const next = updateOpenReceivePaymentWizardSelection(this.selection, normalizedAction);
1503
+ if (action.type === "select_country") {
1504
+ writeOpenReceiveStoredCountryCode(action.countryCode, {
1505
+ storage: this.options.storage,
1506
+ storageKey: this.options.storageKey
1507
+ });
1508
+ }
1509
+ this.selection = next;
1510
+ this.options.onSelection?.(next);
1511
+ return next;
1512
+ }
1513
+ selectMethod(method) {
1514
+ return this.update({
1515
+ type: "select_method",
1516
+ method
1517
+ });
1518
+ }
1519
+ changeMethod() {
1520
+ return this.update({
1521
+ type: "change_method"
1522
+ });
1523
+ }
1524
+ selectRegion(region) {
1525
+ return this.update({
1526
+ type: "select_region",
1527
+ region
1528
+ });
1529
+ }
1530
+ selectCountry(countryCode) {
1531
+ return this.update({
1532
+ type: "select_country",
1533
+ countryCode
1534
+ });
1535
+ }
1536
+ openCountryPicker() {
1537
+ return this.update({
1538
+ type: "open_country_picker"
1539
+ });
1540
+ }
1541
+ selectRoute(route) {
1542
+ return this.update({
1543
+ type: "select_route",
1544
+ route
1545
+ });
1546
+ }
1547
+ }
1548
+ export function createOpenReceivePaymentWizardController(options = {}) {
1549
+ return new OpenReceiveBrowserPaymentWizardController(options);
1550
+ }
1551
+ export function readOpenReceiveStoredCountryCode(options = {}) {
1552
+ const storage = options.storage ?? getBrowserStorage();
1553
+ const storageKey = options.storageKey ?? OPENRECEIVE_COUNTRY_STORAGE_KEY;
1554
+ const legacyStorageKey = options.legacyStorageKey ?? OPENRECEIVE_LEGACY_DEMO_COUNTRY_STORAGE_KEY;
1555
+ const countryCode = readStorageValue(storage, storageKey) ?? readStorageValue(storage, legacyStorageKey);
1556
+ if (countryCode === null)
1557
+ return null;
1558
+ const normalizedCountryCode = countryCode.trim().toUpperCase();
1559
+ return isKnownCountryCode(normalizedCountryCode) ? normalizedCountryCode : null;
1560
+ }
1561
+ export function writeOpenReceiveStoredCountryCode(countryCode, options = {}) {
1562
+ const normalizedCountryCode = countryCode.trim().toUpperCase();
1563
+ if (!isKnownCountryCode(normalizedCountryCode))
1564
+ return;
1565
+ writeStorageValue(options.storage ?? getBrowserStorage(), options.storageKey ?? OPENRECEIVE_COUNTRY_STORAGE_KEY, normalizedCountryCode);
1566
+ }
1567
+ export function readOpenReceiveThemePreference(options = {}) {
1568
+ const value = readStorageValue(options.storage ?? getBrowserStorage(), options.storageKey ?? OPENRECEIVE_THEME_STORAGE_KEY);
1569
+ return value === "light" || value === "dark" || value === "system"
1570
+ ? value
1571
+ : options.defaultTheme ?? "system";
1572
+ }
1573
+ export function writeOpenReceiveThemePreference(theme, options = {}) {
1574
+ writeStorageValue(options.storage ?? getBrowserStorage(), options.storageKey ?? OPENRECEIVE_THEME_STORAGE_KEY, theme);
1575
+ }
1576
+ export function resolveOpenReceiveTheme(theme, options = {}) {
1577
+ if (theme === "light" || theme === "dark")
1578
+ return theme;
1579
+ if (options.systemDark !== undefined)
1580
+ return options.systemDark ? "dark" : "light";
1581
+ return globalThis.matchMedia?.("(prefers-color-scheme: dark)").matches
1582
+ ? "dark"
1583
+ : "light";
1584
+ }
1585
+ export function getOpenReceiveNextThemePreference(theme, options = {}) {
1586
+ return resolveOpenReceiveTheme(theme, options) === "dark" ? "light" : "dark";
1587
+ }
1588
+ export function getOpenReceiveThemeToggleLabel(resolvedTheme) {
1589
+ return `${resolvedTheme} mode`;
1590
+ }
1591
+ export function createOpenReceiveThemeModel(theme, options = {}) {
1592
+ const resolvedTheme = resolveOpenReceiveTheme(theme, options);
1593
+ return {
1594
+ theme,
1595
+ resolvedTheme,
1596
+ nextTheme: getOpenReceiveNextThemePreference(theme, options),
1597
+ toggleLabel: getOpenReceiveThemeToggleLabel(resolvedTheme),
1598
+ attributes: {
1599
+ "data-theme": resolvedTheme,
1600
+ "data-openreceive-theme": resolvedTheme
1601
+ },
1602
+ checkoutElementAttributes: {
1603
+ theme: resolvedTheme
1604
+ }
1605
+ };
1606
+ }
1607
+ export function createOpenReceiveStoredThemeModel(options = {}) {
1608
+ const theme = readOpenReceiveThemePreference(options);
1609
+ return createOpenReceiveThemeModel(theme, { systemDark: options.systemDark });
1610
+ }
1611
+ export function toggleOpenReceiveStoredThemePreference(options = {}) {
1612
+ const currentTheme = createOpenReceiveStoredThemeModel(options);
1613
+ writeOpenReceiveThemePreference(currentTheme.nextTheme, options);
1614
+ return createOpenReceiveStoredThemeModel(options);
1615
+ }
1616
+ export function applyOpenReceiveThemeAttributes(target, theme) {
1617
+ if (target === null || target === undefined)
1618
+ return;
1619
+ for (const [name, value] of Object.entries(theme.attributes)) {
1620
+ target.setAttribute(name, value);
1621
+ }
1622
+ }
1623
+ export function applyCheckoutThemeAttributes(target, theme) {
1624
+ if (target === null || target === undefined)
1625
+ return;
1626
+ for (const [name, value] of Object.entries(theme.checkoutElementAttributes)) {
1627
+ target.setAttribute(name, value);
1628
+ }
1629
+ }
1630
+ export function applyOpenReceiveThemeControls(targets, theme) {
1631
+ applyOpenReceiveThemeAttributes(targets.root, theme);
1632
+ applyCheckoutThemeAttributes(targets.checkout, theme);
1633
+ if (targets.toggle !== null && targets.toggle !== undefined) {
1634
+ targets.toggle.textContent = theme.toggleLabel;
1635
+ }
1636
+ }
1637
+ export function syncOpenReceiveStoredThemeControls(targets, options = {}) {
1638
+ const theme = createOpenReceiveStoredThemeModel(options);
1639
+ applyOpenReceiveThemeControls(targets, theme);
1640
+ return theme;
1641
+ }
1642
+ export function toggleOpenReceiveStoredThemeControls(targets, options = {}) {
1643
+ const theme = toggleOpenReceiveStoredThemePreference(options);
1644
+ applyOpenReceiveThemeControls(targets, theme);
1645
+ return theme;
1646
+ }
1647
+ export function createOpenReceiveTransientFeedbackController(options) {
1648
+ const delayMs = options.delayMs ?? OPENRECEIVE_COPY_FEEDBACK_MS;
1649
+ const setTimeoutFn = options.setTimeout ?? globalThis.setTimeout;
1650
+ const clearTimeoutFn = options.clearTimeout ?? globalThis.clearTimeout;
1651
+ let timeout;
1652
+ const clear = () => {
1653
+ if (timeout === undefined)
1654
+ return;
1655
+ clearTimeoutFn(timeout);
1656
+ timeout = undefined;
1657
+ };
1658
+ return {
1659
+ show(value) {
1660
+ clear();
1661
+ options.onValue(value);
1662
+ timeout = setTimeoutFn(() => {
1663
+ timeout = undefined;
1664
+ options.onValue(options.resetValue);
1665
+ }, delayMs);
1666
+ },
1667
+ clear
1668
+ };
1669
+ }
1670
+ export function formatOpenReceiveCountdown(seconds) {
1671
+ const safeSeconds = Math.max(0, Math.trunc(seconds));
1672
+ const minutes = Math.floor(safeSeconds / 60);
1673
+ const remainderSeconds = safeSeconds % 60;
1674
+ return `${minutes}:${remainderSeconds.toString().padStart(2, "0")}`;
1675
+ }
1676
+ export function escapeOpenReceiveHtml(value) {
1677
+ return value
1678
+ .replaceAll("&", "&amp;")
1679
+ .replaceAll("<", "&lt;")
1680
+ .replaceAll(">", "&gt;")
1681
+ .replaceAll('"', "&quot;");
1682
+ }
1683
+ export function formatOpenReceiveMsats(amountMsats) {
1684
+ if (!Number.isSafeInteger(amountMsats) || amountMsats < 0) {
1685
+ throw new RangeError("amount_msats must be a non-negative safe integer");
1686
+ }
1687
+ if (amountMsats % 1000 === 0) {
1688
+ const sats = amountMsats / 1000;
1689
+ return `${sats} ${sats === 1 ? "sat" : "sats"}`;
1690
+ }
1691
+ return `${amountMsats} msats`;
1692
+ }
1693
+ export function formatOpenReceiveFiatAmount(fiat) {
1694
+ if (fiat?.currency === undefined || fiat.value === undefined)
1695
+ return undefined;
1696
+ return fiat.currency === "USD" ? `$${fiat.value}` : `${fiat.value} ${fiat.currency}`;
1697
+ }
1698
+ export function formatOpenReceivePaymentHashLabel(hash) {
1699
+ return hash.length <= 16 ? hash : `${hash.slice(0, 8)}...${hash.slice(-8)}`;
1700
+ }
1701
+ export function assertOpenReceiveDisplayInvoice(invoice) {
1702
+ assertInvoice(invoice);
1703
+ }
1704
+ export function createCheckoutDisplayModel(data) {
1705
+ return {
1706
+ ...data,
1707
+ lightningUri: createLightningUri(data.invoice),
1708
+ ...(data.amount_msats === undefined
1709
+ ? {}
1710
+ : { amountLabel: formatOpenReceiveMsats(data.amount_msats) }),
1711
+ ...(formatOpenReceiveFiatAmount(data.fiat_quote?.fiat) === undefined
1712
+ ? {}
1713
+ : { fiatLabel: formatOpenReceiveFiatAmount(data.fiat_quote?.fiat) }),
1714
+ ...(data.payment_hash === undefined
1715
+ ? {}
1716
+ : { paymentHashLabel: formatOpenReceivePaymentHashLabel(data.payment_hash) }),
1717
+ ...(data.transaction_state === undefined
1718
+ ? {}
1719
+ : { transactionStateLabel: data.transaction_state })
1720
+ };
1721
+ }
1722
+ export function createLightningUri(invoice) {
1723
+ assertInvoice(invoice);
1724
+ return `lightning:${invoice}`;
1725
+ }
1726
+ export function createCheckoutElementAttributes(snapshot, options = {}) {
1727
+ assertInvoice(snapshot.invoice);
1728
+ const attributes = {
1729
+ [OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.invoiceId]: snapshot.invoice_id,
1730
+ [OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.invoice]: snapshot.invoice
1731
+ };
1732
+ if (snapshot.payment_hash !== undefined) {
1733
+ attributes[OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.paymentHash] = snapshot.payment_hash;
1734
+ }
1735
+ if (snapshot.amount_msats !== undefined) {
1736
+ attributes[OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.amountMsats] = String(snapshot.amount_msats);
1737
+ }
1738
+ const fiat = snapshot.fiat_quote?.fiat;
1739
+ if (fiat?.currency !== undefined) {
1740
+ attributes[OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.fiatCurrency] = fiat.currency;
1741
+ }
1742
+ if (fiat?.value !== undefined) {
1743
+ attributes[OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.fiatValue] = fiat.value;
1744
+ }
1745
+ attributes[OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.status] = deriveStatus(snapshot);
1746
+ if (snapshot.expires_at !== undefined) {
1747
+ attributes[OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.expiresAt] = String(snapshot.expires_at);
1748
+ }
1749
+ if (options.lookupUrl !== undefined) {
1750
+ attributes[OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.lookupUrl] = options.lookupUrl;
1751
+ }
1752
+ if (options.theme !== undefined) {
1753
+ attributes[OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.theme] = options.theme;
1754
+ }
1755
+ if (options.paymentWizard !== undefined) {
1756
+ attributes[OPENRECEIVE_CHECKOUT_ELEMENT_ATTRIBUTES.paymentWizard] = String(options.paymentWizard);
1757
+ }
1758
+ return attributes;
1759
+ }
1760
+ export function createOpenReceiveThemeToggleElementAttributes(options = {}) {
1761
+ return {
1762
+ ...(options.rootSelector === undefined
1763
+ ? {}
1764
+ : { [OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.rootSelector]: options.rootSelector }),
1765
+ ...(options.checkoutSelector === undefined
1766
+ ? {}
1767
+ : { [OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.checkoutSelector]: options.checkoutSelector }),
1768
+ ...(options.defaultTheme === undefined
1769
+ ? {}
1770
+ : { [OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.defaultTheme]: options.defaultTheme }),
1771
+ ...(options.storageKey === undefined
1772
+ ? {}
1773
+ : { [OPENRECEIVE_THEME_TOGGLE_ELEMENT_ATTRIBUTES.storageKey]: options.storageKey })
1774
+ };
1775
+ }
1776
+ export function createCheckoutElementListeners(handlers = {}) {
1777
+ return {
1778
+ ...(handlers.onCopy === undefined
1779
+ ? {}
1780
+ : { [OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.copy]: handlers.onCopy }),
1781
+ ...(handlers.onOpenWallet === undefined
1782
+ ? {}
1783
+ : { [OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.openWallet]: handlers.onOpenWallet }),
1784
+ ...(handlers.onState === undefined
1785
+ ? {}
1786
+ : { [OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.state]: handlers.onState }),
1787
+ ...(handlers.onSettled === undefined
1788
+ ? {}
1789
+ : { [OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.settled]: handlers.onSettled }),
1790
+ ...(handlers.onProviderCopy === undefined
1791
+ ? {}
1792
+ : { [OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.providerCopy]: handlers.onProviderCopy }),
1793
+ ...(handlers.onStartOver === undefined
1794
+ ? {}
1795
+ : { [OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.startOver]: handlers.onStartOver }),
1796
+ ...(handlers.onError === undefined
1797
+ ? {}
1798
+ : { [OPENRECEIVE_CHECKOUT_ELEMENT_EVENTS.error]: handlers.onError })
1799
+ };
1800
+ }
1801
+ export function createCheckoutShellModel(snapshot, options = {}) {
1802
+ const theme = createOpenReceiveStoredThemeModel(options);
1803
+ return {
1804
+ theme,
1805
+ rootAttributes: theme.attributes,
1806
+ checkout: {
1807
+ tagName: OPENRECEIVE_CHECKOUT_ELEMENT_TAG_NAME,
1808
+ attributes: createCheckoutElementAttributes(snapshot, {
1809
+ ...options,
1810
+ theme: theme.resolvedTheme
1811
+ }),
1812
+ listeners: createCheckoutElementListeners(options)
1813
+ },
1814
+ themeToggle: {
1815
+ tagName: OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME,
1816
+ attributes: createOpenReceiveThemeToggleElementAttributes({
1817
+ rootSelector: options.rootSelector,
1818
+ checkoutSelector: options.checkoutSelector ?? OPENRECEIVE_CHECKOUT_ELEMENT_TAG_NAME,
1819
+ defaultTheme: options.defaultTheme,
1820
+ storageKey: options.storageKey
1821
+ })
1822
+ }
1823
+ };
1824
+ }
1825
+ export function applyCheckoutElementAttributes(target, attributes) {
1826
+ for (const [name, value] of Object.entries(attributes)) {
1827
+ if (value !== undefined)
1828
+ target.setAttribute(name, value);
1829
+ }
1830
+ }
1831
+ export function applyCheckoutElementListeners(target, listeners) {
1832
+ for (const [name, listener] of Object.entries(listeners)) {
1833
+ if (listener !== undefined)
1834
+ target.addEventListener(name, listener);
1835
+ }
1836
+ }
1837
+ export function applyOpenReceiveThemeToggleElementAttributes(target, attributes) {
1838
+ for (const [name, value] of Object.entries(attributes)) {
1839
+ if (value !== undefined)
1840
+ target.setAttribute(name, value);
1841
+ }
1842
+ }
1843
+ export function createCheckoutElement(snapshot, options = {}) {
1844
+ const ownerDocument = options.document ?? globalThis.document;
1845
+ if (ownerDocument === undefined) {
1846
+ throw new Error("OpenReceive checkout element creation requires document.");
1847
+ }
1848
+ const element = ownerDocument.createElement(OPENRECEIVE_CHECKOUT_ELEMENT_TAG_NAME);
1849
+ applyCheckoutElementAttributes(element, createCheckoutElementAttributes(snapshot, options));
1850
+ applyCheckoutElementListeners(element, createCheckoutElementListeners(options));
1851
+ return element;
1852
+ }
1853
+ export function createOpenReceiveThemeToggleElement(options = {}) {
1854
+ const ownerDocument = options.document ?? globalThis.document;
1855
+ if (ownerDocument === undefined) {
1856
+ throw new Error("OpenReceive theme toggle element creation requires document.");
1857
+ }
1858
+ const element = ownerDocument.createElement(OPENRECEIVE_THEME_TOGGLE_ELEMENT_TAG_NAME);
1859
+ applyOpenReceiveThemeToggleElementAttributes(element, createOpenReceiveThemeToggleElementAttributes(options));
1860
+ return element;
1861
+ }
1862
+ export function createCheckoutShell(snapshot, options = {}) {
1863
+ const ownerDocument = options.document ?? globalThis.document;
1864
+ if (ownerDocument === undefined) {
1865
+ throw new Error("OpenReceive checkout shell creation requires document.");
1866
+ }
1867
+ const shell = createCheckoutShellModel(snapshot, options);
1868
+ applyOpenReceiveThemeAttributes(options.root, shell.theme);
1869
+ const checkout = ownerDocument.createElement(shell.checkout.tagName);
1870
+ applyCheckoutElementAttributes(checkout, shell.checkout.attributes);
1871
+ applyCheckoutElementListeners(checkout, shell.checkout.listeners);
1872
+ const themeToggle = ownerDocument.createElement(shell.themeToggle.tagName);
1873
+ applyOpenReceiveThemeToggleElementAttributes(themeToggle, shell.themeToggle.attributes);
1874
+ return {
1875
+ theme: shell.theme,
1876
+ rootAttributes: shell.rootAttributes,
1877
+ checkout,
1878
+ themeToggle
1879
+ };
1880
+ }
1881
+ export async function createInvoice(options) {
1882
+ if (options.orderUuid.length === 0) {
1883
+ throw new Error("OpenReceive invoice creation requires an orderUuid.");
1884
+ }
1885
+ const fetcher = options.fetch ?? globalThis.fetch;
1886
+ if (fetcher === undefined) {
1887
+ throw new Error("OpenReceive invoice creation requires fetch.");
1888
+ }
1889
+ const amountSourceCount = [
1890
+ options.amount !== undefined,
1891
+ options.amountInSatoshis !== undefined,
1892
+ options.amount_msats !== undefined,
1893
+ options.fiat !== undefined
1894
+ ].filter(Boolean).length;
1895
+ if (amountSourceCount !== 1) {
1896
+ throw new Error("OpenReceive invoice creation requires exactly one of amount, fiat, amountInSatoshis, or amount_msats.");
1897
+ }
1898
+ if (options.optionalInvoiceDescription !== undefined &&
1899
+ options.optionalInvoiceDescription.length > 500) {
1900
+ throw new Error("OpenReceive optionalInvoiceDescription must be 500 characters or fewer.");
1901
+ }
1902
+ const requestBody = {
1903
+ order_uuid: options.orderUuid,
1904
+ ...(options.amount === undefined ? {} : { amount: options.amount }),
1905
+ ...(options.amountInSatoshis === undefined ? {} : { amount_sats: options.amountInSatoshis }),
1906
+ ...(options.amount_msats === undefined ? {} : { amount_msats: options.amount_msats }),
1907
+ ...(options.fiat === undefined ? {} : { fiat: options.fiat }),
1908
+ ...(options.optionalInvoiceDescription === undefined
1909
+ ? {}
1910
+ : { optional_invoice_description: options.optionalInvoiceDescription }),
1911
+ ...(options.description_hash === undefined ? {} : { description_hash: options.description_hash }),
1912
+ ...(options.expiry === undefined ? {} : { expiry: options.expiry })
1913
+ };
1914
+ assertOpenReceiveBrowserPayloadSafe(requestBody);
1915
+ const response = await fetcher(options.invoiceUrl ?? "/openreceive/v1/invoices", {
1916
+ method: "POST",
1917
+ headers: {
1918
+ "Content-Type": "application/json",
1919
+ ...(options.headers ?? {})
1920
+ },
1921
+ body: JSON.stringify(requestBody)
1922
+ });
1923
+ const body = await response.json();
1924
+ if (!response.ok) {
1925
+ throw new Error(typeof body?.message === "string"
1926
+ ? body.message
1927
+ : "Could not create invoice.");
1928
+ }
1929
+ if (typeof body?.invoice === "string") {
1930
+ assertOpenReceiveDisplayInvoice(body.invoice);
1931
+ }
1932
+ return body;
1933
+ }
1934
+ export function createOpenReceiveLookupInvoiceFetcher(options) {
1935
+ return async (state) => {
1936
+ if (state.payment_hash === undefined) {
1937
+ throw new Error("OpenReceive lookup requires payment_hash.");
1938
+ }
1939
+ const fetcher = options.fetch ?? globalThis.fetch;
1940
+ if (fetcher === undefined) {
1941
+ throw new Error("OpenReceive lookup requires fetch.");
1942
+ }
1943
+ const response = await fetcher(options.lookupUrl, {
1944
+ method: "POST",
1945
+ headers: {
1946
+ "Content-Type": "application/json",
1947
+ ...(options.headers ?? {})
1948
+ },
1949
+ body: JSON.stringify({
1950
+ payment_hash: state.payment_hash
1951
+ })
1952
+ });
1953
+ const body = await response.json();
1954
+ if (!response.ok) {
1955
+ throw new Error(typeof body?.message === "string"
1956
+ ? body.message
1957
+ : "Could not look up invoice.");
1958
+ }
1959
+ return body;
1960
+ };
1961
+ }
1962
+ export function createOpenReceiveRefreshInvoiceFetcher(options) {
1963
+ return async (state) => {
1964
+ const fetcher = options.fetch ?? globalThis.fetch;
1965
+ if (fetcher === undefined) {
1966
+ throw new Error("OpenReceive refresh requires fetch.");
1967
+ }
1968
+ const refreshUrl = typeof options.refreshUrl === "function"
1969
+ ? options.refreshUrl(state)
1970
+ : options.refreshUrl;
1971
+ const idempotencyKey = typeof options.idempotencyKey === "function"
1972
+ ? options.idempotencyKey(state)
1973
+ : options.idempotencyKey;
1974
+ if (idempotencyKey.length === 0) {
1975
+ throw new Error("OpenReceive refresh requires an Idempotency-Key.");
1976
+ }
1977
+ const reason = typeof options.reason === "function"
1978
+ ? options.reason(state)
1979
+ : options.reason ?? state.phase;
1980
+ const response = await fetcher(refreshUrl, {
1981
+ method: "POST",
1982
+ headers: {
1983
+ "Content-Type": "application/json",
1984
+ "Idempotency-Key": idempotencyKey,
1985
+ ...(options.headers ?? {})
1986
+ },
1987
+ body: JSON.stringify({ reason })
1988
+ });
1989
+ const body = await response.json();
1990
+ if (!response.ok) {
1991
+ throw new Error(typeof body?.message === "string"
1992
+ ? body.message
1993
+ : "Could not refresh invoice.");
1994
+ }
1995
+ if (asRecord(body).invoice === undefined) {
1996
+ throw new Error("OpenReceive refresh response did not include an invoice.");
1997
+ }
1998
+ return body;
1999
+ };
2000
+ }
2001
+ export function createCheckoutState(snapshot, options = {}) {
2002
+ const transactionState = snapshot.transaction_state ?? "pending";
2003
+ const workflowState = snapshot.workflow_state ?? "invoice_created";
2004
+ const state = normalizeCheckoutState({
2005
+ invoice_id: snapshot.invoice_id,
2006
+ invoice: snapshot.invoice,
2007
+ lightningUri: createLightningUri(snapshot.invoice),
2008
+ ...(snapshot.payment_hash === undefined
2009
+ ? {}
2010
+ : { payment_hash: snapshot.payment_hash }),
2011
+ ...(snapshot.amount_msats === undefined
2012
+ ? {}
2013
+ : { amount_msats: snapshot.amount_msats }),
2014
+ ...(snapshot.fiat_quote === undefined ? {} : { fiat_quote: snapshot.fiat_quote }),
2015
+ transaction_state: transactionState,
2016
+ workflow_state: workflowState,
2017
+ ...(snapshot.expires_at === undefined
2018
+ ? {}
2019
+ : { expires_at: snapshot.expires_at }),
2020
+ ...(snapshot.settled_at === undefined
2021
+ ? {}
2022
+ : { settled_at: snapshot.settled_at })
2023
+ }, options.now ?? currentUnixSeconds());
2024
+ emitBrowserLog(options.logger, "info", "checkout.state.created", "Created checkout state from invoice snapshot.", checkoutLogFields(state));
2025
+ return state;
2026
+ }
2027
+ export function createCheckoutSnapshotFromDisplayData(data) {
2028
+ if (data.invoice_id === undefined) {
2029
+ throw new TypeError("invoice_id is required for checkout state");
2030
+ }
2031
+ return {
2032
+ invoice_id: data.invoice_id,
2033
+ invoice: data.invoice,
2034
+ ...(data.payment_hash === undefined ? {} : { payment_hash: data.payment_hash }),
2035
+ ...(data.amount_msats === undefined ? {} : { amount_msats: data.amount_msats }),
2036
+ ...(data.fiat_quote === undefined ? {} : { fiat_quote: data.fiat_quote }),
2037
+ ...(data.transaction_state === undefined
2038
+ ? {}
2039
+ : { transaction_state: data.transaction_state }),
2040
+ ...(data.workflow_state === undefined ? {} : { workflow_state: data.workflow_state }),
2041
+ ...(data.expires_at === undefined ? {} : { expires_at: data.expires_at }),
2042
+ ...(data.settled_at === undefined ? {} : { settled_at: data.settled_at })
2043
+ };
2044
+ }
2045
+ export function createCheckoutStateFromDisplayData(data, options = {}) {
2046
+ return createCheckoutState(createCheckoutSnapshotFromDisplayData(data), options);
2047
+ }
2048
+ export function refreshCheckoutState(state, options = {}) {
2049
+ return createCheckoutState(snapshotFromCheckoutState(state), options);
2050
+ }
2051
+ export function mergeCheckoutSnapshot(current, next) {
2052
+ return {
2053
+ invoice_id: next.invoice_id ?? current.invoice_id,
2054
+ invoice: next.invoice ?? current.invoice,
2055
+ ...((next.payment_hash ?? current.payment_hash) === undefined
2056
+ ? {}
2057
+ : { payment_hash: next.payment_hash ?? current.payment_hash }),
2058
+ ...((next.amount_msats ?? current.amount_msats) === undefined
2059
+ ? {}
2060
+ : { amount_msats: next.amount_msats ?? current.amount_msats }),
2061
+ ...((next.fiat_quote ?? current.fiat_quote) === undefined
2062
+ ? {}
2063
+ : { fiat_quote: next.fiat_quote ?? current.fiat_quote }),
2064
+ transaction_state: next.transaction_state ?? current.transaction_state,
2065
+ workflow_state: next.workflow_state ?? current.workflow_state,
2066
+ ...((next.expires_at ?? current.expires_at) === undefined
2067
+ ? {}
2068
+ : { expires_at: next.expires_at ?? current.expires_at }),
2069
+ ...((next.settled_at ?? current.settled_at) === undefined
2070
+ ? {}
2071
+ : { settled_at: next.settled_at ?? current.settled_at })
2072
+ };
2073
+ }
2074
+ export function shouldCheckoutShowWaiting(state, options = {}) {
2075
+ if (state.terminal || state.settled)
2076
+ return false;
2077
+ if (state.expires_at === undefined)
2078
+ return true;
2079
+ return state.expires_at > (options.now ?? currentUnixSeconds());
2080
+ }
2081
+ export function createCheckoutStatusModel(source, options = {}) {
2082
+ const isCheckoutState = source !== undefined && "invoice_id" in source && "invoice" in source;
2083
+ const phase = source?.phase ?? "invoice_created";
2084
+ const expiresInSeconds = source?.expiresInSeconds;
2085
+ const displayPhase = phase !== "settled" &&
2086
+ phase !== "failed" &&
2087
+ phase !== "cancelled" &&
2088
+ expiresInSeconds === 0
2089
+ ? "expired"
2090
+ : phase;
2091
+ const statusText = getOpenReceivePaymentStatusText(displayPhase);
2092
+ return {
2093
+ phase: displayPhase,
2094
+ waiting: displayPhase === "expired"
2095
+ ? false
2096
+ : source === undefined
2097
+ ? false
2098
+ : isCheckoutState
2099
+ ? shouldCheckoutShowWaiting(source, options)
2100
+ : source.waiting ?? false,
2101
+ title: statusText.title,
2102
+ detail: statusText.detail,
2103
+ countdownPrefix: openReceiveCheckoutLabels.countdownPrefix,
2104
+ ...(expiresInSeconds === undefined || displayPhase === "expired"
2105
+ ? {}
2106
+ : {
2107
+ expiresInSeconds,
2108
+ countdownLabel: formatOpenReceiveCountdown(expiresInSeconds)
2109
+ })
2110
+ };
2111
+ }
2112
+ export class CheckoutWatcher {
2113
+ options;
2114
+ state;
2115
+ countdownTimer;
2116
+ pollTimer;
2117
+ running = false;
2118
+ constructor(options) {
2119
+ this.options = options;
2120
+ }
2121
+ start() {
2122
+ this.stop();
2123
+ this.running = true;
2124
+ const state = createCheckoutState(this.options.snapshot, {
2125
+ now: this.now(),
2126
+ logger: this.options.logger
2127
+ });
2128
+ this.applyState(state);
2129
+ return state;
2130
+ }
2131
+ update(options) {
2132
+ this.options = options;
2133
+ return this.start();
2134
+ }
2135
+ stop() {
2136
+ this.running = false;
2137
+ this.stopCountdown();
2138
+ this.stopPolling();
2139
+ }
2140
+ getState() {
2141
+ return this.state;
2142
+ }
2143
+ async reloadState() {
2144
+ const current = this.state ?? createCheckoutState(this.options.snapshot, {
2145
+ now: this.now(),
2146
+ logger: this.options.logger
2147
+ });
2148
+ const lookupInvoice = this.options.lookupInvoice;
2149
+ if (lookupInvoice === undefined || current.payment_hash === undefined) {
2150
+ return current;
2151
+ }
2152
+ try {
2153
+ const next = await lookupInvoice(current);
2154
+ const nextState = createCheckoutState(mergeCheckoutSnapshot(current, next), {
2155
+ now: this.now(),
2156
+ logger: this.options.logger
2157
+ });
2158
+ if (this.running) {
2159
+ this.applyState(nextState);
2160
+ }
2161
+ else {
2162
+ this.state = nextState;
2163
+ }
2164
+ return nextState;
2165
+ }
2166
+ catch (error) {
2167
+ this.options.onError?.(error);
2168
+ throw error;
2169
+ }
2170
+ }
2171
+ applyState(state) {
2172
+ if (!this.running)
2173
+ return;
2174
+ this.state = state;
2175
+ this.options.onState(state);
2176
+ this.syncWatchers();
2177
+ }
2178
+ syncWatchers() {
2179
+ const state = this.state;
2180
+ if (state === undefined || !this.running)
2181
+ return;
2182
+ if (state.terminal) {
2183
+ this.stop();
2184
+ return;
2185
+ }
2186
+ if (state.settled || state.expires_at === undefined) {
2187
+ this.stopCountdown();
2188
+ }
2189
+ else if (this.countdownTimer === undefined) {
2190
+ this.countdownTimer = this.setInterval()(() => {
2191
+ const current = this.state;
2192
+ if (current === undefined)
2193
+ return;
2194
+ this.applyState(refreshCheckoutState(current, {
2195
+ now: this.now(),
2196
+ logger: this.options.logger
2197
+ }));
2198
+ }, 1000);
2199
+ }
2200
+ if (state.settled || this.options.lookupInvoice === undefined || state.payment_hash === undefined) {
2201
+ this.stopPolling();
2202
+ }
2203
+ else if (this.pollTimer === undefined) {
2204
+ this.pollTimer = this.setInterval()(() => {
2205
+ void this.poll();
2206
+ }, this.options.pollIntervalMs ?? OPENRECEIVE_DEFAULT_POLL_INTERVAL_MS);
2207
+ }
2208
+ }
2209
+ async poll() {
2210
+ const lookupInvoice = this.options.lookupInvoice;
2211
+ const current = this.state;
2212
+ if (!this.running || lookupInvoice === undefined || current === undefined)
2213
+ return;
2214
+ if (current.terminal || current.settled) {
2215
+ this.stopPolling();
2216
+ return;
2217
+ }
2218
+ try {
2219
+ const next = await lookupInvoice(current);
2220
+ if (!this.running || this.state === undefined)
2221
+ return;
2222
+ this.applyState(createCheckoutState(mergeCheckoutSnapshot(this.state, next), {
2223
+ now: this.now(),
2224
+ logger: this.options.logger
2225
+ }));
2226
+ }
2227
+ catch (error) {
2228
+ this.options.onError?.(error);
2229
+ }
2230
+ }
2231
+ stopCountdown() {
2232
+ if (this.countdownTimer === undefined)
2233
+ return;
2234
+ this.clearInterval()(this.countdownTimer);
2235
+ this.countdownTimer = undefined;
2236
+ }
2237
+ stopPolling() {
2238
+ if (this.pollTimer === undefined)
2239
+ return;
2240
+ this.clearInterval()(this.pollTimer);
2241
+ this.pollTimer = undefined;
2242
+ }
2243
+ now() {
2244
+ return this.options.now?.() ?? currentUnixSeconds();
2245
+ }
2246
+ setInterval() {
2247
+ return this.options.setInterval ?? globalThis.setInterval;
2248
+ }
2249
+ clearInterval() {
2250
+ return this.options.clearInterval ?? globalThis.clearInterval;
2251
+ }
2252
+ }
2253
+ export class OpenReceiveBrowserCheckoutController {
2254
+ options;
2255
+ watcher;
2256
+ state;
2257
+ constructor(options) {
2258
+ this.options = options;
2259
+ this.watcher = this.createWatcher(options);
2260
+ }
2261
+ start() {
2262
+ this.state = this.watcher.start();
2263
+ return this.state;
2264
+ }
2265
+ update(options) {
2266
+ this.options = options;
2267
+ this.watcher.stop();
2268
+ this.watcher = this.createWatcher(options);
2269
+ return this.start();
2270
+ }
2271
+ stop() {
2272
+ this.watcher.stop();
2273
+ }
2274
+ getState() {
2275
+ return this.state ?? this.watcher.getState();
2276
+ }
2277
+ async copyInvoice() {
2278
+ const state = this.currentState();
2279
+ await copyInvoice({
2280
+ invoice: state.invoice,
2281
+ clipboard: this.options.clipboard,
2282
+ logger: this.options.logger,
2283
+ logContext: checkoutLogFields(state)
2284
+ });
2285
+ }
2286
+ openWallet() {
2287
+ const state = this.currentState();
2288
+ return openWallet({
2289
+ invoice: state.invoice,
2290
+ open: this.options.open,
2291
+ logger: this.options.logger,
2292
+ logContext: checkoutLogFields(state)
2293
+ });
2294
+ }
2295
+ async reloadState() {
2296
+ return this.watcher.reloadState();
2297
+ }
2298
+ async retry() {
2299
+ return this.reloadState();
2300
+ }
2301
+ async refreshExpiredInvoice() {
2302
+ const refreshInvoice = this.createRefreshInvoice(this.options);
2303
+ if (refreshInvoice === undefined) {
2304
+ throw new Error("OpenReceive refresh requires refreshInvoice or refreshUrl.");
2305
+ }
2306
+ const current = this.currentState();
2307
+ const result = await refreshInvoice(current);
2308
+ const snapshot = "new_invoice_id" in result ? result.invoice : result;
2309
+ return this.update({
2310
+ ...this.options,
2311
+ snapshot
2312
+ });
2313
+ }
2314
+ cancel() {
2315
+ this.stop();
2316
+ this.state = this.currentState();
2317
+ emitBrowserLog(this.options.logger, "info", "checkout.cancelled", "Stopped checkout watcher after cancel action.", checkoutLogFields(this.state));
2318
+ return this.state;
2319
+ }
2320
+ createWatcher(options) {
2321
+ const lookupInvoice = options.lookupInvoice ??
2322
+ (options.lookupUrl === undefined
2323
+ ? undefined
2324
+ : createOpenReceiveLookupInvoiceFetcher({
2325
+ lookupUrl: options.lookupUrl,
2326
+ fetch: options.fetch,
2327
+ headers: options.lookupHeaders
2328
+ }));
2329
+ return new CheckoutWatcher({
2330
+ ...options,
2331
+ ...(lookupInvoice === undefined ? {} : { lookupInvoice }),
2332
+ onState: (state) => {
2333
+ this.state = state;
2334
+ options.onState?.(state);
2335
+ }
2336
+ });
2337
+ }
2338
+ createRefreshInvoice(options) {
2339
+ if (options.refreshInvoice !== undefined)
2340
+ return options.refreshInvoice;
2341
+ if (options.refreshUrl === undefined || options.refreshIdempotencyKey === undefined) {
2342
+ return undefined;
2343
+ }
2344
+ return createOpenReceiveRefreshInvoiceFetcher({
2345
+ refreshUrl: options.refreshUrl,
2346
+ idempotencyKey: options.refreshIdempotencyKey,
2347
+ fetch: options.fetch,
2348
+ headers: options.refreshHeaders,
2349
+ reason: options.refreshReason
2350
+ });
2351
+ }
2352
+ currentState() {
2353
+ return this.getState() ?? createCheckoutState(this.options.snapshot, {
2354
+ now: this.options.now?.(),
2355
+ logger: this.options.logger
2356
+ });
2357
+ }
2358
+ }
2359
+ export function createCheckoutController(options) {
2360
+ return new OpenReceiveBrowserCheckoutController(options);
2361
+ }
2362
+ export async function createQrSvg(invoice, options = {}) {
2363
+ const encoder = await getQrEncoder(options.encoder);
2364
+ const svg = await encoder.toString(createLightningUri(invoice), {
2365
+ type: "svg",
2366
+ errorCorrectionLevel: OPENRECEIVE_QR_ERROR_CORRECTION,
2367
+ margin: OPENRECEIVE_QR_QUIET_ZONE_MODULES,
2368
+ width: options.width,
2369
+ color: {
2370
+ dark: OPENRECEIVE_QR_DARK_COLOR,
2371
+ light: OPENRECEIVE_QR_LIGHT_COLOR
2372
+ }
2373
+ });
2374
+ return String(svg);
2375
+ }
2376
+ export async function createQrPngDataUrl(invoice, options = {}) {
2377
+ const encoder = await getQrEncoder(options.encoder);
2378
+ if (encoder.toDataURL === undefined) {
2379
+ throw new Error("QR encoder does not support PNG data URL output.");
2380
+ }
2381
+ const png = await encoder.toDataURL(createLightningUri(invoice), {
2382
+ type: "image/png",
2383
+ errorCorrectionLevel: OPENRECEIVE_QR_ERROR_CORRECTION,
2384
+ margin: OPENRECEIVE_QR_QUIET_ZONE_MODULES,
2385
+ width: options.width,
2386
+ color: {
2387
+ dark: OPENRECEIVE_QR_DARK_COLOR,
2388
+ light: OPENRECEIVE_QR_LIGHT_COLOR
2389
+ }
2390
+ });
2391
+ return String(png);
2392
+ }
2393
+ // Spec-named alias for the canonical QR helper trio
2394
+ // (createQrSvg / createQrPng / createLightningUri). createQrPng returns a
2395
+ // PNG data URL using the same safe quiet-zone, contrast, and payload defaults.
2396
+ export const createQrPng = createQrPngDataUrl;
2397
+ export async function copyInvoice(options) {
2398
+ assertInvoice(options.invoice);
2399
+ const clipboard = options.clipboard ?? globalThis.navigator?.clipboard;
2400
+ if (clipboard === undefined) {
2401
+ throw new Error("Clipboard API is unavailable.");
2402
+ }
2403
+ await clipboard.writeText(options.invoice);
2404
+ emitBrowserLog(options.logger, "info", "checkout.invoice.copied", "Copied Lightning invoice to clipboard.", options.logContext);
2405
+ }
2406
+ export function openWallet(options) {
2407
+ const uri = createLightningUri(options.invoice);
2408
+ if (options.open !== undefined) {
2409
+ options.open(uri);
2410
+ emitBrowserLog(options.logger, "info", "checkout.wallet.opened", "Opened Lightning invoice URI.", options.logContext);
2411
+ return uri;
2412
+ }
2413
+ const location = globalThis.window?.location;
2414
+ if (location === undefined) {
2415
+ throw new Error("window.location is unavailable.");
2416
+ }
2417
+ location.assign(uri);
2418
+ emitBrowserLog(options.logger, "info", "checkout.wallet.opened", "Opened Lightning invoice URI.", options.logContext);
2419
+ return uri;
2420
+ }
2421
+ async function getQrEncoder(encoder) {
2422
+ if (encoder !== undefined)
2423
+ return encoder;
2424
+ const dynamicImport = new Function("specifier", "return import(specifier)");
2425
+ const imported = asRecord(await dynamicImport("qrcode"));
2426
+ const candidate = (imported.default ?? imported);
2427
+ if (isQrEncoder(candidate))
2428
+ return candidate;
2429
+ throw new Error("qrcode package did not expose a compatible encoder.");
2430
+ }
2431
+ function isQrEncoder(value) {
2432
+ const record = asRecord(value);
2433
+ return typeof record.toString === "function";
2434
+ }
2435
+ function assertInvoice(invoice) {
2436
+ if (typeof invoice !== "string" || invoice.length === 0) {
2437
+ throw new TypeError("invoice must be a non-empty BOLT11 string");
2438
+ }
2439
+ if (invoice.startsWith("nostr+walletconnect://")) {
2440
+ throw new TypeError("invoice must not be an NWC connection string");
2441
+ }
2442
+ }
2443
+ function assertOpenReceiveBrowserPayloadSafe(value) {
2444
+ if (typeof value === "string") {
2445
+ if (value.startsWith("nostr+walletconnect://")) {
2446
+ throw new TypeError("OpenReceive browser payload must not include an NWC connection string");
2447
+ }
2448
+ return;
2449
+ }
2450
+ if (Array.isArray(value)) {
2451
+ for (const item of value)
2452
+ assertOpenReceiveBrowserPayloadSafe(item);
2453
+ return;
2454
+ }
2455
+ if (value !== null && typeof value === "object") {
2456
+ for (const item of Object.values(value)) {
2457
+ assertOpenReceiveBrowserPayloadSafe(item);
2458
+ }
2459
+ }
2460
+ }
2461
+ function normalizeCheckoutState(state, now) {
2462
+ const { phase: _phase, settled: _settled, terminal: _terminal, expiresInSeconds: _expiresInSeconds, ...base } = state;
2463
+ const phase = getCheckoutPhase(state.transaction_state, state.workflow_state);
2464
+ return {
2465
+ ...base,
2466
+ phase,
2467
+ settled: base.transaction_state === "settled",
2468
+ terminal: isTerminalPhase(phase),
2469
+ ...(base.expires_at === undefined || now === undefined
2470
+ ? {}
2471
+ : { expiresInSeconds: Math.max(0, base.expires_at - now) })
2472
+ };
2473
+ }
2474
+ function getCheckoutPhase(transactionState, workflowState) {
2475
+ if (workflowState === "cancelled")
2476
+ return "cancelled";
2477
+ if (transactionState === "settled")
2478
+ return "settled";
2479
+ if (transactionState === "expired" || workflowState === "expired_closed") {
2480
+ return "expired";
2481
+ }
2482
+ if (transactionState === "failed" || workflowState === "failed_closed") {
2483
+ return "failed";
2484
+ }
2485
+ if (workflowState === "verifying" ||
2486
+ workflowState === "expiry_pending_verification") {
2487
+ return "verifying";
2488
+ }
2489
+ return "invoice_created";
2490
+ }
2491
+ function isTerminalPhase(phase) {
2492
+ return (phase === "expired" ||
2493
+ phase === "failed" ||
2494
+ phase === "cancelled");
2495
+ }
2496
+ function snapshotFromCheckoutState(state) {
2497
+ return {
2498
+ invoice_id: state.invoice_id,
2499
+ invoice: state.invoice,
2500
+ ...(state.payment_hash === undefined ? {} : { payment_hash: state.payment_hash }),
2501
+ ...(state.amount_msats === undefined ? {} : { amount_msats: state.amount_msats }),
2502
+ ...(state.fiat_quote === undefined ? {} : { fiat_quote: state.fiat_quote }),
2503
+ transaction_state: state.transaction_state,
2504
+ workflow_state: state.workflow_state,
2505
+ ...(state.expires_at === undefined ? {} : { expires_at: state.expires_at }),
2506
+ ...(state.settled_at === undefined ? {} : { settled_at: state.settled_at })
2507
+ };
2508
+ }
2509
+ function currentUnixSeconds() {
2510
+ return Math.floor(Date.now() / 1000);
2511
+ }
2512
+ function getRailForPaymentMethod(method) {
2513
+ if (method === "bank")
2514
+ return "bank";
2515
+ if (method === "card")
2516
+ return "card";
2517
+ return null;
2518
+ }
2519
+ function isKnownCountryCode(countryCode) {
2520
+ return getCountries().some((country) => country.code === countryCode);
2521
+ }
2522
+ function readStorageValue(storage, key) {
2523
+ try {
2524
+ return storage?.getItem(key) ?? null;
2525
+ }
2526
+ catch {
2527
+ return null;
2528
+ }
2529
+ }
2530
+ function writeStorageValue(storage, key, value) {
2531
+ try {
2532
+ storage?.setItem(key, value);
2533
+ }
2534
+ catch {
2535
+ // Browser storage is convenience only; checkout must keep working without it.
2536
+ }
2537
+ }
2538
+ function getBrowserStorage() {
2539
+ try {
2540
+ return globalThis.localStorage;
2541
+ }
2542
+ catch {
2543
+ return undefined;
2544
+ }
2545
+ }
2546
+ function checkoutLogFields(state) {
2547
+ return {
2548
+ ...(state.invoice_id === undefined ? {} : { invoice_id: state.invoice_id }),
2549
+ ...(state.payment_hash === undefined ? {} : { payment_hash: state.payment_hash }),
2550
+ ...(state.amount_msats === undefined ? {} : { amount_msats: state.amount_msats }),
2551
+ ...(state.transaction_state === undefined
2552
+ ? {}
2553
+ : { transaction_state: state.transaction_state }),
2554
+ ...(state.workflow_state === undefined
2555
+ ? {}
2556
+ : { workflow_state: state.workflow_state }),
2557
+ ...(state.phase === undefined ? {} : { phase: state.phase }),
2558
+ ...(state.expiresInSeconds === undefined
2559
+ ? {}
2560
+ : { expires_in_seconds: state.expiresInSeconds })
2561
+ };
2562
+ }
2563
+ function emitBrowserLog(logger, level, event, message, fields = {}) {
2564
+ if (logger === undefined)
2565
+ return;
2566
+ try {
2567
+ logger(sanitizeBrowserLogEntry({
2568
+ level,
2569
+ event,
2570
+ message,
2571
+ ...fields
2572
+ }));
2573
+ }
2574
+ catch {
2575
+ // Checkout logs are diagnostic only and must not affect user actions.
2576
+ }
2577
+ }
2578
+ function sanitizeBrowserLogEntry(entry) {
2579
+ const clean = {};
2580
+ for (const [key, value] of Object.entries(entry)) {
2581
+ if (/secret|token|authorization|cookie|nwc/i.test(key)) {
2582
+ clean[key] = "[REDACTED]";
2583
+ }
2584
+ else {
2585
+ clean[key] = sanitizeBrowserLogValue(value);
2586
+ }
2587
+ }
2588
+ return clean;
2589
+ }
2590
+ function sanitizeBrowserLogValue(value) {
2591
+ if (typeof value === "string")
2592
+ return redactBrowserSecrets(value);
2593
+ if (Array.isArray(value))
2594
+ return value.map(sanitizeBrowserLogValue);
2595
+ if (typeof value !== "object" || value === null)
2596
+ return value;
2597
+ const clean = {};
2598
+ for (const [key, nested] of Object.entries(value)) {
2599
+ if (/secret|token|authorization|cookie|nwc/i.test(key)) {
2600
+ clean[key] = "[REDACTED]";
2601
+ }
2602
+ else {
2603
+ clean[key] = sanitizeBrowserLogValue(nested);
2604
+ }
2605
+ }
2606
+ return clean;
2607
+ }
2608
+ function redactBrowserSecrets(value) {
2609
+ return value
2610
+ .replace(/nostr\+walletconnect:\/\/[^\s"'`<>]+/g, "[REDACTED_NWC]")
2611
+ .replace(/([?&](?:_or_evt|token|secret)=)[^&\s"'`<>]+/gi, "$1[REDACTED]");
2612
+ }
2613
+ function asRecord(value) {
2614
+ if (typeof value !== "object" || value === null)
2615
+ return {};
2616
+ return value;
2617
+ }