@blocklet/payment-react
Version:
Reusable react components for payment kit v2
859 lines (858 loc) • 34.5 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", {
value: true
});
module.exports = ProductItem;
var _jsxRuntime = require("react/jsx-runtime");
var _context = require("@arcblock/ux/lib/Locale/context");
var _material = require("@mui/material");
var _iconsMaterial = require("@mui/icons-material");
var _react = require("react");
var _ahooks = require("ahooks");
var _util = require("@ocap/util");
var _loadingAmount = _interopRequireDefault(require("../components/loading-amount"));
var _status = _interopRequireDefault(require("../components/status"));
var _switchButton = _interopRequireDefault(require("../components/switch-button"));
var _util2 = require("../libs/util");
var _productCard = _interopRequireDefault(require("./product-card"));
var _dayjs = _interopRequireDefault(require("../libs/dayjs"));
var _payment = require("../contexts/payment");
function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
const getRecommendedQuantityFromUrl = priceId => {
try {
const urlParams = new URLSearchParams(window.location.search);
const recommendedQuantity = urlParams.get(`qty_${priceId}`) || urlParams.get("qty");
return recommendedQuantity ? Math.max(1, parseInt(recommendedQuantity, 10)) : void 0;
} catch {
return void 0;
}
};
const getUserQuantityPreference = (userDid, priceId) => {
try {
const key = `quantity_preference_${userDid}_${priceId}`;
const stored = localStorage.getItem(key);
return stored ? Math.max(1, parseInt(stored, 10)) : void 0;
} catch {
return void 0;
}
};
const saveUserQuantityPreference = (userDid, priceId, quantity) => {
try {
const key = `quantity_preference_${userDid}_${priceId}`;
localStorage.setItem(key, quantity.toString());
} catch {}
};
function ProductItem({
item,
items,
trialInDays,
trialEnd = 0,
currency,
mode = "normal",
children = null,
onUpsell,
onDownsell,
completed = false,
adjustableQuantity = {
enabled: false
},
onQuantityChange = () => {},
showFeatures = false,
exchangeRate = null,
isStripePayment = false,
isPriceLocked = false,
isRateLoading = false,
discounts = [],
calculatedDiscountAmount = null
}) {
const {
t,
locale
} = (0, _context.useLocaleContext)();
const {
settings,
setPayable,
session,
api
} = (0, _payment.usePaymentContext)();
const pricingSource = item.upsell_price || item.price;
const isDynamicPricing = pricingSource?.pricing_type === "dynamic";
const pricing = (0, _util2.formatLineItemPricing)(item, currency, {
trialEnd,
trialInDays,
exchangeRate
}, locale);
const saving = (0, _util2.formatUpsellSaving)(items, currency);
const metered = item.price?.recurring?.usage_type === "metered" ? t("common.metered") : "";
const canUpsell = mode === "normal" && items.length === 1;
const isTrial = trialInDays > 0 || trialEnd > (0, _dayjs.default)().unix();
const isCreditProduct = item.price.product?.type === "credit" && item.price.metadata?.credit_config;
const creditAmount = isCreditProduct && item.price.metadata?.credit_config?.credit_amount ? Number(item.price.metadata.credit_config.credit_amount) : 0;
const creditCurrency = isCreditProduct && item.price.metadata?.credit_config?.currency_id ? (0, _util2.findCurrency)(settings.paymentMethods, item.price.metadata.credit_config.currency_id) : null;
const validDuration = item.price.metadata?.credit_config?.valid_duration_value;
const validDurationUnit = item.price.metadata?.credit_config?.valid_duration_unit || "days";
const scheduleConfig = item.price.metadata?.credit_config?.schedule;
const hasSchedule = scheduleConfig?.enabled && scheduleConfig?.delivery_mode && scheduleConfig.delivery_mode !== "invoice";
const userDid = session?.user?.did;
const {
data: pendingAmount
} = (0, _ahooks.useRequest)(async () => {
if (!isCreditProduct || !userDid) return null;
try {
const {
data
} = await api.get("/api/meter-events/pending-amount", {
params: {
customer_id: userDid,
currency_id: creditCurrency?.id
}
});
return data?.[creditCurrency?.id || ""];
} catch (error) {
console.warn("Failed to fetch pending amount:", error);
return null;
}
}, {
refreshDeps: [isCreditProduct, userDid, creditCurrency?.id]
});
const canAdjustQuantity = adjustableQuantity.enabled && mode === "normal";
const minQuantity = Math.max(adjustableQuantity.minimum || 1, 1);
const quantityAvailable = Math.min(item.price?.quantity_limit_per_checkout ?? Infinity, item.price?.quantity_available ?? Infinity);
const maxQuantity = quantityAvailable ? Math.min(adjustableQuantity.maximum || Infinity, quantityAvailable) : adjustableQuantity.maximum || Infinity;
const getMinQuantityForPending = (0, _react.useMemo)(() => {
if (!isCreditProduct || !pendingAmount) return null;
const pendingAmountBN = new _util.BN(pendingAmount || "0");
if (!pendingAmountBN.gt(new _util.BN(0))) return null;
if (!creditAmount || creditAmount <= 0) return null;
const creditAmountBN = (0, _util.fromTokenToUnit)(creditAmount, creditCurrency?.decimal || 2);
if (!creditAmountBN || creditAmountBN.isZero()) return null;
return Math.ceil(pendingAmountBN.mul(new _util.BN(100)).div(creditAmountBN).toNumber() / 100);
}, [isCreditProduct, pendingAmount, creditAmount, creditCurrency?.decimal]);
const initialQuantity = (0, _react.useMemo)(() => {
const urlQuantity = getRecommendedQuantityFromUrl(item.price.id);
if (urlQuantity && urlQuantity > 0) {
if (canAdjustQuantity && getMinQuantityForPending) {
return Math.max(urlQuantity, getMinQuantityForPending, minQuantity);
}
return urlQuantity;
}
if (userDid) {
const preferredQuantity = getUserQuantityPreference(userDid, item.price.id);
if (preferredQuantity && preferredQuantity > 0) {
if (canAdjustQuantity && getMinQuantityForPending) {
return Math.max(preferredQuantity, getMinQuantityForPending, minQuantity);
}
return preferredQuantity;
}
}
let baseQuantity = item.quantity;
if (canAdjustQuantity && getMinQuantityForPending) {
baseQuantity = Math.max(baseQuantity, getMinQuantityForPending, minQuantity);
}
return baseQuantity;
}, [item.quantity, item.price.id, userDid, getMinQuantityForPending, canAdjustQuantity, minQuantity]);
const [localQuantity, setLocalQuantity] = (0, _react.useState)(initialQuantity);
const localQuantityNum = localQuantity || 0;
(0, _react.useEffect)(() => {
if (initialQuantity && initialQuantity > 0) {
if (initialQuantity !== localQuantity) {
setLocalQuantity(initialQuantity);
}
if (initialQuantity !== item.quantity) {
onQuantityChange(item.price_id, initialQuantity);
}
if (isCreditProduct && pendingAmount && getMinQuantityForPending) {
setPayable(initialQuantity >= getMinQuantityForPending);
} else {
setPayable(true);
}
}
}, [initialQuantity, isCreditProduct, pendingAmount, getMinQuantityForPending]);
const handleQuantityChange = newQuantity => {
if (!newQuantity) {
setLocalQuantity(void 0);
setPayable(false);
return;
}
if (newQuantity >= minQuantity && newQuantity <= maxQuantity) {
if ((0, _util2.formatQuantityInventory)(item.price, newQuantity, locale)) {
return;
}
if (isCreditProduct && pendingAmount && getMinQuantityForPending) {
if (newQuantity < getMinQuantityForPending) {
setPayable(false);
setLocalQuantity(newQuantity);
onQuantityChange(item.price_id, newQuantity);
return;
}
}
setPayable(true);
setLocalQuantity(newQuantity);
onQuantityChange(item.price_id, newQuantity);
if (userDid && newQuantity > 0) {
saveUserQuantityPreference(userDid, item.price?.id, newQuantity);
}
}
};
const handleQuantityIncrease = () => {
if (localQuantityNum < maxQuantity) {
handleQuantityChange(localQuantityNum + 1);
}
};
const handleQuantityDecrease = () => {
if (localQuantityNum > minQuantity) {
handleQuantityChange(localQuantityNum - 1);
}
};
const handleQuantityInputChange = event => {
const value = parseInt(event.target.value || "0", 10);
if (!Number.isNaN(value)) {
handleQuantityChange(value);
}
};
const formatCreditInfo = () => {
if (!isCreditProduct) return null;
const totalCreditStr = (0, _util2.formatNumber)(creditAmount * (localQuantity || 0));
const currencySymbol = creditCurrency?.symbol || "Credits";
const formattedTotalCredit = (0, _util2.formatCreditForCheckout)(totalCreditStr, currencySymbol, locale);
const hasPendingAmount = pendingAmount && new _util.BN(pendingAmount || "0").gt(new _util.BN(0));
const isRecurring = item.price?.type === "recurring";
const hasExpiry = validDuration && validDuration > 0;
const buildBaseParams = () => ({
amount: formattedTotalCredit,
...(hasExpiry && {
duration: validDuration,
unit: t(`common.${validDurationUnit}`)
}),
...(isRecurring && {
period: (0, _util2.formatRecurring)(item.price?.recurring, true, "per", locale)
})
});
const buildPendingParams = (pendingBN, availableAmount) => ({
amount: (0, _util2.formatCreditForCheckout)((0, _util2.formatBNStr)(pendingBN.toString(), creditCurrency?.decimal || 2), currencySymbol, locale),
totalAmount: formattedTotalCredit,
...(availableAmount && {
availableAmount: (0, _util2.formatCreditForCheckout)((0, _util2.formatBNStr)(availableAmount, creditCurrency?.decimal || 2), currencySymbol, locale)
}),
...(hasExpiry && {
duration: validDuration,
unit: t(`common.${validDurationUnit}`)
}),
...(isRecurring && {
period: (0, _util2.formatRecurring)(item.price?.recurring, true, "per", locale)
})
});
const getLocaleKey = (category, type2) => {
const suffix = hasExpiry ? "WithExpiry" : "";
return `payment.checkout.credit.${category}.${type2}${suffix}`;
};
if (!hasPendingAmount) {
const type2 = isRecurring ? "recurring" : "oneTime";
return t(getLocaleKey("normal", type2), buildBaseParams());
}
const pendingAmountBN = new _util.BN(pendingAmount || "0");
const creditAmountBN = (0, _util.fromTokenToUnit)(creditAmount, creditCurrency?.decimal || 2);
const minQuantityNeeded = getMinQuantityForPending || 0;
const currentPurchaseCreditBN = creditAmountBN.mul(new _util.BN(localQuantity || 0));
const actualAvailable = currentPurchaseCreditBN.sub(pendingAmountBN).toString();
if (!new _util.BN(actualAvailable).gt(new _util.BN(0))) {
return t("payment.checkout.credit.pending.notEnough", {
amount: (0, _util2.formatCreditForCheckout)((0, _util2.formatBNStr)(pendingAmountBN.toString(), creditCurrency?.decimal || 2), currencySymbol, locale),
quantity: (0, _util2.formatNumber)(minQuantityNeeded)
});
}
const type = isRecurring ? "recurringEnough" : "oneTimeEnough";
return t(getLocaleKey("pending", type), buildPendingParams(pendingAmountBN, actualAvailable));
};
const quantityForUsd = Number.isFinite(localQuantity) ? Number(localQuantity) : item.quantity || 0;
const dynamicTokenDisplay = (0, _react.useMemo)(() => {
if (isStripePayment || !currency) {
return null;
}
const baseAmount = pricingSource?.base_amount;
const unitAmount = pricingSource?.unit_amount;
if (isDynamicPricing && baseAmount !== void 0 && baseAmount !== null) {
if (isPriceLocked) {
const quotedAmount = item?.quoted_amount;
if (quotedAmount) {
const totalBN = new _util.BN(quotedAmount);
const tokenValue = (0, _util.fromUnitToToken)(totalBN, currency.decimal);
return `${(0, _util2.formatDynamicPrice)(tokenValue, true, 6)} ${currency.symbol}`;
}
}
if (!exchangeRate) {
return null;
}
const rate = Number(exchangeRate);
if (rate <= 0 || Number.isNaN(rate)) {
return null;
}
const usdAmount = Number(baseAmount);
const tokenAmount = usdAmount / rate;
const totalTokens = tokenAmount * (quantityForUsd || 1);
return `${(0, _util2.formatDynamicPrice)(totalTokens, true, 6)} ${currency.symbol}`;
}
if (!isDynamicPricing && exchangeRate && unitAmount) {
const rate = Number(exchangeRate);
if (rate <= 0 || Number.isNaN(rate)) {
return null;
}
const usdAmount = Number(unitAmount) / 100;
const tokenAmount = usdAmount / rate;
const totalTokens = tokenAmount * (quantityForUsd || 1);
return `${(0, _util2.formatDynamicPrice)(totalTokens, true, 6)} ${currency.symbol}`;
}
return null;
}, [isStripePayment, isDynamicPricing, currency, pricingSource, item, exchangeRate, quantityForUsd, isPriceLocked]);
const formatScheduleInfo = () => {
if (!hasSchedule || !scheduleConfig) return null;
const totalCredit = creditAmount * (localQuantity || 0);
const currencySymbol = creditCurrency?.symbol || "Credits";
const intervalUnit = scheduleConfig.interval_unit;
const intervalValue = scheduleConfig.interval_value;
let amountPerGrant;
if (scheduleConfig.amount_per_grant) {
amountPerGrant = Number(scheduleConfig.amount_per_grant) * (localQuantity || 1);
} else {
const billingIntervalUnit = item.price.recurring?.interval || "month";
const billingIntervalCount = item.price.recurring?.interval_count || 1;
const unitToHours = {
hour: 1,
day: 24,
week: 168,
month: 720
// ~30 days
};
const billingHours = unitToHours[billingIntervalUnit] * billingIntervalCount;
const scheduleHours = unitToHours[intervalUnit] * intervalValue;
const grantsPerPeriod = Math.floor(billingHours / scheduleHours);
amountPerGrant = grantsPerPeriod > 0 ? totalCredit / grantsPerPeriod : totalCredit;
}
const formattedAmount = (0, _util2.formatCreditAmount)((0, _util2.formatNumber)(amountPerGrant), currencySymbol);
const intervalDisplay = intervalValue === 1 ? t(`common.${intervalUnit}`) : ` ${intervalValue} ${t(`common.${intervalUnit}s`)} `;
const expireWithNext = scheduleConfig.expire_with_next_grant;
if (expireWithNext) {
return t("payment.checkout.credit.schedule.withRefresh", {
amount: formattedAmount,
interval: intervalDisplay
});
}
return t("payment.checkout.credit.schedule.periodic", {
amount: formattedAmount,
interval: intervalDisplay
});
};
const primaryText = (0, _react.useMemo)(() => {
const price = item.upsell_price || item.price || {};
const isRecurring = price?.type === "recurring" && price?.recurring;
if (isTrial) {
return pricing.primary;
}
let displayAmount;
if (!isStripePayment && dynamicTokenDisplay) {
displayAmount = dynamicTokenDisplay;
} else if (isDynamicPricing && !isStripePayment) {
const baseAmount = pricingSource?.base_amount;
if (baseAmount !== void 0 && baseAmount !== null) {
const usdValue = Number(baseAmount);
displayAmount = `\u2248 $${usdValue.toFixed(2)}`;
} else {
displayAmount = pricing.primary;
}
} else {
displayAmount = pricing.primary;
}
if (isRecurring && price?.recurring?.usage_type !== "metered") {
return `${displayAmount} ${price.recurring ? (0, _util2.formatRecurring)(price.recurring, false, "slash", locale) : ""}`;
}
return displayAmount;
}, [isTrial, pricing, item, locale, dynamicTokenDisplay, isDynamicPricing, pricingSource, isStripePayment]);
const usdReference = (0, _react.useMemo)(() => {
if (!currency || !isDynamicPricing || isStripePayment) {
return null;
}
const baseAmount = pricingSource?.base_amount;
const hasBaseAmount = baseAmount !== void 0 && baseAmount !== null;
if (hasBaseAmount) {
return (0, _util2.getUsdAmountFromBaseAmount)(baseAmount, quantityForUsd);
}
return null;
}, [currency, pricingSource, quantityForUsd, isDynamicPricing, isStripePayment]);
const usdReferenceDisplay = (0, _react.useMemo)(() => (0, _util2.formatUsdAmount)(usdReference, locale), [usdReference, locale]);
const upsellTokenDisplay = (0, _react.useMemo)(() => {
const upsellPrice = item.price?.upsell?.upsells_to;
if (!upsellPrice) {
return null;
}
if (isStripePayment && currency) {
const baseAmount2 = upsellPrice?.base_amount;
const unitAmount2 = upsellPrice?.unit_amount;
let usdAmount2;
if (baseAmount2 !== void 0 && baseAmount2 !== null) {
usdAmount2 = Number(baseAmount2);
} else if (unitAmount2) {
usdAmount2 = Number(unitAmount2) / 100;
} else {
return null;
}
const recurring2 = upsellPrice?.recurring ? ` ${(0, _util2.formatRecurring)(upsellPrice.recurring, false, "slash", locale)}` : "";
return `${usdAmount2} ${currency.symbol}${recurring2}`;
}
if (!currency || !exchangeRate) {
return null;
}
const rate = Number(exchangeRate);
if (rate <= 0 || Number.isNaN(rate)) {
return null;
}
const baseAmount = upsellPrice?.base_amount;
const unitAmount = upsellPrice?.unit_amount;
let usdAmount;
if (baseAmount !== void 0 && baseAmount !== null) {
usdAmount = Number(baseAmount);
} else if (unitAmount) {
usdAmount = Number(unitAmount) / 100;
} else {
return null;
}
const tokenAmount = usdAmount / rate;
const recurring = upsellPrice?.recurring ? ` ${(0, _util2.formatRecurring)(upsellPrice.recurring, false, "slash", locale)}` : "";
return `${(0, _util2.formatDynamicPrice)(tokenAmount, true, 6)} ${currency.symbol}${recurring}`;
}, [isStripePayment, currency, exchangeRate, item.price?.upsell?.upsells_to, locale]);
const downsellTokenDisplay = (0, _react.useMemo)(() => {
if (!item.upsell_price_id || !item.price) {
return null;
}
const originalPrice = item.price;
if (isStripePayment && currency) {
const baseAmount2 = originalPrice?.base_amount;
const unitAmount2 = originalPrice?.unit_amount;
let usdAmount2;
if (baseAmount2 !== void 0 && baseAmount2 !== null) {
usdAmount2 = Number(baseAmount2);
} else if (unitAmount2) {
usdAmount2 = Number(unitAmount2) / 100;
} else {
return null;
}
const recurring2 = originalPrice?.recurring ? ` ${(0, _util2.formatRecurring)(originalPrice.recurring, false, "slash", locale)}` : "";
return `${usdAmount2} ${currency.symbol}${recurring2}`;
}
if (!currency || !exchangeRate) {
return null;
}
const rate = Number(exchangeRate);
if (rate <= 0 || Number.isNaN(rate)) {
return null;
}
const baseAmount = originalPrice?.base_amount;
const unitAmount = originalPrice?.unit_amount;
let usdAmount;
if (baseAmount !== void 0 && baseAmount !== null) {
usdAmount = Number(baseAmount);
} else if (unitAmount) {
usdAmount = Number(unitAmount) / 100;
} else {
return null;
}
const tokenAmount = usdAmount / rate;
const recurring = originalPrice?.recurring ? ` ${(0, _util2.formatRecurring)(originalPrice.recurring, false, "slash", locale)}` : "";
return `${(0, _util2.formatDynamicPrice)(tokenAmount, true, 6)} ${currency.symbol}${recurring}`;
}, [isStripePayment, currency, exchangeRate, item.price, item.upsell_price_id, locale]);
const displayItemDiscountAmount = (0, _react.useMemo)(() => {
if (isTrial && item.price?.type === "recurring" || item.price?.recurring?.usage_type === "metered") {
return null;
}
if (!discounts?.length) {
return null;
}
const couponDetails = discounts[0]?.coupon_details;
if (isStripePayment && couponDetails) {
if (!item.discountable) {
return null;
}
const isDynamic = pricingSource?.pricing_type === "dynamic";
const baseAmount = pricingSource?.base_amount;
const unitAmount = pricingSource?.unit_amount;
let amountCents;
if (isDynamic && baseAmount !== void 0 && baseAmount !== null) {
amountCents = Number(baseAmount) * 100;
} else if (unitAmount) {
amountCents = Number(unitAmount);
} else {
return null;
}
const quantity = localQuantity || item.quantity || 1;
const itemSubtotalCents = amountCents * quantity;
if (couponDetails.percent_off && couponDetails.percent_off > 0) {
const discountCents = Math.round(itemSubtotalCents * couponDetails.percent_off / 100);
return discountCents.toString();
}
if (couponDetails.amount_off && items.length === 1 && calculatedDiscountAmount) {
return calculatedDiscountAmount;
}
return null;
}
if (isDynamicPricing && exchangeRate && couponDetails) {
const rateNum = Number(exchangeRate);
if (rateNum <= 0) {
return null;
}
if (!item.discountable) {
return null;
}
const baseAmount = pricingSource?.base_amount;
if (!baseAmount) {
return null;
}
const quantity = localQuantity || item.quantity || 1;
const itemTokenAmount = Number(baseAmount) * quantity / rateNum;
if (couponDetails.percent_off && couponDetails.percent_off > 0) {
const discountAmount = itemTokenAmount * couponDetails.percent_off / 100;
const discountAmountUnit = (0, _util.fromTokenToUnit)(discountAmount.toFixed(currency.decimal || 8), currency.decimal || 8);
return discountAmountUnit.toString();
}
if (couponDetails.amount_off && items.length === 1 && calculatedDiscountAmount) {
return calculatedDiscountAmount;
}
}
if (items.length === 1 && calculatedDiscountAmount) {
return calculatedDiscountAmount;
}
return null;
}, [isTrial, discounts, isStripePayment, isDynamicPricing, exchangeRate, item, pricingSource, localQuantity, currency.decimal, items.length, calculatedDiscountAmount]);
const discountCodeDisplay = (0, _react.useMemo)(() => {
if (!discounts?.length) return null;
const discount = discounts[0];
return discount.promotion_code_details?.code || discount.verification_data?.code || "DISCOUNT";
}, [discounts]);
const quantityInventoryError = (0, _util2.formatQuantityInventory)(item.price, localQuantityNum, locale);
const features = item.price?.product?.features || [];
return /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "column",
spacing: 1,
className: "product-item",
sx: {
alignItems: "flex-start",
width: "100%"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "column",
className: "product-item-content",
sx: {
alignItems: "flex-start",
width: "100%"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 0.5,
sx: {
alignItems: "center",
flexWrap: "wrap",
justifyContent: "space-between",
width: "100%"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_productCard.default, {
logo: item.price?.product?.images[0],
name: item.price?.product?.name,
extra: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
sx: {
display: "flex",
alignItems: "center"
},
children: item.price?.type === "recurring" && item.price?.recurring ? [pricing.quantity, t("common.billed", {
rule: `${(0, _util2.formatRecurring)(item.upsell_price?.recurring || item.price?.recurring, true, "per", locale)} ${metered}`
})].filter(Boolean).join(", ") : pricing.quantity
})
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "column",
sx: {
alignItems: "flex-end",
flex: 1
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_loadingAmount.default, {
value: primaryText,
loading: isRateLoading,
skeletonWidth: 80,
height: 20,
sx: {
color: "text.primary",
fontWeight: 500
}
}), isDynamicPricing && !isStripePayment && !isTrial && usdReferenceDisplay && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
sx: {
fontSize: "0.74375rem",
color: "text.lighter"
},
children: ["\u2248 $", usdReferenceDisplay]
}), pricing.secondary && !isTrial && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
sx: {
fontSize: "0.74375rem",
color: "text.lighter"
},
children: pricing.secondary
})]
})]
}), !(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, {
direction: "row",
spacing: 1,
sx: {
mt: 1,
alignItems: "center",
opacity: isRateLoading ? 0 : 1,
transition: "opacity 300ms ease-in-out"
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Chip, {
icon: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.LocalOffer, {
sx: {
fontSize: "0.8rem !important"
}
}),
label: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
display: "flex",
alignItems: "center",
gap: 0.5
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
component: "span",
sx: {
fontSize: "0.75rem",
fontWeight: "medium"
},
children: discountCodeDisplay || "DISCOUNT"
}), /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
component: "span",
sx: {
fontSize: "0.75rem"
},
children: ["(-", (0, _util2.formatAmount)(displayItemDiscountAmount || item.discount_amounts?.[0]?.amount || "0", currency.decimal), " ", currency.symbol, ")"]
})]
}),
size: "small",
variant: "filled",
sx: {
height: 20,
"& .MuiChip-icon": {
color: "warning.main"
},
"& .MuiChip-label": {
px: 1
}
}
})
}), showFeatures && features.length > 0 && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
sx: {
display: "flex",
alignItems: "center",
justifyContent: "flex-start",
flexWrap: "wrap",
gap: 1.5,
width: "100%",
mt: 1.5
},
children: features.map(feature => /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Box, {
sx: {
display: "flex",
alignItems: "center",
justifyContent: "center",
gap: 0.5,
px: 1,
py: 0.5,
bgcolor: "action.hover",
borderRadius: 1,
border: "1px solid",
borderColor: "divider"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
sx: {
bgcolor: "success.main",
width: 4,
height: 4,
borderRadius: "50%"
}
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
component: "span",
variant: "caption",
sx: {
color: "text.primary",
fontSize: "0.75rem",
fontWeight: 500,
textTransform: "capitalize",
whiteSpace: "nowrap"
},
children: feature.name
})]
}, feature.name))
}), quantityInventoryError ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, {
label: quantityInventoryError,
variant: "outlined",
sx: {
mt: 1,
borderColor: "chip.error.border",
backgroundColor: "chip.error.background",
color: "chip.error.text"
}
}) : null, canAdjustQuantity && !completed && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Box, {
sx: {
mt: 1,
p: 1
},
children: /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
spacing: 1,
sx: {
alignItems: "center"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
variant: "body2",
sx: {
color: "text.secondary",
minWidth: "fit-content"
},
children: [t("common.quantity"), ":"]
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, {
size: "small",
onClick: handleQuantityDecrease,
disabled: localQuantityNum <= minQuantity,
sx: {
minWidth: 32,
width: 32,
height: 32
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.Remove, {
fontSize: "small"
})
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.TextField, {
size: "small",
value: localQuantity,
onChange: handleQuantityInputChange,
type: "number",
slotProps: {
htmlInput: {
min: minQuantity,
max: maxQuantity,
style: {
textAlign: "center",
padding: "4px",
minWidth: 80
}
}
}
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.IconButton, {
size: "small",
onClick: handleQuantityIncrease,
disabled: localQuantityNum >= maxQuantity,
sx: {
minWidth: 32,
width: 32,
height: 32
},
children: /* @__PURE__ */(0, _jsxRuntime.jsx)(_iconsMaterial.Add, {
fontSize: "small"
})
})]
})
}), isCreditProduct && /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Alert, {
severity: "info",
sx: {
mt: 1,
fontSize: "0.875rem"
},
icon: false,
children: hasSchedule ? /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
component: "span",
sx: {
fontSize: "inherit"
},
children: formatScheduleInfo()
}) : /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
component: "span",
sx: {
fontSize: "inherit"
},
children: formatCreditInfo()
})
}), children]
}), canUpsell && !item.upsell_price_id && item.price?.upsell?.upsells_to && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
className: "product-item-upsell",
sx: {
alignItems: "center",
justifyContent: "space-between",
width: "100%"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
component: "label",
htmlFor: "upsell-switch",
sx: {
fontSize: 12,
cursor: "pointer",
color: "text.secondary"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_switchButton.default, {
id: "upsell-switch",
sx: {
mr: 1
},
variant: "success",
checked: false,
onChange: () => onUpsell(item.price_id, item.price?.upsell?.upsells_to_id)
}), t("payment.checkout.upsell.save", {
recurring: (0, _util2.formatRecurring)(item.price?.upsell?.upsells_to?.recurring, true, "per", locale)
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_status.default, {
label: t("payment.checkout.upsell.off", {
saving
}),
variant: "outlined",
sx: {
ml: 1,
borderColor: "chip.warning.border",
backgroundColor: "chip.warning.background",
color: "chip.warning.text"
}
})]
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
component: "span",
sx: {
fontSize: 12
},
children: upsellTokenDisplay || (0, _util2.formatPrice)(item.price?.upsell?.upsells_to, currency, item.price?.product?.unit_label, 1, true, locale)
})]
}), canUpsell && item.upsell_price_id && /* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Stack, {
direction: "row",
className: "product-item-upsell",
sx: {
alignItems: "center",
justifyContent: "space-between",
width: "100%"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsxs)(_material.Typography, {
component: "label",
htmlFor: "upsell-switch",
sx: {
fontSize: 12,
cursor: "pointer",
color: "text.secondary"
},
children: [/* @__PURE__ */(0, _jsxRuntime.jsx)(_switchButton.default, {
id: "upsell-switch",
sx: {
mr: 1
},
variant: "success",
checked: true,
onChange: () => onDownsell(item.upsell_price_id)
}), t("payment.checkout.upsell.revert", {
recurring: t(`common.${(0, _util2.formatRecurring)(item.price?.recurring)}`)
})]
}), /* @__PURE__ */(0, _jsxRuntime.jsx)(_material.Typography, {
component: "span",
sx: {
fontSize: 12
},
children: downsellTokenDisplay || (0, _util2.formatPrice)(item.price, currency, item.price?.product?.unit_label, 1, true, locale)
})]
})]
});
}