UNPKG

@blocklet/payment-react

Version:

Reusable react components for payment kit v2

1,538 lines (1,537 loc) 54.6 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.PAYMENT_KIT_DID = void 0; exports.findCurrency = findCurrency; exports.flattenPaymentMethods = void 0; exports.formatAmount = formatAmount; exports.formatAmountPrecisionLimit = formatAmountPrecisionLimit; exports.formatBNStr = formatBNStr; exports.formatCheckoutHeadlines = formatCheckoutHeadlines; exports.formatCouponTerms = void 0; exports.formatCreditAmount = formatCreditAmount; exports.formatCreditForCheckout = formatCreditForCheckout; exports.formatDateTime = formatDateTime; exports.formatDynamicPrice = formatDynamicPrice; exports.formatError = void 0; exports.formatExchangeRate = formatExchangeRate; exports.formatExchangeRateDisplay = formatExchangeRateDisplay; exports.formatLineItemPricing = formatLineItemPricing; exports.formatLinkWithLocale = formatLinkWithLocale; exports.formatLocale = void 0; exports.formatMeteredThen = formatMeteredThen; exports.formatNumber = formatNumber; exports.formatPriceAmount = exports.formatPrice = exports.formatPrettyMsLocale = void 0; exports.formatPriceDisplay = formatPriceDisplay; exports.formatQuantityInventory = formatQuantityInventory; exports.formatRecurring = formatRecurring; exports.formatSubscriptionProduct = formatSubscriptionProduct; exports.formatSubscriptionStatus = formatSubscriptionStatus; exports.formatThenValue = formatThenValue; exports.formatTime = formatTime; exports.formatToDate = formatToDate; exports.formatToDatetime = formatToDatetime; exports.formatTotalPrice = formatTotalPrice; exports.formatUpsellSaving = formatUpsellSaving; exports.formatUsdAmount = formatUsdAmount; exports.getCheckoutAmount = getCheckoutAmount; exports.getCustomerAvatar = getCustomerAvatar; exports.getFreeTrialTime = getFreeTrialTime; exports.getInvoiceDescriptionAndReason = getInvoiceDescriptionAndReason; exports.getInvoiceStatusColor = getInvoiceStatusColor; exports.getLineItemAmounts = getLineItemAmounts; exports.getLineTimeInfo = void 0; exports.getPaymentIntentStatusColor = getPaymentIntentStatusColor; exports.getPaymentKitComponent = getPaymentKitComponent; exports.getPayoutStatusColor = getPayoutStatusColor; exports.getPrefix = void 0; exports.getPriceCurrencyOptions = getPriceCurrencyOptions; exports.getPriceUintAmountByCurrency = getPriceUintAmountByCurrency; exports.getQueryParams = getQueryParams; exports.getQuoteLockInfo = getQuoteLockInfo; exports.getRecurringPeriod = getRecurringPeriod; exports.getRefundStatusColor = getRefundStatusColor; exports.getStatementDescriptor = getStatementDescriptor; exports.getSubscriptionAction = void 0; exports.getSubscriptionStatusColor = getSubscriptionStatusColor; exports.getSubscriptionTimeSummary = void 0; exports.getTokenBalanceLink = getTokenBalanceLink; exports.getTxLink = void 0; exports.getUsdAmountFromBaseAmount = getUsdAmountFromBaseAmount; exports.getUsdAmountFromTokenUnits = getUsdAmountFromTokenUnits; exports.getUserProfileLink = getUserProfileLink; exports.getWebhookStatusColor = getWebhookStatusColor; exports.getWordBreakStyle = getWordBreakStyle; exports.hasDelegateTxHash = hasDelegateTxHash; exports.hasMultipleRecurringIntervals = hasMultipleRecurringIntervals; exports.isCreditMetered = isCreditMetered; exports.isCrossOrigin = isCrossOrigin; exports.isMobileSafari = isMobileSafari; exports.isPaymentKitMounted = void 0; exports.isValidCountry = isValidCountry; exports.lazyLoad = lazyLoad; exports.mergeExtraParams = void 0; exports.openDonationSettings = openDonationSettings; exports.parseMarkedText = parseMarkedText; exports.primaryContrastColor = primaryContrastColor; exports.showStaking = showStaking; exports.sleep = sleep; exports.stopEvent = stopEvent; exports.truncateText = truncateText; var _util = require("@ocap/util"); var _omit = _interopRequireDefault(require("lodash/omit")); var _trimEnd = _interopRequireDefault(require("lodash/trimEnd")); var _numbro = _interopRequireDefault(require("numbro")); var _stringWidth = _interopRequireDefault(require("string-width")); var _reactInternationalPhone = require("react-international-phone"); var _ufo = require("ufo"); var _locales = require("../locales"); var _dayjs = _interopRequireDefault(require("./dayjs")); function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; } const PAYMENT_KIT_DID = exports.PAYMENT_KIT_DID = "z2qaCNvKMv5GjouKdcDWexv6WqtHbpNPQDnAk"; const formatCouponTerms = (coupon, currency, locale = "en") => { let couponOff = ""; if (coupon.percent_off && coupon.percent_off > 0) { couponOff = (0, _locales.t)("payment.checkout.coupon.percentage", locale, { percent: coupon.percent_off }); } if (coupon.amount_off && coupon.amount_off !== "0") { const { symbol } = currency; couponOff = coupon.currency_id === currency.id ? coupon.amount_off || "" : coupon.currency_options?.[currency.id]?.amount_off || ""; if (couponOff) { couponOff = (0, _locales.t)("payment.checkout.coupon.fixedAmount", locale, { amount: formatAmount(couponOff, currency.decimal), symbol }); } } if (!couponOff) { return (0, _locales.t)("payment.checkout.coupon.noDiscount"); } return (0, _locales.t)(`payment.checkout.coupon.terms.${coupon.duration}`, locale, { couponOff, months: coupon.duration_in_months || 0 }); }; exports.formatCouponTerms = formatCouponTerms; const isPaymentKitMounted = () => { return (window.blocklet?.componentMountPoints || []).some(x => x.did === PAYMENT_KIT_DID); }; exports.isPaymentKitMounted = isPaymentKitMounted; const getPrefix = () => { const prefix = window.blocklet?.prefix || "/"; const baseUrl = window.location?.origin; if (window.__PAYMENT_KIT_BASE_URL) { try { const tmp = new URL(window.__PAYMENT_KIT_BASE_URL); if (tmp.origin !== window.location.origin) { return window.__PAYMENT_KIT_BASE_URL; } } catch (err) { console.warn("Invalid baseUrl for PaymentProvider", window.__PAYMENT_KIT_BASE_URL); } } const componentId = (window.blocklet?.componentId || "").split("/").pop(); if (componentId === PAYMENT_KIT_DID) { return (0, _ufo.joinURL)(baseUrl, prefix); } const component = (window.blocklet?.componentMountPoints || []).find(x => x?.did === PAYMENT_KIT_DID); if (component) { return (0, _ufo.joinURL)(baseUrl, component.mountPoint); } return (0, _ufo.joinURL)(baseUrl, prefix); }; exports.getPrefix = getPrefix; function isCrossOrigin() { try { const prefix = getPrefix(); const prefixOrigin = new URL(prefix).origin; const currentOrigin = window.location.origin; return prefixOrigin !== currentOrigin; } catch (error) { return false; } } function formatToDate(date, locale = "en", format = "YYYY-MM-DD HH:mm:ss") { if (!date) { return "-"; } return (0, _dayjs.default)(date).locale(formatLocale(locale)).format(format); } function formatToDatetime(date, locale = "en") { if (!date) { return "-"; } return (0, _dayjs.default)(date).locale(formatLocale(locale)).format("lll"); } function formatTime(date, format = "YYYY-MM-DD HH:mm:ss", locale = "en") { if (!date) { return "-"; } return (0, _dayjs.default)(date).locale(formatLocale(locale)).format(format); } function formatDateTime(date, locale = "en") { return (0, _dayjs.default)(date).locale(formatLocale(locale)).format("YYYY-MM-DD HH:mm"); } const formatLocale = (locale = "en") => { if (locale === "tw") { return "zh"; } return locale; }; exports.formatLocale = formatLocale; const formatPrettyMsLocale = locale => locale === "zh" ? "zh_CN" : "en_US"; exports.formatPrettyMsLocale = formatPrettyMsLocale; const formatError = err => { if (!err) { return "Unknown error"; } const { details, errors, response } = err; if (Array.isArray(errors)) { return errors.map(x => x.message).join("\n"); } if (Array.isArray(details)) { const formatted = details.map(e => { const errorMessage = e.message.replace(/["]/g, "'"); const errorPath = e.path.join("."); return `${errorPath}: ${errorMessage}`; }); return `Validate failed: ${formatted.join(";")}`; } if (response) { return response.data?.error || `${err.message}: ${JSON.stringify(response.data)}`; } return err.message; }; exports.formatError = formatError; function formatBNStr(str = "", decimals = 18, precision = 6, trim = true, thousandSeparated = true) { if (!str) { return "0"; } return formatNumber((0, _util.fromUnitToToken)(str, decimals), precision, trim, thousandSeparated); } const CURRENCY_SYMBOLS = { USD: "$", EUR: "\u20AC", GBP: "\xA3", JPY: "\xA5", CNY: "\xA5", KRW: "\u20A9", INR: "\u20B9", AUD: "A$", CAD: "C$", CHF: "Fr", HKD: "HK$", SGD: "S$", NZD: "NZ$" }; function formatCreditAmount(formattedAmount, currencySymbol, showUnit = true) { const mappedSymbol = CURRENCY_SYMBOLS[currencySymbol]; if (mappedSymbol) { return `${mappedSymbol}${formattedAmount}`; } return `${formattedAmount} ${showUnit ? currencySymbol : ""}`; } function formatCreditForCheckout(formattedAmount, currencySymbol, locale = "en", showUnit = true) { const mappedSymbol = CURRENCY_SYMBOLS[currencySymbol]; if (mappedSymbol) { return `${mappedSymbol}${formattedAmount} ${showUnit ? (0, _locales.t)("common.credits", locale) : ""}`; } return `${formattedAmount} ${showUnit ? currencySymbol : ""}`; } function formatNumber(n, precision = 6, trim = true, thousandSeparated = true) { if (!n || n === "0") { return "0"; } const num = (0, _numbro.default)(n); const options = { thousandSeparated, ...((precision || precision === 0) && { mantissa: precision }) }; const result = num.format(options); if (!trim) { return result; } const [left, right] = result.split("."); return right ? [left, (0, _trimEnd.default)(right, "0")].filter(Boolean).join(".") : left; } function formatDynamicPrice(n, isDynamic, precision = 6, trim = true, thousandSeparated = true) { if (!isDynamic) { return formatNumber(n, precision, trim, thousandSeparated); } const num = Number(n); if (!Number.isFinite(num)) { return formatNumber(n, precision, trim, thousandSeparated); } const abs = Math.abs(num); const targetPrecision = abs > 0 && abs < 0.01 ? precision : 2; return formatNumber(n, targetPrecision, trim, thousandSeparated); } const USD_DECIMALS = 8; function getUsdAmountFromBaseAmount(amount, quantity, scale = BASE_AMOUNT_SCALE) { if (amount === void 0 || amount === null || !quantity || quantity <= 0) { return null; } const scaled = toScaledBaseAmount(amount, quantity, scale); return formatScaledAmount(scaled, scale); } function getUsdAmountFromTokenUnits(tokenAmount, tokenDecimals, exchangeRate) { if (!exchangeRate && exchangeRate !== "0") { return null; } if (tokenDecimals === void 0 || tokenDecimals === null) { return null; } try { const amountBN = tokenAmount instanceof _util.BN ? tokenAmount : new _util.BN(tokenAmount || "0"); const tokenScale = new _util.BN(10).pow(new _util.BN(tokenDecimals)); if (tokenScale.isZero()) { return null; } const rateBN = new _util.BN(exchangeRate.replace(".", "")); const rateDecimalPlaces = exchangeRate.includes(".") ? exchangeRate.split(".")[1]?.length || 0 : 0; const rateScale = new _util.BN(10).pow(new _util.BN(rateDecimalPlaces)); const usdPrecisionScale = new _util.BN(10).pow(new _util.BN(USD_DECIMALS)); const usdUnit = amountBN.mul(rateBN).mul(usdPrecisionScale).div(tokenScale.mul(rateScale)); return (0, _util.fromUnitToToken)(usdUnit.toString(), USD_DECIMALS); } catch { return null; } } function formatUsdAmount(amount, locale = "en") { if (!amount && amount !== "0") { return null; } const num = Number(amount); if (!Number.isFinite(num)) { return null; } return new Intl.NumberFormat(locale, { minimumFractionDigits: 2, maximumFractionDigits: 2 }).format(num); } function formatExchangeRate(amount) { if (!amount && amount !== "0") { return null; } const value = String(amount); const num = Number(value); if (!Number.isFinite(num)) { return null; } return value; } function formatExchangeRateDisplay(rate, currency = "USD", decimals = 2) { if (rate === null || rate === void 0) { return null; } const num = Number(rate); if (!Number.isFinite(num)) { return null; } const formattedValue = num.toFixed(decimals); if (currency === "USD") { return `$${formattedValue}`; } return `${formattedValue} ${currency}`; } const formatPrice = (price, currency, unit_label, quantity = 1, bn = true, locale = "en") => { if (!currency) { return ""; } if (price.custom_unit_amount) { return `Custom (${currency.symbol})`; } if (price.pricing_type === "dynamic" && price.base_amount && price.base_currency === "USD") { const baseAmount = price.base_amount; const usdAmount = Number(baseAmount) * quantity; const formattedUsd = formatUsdAmount(usdAmount.toString(), locale); if (price?.type === "recurring" && price.recurring) { const recurring = formatRecurring(price.recurring, false, "slash", locale); if (unit_label) { return `$${formattedUsd} / ${unit_label} ${recurring}`; } if (price.recurring.usage_type === "metered") { return `$${formattedUsd} / unit ${recurring}`; } return `$${formattedUsd} ${recurring}`; } return `$${formattedUsd}`; } const unit = getPriceUintAmountByCurrency(price, currency); const amount = bn ? (0, _util.fromUnitToToken)(new _util.BN(unit).mul(new _util.BN(quantity)), currency.decimal).toString() : +unit * quantity; if (price?.type === "recurring" && price.recurring) { const recurring = formatRecurring(price.recurring, false, "slash", locale); if (unit_label) { return `${amount} ${currency.symbol} / ${unit_label} ${recurring}`; } if (price.recurring.usage_type === "metered") { return `${amount} ${currency.symbol} / unit ${recurring}`; } return `${amount} ${currency.symbol} ${recurring}`; } return `${amount} ${currency.symbol}`; }; exports.formatPrice = formatPrice; const formatPriceAmount = (price, currency, unit_label, quantity = 1, bn = true, locale = "en") => { if (!currency) { return ""; } if (price.pricing_type === "dynamic" && price.base_amount && price.base_currency === "USD") { const baseAmount = price.base_amount; const usdAmount = Number(baseAmount) * quantity; const formattedUsd = formatUsdAmount(usdAmount.toString(), locale); if (price?.type === "recurring" && price.recurring) { if (unit_label) { return `$${formattedUsd} / ${unit_label}`; } if (price.recurring.usage_type === "metered") { return `$${formattedUsd} / unit`; } return `$${formattedUsd}`; } return `$${formattedUsd}`; } const unit = getPriceUintAmountByCurrency(price, currency); const amount = bn ? (0, _util.fromUnitToToken)(new _util.BN(unit).mul(new _util.BN(quantity)), currency.decimal).toString() : +unit * quantity; if (price?.type === "recurring" && price.recurring) { if (unit_label) { return `${amount} ${currency.symbol} / ${unit_label}`; } if (price.recurring.usage_type === "metered") { return `${amount} ${currency.symbol} / unit`; } return `${amount} ${currency.symbol}`; } return `${amount} ${currency.symbol}`; }; exports.formatPriceAmount = formatPriceAmount; function getStatementDescriptor(items) { for (const item of items) { if (item.price?.product?.statement_descriptor) { return item.price?.product?.statement_descriptor; } } return window.blocklet?.appName; } function formatRecurring(recurring, translate = true, separator = "per", locale = "en") { const intervals = { hour: "hourly", day: "daily", week: "weekly", month: "monthly", year: "yearly" }; if (!recurring) { return ""; } if (+recurring.interval_count === 1) { const interval = (0, _locales.t)(`common.${recurring.interval}`, locale); return translate ? (0, _locales.t)(`common.${intervals[recurring.interval]}`, locale) : separator ? (0, _locales.t)(`common.${separator}`, locale, { interval }) : interval; } if (recurring.interval === "month") { if (recurring.interval_count === 3) { return (0, _locales.t)("common.month3", locale); } if (recurring.interval_count === 6) { return (0, _locales.t)("common.month6", locale); } } return (0, _locales.t)("common.recurring", locale, { count: recurring.interval_count, interval: (0, _locales.t)(`common.${recurring.interval}s`, locale) }); } function getPriceUintAmountByCurrency(price, currency) { const options = getPriceCurrencyOptions(price); const option = options.find(x => x.currency_id === currency?.id); if (option) { if (option.custom_unit_amount) { return option.custom_unit_amount.preset || option.custom_unit_amount.presets[0]; } return option.unit_amount; } if (price.currency_id === currency?.id) { if (price.custom_unit_amount) { return price.custom_unit_amount.preset || price.custom_unit_amount.presets[0]; } return price.unit_amount; } console.warn(`Currency ${currency?.id} not configured for price`, price); return "0"; } function getPriceCurrencyOptions(price) { if (Array.isArray(price.currency_options)) { return price.currency_options; } return [{ currency_id: price.currency_id, unit_amount: price.unit_amount, custom_unit_amount: price.custom_unit_amount || null, tiers: null }]; } const BASE_AMOUNT_SCALE = 8; function formatScaledAmount(value, scale = BASE_AMOUNT_SCALE) { const isNegative = value.isNeg(); const absValue = value.abs(); const str = absValue.toString().padStart(scale + 1, "0"); const integerPart = str.slice(0, str.length - scale) || "0"; const fraction = str.slice(-scale).replace(/0+$/, ""); const formatted = fraction ? `${integerPart}.${fraction}` : integerPart; return isNegative ? `-${formatted}` : formatted; } function toScaledBaseAmount(amount, quantity, scale = BASE_AMOUNT_SCALE) { if (!amount || !quantity) { return new _util.BN(0); } const normalized = String(amount); const [integer = "0", fraction = ""] = normalized.split("."); const frac = `${fraction}`.padEnd(scale, "0").slice(0, scale); const digits = `${integer.replace("-", "")}${frac}` || "0"; const scaled = new _util.BN(digits).mul(new _util.BN(quantity)); return normalized.startsWith("-") ? scaled.neg() : scaled; } function getLineItemAmounts(item, currency, { useUpsell = true, exchangeRate = null } = {}) { if (!currency) { return { unitAmount: new _util.BN(0), totalAmount: new _util.BN(0), isDynamicQuote: false }; } const price = useUpsell ? item.upsell_price || item.price : item.price; const quantity = new _util.BN(item.quantity || 0); const quoteAmount = item?.quoted_amount; const quoteCurrencyId = item?.quote_currency_id; const isDynamicPrice = price?.pricing_type === "dynamic"; const isQuoteValidForCurrency = isDynamicPrice && quoteAmount && quoteCurrencyId === currency.id; if (isQuoteValidForCurrency) { const totalAmount = new _util.BN(quoteAmount || "0"); const unitAmount2 = quantity.gt(new _util.BN(0)) ? totalAmount.add(quantity).sub(new _util.BN(1)).div(quantity) : new _util.BN(0); return { unitAmount: unitAmount2, totalAmount, isDynamicQuote: true }; } if (isDynamicPrice && exchangeRate && price?.base_amount) { const rate = Number(exchangeRate); if (rate > 0 && Number.isFinite(rate)) { const baseAmountUsd = Number(price.base_amount); if (baseAmountUsd > 0 && Number.isFinite(baseAmountUsd)) { const tokenAmount = baseAmountUsd / rate; const unitAmount2 = (0, _util.fromTokenToUnit)(tokenAmount.toFixed(currency.decimal || 8), currency.decimal || 8); return { unitAmount: unitAmount2, totalAmount: unitAmount2.mul(quantity), isDynamicQuote: false }; } } } const unitAmount = new _util.BN(getPriceUintAmountByCurrency(price, currency)); return { unitAmount, totalAmount: unitAmount.mul(quantity), isDynamicQuote: false }; } function getQuoteLockInfo(items, currency) { if (!items?.length || !currency) { return null; } const dynamicItems = items.filter(item => { const price = item.upsell_price || item.price; return price?.pricing_type === "dynamic" && item?.quoted_amount; }); if (!dynamicItems.length) { return null; } let totalBaseAmount = new _util.BN(0); let totalTokenAmount = new _util.BN(0); let expiresAt = null; dynamicItems.forEach(item => { const price = item.upsell_price || item.price; const quoteAmount = new _util.BN(item?.quoted_amount || "0"); totalTokenAmount = totalTokenAmount.add(quoteAmount); const baseAmount = price?.base_amount; if (baseAmount) { totalBaseAmount = totalBaseAmount.add(toScaledBaseAmount(baseAmount, item.quantity)); } if (item?.expires_at) { expiresAt = expiresAt === null ? item?.expires_at : Math.min(expiresAt, item?.expires_at); } }); return { baseAmount: formatScaledAmount(totalBaseAmount, BASE_AMOUNT_SCALE), baseCurrency: (dynamicItems[0]?.upsell_price || dynamicItems[0]?.price)?.base_currency || "USD", tokenAmount: (0, _util.fromUnitToToken)(totalTokenAmount, currency.decimal).toString(), tokenSymbol: currency.symbol, expiresAt }; } function formatLineItemPricing(item, currency, { trialEnd, trialInDays, exchangeRate = null }, locale = "en") { if (!currency) { return { primary: "", secondary: "", quantity: "" }; } const price = item.upsell_price || item.price; if (!price) { return { primary: "", secondary: "", quantity: "" }; } let quantity = (0, _locales.t)("common.qty", locale, { count: item.quantity }); if (price.recurring?.usage_type === "metered" || +item.quantity === 1) { quantity = ""; } const { unitAmount, totalAmount } = getLineItemAmounts(item, currency, { exchangeRate }); const isDynamic = price?.pricing_type === "dynamic"; const formatLineItemAmount = bn => { const value = (0, _util.fromUnitToToken)(bn, currency.decimal); return formatDynamicPrice(value, isDynamic, 6); }; const total = `${formatLineItemAmount(totalAmount)} ${currency.symbol}`; const unit = `${formatLineItemAmount(unitAmount)} ${currency.symbol}`; const trialResult = getFreeTrialTime({ trialInDays, trialEnd }, locale); const appendUnit = (v, alt) => { if (price.product.unit_label) { return `${v}/${price.product.unit_label}`; } if (price.recurring?.usage_type === "metered" || item.quantity === 1) { return alt; } return quantity ? (0, _locales.t)("common.each", locale, { unit }) : ""; }; if (price.type === "recurring" && price.recurring) { if (trialResult.count > 0) { return { primary: (0, _locales.t)("common.trial", locale, { count: trialResult.count, interval: trialResult.interval }), secondary: `${appendUnit(total, total)} ${formatRecurring(price.recurring, false, "slash", locale)}`, quantity }; } return { primary: total, secondary: appendUnit(total, ""), quantity }; } return { primary: total, secondary: appendUnit(total, ""), quantity }; } function getSubscriptionStatusColor(status) { switch (status) { case "active": return "success"; case "trialing": return "primary"; case "incomplete": case "incomplete_expired": case "paused": return "warning"; case "past_due": case "unpaid": return "error"; case "canceled": default: return "default"; } } function getPaymentIntentStatusColor(status) { switch (status) { case "succeeded": return "success"; case "requires_payment_method": case "requires_confirmation": case "requires_action": case "requires_capture": return "warning"; case "canceled": case "processing": default: return "default"; } } function getRefundStatusColor(status) { switch (status) { case "succeeded": return "success"; case "requires_action": return "warning"; case "canceled": case "pending": default: return "default"; } } function getPayoutStatusColor(status) { switch (status) { case "paid": return "success"; case "deferred": return "warning"; case "failed": return "warning"; case "canceled": case "pending": case "in_transit": default: return "default"; } } function getInvoiceStatusColor(status) { switch (status) { case "paid": return "success"; case "open": return "secondary"; case "uncollectible": return "warning"; case "draft": case "void": default: return "default"; } } function getWebhookStatusColor(status) { switch (status) { case "enabled": return "success"; case "disabled": default: return "default"; } } function getCheckoutAmount(items, currency, trialing = false, upsell = true, { exchangeRate = null } = {}) { if (items.find(x => { const price = upsell ? x.upsell_price || x.price : x.price; return price?.custom_unit_amount; }) && items.length > 1) { throw new Error("Multiple items with custom unit amount are not supported"); } let renew = new _util.BN(0); const total = items.filter(x => { const price = upsell ? x.upsell_price || x.price : x.price; return price != null; }).reduce((acc, x) => { const quoteCurrencyId = x?.quote_currency_id; const isQuoteForDifferentCurrency = quoteCurrencyId && quoteCurrencyId !== currency.id; if (x.custom_amount && !isQuoteForDifferentCurrency) { return acc.add(new _util.BN(x.custom_amount)); } const price = upsell ? x.upsell_price || x.price : x.price; const { totalAmount } = getLineItemAmounts(x, currency, { useUpsell: upsell, exchangeRate }); if (price?.type === "recurring") { renew = renew.add(totalAmount); if (trialing) { return acc; } if (price.recurring?.usage_type === "metered") { return acc; } } return acc.add(totalAmount); }, new _util.BN(0)).toString(); return { subtotal: total, total, renew: formatDynamicPrice(renew.toString(), !!exchangeRate), discount: "0", shipping: "0", tax: "0" }; } function getRecurringPeriod(recurring) { const { interval } = recurring; const count = +recurring.interval_count || 1; const dayInMs = 24 * 60 * 60 * 1e3; switch (interval) { case "hour": return 60 * 60 * 1e3; case "day": return count * dayInMs; case "week": return count * 7 * dayInMs; case "month": return count * 30 * dayInMs; case "year": return count * 365 * dayInMs; default: throw new Error(`Unsupported recurring interval: ${interval}`); } } function formatUpsellSaving(items, currency) { if (items[0]?.upsell_price_id) { return "0"; } if (!items[0]?.price?.upsell?.upsells_to) { return "0"; } const from = getCheckoutAmount(items, currency, false, false); const to = getCheckoutAmount(items.map(x => ({ ...x, upsell_price_id: x.price.upsell?.upsells_to_id, upsell_price: x.price.upsell?.upsells_to })), currency, false, true); const fromRecurring = items[0].price?.recurring; const toRecurring = items[0].price?.upsell?.upsells_to?.recurring; if (!fromRecurring || !toRecurring) { return "0"; } const factor = Math.floor(getRecurringPeriod(toRecurring) / getRecurringPeriod(fromRecurring)); const before = new _util.BN(from.total).mul(new _util.BN(factor)); const after = new _util.BN(to.total); return Number(before.sub(after).mul(new _util.BN(100)).div(before).toString()).toFixed(0); } function formatMeteredThen(subscription, recurring, hasMetered, locale = "en") { if (hasMetered) { return (0, _locales.t)("payment.checkout.meteredThen", locale, { subscription, recurring }); } return (0, _locales.t)("payment.checkout.then", locale, { subscription, recurring }); } function formatThenValue(subscription, recurring, hasMetered, locale = "en") { if (hasMetered) { return (0, _locales.t)("payment.checkout.metered", locale, { recurring }); } return [subscription, recurring].filter(Boolean).join(" "); } function formatPriceDisplay({ amount, then, actualAmount, showThen }, recurring, hasMetered, locale = "en") { if (Number(actualAmount) === 0 && hasMetered) { return (0, _locales.t)("payment.checkout.metered", locale, { recurring }); } if (showThen) { return [amount, then].filter(Boolean).join(", "); } return [amount, then].filter(Boolean).join(" "); } function hasMultipleRecurringIntervals(items) { const intervals = /* @__PURE__ */new Set(); for (const item of items) { if (item.price?.recurring?.interval && item.price?.type === "recurring") { intervals.add(`${item.price.recurring.interval}-${item.price.recurring.interval_count}`); if (intervals.size > 1) { return true; } } } return false; } function getFreeTrialTime({ trialInDays, trialEnd }, locale = "en") { const now = (0, _dayjs.default)().unix(); const plural = (singular, count) => (0, _locales.t)(`common.${count > 1 ? `${singular}s` : singular}`, locale); if (trialEnd > 0 && trialEnd > now) { if (trialEnd - now < 3600) { const count2 = Math.ceil((trialEnd - now) / 60); return { count: count2, interval: plural("minute", count2) }; } if (trialEnd - now < 86400) { const count2 = Math.ceil((trialEnd - now) / 3600); return { count: count2, interval: plural("hour", count2) }; } const count = Math.ceil((trialEnd - now) / 86400); return { count, interval: plural("day", count) }; } if (trialInDays > 0) { return { count: trialInDays, interval: plural("day", trialInDays) }; } return { count: 0, interval: (0, _locales.t)("common.day", locale) }; } function formatCheckoutHeadlines(items, currency, { trialInDays, trialEnd }, locale = "en", { exchangeRate = null } = {}) { const brand = getStatementDescriptor(items); const { total } = getCheckoutAmount(items, currency, trialInDays > 0, true, { exchangeRate }); const actualAmount = (0, _util.fromUnitToToken)(total, currency.decimal); const amount = `${(0, _util.fromUnitToToken)(total, currency.decimal)} ${currency.symbol}`; const trialResult = getFreeTrialTime({ trialInDays, trialEnd }, locale); if (items.length === 0) { return { action: (0, _locales.t)("payment.checkout.empty", locale), amount: "0", then: "", actualAmount: "0", priceDisplay: "0" }; } const { name } = items[0]?.price?.product || { name: "" }; if (items.every(x => x.price?.type === "one_time")) { const action = (0, _locales.t)("payment.checkout.pay", locale, { payee: brand }); if (items.length > 1) { return { action, amount, actualAmount, priceDisplay: amount }; } return { action, amount, then: "", actualAmount, priceDisplay: amount }; } const item = items.find(x => x.price?.type === "recurring"); const recurring = formatRecurring((item?.upsell_price || item?.price)?.recurring, false, "per", locale); const hasMetered = items.some(x => x.price?.type === "recurring" && x.price?.recurring?.usage_type === "metered"); const differentRecurring = hasMultipleRecurringIntervals(items); if (items.every(x => x.price?.type === "recurring")) { const subscription2 = [hasMetered ? (0, _locales.t)("payment.checkout.least", locale) : "", formatDynamicPrice((0, _util.fromUnitToToken)(items.reduce((acc, x) => { const price = x.upsell_price || x.price; if (price?.recurring?.usage_type === "metered") { return acc; } return acc.add(getLineItemAmounts(x, currency, { exchangeRate }).totalAmount); }, new _util.BN(0)), currency.decimal), !!exchangeRate), currency.symbol].filter(Boolean).join(" "); if (items.length > 1) { if (trialResult.count > 0) { const thenDisplay4 = formatMeteredThen(subscription2, recurring, hasMetered && Number(subscription2) === 0, locale); const thenValue4 = formatThenValue(subscription2, recurring, hasMetered && Number(subscription2) === 0, locale); const result4 = { action: (0, _locales.t)("payment.checkout.try2", locale, { name, count: items.length - 1 }), amount: (0, _locales.t)("payment.checkout.free", locale, { count: trialResult.count, interval: trialResult.interval }), then: thenDisplay4, thenValue: thenValue4, showThen: true, actualAmount: "0" }; return { ...result4, priceDisplay: formatPriceDisplay({ amount: result4.amount, then: thenDisplay4, actualAmount: result4.actualAmount, showThen: result4.showThen }, recurring, hasMetered, locale) }; } const thenDisplay3 = hasMetered ? (0, _locales.t)("payment.checkout.meteredThen", locale, { recurring }) : recurring; const thenValue3 = hasMetered ? (0, _locales.t)("payment.checkout.metered", locale, { recurring }) : recurring; const result3 = { action: (0, _locales.t)("payment.checkout.sub2", locale, { name, count: items.length - 1 }), amount, then: thenDisplay3, thenValue: thenValue3, showThen: hasMetered, actualAmount }; return { ...result3, priceDisplay: formatPriceDisplay({ amount: result3.amount, then: thenDisplay3, actualAmount: result3.actualAmount, showThen: result3.showThen }, recurring, hasMetered, locale) }; } if (trialResult.count > 0) { const thenDisplay3 = formatMeteredThen(subscription2, recurring, hasMetered && Number(subscription2) === 0, locale); const thenValue3 = formatThenValue(subscription2, recurring, hasMetered && Number(subscription2) === 0, locale); const result3 = { action: (0, _locales.t)("payment.checkout.try1", locale, { name }), amount: (0, _locales.t)("payment.checkout.free", locale, { count: trialResult.count, interval: trialResult.interval }), then: thenDisplay3, thenValue: thenValue3, showThen: true, actualAmount: "0" }; return { ...result3, priceDisplay: formatPriceDisplay({ amount: result3.amount, then: thenDisplay3, actualAmount: result3.actualAmount, showThen: result3.showThen }, recurring, hasMetered, locale) }; } const thenDisplay2 = hasMetered ? (0, _locales.t)("payment.checkout.meteredThen", locale, { recurring }) : recurring; const thenValue2 = hasMetered ? (0, _locales.t)("payment.checkout.metered", locale, { recurring }) : recurring; const result2 = { action: (0, _locales.t)("payment.checkout.sub1", locale, { name }), amount, then: thenDisplay2, thenValue: thenValue2, showThen: hasMetered && !differentRecurring, actualAmount }; return { ...result2, priceDisplay: formatPriceDisplay({ amount: result2.amount, then: thenDisplay2, actualAmount: result2.actualAmount, showThen: result2.showThen }, recurring, hasMetered, locale) }; } const subscription = (0, _util.fromUnitToToken)(items.filter(x => x.price?.type === "recurring").reduce((acc, x) => { if (x.price?.recurring?.usage_type === "metered") { return acc; } return acc.add(getLineItemAmounts(x, currency, { useUpsell: false, exchangeRate }).totalAmount); }, new _util.BN(0)), currency.decimal); const thenDisplay = formatMeteredThen(`${subscription} ${currency.symbol}`, recurring, hasMetered && Number(subscription) === 0, locale); const thenValue = formatThenValue(`${subscription} ${currency.symbol}`, recurring, hasMetered && Number(subscription) === 0, locale); const result = { action: (0, _locales.t)("payment.checkout.pay", locale, { payee: brand }), amount, then: thenDisplay, thenValue, showThen: !differentRecurring, actualAmount }; return { ...result, priceDisplay: formatPriceDisplay({ amount: result.amount, then: thenDisplay, actualAmount: result.actualAmount, showThen: result.showThen }, recurring, hasMetered, locale) }; } function formatAmount(amount, decimals, precision = 2) { const tokenAmount = (0, _util.fromUnitToToken)(amount, decimals); const numericValue = Number(tokenAmount); if (!Number.isFinite(numericValue)) { return formatBNStr(amount, decimals, precision, true, false); } const abs = Math.abs(numericValue); const targetPrecision = abs > 0 && abs < 0.01 ? decimals : 2; return formatNumber(tokenAmount, targetPrecision, true, false); } function findCurrency(methods, currencyId) { for (const method of methods) { for (const currency of method.payment_currencies) { if (currency.id === currencyId) { return { object: "payment_currency", ...currency, paymentMethod: (0, _omit.default)(method, ["payment_currencies"]) }; } } } return null; } function isValidCountry(code) { return _reactInternationalPhone.defaultCountries.some(x => x[1] === code); } function stopEvent(e) { try { e.stopPropagation(); e.preventDefault(); } catch {} } function sleep(ms) { return new Promise(resolve => { setTimeout(resolve, ms); }); } function formatSubscriptionProduct(items, maxLength = 2) { const names = items.map(x => x.price?.product?.name).filter(Boolean); return names.slice(0, maxLength).join(", ") + (names.length > maxLength ? ` and ${names.length - maxLength} more` : ""); } const getLineTimeInfo = (time, locale = "en") => { const isToday = (0, _dayjs.default)().isSame((0, _dayjs.default)(time), "day"); const timeFormat = isToday ? "HH:mm:ss" : "YYYY-MM-DD"; return { time: formatToDate(time, locale, timeFormat), isToday }; }; exports.getLineTimeInfo = getLineTimeInfo; const getSubscriptionTimeSummary = (subscription, locale = "en") => { if (subscription.status === "active" || subscription.status === "trialing") { if (subscription.cancel_at) { const endTime = subscription.cancel_at * 1e3; const { time: time2, isToday: isToday2 } = getLineTimeInfo(endTime, locale); return { action: endTime > Date.now() ? "willEnd" : "ended", time: time2, isToday: isToday2 }; } if (subscription.cancel_at_period_end) { const endTime = subscription.current_period_end * 1e3; const { time: time2, isToday: isToday2 } = getLineTimeInfo(endTime, locale); return { action: "willEnd", time: time2, isToday: isToday2 }; } const { time, isToday } = getLineTimeInfo(subscription.current_period_end * 1e3, locale); return { action: "renew", time, isToday }; } if (subscription.status === "past_due") { const endTime = (subscription.cancel_at || subscription.current_period_end) * 1e3; const { time, isToday } = getLineTimeInfo(endTime, locale); return { action: endTime > Date.now() ? "willEnd" : "ended", time, isToday }; } if (subscription.status === "canceled") { const { time, isToday } = getLineTimeInfo(subscription.canceled_at * 1e3, locale); return { action: "ended", time, isToday }; } return null; }; exports.getSubscriptionTimeSummary = getSubscriptionTimeSummary; const getSubscriptionAction = (subscription, actionProps) => { if (subscription.status === "active" || subscription.status === "trialing") { if (subscription.cancel_at_period_end) { if (subscription.cancelation_details?.reason === "payment_failed") { return null; } return { action: "recover", variant: "contained", color: "primary", canRenew: false, ...actionProps?.recover }; } if (subscription.cancel_at && subscription.cancel_at !== subscription.current_period_end) { return null; } return { action: "cancel", variant: "outlined", color: "inherit", canRenew: false, ...actionProps?.cancel }; } if (subscription.status === "past_due") { const canRenew = subscription.cancel_at && subscription.cancel_at !== subscription.current_period_end; return { action: "pastDue", variant: "contained", color: "primary", canRenew, ...actionProps?.pastDue }; } if (subscription.status !== "canceled" && subscription.cancel_at_period_end) { return { action: "recover", variant: "contained", color: "primary", canRenew: false, ...actionProps?.recover }; } return null; }; exports.getSubscriptionAction = getSubscriptionAction; const mergeExtraParams = (extra = {}) => { const params = new URLSearchParams(window.location.search); Object.keys(extra).forEach(key => { params.set(key, extra[key]); }); return params.toString(); }; exports.mergeExtraParams = mergeExtraParams; const flattenPaymentMethods = (methods = []) => { const out = []; methods.forEach(method => { const currencies = method.paymentCurrencies || method.payment_currencies || []; currencies.forEach(currency => { out.push({ ...currency, method }); }); }); return out; }; exports.flattenPaymentMethods = flattenPaymentMethods; const getTxLink = (method, details) => { if (!details) { return { text: "N/A", link: "", gas: "" }; } if (method.type === "arcblock" && details.arcblock?.tx_hash) { return { link: (0, _ufo.joinURL)(method.settings.arcblock?.explorer_host, "/txs", details.arcblock?.tx_hash), text: details.arcblock?.tx_hash, gas: "" }; } if (method.type === "bitcoin" && details.bitcoin?.tx_hash) { return { link: (0, _ufo.joinURL)(method.settings.bitcoin?.explorer_host, "/tx", details.bitcoin?.tx_hash), text: details.bitcoin?.tx_hash, gas: "" }; } if (method.type === "ethereum" && details.ethereum?.tx_hash) { return { link: (0, _ufo.joinURL)(method.settings.ethereum?.explorer_host, "/tx", details.ethereum?.tx_hash), text: (details.ethereum?.tx_hash).toUpperCase(), gas: new _util.BN(details.ethereum.gas_price).mul(new _util.BN(details.ethereum.gas_used)).toString() }; } if (method.type === "base" && details.base?.tx_hash) { return { link: (0, _ufo.joinURL)(method.settings.base?.explorer_host, "/tx", details.base?.tx_hash), text: (details.base?.tx_hash).toUpperCase(), gas: "" }; } if (method.type === "stripe") { const dashboard = method.livemode ? "https://dashboard.stripe.com" : "https://dashboard.stripe.com/test"; return { link: (0, _ufo.joinURL)(method.settings.stripe?.dashboard || dashboard, "payments", details.stripe?.payment_intent_id), text: details.stripe?.payment_intent_id, gas: "" }; } return { text: "N/A", link: "", gas: "" }; }; exports.getTxLink = getTxLink; function getQueryParams(url) { const queryParams = {}; const urlObj = new URL(url); urlObj.searchParams.forEach((value, key) => { queryParams[key] = value; }); return queryParams; } function lazyLoad(lazyRun) { if ("requestIdleCallback" in window) { window.requestIdleCallback(() => { lazyRun(); }); return; } if (document.readyState === "complete") { lazyRun(); return; } if ("onload" in window) { window.onload = () => { lazyRun(); }; return; } setTimeout(() => { lazyRun(); }, 0); } function formatTotalPrice({ product, quantity = 1, priceId, locale = "en" }) { const { prices = [], default_price_id: defaultPriceId } = product ?? { prices: [], default_price_id: "" }; const price = prices?.find(x => x.id === (priceId || defaultPriceId)); if (!price || !product) { return { totalPrice: "0", unitPrice: "", quantity: (0, _locales.t)("common.qty", locale, { count: quantity }), totalAmount: "0" }; } const currency = price?.currency ?? {}; const unitValue = new _util.BN(getPriceUintAmountByCurrency(price, currency)); const total = `${(0, _util.fromUnitToToken)(unitValue.mul(new _util.BN(quantity)), currency.decimal)} ${currency.symbol} `; const unit = `${(0, _util.fromUnitToToken)(unitValue, currency.decimal)} ${currency.symbol} `; const appendUnit = (v, alt) => { if (product.unit_label) { return `${v}/${price.product.unit_label}`; } if (price.recurring?.usage_type === "metered" || quantity === 1) { return alt; } return quantity ? (0, _locales.t)("common.each", locale, { unit }) : ""; }; return { totalPrice: total, unitPrice: appendUnit(total, ""), quantity: (0, _locales.t)("common.qty", locale, { count: quantity }), totalAmount: unitValue.mul(new _util.BN(quantity)).toString() }; } function formatQuantityInventory(price, quantity, locale = "en") { const q = Number(quantity); const { quantity_available: quantityAvailable = 0, quantity_sold: quantitySold = 0, quantity_limit_per_checkout: quantityLimitPerCheckout = 0 } = price || {}; if (quantityAvailable > 0 && quantitySold + q > quantityAvailable) { return (0, _locales.t)("common.quantityNotEnough", locale); } if (quantityLimitPerCheckout > 0 && quantityLimitPerCheckout < q) { return (0, _locales.t)("common.quantityLimitPerCheckout", locale); } return ""; } function formatSubscriptionStatus(status) { if (status === "canceled") { return "Ended"; } return status; } function formatAmountPrecisionLimit(amount, locale = "en", precision = 6) { if (!amount) { return ""; } const [, decimal] = String(amount).split("."); if (decimal && decimal.length > precision) { return (0, _locales.t)("common.amountPrecisionLimit", locale, { precision }); } return ""; } function getWordBreakStyle(value) { if (typeof value === "string" && /\s/.test(value)) { return "break-word"; } return "break-all"; } function isMobileSafari() { const ua = navigator.userAgent.toLowerCase(); const isSafari = ua.indexOf("safari") > -1 && ua.indexOf("chrome") === -1; const isMobile = ua.indexOf("mobile") > -1 || /iphone|ipad|ipod/.test(ua); const isIOS = /iphone|ipad|ipod/.test(ua); return isSafari && isMobile && isIOS; } function truncateText(text, maxLength, useWidth = false) { if (!text || !maxLength) { return text; } if (!useWidth) { if (text.length <= maxLength) { return text; } return `${text.substring(0, maxLength)}...`; } let width = 0; let truncated = ""; for (let i = 0; i < text.length; i++) { const charWidth = (0, _stringWidth.default)(text.charAt(i)); if (width + charWidth > maxLength) { break; } truncated += text.charAt(i); width += charWidth; } if (truncated === text) { return truncated; } return `${truncated}...`; } function getCustomerAvatar(did, updated_at, imageSize = 48) { if (!did) return ""; const updated = typeof updated_at === "number" ? updated_at : (0, _dayjs.default)(updated_at).unix(); return `/.well-known/service/user/avatar/${did}?imageFilter=resize&w=${imageSize}&h=${imageSize}&updateAt=${updated || (0, _dayjs.default)().unix()}`; } function hasDelegateTxHash(details, paymentMethod) { return paymentMethod?.type && ["arcblock", "ethereum", "base"].includes(paymentMethod?.type) && // @ts-ignore details?.[paymentMethod?.type]?.tx_hash; } function getInvoiceDescriptionAndReason(invoice, locale = "en") { const { billing_reason: reason, description } = invoice; const reasonMap = { subscription_create: (0, _locales.t)("payment.invoice.reason.creation", locale), subscription_cycle: (0, _locales.t)("payment.invoice.reason.cycle", locale), subscription_update: (0, _locales.t)("payment.invoice.reason.update", locale), subscription_recover: (0, _locales.t)("payment.invoice.reason.recover", locale), subscription_threshold: (0, _locales.t)("payment.invoice.reason.threshold", locale), subscription_cancel: (0, _locales.t)("payment.invoice.reason.cancel", locale), manual: (0, _locales.t)("payment.invoice.reason.manual", locale), upcoming: (0, _locales.t)("payment.invoice.reason.upcoming", locale), slash_stake: (0, _locales.t)("payment.invoice.reason.slashStake", locale), stake: (0, _locales.t)("payment.invoice.reason.stake", locale), return_stake: (0, _locales.t)("payment.invoice.reason.returnStake",