@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
@@ -1,31 +1,35 @@
1
1
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useLocaleContext } from "@arcblock/ux/lib/Locale/context";
3
- import { HelpOutline, Close, LocalOffer } from "@mui/icons-material";
4
- import { Box, Divider, Fade, Grow, Stack, Tooltip, Typography, Collapse, IconButton, Button } from "@mui/material";
3
+ import Toast from "@arcblock/ux/lib/Toast";
4
+ import { HelpOutline } from "@mui/icons-material";
5
+ import { Box, Divider, Fade, Grow, Stack, Tooltip, Typography, Collapse, IconButton } from "@mui/material";
5
6
  import { BN, fromTokenToUnit, fromUnitToToken } from "@ocap/util";
6
7
  import { useRequest, useSetState } from "ahooks";
7
8
  import noop from "lodash/noop";
8
9
  import useBus from "use-bus";
9
10
  import ExpandMoreIcon from "@mui/icons-material/ExpandMore";
10
11
  import { styled } from "@mui/material/styles";
12
+ import { useEffect, useMemo, useRef } from "react";
11
13
  import Status from "../components/status.js";
12
14
  import api from "../libs/api.js";
13
15
  import {
14
16
  formatAmount,
15
17
  formatCheckoutHeadlines,
16
18
  getPriceUintAmountByCurrency,
17
- formatCouponTerms,
18
- formatNumber,
19
- findCurrency
19
+ formatDynamicPrice,
20
+ findCurrency,
21
+ formatError
20
22
  } from "../libs/util.js";
21
- import PaymentAmount from "./amount.js";
22
23
  import ProductDonation from "./product-donation.js";
23
24
  import ProductItem from "./product-item.js";
24
25
  import Livemode from "../components/livemode.js";
25
26
  import { usePaymentContext } from "../contexts/payment.js";
26
27
  import { useMobile } from "../hooks/mobile.js";
28
+ import { useDynamicPricing } from "../hooks/dynamic-pricing.js";
27
29
  import LoadingButton from "../components/loading-button.js";
28
- import PromotionCode from "../components/promotion-code.js";
30
+ import DynamicPricingUnavailable from "../components/dynamic-pricing-unavailable.js";
31
+ import PromotionSection from "./summary-section/promotion-section.js";
32
+ import TotalSection from "./summary-section/total-section.js";
29
33
  const ExpandMore = styled((props) => {
30
34
  const { expand, ...other } = props;
31
35
  return /* @__PURE__ */ jsx(IconButton, { ...other });
@@ -93,9 +97,23 @@ export default function PaymentSummary({
93
97
  trialEnd = 0,
94
98
  completed = false,
95
99
  checkoutSession = void 0,
100
+ paymentIntent = void 0,
96
101
  paymentMethods = [],
97
102
  onPromotionUpdate = noop,
98
103
  showFeatures = false,
104
+ rateUnavailable = false,
105
+ isRateLoading = false,
106
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
107
+ rateError: _rateError = void 0,
108
+ // Technical errors are logged but not displayed to users
109
+ onQuoteExpired = void 0,
110
+ onRefreshRate = void 0,
111
+ onSlippageChange = void 0,
112
+ slippageConfig: slippageConfigProp = void 0,
113
+ liveRate = void 0,
114
+ liveQuoteSnapshot = void 0,
115
+ isStripePayment = false,
116
+ isSubscription: isSubscriptionProp = void 0,
99
117
  ...rest
100
118
  }) {
101
119
  const { t, locale } = useLocaleContext();
@@ -112,16 +130,141 @@ export default function PaymentSummary({
112
130
  paymentMethods,
113
131
  hasDiscounts ? checkoutSession?.currency_id || currency.id : currency.id
114
132
  ) || settings.settings?.baseCurrency : currency;
115
- const headlines = formatCheckoutHeadlines(items, discountCurrency, { trialEnd, trialInDays }, locale);
133
+ const slippageConfig = slippageConfigProp ?? checkoutSession?.metadata?.slippage;
134
+ const {
135
+ hasDynamicPricing,
136
+ isPriceLocked,
137
+ quoteMeta,
138
+ rateInfo,
139
+ quoteLockedAt,
140
+ currentSlippagePercent,
141
+ rateDisplay,
142
+ calculatedTokenAmount,
143
+ calculatedDiscountAmount,
144
+ calculateUsdDisplay,
145
+ buildQuoteDetailRows
146
+ } = useDynamicPricing({
147
+ items,
148
+ currency: discountCurrency,
149
+ liveRate,
150
+ liveQuoteSnapshot,
151
+ checkoutSession,
152
+ paymentIntent,
153
+ locale,
154
+ isStripePayment,
155
+ isSubscription: isSubscriptionProp,
156
+ slippageConfig,
157
+ trialInDays,
158
+ trialEnd,
159
+ discounts: sessionDiscounts
160
+ });
161
+ const headlines = formatCheckoutHeadlines(items, discountCurrency, { trialEnd, trialInDays }, locale, {
162
+ exchangeRate: rateInfo.exchangeRate
163
+ });
116
164
  const staking = showStaking ? getStakingSetup(items, discountCurrency, billingThreshold) : "0";
117
- const getAppliedPromotionCodes = () => {
118
- if (!sessionDiscounts?.length) return [];
119
- return sessionDiscounts.map((discount) => ({
120
- id: discount.promotion_code || discount.coupon,
121
- code: discount.verification_data?.code || "APPLIED",
122
- discount_amount: discount.discount_amount
123
- }));
124
- };
165
+ const effectiveHasDynamicPricing = hasDynamicPricing && !isStripePayment;
166
+ const hasRecurringItems = items.some((x) => (x.upsell_price || x.price)?.type === "recurring");
167
+ const isTrialScenario = headlines.actualAmount === "0" && hasRecurringItems;
168
+ const headlineAmountDisplay = useMemo(() => {
169
+ if (!effectiveHasDynamicPricing) {
170
+ return headlines.amount;
171
+ }
172
+ if (isTrialScenario || !headlines.amount.includes(discountCurrency.symbol)) {
173
+ return headlines.amount;
174
+ }
175
+ if (calculatedTokenAmount) {
176
+ const displayAmount = fromUnitToToken(calculatedTokenAmount, discountCurrency?.decimal);
177
+ const formatted2 = formatDynamicPrice(displayAmount, true, 6);
178
+ return `${formatted2} ${discountCurrency.symbol}`;
179
+ }
180
+ const formatted = formatDynamicPrice(headlines.actualAmount, true, 6);
181
+ return `${formatted} ${discountCurrency.symbol}`;
182
+ }, [
183
+ headlines.amount,
184
+ headlines.actualAmount,
185
+ discountCurrency.symbol,
186
+ discountCurrency?.decimal,
187
+ effectiveHasDynamicPricing,
188
+ calculatedTokenAmount,
189
+ isTrialScenario
190
+ ]);
191
+ const discountAmount = useMemo(
192
+ () => new BN(checkoutSession?.total_details?.amount_discount || "0"),
193
+ [checkoutSession?.total_details?.amount_discount]
194
+ );
195
+ const subtotalAmountUnit = new BN(fromTokenToUnit(headlines.actualAmount, discountCurrency?.decimal)).add(new BN(staking)).toString();
196
+ const subtotalAmount = fromUnitToToken(subtotalAmountUnit, discountCurrency?.decimal);
197
+ const totalAmountUnit = new BN(subtotalAmountUnit).sub(discountAmount).toString();
198
+ const totalAmountValue = fromUnitToToken(totalAmountUnit, discountCurrency?.decimal);
199
+ const subtotalDisplay = useMemo(() => {
200
+ if (effectiveHasDynamicPricing && calculatedTokenAmount && !isTrialScenario) {
201
+ const dynamicSubtotalUnit = new BN(calculatedTokenAmount).add(new BN(staking)).toString();
202
+ const displayAmount = fromUnitToToken(dynamicSubtotalUnit, discountCurrency?.decimal);
203
+ return formatDynamicPrice(displayAmount, true, 6);
204
+ }
205
+ return formatDynamicPrice(subtotalAmount, effectiveHasDynamicPricing, 6);
206
+ }, [
207
+ effectiveHasDynamicPricing,
208
+ calculatedTokenAmount,
209
+ staking,
210
+ discountCurrency?.decimal,
211
+ subtotalAmount,
212
+ isTrialScenario
213
+ ]);
214
+ const totalAmountDisplay = useMemo(() => {
215
+ if (effectiveHasDynamicPricing && calculatedTokenAmount && !isTrialScenario) {
216
+ const effectiveDiscount = calculatedDiscountAmount ? new BN(calculatedDiscountAmount) : discountAmount;
217
+ const dynamicTotalUnit = new BN(calculatedTokenAmount).add(new BN(staking)).sub(effectiveDiscount).toString();
218
+ const displayAmount = fromUnitToToken(dynamicTotalUnit, discountCurrency?.decimal);
219
+ const numericValue = Number(displayAmount);
220
+ if (Number.isFinite(numericValue) && numericValue >= 0) {
221
+ return formatDynamicPrice(displayAmount, true, 6);
222
+ }
223
+ }
224
+ if (isStripePayment && calculatedDiscountAmount && !isTrialScenario) {
225
+ const effectiveDiscount = new BN(calculatedDiscountAmount);
226
+ const adjustedTotalUnit = new BN(subtotalAmountUnit).sub(effectiveDiscount).toString();
227
+ const displayAmount = fromUnitToToken(adjustedTotalUnit, discountCurrency?.decimal);
228
+ const numericValue = Number(displayAmount);
229
+ if (Number.isFinite(numericValue) && numericValue >= 0) {
230
+ return formatDynamicPrice(displayAmount, false, 6);
231
+ }
232
+ }
233
+ return formatDynamicPrice(totalAmountValue, effectiveHasDynamicPricing, 6);
234
+ }, [
235
+ effectiveHasDynamicPricing,
236
+ calculatedTokenAmount,
237
+ staking,
238
+ discountAmount,
239
+ calculatedDiscountAmount,
240
+ discountCurrency?.decimal,
241
+ totalAmountValue,
242
+ isTrialScenario,
243
+ isStripePayment,
244
+ subtotalAmountUnit
245
+ ]);
246
+ const totalAmountText = totalAmountDisplay === "\u2014" ? "\u2014" : `${totalAmountDisplay} ${discountCurrency.symbol}`;
247
+ const totalUsdDisplay = useMemo(() => {
248
+ if (effectiveHasDynamicPricing && calculatedTokenAmount && !isTrialScenario) {
249
+ const effectiveDiscount = calculatedDiscountAmount ? new BN(calculatedDiscountAmount) : discountAmount;
250
+ const dynamicTotalUnit = new BN(calculatedTokenAmount).add(new BN(staking)).sub(effectiveDiscount).toString();
251
+ const dynamicTotalToken = fromUnitToToken(dynamicTotalUnit, discountCurrency?.decimal);
252
+ return calculateUsdDisplay(dynamicTotalToken);
253
+ }
254
+ return calculateUsdDisplay(totalAmountValue);
255
+ }, [
256
+ effectiveHasDynamicPricing,
257
+ calculatedTokenAmount,
258
+ staking,
259
+ discountAmount,
260
+ calculatedDiscountAmount,
261
+ discountCurrency?.decimal,
262
+ totalAmountValue,
263
+ calculateUsdDisplay,
264
+ isTrialScenario
265
+ ]);
266
+ const quoteDetailRows = buildQuoteDetailRows(t);
267
+ const isSubscription = isSubscriptionProp ?? (checkoutSession?.mode === "subscription" || checkoutSession?.mode === "setup");
125
268
  const handlePromotionUpdate = () => {
126
269
  onPromotionUpdate?.();
127
270
  };
@@ -136,15 +279,46 @@ export default function PaymentSummary({
136
279
  console.error("Failed to remove promotion code:", err);
137
280
  }
138
281
  };
139
- const discountAmount = new BN(checkoutSession?.total_details?.amount_discount || "0");
140
- const subtotalAmount = fromUnitToToken(
141
- new BN(fromTokenToUnit(headlines.actualAmount, discountCurrency?.decimal)).add(new BN(staking)).toString(),
142
- discountCurrency?.decimal
143
- );
144
- const totalAmount = fromUnitToToken(
145
- new BN(fromTokenToUnit(subtotalAmount, discountCurrency?.decimal)).sub(discountAmount).toString(),
146
- discountCurrency?.decimal
147
- );
282
+ const expiredHandledRef = useRef(false);
283
+ useEffect(() => {
284
+ if (completed || expiredHandledRef.current) {
285
+ return;
286
+ }
287
+ if (!liveQuoteSnapshot?.expires_at && !quoteMeta?.expiresAt) {
288
+ return;
289
+ }
290
+ const currentTime = Math.floor(Date.now() / 1e3);
291
+ const effectiveExpiresAt = liveQuoteSnapshot?.expires_at ?? quoteMeta?.expiresAt;
292
+ const quoteRemaining = effectiveExpiresAt ? Math.max(0, effectiveExpiresAt - currentTime) : 0;
293
+ const lockRemaining = quoteLockedAt ? Math.max(0, quoteLockedAt + 180 - currentTime) : 0;
294
+ const hasExpiry = !!effectiveExpiresAt;
295
+ const lockActive = lockRemaining > 0;
296
+ if (hasExpiry && !lockActive && quoteRemaining <= 0) {
297
+ expiredHandledRef.current = true;
298
+ onQuoteExpired?.();
299
+ }
300
+ }, [liveQuoteSnapshot?.expires_at, quoteMeta?.expiresAt, quoteLockedAt, completed, onQuoteExpired]);
301
+ const handleSlippageChange = async (newSlippageConfig) => {
302
+ if (!onSlippageChange) {
303
+ return;
304
+ }
305
+ if (!checkoutSessionId) {
306
+ onSlippageChange(newSlippageConfig);
307
+ return;
308
+ }
309
+ try {
310
+ await api.put(`/api/checkout-sessions/${checkoutSessionId}/slippage`, {
311
+ slippage_config: newSlippageConfig
312
+ });
313
+ onSlippageChange(newSlippageConfig);
314
+ if (onQuoteExpired) {
315
+ await onQuoteExpired(true);
316
+ }
317
+ } catch (err) {
318
+ console.error("Failed to update slippage", err);
319
+ Toast.error(err.response?.data?.error || formatError(err));
320
+ }
321
+ };
148
322
  useBus(
149
323
  "error.REQUIRE_CROSS_SELL",
150
324
  () => {
@@ -200,7 +374,7 @@ export default function PaymentSummary({
200
374
  },
201
375
  children: [
202
376
  /* @__PURE__ */ jsx(Stack, { spacing: { xs: 1, sm: 2 }, children: items.map(
203
- (x) => x.price.custom_unit_amount && onChangeAmount && donationSettings ? /* @__PURE__ */ jsx(
377
+ (x) => x.price?.custom_unit_amount && onChangeAmount && donationSettings ? /* @__PURE__ */ jsx(
204
378
  ProductDonation,
205
379
  {
206
380
  item: x,
@@ -217,12 +391,18 @@ export default function PaymentSummary({
217
391
  trialInDays,
218
392
  trialEnd,
219
393
  currency: discountCurrency,
394
+ exchangeRate: rateInfo.exchangeRate,
395
+ isStripePayment,
396
+ isPriceLocked,
397
+ isRateLoading,
220
398
  onUpsell: handleUpsell,
221
399
  onDownsell: handleDownsell,
222
400
  adjustableQuantity: x.adjustable_quantity,
223
401
  completed,
224
402
  showFeatures,
225
403
  onQuantityChange: handleQuantityChange,
404
+ discounts: sessionDiscounts,
405
+ calculatedDiscountAmount: calculatedDiscountAmount || (isStripePayment ? discountAmount.toString() : null),
226
406
  children: x.cross_sell && /* @__PURE__ */ jsxs(
227
407
  Stack,
228
408
  {
@@ -254,56 +434,55 @@ export default function PaymentSummary({
254
434
  `${x.price_id}-${discountCurrency.id}`
255
435
  )
256
436
  ) }),
257
- data && items.some((x) => x.price_id === data.id) === false && /* @__PURE__ */ jsx(Grow, { in: true, children: /* @__PURE__ */ jsx(
258
- Stack,
437
+ data && items.some((x) => x.price_id === data.id) === false && /* @__PURE__ */ jsx(Grow, { in: true, children: /* @__PURE__ */ jsx(Stack, { sx: { mt: 1 }, children: /* @__PURE__ */ jsx(
438
+ ProductItem,
259
439
  {
260
- sx: {
261
- mt: 1
262
- },
263
- children: /* @__PURE__ */ jsx(
264
- ProductItem,
440
+ item: { quantity: 1, price: data, price_id: data.id, cross_sell: true },
441
+ items,
442
+ trialInDays,
443
+ currency: discountCurrency,
444
+ trialEnd,
445
+ exchangeRate: rateInfo.exchangeRate,
446
+ isStripePayment,
447
+ isPriceLocked,
448
+ isRateLoading,
449
+ onUpsell: noop,
450
+ onDownsell: noop,
451
+ children: /* @__PURE__ */ jsxs(
452
+ Stack,
265
453
  {
266
- item: { quantity: 1, price: data, price_id: data.id, cross_sell: true },
267
- items,
268
- trialInDays,
269
- currency: discountCurrency,
270
- trialEnd,
271
- onUpsell: noop,
272
- onDownsell: noop,
273
- children: /* @__PURE__ */ jsxs(
274
- Stack,
275
- {
276
- direction: "row",
277
- sx: {
278
- alignItems: "center",
279
- justifyContent: "space-between",
280
- width: 1
281
- },
282
- children: [
283
- /* @__PURE__ */ jsx(Typography, { children: crossSellBehavior === "required" && /* @__PURE__ */ jsx(Status, { label: t("payment.checkout.required"), color: "info", variant: "outlined", sx: { mr: 1 } }) }),
284
- /* @__PURE__ */ jsx(
285
- LoadingButton,
286
- {
287
- size: "small",
288
- loadingPosition: "end",
289
- endIcon: null,
290
- color: crossSellBehavior === "required" ? "info" : "info",
291
- variant: crossSellBehavior === "required" ? "text" : "text",
292
- loading: state.loading,
293
- onClick: handleApplyCrossSell,
294
- children: t("payment.checkout.cross_sell.add")
295
- }
296
- )
297
- ]
298
- }
299
- )
454
+ direction: "row",
455
+ sx: {
456
+ alignItems: "center",
457
+ justifyContent: "space-between",
458
+ width: 1
459
+ },
460
+ children: [
461
+ /* @__PURE__ */ jsx(Typography, { children: crossSellBehavior === "required" && /* @__PURE__ */ jsx(Status, { label: t("payment.checkout.required"), color: "info", variant: "outlined", sx: { mr: 1 } }) }),
462
+ /* @__PURE__ */ jsx(
463
+ LoadingButton,
464
+ {
465
+ size: "small",
466
+ loadingPosition: "end",
467
+ endIcon: null,
468
+ color: crossSellBehavior === "required" ? "info" : "info",
469
+ variant: crossSellBehavior === "required" ? "text" : "text",
470
+ loading: state.loading,
471
+ onClick: handleApplyCrossSell,
472
+ children: t("payment.checkout.cross_sell.add")
473
+ }
474
+ )
475
+ ]
300
476
  }
301
477
  )
302
478
  }
303
- ) })
479
+ ) }) })
304
480
  ]
305
481
  }
306
482
  );
483
+ if (!discountCurrency || !items?.length) {
484
+ return null;
485
+ }
307
486
  return /* @__PURE__ */ jsx(Fade, { in: true, children: /* @__PURE__ */ jsxs(Stack, { className: "cko-product", direction: "column", ...rest, children: [
308
487
  /* @__PURE__ */ jsxs(
309
488
  Box,
@@ -320,10 +499,7 @@ export default function PaymentSummary({
320
499
  title: t("payment.checkout.orderSummary"),
321
500
  sx: {
322
501
  color: "text.primary",
323
- fontSize: {
324
- xs: "18px",
325
- md: "24px"
326
- },
502
+ fontSize: { xs: "18px", md: "24px" },
327
503
  fontWeight: "700",
328
504
  lineHeight: "32px"
329
505
  },
@@ -334,6 +510,7 @@ export default function PaymentSummary({
334
510
  ]
335
511
  }
336
512
  ),
513
+ effectiveHasDynamicPricing && rateUnavailable && /* @__PURE__ */ jsx(DynamicPricingUnavailable, { sx: { mb: 2 }, onRetry: onRefreshRate }),
337
514
  isMobile && !donationSettings ? /* @__PURE__ */ jsxs(Fragment, { children: [
338
515
  /* @__PURE__ */ jsxs(
339
516
  Stack,
@@ -355,66 +532,24 @@ export default function PaymentSummary({
355
532
  ] }) : ProductCardList,
356
533
  /* @__PURE__ */ jsx(Divider, { sx: { mt: 2.5, mb: 2.5 } }),
357
534
  +staking > 0 && /* @__PURE__ */ jsxs(Stack, { spacing: 1, children: [
358
- /* @__PURE__ */ jsxs(
359
- Stack,
360
- {
361
- direction: "row",
362
- spacing: 1,
363
- sx: {
364
- justifyContent: "space-between",
365
- alignItems: "center"
366
- },
367
- children: [
368
- /* @__PURE__ */ jsxs(
369
- Stack,
370
- {
371
- direction: "row",
372
- spacing: 0.5,
373
- sx: {
374
- alignItems: "center"
375
- },
376
- children: [
377
- /* @__PURE__ */ jsx(Typography, { sx: { color: "text.secondary" }, children: t("payment.checkout.paymentRequired") }),
378
- /* @__PURE__ */ jsx(Tooltip, { title: t("payment.checkout.stakingConfirm"), placement: "top", sx: { maxWidth: "150px" }, children: /* @__PURE__ */ jsx(HelpOutline, { fontSize: "small", sx: { color: "text.lighter" } }) })
379
- ]
380
- }
381
- ),
382
- /* @__PURE__ */ jsx(Typography, { children: headlines.amount })
383
- ]
384
- }
385
- ),
386
- /* @__PURE__ */ jsxs(
387
- Stack,
388
- {
389
- direction: "row",
390
- spacing: 1,
391
- sx: {
392
- justifyContent: "space-between",
393
- alignItems: "center"
394
- },
395
- children: [
396
- /* @__PURE__ */ jsxs(
397
- Stack,
398
- {
399
- direction: "row",
400
- spacing: 0.5,
401
- sx: {
402
- alignItems: "center"
403
- },
404
- children: [
405
- /* @__PURE__ */ jsx(Typography, { sx: { color: "text.secondary" }, children: t("payment.checkout.staking.title") }),
406
- /* @__PURE__ */ jsx(Tooltip, { title: t("payment.checkout.staking.tooltip"), placement: "top", sx: { maxWidth: "150px" }, children: /* @__PURE__ */ jsx(HelpOutline, { fontSize: "small", sx: { color: "text.lighter" } }) })
407
- ]
408
- }
409
- ),
410
- /* @__PURE__ */ jsxs(Typography, { children: [
411
- formatAmount(staking, discountCurrency.decimal),
412
- " ",
413
- discountCurrency.symbol
414
- ] })
415
- ]
416
- }
417
- )
535
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { justifyContent: "space-between", alignItems: "center" }, children: [
536
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 0.5, sx: { alignItems: "center" }, children: [
537
+ /* @__PURE__ */ jsx(Typography, { sx: { color: "text.secondary" }, children: t("payment.checkout.paymentRequired") }),
538
+ /* @__PURE__ */ jsx(Tooltip, { title: t("payment.checkout.stakingConfirm"), placement: "top", sx: { maxWidth: "150px" }, children: /* @__PURE__ */ jsx(HelpOutline, { fontSize: "small", sx: { color: "text.lighter" } }) })
539
+ ] }),
540
+ /* @__PURE__ */ jsx(Typography, { children: headlineAmountDisplay })
541
+ ] }),
542
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 1, sx: { justifyContent: "space-between", alignItems: "center" }, children: [
543
+ /* @__PURE__ */ jsxs(Stack, { direction: "row", spacing: 0.5, sx: { alignItems: "center" }, children: [
544
+ /* @__PURE__ */ jsx(Typography, { sx: { color: "text.secondary" }, children: t("payment.checkout.staking.title") }),
545
+ /* @__PURE__ */ jsx(Tooltip, { title: t("payment.checkout.staking.tooltip"), placement: "top", sx: { maxWidth: "150px" }, children: /* @__PURE__ */ jsx(HelpOutline, { fontSize: "small", sx: { color: "text.lighter" } }) })
546
+ ] }),
547
+ /* @__PURE__ */ jsxs(Typography, { children: [
548
+ formatAmount(staking, discountCurrency.decimal),
549
+ " ",
550
+ discountCurrency.symbol
551
+ ] })
552
+ ] })
418
553
  ] }),
419
554
  (allowPromotionCodes || hasDiscounts) && /* @__PURE__ */ jsxs(
420
555
  Stack,
@@ -434,137 +569,49 @@ export default function PaymentSummary({
434
569
  children: [
435
570
  /* @__PURE__ */ jsx(Typography, { className: "base-label", children: t("common.subtotal") }),
436
571
  /* @__PURE__ */ jsxs(Typography, { children: [
437
- formatNumber(subtotalAmount),
572
+ subtotalDisplay,
438
573
  " ",
439
574
  discountCurrency.symbol
440
575
  ] })
441
576
  ]
442
577
  }
443
578
  ),
444
- allowPromotionCodes && !hasDiscounts && /* @__PURE__ */ jsx(Box, { sx: { mt: 1 }, children: /* @__PURE__ */ jsx(
445
- PromotionCode,
579
+ /* @__PURE__ */ jsx(
580
+ PromotionSection,
446
581
  {
447
- checkoutSessionId: checkoutSession.id,
448
- initialAppliedCodes: getAppliedPromotionCodes(),
449
- disabled: completed,
450
- onUpdate: handlePromotionUpdate,
451
- currencyId: currency.id
452
- }
453
- ) }),
454
- hasDiscounts && /* @__PURE__ */ jsx(Box, { children: sessionDiscounts.map((discount) => {
455
- const promotionCodeInfo = discount.promotion_code_details;
456
- const couponInfo = discount.coupon_details;
457
- const discountDescription = couponInfo ? formatCouponTerms(couponInfo, discountCurrency, locale) : "";
458
- const notSupported = discountDescription === t("payment.checkout.coupon.noDiscount");
459
- return /* @__PURE__ */ jsxs(Stack, { children: [
460
- /* @__PURE__ */ jsxs(
461
- Stack,
462
- {
463
- direction: "row",
464
- spacing: 1,
465
- sx: {
466
- justifyContent: "space-between",
467
- alignItems: "center"
468
- },
469
- children: [
470
- /* @__PURE__ */ jsxs(
471
- Stack,
472
- {
473
- direction: "row",
474
- spacing: 1,
475
- sx: {
476
- alignItems: "center",
477
- backgroundColor: "grey.100",
478
- width: "fit-content",
479
- px: 1,
480
- py: 1,
481
- borderRadius: 1
482
- },
483
- children: [
484
- /* @__PURE__ */ jsxs(
485
- Typography,
486
- {
487
- sx: {
488
- fontWeight: "medium",
489
- display: "flex",
490
- alignItems: "center",
491
- gap: 0.5
492
- },
493
- children: [
494
- /* @__PURE__ */ jsx(LocalOffer, { sx: { color: "warning.main", fontSize: "small" } }),
495
- promotionCodeInfo?.code || discount.verification_data?.code || t("payment.checkout.discount")
496
- ]
497
- }
498
- ),
499
- !completed && /* @__PURE__ */ jsx(
500
- Button,
501
- {
502
- size: "small",
503
- disabled: paymentState.paying || paymentState.stripePaying,
504
- onClick: () => handleRemovePromotion(checkoutSessionId),
505
- sx: {
506
- minWidth: "auto",
507
- width: 16,
508
- height: 16,
509
- color: "text.secondary",
510
- "&.Mui-disabled": {
511
- color: "text.disabled"
512
- }
513
- },
514
- children: /* @__PURE__ */ jsx(Close, { sx: { fontSize: 14 } })
515
- }
516
- )
517
- ]
518
- }
519
- ),
520
- /* @__PURE__ */ jsxs(Typography, { sx: { color: "text.secondary" }, children: [
521
- "-",
522
- formatAmount(discount.discount_amount || "0", discountCurrency.decimal),
523
- " ",
524
- discountCurrency.symbol
525
- ] })
526
- ]
527
- }
528
- ),
529
- discountDescription && /* @__PURE__ */ jsx(
530
- Typography,
531
- {
532
- sx: {
533
- fontSize: "small",
534
- color: notSupported ? "error.main" : "text.secondary",
535
- mt: 0.5
536
- },
537
- children: discountDescription
538
- }
539
- )
540
- ] }, discount.promotion_code || discount.coupon || `discount-${discount.discount_amount}`);
541
- }) }),
542
- hasSubTotal && /* @__PURE__ */ jsx(Divider, { sx: { my: 1 } }),
543
- /* @__PURE__ */ jsxs(
544
- Stack,
545
- {
546
- sx: {
547
- display: "flex",
548
- justifyContent: "space-between",
549
- flexDirection: "row",
550
- alignItems: "center",
551
- width: "100%"
552
- },
553
- children: [
554
- /* @__PURE__ */ jsxs(Box, { className: "base-label", children: [
555
- t("common.total"),
556
- " "
557
- ] }),
558
- /* @__PURE__ */ jsx(PaymentAmount, { amount: `${totalAmount} ${discountCurrency.symbol}`, sx: { fontSize: "16px" } })
559
- ]
582
+ checkoutSessionId: checkoutSession?.id || checkoutSessionId,
583
+ currency: discountCurrency,
584
+ currencyId: currency.id,
585
+ discounts: sessionDiscounts,
586
+ allowPromotionCodes,
587
+ completed,
588
+ disabled: paymentState.paying || paymentState.stripePaying,
589
+ onPromotionUpdate: handlePromotionUpdate,
590
+ onRemovePromotion: handleRemovePromotion,
591
+ calculatedDiscountAmount: calculatedDiscountAmount || (isStripePayment ? discountAmount.toString() : null),
592
+ isRateLoading
560
593
  }
561
594
  ),
562
- headlines.then && headlines.showThen && /* @__PURE__ */ jsx(
563
- Typography,
595
+ hasSubTotal && /* @__PURE__ */ jsx(Divider, { sx: { my: 1 } }),
596
+ /* @__PURE__ */ jsx(
597
+ TotalSection,
564
598
  {
565
- component: "div",
566
- sx: { fontSize: "0.7875rem", color: "text.lighter", textAlign: "right", margin: "-2px 0 8px" },
567
- children: headlines.then
599
+ totalAmountText,
600
+ totalUsdDisplay,
601
+ currency: discountCurrency,
602
+ hasDynamicPricing,
603
+ rateDisplay,
604
+ rateInfo,
605
+ quoteDetailRows,
606
+ currentSlippagePercent,
607
+ slippageConfig,
608
+ isPriceLocked,
609
+ isSubscription,
610
+ completed,
611
+ onSlippageChange: onSlippageChange ? handleSlippageChange : void 0,
612
+ isStripePayment,
613
+ isRateLoading,
614
+ thenInfo: headlines.thenValue && headlines.showThen ? headlines.thenValue : void 0
568
615
  }
569
616
  )
570
617
  ] }) });