import { fromUnitToToken } from '@ocap/util'; import type { TPaymentCurrency } from '@blocklet/payment-types'; export { primaryContrastColor } from '../../libs/util'; // Interval key β†’ locale key mapping export const INTERVAL_LOCALE_KEY: Record = { day: 'common.daily', week: 'common.weekly', month: 'common.monthly', year: 'common.yearly', }; // Convert ISO2 country code to flag emoji (e.g., "us" β†’ "πŸ‡ΊπŸ‡Έ") export function countryCodeToFlag(code: string): string { if (!code || code.length !== 2) return ''; return String.fromCodePoint( ...code .toUpperCase() .split('') .map((c) => 127397 + c.charCodeAt(0)) ); } export function formatTokenAmount(unitAmount: string | number | bigint, currency: TPaymentCurrency | null): string { if (!unitAmount || !currency) return '0'; try { const tokenAmount = fromUnitToToken(String(unitAmount), currency.decimal || 0); const num = Number(tokenAmount); if (!Number.isFinite(num)) return '0'; const abs = Math.abs(num); const precision = abs > 0 && abs < 0.01 ? 6 : 2; const formatted = num.toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: precision }); return formatted.replace(/(\.\d*?)0+$/, '$1').replace(/\.$/, '') || '0'; } catch { return '0'; } } interface CurrencyOption { currency_id: string; unit_amount: string; custom_unit_amount?: { preset?: string; presets?: string[] } | null; } interface PriceWithDynamic { pricing_type?: string; base_amount?: string; unit_amount?: string; base_currency?: string; currency_id?: string; currency_options?: CurrencyOption[]; price_currency_options?: CurrencyOption[]; } // Get the unit_amount for a specific currency, checking currency_options first export function getUnitAmountForCurrency( price: PriceWithDynamic | null | undefined, currency: TPaymentCurrency | null ): string { if (!price || !currency) return '0'; const options = price.price_currency_options || price.currency_options || []; 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] || option.unit_amount; } return option.unit_amount; } if (price.currency_id === currency.id) { return price.unit_amount || '0'; } return '0'; } // Format price with exchange rate for dynamic pricing export function formatDynamicUnitPrice( price: PriceWithDynamic | null | undefined, currency: TPaymentCurrency | null, exchangeRate: string | null ): string | null { if (!price || !currency) return null; const isDynamic = price.pricing_type === 'dynamic'; if (isDynamic && exchangeRate && price.base_amount) { const rate = Number(exchangeRate); if (rate > 0 && Number.isFinite(rate)) { const baseUsd = Number(price.base_amount); if (baseUsd > 0 && Number.isFinite(baseUsd)) { const tokenAmount = baseUsd / rate; const abs = Math.abs(tokenAmount); const precision = abs > 0 && abs < 0.01 ? 6 : 2; return ( tokenAmount .toLocaleString('en-US', { minimumFractionDigits: 0, maximumFractionDigits: precision }) .replace(/(\.\d*?)0+$/, '$1') .replace(/\.$/, '') || '0' ); } } } // Fiat/Stripe fallback: use base_amount when no exchange rate // (unit_amount may be in crypto denomination, not fiat cents) if (!exchangeRate && price.base_amount != null) { const baseUsd = Number(price.base_amount); if (baseUsd >= 0 && Number.isFinite(baseUsd)) { return baseUsd.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); } } // Fallback: look up unit_amount from currency_options for the selected currency return formatTokenAmount(getUnitAmountForCurrency(price, currency), currency); } // Safe translation with fallback for missing keys (returns fallback if t() returns the raw key path) export function tSafe(t: (key: string, params?: any) => string, key: string, fallback: string): string { const v = t(key); return v && !v.includes('.') ? v : fallback; } // Shared white tooltip style for checkout-v2 export const whiteTooltipSx = { '& .MuiTooltip-tooltip': { bgcolor: 'background.paper', color: 'text.primary', border: '1px solid', borderColor: 'divider', borderRadius: '10px', boxShadow: '0 8px 24px rgba(0,0,0,0.12)', p: 1.5, maxWidth: 280, fontSize: 12, }, '& .MuiTooltip-arrow': { color: 'background.paper', '&::before': { border: '1px solid', borderColor: 'divider' }, }, }; // Helper: format trial text export function formatTrialText( t: (key: string, params?: Record) => string, days: number, interval: string ): string { const key = interval || 'day'; const intervalKey = days > 1 ? `common.${key}s` : `common.${key}`; return t('payment.checkout.free', { count: days, interval: t(intervalKey) }); } // ── Item meta: badge / title / subtitle for the header area ── type TFn = (key: string, params?: Record) => string; export type ItemTypeBadge = 'subscription' | 'topup' | 'oneTime'; interface ItemMeta { badge: ItemTypeBadge; badgeLabel: string; title: string; subtitle: string; } /** * Derive structured badge / title / subtitle for a checkout session header. * Works for the "primary product" header above the item list. */ export function getSessionHeaderMeta(t: TFn, session: any, product: any, items: any[]): ItemMeta { const mode = session?.mode || 'payment'; const isSubscription = ['subscription', 'setup'].includes(mode); const isCreditTopup = items.length === 1 && mode === 'payment' && items[0]?.price?.product?.type === 'credit' && items[0]?.price?.metadata?.credit_config; // Badge let badge: ItemTypeBadge; if (isCreditTopup) { badge = 'topup'; } else if (isSubscription) { badge = 'subscription'; } else { badge = 'oneTime'; } const badgeLabel = t(`payment.checkout.typeBadge.${badge}`); // Title: action + product name based on type const productName = product?.name || items[0]?.price?.product?.name || ''; let title = productName; if (productName) { if (isSubscription) { title = t('payment.checkout.headerTitle.subscribe', { name: productName }); } else if (!isCreditTopup) { title = t('payment.checkout.headerTitle.purchase', { name: productName }); } } // Subtitle: system-generated, not product.description let subtitle = ''; if (isCreditTopup) { subtitle = t('payment.checkout.subtitle.creditsTopup'); } else if (isSubscription) { // Use plan name if available; skip generic interval labels like "daily subscription" const planName = product?.metadata?.plan_name || product?.metadata?.plan_display_name; if (planName) { subtitle = planName; } } else { subtitle = t('payment.checkout.subtitle.oneTime'); } return { badge, badgeLabel, title, subtitle }; }