@burdenoff/microfe-billing 2026.910.8 → 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.
- package/dist/billing/modules/checkout/components/OrderSummary.js +1 -1
- package/dist/billing/modules/checkout/components/OrderSummary.js.map +1 -1
- package/dist/billing/modules/checkout/hooks/useCheckout.js +61 -62
- package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
- package/dist/billing/modules/checkout/pages/CheckoutPage.js +30 -30
- package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
- package/package.json +1 -1
|
@@ -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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"}
|
|
@@ -2,39 +2,37 @@ import { useBilling as e } from "../../../providers/BillingProvider.js";
|
|
|
2
2
|
import { useConvertCurrencyLazyQuery as t, useInitiatePaymentCheckoutMutation as n, useSpendCreditsCheckoutMutation as r, useValidateCouponLazyQuery as i } from "../../../../generated/global-operations.js";
|
|
3
3
|
import { useEnsureStoreOrderBillingOrder as a } from "./useStoreShipping.js";
|
|
4
4
|
import { useCallback as o, useEffect as s, useMemo as c, useState as l } from "react";
|
|
5
|
-
//#region src/billing/modules/checkout/hooks/useCheckout.ts
|
|
6
|
-
var ee = 100;
|
|
7
5
|
function u(u = {}) {
|
|
8
|
-
let { currentUser: d } = e(), { defaultCurrency: f = "USD", defaultSaveCard: p = !1, upgradeFromSubscriptionId: m, downgradeFromSubscriptionId: h, defaultSubscriptionId: g } = u, [_, v] = l([]), [y,
|
|
9
|
-
let e =
|
|
6
|
+
let { currentUser: d } = e(), { defaultCurrency: f = "USD", defaultSaveCard: p = !1, upgradeFromSubscriptionId: m, downgradeFromSubscriptionId: h, defaultSubscriptionId: g } = u, [_, v] = l([]), [y, ee] = l(null), [b, x] = l("AUTO"), [S, C] = l(f), [w, T] = l(""), [E, D] = l(0), [te, O] = l("PERCENTAGE"), [k, A] = l(!1), [j, M] = l(0), [N, P] = l("idle"), [ne, F] = l(null), [I, L] = l(0), [re, R] = l(0), [ie, z] = l("NONE"), [B, ae] = l(null), [V, oe] = l(p), [H, se] = l(!1), [U, ce] = l(null), [W, le] = l(g ?? null), [ue] = n(), [de] = a(), [fe] = r(), [G, { data: pe, loading: me }] = t(), [he] = i(), K = c(() => _[0]?.currency ?? "USD", [_]), q = c(() => _.reduce((e, t) => e + t.price * t.quantity, 0), [_]), J = c(() => _.reduce((e, t) => e + (t.priceUSD ?? t.price) * t.quantity, 0), [_]), Y = c(() => E ? Math.min(E, q) : 0, [q, E]), X = c(() => {
|
|
7
|
+
let e = q - Y;
|
|
10
8
|
return (k ? Math.max(0, e - j) : e) + I;
|
|
11
9
|
}, [
|
|
12
|
-
|
|
13
|
-
|
|
10
|
+
q,
|
|
11
|
+
Y,
|
|
14
12
|
k,
|
|
15
13
|
j,
|
|
16
14
|
I
|
|
17
|
-
]),
|
|
15
|
+
]), Z = y?.creditAmount ?? 0, Q = c(() => J * 100, [J]), ge = c(() => Z >= Q, [Z, Q]), _e = c(() => !_.some((e) => e.type === "CREDITS") && Z > 0, [_, Z]), ve = pe?.convertCurrency?.amount ?? null, ye = pe?.convertCurrency?.exchangeRate ?? 1, be = ve ?? X, xe = N === "loading" || me, Se = N === "processing";
|
|
18
16
|
s(() => {
|
|
19
|
-
if (b !== "CREDITS" && _.length !== 0 && !_.every((e) => e.currency === S || e.type === "CREDITS") &&
|
|
20
|
-
if (S === "INR" && I === 0 &&
|
|
21
|
-
|
|
22
|
-
amount:
|
|
17
|
+
if (b !== "CREDITS" && _.length !== 0 && !_.every((e) => e.currency === S || e.type === "CREDITS") && X > 0 && K !== S) {
|
|
18
|
+
if (S === "INR" && I === 0 && q > 0) return;
|
|
19
|
+
G({ variables: {
|
|
20
|
+
amount: X,
|
|
23
21
|
toCurrency: S,
|
|
24
|
-
fromCurrency:
|
|
22
|
+
fromCurrency: K
|
|
25
23
|
} });
|
|
26
24
|
}
|
|
27
25
|
}, [
|
|
28
|
-
|
|
26
|
+
X,
|
|
29
27
|
S,
|
|
30
28
|
b,
|
|
31
|
-
|
|
29
|
+
G,
|
|
32
30
|
I,
|
|
33
|
-
|
|
31
|
+
q,
|
|
34
32
|
_,
|
|
35
|
-
|
|
33
|
+
K
|
|
36
34
|
]), s(() => {
|
|
37
|
-
if (!y ||
|
|
35
|
+
if (!y || q <= 0) {
|
|
38
36
|
L(0), R(0), z("NONE");
|
|
39
37
|
return;
|
|
40
38
|
}
|
|
@@ -46,12 +44,12 @@ function u(u = {}) {
|
|
|
46
44
|
L(0), R(0), z("NONE");
|
|
47
45
|
return;
|
|
48
46
|
}
|
|
49
|
-
let e =
|
|
47
|
+
let e = q - Y, t = .18;
|
|
50
48
|
L(e * t), R(t), z("GST");
|
|
51
49
|
}, [
|
|
52
50
|
y,
|
|
53
|
-
|
|
54
|
-
|
|
51
|
+
q,
|
|
52
|
+
Y,
|
|
55
53
|
S,
|
|
56
54
|
b
|
|
57
55
|
]);
|
|
@@ -92,20 +90,20 @@ function u(u = {}) {
|
|
|
92
90
|
}, []), Ee = o(() => {
|
|
93
91
|
v([]);
|
|
94
92
|
}, []), De = o((e) => {
|
|
95
|
-
|
|
93
|
+
ee(e), A(!1), M(0), x((e) => e === "CREDITS" ? "AUTO" : e);
|
|
96
94
|
}, []), Oe = o((e) => {
|
|
97
95
|
C(e);
|
|
98
96
|
let t = _.every((t) => t.currency === e || t.type === "CREDITS");
|
|
99
|
-
|
|
100
|
-
amount:
|
|
97
|
+
X > 0 && K !== e && !t && G({ variables: {
|
|
98
|
+
amount: X,
|
|
101
99
|
toCurrency: e,
|
|
102
|
-
fromCurrency:
|
|
100
|
+
fromCurrency: K
|
|
103
101
|
} });
|
|
104
102
|
}, [
|
|
105
|
-
|
|
106
|
-
|
|
103
|
+
X,
|
|
104
|
+
G,
|
|
107
105
|
_,
|
|
108
|
-
|
|
106
|
+
K
|
|
109
107
|
]);
|
|
110
108
|
return {
|
|
111
109
|
items: _,
|
|
@@ -114,25 +112,26 @@ function u(u = {}) {
|
|
|
114
112
|
selectedCurrency: S,
|
|
115
113
|
promoCode: w,
|
|
116
114
|
promoDiscount: E,
|
|
117
|
-
promoDiscountType:
|
|
115
|
+
promoDiscountType: te,
|
|
118
116
|
useCredits: k,
|
|
119
117
|
creditsToUse: j,
|
|
120
118
|
checkoutState: N,
|
|
121
|
-
error:
|
|
119
|
+
error: ne,
|
|
122
120
|
selectedPaymentMethodId: B,
|
|
123
|
-
saveCard:
|
|
124
|
-
autoRenew:
|
|
125
|
-
autoRenewPaymentMethodId:
|
|
126
|
-
selectedSubscriptionId:
|
|
127
|
-
subtotal:
|
|
128
|
-
subtotalUSD:
|
|
129
|
-
discountAmount:
|
|
121
|
+
saveCard: V,
|
|
122
|
+
autoRenew: H,
|
|
123
|
+
autoRenewPaymentMethodId: U,
|
|
124
|
+
selectedSubscriptionId: W,
|
|
125
|
+
subtotal: q,
|
|
126
|
+
subtotalUSD: J,
|
|
127
|
+
discountAmount: Y,
|
|
130
128
|
taxAmount: I,
|
|
131
|
-
taxRate:
|
|
132
|
-
taxType:
|
|
133
|
-
total:
|
|
129
|
+
taxRate: re,
|
|
130
|
+
taxType: ie,
|
|
131
|
+
total: X,
|
|
134
132
|
totalInSelectedCurrency: be,
|
|
135
|
-
availableCredits:
|
|
133
|
+
availableCredits: Z,
|
|
134
|
+
creditsRequired: Q,
|
|
136
135
|
hasEnoughCredits: ge,
|
|
137
136
|
canPayWithCredits: _e,
|
|
138
137
|
isLoading: xe,
|
|
@@ -147,7 +146,7 @@ function u(u = {}) {
|
|
|
147
146
|
clearItems: Ee,
|
|
148
147
|
setBillingAccount: De,
|
|
149
148
|
setPaymentProvider: o((e) => {
|
|
150
|
-
x(e), e === "RAZORPAY" && S !== "INR" && C("INR");
|
|
149
|
+
x(e), e === "RAZORPAY" && S !== "INR" && C("INR"), e === "CREDITS" && (T(""), D(0), O("PERCENTAGE"));
|
|
151
150
|
}, [S]),
|
|
152
151
|
setCurrency: Oe,
|
|
153
152
|
setPromoCode: T,
|
|
@@ -165,7 +164,7 @@ function u(u = {}) {
|
|
|
165
164
|
code: w.trim().toUpperCase(),
|
|
166
165
|
userId: d?.id ?? "",
|
|
167
166
|
userEmail: d?.email,
|
|
168
|
-
amount:
|
|
167
|
+
amount: q,
|
|
169
168
|
currency: S
|
|
170
169
|
} } });
|
|
171
170
|
if (t || !e?.validateCoupon) return D(0), P("idle"), {
|
|
@@ -212,7 +211,7 @@ function u(u = {}) {
|
|
|
212
211
|
d?.id,
|
|
213
212
|
w,
|
|
214
213
|
S,
|
|
215
|
-
|
|
214
|
+
q,
|
|
216
215
|
he
|
|
217
216
|
]),
|
|
218
217
|
clearPromoCode: o(() => {
|
|
@@ -220,14 +219,14 @@ function u(u = {}) {
|
|
|
220
219
|
}, []),
|
|
221
220
|
setUseCredits: A,
|
|
222
221
|
setCreditsToUse: o((e) => {
|
|
223
|
-
let t = Math.min(e,
|
|
222
|
+
let t = Math.min(e, Z, q - Y);
|
|
224
223
|
M(Math.max(0, t));
|
|
225
224
|
}, [
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
225
|
+
Z,
|
|
226
|
+
q,
|
|
227
|
+
Y
|
|
229
228
|
]),
|
|
230
|
-
setSelectedPaymentMethodId:
|
|
229
|
+
setSelectedPaymentMethodId: ae,
|
|
231
230
|
setSaveCard: oe,
|
|
232
231
|
setAutoRenew: se,
|
|
233
232
|
setAutoRenewPaymentMethodId: ce,
|
|
@@ -261,7 +260,7 @@ function u(u = {}) {
|
|
|
261
260
|
P("processing"), F(null);
|
|
262
261
|
let e = _[0], t = e.type, n = _.find((e) => e.metadata?.storeOrderId)?.metadata?.storeOrderId, r = _.find((e) => e.metadata?.billingOrderId)?.metadata?.billingOrderId || void 0;
|
|
263
262
|
n && (t = "ORDER");
|
|
264
|
-
let i =
|
|
263
|
+
let i = q - Y, a = b;
|
|
265
264
|
if (a === "AUTO" && (a = S === "INR" ? "RAZORPAY" : "STRIPE"), t === "ORDER" && !r && n && y) {
|
|
266
265
|
let { data: e } = await de({ variables: {
|
|
267
266
|
storeOrderId: n,
|
|
@@ -296,7 +295,7 @@ function u(u = {}) {
|
|
|
296
295
|
isAnnual: t?.metadata?.duration === "yearly",
|
|
297
296
|
upgradeFromSubscriptionId: m || void 0,
|
|
298
297
|
downgradeFromSubscriptionId: h || void 0,
|
|
299
|
-
subscriptionId: e.length > 0 ?
|
|
298
|
+
subscriptionId: e.length > 0 ? W : void 0,
|
|
300
299
|
seatCount: t?.metadata?.seatCount
|
|
301
300
|
} } }), r = n?.spendCredits;
|
|
302
301
|
return r?.creditTransactionID ? (P("success"), {
|
|
@@ -323,7 +322,7 @@ function u(u = {}) {
|
|
|
323
322
|
error: "Failed to process credit payment"
|
|
324
323
|
});
|
|
325
324
|
}
|
|
326
|
-
let o = _.filter((e) => e.type === "ADDON"), s = o.length > 0 ? o.map((e) => e.metadata?.addonId || e.id) : void 0, c = o.length > 0 ? o.map((e) => e.quantity) : void 0, l = _.find((e) => e.type === "PLAN")?.metadata?.duration === "yearly",
|
|
325
|
+
let o = _.filter((e) => e.type === "ADDON"), s = o.length > 0 ? o.map((e) => e.metadata?.addonId || e.id) : void 0, c = o.length > 0 ? o.map((e) => e.quantity) : void 0, l = _.find((e) => e.type === "PLAN")?.metadata?.duration === "yearly", u = t === "PLAN" && H && (U || B) || void 0, { data: d } = await ue({ variables: { input: {
|
|
327
326
|
billingAccountId: y.id,
|
|
328
327
|
amount: i,
|
|
329
328
|
currency: S,
|
|
@@ -336,17 +335,17 @@ function u(u = {}) {
|
|
|
336
335
|
quantity: t === "ADDON" && o.length === 1 && !r ? o[0].quantity : void 0,
|
|
337
336
|
addonIds: t === "ADDON" && o.length > 1 && !r ? s : void 0,
|
|
338
337
|
quantities: t === "ADDON" && o.length > 1 && !r ? c : void 0,
|
|
339
|
-
subscriptionId: t === "ADDON" && !r ?
|
|
338
|
+
subscriptionId: t === "ADDON" && !r ? W : void 0,
|
|
340
339
|
paymentMethodId: B || void 0,
|
|
341
|
-
saveCard: B ? void 0 :
|
|
340
|
+
saveCard: B ? void 0 : V || H,
|
|
342
341
|
upgradeFromSubscriptionId: m || void 0,
|
|
343
342
|
downgradeFromSubscriptionId: h || void 0,
|
|
344
343
|
isAnnual: l || void 0,
|
|
345
344
|
promoCode: w || void 0,
|
|
346
|
-
autoRenew: t === "PLAN" &&
|
|
347
|
-
autoRenewPaymentMethodId:
|
|
348
|
-
} } }),
|
|
349
|
-
return
|
|
345
|
+
autoRenew: t === "PLAN" && H ? !0 : void 0,
|
|
346
|
+
autoRenewPaymentMethodId: u
|
|
347
|
+
} } }), f = d?.initiatePayment;
|
|
348
|
+
return f?.success ? (P("success"), f) : (P("error"), F(f?.error ?? "Payment initiation failed"), {
|
|
350
349
|
success: !1,
|
|
351
350
|
provider: a,
|
|
352
351
|
subtotal: i,
|
|
@@ -356,7 +355,7 @@ function u(u = {}) {
|
|
|
356
355
|
taxType: "NONE",
|
|
357
356
|
jurisdiction: "UNKNOWN",
|
|
358
357
|
currency: S,
|
|
359
|
-
error:
|
|
358
|
+
error: f?.error ?? "Payment initiation failed"
|
|
360
359
|
});
|
|
361
360
|
} catch (e) {
|
|
362
361
|
let t = e instanceof Error ? e.message : "An error occurred";
|
|
@@ -378,14 +377,14 @@ function u(u = {}) {
|
|
|
378
377
|
_,
|
|
379
378
|
S,
|
|
380
379
|
b,
|
|
381
|
-
|
|
382
|
-
|
|
380
|
+
q,
|
|
381
|
+
Y,
|
|
383
382
|
w,
|
|
384
383
|
B,
|
|
384
|
+
V,
|
|
385
385
|
H,
|
|
386
386
|
U,
|
|
387
387
|
W,
|
|
388
|
-
G,
|
|
389
388
|
m,
|
|
390
389
|
h,
|
|
391
390
|
ue,
|
|
@@ -393,7 +392,7 @@ function u(u = {}) {
|
|
|
393
392
|
fe
|
|
394
393
|
]),
|
|
395
394
|
reset: o(() => {
|
|
396
|
-
v([]),
|
|
395
|
+
v([]), ee(null), x("AUTO"), C(f), T(""), D(0), O("PERCENTAGE"), A(!1), M(0), P("idle"), F(null), L(0), R(0), z("NONE"), ae(null), oe(p), se(!1), ce(null), le(g ?? null);
|
|
397
396
|
}, [
|
|
398
397
|
f,
|
|
399
398
|
p,
|
|
@@ -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 */\nconst 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 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 const hasEnoughCredits = useMemo(() => {\n // Use subtotalUSD so this works correctly for any display currency\n const creditsRequired = subtotalUSD * CREDITS_PER_USD;\n return availableCredits >= creditsRequired;\n }, [availableCredits, subtotalUSD]);\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 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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"}
|