@blocklet/payment-react 1.24.3 → 1.25.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (101) hide show
  1. package/es/components/auto-topup/modal.d.ts +2 -0
  2. package/es/components/auto-topup/modal.js +48 -6
  3. package/es/components/auto-topup/product-card.d.ts +16 -1
  4. package/es/components/auto-topup/product-card.js +97 -15
  5. package/es/components/dynamic-pricing-unavailable.d.ts +9 -0
  6. package/es/components/dynamic-pricing-unavailable.js +58 -0
  7. package/es/components/loading-amount.d.ts +17 -0
  8. package/es/components/loading-amount.js +46 -0
  9. package/es/components/price-change-confirm.d.ts +18 -0
  10. package/es/components/price-change-confirm.js +107 -0
  11. package/es/components/quote-details-panel.d.ts +21 -0
  12. package/es/components/quote-details-panel.js +170 -0
  13. package/es/components/quote-lock-banner.d.ts +7 -0
  14. package/es/components/quote-lock-banner.js +79 -0
  15. package/es/components/slippage-config.d.ts +20 -0
  16. package/es/components/slippage-config.js +261 -0
  17. package/es/history/credit/transactions-list.js +11 -1
  18. package/es/history/invoice/list.js +125 -15
  19. package/es/hooks/dynamic-pricing.d.ts +102 -0
  20. package/es/hooks/dynamic-pricing.js +393 -0
  21. package/es/index.d.ts +6 -1
  22. package/es/index.js +9 -1
  23. package/es/libs/util.d.ts +42 -5
  24. package/es/libs/util.js +345 -57
  25. package/es/locales/en.js +114 -3
  26. package/es/locales/zh.js +114 -3
  27. package/es/payment/form/index.d.ts +4 -1
  28. package/es/payment/form/index.js +454 -22
  29. package/es/payment/index.d.ts +1 -1
  30. package/es/payment/index.js +279 -16
  31. package/es/payment/product-item.d.ts +26 -1
  32. package/es/payment/product-item.js +330 -51
  33. package/es/payment/summary-section/promotion-section.d.ts +32 -0
  34. package/es/payment/summary-section/promotion-section.js +143 -0
  35. package/es/payment/summary-section/total-section.d.ts +39 -0
  36. package/es/payment/summary-section/total-section.js +83 -0
  37. package/es/payment/summary.d.ts +17 -2
  38. package/es/payment/summary.js +300 -253
  39. package/es/types/index.d.ts +11 -0
  40. package/lib/components/auto-topup/modal.d.ts +2 -0
  41. package/lib/components/auto-topup/modal.js +54 -6
  42. package/lib/components/auto-topup/product-card.d.ts +16 -1
  43. package/lib/components/auto-topup/product-card.js +75 -7
  44. package/lib/components/dynamic-pricing-unavailable.d.ts +9 -0
  45. package/lib/components/dynamic-pricing-unavailable.js +81 -0
  46. package/lib/components/loading-amount.d.ts +17 -0
  47. package/lib/components/loading-amount.js +53 -0
  48. package/lib/components/price-change-confirm.d.ts +18 -0
  49. package/lib/components/price-change-confirm.js +157 -0
  50. package/lib/components/quote-details-panel.d.ts +21 -0
  51. package/lib/components/quote-details-panel.js +226 -0
  52. package/lib/components/quote-lock-banner.d.ts +7 -0
  53. package/lib/components/quote-lock-banner.js +93 -0
  54. package/lib/components/slippage-config.d.ts +20 -0
  55. package/lib/components/slippage-config.js +316 -0
  56. package/lib/history/credit/transactions-list.js +11 -1
  57. package/lib/history/invoice/list.js +167 -27
  58. package/lib/hooks/dynamic-pricing.d.ts +102 -0
  59. package/lib/hooks/dynamic-pricing.js +390 -0
  60. package/lib/index.d.ts +6 -1
  61. package/lib/index.js +32 -0
  62. package/lib/libs/util.d.ts +42 -5
  63. package/lib/libs/util.js +367 -49
  64. package/lib/locales/en.js +114 -3
  65. package/lib/locales/zh.js +114 -3
  66. package/lib/payment/form/index.d.ts +4 -1
  67. package/lib/payment/form/index.js +476 -20
  68. package/lib/payment/index.d.ts +1 -1
  69. package/lib/payment/index.js +308 -14
  70. package/lib/payment/product-item.d.ts +26 -1
  71. package/lib/payment/product-item.js +270 -35
  72. package/lib/payment/summary-section/promotion-section.d.ts +32 -0
  73. package/lib/payment/summary-section/promotion-section.js +133 -0
  74. package/lib/payment/summary-section/total-section.d.ts +39 -0
  75. package/lib/payment/summary-section/total-section.js +117 -0
  76. package/lib/payment/summary.d.ts +17 -2
  77. package/lib/payment/summary.js +205 -127
  78. package/lib/types/index.d.ts +11 -0
  79. package/package.json +3 -3
  80. package/src/components/auto-topup/modal.tsx +59 -6
  81. package/src/components/auto-topup/product-card.tsx +118 -11
  82. package/src/components/dynamic-pricing-unavailable.tsx +69 -0
  83. package/src/components/loading-amount.tsx +66 -0
  84. package/src/components/price-change-confirm.tsx +136 -0
  85. package/src/components/quote-details-panel.tsx +218 -0
  86. package/src/components/quote-lock-banner.tsx +99 -0
  87. package/src/components/slippage-config.tsx +336 -0
  88. package/src/history/credit/transactions-list.tsx +14 -1
  89. package/src/history/invoice/list.tsx +143 -9
  90. package/src/hooks/dynamic-pricing.ts +617 -0
  91. package/src/index.ts +9 -0
  92. package/src/libs/util.ts +473 -58
  93. package/src/locales/en.tsx +117 -0
  94. package/src/locales/zh.tsx +111 -0
  95. package/src/payment/form/index.tsx +561 -19
  96. package/src/payment/index.tsx +349 -10
  97. package/src/payment/product-item.tsx +451 -37
  98. package/src/payment/summary-section/promotion-section.tsx +172 -0
  99. package/src/payment/summary-section/total-section.tsx +141 -0
  100. package/src/payment/summary.tsx +334 -192
  101. package/src/types/index.ts +15 -0
@@ -9,6 +9,7 @@ import type {
9
9
  TCustomer,
10
10
  TPaymentCurrency,
11
11
  TPaymentMethod,
12
+ TPaymentIntent,
12
13
  TPaymentMethodExpanded,
13
14
  } from '@blocklet/payment-types';
14
15
  import { ArrowBackOutlined } from '@mui/icons-material';
@@ -16,7 +17,7 @@ import { Box, Fade, Stack, type BoxProps } from '@mui/material';
16
17
  import { styled } from '@mui/system';
17
18
  import { fromTokenToUnit } from '@ocap/util';
18
19
  import { useSetState } from 'ahooks';
19
- import { useEffect, useState, useMemo } from 'react';
20
+ import { useEffect, useMemo, useRef, useState } from 'react';
20
21
  import { FormProvider, useForm, useWatch } from 'react-hook-form';
21
22
  import trim from 'lodash/trim';
22
23
  import type { LiteralUnion } from 'type-fest';
@@ -61,11 +62,54 @@ function PaymentInner({
61
62
  onChange,
62
63
  action,
63
64
  showCheckoutSummary = true,
65
+ quotes,
66
+ rateUnavailable,
67
+ rateError,
64
68
  }: MainProps) {
65
69
  const { t } = useLocaleContext();
66
70
  const { settings, session } = usePaymentContext();
67
71
  const { isMobile } = useMobile();
68
- const [state, setState] = useSetState<{ checkoutSession: TCheckoutSessionExpanded }>({ checkoutSession });
72
+ const [state, setState] = useSetState<{
73
+ checkoutSession: TCheckoutSessionExpanded;
74
+ quotes?: any;
75
+ rateUnavailable?: boolean;
76
+ rateError?: string;
77
+ paymentIntent?: TPaymentIntent | null;
78
+ liveRateInfo?: {
79
+ rate?: string;
80
+ provider_id?: string;
81
+ provider_name?: string;
82
+ base_currency?: string;
83
+ timestamp_ms?: number;
84
+ volatility_threshold?: number;
85
+ };
86
+ liveQuoteSnapshot?: {
87
+ id: string;
88
+ quoted_amount: string;
89
+ exchange_rate: string;
90
+ expires_at: number;
91
+ rate_timestamp_ms?: number | null;
92
+ renewed?: boolean;
93
+ };
94
+ liveRateUnavailable?: boolean;
95
+ liveRateError?: string;
96
+ isRateLoading?: boolean;
97
+ }>({
98
+ checkoutSession,
99
+ quotes,
100
+ rateUnavailable,
101
+ rateError,
102
+ paymentIntent,
103
+ liveRateInfo: undefined,
104
+ liveQuoteSnapshot: undefined,
105
+ liveRateUnavailable: false,
106
+ liveRateError: undefined,
107
+ isRateLoading: false,
108
+ });
109
+ // Track if currency is being switched (vs just rate refresh)
110
+ // Only show skeleton during currency switch, not during rate refresh
111
+ const isCurrencySwitchRef = useRef(false);
112
+ const prevCurrencyIdRef = useRef<string | null>(null);
69
113
  const query = getQueryParams(window.location.href);
70
114
 
71
115
  const availableCurrencyIds = useMemo(() => {
@@ -172,16 +216,227 @@ function PaymentInner({
172
216
  (findCurrency(paymentMethods as TPaymentMethodExpanded[], currencyId as string) as TPaymentCurrency) ||
173
217
  settings.baseCurrency;
174
218
  const method = paymentMethods.find((x: any) => x.id === currency.payment_method_id) as TPaymentMethod;
219
+ const hasDynamicPricing = useMemo(
220
+ () =>
221
+ state.checkoutSession.line_items.some(
222
+ (item: any) => (item.upsell_price || item.price)?.pricing_type === 'dynamic'
223
+ ),
224
+ [state.checkoutSession.line_items]
225
+ );
226
+ // Stripe/USD payments don't need exchange rate - base_amount is already in USD
227
+ const isStripePayment = method?.type === 'stripe';
228
+ const needsExchangeRate = hasDynamicPricing && !isStripePayment;
229
+ const effectiveRateUnavailable = needsExchangeRate && (state.rateUnavailable || state.liveRateUnavailable);
230
+ // Don't expose technical errors to UI - only log them for debugging
231
+ if (state.liveRateError || state.rateError) {
232
+ console.error('[Rate Error]', { liveRateError: state.liveRateError, rateError: state.rateError });
233
+ }
234
+
235
+ // Manual refresh function - exposed for retry button
236
+ const refreshRateRef = useRef<(() => Promise<void>) | null>(null);
237
+ useEffect(() => {
238
+ if (!currencyId) {
239
+ return;
240
+ }
241
+ // Detect currency switch (not initial load)
242
+ const isCurrencySwitch = prevCurrencyIdRef.current !== null && prevCurrencyIdRef.current !== currencyId;
243
+ prevCurrencyIdRef.current = currencyId;
244
+
245
+ if (isCurrencySwitch) {
246
+ isCurrencySwitchRef.current = true;
247
+ // Show skeleton during currency switch
248
+ setState({ isRateLoading: true });
249
+ }
250
+
251
+ if (needsExchangeRate) {
252
+ setState({
253
+ liveRateInfo: undefined,
254
+ liveQuoteSnapshot: undefined,
255
+ liveRateUnavailable: false,
256
+ liveRateError: undefined,
257
+ });
258
+ liveRateRefreshRef.current = false;
259
+ refreshRateRef.current?.();
260
+ } else {
261
+ setState({
262
+ liveRateInfo: undefined,
263
+ liveQuoteSnapshot: undefined,
264
+ liveRateUnavailable: false,
265
+ liveRateError: undefined,
266
+ });
267
+ liveRateRefreshRef.current = false;
268
+ // For Stripe payments (no exchange rate needed), clear loading after promotion recalc
269
+ // If no discounts, clear immediately
270
+ if (isCurrencySwitch && !(state.checkoutSession as any)?.discounts?.length) {
271
+ setState({ isRateLoading: false });
272
+ isCurrencySwitchRef.current = false;
273
+ }
274
+ }
275
+ // eslint-disable-next-line react-hooks/exhaustive-deps
276
+ }, [currencyId, needsExchangeRate]);
277
+
278
+ useEffect(() => {
279
+ // Skip exchange rate fetching for Stripe payments (USD base_amount is used directly)
280
+ if (!state.checkoutSession?.id || completed || !needsExchangeRate) {
281
+ return undefined;
282
+ }
283
+ let cancelled = false;
284
+ let consecutiveFailures = 0;
285
+ const baseInterval = 30000;
286
+ const MAX_INTERVAL = 5 * 60 * 1000;
287
+ const QUICK_RETRY_DELAY = 1000; // 1s quick retry
288
+ const MAX_QUICK_RETRIES = 2; // Quick retry 2 times before exponential backoff
289
+ let currentInterval = baseInterval;
290
+ let timer: ReturnType<typeof setInterval> | null = null;
291
+
292
+ const scheduleNext = () => {
293
+ if (timer) {
294
+ clearInterval(timer);
295
+ }
296
+ timer = window.setInterval(() => {
297
+ fetchRate(false);
298
+ }, currentInterval) as unknown as ReturnType<typeof setInterval>;
299
+ };
300
+
301
+ const fetchRate = async (isManualRetry = false) => {
302
+ if (document.hidden && !isManualRetry) {
303
+ return;
304
+ }
305
+
306
+ if (typeof navigator !== 'undefined' && !navigator.onLine) {
307
+ return;
308
+ }
309
+
310
+ if (liveRateRefreshRef.current) {
311
+ return;
312
+ }
313
+ liveRateRefreshRef.current = true;
314
+
315
+ // Quick retry logic: try up to MAX_QUICK_RETRIES times with short delays
316
+ let quickRetryCount = 0;
317
+ let lastError: any = null;
318
+
319
+ try {
320
+ // eslint-disable-next-line no-await-in-loop
321
+ while (quickRetryCount <= MAX_QUICK_RETRIES) {
322
+ try {
323
+ // eslint-disable-next-line no-await-in-loop
324
+ const { data } = await api.get(`/api/checkout-sessions/${state.checkoutSession.id}/exchange-rate`, {
325
+ params: currencyId ? { currency_id: currencyId } : undefined,
326
+ });
327
+ if (cancelled) {
328
+ return;
329
+ }
330
+ consecutiveFailures = 0;
331
+ currentInterval = baseInterval;
332
+ // Final Freeze: Only store rate info, no quote_snapshot
333
+ setState({
334
+ liveRateInfo: data,
335
+ liveQuoteSnapshot: undefined, // Quote is created at submit time only
336
+ liveRateUnavailable: false,
337
+ liveRateError: undefined,
338
+ });
339
+ // Clear loading state after rate fetch - but only if this was a currency switch
340
+ // and no discounts need recalculation (recalculatePromotion will clear it otherwise)
341
+ if (isCurrencySwitchRef.current && !(state.checkoutSession as any)?.discounts?.length) {
342
+ setState({ isRateLoading: false });
343
+ isCurrencySwitchRef.current = false;
344
+ }
345
+ scheduleNext();
346
+ return;
347
+ } catch (err: any) {
348
+ lastError = err;
349
+ quickRetryCount++;
350
+ if (quickRetryCount <= MAX_QUICK_RETRIES && !cancelled) {
351
+ // eslint-disable-next-line no-console
352
+ console.log(
353
+ `[Exchange Rate] Quick retry ${quickRetryCount}/${MAX_QUICK_RETRIES} after ${QUICK_RETRY_DELAY}ms`
354
+ );
355
+ // eslint-disable-next-line no-await-in-loop
356
+ await new Promise((resolve) => {
357
+ setTimeout(resolve, QUICK_RETRY_DELAY);
358
+ });
359
+ }
360
+ }
361
+ }
362
+
363
+ // All quick retries failed
364
+ if (cancelled) {
365
+ return;
366
+ }
367
+ consecutiveFailures++;
368
+ const technicalError = lastError?.response?.data?.error || formatError(lastError);
369
+ console.error('[Exchange Rate Fetch Error]', {
370
+ error: technicalError,
371
+ consecutiveFailures,
372
+ sessionId: state.checkoutSession?.id,
373
+ currencyId,
374
+ });
175
375
 
176
- const recalculatePromotion = () => {
376
+ setState({
377
+ liveRateUnavailable: true,
378
+ liveRateError: undefined,
379
+ });
380
+
381
+ if (consecutiveFailures >= 3) {
382
+ console.warn('Exchange rate fetch failed multiple times', { consecutiveFailures, technicalError });
383
+ }
384
+
385
+ const nextInterval = Math.min(baseInterval * 2 ** (consecutiveFailures - 1), MAX_INTERVAL);
386
+ currentInterval = nextInterval;
387
+ scheduleNext();
388
+ } finally {
389
+ liveRateRefreshRef.current = false;
390
+ }
391
+ };
392
+
393
+ // Expose refresh function for manual retry
394
+ refreshRateRef.current = async () => {
395
+ liveRateRefreshRef.current = false; // Allow immediate retry
396
+ await fetchRate(true);
397
+ };
398
+
399
+ fetchRate(false);
400
+
401
+ const handleVisibilityChange = () => {
402
+ if (!document.hidden) {
403
+ fetchRate(false);
404
+ }
405
+ };
406
+ document.addEventListener('visibilitychange', handleVisibilityChange);
407
+
408
+ return () => {
409
+ cancelled = true;
410
+ refreshRateRef.current = null;
411
+ if (timer) {
412
+ clearInterval(timer);
413
+ }
414
+ document.removeEventListener('visibilitychange', handleVisibilityChange);
415
+ };
416
+ // eslint-disable-next-line react-hooks/exhaustive-deps
417
+ }, [state.checkoutSession?.id, currencyId, completed, needsExchangeRate]);
418
+
419
+ const recalculatePromotion = async () => {
177
420
  if ((state.checkoutSession as any)?.discounts?.length) {
178
- api
179
- .post(`/api/checkout-sessions/${state.checkoutSession.id}/recalculate-promotion`, {
421
+ try {
422
+ await api.post(`/api/checkout-sessions/${state.checkoutSession.id}/recalculate-promotion`, {
180
423
  currency_id: currencyId,
181
- })
182
- .then(() => {
183
- onPromotionUpdate();
184
424
  });
425
+ // Wait for session update before clearing loading state
426
+ await onPromotionUpdate();
427
+ } catch (err) {
428
+ console.error('[recalculatePromotion] Error:', err);
429
+ } finally {
430
+ // Clear loading state after promotion recalculation AND session update complete
431
+ if (isCurrencySwitchRef.current) {
432
+ setState({ isRateLoading: false });
433
+ isCurrencySwitchRef.current = false;
434
+ }
435
+ }
436
+ } else if (isCurrencySwitchRef.current) {
437
+ // No discounts to recalculate - clear loading immediately
438
+ setState({ isRateLoading: false });
439
+ isCurrencySwitchRef.current = false;
185
440
  }
186
441
  };
187
442
 
@@ -239,12 +494,18 @@ function PaymentInner({
239
494
  const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/adjust-quantity`, {
240
495
  itemId,
241
496
  quantity,
497
+ currency_id: currencyId,
242
498
  });
243
499
  if (data.discounts?.length) {
244
500
  recalculatePromotion();
245
501
  return;
246
502
  }
247
- setState({ checkoutSession: data });
503
+ setState({
504
+ checkoutSession: data,
505
+ ...(data.rateUnavailable !== undefined && { rateUnavailable: data.rateUnavailable }),
506
+ ...(data.rateError !== undefined && { rateError: data.rateError }),
507
+ ...(data.quotes !== undefined && { quotes: data.quotes }),
508
+ });
248
509
  } catch (err) {
249
510
  console.error(err);
250
511
  Toast.error(formatError(err));
@@ -297,6 +558,52 @@ function PaymentInner({
297
558
  onPaid(result);
298
559
  };
299
560
 
561
+ const handleQuoteUpdated = (payload: {
562
+ checkoutSession: TCheckoutSessionExpanded;
563
+ quotes?: any;
564
+ rateUnavailable?: boolean;
565
+ rateError?: string;
566
+ paymentIntent?: TPaymentIntent | null;
567
+ }) => {
568
+ setState({
569
+ checkoutSession: payload.checkoutSession,
570
+ ...(payload.quotes !== undefined && { quotes: payload.quotes }),
571
+ ...(payload.rateUnavailable !== undefined && { rateUnavailable: payload.rateUnavailable }),
572
+ ...(payload.rateError !== undefined && { rateError: payload.rateError }),
573
+ ...(payload.paymentIntent !== undefined && { paymentIntent: payload.paymentIntent }),
574
+ });
575
+ };
576
+
577
+ const handlePaymentIntentUpdate = (intent: TPaymentIntent | null) => {
578
+ setState({ paymentIntent: intent });
579
+ };
580
+
581
+ const quoteRefreshRef = useRef(false);
582
+ const liveRateRefreshRef = useRef(false);
583
+ const handleQuoteExpired = async (forceRefresh = false) => {
584
+ if (quoteRefreshRef.current) {
585
+ return;
586
+ }
587
+ quoteRefreshRef.current = true;
588
+ try {
589
+ const { data } = await api.get(`/api/checkout-sessions/retrieve/${state.checkoutSession.id}`, {
590
+ params: forceRefresh ? { forceRefresh: '1' } : undefined,
591
+ });
592
+ handleQuoteUpdated({
593
+ checkoutSession: data.checkoutSession,
594
+ quotes: data.quotes,
595
+ rateUnavailable: data.rateUnavailable,
596
+ rateError: data.rateError,
597
+ paymentIntent: data.paymentIntent,
598
+ });
599
+ } catch (err) {
600
+ console.error(err);
601
+ Toast.error(formatError(err));
602
+ } finally {
603
+ quoteRefreshRef.current = false;
604
+ }
605
+ };
606
+
300
607
  // trialing can be limited with trial_currency, which can be a list
301
608
  let trialInDays = Number(state.checkoutSession?.subscription_data?.trial_period_days || 0);
302
609
  let trialEnd = Number(state.checkoutSession?.subscription_data?.trial_end || 0);
@@ -328,6 +635,7 @@ function PaymentInner({
328
635
  )}
329
636
  showStaking={showStaking(method, currency, !!state.checkoutSession.subscription_data?.no_stake)}
330
637
  currency={currency}
638
+ paymentIntent={state.paymentIntent || paymentIntent}
331
639
  onUpsell={onUpsell}
332
640
  onDownsell={onDownsell}
333
641
  onQuantityChange={onQuantityChange}
@@ -343,6 +651,28 @@ function PaymentInner({
343
651
  onPromotionUpdate={onPromotionUpdate}
344
652
  paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
345
653
  showFeatures={showFeatures}
654
+ rateUnavailable={effectiveRateUnavailable}
655
+ isRateLoading={state.isRateLoading}
656
+ liveRate={state.liveRateInfo}
657
+ liveQuoteSnapshot={state.liveQuoteSnapshot}
658
+ onQuoteExpired={handleQuoteExpired}
659
+ onRefreshRate={refreshRateRef.current || undefined}
660
+ isStripePayment={isStripePayment}
661
+ onSlippageChange={async (slippageConfig) => {
662
+ try {
663
+ const { data } = await api.put(`/api/checkout-sessions/${state.checkoutSession.id}/slippage`, {
664
+ slippage_config: slippageConfig,
665
+ });
666
+ handleQuoteUpdated({
667
+ checkoutSession: data.checkoutSession || state.checkoutSession,
668
+ quotes: data.quotes,
669
+ rateUnavailable: data.rateUnavailable,
670
+ rateError: data.rateError,
671
+ });
672
+ } catch (err) {
673
+ console.error('Failed to update slippage', err);
674
+ }
675
+ }}
346
676
  />
347
677
  {mode === 'standalone' && !isMobile && (
348
678
  <CheckoutFooter className="cko-footer" sx={{ color: 'text.lighter' }} />
@@ -385,13 +715,16 @@ function PaymentInner({
385
715
  currencyId={currencyId}
386
716
  checkoutSession={state.checkoutSession}
387
717
  paymentMethods={paymentMethods as TPaymentMethodExpanded[]}
388
- paymentIntent={paymentIntent}
718
+ paymentIntent={state.paymentIntent || paymentIntent}
389
719
  paymentLink={paymentLink}
390
720
  customer={customer}
391
721
  onPaid={handlePaid}
392
722
  onError={onError}
723
+ onQuoteUpdated={handleQuoteUpdated}
724
+ onPaymentIntentUpdate={handlePaymentIntentUpdate}
393
725
  mode={mode}
394
726
  action={action}
727
+ rateUnavailable={effectiveRateUnavailable}
395
728
  />
396
729
  )}
397
730
  </Stack>
@@ -416,6 +749,9 @@ export default function Payment({
416
749
  goBack,
417
750
  action,
418
751
  showCheckoutSummary = true,
752
+ quotes,
753
+ rateUnavailable,
754
+ rateError,
419
755
  }: Props) {
420
756
  const { t } = useLocaleContext();
421
757
  const { refresh, livemode, setLivemode } = usePaymentContext();
@@ -492,6 +828,9 @@ export default function Payment({
492
828
  mode={mode}
493
829
  action={action}
494
830
  showCheckoutSummary={showCheckoutSummary}
831
+ quotes={quotes}
832
+ rateUnavailable={rateUnavailable}
833
+ rateError={rateError}
495
834
  />
496
835
  );
497
836
  };