@openreceive/browser 0.1.1 → 0.2.2

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