@mintmoney/react 0.1.0-alpha.30 → 0.1.0-alpha.32

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 (88) hide show
  1. package/dist/esm/api/checkouts.js +3 -10
  2. package/dist/esm/api/checkouts.js.map +1 -1
  3. package/dist/esm/api/generated.js +16 -500
  4. package/dist/esm/api/generated.js.map +1 -1
  5. package/dist/esm/api/payments.js +28 -29
  6. package/dist/esm/api/payments.js.map +1 -1
  7. package/dist/esm/api/types.js.map +1 -1
  8. package/dist/esm/checkout/index.js +19 -1
  9. package/dist/esm/checkout/index.js.map +1 -1
  10. package/dist/esm/checkout/modal.js +7 -7
  11. package/dist/esm/checkout/modal.js.map +1 -1
  12. package/dist/esm/checkout/views/crypto-intent/direct-details.js +148 -19
  13. package/dist/esm/checkout/views/crypto-intent/direct-details.js.map +1 -1
  14. package/dist/esm/checkout/views/crypto-intent/methods.js +5 -5
  15. package/dist/esm/checkout/views/crypto-intent/methods.js.map +1 -1
  16. package/dist/esm/checkout/views/crypto-intent/wallet-payment-confirmation-amount.js +30 -0
  17. package/dist/esm/checkout/views/crypto-intent/wallet-payment-confirmation-amount.js.map +1 -0
  18. package/dist/esm/checkout/views/crypto-intent/wallet-payment-options.js +581 -0
  19. package/dist/esm/checkout/views/crypto-intent/wallet-payment-options.js.map +1 -0
  20. package/dist/esm/checkout/views/crypto-intent/wallet-select.js +3 -31
  21. package/dist/esm/checkout/views/crypto-intent/wallet-select.js.map +1 -1
  22. package/dist/esm/checkout/views/initial.js +1 -4
  23. package/dist/esm/checkout/views/initial.js.map +1 -1
  24. package/dist/esm/checkout/views/restart.js +2 -0
  25. package/dist/esm/checkout/views/restart.js.map +1 -1
  26. package/dist/esm/components/text/index.js +1 -0
  27. package/dist/esm/components/text/index.js.map +1 -1
  28. package/dist/esm/components/wallet-selector/wallets/wagmi/index.js +8 -1
  29. package/dist/esm/components/wallet-selector/wallets/wagmi/index.js.map +1 -1
  30. package/dist/esm/config.js +1 -1
  31. package/dist/esm/config.js.map +1 -1
  32. package/dist/esm/integrations/evm-chain.js +22 -0
  33. package/dist/esm/integrations/evm-chain.js.map +1 -0
  34. package/dist/esm/integrations/index.js +1 -0
  35. package/dist/esm/integrations/index.js.map +1 -1
  36. package/dist/esm/integrations/lifi.js +200 -0
  37. package/dist/esm/integrations/lifi.js.map +1 -0
  38. package/dist/esm/state/checkout/payment-attempt.js +43 -0
  39. package/dist/esm/state/checkout/payment-attempt.js.map +1 -0
  40. package/dist/esm/state/checkout/payment-lifecycle.js +58 -0
  41. package/dist/esm/state/checkout/payment-lifecycle.js.map +1 -0
  42. package/dist/esm/state/checkout/store.js +465 -42
  43. package/dist/esm/state/checkout/store.js.map +1 -1
  44. package/dist/types/api/checkouts.d.ts +0 -1
  45. package/dist/types/api/checkouts.d.ts.map +1 -1
  46. package/dist/types/api/generated.d.ts +162 -1406
  47. package/dist/types/api/generated.d.ts.map +1 -1
  48. package/dist/types/api/payments.d.ts +18 -8
  49. package/dist/types/api/payments.d.ts.map +1 -1
  50. package/dist/types/api/types.d.ts +6 -8
  51. package/dist/types/api/types.d.ts.map +1 -1
  52. package/dist/types/checkout/index.d.ts.map +1 -1
  53. package/dist/types/checkout/views/crypto-intent/direct-details.d.ts.map +1 -1
  54. package/dist/types/checkout/views/crypto-intent/wallet-payment-confirmation-amount.d.ts +13 -0
  55. package/dist/types/checkout/views/crypto-intent/wallet-payment-confirmation-amount.d.ts.map +1 -0
  56. package/dist/types/checkout/views/crypto-intent/wallet-payment-options.d.ts +3 -0
  57. package/dist/types/checkout/views/crypto-intent/wallet-payment-options.d.ts.map +1 -0
  58. package/dist/types/checkout/views/crypto-intent/wallet-select.d.ts.map +1 -1
  59. package/dist/types/checkout/views/initial.d.ts.map +1 -1
  60. package/dist/types/checkout/views/restart.d.ts.map +1 -1
  61. package/dist/types/components/text/index.d.ts.map +1 -1
  62. package/dist/types/components/wallet-selector/domain.d.ts +9 -0
  63. package/dist/types/components/wallet-selector/domain.d.ts.map +1 -1
  64. package/dist/types/components/wallet-selector/wallets/wagmi/index.d.ts +449 -0
  65. package/dist/types/components/wallet-selector/wallets/wagmi/index.d.ts.map +1 -1
  66. package/dist/types/config.d.ts +65 -1
  67. package/dist/types/config.d.ts.map +1 -1
  68. package/dist/types/integrations/evm-chain.d.ts +3 -0
  69. package/dist/types/integrations/evm-chain.d.ts.map +1 -0
  70. package/dist/types/integrations/index.d.ts +1 -0
  71. package/dist/types/integrations/index.d.ts.map +1 -1
  72. package/dist/types/integrations/lifi.d.ts +14 -0
  73. package/dist/types/integrations/lifi.d.ts.map +1 -0
  74. package/dist/types/state/checkout/payment-attempt.d.ts +9 -0
  75. package/dist/types/state/checkout/payment-attempt.d.ts.map +1 -0
  76. package/dist/types/state/checkout/payment-lifecycle.d.ts +20 -0
  77. package/dist/types/state/checkout/payment-lifecycle.d.ts.map +1 -0
  78. package/dist/types/state/checkout/store.d.ts +31 -86
  79. package/dist/types/state/checkout/store.d.ts.map +1 -1
  80. package/package.json +4 -2
  81. package/dist/esm/checkout/views/crypto-intent/currency-select.js +0 -44
  82. package/dist/esm/checkout/views/crypto-intent/currency-select.js.map +0 -1
  83. package/dist/esm/components/crypto/default-currency-selector.js +0 -62
  84. package/dist/esm/components/crypto/default-currency-selector.js.map +0 -1
  85. package/dist/types/checkout/views/crypto-intent/currency-select.d.ts +0 -3
  86. package/dist/types/checkout/views/crypto-intent/currency-select.d.ts.map +0 -1
  87. package/dist/types/components/crypto/default-currency-selector.d.ts +0 -9
  88. package/dist/types/components/crypto/default-currency-selector.d.ts.map +0 -1
@@ -1,10 +1,13 @@
1
1
  import { waitForTransactionReceipt } from "@wagmi/core";
2
2
  import { create } from "zustand";
3
3
  import { immer } from "zustand/middleware/immer";
4
- import { getCheckoutById, getCheckoutCryptoCurrencies, } from "../../api/checkouts.js";
5
- import { createPaymentAttempt, createTestPaymentAttempt, createFiatPaymentAttempt, getPaymentById, } from "../../api/payments.js";
4
+ import { getCheckoutCryptoCurrencies } from "../../api/checkouts.js";
5
+ import { createPaymentAttempt, createTestPaymentAttempt, createFiatPaymentAttempt, getPaymentById, getRequiredSettlementConfig, } from "../../api/payments.js";
6
6
  import { PaymentStatus, } from "../../api/types.js";
7
7
  import { WagmiWalletInterface } from "../../components/wallet-selector/wallets/wagmi/index.js";
8
+ import { resolveEvmChainId, switchEvmWalletChain, } from "../../integrations/evm-chain.js";
9
+ import { buildWalletPaymentAttemptPayload } from "./payment-attempt.js";
10
+ import { canCreateFreshAttempt, getAttemptFreshnessStatus, } from "./payment-lifecycle.js";
8
11
  export var ACTIONS;
9
12
  (function (ACTIONS) {
10
13
  ACTIONS["GET_CHECKOUT_CONFIG"] = "GET_CHECKOUT_CONFIG";
@@ -18,6 +21,20 @@ export var ACTIONS;
18
21
  ACTIONS["POLL_PAYMENT"] = "POLL_PAYMENT";
19
22
  ACTIONS["CHECK_CHAIN_STATUS"] = "CHECK_CHAIN_STATUS";
20
23
  })(ACTIONS || (ACTIONS = {}));
24
+ const initialWalletPaymentState = () => ({
25
+ status: "idle",
26
+ options: [],
27
+ walletAddress: null,
28
+ selectedOptionId: null,
29
+ quoteId: null,
30
+ routeId: null,
31
+ sourceTxHash: null,
32
+ destinationTxHash: null,
33
+ quoteStatus: "idle",
34
+ quote: null,
35
+ quoteError: null,
36
+ error: null,
37
+ });
21
38
  const initialActionState = (initialValue) => Object.values(ACTIONS).reduce((acc, action) => {
22
39
  acc[action] = initialValue;
23
40
  return acc;
@@ -26,6 +43,7 @@ export const useCheckoutStore = create()(immer((_set, _get) => ({
26
43
  checkoutId: null,
27
44
  selectedMethod: null,
28
45
  selectedCurrency: null,
46
+ retrySession: false,
29
47
  methods: null,
30
48
  cryptoCurrency: null,
31
49
  checkout: null,
@@ -46,11 +64,280 @@ export const useCheckoutStore = create()(immer((_set, _get) => ({
46
64
  pollCount: 0,
47
65
  lastPollTime: null,
48
66
  },
67
+ attemptFreshness: {
68
+ status: "idle",
69
+ expiresAt: null,
70
+ remainingMs: null,
71
+ },
72
+ attemptRefresh: {
73
+ mode: "none",
74
+ status: "idle",
75
+ error: null,
76
+ },
77
+ walletPayment: initialWalletPaymentState(),
49
78
  })));
50
79
  const set = (fn) => {
51
80
  useCheckoutStore.setState(fn);
52
81
  };
53
82
  const get = () => useCheckoutStore.getState();
83
+ let attemptFreshnessTimer = null;
84
+ let attemptFreshnessInterval = null;
85
+ const clearAttemptFreshnessTimer = () => {
86
+ if (attemptFreshnessTimer) {
87
+ clearTimeout(attemptFreshnessTimer);
88
+ attemptFreshnessTimer = null;
89
+ }
90
+ if (attemptFreshnessInterval) {
91
+ clearInterval(attemptFreshnessInterval);
92
+ attemptFreshnessInterval = null;
93
+ }
94
+ };
95
+ const buildDirectWalletQuote = (option, attempt) => {
96
+ const amount = String(attempt.amount_formatted);
97
+ return {
98
+ quoteId: option.quoteId,
99
+ routeId: option.routeId,
100
+ toolName: "Direct transfer",
101
+ fromAmount: amount,
102
+ fromAmountDisplay: amount,
103
+ toAmount: amount,
104
+ toAmountDisplay: amount,
105
+ estimatedDurationMinutes: 1,
106
+ };
107
+ };
108
+ const setAttemptRefreshState = (update, walletUpdate) => {
109
+ set((state) => {
110
+ Object.assign(state.attemptRefresh, update);
111
+ if (walletUpdate) {
112
+ Object.assign(state.walletPayment, walletUpdate);
113
+ }
114
+ });
115
+ };
116
+ const getWalletQuoteLoadingState = () => ({
117
+ quoteStatus: "loading",
118
+ quote: null,
119
+ quoteError: null,
120
+ quoteId: null,
121
+ routeId: null,
122
+ });
123
+ const getWalletQuoteReadyState = (quote) => ({
124
+ quoteStatus: "ready",
125
+ quote,
126
+ quoteId: quote.quoteId,
127
+ routeId: quote.routeId,
128
+ quoteError: null,
129
+ });
130
+ const getWalletQuoteFailedState = (message) => ({
131
+ quoteStatus: "failed",
132
+ quote: null,
133
+ quoteError: message,
134
+ });
135
+ const beginAttemptRefresh = (walletUpdate) => {
136
+ setAttemptRefreshState({
137
+ status: "refreshing",
138
+ error: null,
139
+ }, walletUpdate);
140
+ };
141
+ const finishAttemptRefresh = (walletUpdate) => {
142
+ setAttemptRefreshState({
143
+ status: "idle",
144
+ error: null,
145
+ }, walletUpdate);
146
+ };
147
+ const failAttemptRefresh = (message) => {
148
+ const { attemptRefresh } = get();
149
+ setAttemptRefreshState({
150
+ status: "failed",
151
+ error: message,
152
+ }, attemptRefresh.mode === "wallet"
153
+ ? getWalletQuoteFailedState(message)
154
+ : undefined);
155
+ };
156
+ const getWalletAttemptRefreshContext = () => {
157
+ const state = get();
158
+ const payment = state.payment;
159
+ const checkout = state.checkout;
160
+ const settlementCurrency = state.cryptoCurrency;
161
+ const selectedOption = state.walletPayment.options.find((option) => option.id === state.walletPayment.selectedOptionId);
162
+ const walletAddress = state.walletPayment.walletAddress;
163
+ if (!payment || !checkout || !settlementCurrency) {
164
+ throw new Error("Wallet payment context is not ready");
165
+ }
166
+ if (!selectedOption || !walletAddress) {
167
+ throw new Error("Missing wallet payment selection for quote refresh");
168
+ }
169
+ return {
170
+ payment,
171
+ checkout,
172
+ settlementCurrency,
173
+ selectedOption,
174
+ walletAddress,
175
+ };
176
+ };
177
+ const resolveRefreshedWalletQuote = async ({ selectedOption, checkout, payment, settlementCurrency, walletAddress, attempt, }) => {
178
+ if (selectedOption.kind === "direct") {
179
+ return buildDirectWalletQuote(selectedOption, attempt);
180
+ }
181
+ // Keep routed-quote loading behind a dynamic import so the checkout store
182
+ // does not eagerly pull the LI.FI integration path into manual-only flows.
183
+ const { fetchWalletPaymentQuote } = await import("../../integrations/index.js");
184
+ return fetchWalletPaymentQuote({
185
+ option: selectedOption,
186
+ context: {
187
+ checkout,
188
+ payment: get().payment ?? payment,
189
+ settlementCurrency,
190
+ walletAddress,
191
+ cryptoPaymentAttempt: attempt,
192
+ },
193
+ });
194
+ };
195
+ const refreshWalletPaymentAttempt = async () => {
196
+ const context = getWalletAttemptRefreshContext();
197
+ beginAttemptRefresh(getWalletQuoteLoadingState());
198
+ const attempt = await createWalletOptionPaymentAttempt(context.selectedOption, context.walletAddress, null);
199
+ const quote = await resolveRefreshedWalletQuote({
200
+ ...context,
201
+ attempt,
202
+ });
203
+ finishAttemptRefresh(getWalletQuoteReadyState(quote));
204
+ };
205
+ const refreshCurrentAttempt = async () => {
206
+ const state = get();
207
+ if (state.attemptRefresh.status === "refreshing") {
208
+ return;
209
+ }
210
+ if (!canCreateFreshAttempt(state.payment)) {
211
+ return;
212
+ }
213
+ try {
214
+ if (state.attemptRefresh.mode === "wallet") {
215
+ await refreshWalletPaymentAttempt();
216
+ return;
217
+ }
218
+ if (state.attemptRefresh.mode === "manual") {
219
+ beginAttemptRefresh();
220
+ await createManualDirectPaymentAttempt();
221
+ finishAttemptRefresh();
222
+ }
223
+ }
224
+ catch (error) {
225
+ const message = error instanceof Error
226
+ ? error.message
227
+ : "Failed to refresh payment quote";
228
+ failAttemptRefresh(message);
229
+ }
230
+ };
231
+ const scheduleAttemptFreshness = (payment, attempt) => {
232
+ clearAttemptFreshnessTimer();
233
+ const freshness = getAttemptFreshnessStatus(payment, attempt ?? null);
234
+ set((state) => {
235
+ state.attemptFreshness = freshness;
236
+ });
237
+ if (freshness.status !== "fresh" || !freshness.expiresAt) {
238
+ return;
239
+ }
240
+ // The one-shot timer is the authoritative expiry transition. When the
241
+ // current attempt crosses its `expires_at`, we recompute freshness against the
242
+ // latest payment snapshot and kick off a single refresh if the attempt is now
243
+ // stale.
244
+ const delay = Math.max(0, freshness.expiresAt - Date.now());
245
+ attemptFreshnessTimer = setTimeout(() => {
246
+ const state = get();
247
+ const currentAttempt = state.cryptoPaymentAttempt ?? state.payment?.payment_attempt;
248
+ const currentFreshness = getAttemptFreshnessStatus(state.payment, currentAttempt);
249
+ set((draft) => {
250
+ draft.attemptFreshness = currentFreshness;
251
+ });
252
+ if (currentFreshness.status === "stale") {
253
+ void refreshCurrentAttempt();
254
+ }
255
+ }, delay);
256
+ // The interval is only for UI countdown updates while an attempt is still
257
+ // fresh. It keeps `remainingMs` moving without waiting for the expiry timer to
258
+ // fire, and stops itself once the attempt is no longer usable.
259
+ attemptFreshnessInterval = setInterval(() => {
260
+ const currentState = get();
261
+ const currentAttempt = currentState.cryptoPaymentAttempt ??
262
+ currentState.payment?.payment_attempt;
263
+ const currentFreshness = getAttemptFreshnessStatus(currentState.payment, currentAttempt);
264
+ if (currentFreshness.status !== "fresh" || !currentFreshness.expiresAt) {
265
+ clearAttemptFreshnessTimer();
266
+ set((state) => {
267
+ state.attemptFreshness = {
268
+ status: currentFreshness.status,
269
+ expiresAt: currentFreshness.expiresAt,
270
+ remainingMs: currentFreshness.expiresAt !== null
271
+ ? 0
272
+ : currentFreshness.remainingMs,
273
+ };
274
+ });
275
+ return;
276
+ }
277
+ set((state) => {
278
+ state.attemptFreshness = {
279
+ ...currentFreshness,
280
+ remainingMs: currentFreshness.remainingMs,
281
+ };
282
+ });
283
+ }, 1000);
284
+ };
285
+ export const syncCheckoutAttemptFreshness = (payment, attempt) => {
286
+ // Re-sync the freshness timers whenever the payment snapshot or active
287
+ // attempt changes. This is the single entrypoint used after loading a
288
+ // payment, creating a fresh attempt, or resetting session state.
289
+ scheduleAttemptFreshness(payment, attempt);
290
+ };
291
+ export const configureWalletAttemptRefresh = (walletAddress) => {
292
+ set((state) => {
293
+ // Wallet refresh mode means stale attempts should be replaced by creating a
294
+ // fresh crypto payment attempt and reloading wallet quote state for the
295
+ // currently selected wallet route.
296
+ state.attemptRefresh.mode = "wallet";
297
+ state.attemptRefresh.error = null;
298
+ if (state.attemptRefresh.status !== "refreshing") {
299
+ state.attemptRefresh.status = "idle";
300
+ }
301
+ state.walletPayment.walletAddress = walletAddress;
302
+ });
303
+ };
304
+ export const configureManualAttemptRefresh = () => {
305
+ set((state) => {
306
+ // Manual refresh mode reuses the same stale-attempt lifecycle, but the
307
+ // refresh path only needs a new direct/manual attempt and not any wallet
308
+ // quote metadata.
309
+ state.attemptRefresh.mode = "manual";
310
+ state.attemptRefresh.error = null;
311
+ if (state.attemptRefresh.status !== "refreshing") {
312
+ state.attemptRefresh.status = "idle";
313
+ }
314
+ });
315
+ };
316
+ export const clearAttemptRefreshContext = () => {
317
+ set((state) => {
318
+ // Clear any stale refresh mode before returning to method selection or
319
+ // closing the checkout so future expiry events do not try to reuse old
320
+ // wallet/manual context.
321
+ state.attemptRefresh = {
322
+ mode: "none",
323
+ status: "idle",
324
+ error: null,
325
+ };
326
+ state.walletPayment.walletAddress = null;
327
+ });
328
+ };
329
+ const updatePaymentSnapshot = (payment, attempt, extra) => {
330
+ set((state) => {
331
+ state.payment = payment;
332
+ if (attempt !== undefined) {
333
+ state.cryptoPaymentAttempt = attempt;
334
+ }
335
+ if (extra) {
336
+ extra(state);
337
+ }
338
+ });
339
+ syncCheckoutAttemptFreshness(payment, attempt ?? payment?.payment_attempt);
340
+ };
54
341
  export const clearErrors = () => {
55
342
  set((state) => {
56
343
  Object.values(ACTIONS).forEach((action) => {
@@ -59,11 +346,12 @@ export const clearErrors = () => {
59
346
  });
60
347
  };
61
348
  export const initialiseStore = async (payment) => {
62
- set((state) => {
63
- state.payment = payment;
64
- });
349
+ resetCheckoutStore();
350
+ updatePaymentSnapshot(payment);
65
351
  try {
66
- await getCheckoutConfig(payment.checkout_id);
352
+ const refreshedPayment = await getPaymentById(payment.id);
353
+ updatePaymentSnapshot(refreshedPayment);
354
+ hydrateCheckoutFromPayment(refreshedPayment);
67
355
  }
68
356
  catch (err) {
69
357
  set((state) => {
@@ -71,24 +359,39 @@ export const initialiseStore = async (payment) => {
71
359
  });
72
360
  }
73
361
  };
74
- export const createCheckoutCryptoPaymentAttempt = async (currency) => {
75
- const paymentId = useCheckoutStore.getState().payment?.id;
362
+ export const createWalletOptionPaymentAttempt = async (
363
+ // "direct" is a routing classification: the payer sends the settlement
364
+ // asset on the settlement chain. It is not tied to manual vs wallet UX.
365
+ option, sendingAddress, quote) => {
366
+ const payment = useCheckoutStore.getState().payment;
367
+ const paymentId = payment?.id;
76
368
  if (!paymentId) {
77
369
  throw Error("Cannot create payment attempt without payment object.");
78
370
  }
371
+ if (!canCreateFreshAttempt(payment)) {
372
+ throw Error("This payment is no longer payable.");
373
+ }
79
374
  try {
80
375
  set((state) => {
81
376
  state.error[ACTIONS.CREATE_PAYMENT_ATTEMPT] = null;
82
377
  state.loading[ACTIONS.CREATE_PAYMENT_ATTEMPT] = true;
83
378
  });
84
- const attempt = await createPaymentAttempt(paymentId, currency, 1);
85
- const payment = await getPaymentById(paymentId);
86
- set((state) => {
87
- state.payment = payment;
88
- state.cryptoPaymentAttempt = attempt;
379
+ const attemptPayload = buildWalletPaymentAttemptPayload({
380
+ option,
381
+ sendingAddress,
382
+ quote,
383
+ requiredConfirmations: 1,
384
+ });
385
+ const attempt = await createPaymentAttempt(paymentId, attemptPayload, 1);
386
+ const refreshedPayment = await getPaymentById(paymentId);
387
+ updatePaymentSnapshot(refreshedPayment, attempt, (state) => {
388
+ state.retrySession = false;
389
+ state.attemptRefresh.status = "idle";
390
+ state.attemptRefresh.error = null;
89
391
  state.error[ACTIONS.CREATE_PAYMENT_ATTEMPT] = null;
90
392
  state.loading[ACTIONS.CREATE_PAYMENT_ATTEMPT] = false;
91
393
  });
394
+ return attempt;
92
395
  }
93
396
  catch (err) {
94
397
  const error = err;
@@ -96,8 +399,35 @@ export const createCheckoutCryptoPaymentAttempt = async (currency) => {
96
399
  state.error[ACTIONS.CREATE_PAYMENT_ATTEMPT] = error.message;
97
400
  state.loading[ACTIONS.CREATE_PAYMENT_ATTEMPT] = false;
98
401
  });
402
+ throw error;
99
403
  }
100
404
  };
405
+ export const createManualDirectPaymentAttempt = async (sendingAddress = "", confirmations = 1) => {
406
+ const payment = useCheckoutStore.getState().payment;
407
+ const paymentId = payment?.id;
408
+ if (!paymentId) {
409
+ throw Error("Cannot create payment attempt without payment object.");
410
+ }
411
+ const settlementConfig = getRequiredSettlementConfig(payment);
412
+ const attempt = await createPaymentAttempt(paymentId, {
413
+ sendingAddress,
414
+ required_confirmations: confirmations,
415
+ routing: {
416
+ type: "direct",
417
+ source: {
418
+ token: settlementConfig.asset,
419
+ chain: settlementConfig.chain,
420
+ },
421
+ },
422
+ });
423
+ const refreshedPayment = await getPaymentById(paymentId);
424
+ updatePaymentSnapshot(refreshedPayment, attempt, (state) => {
425
+ state.retrySession = false;
426
+ state.attemptRefresh.status = "idle";
427
+ state.attemptRefresh.error = null;
428
+ });
429
+ return attempt;
430
+ };
101
431
  export const createSuccessfulTestPaymentAttempt = async () => {
102
432
  const paymentId = useCheckoutStore.getState().payment?.id;
103
433
  if (!paymentId) {
@@ -108,11 +438,14 @@ export const createSuccessfulTestPaymentAttempt = async () => {
108
438
  state.error[ACTIONS.CREATE_TEST_PAYMENT_ATTEMPT] = null;
109
439
  state.loading[ACTIONS.CREATE_TEST_PAYMENT_ATTEMPT] = true;
110
440
  });
111
- const attempt = await createTestPaymentAttempt(paymentId, "0xsuccess");
441
+ const currentPayment = useCheckoutStore.getState().payment;
442
+ if (!currentPayment) {
443
+ throw Error("Cannot create test payment attempt without payment object.");
444
+ }
445
+ const attempt = await createTestPaymentAttempt(paymentId, "0xsuccess", currentPayment);
112
446
  const payment = await getPaymentById(paymentId);
113
- set((state) => {
114
- state.payment = payment;
115
- state.cryptoPaymentAttempt = attempt;
447
+ updatePaymentSnapshot(payment, attempt, (state) => {
448
+ state.retrySession = false;
116
449
  state.error[ACTIONS.CREATE_TEST_PAYMENT_ATTEMPT] = null;
117
450
  state.loading[ACTIONS.CREATE_TEST_PAYMENT_ATTEMPT] = false;
118
451
  });
@@ -136,11 +469,14 @@ export const createFailedTestPaymentAttempt = async () => {
136
469
  state.error[ACTIONS.CREATE_TEST_PAYMENT_ATTEMPT] = null;
137
470
  state.loading[ACTIONS.CREATE_TEST_PAYMENT_ATTEMPT] = true;
138
471
  });
139
- const attempt = await createTestPaymentAttempt(paymentId, "0xfail");
472
+ const currentPayment = useCheckoutStore.getState().payment;
473
+ if (!currentPayment) {
474
+ throw Error("Cannot create test payment attempt without payment object.");
475
+ }
476
+ const attempt = await createTestPaymentAttempt(paymentId, "0xfail", currentPayment);
140
477
  const payment = await getPaymentById(paymentId);
141
- set((state) => {
142
- state.payment = payment;
143
- state.cryptoPaymentAttempt = attempt;
478
+ updatePaymentSnapshot(payment, attempt, (state) => {
479
+ state.retrySession = false;
144
480
  state.error[ACTIONS.CREATE_TEST_PAYMENT_ATTEMPT] = null;
145
481
  state.loading[ACTIONS.CREATE_TEST_PAYMENT_ATTEMPT] = false;
146
482
  });
@@ -188,33 +524,20 @@ export function getAvailableCheckoutMethods(checkout) {
188
524
  intentName: "cryptoIntent",
189
525
  });
190
526
  }
191
- if (checkout.checkout_config.card?.enabled) {
192
- methods.push({
193
- name: "Pay by Card",
194
- intentName: "cardIntent",
195
- });
196
- }
197
- if (checkout.checkout_config.bank?.enabled) {
198
- methods.push({
199
- name: "Pay by Bank",
200
- intentName: "bankIntent",
201
- });
202
- }
203
527
  return methods;
204
528
  }
205
- export const getCheckoutConfig = async (checkoutId) => {
529
+ export const hydrateCheckoutFromPayment = (payment) => {
206
530
  const ACTION = ACTIONS.GET_CHECKOUT_CONFIG;
207
531
  set((state) => {
208
532
  state.loading[ACTION] = true;
209
533
  state.error[ACTION] = null;
210
534
  });
211
535
  try {
212
- const resp = await getCheckoutById(checkoutId);
213
- if (resp) {
536
+ if (payment.checkout) {
214
537
  set((state) => {
215
- state.checkout = resp;
216
- state.methods = getAvailableCheckoutMethods(resp);
217
- state.cryptoCurrency = getCheckoutCryptoCurrencies(resp);
538
+ state.checkout = payment.checkout;
539
+ state.methods = getAvailableCheckoutMethods(payment.checkout);
540
+ state.cryptoCurrency = getCheckoutCryptoCurrencies(payment.checkout);
218
541
  state.loading[ACTION] = false;
219
542
  state.error[ACTION] = null;
220
543
  });
@@ -250,11 +573,12 @@ export const getPayment = async () => {
250
573
  try {
251
574
  const resp = await getPaymentById(payment.id);
252
575
  if (resp) {
576
+ updatePaymentSnapshot(resp);
253
577
  set((state) => {
254
- state.payment = resp;
255
578
  state.loading[ACTION] = false;
256
579
  state.error[ACTION] = null;
257
580
  });
581
+ hydrateCheckoutFromPayment(resp);
258
582
  return resp;
259
583
  }
260
584
  else {
@@ -277,7 +601,7 @@ export const executeTransfer = async (wallet) => {
277
601
  const ACTION = ACTIONS.EXECUTE_TRANSFER;
278
602
  const state = get();
279
603
  console.log("[Execute Transfer] State:", state);
280
- if (!state.cryptoPaymentAttempt || !state.selectedCurrency) {
604
+ if (!state.cryptoPaymentAttempt) {
281
605
  throw new Error("Missing required data for payment execution");
282
606
  }
283
607
  set((state) => {
@@ -287,6 +611,14 @@ export const executeTransfer = async (wallet) => {
287
611
  state.transfer.error = null;
288
612
  });
289
613
  try {
614
+ const settlementChainHex = state.cryptoPaymentAttempt.settlement_config.chain_hex;
615
+ const targetChainId = resolveEvmChainId(settlementChainHex);
616
+ if (targetChainId && wallet.switchChain) {
617
+ await wallet.switchChain(targetChainId);
618
+ }
619
+ else {
620
+ await switchEvmWalletChain(settlementChainHex);
621
+ }
290
622
  const address = await wallet.getAddress();
291
623
  if (!address) {
292
624
  throw new Error("Failed to get wallet address");
@@ -297,7 +629,7 @@ export const executeTransfer = async (wallet) => {
297
629
  amount: Number(state.cryptoPaymentAttempt.amount),
298
630
  evm: {
299
631
  implementationAddress: state.cryptoPaymentAttempt.settlement_token_address,
300
- assetName: state.selectedCurrency.asset,
632
+ assetName: state.cryptoPaymentAttempt.settlement_config.asset_name,
301
633
  },
302
634
  };
303
635
  const txHash = await wallet.executeTransfer(transfer);
@@ -373,6 +705,25 @@ export const resetTransfer = () => {
373
705
  };
374
706
  });
375
707
  };
708
+ export const executeConnectedWalletPayment = async (wallet) => {
709
+ await executeTransfer(wallet);
710
+ void checkChainStatus(wallet);
711
+ await waitForTransferSuccess();
712
+ };
713
+ function waitForTransferSuccess() {
714
+ return new Promise((resolve, reject) => {
715
+ const unsubscribe = useCheckoutStore.subscribe((state) => {
716
+ if (state.transfer.chainStatus === "confirmed") {
717
+ unsubscribe();
718
+ resolve();
719
+ }
720
+ else if (state.transfer.chainStatus === "failed") {
721
+ unsubscribe();
722
+ reject(new Error(state.transfer.error || "Transaction failed on chain"));
723
+ }
724
+ });
725
+ });
726
+ }
376
727
  export const startPaymentPolling = (onPaymentStatusChange, confirmationStatus = PaymentStatus.PAID) => {
377
728
  const ACTION = ACTIONS.POLL_PAYMENT;
378
729
  const POLL_INTERVAL = 5000;
@@ -439,17 +790,29 @@ export const stopPaymentPolling = () => {
439
790
  });
440
791
  };
441
792
  export const resetCheckoutStore = () => {
793
+ clearAttemptFreshnessTimer();
442
794
  set((state) => {
443
795
  Object.assign(state, {
444
796
  amount: null,
445
797
  checkoutId: null,
446
798
  selectedMethod: null,
447
799
  selectedCurrency: null,
800
+ retrySession: false,
448
801
  methods: null,
449
802
  cryptoCurrency: null,
450
803
  checkout: null,
451
804
  payment: null,
452
805
  cryptoPaymentAttempt: null,
806
+ attemptFreshness: {
807
+ status: "idle",
808
+ expiresAt: null,
809
+ remainingMs: null,
810
+ },
811
+ attemptRefresh: {
812
+ mode: "none",
813
+ status: "idle",
814
+ error: null,
815
+ },
453
816
  loading: initialActionState(false),
454
817
  error: initialActionState(null),
455
818
  transfer: {
@@ -463,9 +826,59 @@ export const resetCheckoutStore = () => {
463
826
  pollCount: 0,
464
827
  lastPollTime: null,
465
828
  },
829
+ walletPayment: initialWalletPaymentState(),
466
830
  });
467
831
  });
468
832
  };
833
+ export const resetCheckoutSessionForRetry = () => {
834
+ clearAttemptFreshnessTimer();
835
+ set((state) => {
836
+ // Retry keeps the same payment intent but discards all attempt-scoped UI
837
+ // state so the payer can choose a fresh route. The payment object itself is
838
+ // preserved elsewhere in the store; this reset only clears execution state
839
+ // that was tied to the previous attempt.
840
+ state.selectedMethod = null;
841
+ state.selectedCurrency = null;
842
+ state.retrySession = true;
843
+ state.cryptoPaymentAttempt = null;
844
+ state.fiatPaymentAttempt = null;
845
+ state.processing = false;
846
+ state.transfer = {
847
+ status: "idle",
848
+ error: null,
849
+ transactionHash: null,
850
+ chainStatus: "pending",
851
+ };
852
+ state.polling = {
853
+ isPolling: false,
854
+ pollCount: 0,
855
+ lastPollTime: null,
856
+ };
857
+ state.walletPayment = initialWalletPaymentState();
858
+ state.attemptFreshness = {
859
+ status: "idle",
860
+ expiresAt: null,
861
+ remainingMs: null,
862
+ };
863
+ state.attemptRefresh = {
864
+ mode: "none",
865
+ status: "idle",
866
+ error: null,
867
+ };
868
+ state.error[ACTIONS.CREATE_PAYMENT_ATTEMPT] = null;
869
+ state.error[ACTIONS.CREATE_FIAT_PAYMENT_ATTEMPT] = null;
870
+ state.error[ACTIONS.CREATE_TEST_PAYMENT_ATTEMPT] = null;
871
+ state.error[ACTIONS.EXECUTE_TRANSFER] = null;
872
+ state.error[ACTIONS.POLL_PAYMENT] = null;
873
+ state.error[ACTIONS.CHECK_CHAIN_STATUS] = null;
874
+ state.loading[ACTIONS.CREATE_PAYMENT_ATTEMPT] = false;
875
+ state.loading[ACTIONS.CREATE_FIAT_PAYMENT_ATTEMPT] = false;
876
+ state.loading[ACTIONS.CREATE_TEST_PAYMENT_ATTEMPT] = false;
877
+ state.loading[ACTIONS.EXECUTE_TRANSFER] = false;
878
+ state.loading[ACTIONS.POLL_PAYMENT] = false;
879
+ state.loading[ACTIONS.CHECK_CHAIN_STATUS] = false;
880
+ });
881
+ };
469
882
  export const setCheckoutMethod = (method) => {
470
883
  set((state) => {
471
884
  state.selectedMethod = method;
@@ -491,4 +904,14 @@ export const isTestMode = () => {
491
904
  }
492
905
  return false;
493
906
  };
907
+ export const setWalletPaymentState = (update) => {
908
+ set((state) => {
909
+ Object.assign(state.walletPayment, update);
910
+ });
911
+ };
912
+ export const resetWalletPaymentState = () => {
913
+ set((state) => {
914
+ state.walletPayment = initialWalletPaymentState();
915
+ });
916
+ };
494
917
  //# sourceMappingURL=store.js.map