@burdenoff/microfe-billing 2026.731.1 → 2026.802.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.
Files changed (50) hide show
  1. package/dist/billing/BillingAdminRoutes.js +1 -1
  2. package/dist/billing/BillingAdminRoutes.js.map +1 -1
  3. package/dist/billing/BillingUserRoutes.js +1 -1
  4. package/dist/billing/BillingUserRoutes.js.map +1 -1
  5. package/dist/billing/hooks/useBillingAccountSelection.js +1 -1
  6. package/dist/billing/hooks/useBillingAccountSelection.js.map +1 -1
  7. package/dist/billing/hooks/useBillingNavigate.js +1 -1
  8. package/dist/billing/hooks/useBillingNavigate.js.map +1 -1
  9. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js +1 -1
  10. package/dist/billing/modules/addons/pages/AddonBrowseDetailPage.js.map +1 -1
  11. package/dist/billing/modules/addons/pages/AddonDetailPage.js +1 -1
  12. package/dist/billing/modules/addons/pages/AddonDetailPage.js.map +1 -1
  13. package/dist/billing/modules/addons/pages/AddonEditPage.js +1 -1
  14. package/dist/billing/modules/addons/pages/AddonEditPage.js.map +1 -1
  15. package/dist/billing/modules/addons/pages/AddonsBrowsePage.js.map +1 -1
  16. package/dist/billing/modules/billing/hooks/useRefunds.js.map +1 -1
  17. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js +1 -1
  18. package/dist/billing/modules/billing/pages/InvoiceDetailPage.js.map +1 -1
  19. package/dist/billing/modules/billing/pages/InvoicesListPage.js +1 -1
  20. package/dist/billing/modules/billing/pages/InvoicesListPage.js.map +1 -1
  21. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js +1 -1
  22. package/dist/billing/modules/billing/pages/PaymentMethodsPage.js.map +1 -1
  23. package/dist/billing/modules/billing/pages/TransactionsPage.js +1 -1
  24. package/dist/billing/modules/billing/pages/TransactionsPage.js.map +1 -1
  25. package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
  26. package/dist/billing/modules/checkout/pages/CheckoutPage.js +1 -1
  27. package/dist/billing/modules/checkout/pages/CheckoutPage.js.map +1 -1
  28. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js +1 -1
  29. package/dist/billing/modules/credits/pages/CreditTransactionsPage.js.map +1 -1
  30. package/dist/billing/modules/earnings/pages/EarningsPage.js +1 -1
  31. package/dist/billing/modules/earnings/pages/EarningsPage.js.map +1 -1
  32. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js +1 -1
  33. package/dist/billing/modules/plans/pages/PlanBrowseDetailPage.js.map +1 -1
  34. package/dist/billing/modules/plans/pages/PlanDetailPage.js +1 -1
  35. package/dist/billing/modules/plans/pages/PlanDetailPage.js.map +1 -1
  36. package/dist/billing/modules/plans/pages/PlanEditPage.js +1 -1
  37. package/dist/billing/modules/plans/pages/PlanEditPage.js.map +1 -1
  38. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js +1 -1
  39. package/dist/billing/modules/quota/pages/QuotaTemplateDetailPage.js.map +1 -1
  40. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js +1 -1
  41. package/dist/billing/modules/quota/pages/QuotaTemplateEditPage.js.map +1 -1
  42. package/dist/billing/modules/store/pages/StoreCheckoutPage.js +1 -1
  43. package/dist/billing/modules/store/pages/StoreCheckoutPage.js.map +1 -1
  44. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js +1 -1
  45. package/dist/billing/modules/subscriptions/pages/SubscriptionDetailPage.js.map +1 -1
  46. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js +1 -1
  47. package/dist/billing/modules/subscriptions/pages/UpgradeSubscriptionPage.js.map +1 -1
  48. package/dist/billing/providers/BillingProvider.js +1 -1
  49. package/dist/billing/providers/BillingProvider.js.map +1 -1
  50. package/package.json +3 -3
@@ -1 +1 @@
1
- {"version":3,"file":"TransactionsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/TransactionsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Transactions Page\n * Displays all billing transactions for a specific billing account\n */\n\nimport { useState, useRef, useEffect, type FC } from 'react';\nimport { useParams } from 'react-router-dom';\nimport {\n ArrowLeft,\n MoreHorizontal,\n Eye,\n FileText,\n CreditCard,\n Mail,\n Wallet,\n ChevronLeft,\n ChevronRight,\n Calendar,\n CheckCircle,\n XCircle,\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useTransactions } from '../hooks';\nimport { nativeImpact, nativeOpenUrl } from '../../../../utils/nativeBridge';\nimport { AccessDenied, PageHeader, EmptyState, ServerError } from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport { getTransactionStatusColor, getStatusBadgeClasses } from '../../../shared/utils/status';\nimport type { Transaction, TransactionType } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ResponsiveTable,\n type ResponsiveTableColumn,\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n IllustratedEmptyState,\n PagePurpose,\n EmphasisPanel,\n} from '@burdenoff/fe-libs/ui';\n\ntype FilterType = 'all' | TransactionType;\n\nconst TYPE_LABELS: Record<TransactionType, string> = {\n payment: 'Payment',\n refund: 'Refund',\n adjustment: 'Adjustment',\n};\n\n// Helper to get label for transaction type (case-insensitive)\nconst getTypeLabel = (type: string): string => {\n const normalized = type.toLowerCase() as TransactionType;\n return TYPE_LABELS[normalized] ?? type;\n};\n\n// Transaction Actions Dropdown Component\ninterface TransactionActionsDropdownProps {\n transaction: Transaction;\n onViewDetails: () => void;\n onViewInvoice: () => void;\n hasInvoiceUrl: boolean;\n}\n\nconst TransactionActionsDropdown: FC<TransactionActionsDropdownProps> = ({\n transaction,\n onViewDetails,\n onViewInvoice,\n hasInvoiceUrl,\n}) => {\n const [isOpen, setIsOpen] = useState(false);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, []);\n\n return (\n <div className=\"relative overflow-visible\" ref={dropdownRef}>\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n className=\"p-1.5 rounded-md hover:bg-bg-sunken/50 transition-colors\"\n aria-label=\"Transaction actions\"\n >\n <MoreHorizontal className=\"size-4 text-text-secondary\" />\n </button>\n\n {isOpen && (\n <div className=\"absolute right-0 top-full mt-1 z-50 min-w-[160px] bg-popover border border-border-subtle rounded-md shadow-lg py-1\">\n <button\n type=\"button\"\n onClick={() => {\n onViewDetails();\n setIsOpen(false);\n }}\n className=\"w-full flex items-center gap-2 px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken/50 transition-colors whitespace-nowrap\"\n >\n <Eye className=\"size-4\" />\n View Details\n </button>\n {transaction.type === 'payment' && hasInvoiceUrl && (\n <button\n type=\"button\"\n onClick={() => {\n onViewInvoice();\n setIsOpen(false);\n }}\n className=\"w-full flex items-center gap-2 px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken/50 transition-colors whitespace-nowrap\"\n >\n <FileText className=\"size-4\" />\n View Invoice\n </button>\n )}\n </div>\n )}\n </div>\n );\n};\n\n// Transaction Details Modal Component\ninterface TransactionDetailsModalProps {\n transaction: Transaction | null;\n onClose: () => void;\n onViewInvoice?: (transaction: Transaction) => void;\n invoiceUrl?: string | null;\n invoiceProvider?: string | null;\n}\n\nconst TransactionDetailsModal: FC<TransactionDetailsModalProps> = ({\n transaction,\n onClose,\n onViewInvoice,\n invoiceUrl,\n invoiceProvider,\n}) => {\n if (!transaction) return null;\n\n // Extract tax information from metadata\n const metadata = transaction.metadata as Record<string, unknown> | null;\n const subtotal = metadata?.subtotal as number | undefined;\n const taxAmount = metadata?.taxAmount as number | undefined;\n const taxRate = metadata?.taxRate as number | undefined;\n const taxType = metadata?.taxType as string | undefined;\n const total = metadata?.total as number | undefined;\n const invoiceNumber = metadata?.invoiceNumber as string | undefined;\n const invoiceStatus = metadata?.invoiceStatus as string | undefined;\n\n // Extract refund-specific information\n const isRefund = transaction.type === 'refund';\n const refundRecordId = metadata?.refundRecordId as string | undefined;\n const originalTransactionId = metadata?.originalTransactionId as string | undefined;\n const creditNoteId = metadata?.creditNoteId as string | undefined;\n const creditNoteNumber = metadata?.creditNoteNumber as string | undefined;\n const creditNotePdfUrl = metadata?.creditNotePdfUrl as string | undefined;\n const refundReason = metadata?.reason as string | undefined;\n const purchaseType = metadata?.purchaseType as string | undefined;\n const purchaseDetail = metadata?.purchaseDetail as string | undefined;\n\n // Determine if we should show tax breakdown\n const showTaxBreakdown = total && taxAmount !== undefined && taxAmount > 0;\n\n return (\n <Dialog open={!!transaction} onOpenChange={(open) => !open && onClose()}>\n <DialogContent className=\"max-w-md max-h-[90vh] overflow-auto p-0\">\n <DialogHeader className=\"p-4 border-b border-border-subtle\">\n <DialogTitle className=\"text-lg font-semibold\">Transaction Details</DialogTitle>\n </DialogHeader>\n\n <div className=\"p-4 space-y-4\">\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Transaction ID</p>\n <p className=\"text-sm font-mono break-all\">{transaction.id}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Type</p>\n <p className=\"text-sm capitalize\">{getTypeLabel(transaction.type)}</p>\n </div>\n </div>\n\n {/* Amount Section with Tax Breakdown */}\n <div className=\"space-y-2\">\n {showTaxBreakdown ? (\n <>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">Subtotal</span>\n <span>\n {formatCurrency(subtotal || transaction.amount, transaction.currency)}\n </span>\n </div>\n {taxAmount > 0 && (\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {taxType || 'Tax'} {taxRate ? `(${(taxRate * 100).toFixed(0)}%)` : ''}\n </span>\n <span>{formatCurrency(taxAmount, transaction.currency)}</span>\n </div>\n )}\n <div className=\"flex justify-between text-sm font-semibold border-t border-border-subtle pt-2\">\n <span>Total</span>\n <span className={transaction.type === 'refund' ? 'text-status-error-text' : ''}>\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(total, transaction.currency)}\n </span>\n </div>\n </>\n ) : (\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Amount</p>\n <p\n className={`text-sm font-medium ${transaction.type === 'refund' ? 'text-status-error-text' : ''}`}\n >\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(total || transaction.amount, transaction.currency)}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Status</p>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(\n getTransactionStatusColor(transaction.status)\n )}`}\n >\n {transaction.status}\n </span>\n </div>\n </div>\n )}\n </div>\n\n {/* Status (if tax breakdown shown) */}\n {showTaxBreakdown && (\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Status</p>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(\n getTransactionStatusColor(transaction.status)\n )}`}\n >\n {transaction.status}\n </span>\n </div>\n )}\n\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Date & Time</p>\n <p className=\"text-sm\">{formatDateTime(transaction.createdAt)}</p>\n </div>\n\n {transaction.description && (\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Description</p>\n <p className=\"text-sm\">{transaction.description}</p>\n </div>\n )}\n\n {/* Invoice Information */}\n {invoiceNumber && (\n <div className=\"p-3 bg-bg-sunken/50 rounded-lg space-y-2\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Invoice</p>\n <p className=\"text-sm font-mono\">{invoiceNumber}</p>\n </div>\n {invoiceStatus && (\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${\n invoiceStatus === 'PAID'\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-status-warning-bg-subtle text-status-warning-text'\n }`}\n >\n {invoiceStatus}\n </span>\n )}\n </div>\n {invoiceProvider && (\n <p className=\"text-xs text-text-secondary\">Provider: {invoiceProvider}</p>\n )}\n {invoiceUrl && onViewInvoice && (\n <button\n type=\"button\"\n onClick={() => onViewInvoice(transaction)}\n className=\"flex items-center gap-2 text-sm text-primary hover:underline\"\n >\n <FileText className=\"size-4\" />\n View Invoice\n </button>\n )}\n </div>\n )}\n\n {/* Fallback invoice button if no invoice number but URL exists */}\n {!invoiceNumber && invoiceUrl && onViewInvoice && (\n <div className=\"p-3 bg-bg-sunken/50 rounded-lg\">\n <button\n type=\"button\"\n onClick={() => onViewInvoice(transaction)}\n className=\"flex items-center gap-2 text-sm text-primary hover:underline\"\n >\n <FileText className=\"size-4\" />\n View Invoice {invoiceProvider && `(${invoiceProvider})`}\n </button>\n </div>\n )}\n\n {/* Refund-Specific Information */}\n {isRefund && (creditNoteId || refundReason || purchaseType) && (\n <div className=\"p-3 bg-status-error-bg-subtle/20 border border-border-subtle rounded-lg space-y-3\">\n <div className=\"flex items-center gap-2\">\n <div className=\"size-1.5 rounded-full bg-status-error-bg\" />\n <p className=\"text-sm font-semibold text-status-error-text\">Refund Information</p>\n </div>\n\n {/* Credit Note Section */}\n {creditNoteNumber && (\n <div className=\"space-y-2\">\n <div>\n <p className=\"text-xs text-status-error-text mb-1\">Credit Note</p>\n <p className=\"text-sm font-mono text-status-error-text\">{creditNoteNumber}</p>\n </div>\n {creditNotePdfUrl && (\n <button\n type=\"button\"\n onClick={() => {\n void nativeImpact('light');\n void nativeOpenUrl(creditNotePdfUrl);\n }}\n className=\"flex items-center gap-2 text-sm text-status-error-text hover:underline\"\n >\n <FileText className=\"size-4\" />\n Download Credit Note PDF\n </button>\n )}\n </div>\n )}\n\n {/* Purchase Type & Detail */}\n {purchaseType && (\n <div>\n <p className=\"text-xs text-status-error-text mb-1\">Purchase Type</p>\n <p className=\"text-sm text-status-error-text\">\n {purchaseType}\n {purchaseDetail && ` - ${purchaseDetail}`}\n </p>\n </div>\n )}\n\n {/* Refund Reason */}\n {refundReason && (\n <div>\n <p className=\"text-xs text-status-error-text mb-1\">Reason</p>\n <p className=\"text-sm text-status-error-text\">{refundReason}</p>\n </div>\n )}\n\n {/* Original Transaction Reference */}\n {originalTransactionId && (\n <div>\n <p className=\"text-xs text-status-error-text mb-1\">Original Transaction</p>\n <p className=\"text-sm font-mono text-status-error-text break-all\">\n {originalTransactionId}\n </p>\n </div>\n )}\n\n {/* Refund Record ID */}\n {refundRecordId && (\n <div>\n <p className=\"text-xs text-status-error-text mb-1\">Refund Record ID</p>\n <p className=\"text-sm font-mono text-status-error-text break-all\">\n {refundRecordId}\n </p>\n </div>\n )}\n </div>\n )}\n\n {transaction.stripePaymentId && (\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Stripe Payment ID</p>\n <p className=\"text-sm font-mono break-all\">{transaction.stripePaymentId}</p>\n </div>\n )}\n\n {transaction.razorpayPaymentId && (\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Razorpay Payment ID</p>\n <p className=\"text-sm font-mono break-all\">{transaction.razorpayPaymentId}</p>\n </div>\n )}\n </div>\n\n <DialogFooter className=\"p-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"w-full py-2 px-4 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n Close\n </button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n};\n\nexport const TransactionsPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { billingAccountId } = useParams<{ billingAccountId: string }>();\n const permissions = useBillingPermissions();\n const navigateTo = useBillingNavigate();\n const {\n billingAccount,\n transactions,\n pagination,\n isLoading,\n error,\n refetch,\n goToNextPage,\n goToPreviousPage,\n } = useTransactions(billingAccountId);\n const [filterType, setFilterType] = useState<FilterType>('all');\n const [selectedTransaction, setSelectedTransaction] = useState<Transaction | null>(null);\n\n // Get invoice URL from transaction metadata (supports both Zoho and Stripe)\n const getInvoiceUrl = (transaction: Transaction): string | null => {\n const metadata = transaction.metadata as Record<string, unknown> | null;\n // Check for Zoho Books invoice first (INR payments)\n if (metadata?.zohoBooksInvoiceUrl) {\n return metadata.zohoBooksInvoiceUrl as string;\n }\n // Check for Stripe invoice (international payments)\n if (metadata?.invoiceUrl) {\n return metadata.invoiceUrl as string;\n }\n // Check for Stripe PDF (fallback)\n if (metadata?.invoicePdf) {\n return metadata.invoicePdf as string;\n }\n return null;\n };\n\n // Get total amount (with tax) from metadata if available\n const getTotalAmount = (transaction: Transaction): number => {\n const metadata = transaction.metadata as Record<string, unknown> | null;\n // Check for total in metadata (includes tax)\n if (metadata?.total && typeof metadata.total === 'number') {\n return metadata.total;\n }\n // Fallback to transaction amount\n return transaction.amount;\n };\n\n // Get invoice provider name for display\n const getInvoiceProvider = (transaction: Transaction): string | null => {\n const metadata = transaction.metadata as Record<string, unknown> | null;\n if (metadata?.zohoBooksInvoiceUrl) return 'Zoho Books';\n if (metadata?.invoiceUrl || metadata?.invoicePdf) return 'Stripe';\n return null;\n };\n\n const handleViewInvoice = (transaction: Transaction) => {\n const invoiceUrl = getInvoiceUrl(transaction);\n if (invoiceUrl) {\n void nativeImpact('light');\n void nativeOpenUrl(invoiceUrl);\n }\n };\n\n if (!permissions.canViewPayments) {\n return (\n <AccessDenied\n message={tr(\n 'billing.transactions.noPermission',\n \"You don't have permission to view transaction history.\"\n )}\n />\n );\n }\n\n // Show error if no billing account ID provided\n if (!billingAccountId) {\n return (\n <div className=\"p-6\">\n <EmptyState\n icon={\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n }\n title={tr('billing.transactions.noAccountSelected', 'No billing account selected')}\n description={tr(\n 'billing.transactions.noAccountSelectedDesc',\n 'Please select a billing account from the overview page to view transactions.'\n )}\n />\n </div>\n );\n }\n\n // Show server error if backend is down\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.transactions.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.transactions.serverUnavailableMessage',\n 'Unable to load transactions. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n const filteredTransactions =\n filterType === 'all' ? transactions : transactions.filter((t) => t.type === filterType);\n\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n const transactionColumns: ResponsiveTableColumn<Transaction>[] = [\n {\n key: 'date',\n header: 'Date',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (transaction) => (\n <span className=\"text-sm text-text-primary\">{formatDateTime(transaction.createdAt)}</span>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n cellClassName: 'whitespace-nowrap',\n cell: (transaction) => (\n <span className=\"text-sm text-text-primary capitalize\">\n {getTypeLabel(transaction.type)}\n </span>\n ),\n },\n {\n key: 'description',\n header: 'Description',\n cell: (transaction) => (\n <span className=\"text-sm text-text-secondary\">{transaction.description ?? '-'}</span>\n ),\n },\n {\n key: 'amount',\n header: 'Amount',\n headerClassName: 'text-right',\n cellClassName: 'text-right whitespace-nowrap',\n cell: (transaction) => (\n <span\n className={`text-sm font-medium tabular-nums ${\n transaction.type === 'refund' ? 'text-status-error-text' : 'text-text-primary'\n }`}\n >\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(getTotalAmount(transaction), transaction.currency)}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n headerClassName: 'text-center',\n cellClassName: 'text-center whitespace-nowrap',\n cell: (transaction) => (\n <span\n className={`px-1.5 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(\n getTransactionStatusColor(transaction.status)\n )}`}\n >\n {transaction.status}\n </span>\n ),\n },\n ];\n\n const renderTransactionActions = (transaction: Transaction) => (\n <TransactionActionsDropdown\n transaction={transaction}\n onViewDetails={() => setSelectedTransaction(transaction)}\n onViewInvoice={() => handleViewInvoice(transaction)}\n hasInvoiceUrl={!!getInvoiceUrl(transaction)}\n />\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/overview')}\n className=\"inline-flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Overview\n </button>\n\n <PageHeader\n title={tr('billing.transactions.title', 'Transaction History')}\n description={\n billingAccount\n ? `Transactions for ${billingAccount.name}`\n : 'View all your billing transactions'\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.transactions.purpose',\n 'A complete, time-ordered ledger of money movements on this billing account — payments, refunds and adjustments. Use it to reconcile what you were charged, open the linked invoice or credit note, and see tax breakdowns for each transaction.'\n )}\n </PagePurpose>\n\n {/* Billing Account Info Card — primary emphasis zone */}\n {billingAccount && (\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4\">\n {/* Account Name & Status */}\n <div className=\"flex items-start gap-2\">\n <CreditCard className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Account</p>\n <p className=\"text-sm font-medium\">{billingAccount.name}</p>\n {billingAccount.isActive ? (\n <span className=\"inline-flex items-center gap-1 mt-1 px-1.5 py-0.5 text-xs font-medium rounded bg-status-success-bg-subtle text-status-success-text\">\n <CheckCircle className=\"size-3\" />\n Active\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 mt-1 px-1.5 py-0.5 text-xs font-medium rounded bg-status-error-bg-subtle text-status-error-text\">\n <XCircle className=\"size-3\" />\n Inactive\n </span>\n )}\n </div>\n </div>\n\n {/* Email */}\n <div className=\"flex items-start gap-2\">\n <Mail className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Email</p>\n <p className=\"text-sm font-medium break-all\">\n {billingAccount.billingEmail || billingAccount.email}\n </p>\n </div>\n </div>\n\n {/* Credit Balance */}\n <div className=\"flex items-start gap-2\">\n <Wallet className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Credits Balance</p>\n <p className=\"text-sm font-semibold text-text-primary\">\n {(billingAccount.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n </div>\n\n {/* Tax ID */}\n <div className=\"flex items-start gap-2\">\n <FileText className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Tax ID</p>\n <p className=\"text-sm font-medium\">{billingAccount.taxId || '-'}</p>\n </div>\n </div>\n\n {/* Account Created */}\n <div className=\"flex items-start gap-2\">\n <Calendar className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Member Since</p>\n <p className=\"text-sm font-medium\">\n {billingAccount.createdAt ? formatDateTime(billingAccount.createdAt) : '-'}\n </p>\n </div>\n </div>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Filters */}\n <div className=\"flex items-center gap-4\">\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n {(['all', 'payment', 'refund', 'adjustment'] as FilterType[]).map((type) => (\n <button\n type=\"button\"\n key={type}\n onClick={() => setFilterType(type)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n filterType === type\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {type === 'all' ? 'All' : TYPE_LABELS[type as TransactionType]}\n </button>\n ))}\n </div>\n </div>\n\n {/* Transactions List */}\n {filteredTransactions.length === 0 ? (\n <IllustratedEmptyState\n illustration={filterType !== 'all' ? 'empty-search' : 'empty-data'}\n title={tr('billing.transactions.noTransactionsFound', 'No transactions found')}\n description={\n filterType !== 'all'\n ? tr(\n 'billing.transactions.noTransactionsFiltered',\n 'No transactions match your filter.'\n )\n : tr(\n 'billing.transactions.noTransactionsYet',\n 'Your transaction history will appear here.'\n )\n }\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-visible\">\n <ResponsiveTable<Transaction>\n columns={transactionColumns}\n data={filteredTransactions}\n rowKey={(transaction) => transaction.id}\n rowActions={renderTransactionActions}\n />\n </div>\n )}\n\n {/* Pagination Controls */}\n {pagination.totalCount > 0 && (\n <div className=\"flex items-center justify-between border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-secondary\">\n Showing {pagination.page * pagination.pageSize + 1} to{' '}\n {Math.min((pagination.page + 1) * pagination.pageSize, pagination.totalCount)} of{' '}\n {pagination.totalCount} transactions\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={goToPreviousPage}\n disabled={!pagination.hasPrevious}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <ChevronLeft className=\"size-4\" />\n Previous\n </button>\n <span className=\"text-sm text-text-secondary px-2\">\n Page {pagination.page + 1} of {Math.ceil(pagination.totalCount / pagination.pageSize)}\n </span>\n <button\n type=\"button\"\n onClick={goToNextPage}\n disabled={!pagination.hasMore}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Next\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Transaction Details Modal */}\n <TransactionDetailsModal\n transaction={selectedTransaction}\n onClose={() => setSelectedTransaction(null)}\n onViewInvoice={handleViewInvoice}\n invoiceUrl={selectedTransaction ? getInvoiceUrl(selectedTransaction) : null}\n invoiceProvider={selectedTransaction ? getInvoiceProvider(selectedTransaction) : null}\n />\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"TransactionsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/TransactionsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Transactions Page\n * Displays all billing transactions for a specific billing account\n */\n\nimport { useState, useRef, useEffect, type FC } from 'react';\nimport { useParams } from 'react-router';\nimport {\n ArrowLeft,\n MoreHorizontal,\n Eye,\n FileText,\n CreditCard,\n Mail,\n Wallet,\n ChevronLeft,\n ChevronRight,\n Calendar,\n CheckCircle,\n XCircle,\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useBillingNavigate } from '../../../hooks/useBillingNavigate';\nimport { useTransactions } from '../hooks';\nimport { nativeImpact, nativeOpenUrl } from '../../../../utils/nativeBridge';\nimport { AccessDenied, PageHeader, EmptyState, ServerError } from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport { getTransactionStatusColor, getStatusBadgeClasses } from '../../../shared/utils/status';\nimport type { Transaction, TransactionType } from '../../../shared/types';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n ResponsiveTable,\n type ResponsiveTableColumn,\n Dialog,\n DialogContent,\n DialogHeader,\n DialogTitle,\n DialogFooter,\n IllustratedEmptyState,\n PagePurpose,\n EmphasisPanel,\n} from '@burdenoff/fe-libs/ui';\n\ntype FilterType = 'all' | TransactionType;\n\nconst TYPE_LABELS: Record<TransactionType, string> = {\n payment: 'Payment',\n refund: 'Refund',\n adjustment: 'Adjustment',\n};\n\n// Helper to get label for transaction type (case-insensitive)\nconst getTypeLabel = (type: string): string => {\n const normalized = type.toLowerCase() as TransactionType;\n return TYPE_LABELS[normalized] ?? type;\n};\n\n// Transaction Actions Dropdown Component\ninterface TransactionActionsDropdownProps {\n transaction: Transaction;\n onViewDetails: () => void;\n onViewInvoice: () => void;\n hasInvoiceUrl: boolean;\n}\n\nconst TransactionActionsDropdown: FC<TransactionActionsDropdownProps> = ({\n transaction,\n onViewDetails,\n onViewInvoice,\n hasInvoiceUrl,\n}) => {\n const [isOpen, setIsOpen] = useState(false);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n useEffect(() => {\n const handleClickOutside = (event: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(event.target as Node)) {\n setIsOpen(false);\n }\n };\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }, []);\n\n return (\n <div className=\"relative overflow-visible\" ref={dropdownRef}>\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n className=\"p-1.5 rounded-md hover:bg-bg-sunken/50 transition-colors\"\n aria-label=\"Transaction actions\"\n >\n <MoreHorizontal className=\"size-4 text-text-secondary\" />\n </button>\n\n {isOpen && (\n <div className=\"absolute right-0 top-full mt-1 z-50 min-w-[160px] bg-popover border border-border-subtle rounded-md shadow-lg py-1\">\n <button\n type=\"button\"\n onClick={() => {\n onViewDetails();\n setIsOpen(false);\n }}\n className=\"w-full flex items-center gap-2 px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken/50 transition-colors whitespace-nowrap\"\n >\n <Eye className=\"size-4\" />\n View Details\n </button>\n {transaction.type === 'payment' && hasInvoiceUrl && (\n <button\n type=\"button\"\n onClick={() => {\n onViewInvoice();\n setIsOpen(false);\n }}\n className=\"w-full flex items-center gap-2 px-3 py-2 text-sm text-text-primary hover:bg-bg-sunken/50 transition-colors whitespace-nowrap\"\n >\n <FileText className=\"size-4\" />\n View Invoice\n </button>\n )}\n </div>\n )}\n </div>\n );\n};\n\n// Transaction Details Modal Component\ninterface TransactionDetailsModalProps {\n transaction: Transaction | null;\n onClose: () => void;\n onViewInvoice?: (transaction: Transaction) => void;\n invoiceUrl?: string | null;\n invoiceProvider?: string | null;\n}\n\nconst TransactionDetailsModal: FC<TransactionDetailsModalProps> = ({\n transaction,\n onClose,\n onViewInvoice,\n invoiceUrl,\n invoiceProvider,\n}) => {\n if (!transaction) return null;\n\n // Extract tax information from metadata\n const metadata = transaction.metadata as Record<string, unknown> | null;\n const subtotal = metadata?.subtotal as number | undefined;\n const taxAmount = metadata?.taxAmount as number | undefined;\n const taxRate = metadata?.taxRate as number | undefined;\n const taxType = metadata?.taxType as string | undefined;\n const total = metadata?.total as number | undefined;\n const invoiceNumber = metadata?.invoiceNumber as string | undefined;\n const invoiceStatus = metadata?.invoiceStatus as string | undefined;\n\n // Extract refund-specific information\n const isRefund = transaction.type === 'refund';\n const refundRecordId = metadata?.refundRecordId as string | undefined;\n const originalTransactionId = metadata?.originalTransactionId as string | undefined;\n const creditNoteId = metadata?.creditNoteId as string | undefined;\n const creditNoteNumber = metadata?.creditNoteNumber as string | undefined;\n const creditNotePdfUrl = metadata?.creditNotePdfUrl as string | undefined;\n const refundReason = metadata?.reason as string | undefined;\n const purchaseType = metadata?.purchaseType as string | undefined;\n const purchaseDetail = metadata?.purchaseDetail as string | undefined;\n\n // Determine if we should show tax breakdown\n const showTaxBreakdown = total && taxAmount !== undefined && taxAmount > 0;\n\n return (\n <Dialog open={!!transaction} onOpenChange={(open) => !open && onClose()}>\n <DialogContent className=\"max-w-md max-h-[90vh] overflow-auto p-0\">\n <DialogHeader className=\"p-4 border-b border-border-subtle\">\n <DialogTitle className=\"text-lg font-semibold\">Transaction Details</DialogTitle>\n </DialogHeader>\n\n <div className=\"p-4 space-y-4\">\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Transaction ID</p>\n <p className=\"text-sm font-mono break-all\">{transaction.id}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Type</p>\n <p className=\"text-sm capitalize\">{getTypeLabel(transaction.type)}</p>\n </div>\n </div>\n\n {/* Amount Section with Tax Breakdown */}\n <div className=\"space-y-2\">\n {showTaxBreakdown ? (\n <>\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">Subtotal</span>\n <span>\n {formatCurrency(subtotal || transaction.amount, transaction.currency)}\n </span>\n </div>\n {taxAmount > 0 && (\n <div className=\"flex justify-between text-sm\">\n <span className=\"text-text-secondary\">\n {taxType || 'Tax'} {taxRate ? `(${(taxRate * 100).toFixed(0)}%)` : ''}\n </span>\n <span>{formatCurrency(taxAmount, transaction.currency)}</span>\n </div>\n )}\n <div className=\"flex justify-between text-sm font-semibold border-t border-border-subtle pt-2\">\n <span>Total</span>\n <span className={transaction.type === 'refund' ? 'text-status-error-text' : ''}>\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(total, transaction.currency)}\n </span>\n </div>\n </>\n ) : (\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Amount</p>\n <p\n className={`text-sm font-medium ${transaction.type === 'refund' ? 'text-status-error-text' : ''}`}\n >\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(total || transaction.amount, transaction.currency)}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Status</p>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(\n getTransactionStatusColor(transaction.status)\n )}`}\n >\n {transaction.status}\n </span>\n </div>\n </div>\n )}\n </div>\n\n {/* Status (if tax breakdown shown) */}\n {showTaxBreakdown && (\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Status</p>\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(\n getTransactionStatusColor(transaction.status)\n )}`}\n >\n {transaction.status}\n </span>\n </div>\n )}\n\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Date & Time</p>\n <p className=\"text-sm\">{formatDateTime(transaction.createdAt)}</p>\n </div>\n\n {transaction.description && (\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Description</p>\n <p className=\"text-sm\">{transaction.description}</p>\n </div>\n )}\n\n {/* Invoice Information */}\n {invoiceNumber && (\n <div className=\"p-3 bg-bg-sunken/50 rounded-lg space-y-2\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Invoice</p>\n <p className=\"text-sm font-mono\">{invoiceNumber}</p>\n </div>\n {invoiceStatus && (\n <span\n className={`px-2 py-0.5 text-xs font-medium rounded ${\n invoiceStatus === 'PAID'\n ? 'bg-status-success-bg-subtle text-status-success-text'\n : 'bg-status-warning-bg-subtle text-status-warning-text'\n }`}\n >\n {invoiceStatus}\n </span>\n )}\n </div>\n {invoiceProvider && (\n <p className=\"text-xs text-text-secondary\">Provider: {invoiceProvider}</p>\n )}\n {invoiceUrl && onViewInvoice && (\n <button\n type=\"button\"\n onClick={() => onViewInvoice(transaction)}\n className=\"flex items-center gap-2 text-sm text-primary hover:underline\"\n >\n <FileText className=\"size-4\" />\n View Invoice\n </button>\n )}\n </div>\n )}\n\n {/* Fallback invoice button if no invoice number but URL exists */}\n {!invoiceNumber && invoiceUrl && onViewInvoice && (\n <div className=\"p-3 bg-bg-sunken/50 rounded-lg\">\n <button\n type=\"button\"\n onClick={() => onViewInvoice(transaction)}\n className=\"flex items-center gap-2 text-sm text-primary hover:underline\"\n >\n <FileText className=\"size-4\" />\n View Invoice {invoiceProvider && `(${invoiceProvider})`}\n </button>\n </div>\n )}\n\n {/* Refund-Specific Information */}\n {isRefund && (creditNoteId || refundReason || purchaseType) && (\n <div className=\"p-3 bg-status-error-bg-subtle/20 border border-border-subtle rounded-lg space-y-3\">\n <div className=\"flex items-center gap-2\">\n <div className=\"size-1.5 rounded-full bg-status-error-bg\" />\n <p className=\"text-sm font-semibold text-status-error-text\">Refund Information</p>\n </div>\n\n {/* Credit Note Section */}\n {creditNoteNumber && (\n <div className=\"space-y-2\">\n <div>\n <p className=\"text-xs text-status-error-text mb-1\">Credit Note</p>\n <p className=\"text-sm font-mono text-status-error-text\">{creditNoteNumber}</p>\n </div>\n {creditNotePdfUrl && (\n <button\n type=\"button\"\n onClick={() => {\n void nativeImpact('light');\n void nativeOpenUrl(creditNotePdfUrl);\n }}\n className=\"flex items-center gap-2 text-sm text-status-error-text hover:underline\"\n >\n <FileText className=\"size-4\" />\n Download Credit Note PDF\n </button>\n )}\n </div>\n )}\n\n {/* Purchase Type & Detail */}\n {purchaseType && (\n <div>\n <p className=\"text-xs text-status-error-text mb-1\">Purchase Type</p>\n <p className=\"text-sm text-status-error-text\">\n {purchaseType}\n {purchaseDetail && ` - ${purchaseDetail}`}\n </p>\n </div>\n )}\n\n {/* Refund Reason */}\n {refundReason && (\n <div>\n <p className=\"text-xs text-status-error-text mb-1\">Reason</p>\n <p className=\"text-sm text-status-error-text\">{refundReason}</p>\n </div>\n )}\n\n {/* Original Transaction Reference */}\n {originalTransactionId && (\n <div>\n <p className=\"text-xs text-status-error-text mb-1\">Original Transaction</p>\n <p className=\"text-sm font-mono text-status-error-text break-all\">\n {originalTransactionId}\n </p>\n </div>\n )}\n\n {/* Refund Record ID */}\n {refundRecordId && (\n <div>\n <p className=\"text-xs text-status-error-text mb-1\">Refund Record ID</p>\n <p className=\"text-sm font-mono text-status-error-text break-all\">\n {refundRecordId}\n </p>\n </div>\n )}\n </div>\n )}\n\n {transaction.stripePaymentId && (\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Stripe Payment ID</p>\n <p className=\"text-sm font-mono break-all\">{transaction.stripePaymentId}</p>\n </div>\n )}\n\n {transaction.razorpayPaymentId && (\n <div>\n <p className=\"text-xs text-text-secondary mb-1\">Razorpay Payment ID</p>\n <p className=\"text-sm font-mono break-all\">{transaction.razorpayPaymentId}</p>\n </div>\n )}\n </div>\n\n <DialogFooter className=\"p-4 border-t border-border-subtle\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"w-full py-2 px-4 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg/90 transition-colors\"\n >\n Close\n </button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n};\n\nexport const TransactionsPage: FC = () => {\n const { t } = useI18n();\n const tr = (key: string, fallback: string): string => {\n const translated = t(key);\n return translated === key ? fallback : translated;\n };\n const { billingAccountId } = useParams<{ billingAccountId: string }>();\n const permissions = useBillingPermissions();\n const navigateTo = useBillingNavigate();\n const {\n billingAccount,\n transactions,\n pagination,\n isLoading,\n error,\n refetch,\n goToNextPage,\n goToPreviousPage,\n } = useTransactions(billingAccountId);\n const [filterType, setFilterType] = useState<FilterType>('all');\n const [selectedTransaction, setSelectedTransaction] = useState<Transaction | null>(null);\n\n // Get invoice URL from transaction metadata (supports both Zoho and Stripe)\n const getInvoiceUrl = (transaction: Transaction): string | null => {\n const metadata = transaction.metadata as Record<string, unknown> | null;\n // Check for Zoho Books invoice first (INR payments)\n if (metadata?.zohoBooksInvoiceUrl) {\n return metadata.zohoBooksInvoiceUrl as string;\n }\n // Check for Stripe invoice (international payments)\n if (metadata?.invoiceUrl) {\n return metadata.invoiceUrl as string;\n }\n // Check for Stripe PDF (fallback)\n if (metadata?.invoicePdf) {\n return metadata.invoicePdf as string;\n }\n return null;\n };\n\n // Get total amount (with tax) from metadata if available\n const getTotalAmount = (transaction: Transaction): number => {\n const metadata = transaction.metadata as Record<string, unknown> | null;\n // Check for total in metadata (includes tax)\n if (metadata?.total && typeof metadata.total === 'number') {\n return metadata.total;\n }\n // Fallback to transaction amount\n return transaction.amount;\n };\n\n // Get invoice provider name for display\n const getInvoiceProvider = (transaction: Transaction): string | null => {\n const metadata = transaction.metadata as Record<string, unknown> | null;\n if (metadata?.zohoBooksInvoiceUrl) return 'Zoho Books';\n if (metadata?.invoiceUrl || metadata?.invoicePdf) return 'Stripe';\n return null;\n };\n\n const handleViewInvoice = (transaction: Transaction) => {\n const invoiceUrl = getInvoiceUrl(transaction);\n if (invoiceUrl) {\n void nativeImpact('light');\n void nativeOpenUrl(invoiceUrl);\n }\n };\n\n if (!permissions.canViewPayments) {\n return (\n <AccessDenied\n message={tr(\n 'billing.transactions.noPermission',\n \"You don't have permission to view transaction history.\"\n )}\n />\n );\n }\n\n // Show error if no billing account ID provided\n if (!billingAccountId) {\n return (\n <div className=\"p-6\">\n <EmptyState\n icon={\n <svg\n className=\"size-6 text-text-secondary\"\n fill=\"none\"\n viewBox=\"0 0 24 24\"\n stroke=\"currentColor\"\n >\n <path\n strokeLinecap=\"round\"\n strokeLinejoin=\"round\"\n strokeWidth={2}\n d=\"M12 9v2m0 4h.01m-6.938 4h13.856c1.54 0 2.502-1.667 1.732-3L13.732 4c-.77-1.333-2.694-1.333-3.464 0L3.34 16c-.77 1.333.192 3 1.732 3z\"\n />\n </svg>\n }\n title={tr('billing.transactions.noAccountSelected', 'No billing account selected')}\n description={tr(\n 'billing.transactions.noAccountSelectedDesc',\n 'Please select a billing account from the overview page to view transactions.'\n )}\n />\n </div>\n );\n }\n\n // Show server error if backend is down\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title={tr('billing.transactions.serverUnavailable', 'Server Unavailable')}\n message={tr(\n 'billing.transactions.serverUnavailableMessage',\n 'Unable to load transactions. The server might be down or experiencing issues.'\n )}\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n const filteredTransactions =\n filterType === 'all' ? transactions : transactions.filter((t) => t.type === filterType);\n\n if (isLoading) {\n return (\n <div className=\"space-y-6 p-6\">\n <div className=\"h-8 w-48 bg-bg-sunken animate-pulse rounded\" />\n <div className=\"space-y-2\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-16 bg-bg-sunken animate-pulse rounded\" />\n ))}\n </div>\n </div>\n );\n }\n\n const transactionColumns: ResponsiveTableColumn<Transaction>[] = [\n {\n key: 'date',\n header: 'Date',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (transaction) => (\n <span className=\"text-sm text-text-primary\">{formatDateTime(transaction.createdAt)}</span>\n ),\n },\n {\n key: 'type',\n header: 'Type',\n cellClassName: 'whitespace-nowrap',\n cell: (transaction) => (\n <span className=\"text-sm text-text-primary capitalize\">\n {getTypeLabel(transaction.type)}\n </span>\n ),\n },\n {\n key: 'description',\n header: 'Description',\n cell: (transaction) => (\n <span className=\"text-sm text-text-secondary\">{transaction.description ?? '-'}</span>\n ),\n },\n {\n key: 'amount',\n header: 'Amount',\n headerClassName: 'text-right',\n cellClassName: 'text-right whitespace-nowrap',\n cell: (transaction) => (\n <span\n className={`text-sm font-medium tabular-nums ${\n transaction.type === 'refund' ? 'text-status-error-text' : 'text-text-primary'\n }`}\n >\n {transaction.type === 'refund' ? '-' : ''}\n {formatCurrency(getTotalAmount(transaction), transaction.currency)}\n </span>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n headerClassName: 'text-center',\n cellClassName: 'text-center whitespace-nowrap',\n cell: (transaction) => (\n <span\n className={`px-1.5 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(\n getTransactionStatusColor(transaction.status)\n )}`}\n >\n {transaction.status}\n </span>\n ),\n },\n ];\n\n const renderTransactionActions = (transaction: Transaction) => (\n <TransactionActionsDropdown\n transaction={transaction}\n onViewDetails={() => setSelectedTransaction(transaction)}\n onViewInvoice={() => handleViewInvoice(transaction)}\n hasInvoiceUrl={!!getInvoiceUrl(transaction)}\n />\n );\n\n return (\n <div className=\"space-y-6 p-6\">\n {/* Back Button */}\n <button\n type=\"button\"\n onClick={() => navigateTo('/overview')}\n className=\"inline-flex items-center gap-2 text-sm text-text-secondary hover:text-text-primary transition-colors\"\n >\n <ArrowLeft className=\"size-4\" />\n Back to Overview\n </button>\n\n <PageHeader\n title={tr('billing.transactions.title', 'Transaction History')}\n description={\n billingAccount\n ? `Transactions for ${billingAccount.name}`\n : 'View all your billing transactions'\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.transactions.purpose',\n 'A complete, time-ordered ledger of money movements on this billing account — payments, refunds and adjustments. Use it to reconcile what you were charged, open the linked invoice or credit note, and see tax breakdowns for each transaction.'\n )}\n </PagePurpose>\n\n {/* Billing Account Info Card — primary emphasis zone */}\n {billingAccount && (\n <EmphasisPanel className=\"p-4\">\n <div className=\"grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 gap-4\">\n {/* Account Name & Status */}\n <div className=\"flex items-start gap-2\">\n <CreditCard className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Account</p>\n <p className=\"text-sm font-medium\">{billingAccount.name}</p>\n {billingAccount.isActive ? (\n <span className=\"inline-flex items-center gap-1 mt-1 px-1.5 py-0.5 text-xs font-medium rounded bg-status-success-bg-subtle text-status-success-text\">\n <CheckCircle className=\"size-3\" />\n Active\n </span>\n ) : (\n <span className=\"inline-flex items-center gap-1 mt-1 px-1.5 py-0.5 text-xs font-medium rounded bg-status-error-bg-subtle text-status-error-text\">\n <XCircle className=\"size-3\" />\n Inactive\n </span>\n )}\n </div>\n </div>\n\n {/* Email */}\n <div className=\"flex items-start gap-2\">\n <Mail className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Email</p>\n <p className=\"text-sm font-medium break-all\">\n {billingAccount.billingEmail || billingAccount.email}\n </p>\n </div>\n </div>\n\n {/* Credit Balance */}\n <div className=\"flex items-start gap-2\">\n <Wallet className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Credits Balance</p>\n <p className=\"text-sm font-semibold text-text-primary\">\n {(billingAccount.creditAmount || 0).toLocaleString()} Credits\n </p>\n </div>\n </div>\n\n {/* Tax ID */}\n <div className=\"flex items-start gap-2\">\n <FileText className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Tax ID</p>\n <p className=\"text-sm font-medium\">{billingAccount.taxId || '-'}</p>\n </div>\n </div>\n\n {/* Account Created */}\n <div className=\"flex items-start gap-2\">\n <Calendar className=\"size-4 text-text-secondary mt-0.5\" />\n <div>\n <p className=\"text-xs text-text-secondary mb-0.5\">Member Since</p>\n <p className=\"text-sm font-medium\">\n {billingAccount.createdAt ? formatDateTime(billingAccount.createdAt) : '-'}\n </p>\n </div>\n </div>\n </div>\n </EmphasisPanel>\n )}\n\n {/* Filters */}\n <div className=\"flex items-center gap-4\">\n <div className=\"inline-flex rounded-lg border border-border-subtle p-1 bg-bg-sunken/50\">\n {(['all', 'payment', 'refund', 'adjustment'] as FilterType[]).map((type) => (\n <button\n type=\"button\"\n key={type}\n onClick={() => setFilterType(type)}\n className={`px-3 py-1.5 text-sm font-medium rounded-md transition-colors ${\n filterType === type\n ? 'bg-bg-surface text-text-primary shadow-sm'\n : 'text-text-secondary hover:text-text-primary'\n }`}\n >\n {type === 'all' ? 'All' : TYPE_LABELS[type as TransactionType]}\n </button>\n ))}\n </div>\n </div>\n\n {/* Transactions List */}\n {filteredTransactions.length === 0 ? (\n <IllustratedEmptyState\n illustration={filterType !== 'all' ? 'empty-search' : 'empty-data'}\n title={tr('billing.transactions.noTransactionsFound', 'No transactions found')}\n description={\n filterType !== 'all'\n ? tr(\n 'billing.transactions.noTransactionsFiltered',\n 'No transactions match your filter.'\n )\n : tr(\n 'billing.transactions.noTransactionsYet',\n 'Your transaction history will appear here.'\n )\n }\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-visible\">\n <ResponsiveTable<Transaction>\n columns={transactionColumns}\n data={filteredTransactions}\n rowKey={(transaction) => transaction.id}\n rowActions={renderTransactionActions}\n />\n </div>\n )}\n\n {/* Pagination Controls */}\n {pagination.totalCount > 0 && (\n <div className=\"flex items-center justify-between border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-secondary\">\n Showing {pagination.page * pagination.pageSize + 1} to{' '}\n {Math.min((pagination.page + 1) * pagination.pageSize, pagination.totalCount)} of{' '}\n {pagination.totalCount} transactions\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={goToPreviousPage}\n disabled={!pagination.hasPrevious}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <ChevronLeft className=\"size-4\" />\n Previous\n </button>\n <span className=\"text-sm text-text-secondary px-2\">\n Page {pagination.page + 1} of {Math.ceil(pagination.totalCount / pagination.pageSize)}\n </span>\n <button\n type=\"button\"\n onClick={goToNextPage}\n disabled={!pagination.hasMore}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n Next\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Transaction Details Modal */}\n <TransactionDetailsModal\n transaction={selectedTransaction}\n onClose={() => setSelectedTransaction(null)}\n onViewInvoice={handleViewInvoice}\n invoiceUrl={selectedTransaction ? getInvoiceUrl(selectedTransaction) : null}\n invoiceProvider={selectedTransaction ? getInvoiceProvider(selectedTransaction) : null}\n />\n </div>\n );\n};\n"],"mappings":"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"}
@@ -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 useCreateBillingOrderCheckoutMutation,\n} from '../../../../generated/global-operations';\nimport { useBilling } from '../../../providers/BillingProvider';\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 [createBillingOrderMutation] = useCreateBillingOrderCheckoutMutation();\n const [spendCreditsMutation] = useSpendCreditsCheckoutMutation();\n const [convertCurrency, { data: currencyData, loading: currencyLoading }] =\n useConvertCurrencyLazyQuery();\n const [validateCoupon] = useValidateCouponLazyQuery();\n\n // Computed values\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 non-USD currency\n if (total > 0 && selectedCurrency !== 'USD') {\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: 'USD',\n },\n });\n }\n }, [total, selectedCurrency, selectedProvider, convertCurrency, taxAmount, subtotal, items]);\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\n setUseCredits(false);\n setCreditsToUse(0);\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 && currency !== 'USD' && !allItemsHaveLocalizedPrices) {\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: currency,\n fromCurrency: 'USD',\n },\n });\n }\n },\n [total, convertCurrency, items]\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 }, [\n billingAccount?.id,\n currentUser?.email,\n currentUser?.id,\n promoCode,\n selectedCurrency,\n subtotal,\n validateCoupon,\n ]);\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(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\n | 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, create one now.\n // Pass callbackGraphQL so the billing service can call back to the store service\n // after payment completes to mark the order confirmed.\n if (purchaseType === 'ORDER' && !billingOrderId && storeOrderId && billingAccount) {\n const orderItems = items\n .filter((item) => item.type === 'ORDER')\n .map((item) => ({\n name: item.name,\n description: item.description ?? undefined,\n quantity: item.quantity,\n unitPrice: item.price,\n metadata: {\n productId: item.metadata?.productId ?? undefined,\n variantId: item.metadata?.variantId ?? undefined,\n },\n }));\n\n const { data: billingOrderResult } = await createBillingOrderMutation({\n variables: {\n input: {\n // billingAccountId intentionally not set here — it gets\n // linked at initiatePayment time so the user can switch\n // billing accounts before confirming payment.\n externalOrderId: storeOrderId,\n externalService: 'STORE',\n currency: selectedCurrency,\n subtotal: chargeAmount,\n description: `Store order ${storeOrderId}`,\n items: orderItems,\n callbackGraphQL: {\n endpoint: 'http://global-int-gateway:4002/global/graphql',\n mutationName: 'updateStoreOrderPaymentStatus',\n },\n },\n },\n });\n\n if (billingOrderResult?.createBillingOrder?.success) {\n billingOrderId = billingOrderResult.createBillingOrder.order?.id;\n } else {\n const errMsg =\n billingOrderResult?.createBillingOrder?.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 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 createBillingOrderMutation,\n spendCreditsMutation,\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 useCreateBillingOrderCheckoutMutation,\n} from '../../../../generated/global-operations';\nimport { useBilling } from '../../../providers/BillingProvider';\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 [createBillingOrderMutation] = useCreateBillingOrderCheckoutMutation();\n const [spendCreditsMutation] = useSpendCreditsCheckoutMutation();\n const [convertCurrency, { data: currencyData, loading: currencyLoading }] =\n useConvertCurrencyLazyQuery();\n const [validateCoupon] = useValidateCouponLazyQuery();\n\n // Computed values\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 non-USD currency\n if (total > 0 && selectedCurrency !== 'USD') {\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: 'USD',\n },\n });\n }\n }, [total, selectedCurrency, selectedProvider, convertCurrency, taxAmount, subtotal, items]);\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\n setUseCredits(false);\n setCreditsToUse(0);\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 && currency !== 'USD' && !allItemsHaveLocalizedPrices) {\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: currency,\n fromCurrency: 'USD',\n },\n });\n }\n },\n [total, convertCurrency, items]\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 }, [\n billingAccount?.id,\n currentUser?.email,\n currentUser?.id,\n promoCode,\n selectedCurrency,\n subtotal,\n validateCoupon,\n ]);\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(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, create one now.\n // Pass callbackGraphQL so the billing service can call back to the store service\n // after payment completes to mark the order confirmed.\n if (purchaseType === 'ORDER' && !billingOrderId && storeOrderId && billingAccount) {\n const orderItems = items\n .filter((item) => item.type === 'ORDER')\n .map((item) => ({\n name: item.name,\n description: item.description ?? undefined,\n quantity: item.quantity,\n unitPrice: item.price,\n metadata: {\n productId: item.metadata?.productId ?? undefined,\n variantId: item.metadata?.variantId ?? undefined,\n },\n }));\n\n const { data: billingOrderResult } = await createBillingOrderMutation({\n variables: {\n input: {\n // billingAccountId intentionally not set here — it gets\n // linked at initiatePayment time so the user can switch\n // billing accounts before confirming payment.\n externalOrderId: storeOrderId,\n externalService: 'STORE',\n currency: selectedCurrency,\n subtotal: chargeAmount,\n description: `Store order ${storeOrderId}`,\n items: orderItems,\n callbackGraphQL: {\n endpoint: 'http://global-int-gateway:4002/global/graphql',\n mutationName: 'updateStoreOrderPaymentStatus',\n },\n },\n },\n });\n\n if (billingOrderResult?.createBillingOrder?.success) {\n billingOrderId = billingOrderResult.createBillingOrder.order?.id;\n } else {\n const errMsg =\n billingOrderResult?.createBillingOrder?.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 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 createBillingOrderMutation,\n spendCreditsMutation,\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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"}
@@ -33,7 +33,7 @@ import "../components/index.js";
33
33
  import { buildStoreOrderCheckoutItems as je, normalizeStoreOrderCurrency as Me } from "../utils/storeOrder.js";
34
34
  import { useCallback as s, useEffect as c, useMemo as l, useRef as Ne, useState as u } from "react";
35
35
  import { ArrowLeft as Pe, ArrowRight as Fe, Blocks as Ie, CheckCircle as Le, Coins as Re, CreditCard as ze, Loader2 as d, Package as Be, Receipt as Ve, RefreshCw as He, ShoppingCart as Ue, Wallet as We, XCircle as Ge } from "lucide-react";
36
- import { useLocation as Ke, useParams as qe } from "react-router-dom";
36
+ import { useLocation as Ke, useParams as qe } from "react-router";
37
37
  import { useAppShell as Je } from "@burdenoff/fe-libs/shared/providers/shell";
38
38
  import { Fragment as f, jsx as p, jsxs as m } from "react/jsx-runtime";
39
39
  import { useI18n as Ye } from "@burdenoff/fe-libs/shared/providers/shell/I18nProvider";