@burdenoff/microfe-billing 2026.910.9 → 2026.911.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -213,7 +213,7 @@ var x = 100, S = ({ items: S, subtotal: C, discountAmount: w, taxAmount: T, taxR
213
213
  children: V(A ? B : C)
214
214
  })]
215
215
  }),
216
- w > 0 && /* @__PURE__ */ h("div", {
216
+ w > 0 && !A && /* @__PURE__ */ h("div", {
217
217
  className: "flex items-center justify-between text-sm",
218
218
  children: [/* @__PURE__ */ m("span", {
219
219
  className: t.success.text,
@@ -1 +1 @@
1
- {"version":3,"file":"OrderSummary.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/OrderSummary.tsx"],"sourcesContent":["/**\n * Order Summary Component\n * Displays checkout items, discounts, taxes, and total\n */\n\nimport { type FC, useMemo } from 'react';\nimport {\n Package,\n Blocks,\n Coins,\n Minus,\n Plus,\n Trash2,\n Receipt,\n Check,\n ShoppingCart,\n} from 'lucide-react';\nimport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n} from '@burdenoff/fe-libs/ui';\nimport type { CheckoutItem, Currency, CheckoutFeature } from '../types';\nimport { formatCurrency } from '../../../shared/utils/format';\nimport { categoryTokens, statusTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * Credit conversion rate: How many credits equal 1 USD\n * This should match the backend creditRate.rateUSD (default 0.01 means 100 credits = $1)\n */\nconst CREDITS_PER_USD = 100;\n\ninterface OrderSummaryProps {\n items: CheckoutItem[];\n subtotal: number;\n discountAmount: number;\n taxAmount: number;\n taxRate: number;\n taxType?: string;\n total: number;\n currency: Currency;\n showInCredits?: boolean;\n /** Whether tax is being calculated */\n isCalculatingTax?: boolean;\n onUpdateQuantity?: (itemId: string, quantity: number) => void;\n onRemoveItem?: (itemId: string) => void;\n isEditable?: boolean;\n}\n\nexport const OrderSummary: FC<OrderSummaryProps> = ({\n items,\n subtotal,\n discountAmount,\n taxAmount,\n taxRate,\n taxType,\n total,\n currency,\n showInCredits = false,\n isCalculatingTax = false,\n onUpdateQuantity,\n onRemoveItem,\n isEditable = true,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const getItemIcon = (type: CheckoutItem['type']) => {\n switch (type) {\n case 'PLAN':\n return <Package className=\"size-5\" />;\n case 'ADDON':\n return <Blocks className=\"size-5\" />;\n case 'CREDITS':\n return <Coins className=\"size-5\" />;\n case 'ORDER':\n return <ShoppingCart className=\"size-5\" />;\n default:\n return <Package className=\"size-5\" />;\n }\n };\n\n const getItemTypeBadge = (type: CheckoutItem['type']) => {\n const badges = {\n PLAN: {\n label: 'Plan',\n className: `${categoryTokens.plan.bg} ${categoryTokens.plan.text}`,\n },\n ADDON: {\n label: 'Add-on',\n className: `${categoryTokens.addon.bg} ${categoryTokens.addon.text}`,\n },\n CREDITS: {\n label: 'Credits',\n className: `${categoryTokens.credits.bg} ${categoryTokens.credits.text}`,\n },\n ORDER: {\n label: 'Store Order',\n className: 'bg-status-success-bg-subtle text-status-success-text',\n },\n };\n return badges[type];\n };\n\n // Convert USD amount to credits\n const usdToCredits = (usdAmount: number) => usdAmount * CREDITS_PER_USD;\n\n // Subtotal in USD — used for credit conversion (items may be priced in local currency)\n const subtotalUSD = useMemo(\n () => items.reduce((sum, item) => sum + (item.priceUSD ?? item.price) * item.quantity, 0),\n [items]\n );\n\n // Format amount - show in credits if paying with credits, otherwise in selected currency\n // Items now have their prices stored in the selected currency (from currencyPrices)\n // For credit conversion always use priceUSD (base USD price) — not the localized price.\n const formatAmount = (amount: number, item?: CheckoutItem) => {\n if (showInCredits) {\n const usdAmount = item?.priceUSD ?? amount;\n const creditsAmount = usdToCredits(usdAmount);\n return `${creditsAmount.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 0 })} Credits`;\n }\n // Format in selected currency (items now have localized prices)\n return formatCurrency(amount, currency);\n };\n\n // Format limit value for display\n const formatLimitValue = (_key: string, value: unknown): string => {\n if (typeof value === 'boolean') {\n return value ? 'Included' : 'Not included';\n }\n if (typeof value === 'number') {\n if (value === -1 || value === Infinity) return 'Unlimited';\n return value.toLocaleString();\n }\n return String(value);\n };\n\n // Merge duplicate features by name, combining numeric limits\n const mergeFeatures = (features: CheckoutFeature[]): CheckoutFeature[] => {\n const merged = new Map<string, CheckoutFeature>();\n\n for (const feature of features) {\n const existing = merged.get(feature.name);\n if (existing) {\n // Merge limits - add numeric values together\n const mergedLimits: Record<string, number | string | boolean> = { ...existing.limits };\n if (feature.limits) {\n for (const [key, value] of Object.entries(feature.limits)) {\n const existingValue = mergedLimits[key];\n if (typeof value === 'number' && typeof existingValue === 'number') {\n // Both are numbers - add them (unless unlimited)\n if (existingValue === -1 || value === -1) {\n mergedLimits[key] = -1; // Unlimited stays unlimited\n } else {\n mergedLimits[key] = existingValue + value;\n }\n } else {\n // Non-numeric or first occurrence - just set\n mergedLimits[key] = value;\n }\n }\n }\n existing.limits = mergedLimits;\n } else {\n // Clone the feature to avoid mutating original\n merged.set(feature.name, {\n ...feature,\n limits: feature.limits ? { ...feature.limits } : undefined,\n });\n }\n }\n\n return Array.from(merged.values());\n };\n\n // Render features list\n const renderFeatures = (features: CheckoutFeature[]) => {\n if (!features || features.length === 0) return null;\n\n const mergedFeatures = mergeFeatures(features);\n\n return (\n <ul className=\"space-y-2\">\n {mergedFeatures.map((feature) => (\n <li key={feature.id} className=\"flex items-start gap-2\">\n <Check className={`size-4 ${statusTokens.success.icon} shrink-0 mt-0.5`} />\n <div className=\"flex-1 min-w-0\">\n <span className=\"text-sm text-text-primary\">{feature.name}</span>\n {feature.limits && 'value' in feature.limits && (\n <div className=\"mt-1 flex flex-wrap gap-1.5\">\n <span className=\"inline-flex items-center text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary\">\n {formatLimitValue('value', feature.limits.value)}\n </span>\n </div>\n )}\n </div>\n </li>\n ))}\n </ul>\n );\n };\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface overflow-hidden\">\n {/* Header */}\n <div className=\"px-4 py-3 border-b border-border-subtle bg-bg-sunken\">\n <div className=\"flex items-center gap-2\">\n <Receipt className=\"size-4 text-text-secondary\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('billing.checkout.orderSummary', 'Order Summary')}\n </h3>\n </div>\n </div>\n\n {/* Items */}\n <div className=\"divide-y divide-border-subtle\">\n {items.length === 0 ? (\n <div className=\"p-6 text-center\">\n <Package className=\"mx-auto size-8 text-text-secondary\" />\n <p className=\"mt-2 text-sm text-text-secondary\">No items in checkout</p>\n </div>\n ) : (\n items.map((item) => {\n const badge = getItemTypeBadge(item.type);\n const hasFeatures = item.features && item.features.length > 0;\n\n return (\n <div key={item.id} className=\"p-4\">\n <div className=\"flex items-start gap-3\">\n {/* Icon */}\n <div className=\"p-2 rounded-lg bg-bg-sunken text-text-secondary shrink-0\">\n {getItemIcon(item.type)}\n </div>\n\n {/* Item details */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <span className=\"font-medium text-text-primary\">{item.name}</span>\n <span className={`text-xs px-1.5 py-0.5 rounded ${badge.className}`}>\n {badge.label}\n </span>\n </div>\n {item.description && (\n <p className=\"text-sm text-text-secondary mt-0.5 line-clamp-2\">\n {item.description}\n </p>\n )}\n\n {/* Quantity controls */}\n <div className=\"flex items-center justify-between mt-3\">\n {isEditable && item.type !== 'PLAN' ? (\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity?.(item.id, item.quantity - 1)}\n disabled={item.quantity <= 1}\n className=\"p-2 rounded border border-border-subtle hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed min-w-[36px] min-h-[36px] flex items-center justify-center\"\n aria-label={`Decrease ${item.name} quantity`}\n >\n <Minus className=\"size-3.5\" />\n </button>\n <span className=\"w-8 text-center text-sm font-medium\">\n {item.quantity}\n </span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity?.(item.id, item.quantity + 1)}\n className=\"p-2 rounded border border-border-subtle hover:bg-bg-sunken min-w-[36px] min-h-[36px] flex items-center justify-center\"\n aria-label={`Increase ${item.name} quantity`}\n >\n <Plus className=\"size-3.5\" />\n </button>\n </div>\n ) : (\n <span className=\"text-sm text-text-secondary\">Qty: {item.quantity}</span>\n )}\n\n <div className=\"flex items-center gap-3\">\n <span className=\"font-medium text-text-primary\">\n {formatAmount(item.price * item.quantity, item)}\n </span>\n {isEditable && onRemoveItem && (\n <button\n type=\"button\"\n onClick={() => onRemoveItem(item.id)}\n className=\"p-1.5 rounded text-text-secondary hover:text-status-error-text hover:bg-status-error-bg-subtle\"\n aria-label={`Remove ${item.name}`}\n >\n <Trash2 className=\"size-4\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Features - Collapsible */}\n {hasFeatures &&\n (() => {\n const mergedCount = mergeFeatures(item.features!).length;\n return (\n <Accordion type=\"single\" collapsible className=\"mt-3\">\n <AccordionItem value=\"features\" className=\"border-0\">\n <AccordionTrigger className=\"py-2 px-0 text-sm text-text-secondary hover:text-text-primary hover:no-underline\">\n <span className=\"flex items-center gap-1.5\">\n <Check className=\"size-3.5\" />\n {mergedCount} {mergedCount === 1 ? 'feature' : 'features'}{' '}\n included\n </span>\n </AccordionTrigger>\n <AccordionContent className=\"pt-2 pb-0\">\n {renderFeatures(item.features!)}\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n );\n })()}\n </div>\n </div>\n </div>\n );\n })\n )}\n </div>\n\n {/* Totals */}\n {items.length > 0 && (\n <div className=\"border-t border-border-subtle bg-bg-sunken\">\n <div className=\"p-4 space-y-2\">\n {/* Subtotal - already in selected currency (from currencyPrices) */}\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {tr('billing.checkout.subtotal', 'Subtotal')}\n </span>\n <span className=\"text-text-primary\">\n {showInCredits ? formatAmount(subtotalUSD) : formatAmount(subtotal)}\n </span>\n </div>\n\n {/* Discount */}\n {discountAmount > 0 && (\n <div className=\"flex items-center justify-between text-sm\">\n <span className={statusTokens.success.text}>Discount</span>\n <span className={statusTokens.success.text}>-{formatAmount(discountAmount)}</span>\n </div>\n )}\n\n {/* Tax - calculated on the subtotal in selected currency */}\n {isCalculatingTax ? (\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Tax</span>\n <span className=\"text-text-secondary animate-pulse\">Calculating…</span>\n </div>\n ) : taxAmount > 0 && taxRate > 0 ? (\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {taxType || 'Tax'} ({(taxRate * 100).toFixed(0)}%)\n </span>\n <span className=\"text-text-primary\">{formatAmount(taxAmount)}</span>\n </div>\n ) : null}\n\n {/* Divider */}\n <div className=\"border-t border-border-subtle my-2\" />\n\n {/* Total - show in credits or selected currency */}\n {showInCredits ? (\n <>\n {/* Credit payment: Show total in credits */}\n <div className=\"flex items-center justify-between\">\n <span className=\"font-semibold text-text-primary\">\n {tr('billing.checkout.total', 'Total')}\n </span>\n <span className=\"text-xl font-bold text-text-primary\">\n {usdToCredits(subtotalUSD).toLocaleString(undefined, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n })}{' '}\n Credits\n </span>\n </div>\n <p className=\"text-xs text-center text-text-secondary pt-2\">\n Paying with account credits\n </p>\n </>\n ) : (\n <>\n {/* Card payment: Show total in selected currency */}\n {/* Items already have localized prices from currencyPrices - no conversion needed */}\n <div className=\"flex items-center justify-between\">\n <span className=\"font-semibold text-text-primary\">Total in {currency}</span>\n <span className=\"text-xl font-bold text-text-primary\">\n {formatCurrency(total, currency)}\n </span>\n </div>\n </>\n )}\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"OrderSummary.js","names":[],"sources":["../../../../../src/billing/modules/checkout/components/OrderSummary.tsx"],"sourcesContent":["/**\n * Order Summary Component\n * Displays checkout items, discounts, taxes, and total\n */\n\nimport { type FC, useMemo } from 'react';\nimport {\n Package,\n Blocks,\n Coins,\n Minus,\n Plus,\n Trash2,\n Receipt,\n Check,\n ShoppingCart,\n} from 'lucide-react';\nimport {\n Accordion,\n AccordionItem,\n AccordionTrigger,\n AccordionContent,\n} from '@burdenoff/fe-libs/ui';\nimport type { CheckoutItem, Currency, CheckoutFeature } from '../types';\nimport { formatCurrency } from '../../../shared/utils/format';\nimport { categoryTokens, statusTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\n\n/**\n * Credit conversion rate: How many credits equal 1 USD\n * This should match the backend creditRate.rateUSD (default 0.01 means 100 credits = $1)\n */\nconst CREDITS_PER_USD = 100;\n\ninterface OrderSummaryProps {\n items: CheckoutItem[];\n subtotal: number;\n discountAmount: number;\n taxAmount: number;\n taxRate: number;\n taxType?: string;\n total: number;\n currency: Currency;\n showInCredits?: boolean;\n /** Whether tax is being calculated */\n isCalculatingTax?: boolean;\n onUpdateQuantity?: (itemId: string, quantity: number) => void;\n onRemoveItem?: (itemId: string) => void;\n isEditable?: boolean;\n}\n\nexport const OrderSummary: FC<OrderSummaryProps> = ({\n items,\n subtotal,\n discountAmount,\n taxAmount,\n taxRate,\n taxType,\n total,\n currency,\n showInCredits = false,\n isCalculatingTax = false,\n onUpdateQuantity,\n onRemoveItem,\n isEditable = true,\n}) => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const getItemIcon = (type: CheckoutItem['type']) => {\n switch (type) {\n case 'PLAN':\n return <Package className=\"size-5\" />;\n case 'ADDON':\n return <Blocks className=\"size-5\" />;\n case 'CREDITS':\n return <Coins className=\"size-5\" />;\n case 'ORDER':\n return <ShoppingCart className=\"size-5\" />;\n default:\n return <Package className=\"size-5\" />;\n }\n };\n\n const getItemTypeBadge = (type: CheckoutItem['type']) => {\n const badges = {\n PLAN: {\n label: 'Plan',\n className: `${categoryTokens.plan.bg} ${categoryTokens.plan.text}`,\n },\n ADDON: {\n label: 'Add-on',\n className: `${categoryTokens.addon.bg} ${categoryTokens.addon.text}`,\n },\n CREDITS: {\n label: 'Credits',\n className: `${categoryTokens.credits.bg} ${categoryTokens.credits.text}`,\n },\n ORDER: {\n label: 'Store Order',\n className: 'bg-status-success-bg-subtle text-status-success-text',\n },\n };\n return badges[type];\n };\n\n // Convert USD amount to credits\n const usdToCredits = (usdAmount: number) => usdAmount * CREDITS_PER_USD;\n\n // Subtotal in USD — used for credit conversion (items may be priced in local currency)\n const subtotalUSD = useMemo(\n () => items.reduce((sum, item) => sum + (item.priceUSD ?? item.price) * item.quantity, 0),\n [items]\n );\n\n // Format amount - show in credits if paying with credits, otherwise in selected currency\n // Items now have their prices stored in the selected currency (from currencyPrices)\n // For credit conversion always use priceUSD (base USD price) — not the localized price.\n const formatAmount = (amount: number, item?: CheckoutItem) => {\n if (showInCredits) {\n const usdAmount = item?.priceUSD ?? amount;\n const creditsAmount = usdToCredits(usdAmount);\n return `${creditsAmount.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 0 })} Credits`;\n }\n // Format in selected currency (items now have localized prices)\n return formatCurrency(amount, currency);\n };\n\n // Format limit value for display\n const formatLimitValue = (_key: string, value: unknown): string => {\n if (typeof value === 'boolean') {\n return value ? 'Included' : 'Not included';\n }\n if (typeof value === 'number') {\n if (value === -1 || value === Infinity) return 'Unlimited';\n return value.toLocaleString();\n }\n return String(value);\n };\n\n // Merge duplicate features by name, combining numeric limits\n const mergeFeatures = (features: CheckoutFeature[]): CheckoutFeature[] => {\n const merged = new Map<string, CheckoutFeature>();\n\n for (const feature of features) {\n const existing = merged.get(feature.name);\n if (existing) {\n // Merge limits - add numeric values together\n const mergedLimits: Record<string, number | string | boolean> = { ...existing.limits };\n if (feature.limits) {\n for (const [key, value] of Object.entries(feature.limits)) {\n const existingValue = mergedLimits[key];\n if (typeof value === 'number' && typeof existingValue === 'number') {\n // Both are numbers - add them (unless unlimited)\n if (existingValue === -1 || value === -1) {\n mergedLimits[key] = -1; // Unlimited stays unlimited\n } else {\n mergedLimits[key] = existingValue + value;\n }\n } else {\n // Non-numeric or first occurrence - just set\n mergedLimits[key] = value;\n }\n }\n }\n existing.limits = mergedLimits;\n } else {\n // Clone the feature to avoid mutating original\n merged.set(feature.name, {\n ...feature,\n limits: feature.limits ? { ...feature.limits } : undefined,\n });\n }\n }\n\n return Array.from(merged.values());\n };\n\n // Render features list\n const renderFeatures = (features: CheckoutFeature[]) => {\n if (!features || features.length === 0) return null;\n\n const mergedFeatures = mergeFeatures(features);\n\n return (\n <ul className=\"space-y-2\">\n {mergedFeatures.map((feature) => (\n <li key={feature.id} className=\"flex items-start gap-2\">\n <Check className={`size-4 ${statusTokens.success.icon} shrink-0 mt-0.5`} />\n <div className=\"flex-1 min-w-0\">\n <span className=\"text-sm text-text-primary\">{feature.name}</span>\n {feature.limits && 'value' in feature.limits && (\n <div className=\"mt-1 flex flex-wrap gap-1.5\">\n <span className=\"inline-flex items-center text-xs px-1.5 py-0.5 rounded bg-bg-sunken text-text-secondary\">\n {formatLimitValue('value', feature.limits.value)}\n </span>\n </div>\n )}\n </div>\n </li>\n ))}\n </ul>\n );\n };\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface overflow-hidden\">\n {/* Header */}\n <div className=\"px-4 py-3 border-b border-border-subtle bg-bg-sunken\">\n <div className=\"flex items-center gap-2\">\n <Receipt className=\"size-4 text-text-secondary\" />\n <h3 className=\"font-semibold text-text-primary\">\n {tr('billing.checkout.orderSummary', 'Order Summary')}\n </h3>\n </div>\n </div>\n\n {/* Items */}\n <div className=\"divide-y divide-border-subtle\">\n {items.length === 0 ? (\n <div className=\"p-6 text-center\">\n <Package className=\"mx-auto size-8 text-text-secondary\" />\n <p className=\"mt-2 text-sm text-text-secondary\">No items in checkout</p>\n </div>\n ) : (\n items.map((item) => {\n const badge = getItemTypeBadge(item.type);\n const hasFeatures = item.features && item.features.length > 0;\n\n return (\n <div key={item.id} className=\"p-4\">\n <div className=\"flex items-start gap-3\">\n {/* Icon */}\n <div className=\"p-2 rounded-lg bg-bg-sunken text-text-secondary shrink-0\">\n {getItemIcon(item.type)}\n </div>\n\n {/* Item details */}\n <div className=\"flex-1 min-w-0\">\n <div className=\"flex items-center gap-2 flex-wrap\">\n <span className=\"font-medium text-text-primary\">{item.name}</span>\n <span className={`text-xs px-1.5 py-0.5 rounded ${badge.className}`}>\n {badge.label}\n </span>\n </div>\n {item.description && (\n <p className=\"text-sm text-text-secondary mt-0.5 line-clamp-2\">\n {item.description}\n </p>\n )}\n\n {/* Quantity controls */}\n <div className=\"flex items-center justify-between mt-3\">\n {isEditable && item.type !== 'PLAN' ? (\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity?.(item.id, item.quantity - 1)}\n disabled={item.quantity <= 1}\n className=\"p-2 rounded border border-border-subtle hover:bg-bg-sunken disabled:opacity-50 disabled:cursor-not-allowed min-w-[36px] min-h-[36px] flex items-center justify-center\"\n aria-label={`Decrease ${item.name} quantity`}\n >\n <Minus className=\"size-3.5\" />\n </button>\n <span className=\"w-8 text-center text-sm font-medium\">\n {item.quantity}\n </span>\n <button\n type=\"button\"\n onClick={() => onUpdateQuantity?.(item.id, item.quantity + 1)}\n className=\"p-2 rounded border border-border-subtle hover:bg-bg-sunken min-w-[36px] min-h-[36px] flex items-center justify-center\"\n aria-label={`Increase ${item.name} quantity`}\n >\n <Plus className=\"size-3.5\" />\n </button>\n </div>\n ) : (\n <span className=\"text-sm text-text-secondary\">Qty: {item.quantity}</span>\n )}\n\n <div className=\"flex items-center gap-3\">\n <span className=\"font-medium text-text-primary\">\n {formatAmount(item.price * item.quantity, item)}\n </span>\n {isEditable && onRemoveItem && (\n <button\n type=\"button\"\n onClick={() => onRemoveItem(item.id)}\n className=\"p-1.5 rounded text-text-secondary hover:text-status-error-text hover:bg-status-error-bg-subtle\"\n aria-label={`Remove ${item.name}`}\n >\n <Trash2 className=\"size-4\" />\n </button>\n )}\n </div>\n </div>\n\n {/* Features - Collapsible */}\n {hasFeatures &&\n (() => {\n const mergedCount = mergeFeatures(item.features!).length;\n return (\n <Accordion type=\"single\" collapsible className=\"mt-3\">\n <AccordionItem value=\"features\" className=\"border-0\">\n <AccordionTrigger className=\"py-2 px-0 text-sm text-text-secondary hover:text-text-primary hover:no-underline\">\n <span className=\"flex items-center gap-1.5\">\n <Check className=\"size-3.5\" />\n {mergedCount} {mergedCount === 1 ? 'feature' : 'features'}{' '}\n included\n </span>\n </AccordionTrigger>\n <AccordionContent className=\"pt-2 pb-0\">\n {renderFeatures(item.features!)}\n </AccordionContent>\n </AccordionItem>\n </Accordion>\n );\n })()}\n </div>\n </div>\n </div>\n );\n })\n )}\n </div>\n\n {/* Totals */}\n {items.length > 0 && (\n <div className=\"border-t border-border-subtle bg-bg-sunken\">\n <div className=\"p-4 space-y-2\">\n {/* Subtotal - already in selected currency (from currencyPrices) */}\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {tr('billing.checkout.subtotal', 'Subtotal')}\n </span>\n <span className=\"text-text-primary\">\n {showInCredits ? formatAmount(subtotalUSD) : formatAmount(subtotal)}\n </span>\n </div>\n\n {/* Discount — credits purchases don't support coupons yet (BOFF-7192) */}\n {discountAmount > 0 && !showInCredits && (\n <div className=\"flex items-center justify-between text-sm\">\n <span className={statusTokens.success.text}>Discount</span>\n <span className={statusTokens.success.text}>-{formatAmount(discountAmount)}</span>\n </div>\n )}\n\n {/* Tax - calculated on the subtotal in selected currency */}\n {isCalculatingTax ? (\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">Tax</span>\n <span className=\"text-text-secondary animate-pulse\">Calculating…</span>\n </div>\n ) : taxAmount > 0 && taxRate > 0 ? (\n <div className=\"flex items-center justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {taxType || 'Tax'} ({(taxRate * 100).toFixed(0)}%)\n </span>\n <span className=\"text-text-primary\">{formatAmount(taxAmount)}</span>\n </div>\n ) : null}\n\n {/* Divider */}\n <div className=\"border-t border-border-subtle my-2\" />\n\n {/* Total - show in credits or selected currency */}\n {showInCredits ? (\n <>\n {/* Credit payment: Show total in credits */}\n <div className=\"flex items-center justify-between\">\n <span className=\"font-semibold text-text-primary\">\n {tr('billing.checkout.total', 'Total')}\n </span>\n <span className=\"text-xl font-bold text-text-primary\">\n {usdToCredits(subtotalUSD).toLocaleString(undefined, {\n minimumFractionDigits: 0,\n maximumFractionDigits: 0,\n })}{' '}\n Credits\n </span>\n </div>\n <p className=\"text-xs text-center text-text-secondary pt-2\">\n Paying with account credits\n </p>\n </>\n ) : (\n <>\n {/* Card payment: Show total in selected currency */}\n {/* Items already have localized prices from currencyPrices - no conversion needed */}\n <div className=\"flex items-center justify-between\">\n <span className=\"font-semibold text-text-primary\">Total in {currency}</span>\n <span className=\"text-xl font-bold text-text-primary\">\n {formatCurrency(total, currency)}\n </span>\n </div>\n </>\n )}\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}
@@ -146,7 +146,7 @@ function u(u = {}) {
146
146
  clearItems: Ee,
147
147
  setBillingAccount: De,
148
148
  setPaymentProvider: o((e) => {
149
- x(e), e === "RAZORPAY" && S !== "INR" && C("INR");
149
+ x(e), e === "RAZORPAY" && S !== "INR" && C("INR"), e === "CREDITS" && (T(""), D(0), O("PERCENTAGE"));
150
150
  }, [S]),
151
151
  setCurrency: Oe,
152
152
  setPromoCode: T,
@@ -1 +1 @@
1
- {"version":3,"file":"useCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useCheckout.ts"],"sourcesContent":["/**\n * Main checkout hook for managing checkout state and payment processing\n */\n\nimport { useState, useCallback, useMemo, useEffect } from 'react';\nimport {\n useInitiatePaymentCheckoutMutation,\n useSpendCreditsCheckoutMutation,\n useConvertCurrencyLazyQuery,\n useValidateCouponLazyQuery,\n} from '../../../../generated/global-operations';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useEnsureStoreOrderBillingOrder } from './useStoreShipping';\nimport type {\n CheckoutItem,\n CheckoutBillingAccount,\n PaymentProvider,\n Currency,\n CheckoutState,\n PaymentInitResponse,\n PromoCodeResult,\n PurchaseType,\n} from '../types';\n\n/**\n * Credit conversion rate: How many credits equal 1 USD\n * This should match the backend creditRate.rateUSD (default 0.01 means 100 credits = $1)\n */\n// Exported so callers that need a per-item (rather than aggregate) credits\n// amount — e.g. the success page's itemized \"Items Purchased\" list — convert\n// consistently with creditsRequired/hasEnoughCredits below, instead of each\n// re-declaring their own copy of this rate.\nexport const CREDITS_PER_USD = 100;\n\ninterface UseCheckoutOptions {\n billingAccountId?: string;\n defaultCurrency?: Currency;\n defaultSaveCard?: boolean;\n upgradeFromSubscriptionId?: string;\n /** For plan downgrades — old sub auto-renewal cancelled, runs to natural expiry */\n downgradeFromSubscriptionId?: string;\n /** For addon purchases - pre-selected subscription ID */\n defaultSubscriptionId?: string;\n}\n\ninterface UseCheckoutResult {\n // State\n items: CheckoutItem[];\n billingAccount: CheckoutBillingAccount | null;\n selectedProvider: PaymentProvider;\n selectedCurrency: Currency;\n promoCode: string;\n promoDiscount: number;\n promoDiscountType: 'PERCENTAGE' | 'FIXED';\n useCredits: boolean;\n creditsToUse: number;\n checkoutState: CheckoutState;\n error: string | null;\n\n // Saved card state\n selectedPaymentMethodId: string | null;\n saveCard: boolean;\n\n // Auto-pay state (for subscription purchases)\n autoRenew: boolean;\n autoRenewPaymentMethodId: string | null;\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId: string | null;\n\n // Computed values\n subtotal: number;\n subtotalUSD: number;\n discountAmount: number;\n taxAmount: number;\n taxRate: number;\n taxType: string;\n total: number;\n totalInSelectedCurrency: number;\n availableCredits: number;\n /** subtotalUSD converted to credits (CREDITS_PER_USD) — the actual amount a\n * CREDITS-provider purchase charges, in credits. total/totalInSelectedCurrency\n * are in the selected display currency and must never be shown/used as a\n * credits amount (they're a different number). */\n creditsRequired: number;\n hasEnoughCredits: boolean;\n canPayWithCredits: boolean;\n isLoading: boolean;\n isProcessing: boolean;\n isCalculatingTax: boolean;\n\n // Currency conversion\n conversionRate: number;\n convertedAmount: number | null;\n\n // Actions\n addItem: (item: CheckoutItem) => void;\n removeItem: (itemId: string) => void;\n updateItemQuantity: (itemId: string, quantity: number) => void;\n updateItemPrice: (itemId: string, price: number, currency?: Currency) => void;\n clearItems: () => void;\n setBillingAccount: (account: CheckoutBillingAccount | null) => void;\n setPaymentProvider: (provider: PaymentProvider) => void;\n setCurrency: (currency: Currency) => void;\n setPromoCode: (code: string) => void;\n validatePromoCode: () => Promise<PromoCodeResult>;\n clearPromoCode: () => void;\n setUseCredits: (use: boolean) => void;\n setCreditsToUse: (amount: number) => void;\n setSelectedPaymentMethodId: (id: string | null) => void;\n setSaveCard: (save: boolean) => void;\n setAutoRenew: (enabled: boolean) => void;\n setAutoRenewPaymentMethodId: (id: string | null) => void;\n setSelectedSubscriptionId: (id: string | null) => void;\n initiatePayment: () => Promise<PaymentInitResponse>;\n reset: () => void;\n}\n\nexport function useCheckout(options: UseCheckoutOptions = {}): UseCheckoutResult {\n const { currentUser } = useBilling();\n const {\n defaultCurrency = 'USD',\n defaultSaveCard = false, // CRITICAL: Never save cards without explicit user consent\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n defaultSubscriptionId,\n } = options;\n\n // Core state\n const [items, setItems] = useState<CheckoutItem[]>([]);\n const [billingAccount, setBillingAccount] = useState<CheckoutBillingAccount | null>(null);\n const [selectedProvider, setSelectedProvider] = useState<PaymentProvider>('AUTO');\n const [selectedCurrency, setSelectedCurrency] = useState<Currency>(defaultCurrency);\n const [promoCode, setPromoCode] = useState('');\n const [promoDiscount, setPromoDiscount] = useState(0);\n const [promoDiscountType, setPromoDiscountType] = useState<'PERCENTAGE' | 'FIXED'>('PERCENTAGE');\n const [useCredits, setUseCredits] = useState(false);\n const [creditsToUse, setCreditsToUse] = useState(0);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [taxAmount, setTaxAmount] = useState(0);\n const [taxRate, setTaxRate] = useState(0);\n const [taxType, setTaxType] = useState<string>('NONE');\n\n // Saved card state\n const [selectedPaymentMethodId, setSelectedPaymentMethodId] = useState<string | null>(null);\n const [saveCard, setSaveCard] = useState(defaultSaveCard);\n\n // Auto-pay state (for subscription purchases)\n const [autoRenew, setAutoRenew] = useState(false);\n const [autoRenewPaymentMethodId, setAutoRenewPaymentMethodId] = useState<string | null>(null);\n\n // Subscription state (for addon purchases)\n const [selectedSubscriptionId, setSelectedSubscriptionId] = useState<string | null>(\n defaultSubscriptionId ?? null\n );\n\n // GraphQL mutations and queries\n const [initiatePaymentMutation] = useInitiatePaymentCheckoutMutation();\n const [ensureStoreOrderBillingOrderMutation] = useEnsureStoreOrderBillingOrder();\n const [spendCreditsMutation] = useSpendCreditsCheckoutMutation();\n const [convertCurrency, { data: currencyData, loading: currencyLoading }] =\n useConvertCurrencyLazyQuery();\n const [validateCoupon] = useValidateCouponLazyQuery();\n\n // Computed values\n\n // The currency `total`/`subtotal` are actually denominated in. Plans,\n // addons, and credits are priced in USD; store orders are priced in the\n // order's own currency (item.currency), which may not be USD. Converting\n // `total` to the selected currency must pivot FROM this currency, not\n // always assume USD — see BOFF-7127 (a ₹50 item was shown as $4,748.50\n // because the conversion always requested a USD→X rate and multiplied,\n // which is only correct when the source amount actually is in USD).\n const nativeCurrency = useMemo<Currency>(() => items[0]?.currency ?? 'USD', [items]);\n\n const subtotal = useMemo(() => {\n return items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n }, [items]);\n\n // USD subtotal — always use priceUSD (base price) for credit calculations.\n // item.price may be in a local currency (INR, EUR, etc.) when currencyPrices is set.\n const subtotalUSD = useMemo(() => {\n return items.reduce((sum, item) => sum + (item.priceUSD ?? item.price) * item.quantity, 0);\n }, [items]);\n\n const discountAmount = useMemo(() => {\n if (!promoDiscount) return 0;\n // promoDiscount now contains the actual discount amount from backend validation\n // (not the percentage value), so use it directly\n return Math.min(promoDiscount, subtotal);\n }, [subtotal, promoDiscount]);\n\n // Total includes tax (calculated via calculateTax query)\n const total = useMemo(() => {\n const afterDiscount = subtotal - discountAmount;\n const withCredits = useCredits ? Math.max(0, afterDiscount - creditsToUse) : afterDiscount;\n return withCredits + taxAmount;\n }, [subtotal, discountAmount, useCredits, creditsToUse, taxAmount]);\n\n const availableCredits = billingAccount?.creditAmount ?? 0;\n\n // Use subtotalUSD so this works correctly for any display currency\n const creditsRequired = useMemo(() => subtotalUSD * CREDITS_PER_USD, [subtotalUSD]);\n\n const hasEnoughCredits = useMemo(\n () => availableCredits >= creditsRequired,\n [availableCredits, creditsRequired]\n );\n\n // Can only pay with credits if not buying credits\n const canPayWithCredits = useMemo(() => {\n const hasCreditsPurchase = items.some((item) => item.type === 'CREDITS');\n return !hasCreditsPurchase && availableCredits > 0;\n }, [items, availableCredits]);\n\n // Currency conversion result\n const convertedAmount = currencyData?.convertCurrency?.amount ?? null;\n const conversionRate = currencyData?.convertCurrency?.exchangeRate ?? 1;\n const totalInSelectedCurrency = convertedAmount ?? total;\n\n const isLoading = checkoutState === 'loading' || currencyLoading;\n const isProcessing = checkoutState === 'processing';\n\n // Effect to trigger currency conversion when total or currency changes\n // This ensures the converted amount is always up-to-date\n useEffect(() => {\n // Skip conversion for credit payments (no currency conversion needed)\n if (selectedProvider === 'CREDITS') {\n return;\n }\n\n // Skip if no items yet (cart is still loading)\n if (items.length === 0) {\n return;\n }\n\n // Skip conversion if items already have prices in selected currency\n // (Plans/Addons with multi-currency pricing have currencyPrices stored)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === selectedCurrency || item.type === 'CREDITS'\n );\n\n if (allItemsHaveLocalizedPrices) {\n // Items are already priced in the selected currency (from currencyPrices)\n // No conversion needed - subtotal and total are already correct!\n return;\n }\n\n // Only fetch if we have a valid total and it needs converting at all.\n if (total > 0 && nativeCurrency !== selectedCurrency) {\n // For INR, wait until tax has been calculated (taxAmount > 0 or taxRate is set)\n // This prevents double API calls - one before tax, one after\n if (selectedCurrency === 'INR' && taxAmount === 0 && subtotal > 0) {\n // Tax hasn't been calculated yet, skip this conversion\n // The effect will run again when taxAmount changes\n return;\n }\n\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: selectedCurrency,\n fromCurrency: nativeCurrency,\n },\n });\n }\n }, [\n total,\n selectedCurrency,\n selectedProvider,\n convertCurrency,\n taxAmount,\n subtotal,\n items,\n nativeCurrency,\n ]);\n\n // Effect to calculate tax when billing account, subtotal, or currency changes\n // Tax is only applied for INR payments (18% GST)\n useEffect(() => {\n // Only calculate tax if we have a billing account and items\n if (!billingAccount || subtotal <= 0) {\n // Reset tax if no billing account or empty cart\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Skip if paying with credits (no tax on credit payments)\n if (selectedProvider === 'CREDITS') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Only apply tax for INR payments\n if (selectedCurrency !== 'INR') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // For INR: Calculate 18% GST on the USD amount, then we'll convert for display\n const afterDiscount = subtotal - discountAmount;\n const GST_RATE = 0.18;\n const taxInUSD = afterDiscount * GST_RATE;\n\n setTaxAmount(taxInUSD);\n setTaxRate(GST_RATE);\n setTaxType('GST');\n }, [billingAccount, subtotal, discountAmount, selectedCurrency, selectedProvider]);\n\n // Actions\n const addItem = useCallback((item: CheckoutItem) => {\n setItems((prev) => {\n // Plans can only be purchased once - quantity is always 1\n if (item.type === 'PLAN') {\n // Check if any plan already exists in cart\n const existingPlan = prev.find((i) => i.type === 'PLAN');\n if (existingPlan) {\n // Replace existing plan with new one (can only have 1 plan)\n return prev.map((i) => (i.type === 'PLAN' ? { ...item, quantity: 1 } : i));\n }\n // Add new plan with quantity forced to 1\n return [...prev, { ...item, quantity: 1 }];\n }\n\n // For addons and credits, allow quantity increment\n const existing = prev.find((i) => i.id === item.id);\n if (existing) {\n return prev.map((i) =>\n i.id === item.id ? { ...i, quantity: i.quantity + item.quantity } : i\n );\n }\n return [...prev, item];\n });\n }, []);\n\n const removeItem = useCallback((itemId: string) => {\n setItems((prev) => prev.filter((i) => i.id !== itemId));\n }, []);\n\n const updateItemQuantity = useCallback(\n (itemId: string, quantity: number) => {\n if (quantity <= 0) {\n removeItem(itemId);\n return;\n }\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Plans can only have quantity 1\n if (i.type === 'PLAN') {\n return { ...i, quantity: 1 };\n }\n return { ...i, quantity };\n })\n );\n },\n [removeItem]\n );\n\n const updateItemPrice = useCallback((itemId: string, price: number, currency?: Currency) => {\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Update price and optionally currency\n const updates: Partial<CheckoutItem> = { price };\n if (currency) {\n updates.currency = currency;\n }\n return { ...i, ...updates };\n })\n );\n }, []);\n\n const clearItems = useCallback(() => {\n setItems([]);\n }, []);\n\n const handleSetBillingAccount = useCallback((account: CheckoutBillingAccount | null) => {\n setBillingAccount(account);\n // Reset credits usage when billing account changes — different accounts\n // carry different credit balances, so a CREDITS selection made against\n // the previous account may no longer be valid (or even offered) for\n // this one. Without also resetting the provider, the order summary kept\n // displaying amounts in Credits (and checkout could still submit with\n // provider: 'CREDITS') even after the Credits option had disappeared\n // from the payment method list for the new account.\n setUseCredits(false);\n setCreditsToUse(0);\n setSelectedProvider((prev) => (prev === 'CREDITS' ? 'AUTO' : prev));\n }, []);\n\n const handleSetCurrency = useCallback(\n (currency: Currency) => {\n setSelectedCurrency(currency);\n // Skip conversion if items already have localized prices\n // (Plans/Addons with multi-currency pricing don't need conversion)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === currency || item.type === 'CREDITS'\n );\n\n // Only fetch conversion rate if items don't have localized prices\n if (total > 0 && nativeCurrency !== currency && !allItemsHaveLocalizedPrices) {\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: currency,\n fromCurrency: nativeCurrency,\n },\n });\n }\n },\n [total, convertCurrency, items, nativeCurrency]\n );\n\n const handleSetPaymentProvider = useCallback(\n (provider: PaymentProvider) => {\n setSelectedProvider(provider);\n // Razorpay only supports INR\n if (provider === 'RAZORPAY' && selectedCurrency !== 'INR') {\n setSelectedCurrency('INR');\n }\n },\n [selectedCurrency]\n );\n\n const validatePromoCode = useCallback(async (): Promise<PromoCodeResult> => {\n if (!promoCode.trim()) {\n return {\n valid: false,\n code: '',\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Please enter a promo code',\n };\n }\n\n try {\n setCheckoutState('loading');\n\n // Build validation input\n const validationInput = {\n code: promoCode.trim().toUpperCase(),\n userId: currentUser?.id ?? '',\n userEmail: currentUser?.email,\n amount: subtotal,\n currency: selectedCurrency,\n };\n\n const { data, error: queryError } = await validateCoupon({\n variables: {\n input: validationInput,\n },\n });\n\n if (queryError || !data?.validateCoupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n\n const response = data.validateCoupon;\n\n if (!response.valid) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: response.error || 'Invalid coupon code',\n };\n }\n\n // Extract discount details from coupon\n const coupon = response.coupon;\n if (!coupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Coupon data not found',\n };\n }\n\n // Use the ACTUAL discount amount from validation (includes caps and currency conversion)\n const actualDiscountAmount = response.discountAmount || 0;\n\n const discountType = coupon.type === 'FIXED_AMOUNT' ? 'FIXED' : 'PERCENTAGE';\n setPromoDiscount(actualDiscountAmount); // Use actual discount, not coupon.value\n setPromoDiscountType(discountType);\n setCheckoutState('idle');\n\n return {\n valid: true,\n code: coupon.code,\n discountType,\n discountValue: actualDiscountAmount, // Use actual discount, not coupon.value\n };\n } catch {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n }, [currentUser?.email, currentUser?.id, promoCode, selectedCurrency, subtotal, validateCoupon]);\n\n const clearPromoCode = useCallback(() => {\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n }, []);\n\n const handleSetCreditsToUse = useCallback(\n (amount: number) => {\n const maxCredits = Math.min(amount, availableCredits, subtotal - discountAmount);\n setCreditsToUse(Math.max(0, maxCredits));\n },\n [availableCredits, subtotal, discountAmount]\n );\n\n const initiatePayment = useCallback(\n async (): Promise<PaymentInitResponse> => {\n if (!billingAccount) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Please select a billing account',\n };\n }\n\n if (items.length === 0) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'No items in checkout',\n };\n }\n\n try {\n setCheckoutState('processing');\n setError(null);\n\n // Determine purchase type from first item\n const firstItem = items[0];\n let purchaseType: PurchaseType = firstItem.type;\n\n // Check if this is a store order checkout (detect early)\n const storeOrderItem = items.find((item) => item.metadata?.storeOrderId);\n const storeOrderId = storeOrderItem?.metadata?.storeOrderId as string | undefined;\n let billingOrderId =\n (items.find((item) => item.metadata?.billingOrderId)?.metadata?.billingOrderId as\n string | undefined) || undefined;\n\n // For store orders, use 'ORDER' purchase type\n if (storeOrderId) {\n purchaseType = 'ORDER' as PurchaseType;\n }\n\n // Calculate amount to charge\n const chargeAmount = subtotal - discountAmount;\n\n // Determine provider\n let provider = selectedProvider;\n if (provider === 'AUTO') {\n provider = selectedCurrency === 'INR' ? 'RAZORPAY' : 'STRIPE';\n }\n\n // If this is a store order without a billing order yet, let the store service\n // create it from its authoritative order snapshot. This persists the billing\n // order ID and registers the signed callback on the same service that owns\n // the order; browser line-item prices and shipping values are never sent to\n // billing as the order snapshot.\n if (purchaseType === 'ORDER' && !billingOrderId && storeOrderId && billingAccount) {\n const { data: billingOrderResult } = await ensureStoreOrderBillingOrderMutation({\n variables: {\n storeOrderId,\n billingAccountId: billingAccount.id,\n currency: selectedCurrency,\n },\n });\n const ensureResult = billingOrderResult?.ensureStoreOrderBillingOrder;\n\n if (ensureResult?.success && ensureResult.billingOrderId) {\n billingOrderId = ensureResult.billingOrderId;\n } else {\n const errMsg = ensureResult?.error ?? 'Failed to create billing order';\n setCheckoutState('error');\n setError(errMsg);\n return {\n success: false,\n provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errMsg,\n };\n }\n }\n\n // ========================================\n // CREDITS PAYMENT - use spendCredits mutation\n // Supports multiple addons\n // ========================================\n if (provider === 'CREDITS') {\n const addonItems = items.filter((item) => item.type === 'ADDON');\n const planItem = items.find((item) => item.type === 'PLAN');\n\n const { data } = await spendCreditsMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n planId: planItem ? (planItem.metadata?.planId as string) || planItem.id : undefined,\n // Multiple addons supported via addOnIds array\n addOnIds:\n addonItems.length > 0\n ? addonItems.map((item) => ({\n id: (item.metadata?.addonId as string) || item.id,\n quantity: item.quantity,\n }))\n : undefined,\n isAnnual: planItem?.metadata?.duration === 'yearly',\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // For addon purchases - subscription to attach to\n subscriptionId: addonItems.length > 0 ? selectedSubscriptionId : undefined,\n // For PER_SEAT plans - pass seat count for credit calculation + subscription storage\n seatCount: planItem?.metadata?.seatCount as number | undefined,\n },\n },\n });\n\n const response = data?.spendCredits;\n\n if (!response?.creditTransactionID) {\n setCheckoutState('error');\n setError('Failed to process credit payment');\n return {\n success: false,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Failed to process credit payment',\n };\n }\n\n setCheckoutState('success');\n return {\n success: true,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n // Return transaction ID for success handling\n orderId: response.creditTransactionID,\n };\n }\n\n // ========================================\n // CARD PAYMENT (Stripe/Razorpay) - use initiatePayment\n // Supports single or multiple addons\n // Supports store orders (via orderId parameter)\n // ========================================\n const addonItems = items.filter((item) => item.type === 'ADDON');\n\n // Build addon IDs and quantities for the mutation\n const addonIds =\n addonItems.length > 0\n ? addonItems.map((item) => (item.metadata?.addonId as string) || item.id)\n : undefined;\n const quantities =\n addonItems.length > 0 ? addonItems.map((item) => item.quantity) : undefined;\n\n // Determine if this is an annual plan purchase\n const planItem = items.find((item) => item.type === 'PLAN');\n const isAnnualPlan = planItem?.metadata?.duration === 'yearly';\n\n // Determine auto-renew payment method\n const autoRenewPMId =\n purchaseType === 'PLAN' && autoRenew\n ? autoRenewPaymentMethodId || selectedPaymentMethodId || undefined\n : undefined;\n\n const { data } = await initiatePaymentMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n amount: chargeAmount, // Note: Backend IGNORES this and calculates from DB\n currency: selectedCurrency,\n purchaseType,\n provider: provider,\n // Store order - use orderId parameter (billing order ID)\n orderId: billingOrderId || undefined,\n creditAmount: purchaseType === 'CREDITS' ? firstItem.quantity : undefined,\n planId:\n purchaseType === 'PLAN'\n ? (firstItem.metadata?.planId as string) || firstItem.id\n : undefined,\n // Single addon (backwards compatible) - skip if store order\n addonId:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? (addonItems[0].metadata?.addonId as string) || addonItems[0].id\n : undefined,\n quantity:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? addonItems[0].quantity\n : undefined,\n // Multiple addons (cart checkout) - skip if store order\n addonIds:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? addonIds\n : undefined,\n quantities:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? quantities\n : undefined,\n // For addon purchases - subscription to attach to (skip for store orders)\n subscriptionId:\n purchaseType === 'ADDON' && !billingOrderId ? selectedSubscriptionId : undefined,\n // Saved card support\n paymentMethodId: selectedPaymentMethodId || undefined,\n // Save card if: user checked save card OR auto-renewal is enabled (which requires a saved card)\n // Only save if not using existing card (selectedPaymentMethodId is null)\n saveCard: !selectedPaymentMethodId ? saveCard || autoRenew : undefined,\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // Annual plan flag (for pricing calculation)\n isAnnual: isAnnualPlan || undefined,\n // Promo code (validated and applied on backend)\n promoCode: promoCode || undefined,\n // Auto-renewal (for plan purchases only)\n autoRenew: purchaseType === 'PLAN' && autoRenew ? true : undefined,\n autoRenewPaymentMethodId: autoRenewPMId,\n },\n },\n });\n\n const response = data?.initiatePayment;\n\n if (!response?.success) {\n setCheckoutState('error');\n setError(response?.error ?? 'Payment initiation failed');\n return {\n success: false,\n provider: provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: response?.error ?? 'Payment initiation failed',\n };\n }\n\n // Note: Tax is already calculated upfront in the effect, don't overwrite\n // The response tax is in converted currency which would mess up display\n setCheckoutState('success');\n\n return response;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'An error occurred';\n setCheckoutState('error');\n setError(errorMessage);\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errorMessage,\n };\n }\n },\n [\n billingAccount,\n items,\n selectedCurrency,\n selectedProvider,\n subtotal,\n discountAmount,\n promoCode,\n selectedPaymentMethodId,\n saveCard,\n autoRenew,\n autoRenewPaymentMethodId,\n selectedSubscriptionId,\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n initiatePaymentMutation,\n ensureStoreOrderBillingOrderMutation,\n spendCreditsMutation,\n ]\n );\n\n const reset = useCallback(() => {\n setItems([]);\n setBillingAccount(null);\n setSelectedProvider('AUTO');\n setSelectedCurrency(defaultCurrency);\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n setUseCredits(false);\n setCreditsToUse(0);\n setCheckoutState('idle');\n setError(null);\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n setSelectedPaymentMethodId(null);\n setSaveCard(defaultSaveCard);\n setAutoRenew(false);\n setAutoRenewPaymentMethodId(null);\n setSelectedSubscriptionId(defaultSubscriptionId ?? null);\n }, [defaultCurrency, defaultSaveCard, defaultSubscriptionId]);\n\n return {\n // State\n items,\n billingAccount,\n selectedProvider,\n selectedCurrency,\n promoCode,\n promoDiscount,\n promoDiscountType,\n useCredits,\n creditsToUse,\n checkoutState,\n error,\n\n // Saved card state\n selectedPaymentMethodId,\n saveCard,\n\n // Auto-pay state\n autoRenew,\n autoRenewPaymentMethodId,\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId,\n\n // Computed\n subtotal,\n subtotalUSD,\n discountAmount,\n taxAmount,\n taxRate,\n taxType,\n total,\n totalInSelectedCurrency,\n availableCredits,\n creditsRequired,\n hasEnoughCredits,\n canPayWithCredits,\n isLoading,\n isProcessing,\n isCalculatingTax: false, // Tax is calculated synchronously now\n conversionRate,\n convertedAmount,\n\n // Actions\n addItem,\n removeItem,\n updateItemQuantity,\n updateItemPrice,\n clearItems,\n setBillingAccount: handleSetBillingAccount,\n setPaymentProvider: handleSetPaymentProvider,\n setCurrency: handleSetCurrency,\n setPromoCode,\n validatePromoCode,\n clearPromoCode,\n setUseCredits,\n setCreditsToUse: handleSetCreditsToUse,\n setSelectedPaymentMethodId,\n setSaveCard,\n setAutoRenew,\n setAutoRenewPaymentMethodId,\n setSelectedSubscriptionId,\n initiatePayment,\n reset,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useCheckout.ts"],"sourcesContent":["/**\n * Main checkout hook for managing checkout state and payment processing\n */\n\nimport { useState, useCallback, useMemo, useEffect } from 'react';\nimport {\n useInitiatePaymentCheckoutMutation,\n useSpendCreditsCheckoutMutation,\n useConvertCurrencyLazyQuery,\n useValidateCouponLazyQuery,\n} from '../../../../generated/global-operations';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useEnsureStoreOrderBillingOrder } from './useStoreShipping';\nimport type {\n CheckoutItem,\n CheckoutBillingAccount,\n PaymentProvider,\n Currency,\n CheckoutState,\n PaymentInitResponse,\n PromoCodeResult,\n PurchaseType,\n} from '../types';\n\n/**\n * Credit conversion rate: How many credits equal 1 USD\n * This should match the backend creditRate.rateUSD (default 0.01 means 100 credits = $1)\n */\n// Exported so callers that need a per-item (rather than aggregate) credits\n// amount — e.g. the success page's itemized \"Items Purchased\" list — convert\n// consistently with creditsRequired/hasEnoughCredits below, instead of each\n// re-declaring their own copy of this rate.\nexport const CREDITS_PER_USD = 100;\n\ninterface UseCheckoutOptions {\n billingAccountId?: string;\n defaultCurrency?: Currency;\n defaultSaveCard?: boolean;\n upgradeFromSubscriptionId?: string;\n /** For plan downgrades — old sub auto-renewal cancelled, runs to natural expiry */\n downgradeFromSubscriptionId?: string;\n /** For addon purchases - pre-selected subscription ID */\n defaultSubscriptionId?: string;\n}\n\ninterface UseCheckoutResult {\n // State\n items: CheckoutItem[];\n billingAccount: CheckoutBillingAccount | null;\n selectedProvider: PaymentProvider;\n selectedCurrency: Currency;\n promoCode: string;\n promoDiscount: number;\n promoDiscountType: 'PERCENTAGE' | 'FIXED';\n useCredits: boolean;\n creditsToUse: number;\n checkoutState: CheckoutState;\n error: string | null;\n\n // Saved card state\n selectedPaymentMethodId: string | null;\n saveCard: boolean;\n\n // Auto-pay state (for subscription purchases)\n autoRenew: boolean;\n autoRenewPaymentMethodId: string | null;\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId: string | null;\n\n // Computed values\n subtotal: number;\n subtotalUSD: number;\n discountAmount: number;\n taxAmount: number;\n taxRate: number;\n taxType: string;\n total: number;\n totalInSelectedCurrency: number;\n availableCredits: number;\n /** subtotalUSD converted to credits (CREDITS_PER_USD) — the actual amount a\n * CREDITS-provider purchase charges, in credits. total/totalInSelectedCurrency\n * are in the selected display currency and must never be shown/used as a\n * credits amount (they're a different number). */\n creditsRequired: number;\n hasEnoughCredits: boolean;\n canPayWithCredits: boolean;\n isLoading: boolean;\n isProcessing: boolean;\n isCalculatingTax: boolean;\n\n // Currency conversion\n conversionRate: number;\n convertedAmount: number | null;\n\n // Actions\n addItem: (item: CheckoutItem) => void;\n removeItem: (itemId: string) => void;\n updateItemQuantity: (itemId: string, quantity: number) => void;\n updateItemPrice: (itemId: string, price: number, currency?: Currency) => void;\n clearItems: () => void;\n setBillingAccount: (account: CheckoutBillingAccount | null) => void;\n setPaymentProvider: (provider: PaymentProvider) => void;\n setCurrency: (currency: Currency) => void;\n setPromoCode: (code: string) => void;\n validatePromoCode: () => Promise<PromoCodeResult>;\n clearPromoCode: () => void;\n setUseCredits: (use: boolean) => void;\n setCreditsToUse: (amount: number) => void;\n setSelectedPaymentMethodId: (id: string | null) => void;\n setSaveCard: (save: boolean) => void;\n setAutoRenew: (enabled: boolean) => void;\n setAutoRenewPaymentMethodId: (id: string | null) => void;\n setSelectedSubscriptionId: (id: string | null) => void;\n initiatePayment: () => Promise<PaymentInitResponse>;\n reset: () => void;\n}\n\nexport function useCheckout(options: UseCheckoutOptions = {}): UseCheckoutResult {\n const { currentUser } = useBilling();\n const {\n defaultCurrency = 'USD',\n defaultSaveCard = false, // CRITICAL: Never save cards without explicit user consent\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n defaultSubscriptionId,\n } = options;\n\n // Core state\n const [items, setItems] = useState<CheckoutItem[]>([]);\n const [billingAccount, setBillingAccount] = useState<CheckoutBillingAccount | null>(null);\n const [selectedProvider, setSelectedProvider] = useState<PaymentProvider>('AUTO');\n const [selectedCurrency, setSelectedCurrency] = useState<Currency>(defaultCurrency);\n const [promoCode, setPromoCode] = useState('');\n const [promoDiscount, setPromoDiscount] = useState(0);\n const [promoDiscountType, setPromoDiscountType] = useState<'PERCENTAGE' | 'FIXED'>('PERCENTAGE');\n const [useCredits, setUseCredits] = useState(false);\n const [creditsToUse, setCreditsToUse] = useState(0);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [taxAmount, setTaxAmount] = useState(0);\n const [taxRate, setTaxRate] = useState(0);\n const [taxType, setTaxType] = useState<string>('NONE');\n\n // Saved card state\n const [selectedPaymentMethodId, setSelectedPaymentMethodId] = useState<string | null>(null);\n const [saveCard, setSaveCard] = useState(defaultSaveCard);\n\n // Auto-pay state (for subscription purchases)\n const [autoRenew, setAutoRenew] = useState(false);\n const [autoRenewPaymentMethodId, setAutoRenewPaymentMethodId] = useState<string | null>(null);\n\n // Subscription state (for addon purchases)\n const [selectedSubscriptionId, setSelectedSubscriptionId] = useState<string | null>(\n defaultSubscriptionId ?? null\n );\n\n // GraphQL mutations and queries\n const [initiatePaymentMutation] = useInitiatePaymentCheckoutMutation();\n const [ensureStoreOrderBillingOrderMutation] = useEnsureStoreOrderBillingOrder();\n const [spendCreditsMutation] = useSpendCreditsCheckoutMutation();\n const [convertCurrency, { data: currencyData, loading: currencyLoading }] =\n useConvertCurrencyLazyQuery();\n const [validateCoupon] = useValidateCouponLazyQuery();\n\n // Computed values\n\n // The currency `total`/`subtotal` are actually denominated in. Plans,\n // addons, and credits are priced in USD; store orders are priced in the\n // order's own currency (item.currency), which may not be USD. Converting\n // `total` to the selected currency must pivot FROM this currency, not\n // always assume USD — see BOFF-7127 (a ₹50 item was shown as $4,748.50\n // because the conversion always requested a USD→X rate and multiplied,\n // which is only correct when the source amount actually is in USD).\n const nativeCurrency = useMemo<Currency>(() => items[0]?.currency ?? 'USD', [items]);\n\n const subtotal = useMemo(() => {\n return items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n }, [items]);\n\n // USD subtotal — always use priceUSD (base price) for credit calculations.\n // item.price may be in a local currency (INR, EUR, etc.) when currencyPrices is set.\n const subtotalUSD = useMemo(() => {\n return items.reduce((sum, item) => sum + (item.priceUSD ?? item.price) * item.quantity, 0);\n }, [items]);\n\n const discountAmount = useMemo(() => {\n if (!promoDiscount) return 0;\n // promoDiscount now contains the actual discount amount from backend validation\n // (not the percentage value), so use it directly\n return Math.min(promoDiscount, subtotal);\n }, [subtotal, promoDiscount]);\n\n // Total includes tax (calculated via calculateTax query)\n const total = useMemo(() => {\n const afterDiscount = subtotal - discountAmount;\n const withCredits = useCredits ? Math.max(0, afterDiscount - creditsToUse) : afterDiscount;\n return withCredits + taxAmount;\n }, [subtotal, discountAmount, useCredits, creditsToUse, taxAmount]);\n\n const availableCredits = billingAccount?.creditAmount ?? 0;\n\n // Use subtotalUSD so this works correctly for any display currency\n const creditsRequired = useMemo(() => subtotalUSD * CREDITS_PER_USD, [subtotalUSD]);\n\n const hasEnoughCredits = useMemo(\n () => availableCredits >= creditsRequired,\n [availableCredits, creditsRequired]\n );\n\n // Can only pay with credits if not buying credits\n const canPayWithCredits = useMemo(() => {\n const hasCreditsPurchase = items.some((item) => item.type === 'CREDITS');\n return !hasCreditsPurchase && availableCredits > 0;\n }, [items, availableCredits]);\n\n // Currency conversion result\n const convertedAmount = currencyData?.convertCurrency?.amount ?? null;\n const conversionRate = currencyData?.convertCurrency?.exchangeRate ?? 1;\n const totalInSelectedCurrency = convertedAmount ?? total;\n\n const isLoading = checkoutState === 'loading' || currencyLoading;\n const isProcessing = checkoutState === 'processing';\n\n // Effect to trigger currency conversion when total or currency changes\n // This ensures the converted amount is always up-to-date\n useEffect(() => {\n // Skip conversion for credit payments (no currency conversion needed)\n if (selectedProvider === 'CREDITS') {\n return;\n }\n\n // Skip if no items yet (cart is still loading)\n if (items.length === 0) {\n return;\n }\n\n // Skip conversion if items already have prices in selected currency\n // (Plans/Addons with multi-currency pricing have currencyPrices stored)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === selectedCurrency || item.type === 'CREDITS'\n );\n\n if (allItemsHaveLocalizedPrices) {\n // Items are already priced in the selected currency (from currencyPrices)\n // No conversion needed - subtotal and total are already correct!\n return;\n }\n\n // Only fetch if we have a valid total and it needs converting at all.\n if (total > 0 && nativeCurrency !== selectedCurrency) {\n // For INR, wait until tax has been calculated (taxAmount > 0 or taxRate is set)\n // This prevents double API calls - one before tax, one after\n if (selectedCurrency === 'INR' && taxAmount === 0 && subtotal > 0) {\n // Tax hasn't been calculated yet, skip this conversion\n // The effect will run again when taxAmount changes\n return;\n }\n\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: selectedCurrency,\n fromCurrency: nativeCurrency,\n },\n });\n }\n }, [\n total,\n selectedCurrency,\n selectedProvider,\n convertCurrency,\n taxAmount,\n subtotal,\n items,\n nativeCurrency,\n ]);\n\n // Effect to calculate tax when billing account, subtotal, or currency changes\n // Tax is only applied for INR payments (18% GST)\n useEffect(() => {\n // Only calculate tax if we have a billing account and items\n if (!billingAccount || subtotal <= 0) {\n // Reset tax if no billing account or empty cart\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Skip if paying with credits (no tax on credit payments)\n if (selectedProvider === 'CREDITS') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Only apply tax for INR payments\n if (selectedCurrency !== 'INR') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // For INR: Calculate 18% GST on the USD amount, then we'll convert for display\n const afterDiscount = subtotal - discountAmount;\n const GST_RATE = 0.18;\n const taxInUSD = afterDiscount * GST_RATE;\n\n setTaxAmount(taxInUSD);\n setTaxRate(GST_RATE);\n setTaxType('GST');\n }, [billingAccount, subtotal, discountAmount, selectedCurrency, selectedProvider]);\n\n // Actions\n const addItem = useCallback((item: CheckoutItem) => {\n setItems((prev) => {\n // Plans can only be purchased once - quantity is always 1\n if (item.type === 'PLAN') {\n // Check if any plan already exists in cart\n const existingPlan = prev.find((i) => i.type === 'PLAN');\n if (existingPlan) {\n // Replace existing plan with new one (can only have 1 plan)\n return prev.map((i) => (i.type === 'PLAN' ? { ...item, quantity: 1 } : i));\n }\n // Add new plan with quantity forced to 1\n return [...prev, { ...item, quantity: 1 }];\n }\n\n // For addons and credits, allow quantity increment\n const existing = prev.find((i) => i.id === item.id);\n if (existing) {\n return prev.map((i) =>\n i.id === item.id ? { ...i, quantity: i.quantity + item.quantity } : i\n );\n }\n return [...prev, item];\n });\n }, []);\n\n const removeItem = useCallback((itemId: string) => {\n setItems((prev) => prev.filter((i) => i.id !== itemId));\n }, []);\n\n const updateItemQuantity = useCallback(\n (itemId: string, quantity: number) => {\n if (quantity <= 0) {\n removeItem(itemId);\n return;\n }\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Plans can only have quantity 1\n if (i.type === 'PLAN') {\n return { ...i, quantity: 1 };\n }\n return { ...i, quantity };\n })\n );\n },\n [removeItem]\n );\n\n const updateItemPrice = useCallback((itemId: string, price: number, currency?: Currency) => {\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Update price and optionally currency\n const updates: Partial<CheckoutItem> = { price };\n if (currency) {\n updates.currency = currency;\n }\n return { ...i, ...updates };\n })\n );\n }, []);\n\n const clearItems = useCallback(() => {\n setItems([]);\n }, []);\n\n const handleSetBillingAccount = useCallback((account: CheckoutBillingAccount | null) => {\n setBillingAccount(account);\n // Reset credits usage when billing account changes — different accounts\n // carry different credit balances, so a CREDITS selection made against\n // the previous account may no longer be valid (or even offered) for\n // this one. Without also resetting the provider, the order summary kept\n // displaying amounts in Credits (and checkout could still submit with\n // provider: 'CREDITS') even after the Credits option had disappeared\n // from the payment method list for the new account.\n setUseCredits(false);\n setCreditsToUse(0);\n setSelectedProvider((prev) => (prev === 'CREDITS' ? 'AUTO' : prev));\n }, []);\n\n const handleSetCurrency = useCallback(\n (currency: Currency) => {\n setSelectedCurrency(currency);\n // Skip conversion if items already have localized prices\n // (Plans/Addons with multi-currency pricing don't need conversion)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === currency || item.type === 'CREDITS'\n );\n\n // Only fetch conversion rate if items don't have localized prices\n if (total > 0 && nativeCurrency !== currency && !allItemsHaveLocalizedPrices) {\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: currency,\n fromCurrency: nativeCurrency,\n },\n });\n }\n },\n [total, convertCurrency, items, nativeCurrency]\n );\n\n const handleSetPaymentProvider = useCallback(\n (provider: PaymentProvider) => {\n setSelectedProvider(provider);\n // Razorpay only supports INR\n if (provider === 'RAZORPAY' && selectedCurrency !== 'INR') {\n setSelectedCurrency('INR');\n }\n // Credits purchases don't support coupons yet (BOFF-7192) — clear any\n // promo applied under a card provider so it can't leak into the\n // credits success screen's displayed total.\n if (provider === 'CREDITS') {\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n }\n },\n [selectedCurrency]\n );\n\n const validatePromoCode = useCallback(async (): Promise<PromoCodeResult> => {\n if (!promoCode.trim()) {\n return {\n valid: false,\n code: '',\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Please enter a promo code',\n };\n }\n\n try {\n setCheckoutState('loading');\n\n // Build validation input\n const validationInput = {\n code: promoCode.trim().toUpperCase(),\n userId: currentUser?.id ?? '',\n userEmail: currentUser?.email,\n amount: subtotal,\n currency: selectedCurrency,\n };\n\n const { data, error: queryError } = await validateCoupon({\n variables: {\n input: validationInput,\n },\n });\n\n if (queryError || !data?.validateCoupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n\n const response = data.validateCoupon;\n\n if (!response.valid) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: response.error || 'Invalid coupon code',\n };\n }\n\n // Extract discount details from coupon\n const coupon = response.coupon;\n if (!coupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Coupon data not found',\n };\n }\n\n // Use the ACTUAL discount amount from validation (includes caps and currency conversion)\n const actualDiscountAmount = response.discountAmount || 0;\n\n const discountType = coupon.type === 'FIXED_AMOUNT' ? 'FIXED' : 'PERCENTAGE';\n setPromoDiscount(actualDiscountAmount); // Use actual discount, not coupon.value\n setPromoDiscountType(discountType);\n setCheckoutState('idle');\n\n return {\n valid: true,\n code: coupon.code,\n discountType,\n discountValue: actualDiscountAmount, // Use actual discount, not coupon.value\n };\n } catch {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n }, [currentUser?.email, currentUser?.id, promoCode, selectedCurrency, subtotal, validateCoupon]);\n\n const clearPromoCode = useCallback(() => {\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n }, []);\n\n const handleSetCreditsToUse = useCallback(\n (amount: number) => {\n const maxCredits = Math.min(amount, availableCredits, subtotal - discountAmount);\n setCreditsToUse(Math.max(0, maxCredits));\n },\n [availableCredits, subtotal, discountAmount]\n );\n\n const initiatePayment = useCallback(\n async (): Promise<PaymentInitResponse> => {\n if (!billingAccount) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Please select a billing account',\n };\n }\n\n if (items.length === 0) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'No items in checkout',\n };\n }\n\n try {\n setCheckoutState('processing');\n setError(null);\n\n // Determine purchase type from first item\n const firstItem = items[0];\n let purchaseType: PurchaseType = firstItem.type;\n\n // Check if this is a store order checkout (detect early)\n const storeOrderItem = items.find((item) => item.metadata?.storeOrderId);\n const storeOrderId = storeOrderItem?.metadata?.storeOrderId as string | undefined;\n let billingOrderId =\n (items.find((item) => item.metadata?.billingOrderId)?.metadata?.billingOrderId as\n string | undefined) || undefined;\n\n // For store orders, use 'ORDER' purchase type\n if (storeOrderId) {\n purchaseType = 'ORDER' as PurchaseType;\n }\n\n // Calculate amount to charge\n const chargeAmount = subtotal - discountAmount;\n\n // Determine provider\n let provider = selectedProvider;\n if (provider === 'AUTO') {\n provider = selectedCurrency === 'INR' ? 'RAZORPAY' : 'STRIPE';\n }\n\n // If this is a store order without a billing order yet, let the store service\n // create it from its authoritative order snapshot. This persists the billing\n // order ID and registers the signed callback on the same service that owns\n // the order; browser line-item prices and shipping values are never sent to\n // billing as the order snapshot.\n if (purchaseType === 'ORDER' && !billingOrderId && storeOrderId && billingAccount) {\n const { data: billingOrderResult } = await ensureStoreOrderBillingOrderMutation({\n variables: {\n storeOrderId,\n billingAccountId: billingAccount.id,\n currency: selectedCurrency,\n },\n });\n const ensureResult = billingOrderResult?.ensureStoreOrderBillingOrder;\n\n if (ensureResult?.success && ensureResult.billingOrderId) {\n billingOrderId = ensureResult.billingOrderId;\n } else {\n const errMsg = ensureResult?.error ?? 'Failed to create billing order';\n setCheckoutState('error');\n setError(errMsg);\n return {\n success: false,\n provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errMsg,\n };\n }\n }\n\n // ========================================\n // CREDITS PAYMENT - use spendCredits mutation\n // Supports multiple addons\n // ========================================\n if (provider === 'CREDITS') {\n const addonItems = items.filter((item) => item.type === 'ADDON');\n const planItem = items.find((item) => item.type === 'PLAN');\n\n const { data } = await spendCreditsMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n planId: planItem ? (planItem.metadata?.planId as string) || planItem.id : undefined,\n // Multiple addons supported via addOnIds array\n addOnIds:\n addonItems.length > 0\n ? addonItems.map((item) => ({\n id: (item.metadata?.addonId as string) || item.id,\n quantity: item.quantity,\n }))\n : undefined,\n isAnnual: planItem?.metadata?.duration === 'yearly',\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // For addon purchases - subscription to attach to\n subscriptionId: addonItems.length > 0 ? selectedSubscriptionId : undefined,\n // For PER_SEAT plans - pass seat count for credit calculation + subscription storage\n seatCount: planItem?.metadata?.seatCount as number | undefined,\n },\n },\n });\n\n const response = data?.spendCredits;\n\n if (!response?.creditTransactionID) {\n setCheckoutState('error');\n setError('Failed to process credit payment');\n return {\n success: false,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Failed to process credit payment',\n };\n }\n\n setCheckoutState('success');\n return {\n success: true,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n // Return transaction ID for success handling\n orderId: response.creditTransactionID,\n };\n }\n\n // ========================================\n // CARD PAYMENT (Stripe/Razorpay) - use initiatePayment\n // Supports single or multiple addons\n // Supports store orders (via orderId parameter)\n // ========================================\n const addonItems = items.filter((item) => item.type === 'ADDON');\n\n // Build addon IDs and quantities for the mutation\n const addonIds =\n addonItems.length > 0\n ? addonItems.map((item) => (item.metadata?.addonId as string) || item.id)\n : undefined;\n const quantities =\n addonItems.length > 0 ? addonItems.map((item) => item.quantity) : undefined;\n\n // Determine if this is an annual plan purchase\n const planItem = items.find((item) => item.type === 'PLAN');\n const isAnnualPlan = planItem?.metadata?.duration === 'yearly';\n\n // Determine auto-renew payment method\n const autoRenewPMId =\n purchaseType === 'PLAN' && autoRenew\n ? autoRenewPaymentMethodId || selectedPaymentMethodId || undefined\n : undefined;\n\n const { data } = await initiatePaymentMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n amount: chargeAmount, // Note: Backend IGNORES this and calculates from DB\n currency: selectedCurrency,\n purchaseType,\n provider: provider,\n // Store order - use orderId parameter (billing order ID)\n orderId: billingOrderId || undefined,\n creditAmount: purchaseType === 'CREDITS' ? firstItem.quantity : undefined,\n planId:\n purchaseType === 'PLAN'\n ? (firstItem.metadata?.planId as string) || firstItem.id\n : undefined,\n // Single addon (backwards compatible) - skip if store order\n addonId:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? (addonItems[0].metadata?.addonId as string) || addonItems[0].id\n : undefined,\n quantity:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? addonItems[0].quantity\n : undefined,\n // Multiple addons (cart checkout) - skip if store order\n addonIds:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? addonIds\n : undefined,\n quantities:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? quantities\n : undefined,\n // For addon purchases - subscription to attach to (skip for store orders)\n subscriptionId:\n purchaseType === 'ADDON' && !billingOrderId ? selectedSubscriptionId : undefined,\n // Saved card support\n paymentMethodId: selectedPaymentMethodId || undefined,\n // Save card if: user checked save card OR auto-renewal is enabled (which requires a saved card)\n // Only save if not using existing card (selectedPaymentMethodId is null)\n saveCard: !selectedPaymentMethodId ? saveCard || autoRenew : undefined,\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // Annual plan flag (for pricing calculation)\n isAnnual: isAnnualPlan || undefined,\n // Promo code (validated and applied on backend)\n promoCode: promoCode || undefined,\n // Auto-renewal (for plan purchases only)\n autoRenew: purchaseType === 'PLAN' && autoRenew ? true : undefined,\n autoRenewPaymentMethodId: autoRenewPMId,\n },\n },\n });\n\n const response = data?.initiatePayment;\n\n if (!response?.success) {\n setCheckoutState('error');\n setError(response?.error ?? 'Payment initiation failed');\n return {\n success: false,\n provider: provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: response?.error ?? 'Payment initiation failed',\n };\n }\n\n // Note: Tax is already calculated upfront in the effect, don't overwrite\n // The response tax is in converted currency which would mess up display\n setCheckoutState('success');\n\n return response;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'An error occurred';\n setCheckoutState('error');\n setError(errorMessage);\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errorMessage,\n };\n }\n },\n [\n billingAccount,\n items,\n selectedCurrency,\n selectedProvider,\n subtotal,\n discountAmount,\n promoCode,\n selectedPaymentMethodId,\n saveCard,\n autoRenew,\n autoRenewPaymentMethodId,\n selectedSubscriptionId,\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n initiatePaymentMutation,\n ensureStoreOrderBillingOrderMutation,\n spendCreditsMutation,\n ]\n );\n\n const reset = useCallback(() => {\n setItems([]);\n setBillingAccount(null);\n setSelectedProvider('AUTO');\n setSelectedCurrency(defaultCurrency);\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n setUseCredits(false);\n setCreditsToUse(0);\n setCheckoutState('idle');\n setError(null);\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n setSelectedPaymentMethodId(null);\n setSaveCard(defaultSaveCard);\n setAutoRenew(false);\n setAutoRenewPaymentMethodId(null);\n setSelectedSubscriptionId(defaultSubscriptionId ?? null);\n }, [defaultCurrency, defaultSaveCard, defaultSubscriptionId]);\n\n return {\n // State\n items,\n billingAccount,\n selectedProvider,\n selectedCurrency,\n promoCode,\n promoDiscount,\n promoDiscountType,\n useCredits,\n creditsToUse,\n checkoutState,\n error,\n\n // Saved card state\n selectedPaymentMethodId,\n saveCard,\n\n // Auto-pay state\n autoRenew,\n autoRenewPaymentMethodId,\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId,\n\n // Computed\n subtotal,\n subtotalUSD,\n discountAmount,\n taxAmount,\n taxRate,\n taxType,\n total,\n totalInSelectedCurrency,\n availableCredits,\n creditsRequired,\n hasEnoughCredits,\n canPayWithCredits,\n isLoading,\n isProcessing,\n isCalculatingTax: false, // Tax is calculated synchronously now\n conversionRate,\n convertedAmount,\n\n // Actions\n addItem,\n removeItem,\n updateItemQuantity,\n updateItemPrice,\n clearItems,\n setBillingAccount: handleSetBillingAccount,\n setPaymentProvider: handleSetPaymentProvider,\n setCurrency: handleSetCurrency,\n setPromoCode,\n validatePromoCode,\n clearPromoCode,\n setUseCredits,\n setCreditsToUse: handleSetCreditsToUse,\n setSelectedPaymentMethodId,\n setSaveCard,\n setAutoRenew,\n setAutoRenewPaymentMethodId,\n setSelectedSubscriptionId,\n initiatePayment,\n reset,\n };\n}\n"],"mappings":"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"}
@@ -1343,7 +1343,7 @@ var $e = () => {
1343
1343
  isEditable: E !== "PLAN" && E !== "ORDER"
1344
1344
  })
1345
1345
  }),
1346
- /* @__PURE__ */ p("div", {
1346
+ F.selectedProvider !== "CREDITS" && /* @__PURE__ */ p("div", {
1347
1347
  className: "px-4 pb-4 border-t border-border-subtle pt-4",
1348
1348
  children: /* @__PURE__ */ p(ve, {
1349
1349
  value: F.promoCode,
@@ -1 +1 @@
1
- {"version":3,"file":"CheckoutPage.js","names":[],"sources":["../../../../../src/billing/modules/checkout/pages/CheckoutPage.tsx"],"sourcesContent":["/**\n * Checkout Page Component\n * Main checkout flow for purchasing credits, plans, and add-ons\n * Supports single item checkout and multi-addon cart checkout\n */\n\nimport { type FC, useState, useEffect, useCallback, useMemo, useRef } from 'react';\nimport { useParams, useLocation } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport {\n ArrowLeft,\n ShoppingCart,\n Loader2,\n CheckCircle,\n XCircle,\n Coins,\n Package,\n Blocks,\n Wallet,\n CreditCard,\n ArrowRight,\n Receipt,\n RefreshCw,\n} from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useAddonCartStore } from '../../addons/store/addonCartStore';\n\nimport {\n CREDITS_PER_USD,\n useCheckout,\n useNativeCheckout,\n usePaymentProviders,\n usePaymentStatusPolling,\n} from '../hooks';\nimport { isNativeIAPAvailable } from '../../../lib/native-purchases';\nimport { NativeCheckoutView } from '../components/NativeCheckoutView';\nimport { getStoredBillingAccountId } from '../../../hooks/useBillingAccountSelection';\nimport {\n useGetBillingAccountsCheckoutQuery,\n useGetPlanCheckoutQuery,\n useGetSelectedAddonCheckoutQuery,\n useGetPaymentMethodsCheckoutQuery,\n useGetActiveSubscriptionsCheckoutQuery,\n useGetCostOfCreditsCheckoutQuery,\n useGetStoreOrderCheckoutQuery,\n type GetActiveSubscriptionsCheckoutQuery,\n type GetBillingAccountsCheckoutQuery,\n} from '../../../../generated/global-operations';\nimport {\n BillingAccountSelector,\n OrderSummary,\n CreditsAmountInput,\n StripePaymentForm,\n RazorpayPaymentButton,\n SubscriptionSelector,\n SimpleAutoPayToggle,\n UnifiedPaymentMethodSelector,\n PromoCodeInput,\n CompleteBillingAccountModal,\n isBillingAccountComplete,\n StoreShippingSection,\n type StoreShippingSelection,\n} from '../components';\nimport {\n WorkspaceQuotaAssignmentModal,\n isWorkspaceScopedQuota,\n type WorkspaceQuotaToAssign,\n type WorkspaceOption,\n} from '../components/WorkspaceQuotaAssignmentModal';\nimport {\n buildStoreOrderCheckoutItems,\n normalizeStoreOrderCurrency,\n type StoreOrderLineItem,\n} from '../utils/storeOrder';\nimport { useSetStoreOrderShipping, STORE_SHIPPING_ITEM_ID } from '../hooks/useStoreShipping';\nimport type { SavedPaymentMethod } from '../types';\n\nimport type { CheckoutItem, CheckoutBillingAccount, PurchaseType } from '../types';\n\ntype RawCheckoutBillingAccount = GetBillingAccountsCheckoutQuery['myBillingAccounts'][number];\n\nfunction normalizeCheckoutBillingAccount(\n account: RawCheckoutBillingAccount\n): CheckoutBillingAccount | null {\n if (\n typeof account.id !== 'string' ||\n typeof account.name !== 'string' ||\n typeof account.email !== 'string' ||\n typeof account.currency !== 'string' ||\n typeof account.creditAmount !== 'number' ||\n typeof account.isActive !== 'boolean'\n ) {\n return null;\n }\n\n return {\n ...account,\n id: account.id,\n name: account.name,\n email: account.email,\n currency: account.currency,\n creditAmount: account.creditAmount,\n isActive: account.isActive,\n billingAddresses:\n account.billingAddresses === null\n ? null\n : account.billingAddresses?.flatMap((address) => (address ? [{ ...address }] : [])),\n };\n}\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAppShell } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { recordAffiliateEvent } from '../../affiliate/api';\n\ntype CheckoutMode = 'select' | 'payment' | 'processing' | 'success' | 'error';\n\ninterface LocationState {\n purchaseType?: PurchaseType;\n itemId?: string;\n quantity?: number;\n upgradeFromSubscriptionId?: string;\n downgradeFromSubscriptionId?: string;\n currentPlanName?: string;\n billingAccountId?: string;\n}\n\nexport const CheckoutPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { planId, addonId, storeOrderId } = useParams<{\n planId?: string;\n addonId?: string;\n storeOrderId?: string;\n }>();\n const navigateTo = useBillingNavigate();\n const location = useLocation();\n const locationState = location.state as LocationState | undefined;\n\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const {\n orgId,\n stripePublishableKey,\n razorpayKey,\n apiGatewayUrl,\n authToken,\n currentUser,\n workspaceId,\n } = useBilling();\n const { productSlug } = useAppShell();\n const referralEventReportedRef = useRef<string | null>(null);\n // Workspace options for the assignment modal — single workspace for now.\n // Multi-workspace support requires a listWorkspaces query (future enhancement).\n const workspaceOptions: WorkspaceOption[] = workspaceId\n ? [{ id: workspaceId, name: 'Current workspace' }]\n : [];\n\n // Addon cart store for multi-addon checkout\n const { items: cartItems, clearCart, _hasHydrated: cartHydrated } = useAddonCartStore();\n\n // Check if this is a cart-based checkout (route: /checkout/addons)\n const isCartCheckout = location.pathname.includes('/checkout/addons') && !addonId;\n\n // Check if this is a store order checkout (route: /checkout/store/:storeOrderId)\n const isStoreCheckout = location.pathname.includes('/checkout/store') && !!storeOrderId;\n\n // Determine what we're purchasing\n const purchaseType: PurchaseType = isCartCheckout\n ? 'ADDON'\n : isStoreCheckout\n ? 'ORDER' // Store orders use ORDER purchase type\n : (locationState?.purchaseType ?? (planId ? 'PLAN' : addonId ? 'ADDON' : 'CREDITS'));\n const itemId = locationState?.itemId ?? planId ?? addonId ?? storeOrderId;\n\n // Billing account completion gate\n const [showCompleteAccountModal, setShowCompleteAccountModal] = useState(false);\n // Bypass the isBillingAccountComplete gate for the call that follows modal completion.\n // The gate uses stale Apollo cache (contactNumber/billingAddresses are not returned by\n // updateBillingAccount mutation), so we skip it once immediately after the modal saves.\n const bypassBillingGate = useRef(false);\n\n // Workspace quota assignment modal state\n const [showAssignModal, setShowAssignModal] = useState(false);\n const [pendingWorkspaceQuotas, setPendingWorkspaceQuotas] = useState<WorkspaceQuotaToAssign[]>(\n []\n );\n const [pendingSubscriptionId, setPendingSubscriptionId] = useState<string | null>(null);\n\n // Checkout state\n const [mode, setMode] = useState<CheckoutMode>('select');\n const [paymentResponse, setPaymentResponse] = useState<{\n clientSecret?: string;\n orderId?: string;\n razorpayKeyId?: string;\n razorpayCustomerId?: string;\n paymentId?: string;\n paymentComplete?: boolean;\n prefillName?: string;\n prefillEmail?: string;\n prefillContact?: string;\n total?: number; // Total in target currency from backend (e.g., INR)\n currency?: string;\n } | null>(null);\n const [paymentErrorMessage, setPaymentErrorMessage] = useState<string | null>(null);\n\n // Store purchase details for success page (captured before clearing)\n const [purchaseDetails, setPurchaseDetails] = useState<{\n items: typeof checkout.items;\n total: number;\n billingAccountName: string;\n billingAccountId: string;\n previousBalance: number;\n newBalance: number;\n paymentMethod: 'credits' | 'card';\n currency: string;\n transactionId?: string;\n } | null>(null);\n\n // Track payment type for polling (card vs credits)\n const [pollingPaymentType, setPollingPaymentType] = useState<'card' | 'credits'>('card');\n\n useEffect(() => {\n if (mode !== 'success' || !purchaseDetails) {\n return;\n }\n\n const rawAttribution =\n typeof window !== 'undefined'\n ? window.localStorage.getItem(getProfileScopedKey('boff-referral-attribution'))\n : null;\n if (!rawAttribution) {\n return;\n }\n\n let attribution: { journeyId?: string; codeId?: string };\n try {\n attribution = JSON.parse(rawAttribution) as { journeyId?: string; codeId?: string };\n } catch {\n return;\n }\n if (!attribution.journeyId && !attribution.codeId) {\n return;\n }\n\n const uniqueReference =\n purchaseDetails.transactionId ||\n paymentResponse?.paymentId ||\n paymentResponse?.orderId ||\n storeOrderId ||\n itemId ||\n purchaseType;\n\n const signature = `${attribution.journeyId ?? 'journey'}:${uniqueReference}`;\n if (referralEventReportedRef.current === signature) {\n return;\n }\n referralEventReportedRef.current = signature;\n\n void recordAffiliateEvent(\n {\n journeyId: attribution.journeyId,\n codeId: attribution.codeId,\n eventType: purchaseType === 'ORDER' ? 'STORE_PURCHASE' : 'PAID_CONVERSION',\n productSlug,\n actorUserId: currentUser?.id,\n orderId:\n purchaseDetails.transactionId || paymentResponse?.paymentId || paymentResponse?.orderId,\n billingAccountId: purchaseDetails.billingAccountId,\n storeOrderId: purchaseType === 'ORDER' ? storeOrderId : undefined,\n valueAmount: purchaseDetails.total,\n currency: purchaseDetails.currency,\n mode: 'billing-checkout',\n metadata: {\n purchaseType,\n billingAccountName: purchaseDetails.billingAccountName,\n },\n },\n { apiGatewayUrl, authToken, orgId }\n ).catch(() => {\n referralEventReportedRef.current = null;\n });\n }, [\n apiGatewayUrl,\n authToken,\n currentUser?.id,\n itemId,\n mode,\n orgId,\n paymentResponse?.orderId,\n paymentResponse?.paymentId,\n productSlug,\n purchaseDetails,\n purchaseType,\n storeOrderId,\n ]);\n\n // Per-seat plan seat count\n const [seatCount, setSeatCount] = useState<number>(1);\n\n // Use checkout hook - pass upgrade/downgrade context if this is a plan change\n const checkout = useCheckout({\n defaultCurrency: 'USD',\n upgradeFromSubscriptionId: locationState?.upgradeFromSubscriptionId,\n downgradeFromSubscriptionId: locationState?.downgradeFromSubscriptionId,\n });\n const { config: providerConfig, stripeEnabled, razorpayEnabled } = usePaymentProviders();\n\n // Payment status polling for both card and credit payments\n const {\n // isPolling - available but UI relies on mode state instead\n elapsedTime,\n error: pollingError,\n // paymentResult - available but not directly used (callbacks handle results)\n } = usePaymentStatusPolling({\n paymentId: mode === 'processing' ? (paymentResponse?.paymentId ?? null) : null,\n billingAccountId: checkout.billingAccount?.id ?? purchaseDetails?.billingAccountId ?? null,\n paymentType: pollingPaymentType, // 'card' or 'credits' based on selected provider\n pollingInterval: 5000, // Poll every 5 seconds\n maxPollingTime: 300000, // 5 minutes timeout\n onSuccess: (result) => {\n // Update transaction ID if available from polling result\n if (result.transactionId) {\n setPurchaseDetails((prev) =>\n prev ? { ...prev, transactionId: result.transactionId } : prev\n );\n }\n setMode('success');\n if (isCartCheckout) {\n clearCart();\n }\n\n // Derive workspace-scoped quotas from the purchased plan's features.\n // We only show the modal for PLAN and ADDON purchases that include workspace quotas.\n // A feature's own context.quantity is a per-plan \"absolute limit\" override\n // (set via the Plan admin form) that takes precedence over the quota\n // catalog's generic limits.value default — must match buildWorkspaceQuotaInputs\n // (the backend function that actually grants the quota) or this preview\n // promises a different number than what the customer actually receives.\n if ((purchaseType === 'PLAN' || purchaseType === 'ADDON') && workspaceId) {\n type PlanFeature = {\n context?: Record<string, unknown> | null;\n quota?: { name?: string | null; limits?: Record<string, unknown> | null } | null;\n };\n const rawFeatures: Array<PlanFeature | null | undefined> =\n planData?.getPlan?.features ?? [];\n const features: PlanFeature[] = rawFeatures.filter((f): f is PlanFeature => f != null);\n const wsQuotas: WorkspaceQuotaToAssign[] = features\n .filter((f: PlanFeature) => f.quota?.name && isWorkspaceScopedQuota(f.quota.name))\n .map((f: PlanFeature) => {\n const limits = f.quota?.limits as Record<string, unknown> | null | undefined;\n const limitsType = limits && 'type' in limits ? String(limits.type) : 'number';\n const overrideQuantity =\n f.context && 'quantity' in f.context ? Number(f.context.quantity) : null;\n const catalogValue = limits && 'value' in limits ? Number(limits.value) : null;\n const limitsValue = overrideQuantity ?? catalogValue;\n return {\n quotaName: f.quota!.name!,\n productId:\n ((planData?.getPlan?.context as Record<string, unknown> | null)?.[\n 'productId'\n ] as string) ?? 'platform',\n limit: limitsType === 'number' ? limitsValue : null,\n noLimit: limitsType !== 'number',\n resetPeriod: 'NEVER',\n quotaMode: 'dedicated' as const,\n };\n });\n\n if (wsQuotas.length > 0) {\n setPendingWorkspaceQuotas(wsQuotas);\n // Refetch subscriptions to resolve the subscriptionId for the modal\n void refetchSubscriptions().then(\n (res: { data?: GetActiveSubscriptionsCheckoutQuery }) => {\n const match = res.data?.getActiveSubscriptions?.find(\n (s: { planId: string; id: string }) => s.planId === itemId\n );\n if (match) setPendingSubscriptionId(match.id);\n }\n );\n // With exactly one workspace there's no real choice to make — the\n // backend's autoActivateSingleWorkspaceForSubscription already\n // assigned these quotas to it at purchase time (see\n // global-billing-svc's Razorpay/Stripe webhook handlers), so this\n // modal would just be asking the customer to confirm something\n // that already happened. Only show it when there's a genuine\n // choice — 2+ workspaces — which is exactly the ambiguity it\n // exists to resolve.\n if (workspaceOptions.length > 1) {\n setShowAssignModal(true);\n }\n }\n }\n\n // Auto-redirect is handled by useEffect\n },\n onFailure: (result) => {\n setPaymentErrorMessage(result.failureReason || 'Payment failed');\n setMode('error');\n },\n onTimeout: () => {\n setPaymentErrorMessage(\n 'Payment verification timed out. Please check your billing history for the payment status.'\n );\n setMode('error');\n },\n });\n\n // Fetch billing accounts\n const {\n data: accountsData,\n loading: accountsLoading,\n refetch: refetchAccounts,\n } = useGetBillingAccountsCheckoutQuery({});\n\n // Fetch plan if purchasing a plan\n const {\n data: planData,\n loading: planLoading,\n // error: planError - available but not displayed (loading/empty states suffice)\n } = useGetPlanCheckoutQuery({\n variables: { id: itemId! },\n skip: purchaseType !== 'PLAN' || !itemId,\n });\n\n // Fetch addon if purchasing an addon\n const { data: addonData, loading: addonLoading } = useGetSelectedAddonCheckoutQuery({\n variables: { addonIDs: itemId ? [itemId] : [] },\n skip: purchaseType !== 'ADDON' || !itemId || isStoreCheckout, // Skip for store checkout\n });\n\n // Fetch store order (for store checkout)\n const { data: storeOrderData } = useGetStoreOrderCheckoutQuery({\n variables: { id: storeOrderId! },\n skip: !isStoreCheckout || !storeOrderId,\n });\n\n // ── Physical store-order shipping (physical produce only) ─────────────────\n // All shipping UI + logic below is gated on `isPhysicalStoreOrder`, which\n // REQUIRES isStoreCheckout (purchaseType 'ORDER') AND a PHYSICAL line item.\n // The digital-app / plan / addon / credits paths — and digital-only store\n // orders — never see any of it and behave byte-for-byte as before.\n //\n // getStoreOrder.lineItems is typed Record<string,unknown> by codegen but is a\n // StoreOrderLineItem[] at runtime — cast defensively behind an Array.isArray\n // guard. itemType is written LOWERCASE by microfe-store (CartPage), so compare\n // case-insensitively or the gate silently never fires.\n const storeOrderLineItems = useMemo<StoreOrderLineItem[]>(() => {\n const raw = storeOrderData?.getStoreOrder?.lineItems;\n return Array.isArray(raw) ? (raw as StoreOrderLineItem[]) : [];\n }, [storeOrderData]);\n\n const isPhysicalStoreOrder =\n isStoreCheckout &&\n storeOrderLineItems.some((li) => String(li.itemType).toUpperCase() === 'PHYSICAL');\n\n // The order currency drives the synthetic shipping line so it converts in\n // lockstep with the product ORDER items (see currency-conversion effect below).\n const storeOrderCurrency = normalizeStoreOrderCurrency(storeOrderData?.getStoreOrder?.currency);\n\n // Buyer's shipping selection, surfaced by <StoreShippingSection />. Cost is\n // DISPLAY-ONLY here; the backend recomputes it authoritatively on submit.\n const [shipping, setShipping] = useState<StoreShippingSelection>({\n shippingAddressId: null,\n shippingMethodId: null,\n shippingCost: 0,\n destinationCountry: null,\n });\n\n // NEW backend mutation — see hooks/useStoreShipping.ts. Inert until\n // global-store-svc ships `setStoreOrderShipping`.\n const [setStoreOrderShipping] = useSetStoreOrderShipping();\n\n // Fetch saved payment methods for selected billing account\n const { data: paymentMethodsData, loading: paymentMethodsLoading } =\n useGetPaymentMethodsCheckoutQuery({\n variables: { billingAccountId: checkout.billingAccount?.id! },\n skip: !checkout.billingAccount?.id,\n });\n\n // Fetch active subscriptions for addon purchases (needed to select which subscription to attach addon to)\n // Skip for store checkout - store items don't require an active subscription\n // Also load for PLAN purchases so we can resolve subscriptionId after payment success\n const {\n data: subscriptionsData,\n loading: subscriptionsLoading,\n refetch: refetchSubscriptions,\n } = useGetActiveSubscriptionsCheckoutQuery({\n variables: { billingAccountId: checkout.billingAccount?.id! },\n skip:\n !checkout.billingAccount?.id ||\n (purchaseType !== 'ADDON' && purchaseType !== 'PLAN') ||\n isStoreCheckout,\n });\n\n // Memoized subscriptions list\n const activeSubscriptions = useMemo(() => {\n return subscriptionsData?.getActiveSubscriptions ?? [];\n }, [subscriptionsData]);\n\n // Selected subscription for addon purchases\n const selectedSubscription = useMemo(() => {\n if (!checkout.selectedSubscriptionId || activeSubscriptions.length === 0) return null;\n return (\n activeSubscriptions.find((s: { id: string }) => s.id === checkout.selectedSubscriptionId) ??\n null\n );\n }, [checkout.selectedSubscriptionId, activeSubscriptions]);\n\n // Fetch credit cost from backend (for credits purchase)\n // Use current quantity from items, or default to 100 for initial query\n // Query in selected currency to get correct pricing\n const creditQuantity = checkout.items[0]?.type === 'CREDITS' ? checkout.items[0].quantity : 100;\n const { data: creditCostData, loading: creditCostLoading } = useGetCostOfCreditsCheckoutQuery({\n variables: { creditAmount: creditQuantity, currency: checkout.selectedCurrency || 'USD' },\n skip: purchaseType !== 'CREDITS',\n });\n\n // Calculate cost per credit from backend response\n const costPerCredit = useMemo(() => {\n if (creditCostData?.getCostOfCredits?.amount && creditQuantity > 0) {\n return creditCostData.getCostOfCredits.amount / creditQuantity;\n }\n return 0.01; // Default: 100 credits = $1\n }, [creditCostData, creditQuantity]);\n\n // Memoized saved payment methods\n const savedPaymentMethods = useMemo<SavedPaymentMethod[]>(() => {\n return paymentMethodsData?.getPaymentMethods ?? [];\n }, [paymentMethodsData]);\n\n // Memoized billing accounts list\n const billingAccounts = useMemo<CheckoutBillingAccount[]>(() => {\n return accountsData?.myBillingAccounts ?? [];\n }, [accountsData]);\n\n // Extract billing country from selected billing account's first billing address\n // This is used to restrict currency and payment provider options\n const billingCountry = useMemo<string | undefined>(() => {\n if (!checkout.billingAccount) return undefined;\n\n // Find the full account data with billing addresses\n const fullAccount = billingAccounts.find((acc) => acc.id === checkout.billingAccount?.id);\n const billingAddresses = fullAccount?.billingAddresses;\n\n if (billingAddresses && billingAddresses.length > 0) {\n return billingAddresses[0]?.country || undefined;\n }\n return undefined;\n }, [checkout.billingAccount, billingAccounts]);\n\n // Track if initial provider selection has been done for this billing country\n const [initialProviderSet, setInitialProviderSet] = useState<string | null>(null);\n\n // Auto-select appropriate currency based on billing country and billing account currency\n // - India: INR only (for Zoho Books GST invoice compatibility)\n // - International: Prefer billing account currency, or USD (INR not allowed for Stripe invoice compatibility)\n useEffect(() => {\n if (!billingCountry) return;\n\n if (billingCountry === 'IN') {\n // Indian billing: Force INR currency\n if (checkout.selectedCurrency !== 'INR') {\n checkout.setCurrency('INR');\n }\n // Auto-select Razorpay ONLY on first load for this billing country (don't override user's choice)\n if (\n razorpayEnabled &&\n initialProviderSet !== billingCountry &&\n checkout.selectedProvider !== 'CREDITS'\n ) {\n checkout.setPaymentProvider('RAZORPAY');\n setInitialProviderSet(billingCountry);\n }\n } else {\n // International billing: Force non-INR currency\n if (checkout.selectedCurrency === 'INR') {\n // Prefer billing account's currency if it's not INR, otherwise default to USD\n const accountCurrency = checkout.billingAccount?.currency;\n const preferredCurrency =\n accountCurrency && accountCurrency !== 'INR' ? accountCurrency : 'USD';\n checkout.setCurrency(preferredCurrency);\n }\n // For international, Stripe is the only option (Razorpay is India-only)\n if (checkout.selectedProvider === 'RAZORPAY') {\n checkout.setPaymentProvider('STRIPE');\n setInitialProviderSet(billingCountry);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Using specific checkout properties is intentional for granular updates\n }, [\n billingCountry,\n checkout.selectedCurrency,\n checkout.selectedProvider,\n checkout.setCurrency,\n checkout.setPaymentProvider,\n checkout.billingAccount?.currency,\n razorpayEnabled,\n initialProviderSet,\n ]);\n\n const {\n availableCredits,\n billingAccount: checkoutBillingAccount,\n canPayWithCredits,\n selectedCurrency,\n selectedPaymentMethodId,\n selectedProvider,\n setCurrency,\n setPaymentProvider,\n setSelectedPaymentMethodId,\n total: checkoutTotal,\n } = checkout;\n\n // Auto-select billing account's currency when billing account changes (for international)\n useEffect(() => {\n if (!checkoutBillingAccount?.currency || billingCountry === 'IN') return;\n\n const accountCurrency = checkoutBillingAccount.currency;\n // Update to billing account's currency if it's different and valid (not INR for international)\n if (accountCurrency !== 'INR' && selectedCurrency !== accountCurrency) {\n setCurrency(accountCurrency);\n }\n }, [billingCountry, checkoutBillingAccount, selectedCurrency, setCurrency]);\n\n // Auto-select payment method with priority: Credits > Saved Card > New Card\n useEffect(() => {\n // Skip if already manually selected something (not AUTO or null)\n if (selectedProvider !== 'AUTO' && selectedProvider !== null) {\n return; // Already selected, don't override user choice\n }\n\n const hasEnoughCredits = availableCredits >= checkoutTotal;\n\n // Priority 1: Credits (if available and sufficient)\n if (canPayWithCredits && hasEnoughCredits) {\n setPaymentProvider('CREDITS');\n setSelectedPaymentMethodId(null);\n return;\n }\n\n // Priority 2: Default saved card\n if (savedPaymentMethods.length > 0 && !selectedPaymentMethodId) {\n const defaultMethod = savedPaymentMethods.find((pm) => pm.isDefault && pm.isVerified);\n const firstVerified = savedPaymentMethods.find((pm) => pm.isVerified);\n const methodToSelect = defaultMethod || firstVerified;\n\n if (methodToSelect) {\n setSelectedPaymentMethodId(methodToSelect.id);\n // Auto-set provider based on card type\n if (methodToSelect.razorpayPaymentMethodId) {\n setPaymentProvider('RAZORPAY');\n } else if (methodToSelect.stripePaymentMethodId) {\n setPaymentProvider('STRIPE');\n }\n return;\n }\n }\n\n // Priority 3: New card (auto-select provider based on country)\n // This is already handled by the currency auto-selection effect above\n }, [\n availableCredits,\n canPayWithCredits,\n checkoutTotal,\n savedPaymentMethods,\n selectedPaymentMethodId,\n selectedProvider,\n setPaymentProvider,\n setSelectedPaymentMethodId,\n ]);\n\n // Track if item was already added to prevent duplicates\n const [itemAdded, setItemAdded] = useState(false);\n\n useEffect(() => {\n if (!isStoreCheckout || itemAdded || !storeOrderData?.getStoreOrder) {\n return;\n }\n\n const orderCurrency = normalizeStoreOrderCurrency(storeOrderData.getStoreOrder.currency);\n if (selectedCurrency !== orderCurrency) {\n setCurrency(orderCurrency);\n }\n }, [isStoreCheckout, itemAdded, selectedCurrency, setCurrency, storeOrderData]);\n\n // Initialize checkout with items\n // Initialize seat count from plan minSeats when plan data loads\n useEffect(() => {\n if (purchaseType === 'PLAN' && planData?.getPlan?.pricingModel === 'PER_SEAT') {\n const minSeats = planData.getPlan.minSeats ?? 1;\n setSeatCount((prev) => (prev < minSeats ? minSeats : prev));\n }\n }, [planData, purchaseType]);\n\n // Recalculate checkout item when seat count changes for PER_SEAT plans\n useEffect(() => {\n if (purchaseType === 'PLAN' && planData?.getPlan?.pricingModel === 'PER_SEAT' && itemAdded) {\n setItemAdded(false); // triggers re-add with new seatCount\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [seatCount]);\n\n useEffect(() => {\n // Skip if already added items\n if (itemAdded) {\n return;\n }\n\n // Handle cart-based checkout (multiple addons from cart)\n // Wait for cart to hydrate before processing\n if (isCartCheckout && cartHydrated && cartItems.length > 0) {\n // Clear existing items first to prevent duplicates (React strict mode can run effects twice)\n checkout.clearItems();\n\n for (const cartItem of cartItems) {\n const addon = cartItem.addon;\n\n // Extract features from addon (using SubscriptionFeatures type with quota).\n // context.quantity is the amount this addon actually grants per unit\n // purchased — takes precedence over the quota catalog's generic\n // limits.value, same as the other purchase paths on this page.\n const features =\n addon.features?.map((f) => {\n const limits = f.quota?.limits as Record<string, number | string | boolean> | undefined;\n const overrideQuantity =\n f.context && 'quantity' in f.context ? Number(f.context.quantity) : undefined;\n return {\n id: f.quota?.id || f.id,\n name: f.quota?.name || 'Feature',\n limits:\n overrideQuantity !== undefined && limits && 'type' in limits\n ? { ...limits, value: overrideQuantity }\n : limits,\n };\n }) || [];\n\n // Use stored currency price if available, otherwise fallback to base USD price\n const selectedCurrency = checkout.selectedCurrency || 'USD';\n const currencyPrices = (addon as { currencyPrices?: Record<string, number> })\n .currencyPrices;\n const priceInSelectedCurrency = currencyPrices?.[selectedCurrency] ?? addon.price;\n // addon.price/currency are always localized to the caller's currency\n // context (e.g. an INR request gets addon.price in INR, regardless of\n // the addon's stored base currency) — currencyPrices.USD is the one\n // field that's reliably actually USD, needed for credit calculations.\n const usdPrice = currencyPrices?.USD ?? addon.price;\n\n const item: CheckoutItem = {\n id: addon.id,\n type: 'ADDON',\n name: addon.name,\n description:\n typeof addon.context === 'object' && addon.context !== null\n ? (addon.context as { description?: string }).description\n : typeof addon.context === 'string'\n ? addon.context\n : undefined,\n price: priceInSelectedCurrency,\n priceUSD: usdPrice,\n currency: selectedCurrency,\n quantity: cartItem.quantity,\n features,\n metadata: { addonId: addon.id },\n };\n checkout.addItem(item);\n }\n setItemAdded(true);\n\n return;\n }\n\n if (purchaseType === 'PLAN' && planData?.getPlan) {\n const plan = planData.getPlan;\n\n // Extract features from plan. A feature's own context.quantity is a\n // per-plan \"absolute limit\" override (set via the Plan admin form) that\n // takes precedence over the quota catalog's generic limits.value\n // default — must match buildWorkspaceQuotaInputs (the backend function\n // that actually grants the quota) or this order summary promises a\n // different number than what the customer actually receives.\n const features =\n plan.features?.map(\n (f: {\n id: string;\n context?: Record<string, unknown> | null;\n quota?: { id: string; name: string; limits?: Record<string, unknown> };\n }) => {\n const limits = f.quota?.limits;\n const overrideQuantity =\n f.context && 'quantity' in f.context ? Number(f.context.quantity) : undefined;\n return {\n id: f.quota?.id || f.id,\n name: f.quota?.name || 'Feature',\n limits:\n overrideQuantity !== undefined && limits && 'type' in limits\n ? { ...limits, value: overrideQuantity }\n : limits,\n };\n }\n ) || [];\n\n // Use stored currency price if available, otherwise fallback to base USD price\n const selectedCurrency = checkout.selectedCurrency || 'USD';\n const currencyPrices = plan.currencyPrices as Record<\n string,\n number | { perSeat?: number }\n > | null;\n const isPerSeat = plan.pricingModel === 'PER_SEAT';\n\n let priceInSelectedCurrency: number;\n if (isPerSeat && plan.pricePerSeat) {\n // PER_SEAT: per-seat rate × seat count\n const currencyEntry = currencyPrices?.[selectedCurrency];\n const perSeatRate =\n typeof currencyEntry === 'object' && currencyEntry?.perSeat\n ? currencyEntry.perSeat\n : plan.pricePerSeat;\n priceInSelectedCurrency = perSeatRate * seatCount;\n } else {\n const currencyEntry = currencyPrices?.[selectedCurrency];\n priceInSelectedCurrency = typeof currencyEntry === 'number' ? currencyEntry : plan.price;\n }\n\n // USD price for credit-balance calculations. plan.price/pricePerSeat are\n // only reliably USD when the plan's own base currency is USD — a plan\n // whose base currency is e.g. INR (an admin can pick any currency when\n // creating a plan) stores that INR amount directly in plan.price, not a\n // USD figure. currencyPrices.USD (present for every plan regardless of\n // base currency) is the one field that's always actually USD.\n const usdCurrencyEntry = currencyPrices?.USD;\n const usdFlatPrice = typeof usdCurrencyEntry === 'number' ? usdCurrencyEntry : plan.price;\n const usdPerSeatRate =\n typeof usdCurrencyEntry === 'object' && usdCurrencyEntry?.perSeat\n ? usdCurrencyEntry.perSeat\n : plan.pricePerSeat;\n\n const resolvedSeatCount = isPerSeat ? seatCount : 1;\n\n const item: CheckoutItem = {\n id: plan.id,\n type: 'PLAN',\n name: plan.name,\n description:\n typeof plan.context === 'object' && plan.context !== null\n ? (plan.context as { description?: string }).description\n : typeof plan.context === 'string'\n ? plan.context\n : undefined,\n price: priceInSelectedCurrency,\n priceUSD:\n isPerSeat && plan.pricePerSeat\n ? (usdPerSeatRate ?? plan.pricePerSeat) * resolvedSeatCount\n : usdFlatPrice,\n currency: selectedCurrency,\n quantity: resolvedSeatCount,\n features,\n metadata: {\n planId: plan.id,\n duration: plan.duration,\n seatCount: isPerSeat ? resolvedSeatCount : undefined,\n },\n };\n\n checkout.addItem(item);\n setItemAdded(true);\n } else if (purchaseType === 'ADDON' && !isCartCheckout && addonData?.getSelectedAddons?.[0]) {\n const addon = addonData.getSelectedAddons[0];\n\n // Extract features from addon. context.quantity is the amount this\n // addon actually grants per unit purchased — takes precedence over the\n // quota catalog's generic limits.value, same as the plan path above.\n const features =\n addon.features?.map(\n (f: {\n id: string;\n context?: Record<string, unknown> | null;\n quota?: { id: string; name: string; limits?: Record<string, unknown> };\n }) => {\n const limits = f.quota?.limits;\n const overrideQuantity =\n f.context && 'quantity' in f.context ? Number(f.context.quantity) : undefined;\n return {\n id: f.quota?.id || f.id,\n name: f.quota?.name || 'Feature',\n limits:\n overrideQuantity !== undefined && limits && 'type' in limits\n ? { ...limits, value: overrideQuantity }\n : limits,\n };\n }\n ) || [];\n\n // Use stored currency price if available, otherwise fallback to base USD price\n const selectedCurrency = checkout.selectedCurrency || 'USD';\n const currencyPrices = addon.currencyPrices as Record<string, number> | null;\n const priceInSelectedCurrency = currencyPrices?.[selectedCurrency] ?? addon.price;\n // addon.price/currency are always localized to the caller's currency\n // context (e.g. an INR request gets addon.price in INR, regardless of\n // the addon's stored base currency) — currencyPrices.USD is the one\n // field that's reliably actually USD, needed for credit calculations.\n const usdPrice = currencyPrices?.USD ?? addon.price;\n\n const item: CheckoutItem = {\n id: addon.id,\n type: 'ADDON',\n name: addon.name,\n description:\n typeof addon.context === 'object' && addon.context !== null\n ? (addon.context as { description?: string }).description\n : typeof addon.context === 'string'\n ? addon.context\n : undefined,\n price: priceInSelectedCurrency,\n priceUSD: usdPrice,\n currency: selectedCurrency,\n quantity: locationState?.quantity ?? 1,\n features,\n metadata: { addonId: addon.id },\n };\n checkout.addItem(item);\n setItemAdded(true);\n } else if (isStoreCheckout && storeOrderData?.getStoreOrder) {\n // Load store order items into checkout\n const storeOrder = storeOrderData.getStoreOrder;\n const items = buildStoreOrderCheckoutItems(storeOrder);\n\n for (const item of items) {\n checkout.addItem(item);\n }\n setItemAdded(true);\n } else if (purchaseType === 'CREDITS') {\n const selectedCurrency = checkout.selectedCurrency || 'USD';\n const item: CheckoutItem = {\n id: 'credits-purchase',\n type: 'CREDITS',\n name: 'Credits',\n description: 'Purchase credits for your account',\n price: costPerCredit, // Use actual cost per credit from backend (in selected currency)\n currency: selectedCurrency,\n quantity: 100, // Default amount\n };\n checkout.addItem(item);\n setItemAdded(true);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Using specific checkout properties is intentional for granular updates\n }, [\n purchaseType,\n planData,\n addonData,\n storeOrderData,\n checkout.addItem,\n checkout.clearItems,\n locationState?.quantity,\n itemAdded,\n isCartCheckout,\n isStoreCheckout,\n cartItems,\n cartHydrated,\n costPerCredit,\n checkout.selectedCurrency, // Re-add items when currency changes to use correct prices\n ]);\n\n // Update store-order item prices when a payment currency change requires conversion.\n // If the persisted order currency already matches the selected currency this is a no-op.\n useEffect(() => {\n // convertedAmount !== null signals a real rate has arrived for the current\n // currency pair — conversionRate's own magnitude can't be used for this\n // (a from-INR-to-USD rate is legitimately < 1, unlike the from-USD rates\n // this used to always fetch — see BOFF-7127).\n if (!isStoreCheckout || !itemAdded || checkout.convertedAmount === null) return;\n\n for (const item of checkout.items) {\n if (item.type === 'ORDER' && item.currency !== checkout.selectedCurrency) {\n const convertedPrice = item.price * checkout.conversionRate;\n checkout.updateItemPrice(item.id, convertedPrice, checkout.selectedCurrency);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- checkout.items intentionally excluded to avoid infinite loop\n }, [\n isStoreCheckout,\n itemAdded,\n checkout.convertedAmount,\n checkout.conversionRate,\n checkout.selectedCurrency,\n checkout.updateItemPrice,\n ]);\n\n // Fold the buyer's shipping cost into the billing total as a single synthetic\n // ORDER line item, upserted-by-id so it survives React strict-mode double\n // effects and is never an editable/removable line (OrderSummary disables qty\n // editing for ORDER purchases). Free shipping (cost 0) intentionally adds no\n // line.\n //\n // The line is ALWAYS written in the selected currency, pre-converting from the\n // order currency with the SAME formula the store-order conversion effect above\n // uses (price * conversionRate, only when order currency !== selected currency\n // and a rate is active). Because its currency always equals the selected\n // currency, that conversion effect skips it (`item.currency !== selectedCurrency`\n // is false) — so shipping is counted EXACTLY once and can never be double-applied.\n // This effect owns re-pricing on cost / currency / rate changes.\n //\n // This is the ONLY place shipping enters the billing charge; the backend's\n // StoreOrder.total bump is store-side reconciliation only and is never read\n // back here (no double count).\n const shippingLineRef = useRef<{ price: number; currency: string } | null>(null);\n useEffect(() => {\n if (!isPhysicalStoreOrder) return;\n\n if (shipping.shippingCost > 0) {\n const selectedCurrency = checkout.selectedCurrency;\n const needsConversion =\n storeOrderCurrency !== selectedCurrency && checkout.convertedAmount !== null;\n const price = needsConversion\n ? shipping.shippingCost * checkout.conversionRate\n : shipping.shippingCost;\n\n const prev = shippingLineRef.current;\n if (prev && prev.price === price && prev.currency === selectedCurrency) {\n return; // nothing changed\n }\n if (prev === null) {\n checkout.addItem({\n id: STORE_SHIPPING_ITEM_ID,\n type: 'ORDER',\n name: 'Shipping',\n price,\n currency: selectedCurrency,\n quantity: 1,\n metadata: { storeOrderId, isShipping: true },\n });\n } else {\n checkout.updateItemPrice(STORE_SHIPPING_ITEM_ID, price, selectedCurrency);\n }\n shippingLineRef.current = { price, currency: selectedCurrency };\n } else if (shippingLineRef.current !== null) {\n shippingLineRef.current = null;\n checkout.removeItem(STORE_SHIPPING_ITEM_ID);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- add/update decided via ref; checkout.items excluded to avoid an update loop\n }, [\n isPhysicalStoreOrder,\n shipping.shippingCost,\n storeOrderCurrency,\n checkout.selectedCurrency,\n checkout.conversionRate,\n checkout.convertedAmount,\n storeOrderId,\n checkout.addItem,\n checkout.updateItemPrice,\n checkout.removeItem,\n ]);\n\n // Update item price when backend credit cost changes (after item is already added)\n // This handles cases where the rate might differ based on quantity or currency\n useEffect(() => {\n if (purchaseType === 'CREDITS' && itemAdded && costPerCredit > 0) {\n const currentItem = checkout.items.find((i) => i.id === 'credits-purchase');\n // Only update if price actually changed\n if (currentItem && currentItem.price !== costPerCredit) {\n checkout.updateItemPrice('credits-purchase', costPerCredit);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Using specific checkout properties is intentional for granular updates\n }, [purchaseType, itemAdded, costPerCredit, checkout.items, checkout.updateItemPrice]);\n\n // Update item prices when currency changes (for plans and addons with fixed currencyPrices)\n // This handles mid-checkout currency changes by updating prices from stored currencyPrices\n useEffect(() => {\n if (!itemAdded || checkout.items.length === 0) return;\n\n const selectedCurrency = checkout.selectedCurrency || 'USD';\n\n for (const item of checkout.items) {\n if (item.type === 'PLAN' && planData?.getPlan) {\n const currencyPrices = planData.getPlan.currencyPrices as Record<string, number> | null;\n const priceInSelectedCurrency =\n currencyPrices?.[selectedCurrency] ?? planData.getPlan.price;\n // Update both price AND currency so conversion check works correctly\n if (item.price !== priceInSelectedCurrency || item.currency !== selectedCurrency) {\n checkout.updateItemPrice(item.id, priceInSelectedCurrency, selectedCurrency);\n }\n } else if (item.type === 'ADDON') {\n // For single addon checkout\n if (!isCartCheckout && addonData?.getSelectedAddons?.[0]) {\n const addon = addonData.getSelectedAddons[0];\n const currencyPrices = addon.currencyPrices as Record<string, number> | null;\n const priceInSelectedCurrency = currencyPrices?.[selectedCurrency] ?? addon.price;\n if (item.price !== priceInSelectedCurrency || item.currency !== selectedCurrency) {\n checkout.updateItemPrice(item.id, priceInSelectedCurrency, selectedCurrency);\n }\n }\n // For cart checkout, find the addon in cartItems\n if (isCartCheckout) {\n const cartItem = cartItems.find((ci) => ci.addon.id === item.id);\n if (cartItem) {\n const currencyPrices = (cartItem.addon as { currencyPrices?: Record<string, number> })\n .currencyPrices;\n const priceInSelectedCurrency =\n currencyPrices?.[selectedCurrency] ?? cartItem.addon.price;\n if (item.price !== priceInSelectedCurrency || item.currency !== selectedCurrency) {\n checkout.updateItemPrice(item.id, priceInSelectedCurrency, selectedCurrency);\n }\n }\n }\n }\n // CREDITS are handled by the credit cost effect above\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Using specific checkout properties is intentional for granular updates\n }, [\n checkout.selectedCurrency,\n itemAdded,\n checkout.items,\n checkout.updateItemPrice,\n planData,\n addonData,\n cartItems,\n isCartCheckout,\n ]);\n\n // Set default billing account\n // Priority: 1. Location state (for upgrade flows), 2. localStorage (user's last selection), 3. First account\n useEffect(() => {\n if (billingAccounts.length > 0 && !checkout.billingAccount) {\n // 1. If this is an upgrade, find and select the specific billing account from location state\n if (locationState?.billingAccountId) {\n const targetAccount = billingAccounts.find(\n (acc) => acc.id === locationState.billingAccountId\n );\n if (targetAccount) {\n checkout.setBillingAccount(targetAccount);\n return;\n }\n }\n\n // 2. Check localStorage for previously selected billing account (set from /overview page)\n if (orgId) {\n const storedAccountId = getStoredBillingAccountId(orgId);\n if (storedAccountId) {\n const storedAccount = billingAccounts.find((acc) => acc.id === storedAccountId);\n if (storedAccount) {\n checkout.setBillingAccount(storedAccount);\n return;\n }\n }\n }\n\n // 3. Default: select first account\n checkout.setBillingAccount(billingAccounts[0]);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Using specific checkout properties is intentional for granular updates\n }, [\n billingAccounts,\n checkout.billingAccount,\n checkout.setBillingAccount,\n locationState?.billingAccountId,\n orgId,\n ]);\n\n // Determine if billing account selector should be locked (for upgrade/downgrade flows)\n const isBillingAccountLocked = Boolean(\n locationState?.upgradeFromSubscriptionId || locationState?.downgradeFromSubscriptionId\n );\n\n // Redirect to addons page if cart is empty for cart checkout.\n // Navigation must happen after render to avoid React setState-in-render warnings.\n useEffect(() => {\n if (isCartCheckout && cartHydrated && cartItems.length === 0 && !itemAdded) {\n navigateTo('/addons', { replace: true });\n }\n }, [cartHydrated, cartItems.length, isCartCheckout, itemAdded, navigateTo]);\n\n const nativeCheckout = useNativeCheckout({\n billingAccountId: checkout.billingAccount?.id,\n purchaseType,\n productId: itemId,\n });\n\n const handleProceedToPayment = useCallback(async () => {\n if (!checkout.billingAccount) {\n return;\n }\n\n // Gate: require complete billing details before payment.\n // Skip once after the CompleteBillingAccountModal saves — Apollo cache still holds stale\n // contactNumber/billingAddresses because the mutation selection doesn't return those fields.\n const skipGate = bypassBillingGate.current;\n bypassBillingGate.current = false;\n if (!skipGate && !isBillingAccountComplete(checkout.billingAccount)) {\n setShowCompleteAccountModal(true);\n return;\n }\n\n setPaymentErrorMessage(null);\n\n // Physical store orders: persist the chosen shipping address + method on the\n // store order BEFORE billing creates its immutable order snapshot. The store\n // service recomputes and persists the shipping cost authoritatively; only IDs\n // are sent from this page. This whole block is skipped for non-physical paths.\n if (isPhysicalStoreOrder) {\n try {\n const shippingResult = await setStoreOrderShipping({\n variables: {\n input: {\n orderId: storeOrderId!,\n shippingMethodId: shipping.shippingMethodId!,\n ...(shipping.shippingAddressId\n ? { shippingAddressId: shipping.shippingAddressId }\n : {}),\n },\n },\n });\n const serverCost = shippingResult.data?.setStoreOrderShipping?.shippingCost;\n if (typeof serverCost === 'number' && serverCost !== shipping.shippingCost) {\n // Keep the display in sync if the authoritative quote differs from\n // the estimate shown before the user submitted the order.\n setShipping((prev) => ({ ...prev, shippingCost: serverCost }));\n }\n } catch {\n setPaymentErrorMessage(\n 'Could not save your shipping details. Please review your delivery address and try again.'\n );\n setMode('error');\n return;\n }\n }\n\n // Capture purchase details before payment (for success page)\n const previousBalance = checkout.billingAccount.creditAmount ?? 0;\n const purchasedItems = [...checkout.items];\n // checkout.total is in the selected DISPLAY currency (e.g. INR) — never\n // the right number for a credits purchase, which always charges\n // creditsRequired (subtotalUSD × CREDITS_PER_USD) regardless of display\n // currency. The spendCredits mutation itself doesn't return the amount\n // it charged, so this is recomputed the same way the backend charges it,\n // not read back from the response.\n const purchaseTotal = checkout.total;\n const creditsCharged = checkout.creditsRequired;\n\n // The checkout hook asks the store service to create the billing order from\n // the persisted catalog and shipping snapshots. No browser-calculated total\n // or line item is used to create the payable order.\n const response = await checkout.initiatePayment();\n\n if (response.success) {\n emit('billing.checkout.initiated', {\n route: '/billing/checkout',\n entityId: response.paymentId ?? response.orderId,\n source: 'checkout',\n amount: response.total || purchaseTotal,\n currency: response.currency || checkout.selectedCurrency,\n });\n // For credit payments, go to processing mode and poll for transaction status\n if (checkout.selectedProvider === 'CREDITS') {\n emit('billing.credits.spent', {\n route: '/billing/checkout',\n entityId: response.orderId,\n source: 'checkout',\n amount: creditsCharged,\n currency: 'CREDITS',\n });\n // Store purchase details for success page\n setPurchaseDetails({\n items: purchasedItems,\n total: creditsCharged,\n billingAccountName: checkout.billingAccount.name,\n billingAccountId: checkout.billingAccount.id,\n previousBalance,\n newBalance: previousBalance - creditsCharged,\n paymentMethod: 'credits',\n currency: 'Credits', // Credit payments use credits as currency\n transactionId: response.orderId, // This is creditTransactionID from spendCredits\n });\n\n // Set payment type for polling and start verification\n setPollingPaymentType('credits');\n setPaymentResponse({\n paymentId: response.orderId, // creditTransactionID from spendCredits\n });\n setMode('processing');\n return;\n }\n\n // For card payments, proceed to payment form\n // Store partial details (will be completed after payment confirmation)\n setPurchaseDetails({\n items: purchasedItems,\n total: response.total || purchaseTotal, // Use response total which includes tax\n billingAccountName: checkout.billingAccount.name,\n billingAccountId: checkout.billingAccount.id,\n previousBalance,\n newBalance: previousBalance, // Card payment doesn't affect credits\n paymentMethod: 'card',\n currency: response.currency || checkout.selectedCurrency, // Use actual currency from response\n transactionId: response.paymentId, // Backend payment ID\n });\n\n setPollingPaymentType('card');\n setPaymentResponse({\n clientSecret: response.clientSecret,\n orderId: response.orderId, // Gateway order ID (for Razorpay)\n razorpayKeyId: response.razorpayKeyId,\n razorpayCustomerId: response.razorpayCustomerId, // For Razorpay saved cards\n paymentId: response.paymentId, // Backend payment ID for polling\n paymentComplete: response.paymentComplete, // For saved card one-click payments\n prefillName: response.prefillName,\n prefillEmail: response.prefillEmail,\n prefillContact: response.prefillContact,\n total: response.total, // Total in target currency (from backend, e.g., INR amount)\n currency: response.currency, // Currency code from backend\n });\n\n // If payment completed immediately (saved card), go to processing for verification\n if (response.paymentComplete) {\n setMode('processing');\n } else {\n setMode('payment');\n }\n } else {\n emit('billing.checkout.failed', {\n route: '/billing/checkout',\n source: 'checkout',\n reason: response.error || 'Payment initiation failed',\n });\n setPaymentErrorMessage(response.error || 'Payment initiation failed');\n setMode('error');\n }\n }, [\n checkout,\n emit,\n isPhysicalStoreOrder,\n shipping,\n storeOrderId,\n storeOrderCurrency,\n setStoreOrderShipping,\n ]);\n\n const handlePaymentSuccess = useCallback(\n (_gatewayPaymentId: string) => {\n emit('billing.payment.succeeded', {\n route: '/billing/checkout',\n entityId: paymentResponse?.paymentId ?? paymentResponse?.orderId,\n source: 'checkout',\n });\n // Keep using the backend payment ID (orderId) that was already set\n // The _gatewayPaymentId is for reference only, not for our polling\n setPaymentResponse((prev) => {\n return prev; // Keep existing paymentId (orderId from initiatePayment)\n });\n setMode('processing');\n },\n [emit, paymentResponse?.orderId, paymentResponse?.paymentId]\n );\n\n const handlePaymentError = useCallback(\n (error: string) => {\n emit('billing.payment.failed', {\n route: '/billing/checkout',\n entityId: paymentResponse?.paymentId ?? paymentResponse?.orderId,\n source: 'checkout',\n reason: error,\n });\n setPaymentErrorMessage(error);\n setMode('error');\n },\n [emit, paymentResponse?.orderId, paymentResponse?.paymentId]\n );\n\n const handleCreditsAmountChange = useCallback(\n (amount: number) => {\n checkout.updateItemQuantity('credits-purchase', amount);\n },\n [checkout]\n );\n\n if (!permissions.canProcessPayment) {\n return <AccessDenied message=\"You don't have permission to make purchases.\" />;\n }\n\n if (isNativeIAPAvailable()) {\n return (\n <NativeCheckoutView\n purchaseType={purchaseType}\n productId={itemId}\n billingAccountId={checkout.billingAccount?.id}\n nativeCheckout={nativeCheckout}\n onBack={() => navigateTo('dashboard')}\n />\n );\n }\n\n const isLoading =\n accountsLoading ||\n (planLoading && purchaseType === 'PLAN') ||\n (addonLoading && purchaseType === 'ADDON' && !isCartCheckout) ||\n (isCartCheckout && !cartHydrated);\n\n if (isCartCheckout && cartHydrated && cartItems.length === 0 && !itemAdded) {\n return null;\n }\n\n // Get page title based on purchase type\n const getPageTitle = () => {\n if (isCartCheckout) {\n const addonCount = checkout.items.length;\n return `Purchase ${addonCount} Add-on${addonCount !== 1 ? 's' : ''}`;\n }\n switch (purchaseType) {\n case 'CREDITS':\n return 'Buy Credits';\n case 'PLAN':\n return 'Subscribe to Plan';\n case 'ADDON':\n return 'Purchase Add-on';\n default:\n return 'Checkout';\n }\n };\n\n const getPageIcon = () => {\n switch (purchaseType) {\n case 'CREDITS':\n return <Coins className=\"size-6\" />;\n case 'PLAN':\n return <Package className=\"size-6\" />;\n case 'ADDON':\n return <Blocks className=\"size-6\" />;\n default:\n return <ShoppingCart className=\"size-6\" />;\n }\n };\n\n // Render loading state\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <Loader2 className=\"size-8 animate-spin mx-auto text-text-link\" />\n <p className=\"text-text-secondary\">\n {tr('billing.checkout.loadingCheckout', 'Loading checkout...')}\n </p>\n </div>\n </div>\n );\n }\n\n // Render success state\n if (mode === 'success') {\n // Format amount based on payment method and currency\n const formatAmount = (amount: number) => {\n if (!purchaseDetails) return `${amount.toFixed(2)}`;\n\n const currency = purchaseDetails.currency;\n\n // For credits, show \"X.XX Credits\"\n if (currency === 'Credits') {\n return `${amount.toFixed(2)} Credits`;\n }\n\n // For real currencies, show with symbol\n const currencySymbols: Record<string, string> = {\n INR: '₹',\n USD: '$',\n EUR: '€',\n GBP: '£',\n };\n const symbol = currencySymbols[currency] || currency + ' ';\n return `${symbol}${amount.toFixed(2)}`;\n };\n\n // Keep formatCredits for credit balance section\n const formatCredits = (amount: number) => `${amount.toFixed(2)} Credits`;\n\n return (\n <>\n <div className=\"max-w-2xl mx-auto p-6 space-y-6\">\n {/* Success Header */}\n <div className=\"text-center space-y-4\">\n <div className=\"inline-flex p-4 rounded-full bg-status-success-bg-subtle text-status-success-text\">\n <CheckCircle className=\"size-12\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {tr('billing.checkout.paymentSuccessful', 'Payment Successful!')}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {tr(\n 'billing.checkout.purchaseCompleted',\n 'Your purchase has been completed successfully.'\n )}\n </p>\n </div>\n </div>\n\n {/* Purchase Details Card */}\n {purchaseDetails && (\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg overflow-hidden\">\n {/* Items Purchased */}\n <div className=\"p-4 border-b border-border-subtle\">\n <h3 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Receipt className=\"size-4\" />\n {tr('billing.checkout.itemsPurchased', 'Items Purchased')}\n </h3>\n <div className=\"space-y-2\">\n {purchaseDetails.items.map((item) => (\n <div key={item.id} className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n {item.type === 'PLAN' && <Package className=\"size-4 text-accent-blue\" />}\n {item.type === 'ADDON' && <Blocks className=\"size-4 text-accent-purple\" />}\n {item.type === 'CREDITS' && (\n <Coins className=\"size-4 text-status-warning-text\" />\n )}\n {item.type === 'ORDER' && (\n <ShoppingCart className=\"size-4 text-status-success-text\" />\n )}\n <span className=\"text-text-primary\">{item.name}</span>\n {item.quantity > 1 && (\n <span className=\"text-xs text-text-secondary\">x{item.quantity}</span>\n )}\n </div>\n <span className=\"text-text-secondary\">\n {formatAmount(\n purchaseDetails.paymentMethod === 'credits'\n ? (item.priceUSD ?? item.price) * CREDITS_PER_USD * item.quantity\n : item.price * item.quantity\n )}\n </span>\n </div>\n ))}\n </div>\n </div>\n\n {/* Payment Info */}\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken\">\n <div className=\"grid grid-cols-2 gap-4\">\n {/* Payment Method */}\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.paymentMethod', 'Payment Method')}\n </p>\n <div className=\"flex items-center gap-2\">\n {purchaseDetails.paymentMethod === 'credits' ? (\n <>\n <Wallet className=\"size-4 text-status-warning-text\" />\n <span className=\"font-medium text-text-primary\">\n {tr('billing.checkout.credits', 'Credits')}\n </span>\n </>\n ) : (\n <>\n <CreditCard className=\"size-4 text-accent-blue\" />\n <span className=\"font-medium text-text-primary\">\n {tr('billing.checkout.card', 'Card')}\n </span>\n </>\n )}\n </div>\n </div>\n {/* Total */}\n <div className=\"text-right\">\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.totalPaid', 'Total Paid')}\n </p>\n <p className=\"font-bold text-lg text-text-primary\">\n {formatAmount(purchaseDetails.total)}\n </p>\n </div>\n </div>\n </div>\n\n {/* Credit Balance (only for credit payments) */}\n {purchaseDetails.paymentMethod === 'credits' && (\n <div className=\"p-4 border-b border-border-subtle\">\n <h3 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Wallet className=\"size-4\" />\n {tr('billing.checkout.creditBalance', 'Credit Balance')}\n </h3>\n <div className=\"flex items-center gap-4\">\n <div className=\"flex-1 text-center p-3 rounded-lg bg-bg-sunken\">\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.previousBalance', 'Previous Balance')}\n </p>\n <p className=\"font-semibold text-text-primary\">\n {formatCredits(purchaseDetails.previousBalance)}\n </p>\n </div>\n <ArrowRight className=\"size-5 text-text-secondary shrink-0\" />\n <div className=\"flex-1 text-center p-3 rounded-lg bg-status-success-bg-subtle\">\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.newBalance', 'New Balance')}\n </p>\n <p className=\"font-semibold text-status-success-text\">\n {formatCredits(purchaseDetails.newBalance)}\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Billing Account Info */}\n <div className=\"p-4 bg-bg-sunken\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.billingAccount', 'Billing Account')}\n </p>\n <p className=\"font-medium text-text-primary\">\n {purchaseDetails.billingAccountName}\n </p>\n </div>\n {purchaseDetails.transactionId && (\n <div className=\"text-right\">\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.transactionId', 'Transaction ID')}\n </p>\n <p className=\"font-mono text-xs text-text-secondary\">\n {purchaseDetails.transactionId.slice(0, 8)}...\n </p>\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* Action Buttons */}\n <div className=\"flex flex-col sm:flex-row gap-3 justify-center pt-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/overview')}\n className=\"px-6 py-3 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bgHover transition-colors\"\n >\n {tr('billing.checkout.backToOverview', 'Back to Overview')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/subscriptions')}\n className=\"px-6 py-3 text-sm font-medium border border-border-subtle text-text-primary rounded-lg hover:bg-bg-sunken transition-colors\"\n >\n {tr('billing.checkout.viewSubscriptions', 'View Subscriptions')}\n </button>\n </div>\n </div>\n\n {/* Workspace Quota Assignment Modal */}\n {showAssignModal &&\n pendingWorkspaceQuotas.length > 0 &&\n workspaceId &&\n pendingSubscriptionId && (\n <WorkspaceQuotaAssignmentModal\n billingAccountId={purchaseDetails?.billingAccountId ?? ''}\n subscriptionId={pendingSubscriptionId}\n quotas={pendingWorkspaceQuotas}\n workspaces={workspaceOptions}\n currentWorkspaceId={workspaceId}\n onAssigned={() => setShowAssignModal(false)}\n onSkip={() => setShowAssignModal(false)}\n />\n )}\n </>\n );\n }\n\n // Render error state\n if (mode === 'error') {\n return (\n <div className=\"max-w-lg mx-auto p-6\">\n <div className=\"text-center space-y-6\">\n <div className=\"inline-flex p-4 rounded-full bg-status-error-bg-subtle text-status-error-text\">\n <XCircle className=\"size-12\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {tr('billing.checkout.paymentFailed', 'Payment Failed')}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {paymentErrorMessage ||\n pollingError ||\n checkout.error ||\n tr('billing.checkout.genericError', 'Something went wrong. Please try again.')}\n </p>\n </div>\n <div className=\"flex gap-3 justify-center\">\n <button\n type=\"button\"\n onClick={() => navigateTo(-1)}\n className=\"px-4 py-2 rounded-lg border border-border-subtle hover:bg-bg-sunken\"\n >\n {tr('billing.checkout.goBack', 'Go Back')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setMode('select');\n setPaymentErrorMessage(null);\n checkout.reset();\n }}\n className=\"px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover\"\n >\n {tr('billing.checkout.tryAgain', 'Try Again')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n // Render processing state (waiting for payment confirmation)\n if (mode === 'processing') {\n const formatTime = (ms: number) => {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;\n };\n\n return (\n <div className=\"max-w-lg mx-auto p-6\">\n <div className=\"text-center space-y-6\">\n <div className=\"inline-flex p-4 rounded-full bg-[var(--color-accent-soft)] text-text-link\">\n <Loader2 className=\"size-12 animate-spin\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {tr('billing.checkout.verifyingPayment', 'Verifying Payment')}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {tr(\n 'billing.checkout.verifyingPaymentDescription',\n 'Please wait while we confirm your payment...'\n )}\n </p>\n </div>\n\n {/* Elapsed time */}\n <div className=\"bg-bg-sunken rounded-lg p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.checkout.timeElapsed', 'Time elapsed:')}{' '}\n <span className=\"font-mono font-medium text-text-primary\">\n {formatTime(elapsedTime)}\n </span>\n </p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {tr(\n 'billing.checkout.keepPageOpen',\n \"This may take a few moments. Please don't close this page.\"\n )}\n </p>\n </div>\n\n {/* Progress indicator */}\n <div className=\"w-full bg-bg-sunken rounded-full h-2 overflow-hidden\">\n <div\n className=\"bg-action-primary-bg h-2 rounded-full transition-all duration-1000\"\n style={{ width: `${Math.min((elapsedTime / 300000) * 100, 100)}%` }}\n />\n </div>\n\n {/* Cancel button - only show after 30 seconds */}\n {elapsedTime > 30000 && (\n <button\n type=\"button\"\n onClick={() => {\n setMode('error');\n setPaymentErrorMessage(\n 'Payment verification was cancelled. Please check your billing history.'\n );\n }}\n className=\"text-sm text-text-secondary hover:text-text-primary underline\"\n >\n {tr('billing.checkout.cancelAndCheckHistory', 'Cancel and check billing history')}\n </button>\n )}\n </div>\n </div>\n );\n }\n\n // Render payment form\n if (mode === 'payment' && paymentResponse) {\n // Format currency for display\n const formatDisplayCurrency = (amount: number, currencyCode: string) => {\n const symbols: Record<string, string> = { INR: '₹', USD: '$', EUR: '€', GBP: '£' };\n const symbol = symbols[currencyCode] || currencyCode + ' ';\n return `${symbol}${amount.toFixed(2)}`;\n };\n\n const displayCurrency = paymentResponse.currency || checkout.selectedCurrency;\n const displayTotal = paymentResponse.total ?? checkout.total;\n\n return (\n <div className=\"max-w-2xl mx-auto p-4 sm:p-6 space-y-6\">\n <button\n type=\"button\"\n onClick={() => setMode('select')}\n className=\"flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('billing.checkout.backToCheckout', 'Back to checkout')}\n </button>\n\n <PageHeader\n title={tr('billing.checkout.completePayment', 'Complete Payment')}\n description={tr(\n 'billing.checkout.completePaymentDescription',\n 'Enter your payment details to complete the purchase'\n )}\n />\n\n {/* Payment Summary with correct tax calculation */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 space-y-4\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Receipt className=\"size-4\" />\n {tr('billing.checkout.paymentSummary', 'Payment Summary')}\n </h3>\n\n {/* Billing Account Info */}\n {checkout.billingAccount && (\n <div className=\"p-3 bg-bg-sunken rounded-lg space-y-1\">\n <div className=\"text-xs text-text-secondary uppercase tracking-wide\">\n {tr('billing.checkout.billingAccount', 'Billing Account')}\n </div>\n <div className=\"font-medium text-text-primary\">{checkout.billingAccount.name}</div>\n <div className=\"text-sm text-text-secondary\">{checkout.billingAccount.email}</div>\n </div>\n )}\n\n {/* Subscription Info (for addon purchases) */}\n {purchaseType === 'ADDON' && checkout.selectedSubscriptionId && selectedSubscription && (\n <div className=\"p-3 bg-bg-sunken rounded-lg space-y-1\">\n <div className=\"text-xs text-text-secondary uppercase tracking-wide\">\n {tr('billing.checkout.addingToSubscription', 'Adding to Subscription')}\n </div>\n <div className=\"font-medium text-text-primary\">\n {selectedSubscription.plan?.name ||\n tr('billing.checkout.subscription', 'Subscription')}\n </div>\n {selectedSubscription.plan?.product?.name && (\n <div className=\"text-sm text-text-secondary\">\n {selectedSubscription.plan.product.name}\n </div>\n )}\n </div>\n )}\n\n {/* Price Breakdown */}\n <div className=\"space-y-2 text-sm\">\n {/* Calculate display values correctly */}\n {(() => {\n const taxRate = checkout.taxRate;\n // For non-USD: calculate subtotal and tax from the total\n const calcSubtotal = taxRate > 0 ? displayTotal / (1 + taxRate) : displayTotal;\n const calcTax = taxRate > 0 ? displayTotal - calcSubtotal : 0;\n\n return (\n <>\n {/* Subtotal */}\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">\n {tr('billing.checkout.subtotal', 'Subtotal')}\n </span>\n <span className=\"text-text-primary\">\n {formatDisplayCurrency(calcSubtotal, displayCurrency)}\n </span>\n </div>\n {/* Discount */}\n {checkout.discountAmount > 0 && (\n <div className=\"flex justify-between text-status-success-text\">\n <span>{tr('billing.checkout.discount', 'Discount')}</span>\n <span>\n -{formatDisplayCurrency(checkout.discountAmount, displayCurrency)}\n </span>\n </div>\n )}\n {/* Tax */}\n {calcTax > 0 && (\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">\n {checkout.taxType || tr('billing.checkout.tax', 'Tax')} (\n {(taxRate * 100).toFixed(0)}%)\n </span>\n <span className=\"text-text-primary\">\n {formatDisplayCurrency(calcTax, displayCurrency)}\n </span>\n </div>\n )}\n </>\n );\n })()}\n {/* Divider */}\n <div className=\"border-t border-border-subtle my-2\" />\n {/* Total */}\n <div className=\"flex justify-between font-semibold\">\n <span className=\"text-text-primary\">{tr('billing.checkout.total', 'Total')}</span>\n <span className=\"text-lg text-text-primary\">\n {formatDisplayCurrency(displayTotal, displayCurrency)}\n </span>\n </div>\n </div>\n </div>\n\n {checkout.selectedProvider === 'STRIPE' &&\n paymentResponse.clientSecret &&\n (() => {\n // NOTE: Saved cards for Stripe are temporarily disabled on the backend\n // NOTE: Card saving disabled — re-enable when backend supports setup_future_usage on PaymentIntents\n // const stripeCards = savedPaymentMethods.filter(pm => pm.stripePaymentMethodId && pm.isVerified);\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface overflow-hidden\">\n {/* Stripe Elements form for new card */}\n <div className=\"p-4 space-y-4\">\n {/* Save card checkbox - disabled until backend supports it */}\n {/* <label className=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={checkout.saveCard}\n onChange={(e) => checkout.setSaveCard(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <span className=\"text-sm text-text-secondary\">\n Save card for future purchases\n </span>\n </label> */}\n\n <StripePaymentForm\n clientSecret={paymentResponse.clientSecret}\n publishableKey={\n stripePublishableKey || providerConfig?.stripePublishableKey || ''\n }\n amount={Math.round((paymentResponse.total ?? checkout.total) * 100)}\n currency={paymentResponse.currency || checkout.selectedCurrency}\n onSuccess={handlePaymentSuccess}\n onError={handlePaymentError}\n onCancel={() => setMode('select')}\n isCompletingExistingPayment={!!checkout.selectedPaymentMethodId}\n />\n </div>\n </div>\n );\n })()}\n\n {checkout.selectedProvider === 'RAZORPAY' && paymentResponse.orderId && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface overflow-hidden\">\n {/* Razorpay Payment Section */}\n <div className=\"p-4 space-y-4\">\n {/* Save card checkbox for new card payments */}\n <label className=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={checkout.saveCard}\n onChange={(e) => checkout.setSaveCard(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <span className=\"text-sm text-text-secondary\">Save card for future purchases</span>\n </label>\n\n <RazorpayPaymentButton\n orderId={paymentResponse.orderId}\n keyId={paymentResponse.razorpayKeyId || razorpayKey || ''}\n amount={Math.round((paymentResponse.total ?? checkout.total) * 100)}\n currency={paymentResponse.currency || 'INR'}\n customerId={paymentResponse.razorpayCustomerId}\n prefill={{\n name: paymentResponse.prefillName,\n email: paymentResponse.prefillEmail,\n contact: paymentResponse.prefillContact,\n }}\n onSuccess={(response) => handlePaymentSuccess(response.razorpay_payment_id)}\n onError={handlePaymentError}\n onCancel={() => setMode('select')}\n />\n </div>\n </div>\n )}\n </div>\n );\n }\n\n // Main checkout flow\n return (\n <div className=\"max-w-6xl mx-auto p-4 sm:p-6\">\n {/* Back button */}\n <button\n type=\"button\"\n onClick={() => navigateTo(-1)}\n className=\"flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary mb-4\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n\n {/* Header */}\n <div className=\"flex items-center gap-3 mb-6\">\n <div className=\"p-2 rounded-lg bg-[var(--color-accent-soft)] text-text-link\">\n {getPageIcon()}\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">{getPageTitle()}</h1>\n <p className=\"text-sm text-text-secondary\">Complete your purchase securely</p>\n </div>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n 'billing.checkout.purpose',\n 'Review what you are buying — plan, add-ons or credits — pick the billing account and payment method, apply any discount code, and pay. Taxes and the final total are shown before you confirm.'\n )}\n </PagePurpose>\n\n {/* Upgrade Banner */}\n {locationState?.upgradeFromSubscriptionId && locationState?.currentPlanName && (\n <div className=\"mb-6 p-4 rounded-lg border border-border-subtle bg-status-info-bg-subtle\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-full bg-status-info-bg-subtle\">\n <RefreshCw className=\"size-4 text-accent-blue\" />\n </div>\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-info-text\">\n Upgrading from:{' '}\n <span className=\"font-semibold\">{locationState.currentPlanName}</span>\n </p>\n <p className=\"text-xs text-status-info-text mt-0.5\">\n Your current subscription will be marked as upgraded and quotas will transfer to the\n new plan.\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Downgrade Banner */}\n {locationState?.downgradeFromSubscriptionId && locationState?.currentPlanName && (\n <div className=\"mb-6 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg-subtle\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-full bg-status-warning-bg-subtle\">\n <RefreshCw className=\"size-4 text-status-warning-text\" />\n </div>\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-warning-text\">\n Downgrading from:{' '}\n <span className=\"font-semibold\">{locationState.currentPlanName}</span>\n </p>\n <p className=\"text-xs text-status-warning-text mt-0.5\">\n Your current plan stays active until it expires, you keep all its features until\n then. After expiry, only the new plan applies.\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Main content - responsive grid. On mobile the order summary appears first\n so users see the total before filling in payment details. */}\n <div className=\"grid gap-6 lg:grid-cols-5\">\n {/* Left column - Configuration (renders second on mobile) */}\n <div className=\"lg:col-span-3 space-y-6 order-2 lg:order-1\">\n {/* Credits amount (for credits purchase) */}\n {purchaseType === 'CREDITS' && checkout.items[0] && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <CreditsAmountInput\n value={checkout.items[0].quantity}\n onChange={handleCreditsAmountChange}\n costPerCredit={costPerCredit}\n currency={checkout.selectedCurrency}\n disabled={creditCostLoading}\n />\n </div>\n )}\n\n {/* Billing Account Selection */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6 space-y-4\">\n <BillingAccountSelector\n accounts={billingAccounts}\n selectedAccountId={checkout.billingAccount?.id ?? null}\n onSelect={checkout.setBillingAccount}\n isLoading={accountsLoading}\n disabled={isBillingAccountLocked}\n disabledMessage={\n isBillingAccountLocked ? 'Billing account is locked for plan changes' : undefined\n }\n />\n\n {/* Subscription Selection - inline for addon purchases (only shows if multiple subs or no subs) */}\n {/* Skip subscription selection for store checkout - store items don't require an active subscription */}\n {purchaseType === 'ADDON' && !isStoreCheckout && checkout.billingAccount && (\n <SubscriptionSelector\n subscriptions={activeSubscriptions}\n selectedSubscriptionId={checkout.selectedSubscriptionId}\n onSelect={checkout.setSelectedSubscriptionId}\n isLoading={subscriptionsLoading}\n />\n )}\n </div>\n\n {/* Shipping — physical store orders only. Non-physical flows render\n nothing new here. */}\n {isPhysicalStoreOrder && (\n <StoreShippingSection\n storeOrderId={storeOrderId!}\n currency={checkout.selectedCurrency}\n onChange={setShipping}\n />\n )}\n\n {/* Unified Payment Method Selection */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6 space-y-4\">\n {/* canPayWithCredits below gates whether credit payment applies to\n this purchase at all (e.g. not already buying credits) —\n whether the balance is *sufficient* is the child's own\n concern: it already computes hasEnoughCredits internally and\n shows a disabled option with an \"Insufficient credits\"\n message rather than hiding the option outright. */}\n <UnifiedPaymentMethodSelector\n savedPaymentMethods={savedPaymentMethods}\n selectedPaymentMethodId={checkout.selectedPaymentMethodId}\n onSelectSavedCard={checkout.setSelectedPaymentMethodId}\n selectedProvider={checkout.selectedProvider}\n onSelectProvider={checkout.setPaymentProvider}\n stripeEnabled={stripeEnabled}\n razorpayEnabled={razorpayEnabled}\n canPayWithCredits={checkout.canPayWithCredits}\n availableCredits={checkout.availableCredits}\n totalAmount={checkout.subtotalUSD}\n billingCountry={billingCountry}\n saveCard={checkout.saveCard}\n onSaveCardChange={checkout.setSaveCard}\n autoRenewEnabled={purchaseType === 'PLAN' && checkout.autoRenew}\n isLoading={paymentMethodsLoading}\n />\n\n {/* Currency Info - Locked to Billing Account */}\n {checkout.selectedProvider !== 'CREDITS' && checkout.selectedProvider !== 'AUTO' && (\n <>\n <div className=\"border-t border-border-subtle\" />\n <div className=\"bg-bg-sunken p-4 rounded-button\">\n <div className=\"flex items-start gap-3\">\n <svg\n className=\"size-5 text-text-secondary mt-0.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <div className=\"flex-1 text-sm\">\n <p className=\"font-medium text-text-primary mb-1\">\n Currency: {checkout.selectedCurrency}\n </p>\n <p className=\"text-text-secondary text-xs\">\n All prices are shown in{' '}\n <strong className=\"text-text-primary\">{checkout.selectedCurrency}</strong>{' '}\n based on your billing account settings.\n {checkout.selectedCurrency === 'INR' && ' GST will be applied at checkout.'}\n </p>\n </div>\n </div>\n </div>\n </>\n )}\n\n {/* Seat count selector — only for PER_SEAT plans */}\n {purchaseType === 'PLAN' && planData?.getPlan?.pricingModel === 'PER_SEAT' && (\n <div className=\"border-t border-border-subtle pt-4\">\n <div className=\"space-y-3\">\n <div>\n <label className=\"text-sm font-medium text-text-primary\">Number of seats</label>\n {planData.getPlan.minSeats && (\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Minimum {planData.getPlan.minSeats} seat\n {planData.getPlan.minSeats !== 1 ? 's' : ''} required\n </p>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() =>\n setSeatCount((n) => Math.max(planData.getPlan?.minSeats ?? 1, n - 1))\n }\n disabled={seatCount <= (planData.getPlan.minSeats ?? 1)}\n className=\"size-9 rounded-button border border-border-subtle flex items-center justify-center hover:bg-action-ghost-bgHover disabled:opacity-40 disabled:cursor-not-allowed transition-colors text-text-primary\"\n >\n −\n </button>\n <input\n type=\"number\"\n value={seatCount}\n min={planData.getPlan.minSeats ?? 1}\n max={planData.getPlan.maxSeats ?? 999}\n onChange={(e) => {\n const v = parseInt(e.target.value, 10);\n if (!isNaN(v)) setSeatCount(Math.max(planData.getPlan?.minSeats ?? 1, v));\n }}\n className=\"w-20 h-9 text-center border border-border-subtle rounded-button bg-bg-surface text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <button\n type=\"button\"\n onClick={() =>\n setSeatCount((n) => Math.min(planData.getPlan?.maxSeats ?? 999, n + 1))\n }\n disabled={seatCount >= (planData.getPlan.maxSeats ?? 999)}\n className=\"size-9 rounded-button border border-border-subtle flex items-center justify-center hover:bg-action-ghost-bgHover disabled:opacity-40 disabled:cursor-not-allowed transition-colors text-text-primary\"\n >\n +\n </button>\n {planData.getPlan.pricePerSeat && (\n <span className=\"text-sm text-text-secondary\">\n ×{' '}\n {planData.getPlan.pricePerSeat.toLocaleString('en', {\n style: 'currency',\n currency: planData.getPlan.currency ?? 'USD',\n })}{' '}\n ={' '}\n {(planData.getPlan.pricePerSeat * seatCount).toLocaleString('en', {\n style: 'currency',\n currency: planData.getPlan.currency ?? 'USD',\n })}\n /mo\n </span>\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* Right column - Order Summary (sticky on desktop, shown first on mobile) */}\n <div className=\"lg:col-span-2 order-1 lg:order-2\">\n <div className=\"lg:sticky lg:top-6 space-y-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"p-4\">\n <OrderSummary\n items={checkout.items}\n subtotal={checkout.subtotal}\n discountAmount={checkout.discountAmount}\n taxAmount={checkout.taxAmount}\n taxRate={checkout.taxRate}\n taxType={checkout.taxType}\n total={checkout.total}\n currency={checkout.selectedCurrency}\n isCalculatingTax={checkout.isCalculatingTax}\n showInCredits={checkout.selectedProvider === 'CREDITS'}\n onUpdateQuantity={\n purchaseType !== 'PLAN' && purchaseType !== 'ORDER'\n ? checkout.updateItemQuantity\n : undefined\n }\n onRemoveItem={checkout.removeItem}\n isEditable={purchaseType !== 'PLAN' && purchaseType !== 'ORDER'}\n />\n </div>\n\n {/* Promo Code Input */}\n <div className=\"px-4 pb-4 border-t border-border-subtle pt-4\">\n <PromoCodeInput\n value={checkout.promoCode}\n onChange={checkout.setPromoCode}\n onValidate={async () => {\n const result = await checkout.validatePromoCode();\n return result;\n }}\n onClear={checkout.clearPromoCode}\n isApplied={checkout.promoDiscount > 0}\n discountValue={checkout.promoDiscount}\n discountType={checkout.promoDiscountType}\n isValidating={checkout.isProcessing}\n />\n </div>\n\n {/* Simple Auto-Pay Toggle - only for PLAN purchases */}\n {purchaseType === 'PLAN' &&\n checkout.selectedProvider !== 'CREDITS' &&\n checkout.selectedProvider !== 'AUTO' &&\n (() => {\n // Determine payment method display\n let paymentMethodDisplay = '';\n let hasPaymentMethod = false;\n let isNewCard = false;\n\n if (checkout.selectedPaymentMethodId) {\n // Saved card selected\n const selectedCard = savedPaymentMethods.find(\n (pm) => pm.id === checkout.selectedPaymentMethodId\n );\n if (selectedCard) {\n const type =\n selectedCard.type === 'CREDIT_CARD'\n ? 'Card'\n : selectedCard.type === 'DEBIT_CARD'\n ? 'Card'\n : selectedCard.type;\n const last4 = selectedCard.last4 ? ` •••• ${selectedCard.last4}` : '';\n paymentMethodDisplay = `${type}${last4}`;\n hasPaymentMethod = true;\n isNewCard = false;\n }\n } else if (\n checkout.selectedProvider === 'RAZORPAY' ||\n checkout.selectedProvider === 'STRIPE'\n ) {\n // New card selected\n paymentMethodDisplay =\n checkout.selectedProvider === 'RAZORPAY'\n ? 'new card (Razorpay)'\n : 'new card (Stripe)';\n hasPaymentMethod = true;\n isNewCard = true;\n }\n\n return (\n <div className=\"px-4 pb-4\">\n <SimpleAutoPayToggle\n enabled={checkout.autoRenew}\n onToggle={(enabled) => {\n checkout.setAutoRenew(enabled);\n // When enabling auto-pay with saved card, also set it as auto-renew method\n if (enabled && checkout.selectedPaymentMethodId) {\n checkout.setAutoRenewPaymentMethodId(checkout.selectedPaymentMethodId);\n }\n }}\n hasPaymentMethodSelected={hasPaymentMethod}\n selectedPaymentMethodDisplay={paymentMethodDisplay}\n isNewCard={isNewCard}\n />\n </div>\n );\n })()}\n </div>\n\n {/* Checkout Button */}\n <button\n type=\"button\"\n onClick={handleProceedToPayment}\n disabled={\n checkout.isProcessing ||\n !checkout.billingAccount ||\n checkout.items.length === 0 ||\n // For addon purchases: require active subscription to be selected (skip for store checkout)\n (purchaseType === 'ADDON' && !isStoreCheckout && subscriptionsLoading) ||\n (purchaseType === 'ADDON' &&\n !isStoreCheckout &&\n activeSubscriptions.length === 0) ||\n (purchaseType === 'ADDON' &&\n !isStoreCheckout &&\n !checkout.selectedSubscriptionId) ||\n // Physical store orders: require a delivery address + method\n // (with a resolved estimate) before payment.\n (isPhysicalStoreOrder &&\n (!shipping.shippingAddressId || !shipping.shippingMethodId))\n }\n className=\"w-full px-6 py-4 rounded-lg bg-action-primary-bg text-action-primary-text font-semibold\n hover:bg-action-primary-bgHover disabled:opacity-50 disabled:cursor-not-allowed\n transition-colors flex items-center justify-center gap-2\"\n >\n {checkout.isProcessing ? (\n <>\n <Loader2 className=\"size-5 animate-spin\" />\n Processing…\n </>\n ) : checkout.selectedProvider === 'CREDITS' ? (\n 'Pay with Credits'\n ) : (\n 'Proceed to Payment'\n )}\n </button>\n\n {/* Security badge */}\n <p className=\"text-center text-xs text-text-secondary\">\n 🔒 Secure checkout with 256-bit SSL encryption\n </p>\n </div>\n </div>\n </div>\n\n {/* Billing account completion gate */}\n {showCompleteAccountModal && checkout.billingAccount && (\n <CompleteBillingAccountModal\n billingAccountId={checkout.billingAccount.id}\n hasPhone={Boolean(checkout.billingAccount.contactNumber?.trim())}\n hasAddress={Boolean(\n checkout.billingAccount.billingAddresses?.[0]?.line1?.trim() &&\n checkout.billingAccount.billingAddresses?.[0]?.postalCode?.trim()\n )}\n onCompleted={() => {\n // Set bypass BEFORE closing modal so the re-triggered payment skips the stale check\n bypassBillingGate.current = true;\n setShowCompleteAccountModal(false);\n void handleProceedToPayment();\n // Refetch accounts in background so future gate checks use fresh contactNumber/address\n void refetchAccounts().then((result: { data?: GetBillingAccountsCheckoutQuery }) => {\n const freshAccount = (result.data?.myBillingAccounts ?? [])\n .map(normalizeCheckoutBillingAccount)\n .find((account) => account?.id === checkout.billingAccount?.id);\n if (freshAccount) checkout.setBillingAccount(freshAccount);\n });\n }}\n onCancel={() => setShowCompleteAccountModal(false)}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"CheckoutPage.js","names":[],"sources":["../../../../../src/billing/modules/checkout/pages/CheckoutPage.tsx"],"sourcesContent":["/**\n * Checkout Page Component\n * Main checkout flow for purchasing credits, plans, and add-ons\n * Supports single item checkout and multi-addon cart checkout\n */\n\nimport { type FC, useState, useEffect, useCallback, useMemo, useRef } from 'react';\nimport { useParams, useLocation } from 'react-router';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useBillingEventEmitter } from '../../../hooks/useBillingEventEmitter';\nimport {\n ArrowLeft,\n ShoppingCart,\n Loader2,\n CheckCircle,\n XCircle,\n Coins,\n Package,\n Blocks,\n Wallet,\n CreditCard,\n ArrowRight,\n Receipt,\n RefreshCw,\n} from 'lucide-react';\n\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { AccessDenied, PageHeader } from '../../../shared/components';\nimport { PagePurpose } from '@burdenoff/fe-libs/ui';\nimport { useAddonCartStore } from '../../addons/store/addonCartStore';\n\nimport {\n CREDITS_PER_USD,\n useCheckout,\n useNativeCheckout,\n usePaymentProviders,\n usePaymentStatusPolling,\n} from '../hooks';\nimport { isNativeIAPAvailable } from '../../../lib/native-purchases';\nimport { NativeCheckoutView } from '../components/NativeCheckoutView';\nimport { getStoredBillingAccountId } from '../../../hooks/useBillingAccountSelection';\nimport {\n useGetBillingAccountsCheckoutQuery,\n useGetPlanCheckoutQuery,\n useGetSelectedAddonCheckoutQuery,\n useGetPaymentMethodsCheckoutQuery,\n useGetActiveSubscriptionsCheckoutQuery,\n useGetCostOfCreditsCheckoutQuery,\n useGetStoreOrderCheckoutQuery,\n type GetActiveSubscriptionsCheckoutQuery,\n type GetBillingAccountsCheckoutQuery,\n} from '../../../../generated/global-operations';\nimport {\n BillingAccountSelector,\n OrderSummary,\n CreditsAmountInput,\n StripePaymentForm,\n RazorpayPaymentButton,\n SubscriptionSelector,\n SimpleAutoPayToggle,\n UnifiedPaymentMethodSelector,\n PromoCodeInput,\n CompleteBillingAccountModal,\n isBillingAccountComplete,\n StoreShippingSection,\n type StoreShippingSelection,\n} from '../components';\nimport {\n WorkspaceQuotaAssignmentModal,\n isWorkspaceScopedQuota,\n type WorkspaceQuotaToAssign,\n type WorkspaceOption,\n} from '../components/WorkspaceQuotaAssignmentModal';\nimport {\n buildStoreOrderCheckoutItems,\n normalizeStoreOrderCurrency,\n type StoreOrderLineItem,\n} from '../utils/storeOrder';\nimport { useSetStoreOrderShipping, STORE_SHIPPING_ITEM_ID } from '../hooks/useStoreShipping';\nimport type { SavedPaymentMethod } from '../types';\n\nimport type { CheckoutItem, CheckoutBillingAccount, PurchaseType } from '../types';\n\ntype RawCheckoutBillingAccount = GetBillingAccountsCheckoutQuery['myBillingAccounts'][number];\n\nfunction normalizeCheckoutBillingAccount(\n account: RawCheckoutBillingAccount\n): CheckoutBillingAccount | null {\n if (\n typeof account.id !== 'string' ||\n typeof account.name !== 'string' ||\n typeof account.email !== 'string' ||\n typeof account.currency !== 'string' ||\n typeof account.creditAmount !== 'number' ||\n typeof account.isActive !== 'boolean'\n ) {\n return null;\n }\n\n return {\n ...account,\n id: account.id,\n name: account.name,\n email: account.email,\n currency: account.currency,\n creditAmount: account.creditAmount,\n isActive: account.isActive,\n billingAddresses:\n account.billingAddresses === null\n ? null\n : account.billingAddresses?.flatMap((address) => (address ? [{ ...address }] : [])),\n };\n}\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useAppShell } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { getProfileScopedKey } from '@burdenoff/fe-libs/shared/utils';\nimport { recordAffiliateEvent } from '../../affiliate/api';\n\ntype CheckoutMode = 'select' | 'payment' | 'processing' | 'success' | 'error';\n\ninterface LocationState {\n purchaseType?: PurchaseType;\n itemId?: string;\n quantity?: number;\n upgradeFromSubscriptionId?: string;\n downgradeFromSubscriptionId?: string;\n currentPlanName?: string;\n billingAccountId?: string;\n}\n\nexport const CheckoutPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { planId, addonId, storeOrderId } = useParams<{\n planId?: string;\n addonId?: string;\n storeOrderId?: string;\n }>();\n const navigateTo = useBillingNavigate();\n const location = useLocation();\n const locationState = location.state as LocationState | undefined;\n\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const {\n orgId,\n stripePublishableKey,\n razorpayKey,\n apiGatewayUrl,\n authToken,\n currentUser,\n workspaceId,\n } = useBilling();\n const { productSlug } = useAppShell();\n const referralEventReportedRef = useRef<string | null>(null);\n // Workspace options for the assignment modal — single workspace for now.\n // Multi-workspace support requires a listWorkspaces query (future enhancement).\n const workspaceOptions: WorkspaceOption[] = workspaceId\n ? [{ id: workspaceId, name: 'Current workspace' }]\n : [];\n\n // Addon cart store for multi-addon checkout\n const { items: cartItems, clearCart, _hasHydrated: cartHydrated } = useAddonCartStore();\n\n // Check if this is a cart-based checkout (route: /checkout/addons)\n const isCartCheckout = location.pathname.includes('/checkout/addons') && !addonId;\n\n // Check if this is a store order checkout (route: /checkout/store/:storeOrderId)\n const isStoreCheckout = location.pathname.includes('/checkout/store') && !!storeOrderId;\n\n // Determine what we're purchasing\n const purchaseType: PurchaseType = isCartCheckout\n ? 'ADDON'\n : isStoreCheckout\n ? 'ORDER' // Store orders use ORDER purchase type\n : (locationState?.purchaseType ?? (planId ? 'PLAN' : addonId ? 'ADDON' : 'CREDITS'));\n const itemId = locationState?.itemId ?? planId ?? addonId ?? storeOrderId;\n\n // Billing account completion gate\n const [showCompleteAccountModal, setShowCompleteAccountModal] = useState(false);\n // Bypass the isBillingAccountComplete gate for the call that follows modal completion.\n // The gate uses stale Apollo cache (contactNumber/billingAddresses are not returned by\n // updateBillingAccount mutation), so we skip it once immediately after the modal saves.\n const bypassBillingGate = useRef(false);\n\n // Workspace quota assignment modal state\n const [showAssignModal, setShowAssignModal] = useState(false);\n const [pendingWorkspaceQuotas, setPendingWorkspaceQuotas] = useState<WorkspaceQuotaToAssign[]>(\n []\n );\n const [pendingSubscriptionId, setPendingSubscriptionId] = useState<string | null>(null);\n\n // Checkout state\n const [mode, setMode] = useState<CheckoutMode>('select');\n const [paymentResponse, setPaymentResponse] = useState<{\n clientSecret?: string;\n orderId?: string;\n razorpayKeyId?: string;\n razorpayCustomerId?: string;\n paymentId?: string;\n paymentComplete?: boolean;\n prefillName?: string;\n prefillEmail?: string;\n prefillContact?: string;\n total?: number; // Total in target currency from backend (e.g., INR)\n currency?: string;\n } | null>(null);\n const [paymentErrorMessage, setPaymentErrorMessage] = useState<string | null>(null);\n\n // Store purchase details for success page (captured before clearing)\n const [purchaseDetails, setPurchaseDetails] = useState<{\n items: typeof checkout.items;\n total: number;\n billingAccountName: string;\n billingAccountId: string;\n previousBalance: number;\n newBalance: number;\n paymentMethod: 'credits' | 'card';\n currency: string;\n transactionId?: string;\n } | null>(null);\n\n // Track payment type for polling (card vs credits)\n const [pollingPaymentType, setPollingPaymentType] = useState<'card' | 'credits'>('card');\n\n useEffect(() => {\n if (mode !== 'success' || !purchaseDetails) {\n return;\n }\n\n const rawAttribution =\n typeof window !== 'undefined'\n ? window.localStorage.getItem(getProfileScopedKey('boff-referral-attribution'))\n : null;\n if (!rawAttribution) {\n return;\n }\n\n let attribution: { journeyId?: string; codeId?: string };\n try {\n attribution = JSON.parse(rawAttribution) as { journeyId?: string; codeId?: string };\n } catch {\n return;\n }\n if (!attribution.journeyId && !attribution.codeId) {\n return;\n }\n\n const uniqueReference =\n purchaseDetails.transactionId ||\n paymentResponse?.paymentId ||\n paymentResponse?.orderId ||\n storeOrderId ||\n itemId ||\n purchaseType;\n\n const signature = `${attribution.journeyId ?? 'journey'}:${uniqueReference}`;\n if (referralEventReportedRef.current === signature) {\n return;\n }\n referralEventReportedRef.current = signature;\n\n void recordAffiliateEvent(\n {\n journeyId: attribution.journeyId,\n codeId: attribution.codeId,\n eventType: purchaseType === 'ORDER' ? 'STORE_PURCHASE' : 'PAID_CONVERSION',\n productSlug,\n actorUserId: currentUser?.id,\n orderId:\n purchaseDetails.transactionId || paymentResponse?.paymentId || paymentResponse?.orderId,\n billingAccountId: purchaseDetails.billingAccountId,\n storeOrderId: purchaseType === 'ORDER' ? storeOrderId : undefined,\n valueAmount: purchaseDetails.total,\n currency: purchaseDetails.currency,\n mode: 'billing-checkout',\n metadata: {\n purchaseType,\n billingAccountName: purchaseDetails.billingAccountName,\n },\n },\n { apiGatewayUrl, authToken, orgId }\n ).catch(() => {\n referralEventReportedRef.current = null;\n });\n }, [\n apiGatewayUrl,\n authToken,\n currentUser?.id,\n itemId,\n mode,\n orgId,\n paymentResponse?.orderId,\n paymentResponse?.paymentId,\n productSlug,\n purchaseDetails,\n purchaseType,\n storeOrderId,\n ]);\n\n // Per-seat plan seat count\n const [seatCount, setSeatCount] = useState<number>(1);\n\n // Use checkout hook - pass upgrade/downgrade context if this is a plan change\n const checkout = useCheckout({\n defaultCurrency: 'USD',\n upgradeFromSubscriptionId: locationState?.upgradeFromSubscriptionId,\n downgradeFromSubscriptionId: locationState?.downgradeFromSubscriptionId,\n });\n const { config: providerConfig, stripeEnabled, razorpayEnabled } = usePaymentProviders();\n\n // Payment status polling for both card and credit payments\n const {\n // isPolling - available but UI relies on mode state instead\n elapsedTime,\n error: pollingError,\n // paymentResult - available but not directly used (callbacks handle results)\n } = usePaymentStatusPolling({\n paymentId: mode === 'processing' ? (paymentResponse?.paymentId ?? null) : null,\n billingAccountId: checkout.billingAccount?.id ?? purchaseDetails?.billingAccountId ?? null,\n paymentType: pollingPaymentType, // 'card' or 'credits' based on selected provider\n pollingInterval: 5000, // Poll every 5 seconds\n maxPollingTime: 300000, // 5 minutes timeout\n onSuccess: (result) => {\n // Update transaction ID if available from polling result\n if (result.transactionId) {\n setPurchaseDetails((prev) =>\n prev ? { ...prev, transactionId: result.transactionId } : prev\n );\n }\n setMode('success');\n if (isCartCheckout) {\n clearCart();\n }\n\n // Derive workspace-scoped quotas from the purchased plan's features.\n // We only show the modal for PLAN and ADDON purchases that include workspace quotas.\n // A feature's own context.quantity is a per-plan \"absolute limit\" override\n // (set via the Plan admin form) that takes precedence over the quota\n // catalog's generic limits.value default — must match buildWorkspaceQuotaInputs\n // (the backend function that actually grants the quota) or this preview\n // promises a different number than what the customer actually receives.\n if ((purchaseType === 'PLAN' || purchaseType === 'ADDON') && workspaceId) {\n type PlanFeature = {\n context?: Record<string, unknown> | null;\n quota?: { name?: string | null; limits?: Record<string, unknown> | null } | null;\n };\n const rawFeatures: Array<PlanFeature | null | undefined> =\n planData?.getPlan?.features ?? [];\n const features: PlanFeature[] = rawFeatures.filter((f): f is PlanFeature => f != null);\n const wsQuotas: WorkspaceQuotaToAssign[] = features\n .filter((f: PlanFeature) => f.quota?.name && isWorkspaceScopedQuota(f.quota.name))\n .map((f: PlanFeature) => {\n const limits = f.quota?.limits as Record<string, unknown> | null | undefined;\n const limitsType = limits && 'type' in limits ? String(limits.type) : 'number';\n const overrideQuantity =\n f.context && 'quantity' in f.context ? Number(f.context.quantity) : null;\n const catalogValue = limits && 'value' in limits ? Number(limits.value) : null;\n const limitsValue = overrideQuantity ?? catalogValue;\n return {\n quotaName: f.quota!.name!,\n productId:\n ((planData?.getPlan?.context as Record<string, unknown> | null)?.[\n 'productId'\n ] as string) ?? 'platform',\n limit: limitsType === 'number' ? limitsValue : null,\n noLimit: limitsType !== 'number',\n resetPeriod: 'NEVER',\n quotaMode: 'dedicated' as const,\n };\n });\n\n if (wsQuotas.length > 0) {\n setPendingWorkspaceQuotas(wsQuotas);\n // Refetch subscriptions to resolve the subscriptionId for the modal\n void refetchSubscriptions().then(\n (res: { data?: GetActiveSubscriptionsCheckoutQuery }) => {\n const match = res.data?.getActiveSubscriptions?.find(\n (s: { planId: string; id: string }) => s.planId === itemId\n );\n if (match) setPendingSubscriptionId(match.id);\n }\n );\n // With exactly one workspace there's no real choice to make — the\n // backend's autoActivateSingleWorkspaceForSubscription already\n // assigned these quotas to it at purchase time (see\n // global-billing-svc's Razorpay/Stripe webhook handlers), so this\n // modal would just be asking the customer to confirm something\n // that already happened. Only show it when there's a genuine\n // choice — 2+ workspaces — which is exactly the ambiguity it\n // exists to resolve.\n if (workspaceOptions.length > 1) {\n setShowAssignModal(true);\n }\n }\n }\n\n // Auto-redirect is handled by useEffect\n },\n onFailure: (result) => {\n setPaymentErrorMessage(result.failureReason || 'Payment failed');\n setMode('error');\n },\n onTimeout: () => {\n setPaymentErrorMessage(\n 'Payment verification timed out. Please check your billing history for the payment status.'\n );\n setMode('error');\n },\n });\n\n // Fetch billing accounts\n const {\n data: accountsData,\n loading: accountsLoading,\n refetch: refetchAccounts,\n } = useGetBillingAccountsCheckoutQuery({});\n\n // Fetch plan if purchasing a plan\n const {\n data: planData,\n loading: planLoading,\n // error: planError - available but not displayed (loading/empty states suffice)\n } = useGetPlanCheckoutQuery({\n variables: { id: itemId! },\n skip: purchaseType !== 'PLAN' || !itemId,\n });\n\n // Fetch addon if purchasing an addon\n const { data: addonData, loading: addonLoading } = useGetSelectedAddonCheckoutQuery({\n variables: { addonIDs: itemId ? [itemId] : [] },\n skip: purchaseType !== 'ADDON' || !itemId || isStoreCheckout, // Skip for store checkout\n });\n\n // Fetch store order (for store checkout)\n const { data: storeOrderData } = useGetStoreOrderCheckoutQuery({\n variables: { id: storeOrderId! },\n skip: !isStoreCheckout || !storeOrderId,\n });\n\n // ── Physical store-order shipping (physical produce only) ─────────────────\n // All shipping UI + logic below is gated on `isPhysicalStoreOrder`, which\n // REQUIRES isStoreCheckout (purchaseType 'ORDER') AND a PHYSICAL line item.\n // The digital-app / plan / addon / credits paths — and digital-only store\n // orders — never see any of it and behave byte-for-byte as before.\n //\n // getStoreOrder.lineItems is typed Record<string,unknown> by codegen but is a\n // StoreOrderLineItem[] at runtime — cast defensively behind an Array.isArray\n // guard. itemType is written LOWERCASE by microfe-store (CartPage), so compare\n // case-insensitively or the gate silently never fires.\n const storeOrderLineItems = useMemo<StoreOrderLineItem[]>(() => {\n const raw = storeOrderData?.getStoreOrder?.lineItems;\n return Array.isArray(raw) ? (raw as StoreOrderLineItem[]) : [];\n }, [storeOrderData]);\n\n const isPhysicalStoreOrder =\n isStoreCheckout &&\n storeOrderLineItems.some((li) => String(li.itemType).toUpperCase() === 'PHYSICAL');\n\n // The order currency drives the synthetic shipping line so it converts in\n // lockstep with the product ORDER items (see currency-conversion effect below).\n const storeOrderCurrency = normalizeStoreOrderCurrency(storeOrderData?.getStoreOrder?.currency);\n\n // Buyer's shipping selection, surfaced by <StoreShippingSection />. Cost is\n // DISPLAY-ONLY here; the backend recomputes it authoritatively on submit.\n const [shipping, setShipping] = useState<StoreShippingSelection>({\n shippingAddressId: null,\n shippingMethodId: null,\n shippingCost: 0,\n destinationCountry: null,\n });\n\n // NEW backend mutation — see hooks/useStoreShipping.ts. Inert until\n // global-store-svc ships `setStoreOrderShipping`.\n const [setStoreOrderShipping] = useSetStoreOrderShipping();\n\n // Fetch saved payment methods for selected billing account\n const { data: paymentMethodsData, loading: paymentMethodsLoading } =\n useGetPaymentMethodsCheckoutQuery({\n variables: { billingAccountId: checkout.billingAccount?.id! },\n skip: !checkout.billingAccount?.id,\n });\n\n // Fetch active subscriptions for addon purchases (needed to select which subscription to attach addon to)\n // Skip for store checkout - store items don't require an active subscription\n // Also load for PLAN purchases so we can resolve subscriptionId after payment success\n const {\n data: subscriptionsData,\n loading: subscriptionsLoading,\n refetch: refetchSubscriptions,\n } = useGetActiveSubscriptionsCheckoutQuery({\n variables: { billingAccountId: checkout.billingAccount?.id! },\n skip:\n !checkout.billingAccount?.id ||\n (purchaseType !== 'ADDON' && purchaseType !== 'PLAN') ||\n isStoreCheckout,\n });\n\n // Memoized subscriptions list\n const activeSubscriptions = useMemo(() => {\n return subscriptionsData?.getActiveSubscriptions ?? [];\n }, [subscriptionsData]);\n\n // Selected subscription for addon purchases\n const selectedSubscription = useMemo(() => {\n if (!checkout.selectedSubscriptionId || activeSubscriptions.length === 0) return null;\n return (\n activeSubscriptions.find((s: { id: string }) => s.id === checkout.selectedSubscriptionId) ??\n null\n );\n }, [checkout.selectedSubscriptionId, activeSubscriptions]);\n\n // Fetch credit cost from backend (for credits purchase)\n // Use current quantity from items, or default to 100 for initial query\n // Query in selected currency to get correct pricing\n const creditQuantity = checkout.items[0]?.type === 'CREDITS' ? checkout.items[0].quantity : 100;\n const { data: creditCostData, loading: creditCostLoading } = useGetCostOfCreditsCheckoutQuery({\n variables: { creditAmount: creditQuantity, currency: checkout.selectedCurrency || 'USD' },\n skip: purchaseType !== 'CREDITS',\n });\n\n // Calculate cost per credit from backend response\n const costPerCredit = useMemo(() => {\n if (creditCostData?.getCostOfCredits?.amount && creditQuantity > 0) {\n return creditCostData.getCostOfCredits.amount / creditQuantity;\n }\n return 0.01; // Default: 100 credits = $1\n }, [creditCostData, creditQuantity]);\n\n // Memoized saved payment methods\n const savedPaymentMethods = useMemo<SavedPaymentMethod[]>(() => {\n return paymentMethodsData?.getPaymentMethods ?? [];\n }, [paymentMethodsData]);\n\n // Memoized billing accounts list\n const billingAccounts = useMemo<CheckoutBillingAccount[]>(() => {\n return accountsData?.myBillingAccounts ?? [];\n }, [accountsData]);\n\n // Extract billing country from selected billing account's first billing address\n // This is used to restrict currency and payment provider options\n const billingCountry = useMemo<string | undefined>(() => {\n if (!checkout.billingAccount) return undefined;\n\n // Find the full account data with billing addresses\n const fullAccount = billingAccounts.find((acc) => acc.id === checkout.billingAccount?.id);\n const billingAddresses = fullAccount?.billingAddresses;\n\n if (billingAddresses && billingAddresses.length > 0) {\n return billingAddresses[0]?.country || undefined;\n }\n return undefined;\n }, [checkout.billingAccount, billingAccounts]);\n\n // Track if initial provider selection has been done for this billing country\n const [initialProviderSet, setInitialProviderSet] = useState<string | null>(null);\n\n // Auto-select appropriate currency based on billing country and billing account currency\n // - India: INR only (for Zoho Books GST invoice compatibility)\n // - International: Prefer billing account currency, or USD (INR not allowed for Stripe invoice compatibility)\n useEffect(() => {\n if (!billingCountry) return;\n\n if (billingCountry === 'IN') {\n // Indian billing: Force INR currency\n if (checkout.selectedCurrency !== 'INR') {\n checkout.setCurrency('INR');\n }\n // Auto-select Razorpay ONLY on first load for this billing country (don't override user's choice)\n if (\n razorpayEnabled &&\n initialProviderSet !== billingCountry &&\n checkout.selectedProvider !== 'CREDITS'\n ) {\n checkout.setPaymentProvider('RAZORPAY');\n setInitialProviderSet(billingCountry);\n }\n } else {\n // International billing: Force non-INR currency\n if (checkout.selectedCurrency === 'INR') {\n // Prefer billing account's currency if it's not INR, otherwise default to USD\n const accountCurrency = checkout.billingAccount?.currency;\n const preferredCurrency =\n accountCurrency && accountCurrency !== 'INR' ? accountCurrency : 'USD';\n checkout.setCurrency(preferredCurrency);\n }\n // For international, Stripe is the only option (Razorpay is India-only)\n if (checkout.selectedProvider === 'RAZORPAY') {\n checkout.setPaymentProvider('STRIPE');\n setInitialProviderSet(billingCountry);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Using specific checkout properties is intentional for granular updates\n }, [\n billingCountry,\n checkout.selectedCurrency,\n checkout.selectedProvider,\n checkout.setCurrency,\n checkout.setPaymentProvider,\n checkout.billingAccount?.currency,\n razorpayEnabled,\n initialProviderSet,\n ]);\n\n const {\n availableCredits,\n billingAccount: checkoutBillingAccount,\n canPayWithCredits,\n selectedCurrency,\n selectedPaymentMethodId,\n selectedProvider,\n setCurrency,\n setPaymentProvider,\n setSelectedPaymentMethodId,\n total: checkoutTotal,\n } = checkout;\n\n // Auto-select billing account's currency when billing account changes (for international)\n useEffect(() => {\n if (!checkoutBillingAccount?.currency || billingCountry === 'IN') return;\n\n const accountCurrency = checkoutBillingAccount.currency;\n // Update to billing account's currency if it's different and valid (not INR for international)\n if (accountCurrency !== 'INR' && selectedCurrency !== accountCurrency) {\n setCurrency(accountCurrency);\n }\n }, [billingCountry, checkoutBillingAccount, selectedCurrency, setCurrency]);\n\n // Auto-select payment method with priority: Credits > Saved Card > New Card\n useEffect(() => {\n // Skip if already manually selected something (not AUTO or null)\n if (selectedProvider !== 'AUTO' && selectedProvider !== null) {\n return; // Already selected, don't override user choice\n }\n\n const hasEnoughCredits = availableCredits >= checkoutTotal;\n\n // Priority 1: Credits (if available and sufficient)\n if (canPayWithCredits && hasEnoughCredits) {\n setPaymentProvider('CREDITS');\n setSelectedPaymentMethodId(null);\n return;\n }\n\n // Priority 2: Default saved card\n if (savedPaymentMethods.length > 0 && !selectedPaymentMethodId) {\n const defaultMethod = savedPaymentMethods.find((pm) => pm.isDefault && pm.isVerified);\n const firstVerified = savedPaymentMethods.find((pm) => pm.isVerified);\n const methodToSelect = defaultMethod || firstVerified;\n\n if (methodToSelect) {\n setSelectedPaymentMethodId(methodToSelect.id);\n // Auto-set provider based on card type\n if (methodToSelect.razorpayPaymentMethodId) {\n setPaymentProvider('RAZORPAY');\n } else if (methodToSelect.stripePaymentMethodId) {\n setPaymentProvider('STRIPE');\n }\n return;\n }\n }\n\n // Priority 3: New card (auto-select provider based on country)\n // This is already handled by the currency auto-selection effect above\n }, [\n availableCredits,\n canPayWithCredits,\n checkoutTotal,\n savedPaymentMethods,\n selectedPaymentMethodId,\n selectedProvider,\n setPaymentProvider,\n setSelectedPaymentMethodId,\n ]);\n\n // Track if item was already added to prevent duplicates\n const [itemAdded, setItemAdded] = useState(false);\n\n useEffect(() => {\n if (!isStoreCheckout || itemAdded || !storeOrderData?.getStoreOrder) {\n return;\n }\n\n const orderCurrency = normalizeStoreOrderCurrency(storeOrderData.getStoreOrder.currency);\n if (selectedCurrency !== orderCurrency) {\n setCurrency(orderCurrency);\n }\n }, [isStoreCheckout, itemAdded, selectedCurrency, setCurrency, storeOrderData]);\n\n // Initialize checkout with items\n // Initialize seat count from plan minSeats when plan data loads\n useEffect(() => {\n if (purchaseType === 'PLAN' && planData?.getPlan?.pricingModel === 'PER_SEAT') {\n const minSeats = planData.getPlan.minSeats ?? 1;\n setSeatCount((prev) => (prev < minSeats ? minSeats : prev));\n }\n }, [planData, purchaseType]);\n\n // Recalculate checkout item when seat count changes for PER_SEAT plans\n useEffect(() => {\n if (purchaseType === 'PLAN' && planData?.getPlan?.pricingModel === 'PER_SEAT' && itemAdded) {\n setItemAdded(false); // triggers re-add with new seatCount\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [seatCount]);\n\n useEffect(() => {\n // Skip if already added items\n if (itemAdded) {\n return;\n }\n\n // Handle cart-based checkout (multiple addons from cart)\n // Wait for cart to hydrate before processing\n if (isCartCheckout && cartHydrated && cartItems.length > 0) {\n // Clear existing items first to prevent duplicates (React strict mode can run effects twice)\n checkout.clearItems();\n\n for (const cartItem of cartItems) {\n const addon = cartItem.addon;\n\n // Extract features from addon (using SubscriptionFeatures type with quota).\n // context.quantity is the amount this addon actually grants per unit\n // purchased — takes precedence over the quota catalog's generic\n // limits.value, same as the other purchase paths on this page.\n const features =\n addon.features?.map((f) => {\n const limits = f.quota?.limits as Record<string, number | string | boolean> | undefined;\n const overrideQuantity =\n f.context && 'quantity' in f.context ? Number(f.context.quantity) : undefined;\n return {\n id: f.quota?.id || f.id,\n name: f.quota?.name || 'Feature',\n limits:\n overrideQuantity !== undefined && limits && 'type' in limits\n ? { ...limits, value: overrideQuantity }\n : limits,\n };\n }) || [];\n\n // Use stored currency price if available, otherwise fallback to base USD price\n const selectedCurrency = checkout.selectedCurrency || 'USD';\n const currencyPrices = (addon as { currencyPrices?: Record<string, number> })\n .currencyPrices;\n const priceInSelectedCurrency = currencyPrices?.[selectedCurrency] ?? addon.price;\n // addon.price/currency are always localized to the caller's currency\n // context (e.g. an INR request gets addon.price in INR, regardless of\n // the addon's stored base currency) — currencyPrices.USD is the one\n // field that's reliably actually USD, needed for credit calculations.\n const usdPrice = currencyPrices?.USD ?? addon.price;\n\n const item: CheckoutItem = {\n id: addon.id,\n type: 'ADDON',\n name: addon.name,\n description:\n typeof addon.context === 'object' && addon.context !== null\n ? (addon.context as { description?: string }).description\n : typeof addon.context === 'string'\n ? addon.context\n : undefined,\n price: priceInSelectedCurrency,\n priceUSD: usdPrice,\n currency: selectedCurrency,\n quantity: cartItem.quantity,\n features,\n metadata: { addonId: addon.id },\n };\n checkout.addItem(item);\n }\n setItemAdded(true);\n\n return;\n }\n\n if (purchaseType === 'PLAN' && planData?.getPlan) {\n const plan = planData.getPlan;\n\n // Extract features from plan. A feature's own context.quantity is a\n // per-plan \"absolute limit\" override (set via the Plan admin form) that\n // takes precedence over the quota catalog's generic limits.value\n // default — must match buildWorkspaceQuotaInputs (the backend function\n // that actually grants the quota) or this order summary promises a\n // different number than what the customer actually receives.\n const features =\n plan.features?.map(\n (f: {\n id: string;\n context?: Record<string, unknown> | null;\n quota?: { id: string; name: string; limits?: Record<string, unknown> };\n }) => {\n const limits = f.quota?.limits;\n const overrideQuantity =\n f.context && 'quantity' in f.context ? Number(f.context.quantity) : undefined;\n return {\n id: f.quota?.id || f.id,\n name: f.quota?.name || 'Feature',\n limits:\n overrideQuantity !== undefined && limits && 'type' in limits\n ? { ...limits, value: overrideQuantity }\n : limits,\n };\n }\n ) || [];\n\n // Use stored currency price if available, otherwise fallback to base USD price\n const selectedCurrency = checkout.selectedCurrency || 'USD';\n const currencyPrices = plan.currencyPrices as Record<\n string,\n number | { perSeat?: number }\n > | null;\n const isPerSeat = plan.pricingModel === 'PER_SEAT';\n\n let priceInSelectedCurrency: number;\n if (isPerSeat && plan.pricePerSeat) {\n // PER_SEAT: per-seat rate × seat count\n const currencyEntry = currencyPrices?.[selectedCurrency];\n const perSeatRate =\n typeof currencyEntry === 'object' && currencyEntry?.perSeat\n ? currencyEntry.perSeat\n : plan.pricePerSeat;\n priceInSelectedCurrency = perSeatRate * seatCount;\n } else {\n const currencyEntry = currencyPrices?.[selectedCurrency];\n priceInSelectedCurrency = typeof currencyEntry === 'number' ? currencyEntry : plan.price;\n }\n\n // USD price for credit-balance calculations. plan.price/pricePerSeat are\n // only reliably USD when the plan's own base currency is USD — a plan\n // whose base currency is e.g. INR (an admin can pick any currency when\n // creating a plan) stores that INR amount directly in plan.price, not a\n // USD figure. currencyPrices.USD (present for every plan regardless of\n // base currency) is the one field that's always actually USD.\n const usdCurrencyEntry = currencyPrices?.USD;\n const usdFlatPrice = typeof usdCurrencyEntry === 'number' ? usdCurrencyEntry : plan.price;\n const usdPerSeatRate =\n typeof usdCurrencyEntry === 'object' && usdCurrencyEntry?.perSeat\n ? usdCurrencyEntry.perSeat\n : plan.pricePerSeat;\n\n const resolvedSeatCount = isPerSeat ? seatCount : 1;\n\n const item: CheckoutItem = {\n id: plan.id,\n type: 'PLAN',\n name: plan.name,\n description:\n typeof plan.context === 'object' && plan.context !== null\n ? (plan.context as { description?: string }).description\n : typeof plan.context === 'string'\n ? plan.context\n : undefined,\n price: priceInSelectedCurrency,\n priceUSD:\n isPerSeat && plan.pricePerSeat\n ? (usdPerSeatRate ?? plan.pricePerSeat) * resolvedSeatCount\n : usdFlatPrice,\n currency: selectedCurrency,\n quantity: resolvedSeatCount,\n features,\n metadata: {\n planId: plan.id,\n duration: plan.duration,\n seatCount: isPerSeat ? resolvedSeatCount : undefined,\n },\n };\n\n checkout.addItem(item);\n setItemAdded(true);\n } else if (purchaseType === 'ADDON' && !isCartCheckout && addonData?.getSelectedAddons?.[0]) {\n const addon = addonData.getSelectedAddons[0];\n\n // Extract features from addon. context.quantity is the amount this\n // addon actually grants per unit purchased — takes precedence over the\n // quota catalog's generic limits.value, same as the plan path above.\n const features =\n addon.features?.map(\n (f: {\n id: string;\n context?: Record<string, unknown> | null;\n quota?: { id: string; name: string; limits?: Record<string, unknown> };\n }) => {\n const limits = f.quota?.limits;\n const overrideQuantity =\n f.context && 'quantity' in f.context ? Number(f.context.quantity) : undefined;\n return {\n id: f.quota?.id || f.id,\n name: f.quota?.name || 'Feature',\n limits:\n overrideQuantity !== undefined && limits && 'type' in limits\n ? { ...limits, value: overrideQuantity }\n : limits,\n };\n }\n ) || [];\n\n // Use stored currency price if available, otherwise fallback to base USD price\n const selectedCurrency = checkout.selectedCurrency || 'USD';\n const currencyPrices = addon.currencyPrices as Record<string, number> | null;\n const priceInSelectedCurrency = currencyPrices?.[selectedCurrency] ?? addon.price;\n // addon.price/currency are always localized to the caller's currency\n // context (e.g. an INR request gets addon.price in INR, regardless of\n // the addon's stored base currency) — currencyPrices.USD is the one\n // field that's reliably actually USD, needed for credit calculations.\n const usdPrice = currencyPrices?.USD ?? addon.price;\n\n const item: CheckoutItem = {\n id: addon.id,\n type: 'ADDON',\n name: addon.name,\n description:\n typeof addon.context === 'object' && addon.context !== null\n ? (addon.context as { description?: string }).description\n : typeof addon.context === 'string'\n ? addon.context\n : undefined,\n price: priceInSelectedCurrency,\n priceUSD: usdPrice,\n currency: selectedCurrency,\n quantity: locationState?.quantity ?? 1,\n features,\n metadata: { addonId: addon.id },\n };\n checkout.addItem(item);\n setItemAdded(true);\n } else if (isStoreCheckout && storeOrderData?.getStoreOrder) {\n // Load store order items into checkout\n const storeOrder = storeOrderData.getStoreOrder;\n const items = buildStoreOrderCheckoutItems(storeOrder);\n\n for (const item of items) {\n checkout.addItem(item);\n }\n setItemAdded(true);\n } else if (purchaseType === 'CREDITS') {\n const selectedCurrency = checkout.selectedCurrency || 'USD';\n const item: CheckoutItem = {\n id: 'credits-purchase',\n type: 'CREDITS',\n name: 'Credits',\n description: 'Purchase credits for your account',\n price: costPerCredit, // Use actual cost per credit from backend (in selected currency)\n currency: selectedCurrency,\n quantity: 100, // Default amount\n };\n checkout.addItem(item);\n setItemAdded(true);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Using specific checkout properties is intentional for granular updates\n }, [\n purchaseType,\n planData,\n addonData,\n storeOrderData,\n checkout.addItem,\n checkout.clearItems,\n locationState?.quantity,\n itemAdded,\n isCartCheckout,\n isStoreCheckout,\n cartItems,\n cartHydrated,\n costPerCredit,\n checkout.selectedCurrency, // Re-add items when currency changes to use correct prices\n ]);\n\n // Update store-order item prices when a payment currency change requires conversion.\n // If the persisted order currency already matches the selected currency this is a no-op.\n useEffect(() => {\n // convertedAmount !== null signals a real rate has arrived for the current\n // currency pair — conversionRate's own magnitude can't be used for this\n // (a from-INR-to-USD rate is legitimately < 1, unlike the from-USD rates\n // this used to always fetch — see BOFF-7127).\n if (!isStoreCheckout || !itemAdded || checkout.convertedAmount === null) return;\n\n for (const item of checkout.items) {\n if (item.type === 'ORDER' && item.currency !== checkout.selectedCurrency) {\n const convertedPrice = item.price * checkout.conversionRate;\n checkout.updateItemPrice(item.id, convertedPrice, checkout.selectedCurrency);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- checkout.items intentionally excluded to avoid infinite loop\n }, [\n isStoreCheckout,\n itemAdded,\n checkout.convertedAmount,\n checkout.conversionRate,\n checkout.selectedCurrency,\n checkout.updateItemPrice,\n ]);\n\n // Fold the buyer's shipping cost into the billing total as a single synthetic\n // ORDER line item, upserted-by-id so it survives React strict-mode double\n // effects and is never an editable/removable line (OrderSummary disables qty\n // editing for ORDER purchases). Free shipping (cost 0) intentionally adds no\n // line.\n //\n // The line is ALWAYS written in the selected currency, pre-converting from the\n // order currency with the SAME formula the store-order conversion effect above\n // uses (price * conversionRate, only when order currency !== selected currency\n // and a rate is active). Because its currency always equals the selected\n // currency, that conversion effect skips it (`item.currency !== selectedCurrency`\n // is false) — so shipping is counted EXACTLY once and can never be double-applied.\n // This effect owns re-pricing on cost / currency / rate changes.\n //\n // This is the ONLY place shipping enters the billing charge; the backend's\n // StoreOrder.total bump is store-side reconciliation only and is never read\n // back here (no double count).\n const shippingLineRef = useRef<{ price: number; currency: string } | null>(null);\n useEffect(() => {\n if (!isPhysicalStoreOrder) return;\n\n if (shipping.shippingCost > 0) {\n const selectedCurrency = checkout.selectedCurrency;\n const needsConversion =\n storeOrderCurrency !== selectedCurrency && checkout.convertedAmount !== null;\n const price = needsConversion\n ? shipping.shippingCost * checkout.conversionRate\n : shipping.shippingCost;\n\n const prev = shippingLineRef.current;\n if (prev && prev.price === price && prev.currency === selectedCurrency) {\n return; // nothing changed\n }\n if (prev === null) {\n checkout.addItem({\n id: STORE_SHIPPING_ITEM_ID,\n type: 'ORDER',\n name: 'Shipping',\n price,\n currency: selectedCurrency,\n quantity: 1,\n metadata: { storeOrderId, isShipping: true },\n });\n } else {\n checkout.updateItemPrice(STORE_SHIPPING_ITEM_ID, price, selectedCurrency);\n }\n shippingLineRef.current = { price, currency: selectedCurrency };\n } else if (shippingLineRef.current !== null) {\n shippingLineRef.current = null;\n checkout.removeItem(STORE_SHIPPING_ITEM_ID);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- add/update decided via ref; checkout.items excluded to avoid an update loop\n }, [\n isPhysicalStoreOrder,\n shipping.shippingCost,\n storeOrderCurrency,\n checkout.selectedCurrency,\n checkout.conversionRate,\n checkout.convertedAmount,\n storeOrderId,\n checkout.addItem,\n checkout.updateItemPrice,\n checkout.removeItem,\n ]);\n\n // Update item price when backend credit cost changes (after item is already added)\n // This handles cases where the rate might differ based on quantity or currency\n useEffect(() => {\n if (purchaseType === 'CREDITS' && itemAdded && costPerCredit > 0) {\n const currentItem = checkout.items.find((i) => i.id === 'credits-purchase');\n // Only update if price actually changed\n if (currentItem && currentItem.price !== costPerCredit) {\n checkout.updateItemPrice('credits-purchase', costPerCredit);\n }\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Using specific checkout properties is intentional for granular updates\n }, [purchaseType, itemAdded, costPerCredit, checkout.items, checkout.updateItemPrice]);\n\n // Update item prices when currency changes (for plans and addons with fixed currencyPrices)\n // This handles mid-checkout currency changes by updating prices from stored currencyPrices\n useEffect(() => {\n if (!itemAdded || checkout.items.length === 0) return;\n\n const selectedCurrency = checkout.selectedCurrency || 'USD';\n\n for (const item of checkout.items) {\n if (item.type === 'PLAN' && planData?.getPlan) {\n const currencyPrices = planData.getPlan.currencyPrices as Record<string, number> | null;\n const priceInSelectedCurrency =\n currencyPrices?.[selectedCurrency] ?? planData.getPlan.price;\n // Update both price AND currency so conversion check works correctly\n if (item.price !== priceInSelectedCurrency || item.currency !== selectedCurrency) {\n checkout.updateItemPrice(item.id, priceInSelectedCurrency, selectedCurrency);\n }\n } else if (item.type === 'ADDON') {\n // For single addon checkout\n if (!isCartCheckout && addonData?.getSelectedAddons?.[0]) {\n const addon = addonData.getSelectedAddons[0];\n const currencyPrices = addon.currencyPrices as Record<string, number> | null;\n const priceInSelectedCurrency = currencyPrices?.[selectedCurrency] ?? addon.price;\n if (item.price !== priceInSelectedCurrency || item.currency !== selectedCurrency) {\n checkout.updateItemPrice(item.id, priceInSelectedCurrency, selectedCurrency);\n }\n }\n // For cart checkout, find the addon in cartItems\n if (isCartCheckout) {\n const cartItem = cartItems.find((ci) => ci.addon.id === item.id);\n if (cartItem) {\n const currencyPrices = (cartItem.addon as { currencyPrices?: Record<string, number> })\n .currencyPrices;\n const priceInSelectedCurrency =\n currencyPrices?.[selectedCurrency] ?? cartItem.addon.price;\n if (item.price !== priceInSelectedCurrency || item.currency !== selectedCurrency) {\n checkout.updateItemPrice(item.id, priceInSelectedCurrency, selectedCurrency);\n }\n }\n }\n }\n // CREDITS are handled by the credit cost effect above\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Using specific checkout properties is intentional for granular updates\n }, [\n checkout.selectedCurrency,\n itemAdded,\n checkout.items,\n checkout.updateItemPrice,\n planData,\n addonData,\n cartItems,\n isCartCheckout,\n ]);\n\n // Set default billing account\n // Priority: 1. Location state (for upgrade flows), 2. localStorage (user's last selection), 3. First account\n useEffect(() => {\n if (billingAccounts.length > 0 && !checkout.billingAccount) {\n // 1. If this is an upgrade, find and select the specific billing account from location state\n if (locationState?.billingAccountId) {\n const targetAccount = billingAccounts.find(\n (acc) => acc.id === locationState.billingAccountId\n );\n if (targetAccount) {\n checkout.setBillingAccount(targetAccount);\n return;\n }\n }\n\n // 2. Check localStorage for previously selected billing account (set from /overview page)\n if (orgId) {\n const storedAccountId = getStoredBillingAccountId(orgId);\n if (storedAccountId) {\n const storedAccount = billingAccounts.find((acc) => acc.id === storedAccountId);\n if (storedAccount) {\n checkout.setBillingAccount(storedAccount);\n return;\n }\n }\n }\n\n // 3. Default: select first account\n checkout.setBillingAccount(billingAccounts[0]);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Using specific checkout properties is intentional for granular updates\n }, [\n billingAccounts,\n checkout.billingAccount,\n checkout.setBillingAccount,\n locationState?.billingAccountId,\n orgId,\n ]);\n\n // Determine if billing account selector should be locked (for upgrade/downgrade flows)\n const isBillingAccountLocked = Boolean(\n locationState?.upgradeFromSubscriptionId || locationState?.downgradeFromSubscriptionId\n );\n\n // Redirect to addons page if cart is empty for cart checkout.\n // Navigation must happen after render to avoid React setState-in-render warnings.\n useEffect(() => {\n if (isCartCheckout && cartHydrated && cartItems.length === 0 && !itemAdded) {\n navigateTo('/addons', { replace: true });\n }\n }, [cartHydrated, cartItems.length, isCartCheckout, itemAdded, navigateTo]);\n\n const nativeCheckout = useNativeCheckout({\n billingAccountId: checkout.billingAccount?.id,\n purchaseType,\n productId: itemId,\n });\n\n const handleProceedToPayment = useCallback(async () => {\n if (!checkout.billingAccount) {\n return;\n }\n\n // Gate: require complete billing details before payment.\n // Skip once after the CompleteBillingAccountModal saves — Apollo cache still holds stale\n // contactNumber/billingAddresses because the mutation selection doesn't return those fields.\n const skipGate = bypassBillingGate.current;\n bypassBillingGate.current = false;\n if (!skipGate && !isBillingAccountComplete(checkout.billingAccount)) {\n setShowCompleteAccountModal(true);\n return;\n }\n\n setPaymentErrorMessage(null);\n\n // Physical store orders: persist the chosen shipping address + method on the\n // store order BEFORE billing creates its immutable order snapshot. The store\n // service recomputes and persists the shipping cost authoritatively; only IDs\n // are sent from this page. This whole block is skipped for non-physical paths.\n if (isPhysicalStoreOrder) {\n try {\n const shippingResult = await setStoreOrderShipping({\n variables: {\n input: {\n orderId: storeOrderId!,\n shippingMethodId: shipping.shippingMethodId!,\n ...(shipping.shippingAddressId\n ? { shippingAddressId: shipping.shippingAddressId }\n : {}),\n },\n },\n });\n const serverCost = shippingResult.data?.setStoreOrderShipping?.shippingCost;\n if (typeof serverCost === 'number' && serverCost !== shipping.shippingCost) {\n // Keep the display in sync if the authoritative quote differs from\n // the estimate shown before the user submitted the order.\n setShipping((prev) => ({ ...prev, shippingCost: serverCost }));\n }\n } catch {\n setPaymentErrorMessage(\n 'Could not save your shipping details. Please review your delivery address and try again.'\n );\n setMode('error');\n return;\n }\n }\n\n // Capture purchase details before payment (for success page)\n const previousBalance = checkout.billingAccount.creditAmount ?? 0;\n const purchasedItems = [...checkout.items];\n // checkout.total is in the selected DISPLAY currency (e.g. INR) — never\n // the right number for a credits purchase, which always charges\n // creditsRequired (subtotalUSD × CREDITS_PER_USD) regardless of display\n // currency. The spendCredits mutation itself doesn't return the amount\n // it charged, so this is recomputed the same way the backend charges it,\n // not read back from the response.\n const purchaseTotal = checkout.total;\n const creditsCharged = checkout.creditsRequired;\n\n // The checkout hook asks the store service to create the billing order from\n // the persisted catalog and shipping snapshots. No browser-calculated total\n // or line item is used to create the payable order.\n const response = await checkout.initiatePayment();\n\n if (response.success) {\n emit('billing.checkout.initiated', {\n route: '/billing/checkout',\n entityId: response.paymentId ?? response.orderId,\n source: 'checkout',\n amount: response.total || purchaseTotal,\n currency: response.currency || checkout.selectedCurrency,\n });\n // For credit payments, go to processing mode and poll for transaction status\n if (checkout.selectedProvider === 'CREDITS') {\n emit('billing.credits.spent', {\n route: '/billing/checkout',\n entityId: response.orderId,\n source: 'checkout',\n amount: creditsCharged,\n currency: 'CREDITS',\n });\n // Store purchase details for success page\n setPurchaseDetails({\n items: purchasedItems,\n total: creditsCharged,\n billingAccountName: checkout.billingAccount.name,\n billingAccountId: checkout.billingAccount.id,\n previousBalance,\n newBalance: previousBalance - creditsCharged,\n paymentMethod: 'credits',\n currency: 'Credits', // Credit payments use credits as currency\n transactionId: response.orderId, // This is creditTransactionID from spendCredits\n });\n\n // Set payment type for polling and start verification\n setPollingPaymentType('credits');\n setPaymentResponse({\n paymentId: response.orderId, // creditTransactionID from spendCredits\n });\n setMode('processing');\n return;\n }\n\n // For card payments, proceed to payment form\n // Store partial details (will be completed after payment confirmation)\n setPurchaseDetails({\n items: purchasedItems,\n total: response.total || purchaseTotal, // Use response total which includes tax\n billingAccountName: checkout.billingAccount.name,\n billingAccountId: checkout.billingAccount.id,\n previousBalance,\n newBalance: previousBalance, // Card payment doesn't affect credits\n paymentMethod: 'card',\n currency: response.currency || checkout.selectedCurrency, // Use actual currency from response\n transactionId: response.paymentId, // Backend payment ID\n });\n\n setPollingPaymentType('card');\n setPaymentResponse({\n clientSecret: response.clientSecret,\n orderId: response.orderId, // Gateway order ID (for Razorpay)\n razorpayKeyId: response.razorpayKeyId,\n razorpayCustomerId: response.razorpayCustomerId, // For Razorpay saved cards\n paymentId: response.paymentId, // Backend payment ID for polling\n paymentComplete: response.paymentComplete, // For saved card one-click payments\n prefillName: response.prefillName,\n prefillEmail: response.prefillEmail,\n prefillContact: response.prefillContact,\n total: response.total, // Total in target currency (from backend, e.g., INR amount)\n currency: response.currency, // Currency code from backend\n });\n\n // If payment completed immediately (saved card), go to processing for verification\n if (response.paymentComplete) {\n setMode('processing');\n } else {\n setMode('payment');\n }\n } else {\n emit('billing.checkout.failed', {\n route: '/billing/checkout',\n source: 'checkout',\n reason: response.error || 'Payment initiation failed',\n });\n setPaymentErrorMessage(response.error || 'Payment initiation failed');\n setMode('error');\n }\n }, [\n checkout,\n emit,\n isPhysicalStoreOrder,\n shipping,\n storeOrderId,\n storeOrderCurrency,\n setStoreOrderShipping,\n ]);\n\n const handlePaymentSuccess = useCallback(\n (_gatewayPaymentId: string) => {\n emit('billing.payment.succeeded', {\n route: '/billing/checkout',\n entityId: paymentResponse?.paymentId ?? paymentResponse?.orderId,\n source: 'checkout',\n });\n // Keep using the backend payment ID (orderId) that was already set\n // The _gatewayPaymentId is for reference only, not for our polling\n setPaymentResponse((prev) => {\n return prev; // Keep existing paymentId (orderId from initiatePayment)\n });\n setMode('processing');\n },\n [emit, paymentResponse?.orderId, paymentResponse?.paymentId]\n );\n\n const handlePaymentError = useCallback(\n (error: string) => {\n emit('billing.payment.failed', {\n route: '/billing/checkout',\n entityId: paymentResponse?.paymentId ?? paymentResponse?.orderId,\n source: 'checkout',\n reason: error,\n });\n setPaymentErrorMessage(error);\n setMode('error');\n },\n [emit, paymentResponse?.orderId, paymentResponse?.paymentId]\n );\n\n const handleCreditsAmountChange = useCallback(\n (amount: number) => {\n checkout.updateItemQuantity('credits-purchase', amount);\n },\n [checkout]\n );\n\n if (!permissions.canProcessPayment) {\n return <AccessDenied message=\"You don't have permission to make purchases.\" />;\n }\n\n if (isNativeIAPAvailable()) {\n return (\n <NativeCheckoutView\n purchaseType={purchaseType}\n productId={itemId}\n billingAccountId={checkout.billingAccount?.id}\n nativeCheckout={nativeCheckout}\n onBack={() => navigateTo('dashboard')}\n />\n );\n }\n\n const isLoading =\n accountsLoading ||\n (planLoading && purchaseType === 'PLAN') ||\n (addonLoading && purchaseType === 'ADDON' && !isCartCheckout) ||\n (isCartCheckout && !cartHydrated);\n\n if (isCartCheckout && cartHydrated && cartItems.length === 0 && !itemAdded) {\n return null;\n }\n\n // Get page title based on purchase type\n const getPageTitle = () => {\n if (isCartCheckout) {\n const addonCount = checkout.items.length;\n return `Purchase ${addonCount} Add-on${addonCount !== 1 ? 's' : ''}`;\n }\n switch (purchaseType) {\n case 'CREDITS':\n return 'Buy Credits';\n case 'PLAN':\n return 'Subscribe to Plan';\n case 'ADDON':\n return 'Purchase Add-on';\n default:\n return 'Checkout';\n }\n };\n\n const getPageIcon = () => {\n switch (purchaseType) {\n case 'CREDITS':\n return <Coins className=\"size-6\" />;\n case 'PLAN':\n return <Package className=\"size-6\" />;\n case 'ADDON':\n return <Blocks className=\"size-6\" />;\n default:\n return <ShoppingCart className=\"size-6\" />;\n }\n };\n\n // Render loading state\n if (isLoading) {\n return (\n <div className=\"flex items-center justify-center min-h-[400px]\">\n <div className=\"text-center space-y-4\">\n <Loader2 className=\"size-8 animate-spin mx-auto text-text-link\" />\n <p className=\"text-text-secondary\">\n {tr('billing.checkout.loadingCheckout', 'Loading checkout...')}\n </p>\n </div>\n </div>\n );\n }\n\n // Render success state\n if (mode === 'success') {\n // Format amount based on payment method and currency\n const formatAmount = (amount: number) => {\n if (!purchaseDetails) return `${amount.toFixed(2)}`;\n\n const currency = purchaseDetails.currency;\n\n // For credits, show \"X.XX Credits\"\n if (currency === 'Credits') {\n return `${amount.toFixed(2)} Credits`;\n }\n\n // For real currencies, show with symbol\n const currencySymbols: Record<string, string> = {\n INR: '₹',\n USD: '$',\n EUR: '€',\n GBP: '£',\n };\n const symbol = currencySymbols[currency] || currency + ' ';\n return `${symbol}${amount.toFixed(2)}`;\n };\n\n // Keep formatCredits for credit balance section\n const formatCredits = (amount: number) => `${amount.toFixed(2)} Credits`;\n\n return (\n <>\n <div className=\"max-w-2xl mx-auto p-6 space-y-6\">\n {/* Success Header */}\n <div className=\"text-center space-y-4\">\n <div className=\"inline-flex p-4 rounded-full bg-status-success-bg-subtle text-status-success-text\">\n <CheckCircle className=\"size-12\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {tr('billing.checkout.paymentSuccessful', 'Payment Successful!')}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {tr(\n 'billing.checkout.purchaseCompleted',\n 'Your purchase has been completed successfully.'\n )}\n </p>\n </div>\n </div>\n\n {/* Purchase Details Card */}\n {purchaseDetails && (\n <div className=\"bg-bg-surface border border-border-subtle rounded-lg overflow-hidden\">\n {/* Items Purchased */}\n <div className=\"p-4 border-b border-border-subtle\">\n <h3 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Receipt className=\"size-4\" />\n {tr('billing.checkout.itemsPurchased', 'Items Purchased')}\n </h3>\n <div className=\"space-y-2\">\n {purchaseDetails.items.map((item) => (\n <div key={item.id} className=\"flex items-center justify-between\">\n <div className=\"flex items-center gap-2\">\n {item.type === 'PLAN' && <Package className=\"size-4 text-accent-blue\" />}\n {item.type === 'ADDON' && <Blocks className=\"size-4 text-accent-purple\" />}\n {item.type === 'CREDITS' && (\n <Coins className=\"size-4 text-status-warning-text\" />\n )}\n {item.type === 'ORDER' && (\n <ShoppingCart className=\"size-4 text-status-success-text\" />\n )}\n <span className=\"text-text-primary\">{item.name}</span>\n {item.quantity > 1 && (\n <span className=\"text-xs text-text-secondary\">x{item.quantity}</span>\n )}\n </div>\n <span className=\"text-text-secondary\">\n {formatAmount(\n purchaseDetails.paymentMethod === 'credits'\n ? (item.priceUSD ?? item.price) * CREDITS_PER_USD * item.quantity\n : item.price * item.quantity\n )}\n </span>\n </div>\n ))}\n </div>\n </div>\n\n {/* Payment Info */}\n <div className=\"p-4 border-b border-border-subtle bg-bg-sunken\">\n <div className=\"grid grid-cols-2 gap-4\">\n {/* Payment Method */}\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.paymentMethod', 'Payment Method')}\n </p>\n <div className=\"flex items-center gap-2\">\n {purchaseDetails.paymentMethod === 'credits' ? (\n <>\n <Wallet className=\"size-4 text-status-warning-text\" />\n <span className=\"font-medium text-text-primary\">\n {tr('billing.checkout.credits', 'Credits')}\n </span>\n </>\n ) : (\n <>\n <CreditCard className=\"size-4 text-accent-blue\" />\n <span className=\"font-medium text-text-primary\">\n {tr('billing.checkout.card', 'Card')}\n </span>\n </>\n )}\n </div>\n </div>\n {/* Total */}\n <div className=\"text-right\">\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.totalPaid', 'Total Paid')}\n </p>\n <p className=\"font-bold text-lg text-text-primary\">\n {formatAmount(purchaseDetails.total)}\n </p>\n </div>\n </div>\n </div>\n\n {/* Credit Balance (only for credit payments) */}\n {purchaseDetails.paymentMethod === 'credits' && (\n <div className=\"p-4 border-b border-border-subtle\">\n <h3 className=\"font-semibold text-text-primary mb-3 flex items-center gap-2\">\n <Wallet className=\"size-4\" />\n {tr('billing.checkout.creditBalance', 'Credit Balance')}\n </h3>\n <div className=\"flex items-center gap-4\">\n <div className=\"flex-1 text-center p-3 rounded-lg bg-bg-sunken\">\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.previousBalance', 'Previous Balance')}\n </p>\n <p className=\"font-semibold text-text-primary\">\n {formatCredits(purchaseDetails.previousBalance)}\n </p>\n </div>\n <ArrowRight className=\"size-5 text-text-secondary shrink-0\" />\n <div className=\"flex-1 text-center p-3 rounded-lg bg-status-success-bg-subtle\">\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.newBalance', 'New Balance')}\n </p>\n <p className=\"font-semibold text-status-success-text\">\n {formatCredits(purchaseDetails.newBalance)}\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Billing Account Info */}\n <div className=\"p-4 bg-bg-sunken\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.billingAccount', 'Billing Account')}\n </p>\n <p className=\"font-medium text-text-primary\">\n {purchaseDetails.billingAccountName}\n </p>\n </div>\n {purchaseDetails.transactionId && (\n <div className=\"text-right\">\n <p className=\"text-xs text-text-secondary mb-1\">\n {tr('billing.checkout.transactionId', 'Transaction ID')}\n </p>\n <p className=\"font-mono text-xs text-text-secondary\">\n {purchaseDetails.transactionId.slice(0, 8)}...\n </p>\n </div>\n )}\n </div>\n </div>\n </div>\n )}\n\n {/* Action Buttons */}\n <div className=\"flex flex-col sm:flex-row gap-3 justify-center pt-2\">\n <button\n type=\"button\"\n onClick={() => navigateTo('/overview')}\n className=\"px-6 py-3 text-sm font-medium bg-action-primary-bg text-action-primary-text rounded-lg hover:bg-action-primary-bgHover transition-colors\"\n >\n {tr('billing.checkout.backToOverview', 'Back to Overview')}\n </button>\n <button\n type=\"button\"\n onClick={() => navigateTo('/subscriptions')}\n className=\"px-6 py-3 text-sm font-medium border border-border-subtle text-text-primary rounded-lg hover:bg-bg-sunken transition-colors\"\n >\n {tr('billing.checkout.viewSubscriptions', 'View Subscriptions')}\n </button>\n </div>\n </div>\n\n {/* Workspace Quota Assignment Modal */}\n {showAssignModal &&\n pendingWorkspaceQuotas.length > 0 &&\n workspaceId &&\n pendingSubscriptionId && (\n <WorkspaceQuotaAssignmentModal\n billingAccountId={purchaseDetails?.billingAccountId ?? ''}\n subscriptionId={pendingSubscriptionId}\n quotas={pendingWorkspaceQuotas}\n workspaces={workspaceOptions}\n currentWorkspaceId={workspaceId}\n onAssigned={() => setShowAssignModal(false)}\n onSkip={() => setShowAssignModal(false)}\n />\n )}\n </>\n );\n }\n\n // Render error state\n if (mode === 'error') {\n return (\n <div className=\"max-w-lg mx-auto p-6\">\n <div className=\"text-center space-y-6\">\n <div className=\"inline-flex p-4 rounded-full bg-status-error-bg-subtle text-status-error-text\">\n <XCircle className=\"size-12\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {tr('billing.checkout.paymentFailed', 'Payment Failed')}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {paymentErrorMessage ||\n pollingError ||\n checkout.error ||\n tr('billing.checkout.genericError', 'Something went wrong. Please try again.')}\n </p>\n </div>\n <div className=\"flex gap-3 justify-center\">\n <button\n type=\"button\"\n onClick={() => navigateTo(-1)}\n className=\"px-4 py-2 rounded-lg border border-border-subtle hover:bg-bg-sunken\"\n >\n {tr('billing.checkout.goBack', 'Go Back')}\n </button>\n <button\n type=\"button\"\n onClick={() => {\n setMode('select');\n setPaymentErrorMessage(null);\n checkout.reset();\n }}\n className=\"px-4 py-2 rounded-lg bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover\"\n >\n {tr('billing.checkout.tryAgain', 'Try Again')}\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n // Render processing state (waiting for payment confirmation)\n if (mode === 'processing') {\n const formatTime = (ms: number) => {\n const seconds = Math.floor(ms / 1000);\n const minutes = Math.floor(seconds / 60);\n const remainingSeconds = seconds % 60;\n return `${minutes}:${remainingSeconds.toString().padStart(2, '0')}`;\n };\n\n return (\n <div className=\"max-w-lg mx-auto p-6\">\n <div className=\"text-center space-y-6\">\n <div className=\"inline-flex p-4 rounded-full bg-[var(--color-accent-soft)] text-text-link\">\n <Loader2 className=\"size-12 animate-spin\" />\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">\n {tr('billing.checkout.verifyingPayment', 'Verifying Payment')}\n </h1>\n <p className=\"text-text-secondary mt-2\">\n {tr(\n 'billing.checkout.verifyingPaymentDescription',\n 'Please wait while we confirm your payment...'\n )}\n </p>\n </div>\n\n {/* Elapsed time */}\n <div className=\"bg-bg-sunken rounded-lg p-4\">\n <p className=\"text-sm text-text-secondary\">\n {tr('billing.checkout.timeElapsed', 'Time elapsed:')}{' '}\n <span className=\"font-mono font-medium text-text-primary\">\n {formatTime(elapsedTime)}\n </span>\n </p>\n <p className=\"text-xs text-text-secondary mt-1\">\n {tr(\n 'billing.checkout.keepPageOpen',\n \"This may take a few moments. Please don't close this page.\"\n )}\n </p>\n </div>\n\n {/* Progress indicator */}\n <div className=\"w-full bg-bg-sunken rounded-full h-2 overflow-hidden\">\n <div\n className=\"bg-action-primary-bg h-2 rounded-full transition-all duration-1000\"\n style={{ width: `${Math.min((elapsedTime / 300000) * 100, 100)}%` }}\n />\n </div>\n\n {/* Cancel button - only show after 30 seconds */}\n {elapsedTime > 30000 && (\n <button\n type=\"button\"\n onClick={() => {\n setMode('error');\n setPaymentErrorMessage(\n 'Payment verification was cancelled. Please check your billing history.'\n );\n }}\n className=\"text-sm text-text-secondary hover:text-text-primary underline\"\n >\n {tr('billing.checkout.cancelAndCheckHistory', 'Cancel and check billing history')}\n </button>\n )}\n </div>\n </div>\n );\n }\n\n // Render payment form\n if (mode === 'payment' && paymentResponse) {\n // Format currency for display\n const formatDisplayCurrency = (amount: number, currencyCode: string) => {\n const symbols: Record<string, string> = { INR: '₹', USD: '$', EUR: '€', GBP: '£' };\n const symbol = symbols[currencyCode] || currencyCode + ' ';\n return `${symbol}${amount.toFixed(2)}`;\n };\n\n const displayCurrency = paymentResponse.currency || checkout.selectedCurrency;\n const displayTotal = paymentResponse.total ?? checkout.total;\n\n return (\n <div className=\"max-w-2xl mx-auto p-4 sm:p-6 space-y-6\">\n <button\n type=\"button\"\n onClick={() => setMode('select')}\n className=\"flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary\"\n >\n <ArrowLeft className=\"size-4\" />\n {tr('billing.checkout.backToCheckout', 'Back to checkout')}\n </button>\n\n <PageHeader\n title={tr('billing.checkout.completePayment', 'Complete Payment')}\n description={tr(\n 'billing.checkout.completePaymentDescription',\n 'Enter your payment details to complete the purchase'\n )}\n />\n\n {/* Payment Summary with correct tax calculation */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 space-y-4\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <Receipt className=\"size-4\" />\n {tr('billing.checkout.paymentSummary', 'Payment Summary')}\n </h3>\n\n {/* Billing Account Info */}\n {checkout.billingAccount && (\n <div className=\"p-3 bg-bg-sunken rounded-lg space-y-1\">\n <div className=\"text-xs text-text-secondary uppercase tracking-wide\">\n {tr('billing.checkout.billingAccount', 'Billing Account')}\n </div>\n <div className=\"font-medium text-text-primary\">{checkout.billingAccount.name}</div>\n <div className=\"text-sm text-text-secondary\">{checkout.billingAccount.email}</div>\n </div>\n )}\n\n {/* Subscription Info (for addon purchases) */}\n {purchaseType === 'ADDON' && checkout.selectedSubscriptionId && selectedSubscription && (\n <div className=\"p-3 bg-bg-sunken rounded-lg space-y-1\">\n <div className=\"text-xs text-text-secondary uppercase tracking-wide\">\n {tr('billing.checkout.addingToSubscription', 'Adding to Subscription')}\n </div>\n <div className=\"font-medium text-text-primary\">\n {selectedSubscription.plan?.name ||\n tr('billing.checkout.subscription', 'Subscription')}\n </div>\n {selectedSubscription.plan?.product?.name && (\n <div className=\"text-sm text-text-secondary\">\n {selectedSubscription.plan.product.name}\n </div>\n )}\n </div>\n )}\n\n {/* Price Breakdown */}\n <div className=\"space-y-2 text-sm\">\n {/* Calculate display values correctly */}\n {(() => {\n const taxRate = checkout.taxRate;\n // For non-USD: calculate subtotal and tax from the total\n const calcSubtotal = taxRate > 0 ? displayTotal / (1 + taxRate) : displayTotal;\n const calcTax = taxRate > 0 ? displayTotal - calcSubtotal : 0;\n\n return (\n <>\n {/* Subtotal */}\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">\n {tr('billing.checkout.subtotal', 'Subtotal')}\n </span>\n <span className=\"text-text-primary\">\n {formatDisplayCurrency(calcSubtotal, displayCurrency)}\n </span>\n </div>\n {/* Discount */}\n {checkout.discountAmount > 0 && (\n <div className=\"flex justify-between text-status-success-text\">\n <span>{tr('billing.checkout.discount', 'Discount')}</span>\n <span>\n -{formatDisplayCurrency(checkout.discountAmount, displayCurrency)}\n </span>\n </div>\n )}\n {/* Tax */}\n {calcTax > 0 && (\n <div className=\"flex justify-between\">\n <span className=\"text-text-secondary\">\n {checkout.taxType || tr('billing.checkout.tax', 'Tax')} (\n {(taxRate * 100).toFixed(0)}%)\n </span>\n <span className=\"text-text-primary\">\n {formatDisplayCurrency(calcTax, displayCurrency)}\n </span>\n </div>\n )}\n </>\n );\n })()}\n {/* Divider */}\n <div className=\"border-t border-border-subtle my-2\" />\n {/* Total */}\n <div className=\"flex justify-between font-semibold\">\n <span className=\"text-text-primary\">{tr('billing.checkout.total', 'Total')}</span>\n <span className=\"text-lg text-text-primary\">\n {formatDisplayCurrency(displayTotal, displayCurrency)}\n </span>\n </div>\n </div>\n </div>\n\n {checkout.selectedProvider === 'STRIPE' &&\n paymentResponse.clientSecret &&\n (() => {\n // NOTE: Saved cards for Stripe are temporarily disabled on the backend\n // NOTE: Card saving disabled — re-enable when backend supports setup_future_usage on PaymentIntents\n // const stripeCards = savedPaymentMethods.filter(pm => pm.stripePaymentMethodId && pm.isVerified);\n\n return (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface overflow-hidden\">\n {/* Stripe Elements form for new card */}\n <div className=\"p-4 space-y-4\">\n {/* Save card checkbox - disabled until backend supports it */}\n {/* <label className=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={checkout.saveCard}\n onChange={(e) => checkout.setSaveCard(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <span className=\"text-sm text-text-secondary\">\n Save card for future purchases\n </span>\n </label> */}\n\n <StripePaymentForm\n clientSecret={paymentResponse.clientSecret}\n publishableKey={\n stripePublishableKey || providerConfig?.stripePublishableKey || ''\n }\n amount={Math.round((paymentResponse.total ?? checkout.total) * 100)}\n currency={paymentResponse.currency || checkout.selectedCurrency}\n onSuccess={handlePaymentSuccess}\n onError={handlePaymentError}\n onCancel={() => setMode('select')}\n isCompletingExistingPayment={!!checkout.selectedPaymentMethodId}\n />\n </div>\n </div>\n );\n })()}\n\n {checkout.selectedProvider === 'RAZORPAY' && paymentResponse.orderId && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface overflow-hidden\">\n {/* Razorpay Payment Section */}\n <div className=\"p-4 space-y-4\">\n {/* Save card checkbox for new card payments */}\n <label className=\"flex items-center gap-2 cursor-pointer\">\n <input\n type=\"checkbox\"\n checked={checkout.saveCard}\n onChange={(e) => checkout.setSaveCard(e.target.checked)}\n className=\"size-4 rounded border-border-subtle text-text-link focus:ring-[var(--color-focus-ring)]\"\n />\n <span className=\"text-sm text-text-secondary\">Save card for future purchases</span>\n </label>\n\n <RazorpayPaymentButton\n orderId={paymentResponse.orderId}\n keyId={paymentResponse.razorpayKeyId || razorpayKey || ''}\n amount={Math.round((paymentResponse.total ?? checkout.total) * 100)}\n currency={paymentResponse.currency || 'INR'}\n customerId={paymentResponse.razorpayCustomerId}\n prefill={{\n name: paymentResponse.prefillName,\n email: paymentResponse.prefillEmail,\n contact: paymentResponse.prefillContact,\n }}\n onSuccess={(response) => handlePaymentSuccess(response.razorpay_payment_id)}\n onError={handlePaymentError}\n onCancel={() => setMode('select')}\n />\n </div>\n </div>\n )}\n </div>\n );\n }\n\n // Main checkout flow\n return (\n <div className=\"max-w-6xl mx-auto p-4 sm:p-6\">\n {/* Back button */}\n <button\n type=\"button\"\n onClick={() => navigateTo(-1)}\n className=\"flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary mb-4\"\n >\n <ArrowLeft className=\"size-4\" />\n Back\n </button>\n\n {/* Header */}\n <div className=\"flex items-center gap-3 mb-6\">\n <div className=\"p-2 rounded-lg bg-[var(--color-accent-soft)] text-text-link\">\n {getPageIcon()}\n </div>\n <div>\n <h1 className=\"text-2xl font-bold text-text-primary\">{getPageTitle()}</h1>\n <p className=\"text-sm text-text-secondary\">Complete your purchase securely</p>\n </div>\n </div>\n\n <PagePurpose className=\"mb-6\">\n {tr(\n 'billing.checkout.purpose',\n 'Review what you are buying — plan, add-ons or credits — pick the billing account and payment method, apply any discount code, and pay. Taxes and the final total are shown before you confirm.'\n )}\n </PagePurpose>\n\n {/* Upgrade Banner */}\n {locationState?.upgradeFromSubscriptionId && locationState?.currentPlanName && (\n <div className=\"mb-6 p-4 rounded-lg border border-border-subtle bg-status-info-bg-subtle\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-full bg-status-info-bg-subtle\">\n <RefreshCw className=\"size-4 text-accent-blue\" />\n </div>\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-info-text\">\n Upgrading from:{' '}\n <span className=\"font-semibold\">{locationState.currentPlanName}</span>\n </p>\n <p className=\"text-xs text-status-info-text mt-0.5\">\n Your current subscription will be marked as upgraded and quotas will transfer to the\n new plan.\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Downgrade Banner */}\n {locationState?.downgradeFromSubscriptionId && locationState?.currentPlanName && (\n <div className=\"mb-6 p-4 rounded-lg border border-status-warning-border bg-status-warning-bg-subtle\">\n <div className=\"flex items-center gap-3\">\n <div className=\"p-2 rounded-full bg-status-warning-bg-subtle\">\n <RefreshCw className=\"size-4 text-status-warning-text\" />\n </div>\n <div className=\"flex-1\">\n <p className=\"text-sm font-medium text-status-warning-text\">\n Downgrading from:{' '}\n <span className=\"font-semibold\">{locationState.currentPlanName}</span>\n </p>\n <p className=\"text-xs text-status-warning-text mt-0.5\">\n Your current plan stays active until it expires, you keep all its features until\n then. After expiry, only the new plan applies.\n </p>\n </div>\n </div>\n </div>\n )}\n\n {/* Main content - responsive grid. On mobile the order summary appears first\n so users see the total before filling in payment details. */}\n <div className=\"grid gap-6 lg:grid-cols-5\">\n {/* Left column - Configuration (renders second on mobile) */}\n <div className=\"lg:col-span-3 space-y-6 order-2 lg:order-1\">\n {/* Credits amount (for credits purchase) */}\n {purchaseType === 'CREDITS' && checkout.items[0] && (\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6\">\n <CreditsAmountInput\n value={checkout.items[0].quantity}\n onChange={handleCreditsAmountChange}\n costPerCredit={costPerCredit}\n currency={checkout.selectedCurrency}\n disabled={creditCostLoading}\n />\n </div>\n )}\n\n {/* Billing Account Selection */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6 space-y-4\">\n <BillingAccountSelector\n accounts={billingAccounts}\n selectedAccountId={checkout.billingAccount?.id ?? null}\n onSelect={checkout.setBillingAccount}\n isLoading={accountsLoading}\n disabled={isBillingAccountLocked}\n disabledMessage={\n isBillingAccountLocked ? 'Billing account is locked for plan changes' : undefined\n }\n />\n\n {/* Subscription Selection - inline for addon purchases (only shows if multiple subs or no subs) */}\n {/* Skip subscription selection for store checkout - store items don't require an active subscription */}\n {purchaseType === 'ADDON' && !isStoreCheckout && checkout.billingAccount && (\n <SubscriptionSelector\n subscriptions={activeSubscriptions}\n selectedSubscriptionId={checkout.selectedSubscriptionId}\n onSelect={checkout.setSelectedSubscriptionId}\n isLoading={subscriptionsLoading}\n />\n )}\n </div>\n\n {/* Shipping — physical store orders only. Non-physical flows render\n nothing new here. */}\n {isPhysicalStoreOrder && (\n <StoreShippingSection\n storeOrderId={storeOrderId!}\n currency={checkout.selectedCurrency}\n onChange={setShipping}\n />\n )}\n\n {/* Unified Payment Method Selection */}\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface p-4 sm:p-6 space-y-4\">\n {/* canPayWithCredits below gates whether credit payment applies to\n this purchase at all (e.g. not already buying credits) —\n whether the balance is *sufficient* is the child's own\n concern: it already computes hasEnoughCredits internally and\n shows a disabled option with an \"Insufficient credits\"\n message rather than hiding the option outright. */}\n <UnifiedPaymentMethodSelector\n savedPaymentMethods={savedPaymentMethods}\n selectedPaymentMethodId={checkout.selectedPaymentMethodId}\n onSelectSavedCard={checkout.setSelectedPaymentMethodId}\n selectedProvider={checkout.selectedProvider}\n onSelectProvider={checkout.setPaymentProvider}\n stripeEnabled={stripeEnabled}\n razorpayEnabled={razorpayEnabled}\n canPayWithCredits={checkout.canPayWithCredits}\n availableCredits={checkout.availableCredits}\n totalAmount={checkout.subtotalUSD}\n billingCountry={billingCountry}\n saveCard={checkout.saveCard}\n onSaveCardChange={checkout.setSaveCard}\n autoRenewEnabled={purchaseType === 'PLAN' && checkout.autoRenew}\n isLoading={paymentMethodsLoading}\n />\n\n {/* Currency Info - Locked to Billing Account */}\n {checkout.selectedProvider !== 'CREDITS' && checkout.selectedProvider !== 'AUTO' && (\n <>\n <div className=\"border-t border-border-subtle\" />\n <div className=\"bg-bg-sunken p-4 rounded-button\">\n <div className=\"flex items-start gap-3\">\n <svg\n className=\"size-5 text-text-secondary mt-0.5\"\n fill=\"none\"\n stroke=\"currentColor\"\n viewBox=\"0 0 24 24\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M13 16h-1v-4h-1m1-4h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z\"\n />\n </svg>\n <div className=\"flex-1 text-sm\">\n <p className=\"font-medium text-text-primary mb-1\">\n Currency: {checkout.selectedCurrency}\n </p>\n <p className=\"text-text-secondary text-xs\">\n All prices are shown in{' '}\n <strong className=\"text-text-primary\">{checkout.selectedCurrency}</strong>{' '}\n based on your billing account settings.\n {checkout.selectedCurrency === 'INR' && ' GST will be applied at checkout.'}\n </p>\n </div>\n </div>\n </div>\n </>\n )}\n\n {/* Seat count selector — only for PER_SEAT plans */}\n {purchaseType === 'PLAN' && planData?.getPlan?.pricingModel === 'PER_SEAT' && (\n <div className=\"border-t border-border-subtle pt-4\">\n <div className=\"space-y-3\">\n <div>\n <label className=\"text-sm font-medium text-text-primary\">Number of seats</label>\n {planData.getPlan.minSeats && (\n <p className=\"text-xs text-text-secondary mt-0.5\">\n Minimum {planData.getPlan.minSeats} seat\n {planData.getPlan.minSeats !== 1 ? 's' : ''} required\n </p>\n )}\n </div>\n <div className=\"flex items-center gap-3\">\n <button\n type=\"button\"\n onClick={() =>\n setSeatCount((n) => Math.max(planData.getPlan?.minSeats ?? 1, n - 1))\n }\n disabled={seatCount <= (planData.getPlan.minSeats ?? 1)}\n className=\"size-9 rounded-button border border-border-subtle flex items-center justify-center hover:bg-action-ghost-bgHover disabled:opacity-40 disabled:cursor-not-allowed transition-colors text-text-primary\"\n >\n −\n </button>\n <input\n type=\"number\"\n value={seatCount}\n min={planData.getPlan.minSeats ?? 1}\n max={planData.getPlan.maxSeats ?? 999}\n onChange={(e) => {\n const v = parseInt(e.target.value, 10);\n if (!isNaN(v)) setSeatCount(Math.max(planData.getPlan?.minSeats ?? 1, v));\n }}\n className=\"w-20 h-9 text-center border border-border-subtle rounded-button bg-bg-surface text-text-primary text-sm focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n />\n <button\n type=\"button\"\n onClick={() =>\n setSeatCount((n) => Math.min(planData.getPlan?.maxSeats ?? 999, n + 1))\n }\n disabled={seatCount >= (planData.getPlan.maxSeats ?? 999)}\n className=\"size-9 rounded-button border border-border-subtle flex items-center justify-center hover:bg-action-ghost-bgHover disabled:opacity-40 disabled:cursor-not-allowed transition-colors text-text-primary\"\n >\n +\n </button>\n {planData.getPlan.pricePerSeat && (\n <span className=\"text-sm text-text-secondary\">\n ×{' '}\n {planData.getPlan.pricePerSeat.toLocaleString('en', {\n style: 'currency',\n currency: planData.getPlan.currency ?? 'USD',\n })}{' '}\n ={' '}\n {(planData.getPlan.pricePerSeat * seatCount).toLocaleString('en', {\n style: 'currency',\n currency: planData.getPlan.currency ?? 'USD',\n })}\n /mo\n </span>\n )}\n </div>\n </div>\n </div>\n )}\n </div>\n </div>\n\n {/* Right column - Order Summary (sticky on desktop, shown first on mobile) */}\n <div className=\"lg:col-span-2 order-1 lg:order-2\">\n <div className=\"lg:sticky lg:top-6 space-y-4\">\n <div className=\"rounded-lg border border-border-subtle bg-bg-surface\">\n <div className=\"p-4\">\n <OrderSummary\n items={checkout.items}\n subtotal={checkout.subtotal}\n discountAmount={checkout.discountAmount}\n taxAmount={checkout.taxAmount}\n taxRate={checkout.taxRate}\n taxType={checkout.taxType}\n total={checkout.total}\n currency={checkout.selectedCurrency}\n isCalculatingTax={checkout.isCalculatingTax}\n showInCredits={checkout.selectedProvider === 'CREDITS'}\n onUpdateQuantity={\n purchaseType !== 'PLAN' && purchaseType !== 'ORDER'\n ? checkout.updateItemQuantity\n : undefined\n }\n onRemoveItem={checkout.removeItem}\n isEditable={purchaseType !== 'PLAN' && purchaseType !== 'ORDER'}\n />\n </div>\n\n {/* Promo Code Input — not supported for credits purchases yet (BOFF-7192) */}\n {checkout.selectedProvider !== 'CREDITS' && (\n <div className=\"px-4 pb-4 border-t border-border-subtle pt-4\">\n <PromoCodeInput\n value={checkout.promoCode}\n onChange={checkout.setPromoCode}\n onValidate={async () => {\n const result = await checkout.validatePromoCode();\n return result;\n }}\n onClear={checkout.clearPromoCode}\n isApplied={checkout.promoDiscount > 0}\n discountValue={checkout.promoDiscount}\n discountType={checkout.promoDiscountType}\n isValidating={checkout.isProcessing}\n />\n </div>\n )}\n\n {/* Simple Auto-Pay Toggle - only for PLAN purchases */}\n {purchaseType === 'PLAN' &&\n checkout.selectedProvider !== 'CREDITS' &&\n checkout.selectedProvider !== 'AUTO' &&\n (() => {\n // Determine payment method display\n let paymentMethodDisplay = '';\n let hasPaymentMethod = false;\n let isNewCard = false;\n\n if (checkout.selectedPaymentMethodId) {\n // Saved card selected\n const selectedCard = savedPaymentMethods.find(\n (pm) => pm.id === checkout.selectedPaymentMethodId\n );\n if (selectedCard) {\n const type =\n selectedCard.type === 'CREDIT_CARD'\n ? 'Card'\n : selectedCard.type === 'DEBIT_CARD'\n ? 'Card'\n : selectedCard.type;\n const last4 = selectedCard.last4 ? ` •••• ${selectedCard.last4}` : '';\n paymentMethodDisplay = `${type}${last4}`;\n hasPaymentMethod = true;\n isNewCard = false;\n }\n } else if (\n checkout.selectedProvider === 'RAZORPAY' ||\n checkout.selectedProvider === 'STRIPE'\n ) {\n // New card selected\n paymentMethodDisplay =\n checkout.selectedProvider === 'RAZORPAY'\n ? 'new card (Razorpay)'\n : 'new card (Stripe)';\n hasPaymentMethod = true;\n isNewCard = true;\n }\n\n return (\n <div className=\"px-4 pb-4\">\n <SimpleAutoPayToggle\n enabled={checkout.autoRenew}\n onToggle={(enabled) => {\n checkout.setAutoRenew(enabled);\n // When enabling auto-pay with saved card, also set it as auto-renew method\n if (enabled && checkout.selectedPaymentMethodId) {\n checkout.setAutoRenewPaymentMethodId(checkout.selectedPaymentMethodId);\n }\n }}\n hasPaymentMethodSelected={hasPaymentMethod}\n selectedPaymentMethodDisplay={paymentMethodDisplay}\n isNewCard={isNewCard}\n />\n </div>\n );\n })()}\n </div>\n\n {/* Checkout Button */}\n <button\n type=\"button\"\n onClick={handleProceedToPayment}\n disabled={\n checkout.isProcessing ||\n !checkout.billingAccount ||\n checkout.items.length === 0 ||\n // For addon purchases: require active subscription to be selected (skip for store checkout)\n (purchaseType === 'ADDON' && !isStoreCheckout && subscriptionsLoading) ||\n (purchaseType === 'ADDON' &&\n !isStoreCheckout &&\n activeSubscriptions.length === 0) ||\n (purchaseType === 'ADDON' &&\n !isStoreCheckout &&\n !checkout.selectedSubscriptionId) ||\n // Physical store orders: require a delivery address + method\n // (with a resolved estimate) before payment.\n (isPhysicalStoreOrder &&\n (!shipping.shippingAddressId || !shipping.shippingMethodId))\n }\n className=\"w-full px-6 py-4 rounded-lg bg-action-primary-bg text-action-primary-text font-semibold\n hover:bg-action-primary-bgHover disabled:opacity-50 disabled:cursor-not-allowed\n transition-colors flex items-center justify-center gap-2\"\n >\n {checkout.isProcessing ? (\n <>\n <Loader2 className=\"size-5 animate-spin\" />\n Processing…\n </>\n ) : checkout.selectedProvider === 'CREDITS' ? (\n 'Pay with Credits'\n ) : (\n 'Proceed to Payment'\n )}\n </button>\n\n {/* Security badge */}\n <p className=\"text-center text-xs text-text-secondary\">\n 🔒 Secure checkout with 256-bit SSL encryption\n </p>\n </div>\n </div>\n </div>\n\n {/* Billing account completion gate */}\n {showCompleteAccountModal && checkout.billingAccount && (\n <CompleteBillingAccountModal\n billingAccountId={checkout.billingAccount.id}\n hasPhone={Boolean(checkout.billingAccount.contactNumber?.trim())}\n hasAddress={Boolean(\n checkout.billingAccount.billingAddresses?.[0]?.line1?.trim() &&\n checkout.billingAccount.billingAddresses?.[0]?.postalCode?.trim()\n )}\n onCompleted={() => {\n // Set bypass BEFORE closing modal so the re-triggered payment skips the stale check\n bypassBillingGate.current = true;\n setShowCompleteAccountModal(false);\n void handleProceedToPayment();\n // Refetch accounts in background so future gate checks use fresh contactNumber/address\n void refetchAccounts().then((result: { data?: GetBillingAccountsCheckoutQuery }) => {\n const freshAccount = (result.data?.myBillingAccounts ?? [])\n .map(normalizeCheckoutBillingAccount)\n .find((account) => account?.id === checkout.billingAccount?.id);\n if (freshAccount) checkout.setBillingAccount(freshAccount);\n });\n }}\n onCancel={() => setShowCompleteAccountModal(false)}\n />\n )}\n </div>\n );\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-billing",
3
- "version": "2026.910.9",
3
+ "version": "2026.911.1",
4
4
  "description": "Billing microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [