@blocklet/payment-react 1.24.4 → 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 (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
@@ -11,6 +11,7 @@ var _iconsMaterial = require("@mui/icons-material");
11
11
  var _react = require("react");
12
12
  var _ahooks = require("ahooks");
13
13
  var _util = require("@ocap/util");
14
+ var _loadingAmount = _interopRequireDefault(require("../components/loading-amount"));
14
15
  var _status = _interopRequireDefault(require("../components/status"));
15
16
  var _switchButton = _interopRequireDefault(require("../components/switch-button"));
16
17
  var _util2 = require("../libs/util");
@@ -57,7 +58,13 @@ function ProductItem({
57
58
  enabled: false
58
59
  },
59
60
  onQuantityChange = () => {},
60
- showFeatures = false
61
+ showFeatures = false,
62
+ exchangeRate = null,
63
+ isStripePayment = false,
64
+ isPriceLocked = false,
65
+ isRateLoading = false,
66
+ discounts = [],
67
+ calculatedDiscountAmount = null
61
68
  }) {
62
69
  const {
63
70
  t,
@@ -69,13 +76,17 @@ function ProductItem({
69
76
  session,
70
77
  api
71
78
  } = (0, _payment.usePaymentContext)();
79
+ const pricingSource = item.upsell_price || item.price;
80
+ const isDynamicPricing = pricingSource?.pricing_type === "dynamic";
72
81
  const pricing = (0, _util2.formatLineItemPricing)(item, currency, {
73
82
  trialEnd,
74
- trialInDays
83
+ trialInDays,
84
+ exchangeRate
75
85
  }, locale);
76
86
  const saving = (0, _util2.formatUpsellSaving)(items, currency);
77
87
  const metered = item.price?.recurring?.usage_type === "metered" ? t("common.metered") : "";
78
88
  const canUpsell = mode === "normal" && items.length === 1;
89
+ const isTrial = trialInDays > 0 || trialEnd > (0, _dayjs.default)().unix();
79
90
  const isCreditProduct = item.price.product?.type === "credit" && item.price.metadata?.credit_config;
80
91
  const creditAmount = isCreditProduct && item.price.metadata?.credit_config?.credit_amount ? Number(item.price.metadata.credit_config.credit_amount) : 0;
81
92
  const creditCurrency = isCreditProduct && item.price.metadata?.credit_config?.currency_id ? (0, _util2.findCurrency)(settings.paymentMethods, item.price.metadata.credit_config.currency_id) : null;
@@ -107,7 +118,7 @@ function ProductItem({
107
118
  });
108
119
  const canAdjustQuantity = adjustableQuantity.enabled && mode === "normal";
109
120
  const minQuantity = Math.max(adjustableQuantity.minimum || 1, 1);
110
- const quantityAvailable = Math.min(item.price.quantity_limit_per_checkout, item.price.quantity_available);
121
+ const quantityAvailable = Math.min(item.price?.quantity_limit_per_checkout ?? Infinity, item.price?.quantity_available ?? Infinity);
111
122
  const maxQuantity = quantityAvailable ? Math.min(adjustableQuantity.maximum || Infinity, quantityAvailable) : adjustableQuantity.maximum || Infinity;
112
123
  const getMinQuantityForPending = (0, _react.useMemo)(() => {
113
124
  if (!isCreditProduct || !pendingAmount) return null;
@@ -180,7 +191,7 @@ function ProductItem({
180
191
  setLocalQuantity(newQuantity);
181
192
  onQuantityChange(item.price_id, newQuantity);
182
193
  if (userDid && newQuantity > 0) {
183
- saveUserQuantityPreference(userDid, item.price.id, newQuantity);
194
+ saveUserQuantityPreference(userDid, item.price?.id, newQuantity);
184
195
  }
185
196
  }
186
197
  };
@@ -206,7 +217,7 @@ function ProductItem({
206
217
  const currencySymbol = creditCurrency?.symbol || "Credits";
207
218
  const formattedTotalCredit = (0, _util2.formatCreditForCheckout)(totalCreditStr, currencySymbol, locale);
208
219
  const hasPendingAmount = pendingAmount && new _util.BN(pendingAmount || "0").gt(new _util.BN(0));
209
- const isRecurring = item.price.type === "recurring";
220
+ const isRecurring = item.price?.type === "recurring";
210
221
  const hasExpiry = validDuration && validDuration > 0;
211
222
  const buildBaseParams = () => ({
212
223
  amount: formattedTotalCredit,
@@ -215,7 +226,7 @@ function ProductItem({
215
226
  unit: t(`common.${validDurationUnit}`)
216
227
  }),
217
228
  ...(isRecurring && {
218
- period: (0, _util2.formatRecurring)(item.price.recurring, true, "per", locale)
229
+ period: (0, _util2.formatRecurring)(item.price?.recurring, true, "per", locale)
219
230
  })
220
231
  });
221
232
  const buildPendingParams = (pendingBN, availableAmount) => ({
@@ -229,7 +240,7 @@ function ProductItem({
229
240
  unit: t(`common.${validDurationUnit}`)
230
241
  }),
231
242
  ...(isRecurring && {
232
- period: (0, _util2.formatRecurring)(item.price.recurring, true, "per", locale)
243
+ period: (0, _util2.formatRecurring)(item.price?.recurring, true, "per", locale)
233
244
  })
234
245
  });
235
246
  const getLocaleKey = (category, type2) => {
@@ -254,6 +265,46 @@ function ProductItem({
254
265
  const type = isRecurring ? "recurringEnough" : "oneTimeEnough";
255
266
  return t(getLocaleKey("pending", type), buildPendingParams(pendingAmountBN, actualAvailable));
256
267
  };
268
+ const quantityForUsd = Number.isFinite(localQuantity) ? Number(localQuantity) : item.quantity || 0;
269
+ const dynamicTokenDisplay = (0, _react.useMemo)(() => {
270
+ if (isStripePayment || !currency) {
271
+ return null;
272
+ }
273
+ const baseAmount = pricingSource?.base_amount;
274
+ const unitAmount = pricingSource?.unit_amount;
275
+ if (isDynamicPricing && baseAmount !== void 0 && baseAmount !== null) {
276
+ if (isPriceLocked) {
277
+ const quotedAmount = item?.quoted_amount;
278
+ if (quotedAmount) {
279
+ const totalBN = new _util.BN(quotedAmount);
280
+ const tokenValue = (0, _util.fromUnitToToken)(totalBN, currency.decimal);
281
+ return `${(0, _util2.formatDynamicPrice)(tokenValue, true, 6)} ${currency.symbol}`;
282
+ }
283
+ }
284
+ if (!exchangeRate) {
285
+ return null;
286
+ }
287
+ const rate = Number(exchangeRate);
288
+ if (rate <= 0 || Number.isNaN(rate)) {
289
+ return null;
290
+ }
291
+ const usdAmount = Number(baseAmount);
292
+ const tokenAmount = usdAmount / rate;
293
+ const totalTokens = tokenAmount * (quantityForUsd || 1);
294
+ return `${(0, _util2.formatDynamicPrice)(totalTokens, true, 6)} ${currency.symbol}`;
295
+ }
296
+ if (!isDynamicPricing && exchangeRate && unitAmount) {
297
+ const rate = Number(exchangeRate);
298
+ if (rate <= 0 || Number.isNaN(rate)) {
299
+ return null;
300
+ }
301
+ const usdAmount = Number(unitAmount) / 100;
302
+ const tokenAmount = usdAmount / rate;
303
+ const totalTokens = tokenAmount * (quantityForUsd || 1);
304
+ return `${(0, _util2.formatDynamicPrice)(totalTokens, true, 6)} ${currency.symbol}`;
305
+ }
306
+ return null;
307
+ }, [isStripePayment, isDynamicPricing, currency, pricingSource, item, exchangeRate, quantityForUsd, isPriceLocked]);
257
308
  const formatScheduleInfo = () => {
258
309
  if (!hasSchedule || !scheduleConfig) return null;
259
310
  const totalCredit = creditAmount * (localQuantity || 0);
@@ -295,14 +346,189 @@ function ProductItem({
295
346
  const primaryText = (0, _react.useMemo)(() => {
296
347
  const price = item.upsell_price || item.price || {};
297
348
  const isRecurring = price?.type === "recurring" && price?.recurring;
298
- const trial = trialInDays > 0 || trialEnd > (0, _dayjs.default)().unix();
299
- if (isRecurring && !trial && price?.recurring?.usage_type !== "metered") {
300
- return `${pricing.primary} ${price.recurring ? (0, _util2.formatRecurring)(price.recurring, false, "slash", locale) : ""}`;
349
+ if (isTrial) {
350
+ return pricing.primary;
351
+ }
352
+ let displayAmount;
353
+ if (!isStripePayment && dynamicTokenDisplay) {
354
+ displayAmount = dynamicTokenDisplay;
355
+ } else if (isDynamicPricing && !isStripePayment) {
356
+ const baseAmount = pricingSource?.base_amount;
357
+ if (baseAmount !== void 0 && baseAmount !== null) {
358
+ const usdValue = Number(baseAmount);
359
+ displayAmount = `\u2248 $${usdValue.toFixed(2)}`;
360
+ } else {
361
+ displayAmount = pricing.primary;
362
+ }
363
+ } else {
364
+ displayAmount = pricing.primary;
365
+ }
366
+ if (isRecurring && price?.recurring?.usage_type !== "metered") {
367
+ return `${displayAmount} ${price.recurring ? (0, _util2.formatRecurring)(price.recurring, false, "slash", locale) : ""}`;
368
+ }
369
+ return displayAmount;
370
+ }, [isTrial, pricing, item, locale, dynamicTokenDisplay, isDynamicPricing, pricingSource, isStripePayment]);
371
+ const usdReference = (0, _react.useMemo)(() => {
372
+ if (!currency || !isDynamicPricing || isStripePayment) {
373
+ return null;
374
+ }
375
+ const baseAmount = pricingSource?.base_amount;
376
+ const hasBaseAmount = baseAmount !== void 0 && baseAmount !== null;
377
+ if (hasBaseAmount) {
378
+ return (0, _util2.getUsdAmountFromBaseAmount)(baseAmount, quantityForUsd);
379
+ }
380
+ return null;
381
+ }, [currency, pricingSource, quantityForUsd, isDynamicPricing, isStripePayment]);
382
+ const usdReferenceDisplay = (0, _react.useMemo)(() => (0, _util2.formatUsdAmount)(usdReference, locale), [usdReference, locale]);
383
+ const upsellTokenDisplay = (0, _react.useMemo)(() => {
384
+ const upsellPrice = item.price?.upsell?.upsells_to;
385
+ if (!upsellPrice) {
386
+ return null;
387
+ }
388
+ if (isStripePayment && currency) {
389
+ const baseAmount2 = upsellPrice?.base_amount;
390
+ const unitAmount2 = upsellPrice?.unit_amount;
391
+ let usdAmount2;
392
+ if (baseAmount2 !== void 0 && baseAmount2 !== null) {
393
+ usdAmount2 = Number(baseAmount2);
394
+ } else if (unitAmount2) {
395
+ usdAmount2 = Number(unitAmount2) / 100;
396
+ } else {
397
+ return null;
398
+ }
399
+ const recurring2 = upsellPrice?.recurring ? ` ${(0, _util2.formatRecurring)(upsellPrice.recurring, false, "slash", locale)}` : "";
400
+ return `${usdAmount2} ${currency.symbol}${recurring2}`;
401
+ }
402
+ if (!currency || !exchangeRate) {
403
+ return null;
404
+ }
405
+ const rate = Number(exchangeRate);
406
+ if (rate <= 0 || Number.isNaN(rate)) {
407
+ return null;
408
+ }
409
+ const baseAmount = upsellPrice?.base_amount;
410
+ const unitAmount = upsellPrice?.unit_amount;
411
+ let usdAmount;
412
+ if (baseAmount !== void 0 && baseAmount !== null) {
413
+ usdAmount = Number(baseAmount);
414
+ } else if (unitAmount) {
415
+ usdAmount = Number(unitAmount) / 100;
416
+ } else {
417
+ return null;
418
+ }
419
+ const tokenAmount = usdAmount / rate;
420
+ const recurring = upsellPrice?.recurring ? ` ${(0, _util2.formatRecurring)(upsellPrice.recurring, false, "slash", locale)}` : "";
421
+ return `${(0, _util2.formatDynamicPrice)(tokenAmount, true, 6)} ${currency.symbol}${recurring}`;
422
+ }, [isStripePayment, currency, exchangeRate, item.price?.upsell?.upsells_to, locale]);
423
+ const downsellTokenDisplay = (0, _react.useMemo)(() => {
424
+ if (!item.upsell_price_id || !item.price) {
425
+ return null;
426
+ }
427
+ const originalPrice = item.price;
428
+ if (isStripePayment && currency) {
429
+ const baseAmount2 = originalPrice?.base_amount;
430
+ const unitAmount2 = originalPrice?.unit_amount;
431
+ let usdAmount2;
432
+ if (baseAmount2 !== void 0 && baseAmount2 !== null) {
433
+ usdAmount2 = Number(baseAmount2);
434
+ } else if (unitAmount2) {
435
+ usdAmount2 = Number(unitAmount2) / 100;
436
+ } else {
437
+ return null;
438
+ }
439
+ const recurring2 = originalPrice?.recurring ? ` ${(0, _util2.formatRecurring)(originalPrice.recurring, false, "slash", locale)}` : "";
440
+ return `${usdAmount2} ${currency.symbol}${recurring2}`;
301
441
  }
302
- return pricing.primary;
303
- }, [trialInDays, trialEnd, pricing, item, locale]);
442
+ if (!currency || !exchangeRate) {
443
+ return null;
444
+ }
445
+ const rate = Number(exchangeRate);
446
+ if (rate <= 0 || Number.isNaN(rate)) {
447
+ return null;
448
+ }
449
+ const baseAmount = originalPrice?.base_amount;
450
+ const unitAmount = originalPrice?.unit_amount;
451
+ let usdAmount;
452
+ if (baseAmount !== void 0 && baseAmount !== null) {
453
+ usdAmount = Number(baseAmount);
454
+ } else if (unitAmount) {
455
+ usdAmount = Number(unitAmount) / 100;
456
+ } else {
457
+ return null;
458
+ }
459
+ const tokenAmount = usdAmount / rate;
460
+ const recurring = originalPrice?.recurring ? ` ${(0, _util2.formatRecurring)(originalPrice.recurring, false, "slash", locale)}` : "";
461
+ return `${(0, _util2.formatDynamicPrice)(tokenAmount, true, 6)} ${currency.symbol}${recurring}`;
462
+ }, [isStripePayment, currency, exchangeRate, item.price, item.upsell_price_id, locale]);
463
+ const displayItemDiscountAmount = (0, _react.useMemo)(() => {
464
+ if (isTrial && item.price?.type === "recurring" || item.price?.recurring?.usage_type === "metered") {
465
+ return null;
466
+ }
467
+ if (!discounts?.length) {
468
+ return null;
469
+ }
470
+ const couponDetails = discounts[0]?.coupon_details;
471
+ if (isStripePayment && couponDetails) {
472
+ if (!item.discountable) {
473
+ return null;
474
+ }
475
+ const isDynamic = pricingSource?.pricing_type === "dynamic";
476
+ const baseAmount = pricingSource?.base_amount;
477
+ const unitAmount = pricingSource?.unit_amount;
478
+ let amountCents;
479
+ if (isDynamic && baseAmount !== void 0 && baseAmount !== null) {
480
+ amountCents = Number(baseAmount) * 100;
481
+ } else if (unitAmount) {
482
+ amountCents = Number(unitAmount);
483
+ } else {
484
+ return null;
485
+ }
486
+ const quantity = localQuantity || item.quantity || 1;
487
+ const itemSubtotalCents = amountCents * quantity;
488
+ if (couponDetails.percent_off && couponDetails.percent_off > 0) {
489
+ const discountCents = Math.round(itemSubtotalCents * couponDetails.percent_off / 100);
490
+ return discountCents.toString();
491
+ }
492
+ if (couponDetails.amount_off && items.length === 1 && calculatedDiscountAmount) {
493
+ return calculatedDiscountAmount;
494
+ }
495
+ return null;
496
+ }
497
+ if (isDynamicPricing && exchangeRate && couponDetails) {
498
+ const rateNum = Number(exchangeRate);
499
+ if (rateNum <= 0) {
500
+ return null;
501
+ }
502
+ if (!item.discountable) {
503
+ return null;
504
+ }
505
+ const baseAmount = pricingSource?.base_amount;
506
+ if (!baseAmount) {
507
+ return null;
508
+ }
509
+ const quantity = localQuantity || item.quantity || 1;
510
+ const itemTokenAmount = Number(baseAmount) * quantity / rateNum;
511
+ if (couponDetails.percent_off && couponDetails.percent_off > 0) {
512
+ const discountAmount = itemTokenAmount * couponDetails.percent_off / 100;
513
+ const discountAmountUnit = (0, _util.fromTokenToUnit)(discountAmount.toFixed(currency.decimal || 8), currency.decimal || 8);
514
+ return discountAmountUnit.toString();
515
+ }
516
+ if (couponDetails.amount_off && items.length === 1 && calculatedDiscountAmount) {
517
+ return calculatedDiscountAmount;
518
+ }
519
+ }
520
+ if (items.length === 1 && calculatedDiscountAmount) {
521
+ return calculatedDiscountAmount;
522
+ }
523
+ return null;
524
+ }, [isTrial, discounts, isStripePayment, isDynamicPricing, exchangeRate, item, pricingSource, localQuantity, currency.decimal, items.length, calculatedDiscountAmount]);
525
+ const discountCodeDisplay = (0, _react.useMemo)(() => {
526
+ if (!discounts?.length) return null;
527
+ const discount = discounts[0];
528
+ return discount.promotion_code_details?.code || discount.verification_data?.code || "DISCOUNT";
529
+ }, [discounts]);
304
530
  const quantityInventoryError = (0, _util2.formatQuantityInventory)(item.price, localQuantityNum, locale);
305
- const features = item.price.product?.features || [];
531
+ const features = item.price?.product?.features || [];
306
532
  return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
307
533
  direction: "column",
308
534
  spacing: 1,
@@ -328,15 +554,15 @@ function ProductItem({
328
554
  width: "100%"
329
555
  },
330
556
  children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_productCard.default, {
331
- logo: item.price.product?.images[0],
332
- name: item.price.product?.name,
557
+ logo: item.price?.product?.images[0],
558
+ name: item.price?.product?.name,
333
559
  extra: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
334
560
  sx: {
335
561
  display: "flex",
336
562
  alignItems: "center"
337
563
  },
338
- children: item.price.type === "recurring" && item.price.recurring ? [pricing.quantity, t("common.billed", {
339
- rule: `${(0, _util2.formatRecurring)(item.upsell_price?.recurring || item.price.recurring, true, "per", locale)} ${metered}`
564
+ children: item.price?.type === "recurring" && item.price?.recurring ? [pricing.quantity, t("common.billed", {
565
+ rule: `${(0, _util2.formatRecurring)(item.upsell_price?.recurring || item.price?.recurring, true, "per", locale)} ${metered}`
340
566
  })].filter(Boolean).join(", ") : pricing.quantity
341
567
  })
342
568
  }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
@@ -345,15 +571,22 @@ function ProductItem({
345
571
  alignItems: "flex-end",
346
572
  flex: 1
347
573
  },
348
- children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
574
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_loadingAmount.default, {
575
+ value: primaryText,
576
+ loading: isRateLoading,
577
+ skeletonWidth: 80,
578
+ height: 20,
349
579
  sx: {
350
580
  color: "text.primary",
351
- fontWeight: 500,
352
- whiteSpace: "nowrap"
581
+ fontWeight: 500
582
+ }
583
+ }), isDynamicPricing && !isStripePayment && !isTrial && usdReferenceDisplay && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
584
+ sx: {
585
+ fontSize: "0.74375rem",
586
+ color: "text.lighter"
353
587
  },
354
- gutterBottom: true,
355
- children: primaryText
356
- }), pricing.secondary && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
588
+ children: ["\u2248 $", usdReferenceDisplay]
589
+ }), pricing.secondary && !isTrial && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
357
590
  sx: {
358
591
  fontSize: "0.74375rem",
359
592
  color: "text.lighter"
@@ -361,14 +594,16 @@ function ProductItem({
361
594
  children: pricing.secondary
362
595
  })]
363
596
  })]
364
- }), item.discount_amounts && item.discount_amounts.length > 0 && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
597
+ }), !(isTrial && item.price?.type === "recurring" || item.price?.recurring?.usage_type === "metered") && (displayItemDiscountAmount || item.discount_amounts && item.discount_amounts.length > 0) && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Stack, {
365
598
  direction: "row",
366
599
  spacing: 1,
367
600
  sx: {
368
601
  mt: 1,
369
- alignItems: "center"
602
+ alignItems: "center",
603
+ opacity: isRateLoading ? 0 : 1,
604
+ transition: "opacity 300ms ease-in-out"
370
605
  },
371
- children: item.discount_amounts.map(discountAmount => /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, {
606
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, {
372
607
  icon: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.LocalOffer, {
373
608
  sx: {
374
609
  fontSize: "0.8rem !important"
@@ -386,13 +621,13 @@ function ProductItem({
386
621
  fontSize: "0.75rem",
387
622
  fontWeight: "medium"
388
623
  },
389
- children: discountAmount.promotion_code?.code || "DISCOUNT"
624
+ children: discountCodeDisplay || "DISCOUNT"
390
625
  }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
391
626
  component: "span",
392
627
  sx: {
393
628
  fontSize: "0.75rem"
394
629
  },
395
- children: ["(-", (0, _util2.formatAmount)(discountAmount.amount || "0", currency.decimal), " ", currency.symbol, ")"]
630
+ children: ["(-", (0, _util2.formatAmount)(displayItemDiscountAmount || item.discount_amounts?.[0]?.amount || "0", currency.decimal), " ", currency.symbol, ")"]
396
631
  })]
397
632
  }),
398
633
  size: "small",
@@ -406,7 +641,7 @@ function ProductItem({
406
641
  px: 1
407
642
  }
408
643
  }
409
- }, discountAmount.promotion_code))
644
+ })
410
645
  }), showFeatures && features.length > 0 && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
411
646
  sx: {
412
647
  display: "flex",
@@ -540,7 +775,7 @@ function ProductItem({
540
775
  children: formatCreditInfo()
541
776
  })
542
777
  }), children]
543
- }), canUpsell && !item.upsell_price_id && item.price.upsell?.upsells_to && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
778
+ }), canUpsell && !item.upsell_price_id && item.price?.upsell?.upsells_to && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
544
779
  direction: "row",
545
780
  className: "product-item-upsell",
546
781
  sx: {
@@ -563,9 +798,9 @@ function ProductItem({
563
798
  },
564
799
  variant: "success",
565
800
  checked: false,
566
- onChange: () => onUpsell(item.price_id, item.price.upsell?.upsells_to_id)
801
+ onChange: () => onUpsell(item.price_id, item.price?.upsell?.upsells_to_id)
567
802
  }), t("payment.checkout.upsell.save", {
568
- recurring: (0, _util2.formatRecurring)(item.price.upsell.upsells_to.recurring, true, "per", locale)
803
+ recurring: (0, _util2.formatRecurring)(item.price?.upsell?.upsells_to?.recurring, true, "per", locale)
569
804
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, {
570
805
  label: t("payment.checkout.upsell.off", {
571
806
  saving
@@ -583,7 +818,7 @@ function ProductItem({
583
818
  sx: {
584
819
  fontSize: 12
585
820
  },
586
- children: (0, _util2.formatPrice)(item.price.upsell.upsells_to, currency, item.price.product?.unit_label, 1, true, locale)
821
+ children: upsellTokenDisplay || (0, _util2.formatPrice)(item.price?.upsell?.upsells_to, currency, item.price?.product?.unit_label, 1, true, locale)
587
822
  })]
588
823
  }), canUpsell && item.upsell_price_id && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
589
824
  direction: "row",
@@ -610,14 +845,14 @@ function ProductItem({
610
845
  checked: true,
611
846
  onChange: () => onDownsell(item.upsell_price_id)
612
847
  }), t("payment.checkout.upsell.revert", {
613
- recurring: t(`common.${(0, _util2.formatRecurring)(item.price.recurring)}`)
848
+ recurring: t(`common.${(0, _util2.formatRecurring)(item.price?.recurring)}`)
614
849
  })]
615
850
  }), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
616
851
  component: "span",
617
852
  sx: {
618
853
  fontSize: 12
619
854
  },
620
- children: (0, _util2.formatPrice)(item.price, currency, item.price.product?.unit_label, 1, true, locale)
855
+ children: downsellTokenDisplay || (0, _util2.formatPrice)(item.price, currency, item.price?.product?.unit_label, 1, true, locale)
621
856
  })]
622
857
  })]
623
858
  });
@@ -0,0 +1,32 @@
1
+ /**
2
+ * PromotionSection Component
3
+ *
4
+ * Handles promotion code input, applied discounts display, and removal.
5
+ */
6
+ import type { TPaymentCurrency } from '@blocklet/payment-types';
7
+ export interface DiscountInfo {
8
+ promotion_code?: string;
9
+ coupon?: string;
10
+ discount_amount?: string;
11
+ promotion_code_details?: {
12
+ code?: string;
13
+ };
14
+ coupon_details?: any;
15
+ verification_data?: {
16
+ code?: string;
17
+ };
18
+ }
19
+ export interface PromotionSectionProps {
20
+ checkoutSessionId: string;
21
+ currency: TPaymentCurrency;
22
+ currencyId: string;
23
+ discounts: DiscountInfo[];
24
+ allowPromotionCodes: boolean;
25
+ completed?: boolean;
26
+ disabled?: boolean;
27
+ onPromotionUpdate: () => void;
28
+ onRemovePromotion: (sessionId: string) => void;
29
+ calculatedDiscountAmount?: string | null;
30
+ isRateLoading?: boolean;
31
+ }
32
+ export default function PromotionSection({ checkoutSessionId, currency, currencyId, discounts, allowPromotionCodes, completed, disabled, onPromotionUpdate, onRemovePromotion, calculatedDiscountAmount, isRateLoading, }: PromotionSectionProps): import("react").JSX.Element | null;
@@ -0,0 +1,133 @@
1
+ "use strict";
2
+
3
+ Object.defineProperty(exports, "__esModule", {
4
+ value: true
5
+ });
6
+ module.exports = PromotionSection;
7
+ var _jsxRuntime = require("react/jsx-runtime");
8
+ var _context = require("@arcblock/ux/lib/Locale/context");
9
+ var _iconsMaterial = require("@mui/icons-material");
10
+ var _material = require("@mui/material");
11
+ var _promotionCode = _interopRequireDefault(require("../../components/promotion-code"));
12
+ var _util = require("../../libs/util");
13
+ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
14
+ function PromotionSection({
15
+ checkoutSessionId,
16
+ currency,
17
+ currencyId,
18
+ discounts,
19
+ allowPromotionCodes,
20
+ completed = false,
21
+ disabled = false,
22
+ onPromotionUpdate,
23
+ onRemovePromotion,
24
+ calculatedDiscountAmount = null,
25
+ isRateLoading = false
26
+ }) {
27
+ const {
28
+ t,
29
+ locale
30
+ } = (0, _context.useLocaleContext)();
31
+ const hasDiscounts = discounts?.length > 0;
32
+ const getAppliedPromotionCodes = () => {
33
+ if (!discounts?.length) return [];
34
+ return discounts.filter(discount => discount.promotion_code || discount.coupon).map(discount => ({
35
+ id: discount.promotion_code || discount.coupon,
36
+ code: discount.verification_data?.code || "APPLIED",
37
+ discount_amount: discount.discount_amount
38
+ }));
39
+ };
40
+ if (allowPromotionCodes && !hasDiscounts) {
41
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
42
+ sx: {
43
+ mt: 1
44
+ },
45
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_promotionCode.default, {
46
+ checkoutSessionId,
47
+ initialAppliedCodes: getAppliedPromotionCodes(),
48
+ disabled: completed,
49
+ onUpdate: onPromotionUpdate,
50
+ currencyId
51
+ })
52
+ });
53
+ }
54
+ if (!hasDiscounts) {
55
+ return null;
56
+ }
57
+ return /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
58
+ children: discounts.map(discount => {
59
+ const promotionCodeInfo = discount.promotion_code_details;
60
+ const couponInfo = discount.coupon_details;
61
+ const discountDescription = couponInfo ? (0, _util.formatCouponTerms)(couponInfo, currency, locale) : "";
62
+ const notSupported = discountDescription === t("payment.checkout.coupon.noDiscount");
63
+ return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
64
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
65
+ direction: "row",
66
+ spacing: 1,
67
+ sx: {
68
+ justifyContent: "space-between",
69
+ alignItems: "center"
70
+ },
71
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
72
+ direction: "row",
73
+ spacing: 1,
74
+ sx: {
75
+ alignItems: "center",
76
+ backgroundColor: "grey.100",
77
+ width: "fit-content",
78
+ px: 1,
79
+ py: 1,
80
+ borderRadius: 1
81
+ },
82
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
83
+ sx: {
84
+ fontWeight: "medium",
85
+ display: "flex",
86
+ alignItems: "center",
87
+ gap: 0.5
88
+ },
89
+ children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.LocalOffer, {
90
+ sx: {
91
+ color: "warning.main",
92
+ fontSize: "small"
93
+ }
94
+ }), promotionCodeInfo?.code || discount.verification_data?.code || t("payment.checkout.discount")]
95
+ }), !completed && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Button, {
96
+ size: "small",
97
+ disabled,
98
+ onClick: () => onRemovePromotion(checkoutSessionId),
99
+ sx: {
100
+ minWidth: "auto",
101
+ width: 16,
102
+ height: 16,
103
+ color: "text.secondary",
104
+ "&.Mui-disabled": {
105
+ color: "text.disabled"
106
+ }
107
+ },
108
+ children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.Close, {
109
+ sx: {
110
+ fontSize: 14
111
+ }
112
+ })
113
+ })]
114
+ }), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
115
+ sx: {
116
+ color: "text.secondary",
117
+ opacity: isRateLoading ? 0 : 1,
118
+ transition: "opacity 300ms ease-in-out"
119
+ },
120
+ children: ["-", (0, _util.formatAmount)(calculatedDiscountAmount || "0", currency.decimal), " ", currency.symbol]
121
+ })]
122
+ }), discountDescription && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
123
+ sx: {
124
+ fontSize: "small",
125
+ color: notSupported ? "error.main" : "text.secondary",
126
+ mt: 0.5
127
+ },
128
+ children: discountDescription
129
+ })]
130
+ }, discount.promotion_code || discount.coupon || `discount-${discount.discount_amount}`);
131
+ })
132
+ });
133
+ }
@@ -0,0 +1,39 @@
1
+ /**
2
+ * TotalSection Component
3
+ *
4
+ * Displays total amount, USD equivalent, exchange rate details,
5
+ * and quote information for dynamic pricing.
6
+ */
7
+ import type { TPaymentCurrency } from '@blocklet/payment-types';
8
+ import type { SlippageConfigValue } from '../../components/slippage-config';
9
+ export interface RateInfo {
10
+ exchangeRate: string | null;
11
+ baseCurrency: string;
12
+ providerName?: string | null;
13
+ providerId?: string | null;
14
+ timestampMs?: number | null;
15
+ }
16
+ export interface QuoteDetailRow {
17
+ label: string;
18
+ value: string | React.ReactNode;
19
+ isSlippage?: boolean;
20
+ }
21
+ export interface TotalSectionProps {
22
+ totalAmountText: string;
23
+ totalUsdDisplay: string | null;
24
+ currency: TPaymentCurrency;
25
+ hasDynamicPricing: boolean;
26
+ rateDisplay: string | null;
27
+ rateInfo: RateInfo;
28
+ quoteDetailRows: QuoteDetailRow[];
29
+ currentSlippagePercent: number;
30
+ slippageConfig?: SlippageConfigValue;
31
+ isPriceLocked: boolean;
32
+ isSubscription: boolean;
33
+ completed?: boolean;
34
+ onSlippageChange?: (slippageConfig: SlippageConfigValue) => void;
35
+ isStripePayment?: boolean;
36
+ thenInfo?: string;
37
+ isRateLoading?: boolean;
38
+ }
39
+ export default function TotalSection({ totalAmountText, totalUsdDisplay, currency, hasDynamicPricing, rateDisplay, rateInfo, quoteDetailRows, currentSlippagePercent, slippageConfig, isPriceLocked, isSubscription, completed, onSlippageChange, isStripePayment, thenInfo, isRateLoading, }: TotalSectionProps): import("react").JSX.Element;