@blocklet/payment-react 1.24.3 → 1.25.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/es/components/auto-topup/modal.d.ts +2 -0
- package/es/components/auto-topup/modal.js +48 -6
- package/es/components/auto-topup/product-card.d.ts +16 -1
- package/es/components/auto-topup/product-card.js +97 -15
- package/es/components/dynamic-pricing-unavailable.d.ts +9 -0
- package/es/components/dynamic-pricing-unavailable.js +58 -0
- package/es/components/loading-amount.d.ts +17 -0
- package/es/components/loading-amount.js +46 -0
- package/es/components/price-change-confirm.d.ts +18 -0
- package/es/components/price-change-confirm.js +107 -0
- package/es/components/quote-details-panel.d.ts +21 -0
- package/es/components/quote-details-panel.js +170 -0
- package/es/components/quote-lock-banner.d.ts +7 -0
- package/es/components/quote-lock-banner.js +79 -0
- package/es/components/slippage-config.d.ts +20 -0
- package/es/components/slippage-config.js +261 -0
- package/es/history/credit/transactions-list.js +11 -1
- package/es/history/invoice/list.js +125 -15
- package/es/hooks/dynamic-pricing.d.ts +102 -0
- package/es/hooks/dynamic-pricing.js +393 -0
- package/es/index.d.ts +6 -1
- package/es/index.js +9 -1
- package/es/libs/util.d.ts +42 -5
- package/es/libs/util.js +345 -57
- package/es/locales/en.js +114 -3
- package/es/locales/zh.js +114 -3
- package/es/payment/form/index.d.ts +4 -1
- package/es/payment/form/index.js +454 -22
- package/es/payment/index.d.ts +1 -1
- package/es/payment/index.js +279 -16
- package/es/payment/product-item.d.ts +26 -1
- package/es/payment/product-item.js +330 -51
- package/es/payment/summary-section/promotion-section.d.ts +32 -0
- package/es/payment/summary-section/promotion-section.js +143 -0
- package/es/payment/summary-section/total-section.d.ts +39 -0
- package/es/payment/summary-section/total-section.js +83 -0
- package/es/payment/summary.d.ts +17 -2
- package/es/payment/summary.js +300 -253
- package/es/types/index.d.ts +11 -0
- package/lib/components/auto-topup/modal.d.ts +2 -0
- package/lib/components/auto-topup/modal.js +54 -6
- package/lib/components/auto-topup/product-card.d.ts +16 -1
- package/lib/components/auto-topup/product-card.js +75 -7
- package/lib/components/dynamic-pricing-unavailable.d.ts +9 -0
- package/lib/components/dynamic-pricing-unavailable.js +81 -0
- package/lib/components/loading-amount.d.ts +17 -0
- package/lib/components/loading-amount.js +53 -0
- package/lib/components/price-change-confirm.d.ts +18 -0
- package/lib/components/price-change-confirm.js +157 -0
- package/lib/components/quote-details-panel.d.ts +21 -0
- package/lib/components/quote-details-panel.js +226 -0
- package/lib/components/quote-lock-banner.d.ts +7 -0
- package/lib/components/quote-lock-banner.js +93 -0
- package/lib/components/slippage-config.d.ts +20 -0
- package/lib/components/slippage-config.js +316 -0
- package/lib/history/credit/transactions-list.js +11 -1
- package/lib/history/invoice/list.js +167 -27
- package/lib/hooks/dynamic-pricing.d.ts +102 -0
- package/lib/hooks/dynamic-pricing.js +390 -0
- package/lib/index.d.ts +6 -1
- package/lib/index.js +32 -0
- package/lib/libs/util.d.ts +42 -5
- package/lib/libs/util.js +367 -49
- package/lib/locales/en.js +114 -3
- package/lib/locales/zh.js +114 -3
- package/lib/payment/form/index.d.ts +4 -1
- package/lib/payment/form/index.js +476 -20
- package/lib/payment/index.d.ts +1 -1
- package/lib/payment/index.js +308 -14
- package/lib/payment/product-item.d.ts +26 -1
- package/lib/payment/product-item.js +270 -35
- package/lib/payment/summary-section/promotion-section.d.ts +32 -0
- package/lib/payment/summary-section/promotion-section.js +133 -0
- package/lib/payment/summary-section/total-section.d.ts +39 -0
- package/lib/payment/summary-section/total-section.js +117 -0
- package/lib/payment/summary.d.ts +17 -2
- package/lib/payment/summary.js +205 -127
- package/lib/types/index.d.ts +11 -0
- package/package.json +3 -3
- package/src/components/auto-topup/modal.tsx +59 -6
- package/src/components/auto-topup/product-card.tsx +118 -11
- package/src/components/dynamic-pricing-unavailable.tsx +69 -0
- package/src/components/loading-amount.tsx +66 -0
- package/src/components/price-change-confirm.tsx +136 -0
- package/src/components/quote-details-panel.tsx +218 -0
- package/src/components/quote-lock-banner.tsx +99 -0
- package/src/components/slippage-config.tsx +336 -0
- package/src/history/credit/transactions-list.tsx +14 -1
- package/src/history/invoice/list.tsx +143 -9
- package/src/hooks/dynamic-pricing.ts +617 -0
- package/src/index.ts +9 -0
- package/src/libs/util.ts +473 -58
- package/src/locales/en.tsx +117 -0
- package/src/locales/zh.tsx +111 -0
- package/src/payment/form/index.tsx +561 -19
- package/src/payment/index.tsx +349 -10
- package/src/payment/product-item.tsx +451 -37
- package/src/payment/summary-section/promotion-section.tsx +172 -0
- package/src/payment/summary-section/total-section.tsx +141 -0
- package/src/payment/summary.tsx +334 -192
- 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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
299
|
-
|
|
300
|
-
|
|
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
|
-
|
|
303
|
-
|
|
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
|
|
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
|
|
332
|
-
name: item.price
|
|
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
|
|
339
|
-
rule: `${(0, _util2.formatRecurring)(item.upsell_price?.recurring || item.price
|
|
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)(
|
|
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
|
-
|
|
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
|
-
|
|
355
|
-
|
|
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:
|
|
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:
|
|
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)(
|
|
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
|
-
}
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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;
|