@rhinestone/deposit-modal 0.0.0-dev-20260921145008 → 0.0.0-dev-20260922130636

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 (62) hide show
  1. package/README.md +58 -3
  2. package/dist/BankOnrampStep-TGCFBOK6.mjs +581 -0
  3. package/dist/BankOnrampStep-YRLTVIUO.cjs +581 -0
  4. package/dist/{DepositModalReown-SPDVO4JA.mjs → DepositModalReown-EUTQNZJH.mjs} +4 -3
  5. package/dist/{DepositModalReown-FZ2EKFGG.cjs → DepositModalReown-U722QHH6.cjs} +17 -16
  6. package/dist/{WithdrawModal-GP5YD7GH.mjs → WithdrawModal-AKHOLJWO.mjs} +4 -3
  7. package/dist/WithdrawModal-FPNYN6PW.cjs +12 -0
  8. package/dist/{chunk-Y5ZEE4LP.mjs → chunk-AGVLO2I4.mjs} +101 -46
  9. package/dist/{chunk-MSMSLMVK.mjs → chunk-DFYHOBOV.mjs} +1 -1
  10. package/dist/{chunk-67EYNPTZ.cjs → chunk-EJVUZ3TW.cjs} +2 -2
  11. package/dist/chunk-EY2YNGFX.mjs +19 -0
  12. package/dist/chunk-G264OM23.cjs +224 -0
  13. package/dist/chunk-NU2EY65R.cjs +19 -0
  14. package/dist/{chunk-LE6CPB5Z.mjs → chunk-PXL3MSJT.mjs} +10 -8
  15. package/dist/{chunk-4T4LX4Z6.cjs → chunk-Q3YAJLOP.cjs} +96 -295
  16. package/dist/{chunk-45ADKWY3.cjs → chunk-SH5DPTYM.cjs} +396 -341
  17. package/dist/{chunk-UNO5CQL2.cjs → chunk-TCLVBWA7.cjs} +74 -72
  18. package/dist/{chunk-6WCIP52T.mjs → chunk-TQKXJDDO.mjs} +1 -1
  19. package/dist/chunk-WRDH7PR7.cjs +38 -0
  20. package/dist/{chunk-X4EH2ZV6.mjs → chunk-X3IBAIQB.mjs} +444 -643
  21. package/dist/chunk-XCLR5YGQ.mjs +38 -0
  22. package/dist/{chunk-G6IR4SMZ.cjs → chunk-YNERADQK.cjs} +1 -1
  23. package/dist/chunk-YY4ZOAH5.mjs +224 -0
  24. package/dist/compliance.cjs +4 -7
  25. package/dist/compliance.d.cts +24 -1
  26. package/dist/compliance.d.ts +24 -1
  27. package/dist/compliance.mjs +6 -9
  28. package/dist/constants.cjs +2 -2
  29. package/dist/constants.d.cts +1 -1
  30. package/dist/constants.d.ts +1 -1
  31. package/dist/constants.mjs +1 -1
  32. package/dist/deposit.cjs +5 -4
  33. package/dist/deposit.d.cts +4 -2
  34. package/dist/deposit.d.ts +4 -2
  35. package/dist/deposit.mjs +4 -3
  36. package/dist/embed.cjs +15 -14
  37. package/dist/embed.d.cts +3 -1
  38. package/dist/embed.d.ts +3 -1
  39. package/dist/embed.mjs +5 -4
  40. package/dist/index.cjs +6 -5
  41. package/dist/index.d.cts +3 -1
  42. package/dist/index.d.ts +3 -1
  43. package/dist/index.mjs +5 -4
  44. package/dist/offramp.cjs +4 -4
  45. package/dist/offramp.d.cts +2 -2
  46. package/dist/offramp.d.ts +2 -2
  47. package/dist/offramp.mjs +2 -2
  48. package/dist/onramp.cjs +5 -33
  49. package/dist/onramp.d.cts +3 -2
  50. package/dist/onramp.d.ts +3 -2
  51. package/dist/onramp.mjs +6 -34
  52. package/dist/{ramp-types-C6aUz4SZ.d.cts → ramp-types-CGnr3nRp.d.cts} +1 -1
  53. package/dist/{ramp-types-C6aUz4SZ.d.ts → ramp-types-CGnr3nRp.d.ts} +1 -1
  54. package/dist/styles.css +105 -0
  55. package/dist/{types-BShYNpvO.d.cts → types-D3EoU6Ux.d.cts} +22 -1
  56. package/dist/{types-BOxa1DE-.d.ts → types-DBbMympO.d.ts} +22 -1
  57. package/dist/withdraw.cjs +5 -4
  58. package/dist/withdraw.d.cts +4 -2
  59. package/dist/withdraw.d.ts +4 -2
  60. package/dist/withdraw.mjs +4 -3
  61. package/package.json +4 -4
  62. package/dist/WithdrawModal-3W7IS2MB.cjs +0 -11
package/README.md CHANGED
@@ -308,6 +308,61 @@ frame-src https://widget.swapped.com https://sandbox.swapped.com https://connect
308
308
  | `enableQrTransfer` | `boolean` | `true` | Show the Transfer Crypto / QR row. Set false for fiat-only. |
309
309
  | `fiatMethods` | `FiatMethodsConfig` | regional | Optional exact subset of standard `payment_group`s. Providing it opts out of regional personalization; empty means none. |
310
310
 
311
+ ### Bank transfer accounts (Noah)
312
+
313
+ > **Preview.** One rough edge remains, and it is easy to misread as a bug in
314
+ > your integration: when hosted onboarding finishes, the provider redirects the
315
+ > frame to a confirmation page that does not exist yet, so the customer briefly
316
+ > sees a 404 inside the modal. Nothing is lost — the modal reads the outcome
317
+ > from the compliance status, not from the frame, so verification still
318
+ > completes and the flow continues.
319
+ >
320
+ > Your proxy must forward `POST /compliance/verification` on the customer
321
+ > bearer; deploy it before shipping a modal that calls it, or first-time
322
+ > verification 404s at the proxy.
323
+ >
324
+ > The prop is opt-in and inert unless you pass it. Treat it as unstable until
325
+ > this note is removed.
326
+
327
+ Pass `bankOnramp` to replace only the Bank transfer row with reusable USD/EUR
328
+ account details. Card and Apple Pay continue through Swapped. A customer who has
329
+ not been verified is taken through hosted verification first; an existing active
330
+ account is resumed; otherwise the selected currency is configured once.
331
+
332
+ Payment status is shown under the bank details and polls while the account is on
333
+ screen, so a customer who transferred earlier sees where their money is on
334
+ reopening. `delivered` means the crypto has been confirmed on chain — not merely
335
+ that the provider reported sending it.
336
+
337
+ ```tsx
338
+ <DepositModal
339
+ bankOnramp={{
340
+ country: "US",
341
+ currency: "USD", // Optional; omit to show every available currency.
342
+ getSession: (smartAccount) =>
343
+ authenticatedSessionClient.getCustomerSession({ smartAccount }),
344
+ }}
345
+ // ...
346
+ />
347
+ ```
348
+
349
+ `getSession` must call your authenticated server endpoint, which mints a
350
+ short-lived customer session with `createCustomerSessionHandler`. It is called
351
+ for every request; do not put the Rhinestone API key in the browser. Hosted bank
352
+ details are minted fresh for every view and retry, and the modal never receives
353
+ or stores the underlying bank coordinates.
354
+
355
+ `bankOnramp` enables Cash when `fiatMethods` is omitted. When an exact
356
+ `fiatMethods` subset is provided, it must include
357
+ `{ "bank-transfer": true }`. The proxy must forward the `/onramp/noah/*`
358
+ customer routes with the bearer token and without attaching its API key.
359
+
360
+ Hosts with a strict CSP must allow Noah Checkout:
361
+
362
+ ```
363
+ frame-src https://checkout.noah.com https://checkout.sandbox.noah.com;
364
+ ```
365
+
311
366
  ## Fund from Exchange (Swapped Connect)
312
367
 
313
368
  Pass `enableExchangeConnect` to expose a "Fund from Exchange" row alongside the existing options. Clicking it opens an exchange picker backed by `/onramp/swapped/connect-exchanges`; after the user picks an exchange, the modal embeds Swapped's Connect iframe for that selected `connection`, signs in, and Swapped pulls the crypto on-chain to the smart account.
@@ -453,6 +508,6 @@ No crypto-input quoting is exposed. Accounts return safe references/config/statu
453
508
  not bank instructions. Hosted interaction completion never means payment
454
509
  settlement; funding, payout and late returns have independent status fields.
455
510
 
456
- The provider adapter and journey execution are not connected by this release.
457
- Expect 503 until those implementations are deployed. Render hosted URLs only
458
- after the journey's iframe presentation has been verified; do not log/store URLs.
511
+ `listPayments`, `getPayment` and the whole offramp client answer 503 until their
512
+ processor implementations are deployed, so payment lifecycle is not readable yet.
513
+ Do not log or store hosted URLs.
@@ -0,0 +1,581 @@
1
+ import {
2
+ BankIcon,
3
+ BodyHeader,
4
+ Spinner,
5
+ useLatestRef
6
+ } from "./chunk-YY4ZOAH5.mjs";
7
+ import {
8
+ createOnrampService
9
+ } from "./chunk-XCLR5YGQ.mjs";
10
+ import "./chunk-PP4QE32S.mjs";
11
+ import {
12
+ getComplianceStatus,
13
+ startComplianceVerification
14
+ } from "./chunk-EY2YNGFX.mjs";
15
+ import "./chunk-TQKXJDDO.mjs";
16
+ import "./chunk-DFYHOBOV.mjs";
17
+
18
+ // src/components/steps/BankOnrampStep.tsx
19
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
20
+
21
+ // src/core/bank-onramp-progress.ts
22
+ var HELD = "on_hold";
23
+ var FAILED = "failed";
24
+ var DONE = "completed";
25
+ function started(status) {
26
+ return status !== "not_started";
27
+ }
28
+ function toPaymentProgress(payment) {
29
+ if (payment.bankReceipt === HELD || payment.conversion === HELD || payment.cryptoDelivery === HELD) {
30
+ return "on_hold";
31
+ }
32
+ if (payment.refund !== "none" || payment.adjustment !== "none") {
33
+ return "returned";
34
+ }
35
+ if (payment.bankReceipt === FAILED || payment.conversion === FAILED || payment.cryptoDelivery === FAILED) {
36
+ return "failed";
37
+ }
38
+ if (payment.cryptoDelivery === DONE) return "delivered";
39
+ if (started(payment.cryptoDelivery)) return "sending";
40
+ if (payment.conversion === DONE) return "sending";
41
+ if (started(payment.conversion)) return "converting";
42
+ if (payment.bankReceipt === DONE) return "received";
43
+ return "awaiting_transfer";
44
+ }
45
+ var TERMINAL = /* @__PURE__ */ new Set([
46
+ "delivered",
47
+ "returned",
48
+ "failed"
49
+ ]);
50
+ function toPaymentView(payment) {
51
+ const progress = toPaymentProgress(payment);
52
+ return {
53
+ id: payment.id,
54
+ amount: payment.amount,
55
+ currency: payment.currency,
56
+ progress,
57
+ terminal: TERMINAL.has(progress)
58
+ };
59
+ }
60
+ function selectActivePayment(payments, currency) {
61
+ const scoped = currency ? payments.filter((p) => p.currency === currency) : payments;
62
+ if (scoped.length === 0) return null;
63
+ const views = scoped.map(toPaymentView);
64
+ const byRecency = [...views].sort((a, b) => {
65
+ const at = scoped.find((p) => p.id === a.id)?.updatedAt ?? "";
66
+ const bt = scoped.find((p) => p.id === b.id)?.updatedAt ?? "";
67
+ return bt.localeCompare(at);
68
+ });
69
+ return byRecency.find((view) => !view.terminal) ?? byRecency[0] ?? null;
70
+ }
71
+ function shouldKeepPolling(view) {
72
+ return view === null || !view.terminal;
73
+ }
74
+ var BANK_ONRAMP_COPY = {
75
+ awaiting_transfer: {
76
+ title: "Waiting for your transfer",
77
+ subtitle: "Send the transfer from your bank using the details above."
78
+ },
79
+ received: {
80
+ title: "Transfer received",
81
+ subtitle: "Your bank transfer has arrived and is being processed."
82
+ },
83
+ converting: {
84
+ title: "Converting",
85
+ subtitle: "Your money is being converted to crypto."
86
+ },
87
+ sending: {
88
+ title: "Sending to your account",
89
+ subtitle: "The crypto is on its way. This usually takes a few minutes."
90
+ },
91
+ delivered: {
92
+ title: "Funds delivered",
93
+ subtitle: "The crypto has arrived in your account."
94
+ },
95
+ on_hold: {
96
+ title: "Under review",
97
+ subtitle: "Your provider is reviewing this transfer and may contact you for more information."
98
+ },
99
+ returned: {
100
+ title: "Transfer returned",
101
+ subtitle: "This transfer was returned. Contact support if unexpected."
102
+ },
103
+ failed: {
104
+ title: "Transfer failed",
105
+ subtitle: "This transfer could not be completed. Contact support."
106
+ }
107
+ };
108
+
109
+ // src/core/noah-hosted.ts
110
+ var NOAH_HOSTED_ORIGINS = [
111
+ "https://checkout.noah.com",
112
+ "https://checkout.sandbox.noah.com"
113
+ ];
114
+ function isTrustedNoahHostedUrl(url) {
115
+ let parsed;
116
+ try {
117
+ parsed = new URL(url);
118
+ } catch {
119
+ return false;
120
+ }
121
+ return NOAH_HOSTED_ORIGINS.includes(parsed.origin);
122
+ }
123
+
124
+ // src/components/steps/BankOnrampStep.tsx
125
+ import { jsx, jsxs } from "react/jsx-runtime";
126
+ var SAFE_ERROR = "Bank details are unavailable. Try again.";
127
+ var OPTIONS_ERROR = "Bank transfer options are unavailable. Try again.";
128
+ var PAYMENT_POLL_MS = 15e3;
129
+ var VERIFICATION_POLL_MS = 5e3;
130
+ function BankOnrampStep({
131
+ backendUrl,
132
+ smartAccount,
133
+ bankOnramp,
134
+ onGoBack,
135
+ onError,
136
+ openExternally
137
+ }) {
138
+ const getSessionRef = useLatestRef(bankOnramp.getSession);
139
+ const sessionOptions = useMemo(
140
+ () => ({
141
+ backendUrl,
142
+ getSession: () => getSessionRef.current(smartAccount)
143
+ }),
144
+ [backendUrl, getSessionRef, smartAccount]
145
+ );
146
+ const service = useMemo(
147
+ () => createOnrampService(sessionOptions),
148
+ [sessionOptions]
149
+ );
150
+ const [options, setOptions] = useState([]);
151
+ const [accounts, setAccounts] = useState([]);
152
+ const [loading, setLoading] = useState(true);
153
+ const [selectedAccount, setSelectedAccount] = useState(null);
154
+ const [hostedUrl, setHostedUrl] = useState(null);
155
+ const [iframeLoaded, setIframeLoaded] = useState(false);
156
+ const [externalOpened, setExternalOpened] = useState(false);
157
+ const [error, setError] = useState(null);
158
+ const [payment, setPayment] = useState(null);
159
+ const [verifying, setVerifying] = useState(null);
160
+ const requestGenerationRef = useRef(0);
161
+ const isCurrentRequest = useCallback(
162
+ (generation) => requestGenerationRef.current === generation,
163
+ []
164
+ );
165
+ const ensureVerified = useCallback(async () => {
166
+ const current = await getComplianceStatus(sessionOptions);
167
+ if (current.status === "approved") return "approved";
168
+ if (current.status === "declined") return "declined";
169
+ const session = await startComplianceVerification(sessionOptions, {
170
+ // Verification is per fiat currency. Without an explicit choice the US
171
+ // dollar journey is the one every configured country can complete.
172
+ currency: bankOnramp.currency ?? "USD"
173
+ });
174
+ if (session.status === "failed") return "declined";
175
+ if (!session.nextAction) return "reviewing";
176
+ if (!isTrustedNoahHostedUrl(session.nextAction.url)) return "reviewing";
177
+ if (openExternally) {
178
+ await openExternally(session.nextAction.url);
179
+ setExternalOpened(true);
180
+ return "action_required";
181
+ }
182
+ setHostedUrl(session.nextAction.url);
183
+ setIframeLoaded(false);
184
+ return "action_required";
185
+ }, [bankOnramp.currency, openExternally, sessionOptions]);
186
+ const loadAccounts = useCallback(async () => {
187
+ const generation = ++requestGenerationRef.current;
188
+ setLoading(true);
189
+ setError(null);
190
+ try {
191
+ const verification = await ensureVerified();
192
+ if (!isCurrentRequest(generation)) return;
193
+ if (verification !== "approved") {
194
+ setVerifying(verification);
195
+ return;
196
+ }
197
+ setVerifying(null);
198
+ setExternalOpened(false);
199
+ const intent = {
200
+ country: bankOnramp.country,
201
+ paymentMethod: "bank_transfer",
202
+ ...bankOnramp.currency ? { currency: bankOnramp.currency } : {}
203
+ };
204
+ const [available, existing] = await Promise.all([
205
+ service.getOptions(intent),
206
+ service.listAccounts()
207
+ ]);
208
+ if (!isCurrentRequest(generation)) return;
209
+ setOptions(available.items);
210
+ setAccounts(existing.items);
211
+ } catch {
212
+ if (!isCurrentRequest(generation)) return;
213
+ setError(OPTIONS_ERROR);
214
+ onError?.(OPTIONS_ERROR, "BANK_ONRAMP_OPTIONS_FAILED");
215
+ } finally {
216
+ if (isCurrentRequest(generation)) setLoading(false);
217
+ }
218
+ }, [
219
+ bankOnramp.country,
220
+ bankOnramp.currency,
221
+ ensureVerified,
222
+ isCurrentRequest,
223
+ onError,
224
+ service
225
+ ]);
226
+ useEffect(() => {
227
+ setSelectedAccount(null);
228
+ setHostedUrl(null);
229
+ setExternalOpened(false);
230
+ void loadAccounts();
231
+ return () => {
232
+ requestGenerationRef.current += 1;
233
+ };
234
+ }, [loadAccounts]);
235
+ const openDetails = useCallback(
236
+ async (account) => {
237
+ const generation = ++requestGenerationRef.current;
238
+ setLoading(true);
239
+ setError(null);
240
+ setHostedUrl(null);
241
+ setIframeLoaded(false);
242
+ setExternalOpened(false);
243
+ try {
244
+ const details = await service.getAccountDetails(account.id);
245
+ if (!isCurrentRequest(generation)) return;
246
+ const url = details.nextAction.url;
247
+ if (!isTrustedNoahHostedUrl(url)) {
248
+ if (!isCurrentRequest(generation)) return;
249
+ setError(SAFE_ERROR);
250
+ onError?.(SAFE_ERROR, "BANK_ONRAMP_DETAILS_FAILED");
251
+ return;
252
+ }
253
+ if (openExternally) {
254
+ try {
255
+ await openExternally(url);
256
+ } catch {
257
+ if (!isCurrentRequest(generation)) return;
258
+ setError(SAFE_ERROR);
259
+ onError?.(SAFE_ERROR, "BANK_ONRAMP_EXTERNAL_OPEN_FAILED");
260
+ return;
261
+ }
262
+ if (!isCurrentRequest(generation)) return;
263
+ setExternalOpened(true);
264
+ } else {
265
+ setHostedUrl(url);
266
+ }
267
+ } catch {
268
+ if (!isCurrentRequest(generation)) return;
269
+ setError(SAFE_ERROR);
270
+ onError?.(SAFE_ERROR, "BANK_ONRAMP_DETAILS_FAILED");
271
+ } finally {
272
+ if (isCurrentRequest(generation)) setLoading(false);
273
+ }
274
+ },
275
+ [isCurrentRequest, onError, openExternally, service]
276
+ );
277
+ const selectOption = useCallback(
278
+ async (option, account) => {
279
+ if (account) {
280
+ setSelectedAccount(account);
281
+ await openDetails(account);
282
+ return;
283
+ }
284
+ const generation = ++requestGenerationRef.current;
285
+ setLoading(true);
286
+ setError(null);
287
+ try {
288
+ const request = {
289
+ purpose: "account_setup",
290
+ country: bankOnramp.country,
291
+ currency: option.currency,
292
+ paymentMethod: option.paymentMethod,
293
+ optionId: option.optionId
294
+ };
295
+ const nextAccount = (await service.createSetup(request)).account;
296
+ if (!isCurrentRequest(generation)) return;
297
+ setSelectedAccount(nextAccount);
298
+ await openDetails(nextAccount);
299
+ } catch {
300
+ if (!isCurrentRequest(generation)) return;
301
+ setError(SAFE_ERROR);
302
+ onError?.(SAFE_ERROR, "BANK_ONRAMP_SETUP_FAILED");
303
+ setLoading(false);
304
+ }
305
+ },
306
+ [bankOnramp.country, isCurrentRequest, onError, openDetails, service]
307
+ );
308
+ const backToCurrencies = useCallback(() => {
309
+ requestGenerationRef.current += 1;
310
+ setSelectedAccount(null);
311
+ setHostedUrl(null);
312
+ setExternalOpened(false);
313
+ setError(null);
314
+ }, []);
315
+ useEffect(() => {
316
+ if (verifying !== "action_required" && verifying !== "reviewing") return;
317
+ let active = true;
318
+ const timer = setInterval(() => {
319
+ void (async () => {
320
+ try {
321
+ const status = await getComplianceStatus(sessionOptions);
322
+ if (!active) return;
323
+ if (status.status === "approved") {
324
+ setHostedUrl(null);
325
+ void loadAccounts();
326
+ } else if (status.status === "declined") {
327
+ setVerifying("declined");
328
+ }
329
+ } catch {
330
+ }
331
+ })();
332
+ }, VERIFICATION_POLL_MS);
333
+ return () => {
334
+ active = false;
335
+ clearInterval(timer);
336
+ };
337
+ }, [verifying, sessionOptions, loadAccounts]);
338
+ useEffect(() => {
339
+ if (!selectedAccount) {
340
+ setPayment(null);
341
+ return;
342
+ }
343
+ let active = true;
344
+ let timer;
345
+ const load = async () => {
346
+ try {
347
+ const page = await service.listPayments({ limit: 10 });
348
+ if (!active) return;
349
+ const view = selectActivePayment(
350
+ page.items ?? [],
351
+ selectedAccount.currency
352
+ );
353
+ setPayment(view);
354
+ if (!shouldKeepPolling(view) && timer) clearInterval(timer);
355
+ } catch {
356
+ }
357
+ };
358
+ void load();
359
+ timer = setInterval(() => void load(), PAYMENT_POLL_MS);
360
+ return () => {
361
+ active = false;
362
+ if (timer) clearInterval(timer);
363
+ };
364
+ }, [selectedAccount, service]);
365
+ const leaveStep = useCallback(() => {
366
+ requestGenerationRef.current += 1;
367
+ onGoBack();
368
+ }, [onGoBack]);
369
+ const retry = selectedAccount ? () => void openDetails(selectedAccount) : () => void loadAccounts();
370
+ const paymentStatus = payment ? /* @__PURE__ */ jsxs(
371
+ "div",
372
+ {
373
+ className: `rs-bank-onramp-status rs-bank-onramp-status--${payment.progress}`,
374
+ "aria-live": "polite",
375
+ "data-testid": "bank-onramp-payment-status",
376
+ children: [
377
+ /* @__PURE__ */ jsx("div", { className: "rs-bank-onramp-status-title", children: BANK_ONRAMP_COPY[payment.progress].title }),
378
+ /* @__PURE__ */ jsx("div", { className: "rs-bank-onramp-status-subtitle", children: BANK_ONRAMP_COPY[payment.progress].subtitle })
379
+ ]
380
+ }
381
+ ) : null;
382
+ return /* @__PURE__ */ jsx("div", { className: "rs-screen rs-bank-onramp", children: /* @__PURE__ */ jsxs("div", { className: "rs-screen-body rs-screen-body--gap-32", children: [
383
+ /* @__PURE__ */ jsx(
384
+ BodyHeader,
385
+ {
386
+ icon: /* @__PURE__ */ jsx(BankIcon, {}),
387
+ title: "Bank transfer",
388
+ subtitle: "Choose an account to view transfer details"
389
+ }
390
+ ),
391
+ loading && /* @__PURE__ */ jsxs("div", { className: "rs-loading-state", "aria-live": "polite", children: [
392
+ /* @__PURE__ */ jsx(Spinner, {}),
393
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-title", children: "Preparing bank details\u2026" })
394
+ ] }),
395
+ !loading && error && /* @__PURE__ */ jsxs("div", { className: "rs-loading-state", "aria-live": "polite", children: [
396
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-title", children: error }),
397
+ /* @__PURE__ */ jsx(
398
+ "button",
399
+ {
400
+ type: "button",
401
+ className: "rs-button rs-button--default",
402
+ onClick: retry,
403
+ children: "Retry"
404
+ }
405
+ ),
406
+ /* @__PURE__ */ jsx(
407
+ "button",
408
+ {
409
+ type: "button",
410
+ className: "rs-button rs-button--default",
411
+ onClick: selectedAccount ? backToCurrencies : leaveStep,
412
+ children: "Back"
413
+ }
414
+ )
415
+ ] }),
416
+ !loading && !error && verifying === "declined" && /* @__PURE__ */ jsxs("div", { className: "rs-loading-state", "aria-live": "polite", children: [
417
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-title", children: "Verification unsuccessful" }),
418
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-subtitle", children: "Your provider could not verify these details. Contact support to continue." }),
419
+ /* @__PURE__ */ jsx(
420
+ "button",
421
+ {
422
+ type: "button",
423
+ className: "rs-button rs-button--default",
424
+ onClick: leaveStep,
425
+ children: "Back"
426
+ }
427
+ )
428
+ ] }),
429
+ !loading && !error && verifying === "reviewing" && /* @__PURE__ */ jsxs("div", { className: "rs-loading-state", "aria-live": "polite", children: [
430
+ /* @__PURE__ */ jsx(Spinner, {}),
431
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-title", children: "Checking your details" }),
432
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-subtitle", children: "This usually takes a moment. You can close this and come back." }),
433
+ /* @__PURE__ */ jsx(
434
+ "button",
435
+ {
436
+ type: "button",
437
+ className: "rs-button rs-button--default",
438
+ onClick: leaveStep,
439
+ children: "Back"
440
+ }
441
+ )
442
+ ] }),
443
+ !loading && !error && verifying === "action_required" && externalOpened && /* @__PURE__ */ jsxs("div", { className: "rs-loading-state", "aria-live": "polite", children: [
444
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-title", children: "Verification opened" }),
445
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-subtitle", children: "Finish verifying in your browser, then return here." }),
446
+ /* @__PURE__ */ jsx(
447
+ "button",
448
+ {
449
+ type: "button",
450
+ className: "rs-button rs-button--default",
451
+ onClick: leaveStep,
452
+ children: "Back"
453
+ }
454
+ )
455
+ ] }),
456
+ !loading && !error && verifying === "action_required" && hostedUrl && /* @__PURE__ */ jsxs("div", { className: "rs-bank-onramp-hosted", children: [
457
+ !iframeLoaded && /* @__PURE__ */ jsx("div", { className: "rs-bank-onramp-frame-loading", "aria-live": "polite", children: /* @__PURE__ */ jsx(Spinner, {}) }),
458
+ /* @__PURE__ */ jsx(
459
+ "iframe",
460
+ {
461
+ title: "Verify your details",
462
+ src: hostedUrl,
463
+ sandbox: "allow-forms allow-scripts allow-same-origin",
464
+ referrerPolicy: "no-referrer",
465
+ allow: "camera; microphone; clipboard-write",
466
+ onLoad: () => setIframeLoaded(true)
467
+ }
468
+ ),
469
+ /* @__PURE__ */ jsx("div", { className: "rs-screen-button-row", children: /* @__PURE__ */ jsx(
470
+ "button",
471
+ {
472
+ type: "button",
473
+ className: "rs-button rs-button--default",
474
+ onClick: leaveStep,
475
+ children: "Back"
476
+ }
477
+ ) })
478
+ ] }),
479
+ !loading && !error && !verifying && !selectedAccount && /* @__PURE__ */ jsxs("div", { className: "rs-list", children: [
480
+ options.map((option) => {
481
+ const account = accounts.find(
482
+ (candidate) => candidate.status === "active" && candidate.currency === option.currency
483
+ );
484
+ return /* @__PURE__ */ jsxs(
485
+ "button",
486
+ {
487
+ type: "button",
488
+ className: "rs-list-row",
489
+ onClick: () => void selectOption(option, account),
490
+ "aria-label": `${account ? "View" : "Set up"} ${option.currency} bank account`,
491
+ children: [
492
+ /* @__PURE__ */ jsx("span", { className: "rs-list-row-leading", children: /* @__PURE__ */ jsx(BankIcon, {}) }),
493
+ /* @__PURE__ */ jsx("span", { className: "rs-list-row-body", children: /* @__PURE__ */ jsxs("span", { className: "rs-list-row-text", children: [
494
+ /* @__PURE__ */ jsxs("span", { className: "rs-list-row-title", children: [
495
+ option.currency,
496
+ " account"
497
+ ] }),
498
+ /* @__PURE__ */ jsx("span", { className: "rs-list-row-subtitle", children: account ? "View bank details" : "Set up bank details" })
499
+ ] }) })
500
+ ]
501
+ },
502
+ option.currency
503
+ );
504
+ }),
505
+ options.length === 0 && /* @__PURE__ */ jsxs("div", { className: "rs-loading-state", children: [
506
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-title", children: "No bank transfer options are available." }),
507
+ /* @__PURE__ */ jsx(
508
+ "button",
509
+ {
510
+ type: "button",
511
+ className: "rs-button rs-button--default",
512
+ onClick: leaveStep,
513
+ children: "Back"
514
+ }
515
+ )
516
+ ] })
517
+ ] }),
518
+ !loading && !error && !verifying && hostedUrl && /* @__PURE__ */ jsxs("div", { className: "rs-bank-onramp-hosted", children: [
519
+ !iframeLoaded && /* @__PURE__ */ jsx("div", { className: "rs-bank-onramp-frame-loading", "aria-live": "polite", children: /* @__PURE__ */ jsx(Spinner, {}) }),
520
+ /* @__PURE__ */ jsx(
521
+ "iframe",
522
+ {
523
+ title: "Bank account details",
524
+ src: hostedUrl,
525
+ sandbox: "allow-forms allow-scripts allow-same-origin",
526
+ referrerPolicy: "no-referrer",
527
+ allow: "clipboard-write",
528
+ onLoad: () => setIframeLoaded(true)
529
+ }
530
+ ),
531
+ paymentStatus,
532
+ /* @__PURE__ */ jsxs("div", { className: "rs-screen-button-row", children: [
533
+ /* @__PURE__ */ jsx(
534
+ "button",
535
+ {
536
+ type: "button",
537
+ className: "rs-button rs-button--default",
538
+ onClick: retry,
539
+ children: "Retry"
540
+ }
541
+ ),
542
+ /* @__PURE__ */ jsx(
543
+ "button",
544
+ {
545
+ type: "button",
546
+ className: "rs-button rs-button--default",
547
+ onClick: backToCurrencies,
548
+ children: "Back"
549
+ }
550
+ )
551
+ ] })
552
+ ] }),
553
+ !loading && !error && !verifying && externalOpened && /* @__PURE__ */ jsxs("div", { className: "rs-loading-state", "aria-live": "polite", children: [
554
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-title", children: "Bank details opened" }),
555
+ /* @__PURE__ */ jsx("div", { className: "rs-loading-subtitle", children: "Return here when you have finished." }),
556
+ paymentStatus,
557
+ /* @__PURE__ */ jsx(
558
+ "button",
559
+ {
560
+ type: "button",
561
+ className: "rs-button rs-button--default",
562
+ onClick: retry,
563
+ children: "Retry"
564
+ }
565
+ ),
566
+ /* @__PURE__ */ jsx(
567
+ "button",
568
+ {
569
+ type: "button",
570
+ className: "rs-button rs-button--default",
571
+ onClick: backToCurrencies,
572
+ children: "Back"
573
+ }
574
+ )
575
+ ] })
576
+ ] }) });
577
+ }
578
+ BankOnrampStep.displayName = "BankOnrampStep";
579
+ export {
580
+ BankOnrampStep
581
+ };