@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
package/es/libs/util.js CHANGED
@@ -1,4 +1,4 @@
1
- import { BN, fromUnitToToken } from "@ocap/util";
1
+ import { BN, fromUnitToToken, fromTokenToUnit } from "@ocap/util";
2
2
  import omit from "lodash/omit";
3
3
  import trimEnd from "lodash/trimEnd";
4
4
  import numbro from "numbro";
@@ -167,6 +167,87 @@ export function formatNumber(n, precision = 6, trim = true, thousandSeparated =
167
167
  const [left, right] = result.split(".");
168
168
  return right ? [left, trimEnd(right, "0")].filter(Boolean).join(".") : left;
169
169
  }
170
+ export function formatDynamicPrice(n, isDynamic, precision = 6, trim = true, thousandSeparated = true) {
171
+ if (!isDynamic) {
172
+ return formatNumber(n, precision, trim, thousandSeparated);
173
+ }
174
+ const num = Number(n);
175
+ if (!Number.isFinite(num)) {
176
+ return formatNumber(n, precision, trim, thousandSeparated);
177
+ }
178
+ const abs = Math.abs(num);
179
+ const targetPrecision = abs > 0 && abs < 0.01 ? precision : 2;
180
+ return formatNumber(n, targetPrecision, trim, thousandSeparated);
181
+ }
182
+ const USD_DECIMALS = 8;
183
+ export function getUsdAmountFromBaseAmount(amount, quantity, scale = BASE_AMOUNT_SCALE) {
184
+ if (amount === void 0 || amount === null || !quantity || quantity <= 0) {
185
+ return null;
186
+ }
187
+ const scaled = toScaledBaseAmount(amount, quantity, scale);
188
+ return formatScaledAmount(scaled, scale);
189
+ }
190
+ export function getUsdAmountFromTokenUnits(tokenAmount, tokenDecimals, exchangeRate) {
191
+ if (!exchangeRate && exchangeRate !== "0") {
192
+ return null;
193
+ }
194
+ if (tokenDecimals === void 0 || tokenDecimals === null) {
195
+ return null;
196
+ }
197
+ try {
198
+ const amountBN = tokenAmount instanceof BN ? tokenAmount : new BN(tokenAmount || "0");
199
+ const tokenScale = new BN(10).pow(new BN(tokenDecimals));
200
+ if (tokenScale.isZero()) {
201
+ return null;
202
+ }
203
+ const rateBN = new BN(exchangeRate.replace(".", ""));
204
+ const rateDecimalPlaces = exchangeRate.includes(".") ? exchangeRate.split(".")[1]?.length || 0 : 0;
205
+ const rateScale = new BN(10).pow(new BN(rateDecimalPlaces));
206
+ const usdPrecisionScale = new BN(10).pow(new BN(USD_DECIMALS));
207
+ const usdUnit = amountBN.mul(rateBN).mul(usdPrecisionScale).div(tokenScale.mul(rateScale));
208
+ return fromUnitToToken(usdUnit.toString(), USD_DECIMALS);
209
+ } catch {
210
+ return null;
211
+ }
212
+ }
213
+ export function formatUsdAmount(amount, locale = "en") {
214
+ if (!amount && amount !== "0") {
215
+ return null;
216
+ }
217
+ const num = Number(amount);
218
+ if (!Number.isFinite(num)) {
219
+ return null;
220
+ }
221
+ return new Intl.NumberFormat(locale, {
222
+ minimumFractionDigits: 2,
223
+ maximumFractionDigits: 2
224
+ }).format(num);
225
+ }
226
+ export function formatExchangeRate(amount) {
227
+ if (!amount && amount !== "0") {
228
+ return null;
229
+ }
230
+ const value = String(amount);
231
+ const num = Number(value);
232
+ if (!Number.isFinite(num)) {
233
+ return null;
234
+ }
235
+ return value;
236
+ }
237
+ export function formatExchangeRateDisplay(rate, currency = "USD", decimals = 2) {
238
+ if (rate === null || rate === void 0) {
239
+ return null;
240
+ }
241
+ const num = Number(rate);
242
+ if (!Number.isFinite(num)) {
243
+ return null;
244
+ }
245
+ const formattedValue = num.toFixed(decimals);
246
+ if (currency === "USD") {
247
+ return `$${formattedValue}`;
248
+ }
249
+ return `${formattedValue} ${currency}`;
250
+ }
170
251
  export const formatPrice = (price, currency, unit_label, quantity = 1, bn = true, locale = "en") => {
171
252
  if (!currency) {
172
253
  return "";
@@ -174,6 +255,22 @@ export const formatPrice = (price, currency, unit_label, quantity = 1, bn = true
174
255
  if (price.custom_unit_amount) {
175
256
  return `Custom (${currency.symbol})`;
176
257
  }
258
+ if (price.pricing_type === "dynamic" && price.base_amount && price.base_currency === "USD") {
259
+ const baseAmount = price.base_amount;
260
+ const usdAmount = Number(baseAmount) * quantity;
261
+ const formattedUsd = formatUsdAmount(usdAmount.toString(), locale);
262
+ if (price?.type === "recurring" && price.recurring) {
263
+ const recurring = formatRecurring(price.recurring, false, "slash", locale);
264
+ if (unit_label) {
265
+ return `$${formattedUsd} / ${unit_label} ${recurring}`;
266
+ }
267
+ if (price.recurring.usage_type === "metered") {
268
+ return `$${formattedUsd} / unit ${recurring}`;
269
+ }
270
+ return `$${formattedUsd} ${recurring}`;
271
+ }
272
+ return `$${formattedUsd}`;
273
+ }
177
274
  const unit = getPriceUintAmountByCurrency(price, currency);
178
275
  const amount = bn ? fromUnitToToken(new BN(unit).mul(new BN(quantity)), currency.decimal).toString() : +unit * quantity;
179
276
  if (price?.type === "recurring" && price.recurring) {
@@ -188,10 +285,25 @@ export const formatPrice = (price, currency, unit_label, quantity = 1, bn = true
188
285
  }
189
286
  return `${amount} ${currency.symbol}`;
190
287
  };
191
- export const formatPriceAmount = (price, currency, unit_label, quantity = 1, bn = true) => {
288
+ export const formatPriceAmount = (price, currency, unit_label, quantity = 1, bn = true, locale = "en") => {
192
289
  if (!currency) {
193
290
  return "";
194
291
  }
292
+ if (price.pricing_type === "dynamic" && price.base_amount && price.base_currency === "USD") {
293
+ const baseAmount = price.base_amount;
294
+ const usdAmount = Number(baseAmount) * quantity;
295
+ const formattedUsd = formatUsdAmount(usdAmount.toString(), locale);
296
+ if (price?.type === "recurring" && price.recurring) {
297
+ if (unit_label) {
298
+ return `$${formattedUsd} / ${unit_label}`;
299
+ }
300
+ if (price.recurring.usage_type === "metered") {
301
+ return `$${formattedUsd} / unit`;
302
+ }
303
+ return `$${formattedUsd}`;
304
+ }
305
+ return `$${formattedUsd}`;
306
+ }
195
307
  const unit = getPriceUintAmountByCurrency(price, currency);
196
308
  const amount = bn ? fromUnitToToken(new BN(unit).mul(new BN(quantity)), currency.decimal).toString() : +unit * quantity;
197
309
  if (price?.type === "recurring" && price.recurring) {
@@ -272,18 +384,122 @@ export function getPriceCurrencyOptions(price) {
272
384
  }
273
385
  ];
274
386
  }
275
- export function formatLineItemPricing(item, currency, { trialEnd, trialInDays }, locale = "en") {
387
+ const BASE_AMOUNT_SCALE = 8;
388
+ function formatScaledAmount(value, scale = BASE_AMOUNT_SCALE) {
389
+ const isNegative = value.isNeg();
390
+ const absValue = value.abs();
391
+ const str = absValue.toString().padStart(scale + 1, "0");
392
+ const integerPart = str.slice(0, str.length - scale) || "0";
393
+ const fraction = str.slice(-scale).replace(/0+$/, "");
394
+ const formatted = fraction ? `${integerPart}.${fraction}` : integerPart;
395
+ return isNegative ? `-${formatted}` : formatted;
396
+ }
397
+ function toScaledBaseAmount(amount, quantity, scale = BASE_AMOUNT_SCALE) {
398
+ if (!amount || !quantity) {
399
+ return new BN(0);
400
+ }
401
+ const normalized = String(amount);
402
+ const [integer = "0", fraction = ""] = normalized.split(".");
403
+ const frac = `${fraction}`.padEnd(scale, "0").slice(0, scale);
404
+ const digits = `${integer.replace("-", "")}${frac}` || "0";
405
+ const scaled = new BN(digits).mul(new BN(quantity));
406
+ return normalized.startsWith("-") ? scaled.neg() : scaled;
407
+ }
408
+ export function getLineItemAmounts(item, currency, { useUpsell = true, exchangeRate = null } = {}) {
409
+ if (!currency) {
410
+ return { unitAmount: new BN(0), totalAmount: new BN(0), isDynamicQuote: false };
411
+ }
412
+ const price = useUpsell ? item.upsell_price || item.price : item.price;
413
+ const quantity = new BN(item.quantity || 0);
414
+ const quoteAmount = item?.quoted_amount;
415
+ const quoteCurrencyId = item?.quote_currency_id;
416
+ const isDynamicPrice = price?.pricing_type === "dynamic";
417
+ const isQuoteValidForCurrency = isDynamicPrice && quoteAmount && quoteCurrencyId === currency.id;
418
+ if (isQuoteValidForCurrency) {
419
+ const totalAmount = new BN(quoteAmount || "0");
420
+ const unitAmount2 = quantity.gt(new BN(0)) ? totalAmount.add(quantity).sub(new BN(1)).div(quantity) : new BN(0);
421
+ return { unitAmount: unitAmount2, totalAmount, isDynamicQuote: true };
422
+ }
423
+ if (isDynamicPrice && exchangeRate && price?.base_amount) {
424
+ const rate = Number(exchangeRate);
425
+ if (rate > 0 && Number.isFinite(rate)) {
426
+ const baseAmountUsd = Number(price.base_amount);
427
+ if (baseAmountUsd > 0 && Number.isFinite(baseAmountUsd)) {
428
+ const tokenAmount = baseAmountUsd / rate;
429
+ const unitAmount2 = fromTokenToUnit(tokenAmount.toFixed(currency.decimal || 8), currency.decimal || 8);
430
+ return {
431
+ unitAmount: unitAmount2,
432
+ totalAmount: unitAmount2.mul(quantity),
433
+ isDynamicQuote: false
434
+ };
435
+ }
436
+ }
437
+ }
438
+ const unitAmount = new BN(getPriceUintAmountByCurrency(price, currency));
439
+ return {
440
+ unitAmount,
441
+ totalAmount: unitAmount.mul(quantity),
442
+ isDynamicQuote: false
443
+ };
444
+ }
445
+ export function getQuoteLockInfo(items, currency) {
446
+ if (!items?.length || !currency) {
447
+ return null;
448
+ }
449
+ const dynamicItems = items.filter((item) => {
450
+ const price = item.upsell_price || item.price;
451
+ return price?.pricing_type === "dynamic" && item?.quoted_amount;
452
+ });
453
+ if (!dynamicItems.length) {
454
+ return null;
455
+ }
456
+ let totalBaseAmount = new BN(0);
457
+ let totalTokenAmount = new BN(0);
458
+ let expiresAt = null;
459
+ dynamicItems.forEach((item) => {
460
+ const price = item.upsell_price || item.price;
461
+ const quoteAmount = new BN(item?.quoted_amount || "0");
462
+ totalTokenAmount = totalTokenAmount.add(quoteAmount);
463
+ const baseAmount = price?.base_amount;
464
+ if (baseAmount) {
465
+ totalBaseAmount = totalBaseAmount.add(toScaledBaseAmount(baseAmount, item.quantity));
466
+ }
467
+ if (item?.expires_at) {
468
+ expiresAt = expiresAt === null ? item?.expires_at : Math.min(expiresAt, item?.expires_at);
469
+ }
470
+ });
471
+ return {
472
+ baseAmount: formatScaledAmount(totalBaseAmount, BASE_AMOUNT_SCALE),
473
+ baseCurrency: (dynamicItems[0]?.upsell_price || dynamicItems[0]?.price)?.base_currency || "USD",
474
+ tokenAmount: fromUnitToToken(totalTokenAmount, currency.decimal).toString(),
475
+ tokenSymbol: currency.symbol,
476
+ expiresAt
477
+ };
478
+ }
479
+ export function formatLineItemPricing(item, currency, {
480
+ trialEnd,
481
+ trialInDays,
482
+ exchangeRate = null
483
+ }, locale = "en") {
276
484
  if (!currency) {
277
485
  return { primary: "", secondary: "", quantity: "" };
278
486
  }
279
487
  const price = item.upsell_price || item.price;
488
+ if (!price) {
489
+ return { primary: "", secondary: "", quantity: "" };
490
+ }
280
491
  let quantity = t("common.qty", locale, { count: item.quantity });
281
492
  if (price.recurring?.usage_type === "metered" || +item.quantity === 1) {
282
493
  quantity = "";
283
494
  }
284
- const unitValue = new BN(getPriceUintAmountByCurrency(price, currency));
285
- const total = `${fromUnitToToken(unitValue.mul(new BN(item.quantity)), currency.decimal)} ${currency.symbol}`;
286
- const unit = `${fromUnitToToken(unitValue, currency.decimal)} ${currency.symbol}`;
495
+ const { unitAmount, totalAmount } = getLineItemAmounts(item, currency, { exchangeRate });
496
+ const isDynamic = price?.pricing_type === "dynamic";
497
+ const formatLineItemAmount = (bn) => {
498
+ const value = fromUnitToToken(bn, currency.decimal);
499
+ return formatDynamicPrice(value, isDynamic, 6);
500
+ };
501
+ const total = `${formatLineItemAmount(totalAmount)} ${currency.symbol}`;
502
+ const unit = `${formatLineItemAmount(unitAmount)} ${currency.symbol}`;
287
503
  const trialResult = getFreeTrialTime({ trialInDays, trialEnd }, locale);
288
504
  const appendUnit = (v, alt) => {
289
505
  if (price.product.unit_label) {
@@ -397,8 +613,11 @@ export function getWebhookStatusColor(status) {
397
613
  return "default";
398
614
  }
399
615
  }
400
- export function getCheckoutAmount(items, currency, trialing = false, upsell = true) {
401
- if (items.find((x) => (x.upsell_price || x.price).custom_unit_amount) && items.length > 1) {
616
+ export function getCheckoutAmount(items, currency, trialing = false, upsell = true, { exchangeRate = null } = {}) {
617
+ if (items.find((x) => {
618
+ const price = upsell ? x.upsell_price || x.price : x.price;
619
+ return price?.custom_unit_amount;
620
+ }) && items.length > 1) {
402
621
  throw new Error("Multiple items with custom unit amount are not supported");
403
622
  }
404
623
  let renew = new BN(0);
@@ -406,18 +625,15 @@ export function getCheckoutAmount(items, currency, trialing = false, upsell = tr
406
625
  const price = upsell ? x.upsell_price || x.price : x.price;
407
626
  return price != null;
408
627
  }).reduce((acc, x) => {
409
- if (x.custom_amount) {
628
+ const quoteCurrencyId = x?.quote_currency_id;
629
+ const isQuoteForDifferentCurrency = quoteCurrencyId && quoteCurrencyId !== currency.id;
630
+ if (x.custom_amount && !isQuoteForDifferentCurrency) {
410
631
  return acc.add(new BN(x.custom_amount));
411
632
  }
412
633
  const price = upsell ? x.upsell_price || x.price : x.price;
413
- const unitPrice = getPriceUintAmountByCurrency(price, currency);
414
- if (price.custom_unit_amount) {
415
- if (unitPrice) {
416
- return acc.add(new BN(unitPrice).mul(new BN(x.quantity)));
417
- }
418
- }
634
+ const { totalAmount } = getLineItemAmounts(x, currency, { useUpsell: upsell, exchangeRate });
419
635
  if (price?.type === "recurring") {
420
- renew = renew.add(new BN(unitPrice).mul(new BN(x.quantity)));
636
+ renew = renew.add(totalAmount);
421
637
  if (trialing) {
422
638
  return acc;
423
639
  }
@@ -425,9 +641,16 @@ export function getCheckoutAmount(items, currency, trialing = false, upsell = tr
425
641
  return acc;
426
642
  }
427
643
  }
428
- return acc.add(new BN(unitPrice).mul(new BN(x.quantity)));
644
+ return acc.add(totalAmount);
429
645
  }, new BN(0)).toString();
430
- return { subtotal: total, total, renew: renew.toString(), discount: "0", shipping: "0", tax: "0" };
646
+ return {
647
+ subtotal: total,
648
+ total,
649
+ renew: formatDynamicPrice(renew.toString(), !!exchangeRate),
650
+ discount: "0",
651
+ shipping: "0",
652
+ tax: "0"
653
+ };
431
654
  }
432
655
  export function getRecurringPeriod(recurring) {
433
656
  const { interval } = recurring;
@@ -452,7 +675,7 @@ export function formatUpsellSaving(items, currency) {
452
675
  if (items[0]?.upsell_price_id) {
453
676
  return "0";
454
677
  }
455
- if (!items[0]?.price.upsell?.upsells_to) {
678
+ if (!items[0]?.price?.upsell?.upsells_to) {
456
679
  return "0";
457
680
  }
458
681
  const from = getCheckoutAmount(items, currency, false, false);
@@ -482,6 +705,12 @@ export function formatMeteredThen(subscription, recurring, hasMetered, locale =
482
705
  }
483
706
  return t("payment.checkout.then", locale, { subscription, recurring });
484
707
  }
708
+ export function formatThenValue(subscription, recurring, hasMetered, locale = "en") {
709
+ if (hasMetered) {
710
+ return t("payment.checkout.metered", locale, { recurring });
711
+ }
712
+ return [subscription, recurring].filter(Boolean).join(" ");
713
+ }
485
714
  export function formatPriceDisplay({ amount, then, actualAmount, showThen }, recurring, hasMetered, locale = "en") {
486
715
  if (Number(actualAmount) === 0 && hasMetered) {
487
716
  return t("payment.checkout.metered", locale, { recurring });
@@ -534,9 +763,9 @@ export function getFreeTrialTime({ trialInDays, trialEnd }, locale = "en") {
534
763
  interval: t("common.day", locale)
535
764
  };
536
765
  }
537
- export function formatCheckoutHeadlines(items, currency, { trialInDays, trialEnd }, locale = "en") {
766
+ export function formatCheckoutHeadlines(items, currency, { trialInDays, trialEnd }, locale = "en", { exchangeRate = null } = {}) {
538
767
  const brand = getStatementDescriptor(items);
539
- const { total } = getCheckoutAmount(items, currency, trialInDays > 0);
768
+ const { total } = getCheckoutAmount(items, currency, trialInDays > 0, true, { exchangeRate });
540
769
  const actualAmount = fromUnitToToken(total, currency.decimal);
541
770
  const amount = `${fromUnitToToken(total, currency.decimal)} ${currency.symbol}`;
542
771
  const trialResult = getFreeTrialTime({ trialInDays, trialEnd }, locale);
@@ -549,118 +778,177 @@ export function formatCheckoutHeadlines(items, currency, { trialInDays, trialEnd
549
778
  priceDisplay: "0"
550
779
  };
551
780
  }
552
- const { name } = items[0]?.price.product || { name: "" };
553
- if (items.every((x) => x.price.type === "one_time")) {
781
+ const { name } = items[0]?.price?.product || { name: "" };
782
+ if (items.every((x) => x.price?.type === "one_time")) {
554
783
  const action = t("payment.checkout.pay", locale, { payee: brand });
555
784
  if (items.length > 1) {
556
785
  return { action, amount, actualAmount, priceDisplay: amount };
557
786
  }
558
787
  return { action, amount, then: "", actualAmount, priceDisplay: amount };
559
788
  }
560
- const item = items.find((x) => x.price.type === "recurring");
789
+ const item = items.find((x) => x.price?.type === "recurring");
561
790
  const recurring = formatRecurring(
562
791
  (item?.upsell_price || item?.price)?.recurring,
563
792
  false,
564
793
  "per",
565
794
  locale
566
795
  );
567
- const hasMetered = items.some((x) => x.price.type === "recurring" && x.price.recurring?.usage_type === "metered");
796
+ const hasMetered = items.some((x) => x.price?.type === "recurring" && x.price?.recurring?.usage_type === "metered");
568
797
  const differentRecurring = hasMultipleRecurringIntervals(items);
569
- if (items.every((x) => x.price.type === "recurring")) {
798
+ if (items.every((x) => x.price?.type === "recurring")) {
570
799
  const subscription2 = [
571
800
  hasMetered ? t("payment.checkout.least", locale) : "",
572
- fromUnitToToken(
573
- items.reduce((acc, x) => {
574
- if (x.price.recurring?.usage_type === "metered") {
575
- return acc;
576
- }
577
- return acc.add(
578
- new BN(getPriceUintAmountByCurrency(x.upsell_price || x.price, currency)).mul(new BN(x.quantity))
579
- );
580
- }, new BN(0)),
581
- currency.decimal
801
+ formatDynamicPrice(
802
+ fromUnitToToken(
803
+ items.reduce((acc, x) => {
804
+ const price = x.upsell_price || x.price;
805
+ if (price?.recurring?.usage_type === "metered") {
806
+ return acc;
807
+ }
808
+ return acc.add(getLineItemAmounts(x, currency, { exchangeRate }).totalAmount);
809
+ }, new BN(0)),
810
+ currency.decimal
811
+ ),
812
+ !!exchangeRate
582
813
  ),
583
814
  currency.symbol
584
815
  ].filter(Boolean).join(" ");
585
816
  if (items.length > 1) {
586
817
  if (trialResult.count > 0) {
818
+ const thenDisplay4 = formatMeteredThen(
819
+ subscription2,
820
+ recurring,
821
+ hasMetered && Number(subscription2) === 0,
822
+ locale
823
+ );
824
+ const thenValue4 = formatThenValue(subscription2, recurring, hasMetered && Number(subscription2) === 0, locale);
587
825
  const result4 = {
588
826
  action: t("payment.checkout.try2", locale, { name, count: items.length - 1 }),
589
827
  amount: t("payment.checkout.free", locale, { count: trialResult.count, interval: trialResult.interval }),
590
- then: formatMeteredThen(subscription2, recurring, hasMetered && Number(subscription2) === 0, locale),
828
+ then: thenDisplay4,
829
+ thenValue: thenValue4,
591
830
  showThen: true,
592
831
  actualAmount: "0"
593
832
  };
594
833
  return {
595
834
  ...result4,
596
- priceDisplay: formatPriceDisplay(result4, recurring, hasMetered, locale)
835
+ priceDisplay: formatPriceDisplay(
836
+ { amount: result4.amount, then: thenDisplay4, actualAmount: result4.actualAmount, showThen: result4.showThen },
837
+ recurring,
838
+ hasMetered,
839
+ locale
840
+ )
597
841
  };
598
842
  }
843
+ const thenDisplay3 = hasMetered ? t("payment.checkout.meteredThen", locale, { recurring }) : recurring;
844
+ const thenValue3 = hasMetered ? t("payment.checkout.metered", locale, { recurring }) : recurring;
599
845
  const result3 = {
600
846
  action: t("payment.checkout.sub2", locale, { name, count: items.length - 1 }),
601
847
  amount,
602
- then: hasMetered ? t("payment.checkout.meteredThen", locale, { recurring }) : recurring,
848
+ then: thenDisplay3,
849
+ thenValue: thenValue3,
603
850
  showThen: hasMetered,
604
851
  actualAmount
605
852
  };
606
853
  return {
607
854
  ...result3,
608
- priceDisplay: formatPriceDisplay(result3, recurring, hasMetered, locale)
855
+ priceDisplay: formatPriceDisplay(
856
+ { amount: result3.amount, then: thenDisplay3, actualAmount: result3.actualAmount, showThen: result3.showThen },
857
+ recurring,
858
+ hasMetered,
859
+ locale
860
+ )
609
861
  };
610
862
  }
611
863
  if (trialResult.count > 0) {
864
+ const thenDisplay3 = formatMeteredThen(subscription2, recurring, hasMetered && Number(subscription2) === 0, locale);
865
+ const thenValue3 = formatThenValue(subscription2, recurring, hasMetered && Number(subscription2) === 0, locale);
612
866
  const result3 = {
613
867
  action: t("payment.checkout.try1", locale, { name }),
614
868
  amount: t("payment.checkout.free", locale, { count: trialResult.count, interval: trialResult.interval }),
615
- then: formatMeteredThen(subscription2, recurring, hasMetered && Number(subscription2) === 0, locale),
869
+ then: thenDisplay3,
870
+ thenValue: thenValue3,
616
871
  showThen: true,
617
872
  actualAmount: "0"
618
873
  };
619
874
  return {
620
875
  ...result3,
621
- priceDisplay: formatPriceDisplay(result3, recurring, hasMetered, locale)
876
+ priceDisplay: formatPriceDisplay(
877
+ { amount: result3.amount, then: thenDisplay3, actualAmount: result3.actualAmount, showThen: result3.showThen },
878
+ recurring,
879
+ hasMetered,
880
+ locale
881
+ )
622
882
  };
623
883
  }
884
+ const thenDisplay2 = hasMetered ? t("payment.checkout.meteredThen", locale, { recurring }) : recurring;
885
+ const thenValue2 = hasMetered ? t("payment.checkout.metered", locale, { recurring }) : recurring;
624
886
  const result2 = {
625
887
  action: t("payment.checkout.sub1", locale, { name }),
626
888
  amount,
627
- then: hasMetered ? t("payment.checkout.meteredThen", locale, { recurring }) : recurring,
889
+ then: thenDisplay2,
890
+ thenValue: thenValue2,
628
891
  showThen: hasMetered && !differentRecurring,
629
892
  actualAmount
630
893
  };
631
894
  return {
632
895
  ...result2,
633
- priceDisplay: formatPriceDisplay(result2, recurring, hasMetered, locale)
896
+ priceDisplay: formatPriceDisplay(
897
+ { amount: result2.amount, then: thenDisplay2, actualAmount: result2.actualAmount, showThen: result2.showThen },
898
+ recurring,
899
+ hasMetered,
900
+ locale
901
+ )
634
902
  };
635
903
  }
636
904
  const subscription = fromUnitToToken(
637
- items.filter((x) => x.price.type === "recurring").reduce((acc, x) => {
638
- if (x.price.recurring?.usage_type === "metered") {
905
+ items.filter((x) => x.price?.type === "recurring").reduce((acc, x) => {
906
+ if (x.price?.recurring?.usage_type === "metered") {
639
907
  return acc;
640
908
  }
641
- return acc.add(new BN(getPriceUintAmountByCurrency(x.price, currency)).mul(new BN(x.quantity)));
909
+ return acc.add(getLineItemAmounts(x, currency, { useUpsell: false, exchangeRate }).totalAmount);
642
910
  }, new BN(0)),
643
911
  currency.decimal
644
912
  );
913
+ const thenDisplay = formatMeteredThen(
914
+ `${subscription} ${currency.symbol}`,
915
+ recurring,
916
+ hasMetered && Number(subscription) === 0,
917
+ locale
918
+ );
919
+ const thenValue = formatThenValue(
920
+ `${subscription} ${currency.symbol}`,
921
+ recurring,
922
+ hasMetered && Number(subscription) === 0,
923
+ locale
924
+ );
645
925
  const result = {
646
926
  action: t("payment.checkout.pay", locale, { payee: brand }),
647
927
  amount,
648
- then: formatMeteredThen(
649
- `${subscription} ${currency.symbol}`,
650
- recurring,
651
- hasMetered && Number(subscription) === 0,
652
- locale
653
- ),
928
+ then: thenDisplay,
929
+ thenValue,
654
930
  showThen: !differentRecurring,
655
931
  actualAmount
656
932
  };
657
933
  return {
658
934
  ...result,
659
- priceDisplay: formatPriceDisplay(result, recurring, hasMetered, locale)
935
+ priceDisplay: formatPriceDisplay(
936
+ { amount: result.amount, then: thenDisplay, actualAmount: result.actualAmount, showThen: result.showThen },
937
+ recurring,
938
+ hasMetered,
939
+ locale
940
+ )
660
941
  };
661
942
  }
662
- export function formatAmount(amount, decimals) {
663
- return fromUnitToToken(amount, decimals);
943
+ export function formatAmount(amount, decimals, precision = 2) {
944
+ const tokenAmount = fromUnitToToken(amount, decimals);
945
+ const numericValue = Number(tokenAmount);
946
+ if (!Number.isFinite(numericValue)) {
947
+ return formatBNStr(amount, decimals, precision, true, false);
948
+ }
949
+ const abs = Math.abs(numericValue);
950
+ const targetPrecision = abs > 0 && abs < 0.01 ? decimals : 2;
951
+ return formatNumber(tokenAmount, targetPrecision, true, false);
664
952
  }
665
953
  export function findCurrency(methods, currencyId) {
666
954
  for (const method of methods) {
@@ -1063,7 +1351,7 @@ export function showStaking(method, currency, noStake) {
1063
1351
  if (noStake) {
1064
1352
  return false;
1065
1353
  }
1066
- if (method.type === "arcblock") {
1354
+ if (method && method.type === "arcblock") {
1067
1355
  return currency.type !== "credit";
1068
1356
  }
1069
1357
  return false;