@blocklet/payment-react 1.24.4 → 1.25.1

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 (98) 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/invoice/list.js +125 -15
  18. package/es/hooks/dynamic-pricing.d.ts +102 -0
  19. package/es/hooks/dynamic-pricing.js +393 -0
  20. package/es/index.d.ts +6 -1
  21. package/es/index.js +9 -1
  22. package/es/libs/util.d.ts +42 -5
  23. package/es/libs/util.js +345 -57
  24. package/es/locales/en.js +114 -3
  25. package/es/locales/zh.js +114 -3
  26. package/es/payment/form/index.d.ts +4 -1
  27. package/es/payment/form/index.js +454 -22
  28. package/es/payment/index.d.ts +1 -1
  29. package/es/payment/index.js +279 -16
  30. package/es/payment/product-item.d.ts +26 -1
  31. package/es/payment/product-item.js +330 -51
  32. package/es/payment/summary-section/promotion-section.d.ts +32 -0
  33. package/es/payment/summary-section/promotion-section.js +143 -0
  34. package/es/payment/summary-section/total-section.d.ts +39 -0
  35. package/es/payment/summary-section/total-section.js +83 -0
  36. package/es/payment/summary.d.ts +17 -2
  37. package/es/payment/summary.js +300 -253
  38. package/es/types/index.d.ts +11 -0
  39. package/lib/components/auto-topup/modal.d.ts +2 -0
  40. package/lib/components/auto-topup/modal.js +54 -6
  41. package/lib/components/auto-topup/product-card.d.ts +16 -1
  42. package/lib/components/auto-topup/product-card.js +75 -7
  43. package/lib/components/dynamic-pricing-unavailable.d.ts +9 -0
  44. package/lib/components/dynamic-pricing-unavailable.js +81 -0
  45. package/lib/components/loading-amount.d.ts +17 -0
  46. package/lib/components/loading-amount.js +53 -0
  47. package/lib/components/price-change-confirm.d.ts +18 -0
  48. package/lib/components/price-change-confirm.js +157 -0
  49. package/lib/components/quote-details-panel.d.ts +21 -0
  50. package/lib/components/quote-details-panel.js +226 -0
  51. package/lib/components/quote-lock-banner.d.ts +7 -0
  52. package/lib/components/quote-lock-banner.js +93 -0
  53. package/lib/components/slippage-config.d.ts +20 -0
  54. package/lib/components/slippage-config.js +316 -0
  55. package/lib/history/invoice/list.js +167 -27
  56. package/lib/hooks/dynamic-pricing.d.ts +102 -0
  57. package/lib/hooks/dynamic-pricing.js +390 -0
  58. package/lib/index.d.ts +6 -1
  59. package/lib/index.js +32 -0
  60. package/lib/libs/util.d.ts +42 -5
  61. package/lib/libs/util.js +367 -49
  62. package/lib/locales/en.js +114 -3
  63. package/lib/locales/zh.js +114 -3
  64. package/lib/payment/form/index.d.ts +4 -1
  65. package/lib/payment/form/index.js +476 -20
  66. package/lib/payment/index.d.ts +1 -1
  67. package/lib/payment/index.js +308 -14
  68. package/lib/payment/product-item.d.ts +26 -1
  69. package/lib/payment/product-item.js +270 -35
  70. package/lib/payment/summary-section/promotion-section.d.ts +32 -0
  71. package/lib/payment/summary-section/promotion-section.js +133 -0
  72. package/lib/payment/summary-section/total-section.d.ts +39 -0
  73. package/lib/payment/summary-section/total-section.js +117 -0
  74. package/lib/payment/summary.d.ts +17 -2
  75. package/lib/payment/summary.js +205 -127
  76. package/lib/types/index.d.ts +11 -0
  77. package/package.json +3 -3
  78. package/src/components/auto-topup/modal.tsx +59 -6
  79. package/src/components/auto-topup/product-card.tsx +118 -11
  80. package/src/components/dynamic-pricing-unavailable.tsx +69 -0
  81. package/src/components/loading-amount.tsx +66 -0
  82. package/src/components/price-change-confirm.tsx +136 -0
  83. package/src/components/quote-details-panel.tsx +218 -0
  84. package/src/components/quote-lock-banner.tsx +99 -0
  85. package/src/components/slippage-config.tsx +336 -0
  86. package/src/history/invoice/list.tsx +143 -9
  87. package/src/hooks/dynamic-pricing.ts +617 -0
  88. package/src/index.ts +9 -0
  89. package/src/libs/util.ts +473 -58
  90. package/src/locales/en.tsx +117 -0
  91. package/src/locales/zh.tsx +111 -0
  92. package/src/payment/form/index.tsx +561 -19
  93. package/src/payment/index.tsx +349 -10
  94. package/src/payment/product-item.tsx +451 -37
  95. package/src/payment/summary-section/promotion-section.tsx +172 -0
  96. package/src/payment/summary-section/total-section.tsx +141 -0
  97. package/src/payment/summary.tsx +334 -192
  98. package/src/types/index.ts +15 -0
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ module.exports = TotalSection;
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ var _context = require("@arcblock/ux/lib/Locale/context");
9
+ var _material = require("@mui/material");
10
+ var _amount = _interopRequireDefault(require("../amount"));
11
+ var _quoteDetailsPanel = _interopRequireDefault(require("../../components/quote-details-panel"));
12
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
13
+ function TotalSection({
14
+ totalAmountText,
15
+ totalUsdDisplay,
16
+ currency,
17
+ hasDynamicPricing,
18
+ rateDisplay,
19
+ rateInfo,
20
+ quoteDetailRows,
21
+ currentSlippagePercent,
22
+ slippageConfig = void 0,
23
+ isPriceLocked,
24
+ isSubscription,
25
+ completed = false,
26
+ onSlippageChange = void 0,
27
+ isStripePayment = false,
28
+ thenInfo = "",
29
+ isRateLoading = false
30
+ }) {
31
+ const {
32
+ t
33
+ } = (0, _context.useLocaleContext)();
34
+ return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
35
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
36
+ sx: {
37
+ display: "flex",
38
+ justifyContent: "space-between",
39
+ flexDirection: "row",
40
+ alignItems: "flex-start",
41
+ width: "100%"
42
+ },
43
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
44
+ className: "base-label",
45
+ children: t("common.total")
46
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
47
+ sx: {
48
+ alignItems: "flex-end"
49
+ },
50
+ children: [isRateLoading ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Skeleton, {
51
+ variant: "text",
52
+ width: 100,
53
+ height: 24
54
+ }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_amount.default, {
55
+ amount: totalAmountText,
56
+ sx: {
57
+ fontSize: "16px"
58
+ }
59
+ }), hasDynamicPricing && !isStripePayment && !isRateLoading && (totalUsdDisplay ? /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
60
+ sx: {
61
+ fontSize: "0.7875rem",
62
+ color: "text.lighter"
63
+ },
64
+ children: ["\u2248 $", totalUsdDisplay]
65
+ }) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Tooltip, {
66
+ title: t("payment.checkout.quote.referenceUnavailable"),
67
+ placement: "top",
68
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
69
+ component: "span",
70
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
71
+ sx: {
72
+ fontSize: "0.7875rem",
73
+ color: "text.lighter"
74
+ },
75
+ children: "\u2248 \u2014"
76
+ })
77
+ })
78
+ })), hasDynamicPricing && !isStripePayment && /* @__PURE__ */(0, _jsxRuntime.jsx)(_quoteDetailsPanel.default, {
79
+ rateLine: rateDisplay ? t("payment.checkout.quote.rateLine", {
80
+ symbol: currency.symbol,
81
+ rate: rateDisplay
82
+ }) : "",
83
+ rows: quoteDetailRows,
84
+ isSubscription,
85
+ slippageValue: currentSlippagePercent,
86
+ slippageConfig,
87
+ onSlippageChange: !completed && onSlippageChange ? onSlippageChange : void 0,
88
+ exchangeRate: rateInfo.exchangeRate,
89
+ baseCurrency: rateInfo.baseCurrency,
90
+ disabled: isPriceLocked
91
+ })]
92
+ })]
93
+ }), thenInfo && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
94
+ sx: {
95
+ display: "flex",
96
+ justifyContent: "space-between",
97
+ flexDirection: "row",
98
+ alignItems: "flex-start",
99
+ width: "100%",
100
+ borderTop: "1px solid",
101
+ borderColor: "divider",
102
+ pt: 1,
103
+ mt: 1
104
+ },
105
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
106
+ className: "base-label",
107
+ children: t("common.nextCharge")
108
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
109
+ sx: {
110
+ fontSize: "16px",
111
+ color: "text.secondary"
112
+ },
113
+ children: thenInfo
114
+ })]
115
+ })]
116
+ });
117
+ }
@@ -1,4 +1,6 @@
1
- import type { DonationSettings, TLineItemExpanded, TPaymentCurrency, TCheckoutSession, TPaymentMethodExpanded } from '@blocklet/payment-types';
1
+ import type { DonationSettings, TCheckoutSession, TLineItemExpanded, TPaymentCurrency, TPaymentIntent, TPaymentMethodExpanded } from '@blocklet/payment-types';
2
+ import { type LiveRateInfo, type LiveQuoteSnapshot } from '../hooks/dynamic-pricing';
3
+ import type { SlippageConfigValue } from '../components/slippage-config';
2
4
  type Props = {
3
5
  items: TLineItemExpanded[];
4
6
  currency: TPaymentCurrency;
@@ -18,9 +20,22 @@ type Props = {
18
20
  action?: string;
19
21
  completed?: boolean;
20
22
  checkoutSession?: TCheckoutSession;
23
+ paymentIntent?: TPaymentIntent | null;
21
24
  onPromotionUpdate?: () => void;
22
25
  paymentMethods?: TPaymentMethodExpanded[];
23
26
  showFeatures?: boolean;
27
+ rateUnavailable?: boolean;
28
+ rateError?: string;
29
+ isRateLoading?: boolean;
30
+ onQuoteExpired?: (forceRefresh?: boolean) => void;
31
+ onRefreshRate?: () => Promise<void>;
32
+ onSlippageChange?: (slippageConfig: SlippageConfigValue) => void;
33
+ slippageConfig?: SlippageConfigValue;
34
+ liveRate?: LiveRateInfo;
35
+ liveQuoteSnapshot?: LiveQuoteSnapshot;
36
+ isStripePayment?: boolean;
37
+ isSubscription?: boolean;
24
38
  };
25
- export default function PaymentSummary({ items, currency, trialInDays, billingThreshold, onUpsell, onDownsell, onQuantityChange, onApplyCrossSell, onCancelCrossSell, onChangeAmount, checkoutSessionId, crossSellBehavior, showStaking, donationSettings, action, trialEnd, completed, checkoutSession, paymentMethods, onPromotionUpdate, showFeatures, ...rest }: Props): import("react").JSX.Element;
39
+ export default function PaymentSummary({ items, currency, trialInDays, billingThreshold, onUpsell, onDownsell, onQuantityChange, onApplyCrossSell, onCancelCrossSell, onChangeAmount, checkoutSessionId, crossSellBehavior, showStaking, donationSettings, action, trialEnd, completed, checkoutSession, paymentIntent, paymentMethods, onPromotionUpdate, showFeatures, rateUnavailable, isRateLoading, rateError: _rateError, // Technical errors are logged but not displayed to users
40
+ onQuoteExpired, onRefreshRate, onSlippageChange, slippageConfig: slippageConfigProp, liveRate, liveQuoteSnapshot, isStripePayment, isSubscription: isSubscriptionProp, ...rest }: Props): import("react").JSX.Element | null;
26
41
  export {};
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", {
6
6
  module.exports = PaymentSummary;
7
7
  var _jsxRuntime = require("react/jsx-runtime");
8
8
  var _context = require("@arcblock/ux/lib/Locale/context");
9
+ var _Toast = _interopRequireDefault(require("@arcblock/ux/lib/Toast"));
9
10
  var _iconsMaterial = require("@mui/icons-material");
10
11
  var _material = require("@mui/material");
11
12
  var _util = require("@ocap/util");
@@ -14,17 +15,20 @@ var _noop = _interopRequireDefault(require("lodash/noop"));
14
15
  var _useBus = _interopRequireDefault(require("use-bus"));
15
16
  var _ExpandMore = _interopRequireDefault(require("@mui/icons-material/ExpandMore"));
16
17
  var _styles = require("@mui/material/styles");
18
+ var _react = require("react");
17
19
  var _status = _interopRequireDefault(require("../components/status"));
18
20
  var _api = _interopRequireDefault(require("../libs/api"));
19
21
  var _util2 = require("../libs/util");
20
- var _amount = _interopRequireDefault(require("./amount"));
21
22
  var _productDonation = _interopRequireDefault(require("./product-donation"));
22
23
  var _productItem = _interopRequireDefault(require("./product-item"));
23
24
  var _livemode = _interopRequireDefault(require("../components/livemode"));
24
25
  var _payment = require("../contexts/payment");
25
26
  var _mobile = require("../hooks/mobile");
27
+ var _dynamicPricing = require("../hooks/dynamic-pricing");
26
28
  var _loadingButton = _interopRequireDefault(require("../components/loading-button"));
27
- var _promotionCode = _interopRequireDefault(require("../components/promotion-code"));
29
+ var _dynamicPricingUnavailable = _interopRequireDefault(require("../components/dynamic-pricing-unavailable"));
30
+ var _promotionSection = _interopRequireDefault(require("./summary-section/promotion-section"));
31
+ var _totalSection = _interopRequireDefault(require("./summary-section/total-section"));
28
32
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
29
33
  const ExpandMore = (0, _styles.styled)(props => {
30
34
  const {
@@ -103,9 +107,23 @@ function PaymentSummary({
103
107
  trialEnd = 0,
104
108
  completed = false,
105
109
  checkoutSession = void 0,
110
+ paymentIntent = void 0,
106
111
  paymentMethods = [],
107
112
  onPromotionUpdate = _noop.default,
108
113
  showFeatures = false,
114
+ rateUnavailable = false,
115
+ isRateLoading = false,
116
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
117
+ rateError: _rateError = void 0,
118
+ // Technical errors are logged but not displayed to users
119
+ onQuoteExpired = void 0,
120
+ onRefreshRate = void 0,
121
+ onSlippageChange = void 0,
122
+ slippageConfig: slippageConfigProp = void 0,
123
+ liveRate = void 0,
124
+ liveQuoteSnapshot = void 0,
125
+ isStripePayment = false,
126
+ isSubscription: isSubscriptionProp = void 0,
109
127
  ...rest
110
128
  }) {
111
129
  const {
@@ -132,19 +150,105 @@ function PaymentSummary({
132
150
  const allowPromotionCodes = !!checkoutSession?.allow_promotion_codes;
133
151
  const hasDiscounts = sessionDiscounts?.length > 0;
134
152
  const discountCurrency = paymentMethods && checkoutSession ? (0, _util2.findCurrency)(paymentMethods, hasDiscounts ? checkoutSession?.currency_id || currency.id : currency.id) || settings.settings?.baseCurrency : currency;
153
+ const slippageConfig = slippageConfigProp ?? checkoutSession?.metadata?.slippage;
154
+ const {
155
+ hasDynamicPricing,
156
+ isPriceLocked,
157
+ quoteMeta,
158
+ rateInfo,
159
+ quoteLockedAt,
160
+ currentSlippagePercent,
161
+ rateDisplay,
162
+ calculatedTokenAmount,
163
+ calculatedDiscountAmount,
164
+ calculateUsdDisplay,
165
+ buildQuoteDetailRows
166
+ } = (0, _dynamicPricing.useDynamicPricing)({
167
+ items,
168
+ currency: discountCurrency,
169
+ liveRate,
170
+ liveQuoteSnapshot,
171
+ checkoutSession,
172
+ paymentIntent,
173
+ locale,
174
+ isStripePayment,
175
+ isSubscription: isSubscriptionProp,
176
+ slippageConfig,
177
+ trialInDays,
178
+ trialEnd,
179
+ discounts: sessionDiscounts
180
+ });
135
181
  const headlines = (0, _util2.formatCheckoutHeadlines)(items, discountCurrency, {
136
182
  trialEnd,
137
183
  trialInDays
138
- }, locale);
184
+ }, locale, {
185
+ exchangeRate: rateInfo.exchangeRate
186
+ });
139
187
  const staking = showStaking ? getStakingSetup(items, discountCurrency, billingThreshold) : "0";
140
- const getAppliedPromotionCodes = () => {
141
- if (!sessionDiscounts?.length) return [];
142
- return sessionDiscounts.map(discount => ({
143
- id: discount.promotion_code || discount.coupon,
144
- code: discount.verification_data?.code || "APPLIED",
145
- discount_amount: discount.discount_amount
146
- }));
147
- };
188
+ const effectiveHasDynamicPricing = hasDynamicPricing && !isStripePayment;
189
+ const hasRecurringItems = items.some(x => (x.upsell_price || x.price)?.type === "recurring");
190
+ const isTrialScenario = headlines.actualAmount === "0" && hasRecurringItems;
191
+ const headlineAmountDisplay = (0, _react.useMemo)(() => {
192
+ if (!effectiveHasDynamicPricing) {
193
+ return headlines.amount;
194
+ }
195
+ if (isTrialScenario || !headlines.amount.includes(discountCurrency.symbol)) {
196
+ return headlines.amount;
197
+ }
198
+ if (calculatedTokenAmount) {
199
+ const displayAmount = (0, _util.fromUnitToToken)(calculatedTokenAmount, discountCurrency?.decimal);
200
+ const formatted2 = (0, _util2.formatDynamicPrice)(displayAmount, true, 6);
201
+ return `${formatted2} ${discountCurrency.symbol}`;
202
+ }
203
+ const formatted = (0, _util2.formatDynamicPrice)(headlines.actualAmount, true, 6);
204
+ return `${formatted} ${discountCurrency.symbol}`;
205
+ }, [headlines.amount, headlines.actualAmount, discountCurrency.symbol, discountCurrency?.decimal, effectiveHasDynamicPricing, calculatedTokenAmount, isTrialScenario]);
206
+ const discountAmount = (0, _react.useMemo)(() => new _util.BN(checkoutSession?.total_details?.amount_discount || "0"), [checkoutSession?.total_details?.amount_discount]);
207
+ const subtotalAmountUnit = new _util.BN((0, _util.fromTokenToUnit)(headlines.actualAmount, discountCurrency?.decimal)).add(new _util.BN(staking)).toString();
208
+ const subtotalAmount = (0, _util.fromUnitToToken)(subtotalAmountUnit, discountCurrency?.decimal);
209
+ const totalAmountUnit = new _util.BN(subtotalAmountUnit).sub(discountAmount).toString();
210
+ const totalAmountValue = (0, _util.fromUnitToToken)(totalAmountUnit, discountCurrency?.decimal);
211
+ const subtotalDisplay = (0, _react.useMemo)(() => {
212
+ if (effectiveHasDynamicPricing && calculatedTokenAmount && !isTrialScenario) {
213
+ const dynamicSubtotalUnit = new _util.BN(calculatedTokenAmount).add(new _util.BN(staking)).toString();
214
+ const displayAmount = (0, _util.fromUnitToToken)(dynamicSubtotalUnit, discountCurrency?.decimal);
215
+ return (0, _util2.formatDynamicPrice)(displayAmount, true, 6);
216
+ }
217
+ return (0, _util2.formatDynamicPrice)(subtotalAmount, effectiveHasDynamicPricing, 6);
218
+ }, [effectiveHasDynamicPricing, calculatedTokenAmount, staking, discountCurrency?.decimal, subtotalAmount, isTrialScenario]);
219
+ const totalAmountDisplay = (0, _react.useMemo)(() => {
220
+ if (effectiveHasDynamicPricing && calculatedTokenAmount && !isTrialScenario) {
221
+ const effectiveDiscount = calculatedDiscountAmount ? new _util.BN(calculatedDiscountAmount) : discountAmount;
222
+ const dynamicTotalUnit = new _util.BN(calculatedTokenAmount).add(new _util.BN(staking)).sub(effectiveDiscount).toString();
223
+ const displayAmount = (0, _util.fromUnitToToken)(dynamicTotalUnit, discountCurrency?.decimal);
224
+ const numericValue = Number(displayAmount);
225
+ if (Number.isFinite(numericValue) && numericValue >= 0) {
226
+ return (0, _util2.formatDynamicPrice)(displayAmount, true, 6);
227
+ }
228
+ }
229
+ if (isStripePayment && calculatedDiscountAmount && !isTrialScenario) {
230
+ const effectiveDiscount = new _util.BN(calculatedDiscountAmount);
231
+ const adjustedTotalUnit = new _util.BN(subtotalAmountUnit).sub(effectiveDiscount).toString();
232
+ const displayAmount = (0, _util.fromUnitToToken)(adjustedTotalUnit, discountCurrency?.decimal);
233
+ const numericValue = Number(displayAmount);
234
+ if (Number.isFinite(numericValue) && numericValue >= 0) {
235
+ return (0, _util2.formatDynamicPrice)(displayAmount, false, 6);
236
+ }
237
+ }
238
+ return (0, _util2.formatDynamicPrice)(totalAmountValue, effectiveHasDynamicPricing, 6);
239
+ }, [effectiveHasDynamicPricing, calculatedTokenAmount, staking, discountAmount, calculatedDiscountAmount, discountCurrency?.decimal, totalAmountValue, isTrialScenario, isStripePayment, subtotalAmountUnit]);
240
+ const totalAmountText = totalAmountDisplay === "\u2014" ? "\u2014" : `${totalAmountDisplay} ${discountCurrency.symbol}`;
241
+ const totalUsdDisplay = (0, _react.useMemo)(() => {
242
+ if (effectiveHasDynamicPricing && calculatedTokenAmount && !isTrialScenario) {
243
+ const effectiveDiscount = calculatedDiscountAmount ? new _util.BN(calculatedDiscountAmount) : discountAmount;
244
+ const dynamicTotalUnit = new _util.BN(calculatedTokenAmount).add(new _util.BN(staking)).sub(effectiveDiscount).toString();
245
+ const dynamicTotalToken = (0, _util.fromUnitToToken)(dynamicTotalUnit, discountCurrency?.decimal);
246
+ return calculateUsdDisplay(dynamicTotalToken);
247
+ }
248
+ return calculateUsdDisplay(totalAmountValue);
249
+ }, [effectiveHasDynamicPricing, calculatedTokenAmount, staking, discountAmount, calculatedDiscountAmount, discountCurrency?.decimal, totalAmountValue, calculateUsdDisplay, isTrialScenario]);
250
+ const quoteDetailRows = buildQuoteDetailRows(t);
251
+ const isSubscription = isSubscriptionProp ?? (checkoutSession?.mode === "subscription" || checkoutSession?.mode === "setup");
148
252
  const handlePromotionUpdate = () => {
149
253
  onPromotionUpdate?.();
150
254
  };
@@ -159,9 +263,46 @@ function PaymentSummary({
159
263
  console.error("Failed to remove promotion code:", err);
160
264
  }
161
265
  };
162
- const discountAmount = new _util.BN(checkoutSession?.total_details?.amount_discount || "0");
163
- const subtotalAmount = (0, _util.fromUnitToToken)(new _util.BN((0, _util.fromTokenToUnit)(headlines.actualAmount, discountCurrency?.decimal)).add(new _util.BN(staking)).toString(), discountCurrency?.decimal);
164
- const totalAmount = (0, _util.fromUnitToToken)(new _util.BN((0, _util.fromTokenToUnit)(subtotalAmount, discountCurrency?.decimal)).sub(discountAmount).toString(), discountCurrency?.decimal);
266
+ const expiredHandledRef = (0, _react.useRef)(false);
267
+ (0, _react.useEffect)(() => {
268
+ if (completed || expiredHandledRef.current) {
269
+ return;
270
+ }
271
+ if (!liveQuoteSnapshot?.expires_at && !quoteMeta?.expiresAt) {
272
+ return;
273
+ }
274
+ const currentTime = Math.floor(Date.now() / 1e3);
275
+ const effectiveExpiresAt = liveQuoteSnapshot?.expires_at ?? quoteMeta?.expiresAt;
276
+ const quoteRemaining = effectiveExpiresAt ? Math.max(0, effectiveExpiresAt - currentTime) : 0;
277
+ const lockRemaining = quoteLockedAt ? Math.max(0, quoteLockedAt + 180 - currentTime) : 0;
278
+ const hasExpiry = !!effectiveExpiresAt;
279
+ const lockActive = lockRemaining > 0;
280
+ if (hasExpiry && !lockActive && quoteRemaining <= 0) {
281
+ expiredHandledRef.current = true;
282
+ onQuoteExpired?.();
283
+ }
284
+ }, [liveQuoteSnapshot?.expires_at, quoteMeta?.expiresAt, quoteLockedAt, completed, onQuoteExpired]);
285
+ const handleSlippageChange = async newSlippageConfig => {
286
+ if (!onSlippageChange) {
287
+ return;
288
+ }
289
+ if (!checkoutSessionId) {
290
+ onSlippageChange(newSlippageConfig);
291
+ return;
292
+ }
293
+ try {
294
+ await _api.default.put(`/api/checkout-sessions/${checkoutSessionId}/slippage`, {
295
+ slippage_config: newSlippageConfig
296
+ });
297
+ onSlippageChange(newSlippageConfig);
298
+ if (onQuoteExpired) {
299
+ await onQuoteExpired(true);
300
+ }
301
+ } catch (err) {
302
+ console.error("Failed to update slippage", err);
303
+ _Toast.default.error(err.response?.data?.error || (0, _util2.formatError)(err));
304
+ }
305
+ };
165
306
  (0, _useBus.default)("error.REQUIRE_CROSS_SELL", () => {
166
307
  setState({
167
308
  shake: true
@@ -226,7 +367,7 @@ function PaymentSummary({
226
367
  xs: 1,
227
368
  sm: 2
228
369
  },
229
- children: items.map(x => x.price.custom_unit_amount && onChangeAmount && donationSettings ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_productDonation.default, {
370
+ children: items.map(x => x.price?.custom_unit_amount && onChangeAmount && donationSettings ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_productDonation.default, {
230
371
  item: x,
231
372
  settings: donationSettings,
232
373
  onChange: onChangeAmount,
@@ -237,12 +378,18 @@ function PaymentSummary({
237
378
  trialInDays,
238
379
  trialEnd,
239
380
  currency: discountCurrency,
381
+ exchangeRate: rateInfo.exchangeRate,
382
+ isStripePayment,
383
+ isPriceLocked,
384
+ isRateLoading,
240
385
  onUpsell: handleUpsell,
241
386
  onDownsell: handleDownsell,
242
387
  adjustableQuantity: x.adjustable_quantity,
243
388
  completed,
244
389
  showFeatures,
245
390
  onQuantityChange: handleQuantityChange,
391
+ discounts: sessionDiscounts,
392
+ calculatedDiscountAmount: calculatedDiscountAmount || (isStripePayment ? discountAmount.toString() : null),
246
393
  children: x.cross_sell && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
247
394
  direction: "row",
248
395
  sx: {
@@ -279,6 +426,10 @@ function PaymentSummary({
279
426
  trialInDays,
280
427
  currency: discountCurrency,
281
428
  trialEnd,
429
+ exchangeRate: rateInfo.exchangeRate,
430
+ isStripePayment,
431
+ isPriceLocked,
432
+ isRateLoading,
282
433
  onUpsell: _noop.default,
283
434
  onDownsell: _noop.default,
284
435
  children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
@@ -312,6 +463,9 @@ function PaymentSummary({
312
463
  })
313
464
  })]
314
465
  });
466
+ if (!discountCurrency || !items?.length) {
467
+ return null;
468
+ }
315
469
  return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Fade, {
316
470
  in: true,
317
471
  children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
@@ -337,6 +491,11 @@ function PaymentSummary({
337
491
  },
338
492
  children: action || t("payment.checkout.orderSummary")
339
493
  }), !settings.livemode && /* @__PURE__ */(0, _jsxRuntime.jsx)(_livemode.default, {})]
494
+ }), effectiveHasDynamicPricing && rateUnavailable && /* @__PURE__ */(0, _jsxRuntime.jsx)(_dynamicPricingUnavailable.default, {
495
+ sx: {
496
+ mb: 2
497
+ },
498
+ onRetry: onRefreshRate
340
499
  }), isMobile && !donationSettings ? /* @__PURE__ */(0, _jsxRuntime.jsxs)(_jsxRuntime.Fragment, {
341
500
  children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
342
501
  onClick: () => setState({
@@ -403,7 +562,7 @@ function PaymentSummary({
403
562
  })
404
563
  })]
405
564
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
406
- children: headlines.amount
565
+ children: headlineAmountDisplay
407
566
  })]
408
567
  }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
409
568
  direction: "row",
@@ -457,122 +616,41 @@ function PaymentSummary({
457
616
  className: "base-label",
458
617
  children: t("common.subtotal")
459
618
  }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
460
- children: [(0, _util2.formatNumber)(subtotalAmount), " ", discountCurrency.symbol]
619
+ children: [subtotalDisplay, " ", discountCurrency.symbol]
461
620
  })]
462
- }), allowPromotionCodes && !hasDiscounts && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
463
- sx: {
464
- mt: 1
465
- },
466
- children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_promotionCode.default, {
467
- checkoutSessionId: checkoutSession.id,
468
- initialAppliedCodes: getAppliedPromotionCodes(),
469
- disabled: completed,
470
- onUpdate: handlePromotionUpdate,
471
- currencyId: currency.id
472
- })
473
- }), hasDiscounts && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
474
- children: sessionDiscounts.map(discount => {
475
- const promotionCodeInfo = discount.promotion_code_details;
476
- const couponInfo = discount.coupon_details;
477
- const discountDescription = couponInfo ? (0, _util2.formatCouponTerms)(couponInfo, discountCurrency, locale) : "";
478
- const notSupported = discountDescription === t("payment.checkout.coupon.noDiscount");
479
- return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
480
- children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
481
- direction: "row",
482
- spacing: 1,
483
- sx: {
484
- justifyContent: "space-between",
485
- alignItems: "center"
486
- },
487
- children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
488
- direction: "row",
489
- spacing: 1,
490
- sx: {
491
- alignItems: "center",
492
- backgroundColor: "grey.100",
493
- width: "fit-content",
494
- px: 1,
495
- py: 1,
496
- borderRadius: 1
497
- },
498
- children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
499
- sx: {
500
- fontWeight: "medium",
501
- display: "flex",
502
- alignItems: "center",
503
- gap: 0.5
504
- },
505
- children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.LocalOffer, {
506
- sx: {
507
- color: "warning.main",
508
- fontSize: "small"
509
- }
510
- }), promotionCodeInfo?.code || discount.verification_data?.code || t("payment.checkout.discount")]
511
- }), !completed && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
512
- size: "small",
513
- disabled: paymentState.paying || paymentState.stripePaying,
514
- onClick: () => handleRemovePromotion(checkoutSessionId),
515
- sx: {
516
- minWidth: "auto",
517
- width: 16,
518
- height: 16,
519
- color: "text.secondary",
520
- "&.Mui-disabled": {
521
- color: "text.disabled"
522
- }
523
- },
524
- children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.Close, {
525
- sx: {
526
- fontSize: 14
527
- }
528
- })
529
- })]
530
- }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
531
- sx: {
532
- color: "text.secondary"
533
- },
534
- children: ["-", (0, _util2.formatAmount)(discount.discount_amount || "0", discountCurrency.decimal), " ", discountCurrency.symbol]
535
- })]
536
- }), discountDescription && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
537
- sx: {
538
- fontSize: "small",
539
- color: notSupported ? "error.main" : "text.secondary",
540
- mt: 0.5
541
- },
542
- children: discountDescription
543
- })]
544
- }, discount.promotion_code || discount.coupon || `discount-${discount.discount_amount}`);
545
- })
621
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_promotionSection.default, {
622
+ checkoutSessionId: checkoutSession?.id || checkoutSessionId,
623
+ currency: discountCurrency,
624
+ currencyId: currency.id,
625
+ discounts: sessionDiscounts,
626
+ allowPromotionCodes,
627
+ completed,
628
+ disabled: paymentState.paying || paymentState.stripePaying,
629
+ onPromotionUpdate: handlePromotionUpdate,
630
+ onRemovePromotion: handleRemovePromotion,
631
+ calculatedDiscountAmount: calculatedDiscountAmount || (isStripePayment ? discountAmount.toString() : null),
632
+ isRateLoading
546
633
  }), hasSubTotal && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Divider, {
547
634
  sx: {
548
635
  my: 1
549
636
  }
550
- }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
551
- sx: {
552
- display: "flex",
553
- justifyContent: "space-between",
554
- flexDirection: "row",
555
- alignItems: "center",
556
- width: "100%"
557
- },
558
- children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
559
- className: "base-label",
560
- children: [t("common.total"), " "]
561
- }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_amount.default, {
562
- amount: `${totalAmount} ${discountCurrency.symbol}`,
563
- sx: {
564
- fontSize: "16px"
565
- }
566
- })]
567
- }), headlines.then && headlines.showThen && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
568
- component: "div",
569
- sx: {
570
- fontSize: "0.7875rem",
571
- color: "text.lighter",
572
- textAlign: "right",
573
- margin: "-2px 0 8px"
574
- },
575
- children: headlines.then
637
+ }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_totalSection.default, {
638
+ totalAmountText,
639
+ totalUsdDisplay,
640
+ currency: discountCurrency,
641
+ hasDynamicPricing,
642
+ rateDisplay,
643
+ rateInfo,
644
+ quoteDetailRows,
645
+ currentSlippagePercent,
646
+ slippageConfig,
647
+ isPriceLocked,
648
+ isSubscription,
649
+ completed,
650
+ onSlippageChange: onSlippageChange ? handleSlippageChange : void 0,
651
+ isStripePayment,
652
+ isRateLoading,
653
+ thenInfo: headlines.thenValue && headlines.showThen ? headlines.thenValue : void 0
576
654
  })]
577
655
  })
578
656
  });
@@ -11,6 +11,16 @@ export type CheckoutContext = {
11
11
  action?: string;
12
12
  showCheckoutSummary?: boolean;
13
13
  currencyId?: string;
14
+ quotes?: Record<string, {
15
+ quote_id: string;
16
+ expires_at: number;
17
+ quoted_amount: string;
18
+ exchange_rate?: string;
19
+ rate_provider_name?: string;
20
+ rate_provider_id?: string;
21
+ }>;
22
+ rateUnavailable?: boolean;
23
+ rateError?: string;
14
24
  };
15
25
  export type CheckoutFormData = {
16
26
  customer_name: string;
@@ -44,6 +54,7 @@ export type CheckoutCallbacks = {
44
54
  onError: (err: Error) => void;
45
55
  onChange?: (data: CheckoutFormData) => void;
46
56
  goBack?: () => void;
57
+ onRefreshQuote?: () => Promise<boolean>;
47
58
  };
48
59
  export type PricingRenderProps = {
49
60
  totalPrice: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@blocklet/payment-react",
3
- "version": "1.24.4",
3
+ "version": "1.25.1",
4
4
  "description": "Reusable react components for payment kit v2",
5
5
  "keywords": [
6
6
  "react",
@@ -96,7 +96,7 @@
96
96
  "@babel/core": "^7.27.4",
97
97
  "@babel/preset-env": "^7.27.2",
98
98
  "@babel/preset-react": "^7.27.1",
99
- "@blocklet/payment-types": "1.24.4",
99
+ "@blocklet/payment-types": "1.25.1",
100
100
  "@storybook/addon-essentials": "^7.6.20",
101
101
  "@storybook/addon-interactions": "^7.6.20",
102
102
  "@storybook/addon-links": "^7.6.20",
@@ -127,5 +127,5 @@
127
127
  "vite-plugin-babel": "^1.3.1",
128
128
  "vite-plugin-node-polyfills": "^0.23.0"
129
129
  },
130
- "gitHead": "d4a5f67e657cafa8862912bb8de38a3d56a7919d"
130
+ "gitHead": "87555132c62024e5c677fd0a39df0d90be06a543"
131
131
  }