@burdenoff/microfe-billing 2026.912.3 → 2026.912.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/billing/modules/billing/pages/OrderCallbackLogsPage.js.map +1 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js +0 -1
- package/dist/billing/modules/billing/pages/RefundsPage.js.map +1 -1
- package/dist/billing/modules/checkout/hooks/useCheckout.js +8 -7
- package/dist/billing/modules/checkout/hooks/useCheckout.js.map +1 -1
- package/package.json +1 -1
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OrderCallbackLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/OrderCallbackLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Order Callback Logs Page\n * Admin visibility into the billing→external-service (store, etc.) order\n * completion callback — every BillingOrder's callback delivery attempt, not\n * just the ones currently FAILED. The outbound counterpart to\n * BillingWebhookLogsPage's inbound Stripe/Razorpay webhook view: lets\n * whoever owns billing ops see and retry a stuck billing→store handoff\n * without needing direct DB access (BOFF-7192).\n */\n\nimport { useState, type FC } from 'react';\nimport {\n RefreshCw,\n ChevronLeft,\n ChevronRight,\n CheckCircle,\n AlertTriangle,\n Clock,\n X,\n RotateCw,\n Loader2,\n Eye,\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useOrderCallbacks,\n useOrderCallbackMutations,\n type OrderCallbackFilters,\n type OrderCallback,\n} from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { formatDateTime, isServerError } from '../../../shared/utils';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport {\n ResponsiveTable,\n type ResponsiveTableColumn,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nconst PAGE_SIZE = 25;\nconst CALLBACK_STATUS_OPTIONS = ['PENDING', 'SUCCESS', 'FAILED'];\n\nconst getBadge = (\n order: OrderCallback\n): { label: string; className: string; icon: FC<{ className?: string }> } => {\n if (order.callbackStatus === 'FAILED') {\n return {\n label: 'Failed',\n className: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n icon: AlertTriangle,\n };\n }\n if (order.callbackStatus === 'SUCCESS') {\n return {\n label: 'Delivered',\n className: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n icon: CheckCircle,\n };\n }\n return {\n label: order.callbackStatus || 'Pending',\n className: `${statusTokens.warning.bg} ${statusTokens.warning.text}`,\n icon: Clock,\n };\n};\n\nconst OrderCallbackDetailModal: FC<{\n order: OrderCallback | null;\n onClose: () => void;\n onRetry: (order: OrderCallback) => void;\n isRetrying: boolean;\n}> = ({ order, onClose, onRetry, isRetrying }) => {\n if (!order) return null;\n\n const canRetry = order.callbackStatus === 'FAILED' && Boolean(order.billingAccountId);\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"w-full max-w-2xl max-h-[85vh] overflow-y-auto rounded-lg bg-bg-surface border border-border-subtle shadow-[var(--shadow-elevation-2)]\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h3 className=\"text-base font-semibold text-text-primary\">Order callback details</h3>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded text-text-muted hover:text-text-primary hover:bg-bg-sunken/50\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-muted\">Order ID</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">{order.id}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Service</p>\n <p className=\"text-text-primary\">{order.externalService}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Order status</p>\n <p className=\"text-text-primary\">{order.status}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Amount</p>\n <p className=\"text-text-primary\">\n {order.subtotal} {order.currency}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Billing account</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">\n {order.billingAccountId || '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Payment / Transaction</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">\n {order.paymentId || '—'} / {order.transactionId || '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Created</p>\n <p className=\"text-text-primary\">{formatDateTime(order.createdAt)}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Last callback attempt</p>\n <p className=\"text-text-primary\">\n {order.callbackLastAttemptAt ? formatDateTime(order.callbackLastAttemptAt) : '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Callback attempts</p>\n <p className=\"text-text-primary\">{order.callbackAttempts ?? 0}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Callback status</p>\n <p className=\"text-text-primary\">{order.callbackStatus || 'PENDING'}</p>\n </div>\n </div>\n {order.callbackLastError && (\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Last error</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border\">\n {order.callbackLastError}\n </pre>\n </div>\n )}\n {order.callbackStatus === 'FAILED' && !order.billingAccountId && (\n <p className=\"text-xs text-text-muted\">\n No billing account was resolved on this order, so it can't be retried from here\n — the callback was never able to identify who paid.\n </p>\n )}\n {order.callbackStatus === 'FAILED' && (\n <button\n type=\"button\"\n onClick={() => onRetry(order)}\n disabled={!canRetry || isRetrying}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {isRetrying ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <RotateCw className=\"size-4\" />\n )}\n Retry callback\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport const OrderCallbackLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n const [page, setPage] = useState(0);\n const [filters, setFilters] = useState<OrderCallbackFilters>({});\n const [selectedOrder, setSelectedOrder] = useState<OrderCallback | null>(null);\n const [retryError, setRetryError] = useState<string | null>(null);\n\n const { orders, totalCount, isLoading, error, refetch } = useOrderCallbacks(\n filters,\n page,\n PAGE_SIZE\n );\n const { retryOrderCallback, isRetrying } = useOrderCallbackMutations();\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to view order callback logs.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load order callback logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const handleFilterChange = <K extends keyof OrderCallbackFilters>(\n key: K,\n value: OrderCallbackFilters[K]\n ) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n setPage(0);\n };\n\n const handleRetry = async (order: OrderCallback) => {\n if (!order.billingAccountId) return;\n setRetryError(null);\n try {\n await retryOrderCallback(order.id, order.billingAccountId);\n await refetch();\n setSelectedOrder(null);\n } catch (err) {\n setRetryError(err instanceof Error ? err.message : 'Failed to retry callback');\n }\n };\n\n const columns: ResponsiveTableColumn<OrderCallback>[] = [\n {\n key: 'date',\n header: 'Created',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(order.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(order.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'callbackStatus',\n header: 'Callback',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => {\n const badge = getBadge(order);\n const BadgeIcon = badge.icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${badge.className}`}\n >\n <BadgeIcon className=\"size-3\" />\n {badge.label}\n </span>\n );\n },\n },\n {\n key: 'service',\n header: 'Service',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => <span className=\"text-sm text-text-primary\">{order.externalService}</span>,\n },\n {\n key: 'orderId',\n header: 'Order ID',\n cellClassName: 'whitespace-nowrap max-w-[220px] truncate',\n cell: (order) => (\n <span className=\"text-sm font-mono text-text-muted\" title={order.id}>\n {order.id}\n </span>\n ),\n },\n {\n key: 'attempts',\n header: 'Attempts',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => (\n <span className=\"text-sm text-text-secondary\">{order.callbackAttempts ?? 0}</span>\n ),\n },\n {\n key: 'error',\n header: 'Error',\n priority: 'hidden-on-mobile',\n cellClassName: 'max-w-[280px] truncate',\n cell: (order) => (\n <span className=\"text-sm text-status-error-text\">{order.callbackLastError || '-'}</span>\n ),\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Order Callback Logs\"\n description=\"Every billing→store (and other external service) order-completion callback — investigate a stuck handoff directly.\"\n actions={\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n }\n />\n\n {retryError && (\n <div className=\"p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border text-sm\">\n {retryError}\n </div>\n )}\n\n {/* Filters */}\n <div className=\"p-4 border border-border-subtle rounded-lg bg-bg-sunken/30 grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">Callback status</label>\n <select\n value={filters.callbackStatus || ''}\n onChange={(e) => handleFilterChange('callbackStatus', e.target.value || undefined)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n {CALLBACK_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Service</label>\n <input\n type=\"text\"\n value={filters.externalService || ''}\n onChange={(e) => handleFilterChange('externalService', e.target.value || undefined)}\n placeholder=\"e.g. STORE\"\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Has Error</label>\n <select\n value={filters.hasError === undefined ? '' : String(filters.hasError)}\n onChange={(e) =>\n handleFilterChange(\n 'hasError',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n <option value=\"true\">Has error</option>\n <option value=\"false\">No error</option>\n </select>\n </div>\n </div>\n\n {/* Table */}\n {isLoading && orders.length === 0 ? (\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 ) : orders.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No order callbacks found\"\n description=\"Nothing matches these filters — try widening them, or check back after the next store purchase.\"\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<OrderCallback>\n columns={columns}\n data={orders}\n rowKey={(order) => order.id}\n rowActions={(order) => (\n <button\n type=\"button\"\n onClick={() => setSelectedOrder(order)}\n className=\"inline-flex items-center gap-1.5 px-2 py-1 text-sm font-medium rounded-button text-text-secondary hover:text-text-primary hover:bg-bg-sunken/60 transition-colors\"\n >\n <Eye className=\"size-4\" />\n <span className=\"hidden sm:inline\">View</span>\n </button>\n )}\n />\n </div>\n )}\n\n {/* Pagination */}\n {totalCount > 0 && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-muted text-center sm:text-left\">\n Showing {page * PAGE_SIZE + 1} to {Math.min((page + 1) * PAGE_SIZE, totalCount)} of{' '}\n {totalCount} orders\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button 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 <span className=\"hidden sm:inline\">Previous</span>\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n <span className=\"hidden sm:inline\">Page </span>\n {page + 1} <span className=\"hidden sm:inline\">of</span>\n <span className=\"sm:hidden\">/</span> {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => (p + 1 < totalPages ? p + 1 : p))}\n disabled={page + 1 >= totalPages}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n <OrderCallbackDetailModal\n order={selectedOrder}\n onClose={() => setSelectedOrder(null)}\n onRetry={handleRetry}\n isRetrying={isRetrying}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OrderCallbackLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/OrderCallbackLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Order Callback Logs Page\n * Admin visibility into the billing→external-service (store, etc.) order\n * completion callback — every BillingOrder's callback delivery attempt, not\n * just the ones currently FAILED. The outbound counterpart to\n * BillingWebhookLogsPage's inbound Stripe/Razorpay webhook view: lets\n * whoever owns billing ops see and retry a stuck billing→store handoff\n * without needing direct DB access (BOFF-7192).\n */\n\nimport { useState, type FC } from 'react';\nimport {\n RefreshCw,\n ChevronLeft,\n ChevronRight,\n CheckCircle,\n AlertTriangle,\n Clock,\n X,\n RotateCw,\n Loader2,\n Eye,\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useOrderCallbacks,\n useOrderCallbackMutations,\n type OrderCallbackFilters,\n type OrderCallback,\n} from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { formatDateTime, isServerError } from '../../../shared/utils';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport {\n ResponsiveTable,\n type ResponsiveTableColumn,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\nconst PAGE_SIZE = 25;\nconst CALLBACK_STATUS_OPTIONS = ['PENDING', 'SUCCESS', 'FAILED'];\n\nconst getBadge = (\n order: OrderCallback\n): { label: string; className: string; icon: FC<{ className?: string }> } => {\n if (order.callbackStatus === 'FAILED') {\n return {\n label: 'Failed',\n className: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n icon: AlertTriangle,\n };\n }\n if (order.callbackStatus === 'SUCCESS') {\n return {\n label: 'Delivered',\n className: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n icon: CheckCircle,\n };\n }\n return {\n label: order.callbackStatus || 'Pending',\n className: `${statusTokens.warning.bg} ${statusTokens.warning.text}`,\n icon: Clock,\n };\n};\n\nconst OrderCallbackDetailModal: FC<{\n order: OrderCallback | null;\n onClose: () => void;\n onRetry: (order: OrderCallback) => void;\n isRetrying: boolean;\n}> = ({ order, onClose, onRetry, isRetrying }) => {\n if (!order) return null;\n\n const canRetry = order.callbackStatus === 'FAILED' && Boolean(order.billingAccountId);\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center bg-bg-overlay p-4\">\n <div className=\"w-full max-w-2xl max-h-[85vh] overflow-y-auto rounded-lg bg-bg-surface border border-border-subtle shadow-[var(--shadow-elevation-2)]\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h3 className=\"text-base font-semibold text-text-primary\">Order callback details</h3>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded text-text-muted hover:text-text-primary hover:bg-bg-sunken/50\"\n >\n <X className=\"size-4\" />\n </button>\n </div>\n <div className=\"p-4 space-y-4\">\n <div className=\"grid grid-cols-2 gap-4 text-sm\">\n <div>\n <p className=\"text-text-muted\">Order ID</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">{order.id}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Service</p>\n <p className=\"text-text-primary\">{order.externalService}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Order status</p>\n <p className=\"text-text-primary\">{order.status}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Amount</p>\n <p className=\"text-text-primary\">\n {order.subtotal} {order.currency}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Billing account</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">\n {order.billingAccountId || '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Payment / Transaction</p>\n <p className=\"text-text-primary font-mono text-xs break-all\">\n {order.paymentId || '—'} / {order.transactionId || '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Created</p>\n <p className=\"text-text-primary\">{formatDateTime(order.createdAt)}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Last callback attempt</p>\n <p className=\"text-text-primary\">\n {order.callbackLastAttemptAt ? formatDateTime(order.callbackLastAttemptAt) : '—'}\n </p>\n </div>\n <div>\n <p className=\"text-text-muted\">Callback attempts</p>\n <p className=\"text-text-primary\">{order.callbackAttempts ?? 0}</p>\n </div>\n <div>\n <p className=\"text-text-muted\">Callback status</p>\n <p className=\"text-text-primary\">{order.callbackStatus || 'PENDING'}</p>\n </div>\n </div>\n {order.callbackLastError && (\n <div>\n <p className=\"text-sm text-text-muted mb-1\">Last error</p>\n <pre className=\"text-xs whitespace-pre-wrap p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border\">\n {order.callbackLastError}\n </pre>\n </div>\n )}\n {order.callbackStatus === 'FAILED' && !order.billingAccountId && (\n <p className=\"text-xs text-text-muted\">\n No billing account was resolved on this order, so it can't be retried from here —\n the callback was never able to identify who paid.\n </p>\n )}\n {order.callbackStatus === 'FAILED' && (\n <button\n type=\"button\"\n onClick={() => onRetry(order)}\n disabled={!canRetry || isRetrying}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bgHover disabled:opacity-50\"\n >\n {isRetrying ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <RotateCw className=\"size-4\" />\n )}\n Retry callback\n </button>\n )}\n </div>\n </div>\n </div>\n );\n};\n\nexport const OrderCallbackLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n const [page, setPage] = useState(0);\n const [filters, setFilters] = useState<OrderCallbackFilters>({});\n const [selectedOrder, setSelectedOrder] = useState<OrderCallback | null>(null);\n const [retryError, setRetryError] = useState<string | null>(null);\n\n const { orders, totalCount, isLoading, error, refetch } = useOrderCallbacks(\n filters,\n page,\n PAGE_SIZE\n );\n const { retryOrderCallback, isRetrying } = useOrderCallbackMutations();\n\n if (!permissions.isBillingAdmin) {\n return <AccessDenied message=\"You don't have permission to view order callback logs.\" />;\n }\n\n if (error && isServerError(error)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load order callback logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const handleFilterChange = <K extends keyof OrderCallbackFilters>(\n key: K,\n value: OrderCallbackFilters[K]\n ) => {\n setFilters((prev) => ({ ...prev, [key]: value }));\n setPage(0);\n };\n\n const handleRetry = async (order: OrderCallback) => {\n if (!order.billingAccountId) return;\n setRetryError(null);\n try {\n await retryOrderCallback(order.id, order.billingAccountId);\n await refetch();\n setSelectedOrder(null);\n } catch (err) {\n setRetryError(err instanceof Error ? err.message : 'Failed to retry callback');\n }\n };\n\n const columns: ResponsiveTableColumn<OrderCallback>[] = [\n {\n key: 'date',\n header: 'Created',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(order.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(order.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'callbackStatus',\n header: 'Callback',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => {\n const badge = getBadge(order);\n const BadgeIcon = badge.icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${badge.className}`}\n >\n <BadgeIcon className=\"size-3\" />\n {badge.label}\n </span>\n );\n },\n },\n {\n key: 'service',\n header: 'Service',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => <span className=\"text-sm text-text-primary\">{order.externalService}</span>,\n },\n {\n key: 'orderId',\n header: 'Order ID',\n cellClassName: 'whitespace-nowrap max-w-[220px] truncate',\n cell: (order) => (\n <span className=\"text-sm font-mono text-text-muted\" title={order.id}>\n {order.id}\n </span>\n ),\n },\n {\n key: 'attempts',\n header: 'Attempts',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (order) => (\n <span className=\"text-sm text-text-secondary\">{order.callbackAttempts ?? 0}</span>\n ),\n },\n {\n key: 'error',\n header: 'Error',\n priority: 'hidden-on-mobile',\n cellClassName: 'max-w-[280px] truncate',\n cell: (order) => (\n <span className=\"text-sm text-status-error-text\">{order.callbackLastError || '-'}</span>\n ),\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Order Callback Logs\"\n description=\"Every billing→store (and other external service) order-completion callback — investigate a stuck handoff directly.\"\n actions={\n <button\n type=\"button\"\n onClick={() => refetch()}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50\"\n >\n <RefreshCw className=\"size-4\" />\n Refresh\n </button>\n }\n />\n\n {retryError && (\n <div className=\"p-3 rounded-md bg-status-error-bg-subtle text-status-error-text border border-status-error-border text-sm\">\n {retryError}\n </div>\n )}\n\n {/* Filters */}\n <div className=\"p-4 border border-border-subtle rounded-lg bg-bg-sunken/30 grid grid-cols-1 sm:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">Callback status</label>\n <select\n value={filters.callbackStatus || ''}\n onChange={(e) => handleFilterChange('callbackStatus', e.target.value || undefined)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n {CALLBACK_STATUS_OPTIONS.map((s) => (\n <option key={s} value={s}>\n {s}\n </option>\n ))}\n </select>\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Service</label>\n <input\n type=\"text\"\n value={filters.externalService || ''}\n onChange={(e) => handleFilterChange('externalService', e.target.value || undefined)}\n placeholder=\"e.g. STORE\"\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n />\n </div>\n <div>\n <label className=\"block text-sm font-medium mb-1\">Has Error</label>\n <select\n value={filters.hasError === undefined ? '' : String(filters.hasError)}\n onChange={(e) =>\n handleFilterChange(\n 'hasError',\n e.target.value === '' ? undefined : e.target.value === 'true'\n )\n }\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All</option>\n <option value=\"true\">Has error</option>\n <option value=\"false\">No error</option>\n </select>\n </div>\n </div>\n\n {/* Table */}\n {isLoading && orders.length === 0 ? (\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 ) : orders.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No order callbacks found\"\n description=\"Nothing matches these filters — try widening them, or check back after the next store purchase.\"\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<OrderCallback>\n columns={columns}\n data={orders}\n rowKey={(order) => order.id}\n rowActions={(order) => (\n <button\n type=\"button\"\n onClick={() => setSelectedOrder(order)}\n className=\"inline-flex items-center gap-1.5 px-2 py-1 text-sm font-medium rounded-button text-text-secondary hover:text-text-primary hover:bg-bg-sunken/60 transition-colors\"\n >\n <Eye className=\"size-4\" />\n <span className=\"hidden sm:inline\">View</span>\n </button>\n )}\n />\n </div>\n )}\n\n {/* Pagination */}\n {totalCount > 0 && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-muted text-center sm:text-left\">\n Showing {page * PAGE_SIZE + 1} to {Math.min((page + 1) * PAGE_SIZE, totalCount)} of{' '}\n {totalCount} orders\n </p>\n <div className=\"flex items-center gap-2\">\n <button\n type=\"button\"\n onClick={() => setPage((p) => Math.max(0, p - 1))}\n disabled={page === 0}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button 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 <span className=\"hidden sm:inline\">Previous</span>\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n <span className=\"hidden sm:inline\">Page </span>\n {page + 1} <span className=\"hidden sm:inline\">of</span>\n <span className=\"sm:hidden\">/</span> {totalPages}\n </span>\n <button\n type=\"button\"\n onClick={() => setPage((p) => (p + 1 < totalPages ? p + 1 : p))}\n disabled={page + 1 >= totalPages}\n className=\"inline-flex items-center gap-1 px-3 py-1.5 text-sm font-medium rounded-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n <OrderCallbackDetailModal\n order={selectedOrder}\n onClose={() => setSelectedOrder(null)}\n onRetry={handleRetry}\n isRetrying={isRetrying}\n />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -153,7 +153,6 @@ var I = {
|
|
|
153
153
|
let o = n.razorpayPaymentId ? "razorpay" : "stripe", s = n.razorpayPaymentId ?? n.stripePaymentId ?? "", l = r?.purchaseType ?? "", u = n.description ?? "";
|
|
154
154
|
y({
|
|
155
155
|
transactionId: n.id,
|
|
156
|
-
billingAccountId: n.billingAccountId,
|
|
157
156
|
provider: o,
|
|
158
157
|
gatewayPaymentId: s,
|
|
159
158
|
currency: n.currency,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"RefundsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/RefundsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Refunds Page\n * Admin page for managing refund records\n */\n\nimport { useState, useRef, useEffect, useMemo, useCallback, type FC } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport {\n RefreshCw,\n Plus,\n MoreHorizontal,\n Eye,\n Play,\n SkipForward,\n RotateCcw,\n ChevronLeft,\n ChevronRight,\n X,\n Search,\n Filter,\n AlertTriangle,\n CheckCircle,\n Clock,\n XCircle,\n Loader2,\n Ban,\n Download,\n // Calendar - available but not currently used\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useRefunds,\n type RefundRecord,\n type RefundRecordStatus,\n type RefundSource,\n type RefundRecordFilters,\n} from '../hooks';\nimport {\n GetTransactionDocument,\n type GetTransactionQuery,\n} from '../../../../generated/global-operations';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n CTAOverflowMenu,\n ResponsiveTable,\n type ResponsiveTableColumn,\n type CTAAction,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst STATUS_CONFIG: Record<\n RefundRecordStatus,\n { label: string; color: string; icon: FC<{ className?: string }> }\n> = {\n PENDING: { label: 'Pending', color: 'yellow', icon: Clock },\n PROCESSING: { label: 'Processing', color: 'blue', icon: Loader2 },\n COMPLETED: { label: 'Completed', color: 'green', icon: CheckCircle },\n FAILED: { label: 'Failed', color: 'red', icon: XCircle },\n SKIPPED: { label: 'Skipped', color: 'gray', icon: Ban },\n};\n\nconst SOURCE_LABELS: Record<RefundSource, string> = {\n MANUAL: 'Manual',\n WEBHOOK_RECOVERY: 'Webhook Recovery',\n SUBSCRIPTION_CANCEL: 'Subscription Cancel',\n DUPLICATE_PAYMENT: 'Duplicate Payment',\n};\n\nconst PROVIDER_OPTIONS = ['stripe', 'razorpay'];\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\nconst getStatusBadgeClasses = (color: string): string => {\n const classes: Record<string, string> = {\n yellow: `${statusTokens.warning.bg} ${statusTokens.warning.text}`,\n blue: `${statusTokens.info.bg} ${statusTokens.info.text}`,\n green: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n red: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n gray: `${statusTokens.neutral.bg} ${statusTokens.neutral.text}`,\n };\n return classes[color] || classes.gray;\n};\n\n// Actions Dropdown\ninterface RefundActionsDropdownProps {\n record: RefundRecord;\n onViewDetails: () => void;\n onProcess: () => void;\n onSkip: () => void;\n onRetry: () => void;\n isProcessing: boolean;\n}\n\nconst RefundActionsDropdown: FC<RefundActionsDropdownProps> = ({\n record,\n onViewDetails,\n onProcess,\n onSkip,\n onRetry,\n isProcessing,\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 const canProcess = record.status === 'PENDING';\n const canSkip = record.status === 'PENDING';\n const canRetry = record.status === 'FAILED';\n\n return (\n <div className=\"relative\" ref={dropdownRef}>\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n disabled={isProcessing}\n className=\"p-1.5 rounded-md hover:bg-bg-sunken/50 transition-colors disabled:opacity-50\"\n aria-label=\"Refund actions\"\n >\n {isProcessing ? (\n <Loader2 className=\"size-4 animate-spin text-text-muted\" />\n ) : (\n <MoreHorizontal className=\"size-4 text-text-muted\" />\n )}\n </button>\n\n {isOpen && (\n <div className=\"absolute right-0 top-full mt-1 z-50 min-w-[160px] bg-bg-elevated border border-border-subtle rounded-input shadow-[var(--shadow-elevation-3)] 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\"\n >\n <Eye className=\"size-4\" />\n View Details\n </button>\n\n {canProcess && (\n <button\n type=\"button\"\n onClick={() => {\n onProcess();\n setIsOpen(false);\n }}\n className={`w-full flex items-center gap-2 px-3 py-2 text-sm ${statusTokens.success.text} hover:bg-bg-sunken/50 transition-colors`}\n >\n <Play className=\"size-4\" />\n Process Refund\n </button>\n )}\n\n {canSkip && (\n <button\n type=\"button\"\n onClick={() => {\n onSkip();\n setIsOpen(false);\n }}\n className={`w-full flex items-center gap-2 px-3 py-2 text-sm ${statusTokens.warning.text} hover:bg-bg-sunken/50 transition-colors`}\n >\n <SkipForward className=\"size-4\" />\n Skip Refund\n </button>\n )}\n\n {canRetry && (\n <button\n type=\"button\"\n onClick={() => {\n onRetry();\n setIsOpen(false);\n }}\n className={`w-full flex items-center gap-2 px-3 py-2 text-sm ${statusTokens.info.text} hover:bg-bg-sunken/50 transition-colors`}\n >\n <RotateCcw className=\"size-4\" />\n Retry Refund\n </button>\n )}\n </div>\n )}\n </div>\n );\n};\n\n// Create Refund Modal\ninterface CreateRefundModalProps {\n isOpen: boolean;\n onClose: () => void;\n onSubmit: (data: CreateRefundFormData) => Promise<void>;\n isSubmitting: boolean;\n}\n\ninterface CreateRefundFormData {\n billingAccountId?: string;\n provider: string;\n gatewayPaymentId: string;\n gatewayOrderId?: string;\n amount: number;\n currency: string;\n purchaseType?: string;\n purchaseDetail?: string;\n reason?: string;\n notes?: string;\n transactionId?: string;\n}\n\ninterface LookedUpTransaction {\n id: string;\n billingAccountId: string;\n amount: number;\n totalAmount: number; // amount + tax (what was actually charged)\n currency: string;\n description: string | null;\n razorpayPaymentId: string | null;\n stripePaymentId: string | null;\n metadata: Record<string, unknown> | null;\n}\n\nconst EMPTY_FORM: CreateRefundFormData = {\n provider: 'stripe',\n gatewayPaymentId: '',\n amount: 0,\n currency: 'USD',\n};\n\nconst CreateRefundModal: FC<CreateRefundModalProps> = ({\n isOpen,\n onClose,\n onSubmit,\n isSubmitting,\n}) => {\n const client = useApolloClient();\n const [transactionId, setTransactionId] = useState('');\n const [lookedUp, setLookedUp] = useState<LookedUpTransaction | null>(null);\n const [lookupError, setLookupError] = useState<string | null>(null);\n const [isLookingUp, setIsLookingUp] = useState(false);\n const [formData, setFormData] = useState<CreateRefundFormData>(EMPTY_FORM);\n\n const reset = () => {\n setTransactionId('');\n setLookedUp(null);\n setLookupError(null);\n setFormData(EMPTY_FORM);\n };\n\n const handleClose = () => {\n reset();\n onClose();\n };\n\n const lookupTransaction = useCallback(\n async (id: string) => {\n const trimmed = id.trim();\n if (!trimmed) return;\n setIsLookingUp(true);\n setLookupError(null);\n setLookedUp(null);\n try {\n const { data } = await client.query<GetTransactionQuery>({\n query: GetTransactionDocument,\n variables: { id: trimmed },\n fetchPolicy: 'network-only',\n });\n const txn = data?.getTransaction ?? null;\n if (!txn) {\n setLookupError('Transaction not found. Check the ID and try again.');\n setFormData(EMPTY_FORM);\n return;\n }\n const meta = txn.metadata as Record<string, unknown> | null;\n const totalAmount = typeof meta?.total === 'number' ? meta.total : txn.amount;\n const lookedUpWithTotal: LookedUpTransaction = {\n id: txn.id,\n billingAccountId: txn.billingAccountId,\n amount: txn.amount,\n totalAmount,\n currency: txn.currency,\n description: txn.description ?? null,\n razorpayPaymentId: txn.razorpayPaymentId ?? null,\n stripePaymentId: txn.stripePaymentId ?? null,\n metadata: txn.metadata ?? null,\n };\n setLookedUp(lookedUpWithTotal);\n const provider = txn.razorpayPaymentId ? 'razorpay' : 'stripe';\n const gatewayPaymentId = txn.razorpayPaymentId ?? txn.stripePaymentId ?? '';\n const purchaseType = (meta?.purchaseType as string) ?? '';\n const purchaseDetail = txn.description ?? '';\n setFormData({\n transactionId: txn.id,\n billingAccountId: txn.billingAccountId,\n provider,\n gatewayPaymentId,\n currency: txn.currency,\n amount: totalAmount,\n purchaseType,\n purchaseDetail,\n reason: '',\n notes: '',\n });\n } catch {\n setLookupError('Failed to look up transaction. Ensure the ID is correct.');\n setFormData(EMPTY_FORM);\n } finally {\n setIsLookingUp(false);\n }\n },\n [client]\n );\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n await onSubmit(formData);\n reset();\n };\n\n if (!isOpen) return null;\n\n const maxAmount = lookedUp?.totalAmount ?? undefined;\n const isAmountExceedingMax = maxAmount !== undefined && formData.amount > maxAmount + 0.01;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={handleClose} />\n <div className=\"relative bg-bg-elevated border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-lg w-full mx-4 max-h-[90vh] overflow-auto\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h2 className=\"text-lg font-semibold\">Create Manual Refund</h2>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"p-1 rounded-md hover:bg-bg-sunken/50 transition-colors\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-4 space-y-4\">\n {/* Step 1: Transaction ID lookup */}\n <div>\n <label className=\"block text-sm font-medium mb-1\">Transaction ID *</label>\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={transactionId}\n onChange={(e) => {\n setTransactionId(e.target.value);\n setLookedUp(null);\n setLookupError(null);\n }}\n onBlur={() => lookupTransaction(transactionId)}\n className=\"flex-1 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"Paste transaction UUID...\"\n required\n />\n <button\n type=\"button\"\n onClick={() => lookupTransaction(transactionId)}\n disabled={!transactionId.trim() || isLookingUp}\n className=\"px-3 py-2 border border-border-subtle rounded-md hover:bg-bg-sunken/50 transition-colors disabled:opacity-50 flex items-center gap-1 text-sm\"\n >\n {isLookingUp ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Search className=\"size-4\" />\n )}\n </button>\n </div>\n {lookupError && <p className=\"text-xs text-status-error-text mt-1\">{lookupError}</p>}\n {isLookingUp && <p className=\"text-xs text-text-muted mt-1\">Looking up transaction…</p>}\n </div>\n\n {/* Auto-filled fields — shown once transaction is found */}\n {lookedUp && (\n <>\n {/* Transaction summary banner */}\n <div className=\"p-3 bg-status-success-bg-subtle border border-border-subtle rounded-md text-sm space-y-1\">\n <div className=\"flex items-center gap-2 text-status-success-text font-medium\">\n <CheckCircle className=\"size-4\" />\n Transaction found\n </div>\n <div className=\"text-text-muted grid grid-cols-2 gap-x-4 gap-y-0.5 mt-1\">\n <span>\n Account:{' '}\n <span className=\"text-text-primary font-mono text-xs\">\n {lookedUp.billingAccountId.slice(0, 16)}…\n </span>\n </span>\n <span>\n Paid:{' '}\n <span className=\"text-text-primary\">\n {formatCurrency(lookedUp.totalAmount, lookedUp.currency)}\n </span>\n </span>\n <span>\n Provider:{' '}\n <span className=\"text-text-primary capitalize\">{formData.provider}</span>\n </span>\n <span>\n Currency: <span className=\"text-text-primary\">{lookedUp.currency}</span>\n </span>\n </div>\n </div>\n\n {/* Read-only auto-filled fields */}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs font-medium text-text-muted mb-1\">Provider</label>\n <div className=\"px-3 py-2 border border-border-subtle rounded-md bg-bg-sunken/30 text-sm capitalize\">\n {formData.provider}\n </div>\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-muted mb-1\">Currency</label>\n <div className=\"px-3 py-2 border border-border-subtle rounded-md bg-bg-sunken/30 text-sm\">\n {formData.currency}\n </div>\n </div>\n </div>\n\n <div>\n <label className=\"block text-xs font-medium text-text-muted mb-1\">\n Gateway Payment ID\n </label>\n <div className=\"px-3 py-2 border border-border-subtle rounded-md bg-bg-sunken/30 text-sm font-mono truncate\">\n {formData.gatewayPaymentId}\n </div>\n </div>\n\n {formData.purchaseType && (\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs font-medium text-text-muted mb-1\">\n Purchase Type\n </label>\n <div className=\"px-3 py-2 border border-border-subtle rounded-md bg-bg-sunken/30 text-sm\">\n {formData.purchaseType}\n </div>\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-muted mb-1\">\n Purchase Detail\n </label>\n <div className=\"px-3 py-2 border border-border-subtle rounded-md bg-bg-sunken/30 text-sm truncate\">\n {formData.purchaseDetail || '—'}\n </div>\n </div>\n </div>\n )}\n\n {/* Editable: Amount (capped at transaction amount) */}\n <div>\n <label className=\"block text-sm font-medium mb-1\">\n Refund Amount *\n <span className=\"text-xs text-text-muted ml-1\">\n (max {formatCurrency(maxAmount!, formData.currency)})\n </span>\n </label>\n <input\n type=\"number\"\n step=\"0.01\"\n min=\"0.01\"\n max={maxAmount}\n value={formData.amount}\n onChange={(e) =>\n setFormData({ ...formData, amount: parseFloat(e.target.value) || 0 })\n }\n className={`w-full px-3 py-2 border rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n isAmountExceedingMax ? 'border-status-error-text' : 'border-border-subtle'\n }`}\n required\n />\n {isAmountExceedingMax && (\n <p className=\"text-xs text-status-error-text mt-1\">\n Amount cannot exceed the original payment of{' '}\n {formatCurrency(maxAmount!, formData.currency)}\n </p>\n )}\n </div>\n\n {/* Editable: Reason & Notes */}\n <div>\n <label className=\"block text-sm font-medium mb-1\">Reason</label>\n <input\n type=\"text\"\n value={formData.reason || ''}\n onChange={(e) => setFormData({ ...formData, reason: e.target.value })}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"Reason for refund\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium mb-1\">Notes</label>\n <textarea\n value={formData.notes || ''}\n onChange={(e) => setFormData({ ...formData, notes: e.target.value })}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n rows={2}\n placeholder=\"Internal notes\"\n />\n </div>\n </>\n )}\n\n <div className=\"flex gap-2 pt-2\">\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"flex-1 py-2 px-4 border border-border-subtle rounded-md hover:bg-bg-sunken/50 transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={\n isSubmitting ||\n !lookedUp ||\n !formData.gatewayPaymentId ||\n formData.amount <= 0 ||\n isAmountExceedingMax\n }\n className=\"flex-1 py-2 px-4 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2\"\n >\n {isSubmitting && <Loader2 className=\"size-4 animate-spin\" />}\n Create Refund\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n\n// Skip Refund Modal\ninterface SkipRefundModalProps {\n isOpen: boolean;\n record: RefundRecord | null;\n onClose: () => void;\n onSubmit: (reason: string) => Promise<void>;\n isSubmitting: boolean;\n}\n\nconst SkipRefundModal: FC<SkipRefundModalProps> = ({\n isOpen,\n record,\n onClose,\n onSubmit,\n isSubmitting,\n}) => {\n const [reason, setReason] = useState('');\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n await onSubmit(reason);\n setReason('');\n };\n\n if (!isOpen || !record) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={onClose} />\n <div className=\"relative bg-bg-elevated border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-md w-full mx-4\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h2 className=\"text-lg font-semibold\">Skip Refund</h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded-md hover:bg-bg-sunken/50 transition-colors\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-4 space-y-4\">\n <div\n className={`p-3 ${statusTokens.warning.bg} border ${statusTokens.warning.border} rounded-md`}\n >\n <div className=\"flex items-start gap-2\">\n <AlertTriangle className={`size-5 ${statusTokens.warning.icon} mt-0.5`} />\n <div>\n <p className={`text-sm font-medium ${statusTokens.warning.text}`}>\n You are about to skip this refund\n </p>\n <p className={`text-sm ${statusTokens.warning.text} mt-1`}>\n Amount: {formatCurrency(record.amount, record.currency)}\n </p>\n </div>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium mb-1\">Reason for skipping *</label>\n <textarea\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface resize-none\"\n rows={3}\n placeholder=\"Explain why this refund is being skipped...\"\n required\n />\n </div>\n\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"flex-1 py-2 px-4 border border-border-subtle rounded-md hover:bg-bg-sunken/50 transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting || !reason.trim()}\n className={`flex-1 py-2 px-4 ${statusTokens.warning.dot} text-action-primary-text rounded-md hover:opacity-90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2`}\n >\n {isSubmitting && <Loader2 className=\"size-4 animate-spin\" />}\n Skip Refund\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n\n// Refund Details Modal\ninterface RefundDetailsModalProps {\n record: RefundRecord | null;\n onClose: () => void;\n}\n\nconst RefundDetailsModal: FC<RefundDetailsModalProps> = ({ record, onClose }) => {\n if (!record) return null;\n\n const StatusIcon = STATUS_CONFIG[record.status].icon;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={onClose} />\n <div className=\"relative bg-bg-elevated border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-lg w-full mx-4 max-h-[90vh] overflow-auto\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h2 className=\"text-lg font-semibold\">Refund Details</h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded-md hover:bg-bg-sunken/50 transition-colors\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n <div className=\"p-4 space-y-4\">\n {/* Status Banner */}\n <div\n className={`flex items-center gap-2 p-3 rounded-md ${getStatusBadgeClasses(STATUS_CONFIG[record.status].color)}`}\n >\n <StatusIcon className=\"size-5\" />\n <span className=\"font-medium\">{STATUS_CONFIG[record.status].label}</span>\n </div>\n\n {/* Basic Info */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Refund ID</p>\n <p className=\"text-sm font-mono break-all\">{record.id}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Source</p>\n <p className=\"text-sm\">{SOURCE_LABELS[record.source]}</p>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Amount</p>\n <p className=\"text-lg font-semibold\">\n {formatCurrency(record.amount, record.currency)}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Provider</p>\n <p className=\"text-sm capitalize\">{record.provider}</p>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Gateway Payment ID</p>\n <p className=\"text-sm font-mono break-all\">{record.gatewayPaymentId}</p>\n </div>\n {record.gatewayOrderId && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Gateway Order ID</p>\n <p className=\"text-sm font-mono break-all\">{record.gatewayOrderId}</p>\n </div>\n )}\n </div>\n\n {record.gatewayRefundId && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Gateway Refund ID</p>\n <p className=\"text-sm font-mono break-all\">{record.gatewayRefundId}</p>\n </div>\n )}\n\n {(record.purchaseType || record.purchaseDetail) && (\n <div className=\"grid grid-cols-2 gap-4\">\n {record.purchaseType && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Purchase Type</p>\n <p className=\"text-sm\">{record.purchaseType}</p>\n </div>\n )}\n {record.purchaseDetail && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Purchase Detail</p>\n <p className=\"text-sm\">{record.purchaseDetail}</p>\n </div>\n )}\n </div>\n )}\n\n {record.reason && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Reason</p>\n <p className=\"text-sm\">{record.reason}</p>\n </div>\n )}\n\n {record.notes && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Notes</p>\n <p className=\"text-sm\">{record.notes}</p>\n </div>\n )}\n\n {record.errorMessage && (\n <div\n className={`p-3 ${statusTokens.error.bg} border ${statusTokens.error.border} rounded-md`}\n >\n <p className={`text-xs ${statusTokens.error.text} mb-1`}>Error Message</p>\n <p className={`text-sm ${statusTokens.error.text}`}>{record.errorMessage}</p>\n </div>\n )}\n\n <div className=\"grid grid-cols-2 gap-4 pt-2 border-t border-border-subtle\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Created At</p>\n <p className=\"text-sm\">{formatDateTime(record.createdAt)}</p>\n </div>\n {record.processedAt && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Processed At</p>\n <p className=\"text-sm\">{formatDateTime(record.processedAt)}</p>\n </div>\n )}\n </div>\n\n {record.retryCount > 0 && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Retry Count</p>\n <p className=\"text-sm\">{record.retryCount}</p>\n </div>\n )}\n </div>\n\n <div 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-bgHover transition-colors\"\n >\n Close\n </button>\n </div>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\ntype TabStatus = 'PENDING' | 'COMPLETED' | 'FAILED' | 'ALL';\n\nconst TAB_CONFIG: Record<TabStatus, { label: string; status?: RefundRecordStatus }> = {\n PENDING: { label: 'Pending', status: 'PENDING' },\n COMPLETED: { label: 'Completed', status: 'COMPLETED' },\n FAILED: { label: 'Failed', status: 'FAILED' },\n ALL: { label: 'All', status: undefined },\n};\n\nexport const RefundsPage: 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 permissions = useBillingPermissions();\n\n // State\n const [activeTab, setActiveTab] = useState<TabStatus>('PENDING');\n const [searchQuery, setSearchQuery] = useState('');\n const [filters, setFilters] = useState<RefundRecordFilters>({});\n const [showFilters, setShowFilters] = useState(false);\n const [selectedRecord, setSelectedRecord] = useState<RefundRecord | null>(null);\n const [showCreateModal, setShowCreateModal] = useState(false);\n const [showSkipModal, setShowSkipModal] = useState(false);\n const [recordToSkip, setRecordToSkip] = useState<RefundRecord | null>(null);\n const [actionLoadingId, setActionLoadingId] = useState<string | null>(null);\n\n // Build filters based on active tab - memoized to prevent infinite re-render loop\n // (useRefundRecords has a useEffect on options.filters, so a new object every render would cause\n // infinite updates if tabFilters were not stable)\n const tabFilters = useMemo<RefundRecordFilters>(\n () => ({\n ...filters,\n status: TAB_CONFIG[activeTab].status,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps -- filters is an object; spread ensures correct deps\n [\n activeTab,\n filters.source,\n filters.provider,\n filters.billingAccountId,\n filters.fromDate,\n filters.toDate,\n ]\n );\n\n // Data hooks - filters automatically applied based on active tab\n const {\n records,\n stats,\n pagination,\n isLoading,\n recordsError,\n unprocessedWebhooks,\n refetch,\n goToNextPage,\n goToPreviousPage,\n setFilters: updateFilters,\n createRefundRecord,\n processRefund,\n skipRefund,\n retryRefund,\n createRefundRecordsFromFailedWebhooks,\n isCreating,\n isProcessing,\n isCreatingFromWebhooks,\n } = useRefunds({ filters: tabFilters });\n\n // Permission check\n if (!permissions.canViewPayments) {\n return <AccessDenied message=\"You don't have permission to view refunds.\" />;\n }\n\n // Error handling\n if (recordsError && isServerError(recordsError)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load refunds. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n // Handlers\n const handleTabChange = (tab: TabStatus) => {\n setActiveTab(tab);\n // Reset filters when changing tabs\n setFilters({});\n setSearchQuery('');\n };\n\n const handleFilterChange = (key: keyof RefundRecordFilters, value: string | undefined) => {\n // Don't allow status filter changes as tabs control status\n if (key === 'status') return;\n\n const newFilters = { ...filters, [key]: value || undefined };\n setFilters(newFilters);\n updateFilters({ ...newFilters, status: TAB_CONFIG[activeTab].status });\n };\n\n const handleCreateRefund = async (data: CreateRefundFormData) => {\n try {\n await createRefundRecord(data);\n setShowCreateModal(false);\n } catch (error) {\n console.error('Failed to create refund:', error);\n }\n };\n\n const handleProcess = async (record: RefundRecord) => {\n setActionLoadingId(record.id);\n try {\n const result = await processRefund(record.id);\n if (!result.success && result.error) {\n console.error('Refund processing failed:', result.error);\n }\n } catch (error) {\n console.error('Failed to process refund:', error);\n } finally {\n setActionLoadingId(null);\n }\n };\n\n const handleSkip = async (reason: string) => {\n if (!recordToSkip) return;\n try {\n await skipRefund(recordToSkip.id, reason);\n setShowSkipModal(false);\n setRecordToSkip(null);\n } catch (error) {\n console.error('Failed to skip refund:', error);\n }\n };\n\n const handleRetry = async (record: RefundRecord) => {\n setActionLoadingId(record.id);\n try {\n const result = await retryRefund(record.id);\n if (!result.success && result.error) {\n console.error('Refund retry failed:', result.error);\n }\n } catch (error) {\n console.error('Failed to retry refund:', error);\n } finally {\n setActionLoadingId(null);\n }\n };\n\n const handleCreateFromWebhooks = async () => {\n try {\n await createRefundRecordsFromFailedWebhooks();\n } catch (error) {\n console.error('Failed to create refunds from webhooks:', error);\n }\n };\n\n // Filter records by search query (client-side)\n const filteredRecords = searchQuery\n ? records.filter(\n (r) =>\n r.gatewayPaymentId.toLowerCase().includes(searchQuery.toLowerCase()) ||\n r.gatewayOrderId?.toLowerCase().includes(searchQuery.toLowerCase()) ||\n r.reason?.toLowerCase().includes(searchQuery.toLowerCase())\n )\n : records;\n\n const refundColumns: ResponsiveTableColumn<RefundRecord>[] = [\n {\n key: 'date',\n header: 'Date',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(record.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(record.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => {\n const StatusIcon = STATUS_CONFIG[record.status].icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(\n STATUS_CONFIG[record.status].color\n )}`}\n >\n <StatusIcon className=\"size-3\" />\n <span className=\"hidden sm:inline\">{STATUS_CONFIG[record.status].label}</span>\n </span>\n );\n },\n },\n {\n key: 'source',\n header: 'Source',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm text-text-muted\">{SOURCE_LABELS[record.source]}</span>\n ),\n },\n {\n key: 'provider',\n header: 'Provider',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm text-text-primary capitalize\">{record.provider}</span>\n ),\n },\n {\n key: 'paymentId',\n header: 'Payment ID',\n cellClassName: 'whitespace-nowrap max-w-[120px] sm:max-w-[200px] truncate',\n cell: (record) => (\n <span className=\"text-sm font-mono text-text-muted\">{record.gatewayPaymentId}</span>\n ),\n },\n {\n key: 'amount',\n header: 'Amount',\n headerClassName: 'text-right',\n cellClassName: 'text-right whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm font-medium tabular-nums\">\n {formatCurrency(record.amount, record.currency)}\n </span>\n ),\n },\n {\n key: 'reason',\n header: 'Reason',\n priority: 'hidden-on-mobile',\n cellClassName: 'max-w-[200px] truncate',\n cell: (record) => <span className=\"text-sm text-text-muted\">{record.reason || '-'}</span>,\n },\n ];\n\n const renderRefundActions = (record: RefundRecord) => (\n <RefundActionsDropdown\n record={record}\n onViewDetails={() => setSelectedRecord(record)}\n onProcess={() => handleProcess(record)}\n onSkip={() => {\n setRecordToSkip(record);\n setShowSkipModal(true);\n }}\n onRetry={() => handleRetry(record)}\n isProcessing={actionLoadingId === record.id}\n />\n );\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=\"grid grid-cols-5 gap-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-24 bg-bg-sunken animate-pulse rounded-lg\" />\n ))}\n </div>\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 return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.refunds.title', 'Refund Management')}\n description=\"View and manage refund records\"\n actions={\n <CTAOverflowMenu\n primary={{\n label: 'Create Manual Refund',\n onSelect: () => setShowCreateModal(true),\n icon: <Plus className=\"size-4\" />,\n }}\n actions={\n [\n ...(unprocessedWebhooks.length > 0\n ? [\n {\n label: `Import ${unprocessedWebhooks.length} Failed Webhooks`,\n onSelect: handleCreateFromWebhooks,\n disabled: isCreatingFromWebhooks,\n icon: isCreatingFromWebhooks ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Download className=\"size-4\" />\n ),\n },\n ]\n : []),\n {\n label: 'Refresh',\n onSelect: () => {\n void refetch();\n },\n icon: <RefreshCw className=\"size-4\" />,\n },\n ] satisfies CTAAction[]\n }\n />\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.refunds.purpose',\n 'Track and act on every refund across all billing accounts — manual refunds, ones triggered by cancellations, and those recovered from failed payment-gateway webhooks. Use it to process pending refunds, retry failed ones, skip a refund with a reason, or create a manual refund for a specific transaction.'\n )}\n </PagePurpose>\n\n {/* Tabs Navigation */}\n <div className=\"border-b border-border-subtle\">\n <div className=\"flex flex-wrap gap-2 sm:gap-0\">\n {(Object.keys(TAB_CONFIG) as TabStatus[]).map((tab) => {\n const isActive = activeTab === tab;\n const count = stats\n ? tab === 'PENDING'\n ? stats.pending\n : tab === 'COMPLETED'\n ? stats.completed\n : tab === 'FAILED'\n ? stats.failed\n : stats.pending +\n stats.processing +\n stats.completed +\n stats.failed +\n stats.skipped\n : 0;\n\n return (\n <button\n type=\"button\"\n key={tab}\n onClick={() => handleTabChange(tab)}\n className={`flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${\n isActive\n ? 'border-action-primary-bg text-text-link'\n : 'border-transparent text-text-muted hover:text-text-primary hover:border-border-subtle'\n }`}\n >\n {TAB_CONFIG[tab].label}\n {stats && (\n <span\n className={`px-2 py-0.5 text-xs rounded-full ${\n isActive\n ? 'bg-[var(--color-accent-soft)] text-text-link'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {count}\n </span>\n )}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row gap-4\">\n {/* Search */}\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted\" />\n <input\n type=\"text\"\n placeholder=\"Search by payment ID, order ID, or reason...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n />\n </div>\n\n {/* Filter Toggle */}\n <button\n type=\"button\"\n onClick={() => setShowFilters(!showFilters)}\n className={`inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border transition-colors ${\n showFilters\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-link'\n : 'border-border-subtle bg-bg-surface hover:bg-bg-sunken/50'\n }`}\n >\n <Filter className=\"size-4\" />\n Filters\n {Object.values(filters).filter(Boolean).length > 0 && (\n <span className=\"px-1.5 py-0.5 text-xs rounded-full bg-action-primary-bg text-action-primary-text\">\n {Object.values(filters).filter(Boolean).length}\n </span>\n )}\n </button>\n\n {/* Clear Filters */}\n {Object.values(filters).filter(Boolean).length > 0 && (\n <button\n type=\"button\"\n onClick={() => {\n setFilters({});\n updateFilters({});\n }}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md text-text-muted hover:text-text-primary transition-colors\"\n >\n <X className=\"size-4\" />\n Clear\n </button>\n )}\n </div>\n\n {/* Expanded Filters */}\n {showFilters && (\n <div className=\"p-4 border border-border-subtle rounded-lg bg-bg-sunken/30 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">Source</label>\n <select\n value={filters.source || ''}\n onChange={(e) => handleFilterChange('source', e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All Sources</option>\n {Object.entries(SOURCE_LABELS).map(([key, label]) => (\n <option key={key} value={key}>\n {label}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium mb-1\">Provider</label>\n <select\n value={filters.provider || ''}\n onChange={(e) => handleFilterChange('provider', e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All Providers</option>\n {PROVIDER_OPTIONS.map((p) => (\n <option key={p} value={p}>\n {p.charAt(0).toUpperCase() + p.slice(1)}\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"sm:col-span-2 lg:col-span-1\">\n <label className=\"block text-sm font-medium mb-1\">Date Range</label>\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-2\">\n <input\n type=\"date\"\n value={filters.fromDate ? filters.fromDate.split('T')[0] : ''}\n onChange={(e) =>\n handleFilterChange(\n 'fromDate',\n e.target.value ? new Date(e.target.value).toISOString() : undefined\n )\n }\n className=\"w-full sm:flex-1 p-2 border border-border-subtle rounded-md bg-bg-surface text-sm\"\n />\n <span className=\"text-text-muted hidden sm:inline\">-</span>\n <input\n type=\"date\"\n value={filters.toDate ? filters.toDate.split('T')[0] : ''}\n onChange={(e) =>\n handleFilterChange(\n 'toDate',\n e.target.value ? new Date(e.target.value).toISOString() : undefined\n )\n }\n className=\"w-full sm:flex-1 p-2 border border-border-subtle rounded-md bg-bg-surface text-sm\"\n />\n </div>\n </div>\n </div>\n )}\n\n {/* Table */}\n {filteredRecords.length === 0 ? (\n <IllustratedEmptyState\n illustration={\n Object.values(filters).filter(Boolean).length > 0 ? 'empty-search' : 'empty-data'\n }\n title=\"No refund records found\"\n description={\n Object.values(filters).filter(Boolean).length > 0\n ? 'Try adjusting your filters.'\n : activeTab === 'PENDING'\n ? 'No pending refunds to process.'\n : 'Refund records will appear here when created.'\n }\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<RefundRecord>\n columns={refundColumns}\n data={filteredRecords}\n rowKey={(record) => record.id}\n rowActions={renderRefundActions}\n />\n </div>\n )}\n\n {/* Pagination */}\n {pagination.totalCount > 0 && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-muted text-center sm:text-left\">\n Showing {pagination.page * pagination.pageSize + 1} to{' '}\n {Math.min((pagination.page + 1) * pagination.pageSize, pagination.totalCount)} of{' '}\n {pagination.totalCount} refunds\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-button 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 <span className=\"hidden sm:inline\">Previous</span>\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n <span className=\"hidden sm:inline\">Page </span>\n {pagination.page + 1} <span className=\"hidden sm:inline\">of</span>\n <span className=\"sm:hidden\">/</span>{' '}\n {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-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Modals */}\n <CreateRefundModal\n isOpen={showCreateModal}\n onClose={() => setShowCreateModal(false)}\n onSubmit={handleCreateRefund}\n isSubmitting={isCreating}\n />\n\n <SkipRefundModal\n isOpen={showSkipModal}\n record={recordToSkip}\n onClose={() => {\n setShowSkipModal(false);\n setRecordToSkip(null);\n }}\n onSubmit={handleSkip}\n isSubmitting={isProcessing}\n />\n\n <RefundDetailsModal record={selectedRecord} onClose={() => setSelectedRecord(null)} />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"RefundsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/RefundsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Refunds Page\n * Admin page for managing refund records\n */\n\nimport { useState, useRef, useEffect, useMemo, useCallback, type FC } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport {\n RefreshCw,\n Plus,\n MoreHorizontal,\n Eye,\n Play,\n SkipForward,\n RotateCcw,\n ChevronLeft,\n ChevronRight,\n X,\n Search,\n Filter,\n AlertTriangle,\n CheckCircle,\n Clock,\n XCircle,\n Loader2,\n Ban,\n Download,\n // Calendar - available but not currently used\n} from 'lucide-react';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport {\n useRefunds,\n type RefundRecord,\n type RefundRecordStatus,\n type RefundSource,\n type RefundRecordFilters,\n} from '../hooks';\nimport {\n GetTransactionDocument,\n type GetTransactionQuery,\n} from '../../../../generated/global-operations';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { formatCurrency, formatDateTime, isServerError } from '../../../shared/utils';\nimport { statusTokens } from '../../../shared/utils/tokens';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport {\n CTAOverflowMenu,\n ResponsiveTable,\n type ResponsiveTableColumn,\n type CTAAction,\n PagePurpose,\n IllustratedEmptyState,\n} from '@burdenoff/fe-libs/ui';\n\n// ============================================================================\n// Constants\n// ============================================================================\n\nconst STATUS_CONFIG: Record<\n RefundRecordStatus,\n { label: string; color: string; icon: FC<{ className?: string }> }\n> = {\n PENDING: { label: 'Pending', color: 'yellow', icon: Clock },\n PROCESSING: { label: 'Processing', color: 'blue', icon: Loader2 },\n COMPLETED: { label: 'Completed', color: 'green', icon: CheckCircle },\n FAILED: { label: 'Failed', color: 'red', icon: XCircle },\n SKIPPED: { label: 'Skipped', color: 'gray', icon: Ban },\n};\n\nconst SOURCE_LABELS: Record<RefundSource, string> = {\n MANUAL: 'Manual',\n WEBHOOK_RECOVERY: 'Webhook Recovery',\n SUBSCRIPTION_CANCEL: 'Subscription Cancel',\n DUPLICATE_PAYMENT: 'Duplicate Payment',\n};\n\nconst PROVIDER_OPTIONS = ['stripe', 'razorpay'];\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\nconst getStatusBadgeClasses = (color: string): string => {\n const classes: Record<string, string> = {\n yellow: `${statusTokens.warning.bg} ${statusTokens.warning.text}`,\n blue: `${statusTokens.info.bg} ${statusTokens.info.text}`,\n green: `${statusTokens.success.bg} ${statusTokens.success.text}`,\n red: `${statusTokens.error.bg} ${statusTokens.error.text}`,\n gray: `${statusTokens.neutral.bg} ${statusTokens.neutral.text}`,\n };\n return classes[color] || classes.gray;\n};\n\n// Actions Dropdown\ninterface RefundActionsDropdownProps {\n record: RefundRecord;\n onViewDetails: () => void;\n onProcess: () => void;\n onSkip: () => void;\n onRetry: () => void;\n isProcessing: boolean;\n}\n\nconst RefundActionsDropdown: FC<RefundActionsDropdownProps> = ({\n record,\n onViewDetails,\n onProcess,\n onSkip,\n onRetry,\n isProcessing,\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 const canProcess = record.status === 'PENDING';\n const canSkip = record.status === 'PENDING';\n const canRetry = record.status === 'FAILED';\n\n return (\n <div className=\"relative\" ref={dropdownRef}>\n <button\n type=\"button\"\n onClick={() => setIsOpen(!isOpen)}\n disabled={isProcessing}\n className=\"p-1.5 rounded-md hover:bg-bg-sunken/50 transition-colors disabled:opacity-50\"\n aria-label=\"Refund actions\"\n >\n {isProcessing ? (\n <Loader2 className=\"size-4 animate-spin text-text-muted\" />\n ) : (\n <MoreHorizontal className=\"size-4 text-text-muted\" />\n )}\n </button>\n\n {isOpen && (\n <div className=\"absolute right-0 top-full mt-1 z-50 min-w-[160px] bg-bg-elevated border border-border-subtle rounded-input shadow-[var(--shadow-elevation-3)] 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\"\n >\n <Eye className=\"size-4\" />\n View Details\n </button>\n\n {canProcess && (\n <button\n type=\"button\"\n onClick={() => {\n onProcess();\n setIsOpen(false);\n }}\n className={`w-full flex items-center gap-2 px-3 py-2 text-sm ${statusTokens.success.text} hover:bg-bg-sunken/50 transition-colors`}\n >\n <Play className=\"size-4\" />\n Process Refund\n </button>\n )}\n\n {canSkip && (\n <button\n type=\"button\"\n onClick={() => {\n onSkip();\n setIsOpen(false);\n }}\n className={`w-full flex items-center gap-2 px-3 py-2 text-sm ${statusTokens.warning.text} hover:bg-bg-sunken/50 transition-colors`}\n >\n <SkipForward className=\"size-4\" />\n Skip Refund\n </button>\n )}\n\n {canRetry && (\n <button\n type=\"button\"\n onClick={() => {\n onRetry();\n setIsOpen(false);\n }}\n className={`w-full flex items-center gap-2 px-3 py-2 text-sm ${statusTokens.info.text} hover:bg-bg-sunken/50 transition-colors`}\n >\n <RotateCcw className=\"size-4\" />\n Retry Refund\n </button>\n )}\n </div>\n )}\n </div>\n );\n};\n\n// Create Refund Modal\ninterface CreateRefundModalProps {\n isOpen: boolean;\n onClose: () => void;\n onSubmit: (data: CreateRefundFormData) => Promise<void>;\n isSubmitting: boolean;\n}\n\ninterface CreateRefundFormData {\n provider: string;\n gatewayPaymentId: string;\n gatewayOrderId?: string;\n amount: number;\n currency: string;\n purchaseType?: string;\n purchaseDetail?: string;\n reason?: string;\n notes?: string;\n transactionId?: string;\n}\n\ninterface LookedUpTransaction {\n id: string;\n billingAccountId: string;\n amount: number;\n totalAmount: number; // amount + tax (what was actually charged)\n currency: string;\n description: string | null;\n razorpayPaymentId: string | null;\n stripePaymentId: string | null;\n metadata: Record<string, unknown> | null;\n}\n\nconst EMPTY_FORM: CreateRefundFormData = {\n provider: 'stripe',\n gatewayPaymentId: '',\n amount: 0,\n currency: 'USD',\n};\n\nconst CreateRefundModal: FC<CreateRefundModalProps> = ({\n isOpen,\n onClose,\n onSubmit,\n isSubmitting,\n}) => {\n const client = useApolloClient();\n const [transactionId, setTransactionId] = useState('');\n const [lookedUp, setLookedUp] = useState<LookedUpTransaction | null>(null);\n const [lookupError, setLookupError] = useState<string | null>(null);\n const [isLookingUp, setIsLookingUp] = useState(false);\n const [formData, setFormData] = useState<CreateRefundFormData>(EMPTY_FORM);\n\n const reset = () => {\n setTransactionId('');\n setLookedUp(null);\n setLookupError(null);\n setFormData(EMPTY_FORM);\n };\n\n const handleClose = () => {\n reset();\n onClose();\n };\n\n const lookupTransaction = useCallback(\n async (id: string) => {\n const trimmed = id.trim();\n if (!trimmed) return;\n setIsLookingUp(true);\n setLookupError(null);\n setLookedUp(null);\n try {\n const { data } = await client.query<GetTransactionQuery>({\n query: GetTransactionDocument,\n variables: { id: trimmed },\n fetchPolicy: 'network-only',\n });\n const txn = data?.getTransaction ?? null;\n if (!txn) {\n setLookupError('Transaction not found. Check the ID and try again.');\n setFormData(EMPTY_FORM);\n return;\n }\n const meta = txn.metadata as Record<string, unknown> | null;\n const totalAmount = typeof meta?.total === 'number' ? meta.total : txn.amount;\n const lookedUpWithTotal: LookedUpTransaction = {\n id: txn.id,\n billingAccountId: txn.billingAccountId,\n amount: txn.amount,\n totalAmount,\n currency: txn.currency,\n description: txn.description ?? null,\n razorpayPaymentId: txn.razorpayPaymentId ?? null,\n stripePaymentId: txn.stripePaymentId ?? null,\n metadata: txn.metadata ?? null,\n };\n setLookedUp(lookedUpWithTotal);\n const provider = txn.razorpayPaymentId ? 'razorpay' : 'stripe';\n const gatewayPaymentId = txn.razorpayPaymentId ?? txn.stripePaymentId ?? '';\n const purchaseType = (meta?.purchaseType as string) ?? '';\n const purchaseDetail = txn.description ?? '';\n setFormData({\n transactionId: txn.id,\n // billingAccountId is intentionally not sent — the backend always derives\n // it from the transaction itself, which reflects whoever actually paid.\n provider,\n gatewayPaymentId,\n currency: txn.currency,\n amount: totalAmount,\n purchaseType,\n purchaseDetail,\n reason: '',\n notes: '',\n });\n } catch {\n setLookupError('Failed to look up transaction. Ensure the ID is correct.');\n setFormData(EMPTY_FORM);\n } finally {\n setIsLookingUp(false);\n }\n },\n [client]\n );\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n await onSubmit(formData);\n reset();\n };\n\n if (!isOpen) return null;\n\n const maxAmount = lookedUp?.totalAmount ?? undefined;\n const isAmountExceedingMax = maxAmount !== undefined && formData.amount > maxAmount + 0.01;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={handleClose} />\n <div className=\"relative bg-bg-elevated border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-lg w-full mx-4 max-h-[90vh] overflow-auto\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h2 className=\"text-lg font-semibold\">Create Manual Refund</h2>\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"p-1 rounded-md hover:bg-bg-sunken/50 transition-colors\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-4 space-y-4\">\n {/* Step 1: Transaction ID lookup */}\n <div>\n <label className=\"block text-sm font-medium mb-1\">Transaction ID *</label>\n <div className=\"flex gap-2\">\n <input\n type=\"text\"\n value={transactionId}\n onChange={(e) => {\n setTransactionId(e.target.value);\n setLookedUp(null);\n setLookupError(null);\n }}\n onBlur={() => lookupTransaction(transactionId)}\n className=\"flex-1 px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"Paste transaction UUID...\"\n required\n />\n <button\n type=\"button\"\n onClick={() => lookupTransaction(transactionId)}\n disabled={!transactionId.trim() || isLookingUp}\n className=\"px-3 py-2 border border-border-subtle rounded-md hover:bg-bg-sunken/50 transition-colors disabled:opacity-50 flex items-center gap-1 text-sm\"\n >\n {isLookingUp ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Search className=\"size-4\" />\n )}\n </button>\n </div>\n {lookupError && <p className=\"text-xs text-status-error-text mt-1\">{lookupError}</p>}\n {isLookingUp && <p className=\"text-xs text-text-muted mt-1\">Looking up transaction…</p>}\n </div>\n\n {/* Auto-filled fields — shown once transaction is found */}\n {lookedUp && (\n <>\n {/* Transaction summary banner */}\n <div className=\"p-3 bg-status-success-bg-subtle border border-border-subtle rounded-md text-sm space-y-1\">\n <div className=\"flex items-center gap-2 text-status-success-text font-medium\">\n <CheckCircle className=\"size-4\" />\n Transaction found\n </div>\n <div className=\"text-text-muted grid grid-cols-2 gap-x-4 gap-y-0.5 mt-1\">\n <span>\n Account:{' '}\n <span className=\"text-text-primary font-mono text-xs\">\n {lookedUp.billingAccountId.slice(0, 16)}…\n </span>\n </span>\n <span>\n Paid:{' '}\n <span className=\"text-text-primary\">\n {formatCurrency(lookedUp.totalAmount, lookedUp.currency)}\n </span>\n </span>\n <span>\n Provider:{' '}\n <span className=\"text-text-primary capitalize\">{formData.provider}</span>\n </span>\n <span>\n Currency: <span className=\"text-text-primary\">{lookedUp.currency}</span>\n </span>\n </div>\n </div>\n\n {/* Read-only auto-filled fields */}\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs font-medium text-text-muted mb-1\">Provider</label>\n <div className=\"px-3 py-2 border border-border-subtle rounded-md bg-bg-sunken/30 text-sm capitalize\">\n {formData.provider}\n </div>\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-muted mb-1\">Currency</label>\n <div className=\"px-3 py-2 border border-border-subtle rounded-md bg-bg-sunken/30 text-sm\">\n {formData.currency}\n </div>\n </div>\n </div>\n\n <div>\n <label className=\"block text-xs font-medium text-text-muted mb-1\">\n Gateway Payment ID\n </label>\n <div className=\"px-3 py-2 border border-border-subtle rounded-md bg-bg-sunken/30 text-sm font-mono truncate\">\n {formData.gatewayPaymentId}\n </div>\n </div>\n\n {formData.purchaseType && (\n <div className=\"grid grid-cols-2 gap-3\">\n <div>\n <label className=\"block text-xs font-medium text-text-muted mb-1\">\n Purchase Type\n </label>\n <div className=\"px-3 py-2 border border-border-subtle rounded-md bg-bg-sunken/30 text-sm\">\n {formData.purchaseType}\n </div>\n </div>\n <div>\n <label className=\"block text-xs font-medium text-text-muted mb-1\">\n Purchase Detail\n </label>\n <div className=\"px-3 py-2 border border-border-subtle rounded-md bg-bg-sunken/30 text-sm truncate\">\n {formData.purchaseDetail || '—'}\n </div>\n </div>\n </div>\n )}\n\n {/* Editable: Amount (capped at transaction amount) */}\n <div>\n <label className=\"block text-sm font-medium mb-1\">\n Refund Amount *\n <span className=\"text-xs text-text-muted ml-1\">\n (max {formatCurrency(maxAmount!, formData.currency)})\n </span>\n </label>\n <input\n type=\"number\"\n step=\"0.01\"\n min=\"0.01\"\n max={maxAmount}\n value={formData.amount}\n onChange={(e) =>\n setFormData({ ...formData, amount: parseFloat(e.target.value) || 0 })\n }\n className={`w-full px-3 py-2 border rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] ${\n isAmountExceedingMax ? 'border-status-error-text' : 'border-border-subtle'\n }`}\n required\n />\n {isAmountExceedingMax && (\n <p className=\"text-xs text-status-error-text mt-1\">\n Amount cannot exceed the original payment of{' '}\n {formatCurrency(maxAmount!, formData.currency)}\n </p>\n )}\n </div>\n\n {/* Editable: Reason & Notes */}\n <div>\n <label className=\"block text-sm font-medium mb-1\">Reason</label>\n <input\n type=\"text\"\n value={formData.reason || ''}\n onChange={(e) => setFormData({ ...formData, reason: e.target.value })}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)]\"\n placeholder=\"Reason for refund\"\n />\n </div>\n\n <div>\n <label className=\"block text-sm font-medium mb-1\">Notes</label>\n <textarea\n value={formData.notes || ''}\n onChange={(e) => setFormData({ ...formData, notes: e.target.value })}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-input bg-bg-surface text-text-primary focus:outline-none focus:ring-2 focus:ring-[var(--color-focus-ring)] resize-none\"\n rows={2}\n placeholder=\"Internal notes\"\n />\n </div>\n </>\n )}\n\n <div className=\"flex gap-2 pt-2\">\n <button\n type=\"button\"\n onClick={handleClose}\n className=\"flex-1 py-2 px-4 border border-border-subtle rounded-md hover:bg-bg-sunken/50 transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={\n isSubmitting ||\n !lookedUp ||\n !formData.gatewayPaymentId ||\n formData.amount <= 0 ||\n isAmountExceedingMax\n }\n className=\"flex-1 py-2 px-4 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bgHover transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2\"\n >\n {isSubmitting && <Loader2 className=\"size-4 animate-spin\" />}\n Create Refund\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n\n// Skip Refund Modal\ninterface SkipRefundModalProps {\n isOpen: boolean;\n record: RefundRecord | null;\n onClose: () => void;\n onSubmit: (reason: string) => Promise<void>;\n isSubmitting: boolean;\n}\n\nconst SkipRefundModal: FC<SkipRefundModalProps> = ({\n isOpen,\n record,\n onClose,\n onSubmit,\n isSubmitting,\n}) => {\n const [reason, setReason] = useState('');\n\n const handleSubmit = async (e: React.FormEvent) => {\n e.preventDefault();\n await onSubmit(reason);\n setReason('');\n };\n\n if (!isOpen || !record) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={onClose} />\n <div className=\"relative bg-bg-elevated border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-md w-full mx-4\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h2 className=\"text-lg font-semibold\">Skip Refund</h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded-md hover:bg-bg-sunken/50 transition-colors\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n <form onSubmit={handleSubmit} className=\"p-4 space-y-4\">\n <div\n className={`p-3 ${statusTokens.warning.bg} border ${statusTokens.warning.border} rounded-md`}\n >\n <div className=\"flex items-start gap-2\">\n <AlertTriangle className={`size-5 ${statusTokens.warning.icon} mt-0.5`} />\n <div>\n <p className={`text-sm font-medium ${statusTokens.warning.text}`}>\n You are about to skip this refund\n </p>\n <p className={`text-sm ${statusTokens.warning.text} mt-1`}>\n Amount: {formatCurrency(record.amount, record.currency)}\n </p>\n </div>\n </div>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium mb-1\">Reason for skipping *</label>\n <textarea\n value={reason}\n onChange={(e) => setReason(e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface resize-none\"\n rows={3}\n placeholder=\"Explain why this refund is being skipped...\"\n required\n />\n </div>\n\n <div className=\"flex gap-2\">\n <button\n type=\"button\"\n onClick={onClose}\n className=\"flex-1 py-2 px-4 border border-border-subtle rounded-md hover:bg-bg-sunken/50 transition-colors\"\n >\n Cancel\n </button>\n <button\n type=\"submit\"\n disabled={isSubmitting || !reason.trim()}\n className={`flex-1 py-2 px-4 ${statusTokens.warning.dot} text-action-primary-text rounded-md hover:opacity-90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed flex items-center justify-center gap-2`}\n >\n {isSubmitting && <Loader2 className=\"size-4 animate-spin\" />}\n Skip Refund\n </button>\n </div>\n </form>\n </div>\n </div>\n );\n};\n\n// Refund Details Modal\ninterface RefundDetailsModalProps {\n record: RefundRecord | null;\n onClose: () => void;\n}\n\nconst RefundDetailsModal: FC<RefundDetailsModalProps> = ({ record, onClose }) => {\n if (!record) return null;\n\n const StatusIcon = STATUS_CONFIG[record.status].icon;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center\">\n <div className=\"absolute inset-0 bg-overlay-scrim\" onClick={onClose} />\n <div className=\"relative bg-bg-elevated border border-border-seam rounded-card shadow-[var(--shadow-elevation-4)] max-w-lg w-full mx-4 max-h-[90vh] overflow-auto\">\n <div className=\"flex items-center justify-between p-4 border-b border-border-subtle\">\n <h2 className=\"text-lg font-semibold\">Refund Details</h2>\n <button\n type=\"button\"\n onClick={onClose}\n className=\"p-1 rounded-md hover:bg-bg-sunken/50 transition-colors\"\n >\n <X className=\"size-5\" />\n </button>\n </div>\n\n <div className=\"p-4 space-y-4\">\n {/* Status Banner */}\n <div\n className={`flex items-center gap-2 p-3 rounded-md ${getStatusBadgeClasses(STATUS_CONFIG[record.status].color)}`}\n >\n <StatusIcon className=\"size-5\" />\n <span className=\"font-medium\">{STATUS_CONFIG[record.status].label}</span>\n </div>\n\n {/* Basic Info */}\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Refund ID</p>\n <p className=\"text-sm font-mono break-all\">{record.id}</p>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Source</p>\n <p className=\"text-sm\">{SOURCE_LABELS[record.source]}</p>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Amount</p>\n <p className=\"text-lg font-semibold\">\n {formatCurrency(record.amount, record.currency)}\n </p>\n </div>\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Provider</p>\n <p className=\"text-sm capitalize\">{record.provider}</p>\n </div>\n </div>\n\n <div className=\"grid grid-cols-2 gap-4\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Gateway Payment ID</p>\n <p className=\"text-sm font-mono break-all\">{record.gatewayPaymentId}</p>\n </div>\n {record.gatewayOrderId && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Gateway Order ID</p>\n <p className=\"text-sm font-mono break-all\">{record.gatewayOrderId}</p>\n </div>\n )}\n </div>\n\n {record.gatewayRefundId && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Gateway Refund ID</p>\n <p className=\"text-sm font-mono break-all\">{record.gatewayRefundId}</p>\n </div>\n )}\n\n {(record.purchaseType || record.purchaseDetail) && (\n <div className=\"grid grid-cols-2 gap-4\">\n {record.purchaseType && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Purchase Type</p>\n <p className=\"text-sm\">{record.purchaseType}</p>\n </div>\n )}\n {record.purchaseDetail && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Purchase Detail</p>\n <p className=\"text-sm\">{record.purchaseDetail}</p>\n </div>\n )}\n </div>\n )}\n\n {record.reason && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Reason</p>\n <p className=\"text-sm\">{record.reason}</p>\n </div>\n )}\n\n {record.notes && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Notes</p>\n <p className=\"text-sm\">{record.notes}</p>\n </div>\n )}\n\n {record.errorMessage && (\n <div\n className={`p-3 ${statusTokens.error.bg} border ${statusTokens.error.border} rounded-md`}\n >\n <p className={`text-xs ${statusTokens.error.text} mb-1`}>Error Message</p>\n <p className={`text-sm ${statusTokens.error.text}`}>{record.errorMessage}</p>\n </div>\n )}\n\n <div className=\"grid grid-cols-2 gap-4 pt-2 border-t border-border-subtle\">\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Created At</p>\n <p className=\"text-sm\">{formatDateTime(record.createdAt)}</p>\n </div>\n {record.processedAt && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Processed At</p>\n <p className=\"text-sm\">{formatDateTime(record.processedAt)}</p>\n </div>\n )}\n </div>\n\n {record.retryCount > 0 && (\n <div>\n <p className=\"text-xs text-text-muted mb-1\">Retry Count</p>\n <p className=\"text-sm\">{record.retryCount}</p>\n </div>\n )}\n </div>\n\n <div 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-bgHover transition-colors\"\n >\n Close\n </button>\n </div>\n </div>\n </div>\n );\n};\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\ntype TabStatus = 'PENDING' | 'COMPLETED' | 'FAILED' | 'ALL';\n\nconst TAB_CONFIG: Record<TabStatus, { label: string; status?: RefundRecordStatus }> = {\n PENDING: { label: 'Pending', status: 'PENDING' },\n COMPLETED: { label: 'Completed', status: 'COMPLETED' },\n FAILED: { label: 'Failed', status: 'FAILED' },\n ALL: { label: 'All', status: undefined },\n};\n\nexport const RefundsPage: 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 permissions = useBillingPermissions();\n\n // State\n const [activeTab, setActiveTab] = useState<TabStatus>('PENDING');\n const [searchQuery, setSearchQuery] = useState('');\n const [filters, setFilters] = useState<RefundRecordFilters>({});\n const [showFilters, setShowFilters] = useState(false);\n const [selectedRecord, setSelectedRecord] = useState<RefundRecord | null>(null);\n const [showCreateModal, setShowCreateModal] = useState(false);\n const [showSkipModal, setShowSkipModal] = useState(false);\n const [recordToSkip, setRecordToSkip] = useState<RefundRecord | null>(null);\n const [actionLoadingId, setActionLoadingId] = useState<string | null>(null);\n\n // Build filters based on active tab - memoized to prevent infinite re-render loop\n // (useRefundRecords has a useEffect on options.filters, so a new object every render would cause\n // infinite updates if tabFilters were not stable)\n const tabFilters = useMemo<RefundRecordFilters>(\n () => ({\n ...filters,\n status: TAB_CONFIG[activeTab].status,\n }),\n // eslint-disable-next-line react-hooks/exhaustive-deps -- filters is an object; spread ensures correct deps\n [\n activeTab,\n filters.source,\n filters.provider,\n filters.billingAccountId,\n filters.fromDate,\n filters.toDate,\n ]\n );\n\n // Data hooks - filters automatically applied based on active tab\n const {\n records,\n stats,\n pagination,\n isLoading,\n recordsError,\n unprocessedWebhooks,\n refetch,\n goToNextPage,\n goToPreviousPage,\n setFilters: updateFilters,\n createRefundRecord,\n processRefund,\n skipRefund,\n retryRefund,\n createRefundRecordsFromFailedWebhooks,\n isCreating,\n isProcessing,\n isCreatingFromWebhooks,\n } = useRefunds({ filters: tabFilters });\n\n // Permission check\n if (!permissions.canViewPayments) {\n return <AccessDenied message=\"You don't have permission to view refunds.\" />;\n }\n\n // Error handling\n if (recordsError && isServerError(recordsError)) {\n return (\n <div className=\"p-6\">\n <ServerError\n title=\"Server Unavailable\"\n message=\"Unable to load refunds. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n // Handlers\n const handleTabChange = (tab: TabStatus) => {\n setActiveTab(tab);\n // Reset filters when changing tabs\n setFilters({});\n setSearchQuery('');\n };\n\n const handleFilterChange = (key: keyof RefundRecordFilters, value: string | undefined) => {\n // Don't allow status filter changes as tabs control status\n if (key === 'status') return;\n\n const newFilters = { ...filters, [key]: value || undefined };\n setFilters(newFilters);\n updateFilters({ ...newFilters, status: TAB_CONFIG[activeTab].status });\n };\n\n const handleCreateRefund = async (data: CreateRefundFormData) => {\n try {\n await createRefundRecord(data);\n setShowCreateModal(false);\n } catch (error) {\n console.error('Failed to create refund:', error);\n }\n };\n\n const handleProcess = async (record: RefundRecord) => {\n setActionLoadingId(record.id);\n try {\n const result = await processRefund(record.id);\n if (!result.success && result.error) {\n console.error('Refund processing failed:', result.error);\n }\n } catch (error) {\n console.error('Failed to process refund:', error);\n } finally {\n setActionLoadingId(null);\n }\n };\n\n const handleSkip = async (reason: string) => {\n if (!recordToSkip) return;\n try {\n await skipRefund(recordToSkip.id, reason);\n setShowSkipModal(false);\n setRecordToSkip(null);\n } catch (error) {\n console.error('Failed to skip refund:', error);\n }\n };\n\n const handleRetry = async (record: RefundRecord) => {\n setActionLoadingId(record.id);\n try {\n const result = await retryRefund(record.id);\n if (!result.success && result.error) {\n console.error('Refund retry failed:', result.error);\n }\n } catch (error) {\n console.error('Failed to retry refund:', error);\n } finally {\n setActionLoadingId(null);\n }\n };\n\n const handleCreateFromWebhooks = async () => {\n try {\n await createRefundRecordsFromFailedWebhooks();\n } catch (error) {\n console.error('Failed to create refunds from webhooks:', error);\n }\n };\n\n // Filter records by search query (client-side)\n const filteredRecords = searchQuery\n ? records.filter(\n (r) =>\n r.gatewayPaymentId.toLowerCase().includes(searchQuery.toLowerCase()) ||\n r.gatewayOrderId?.toLowerCase().includes(searchQuery.toLowerCase()) ||\n r.reason?.toLowerCase().includes(searchQuery.toLowerCase())\n )\n : records;\n\n const refundColumns: ResponsiveTableColumn<RefundRecord>[] = [\n {\n key: 'date',\n header: 'Date',\n priority: 'primary',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <div className=\"flex flex-col\">\n <span className=\"text-sm text-text-primary\">\n {formatDateTime(record.createdAt).split(' ')[0]}\n </span>\n <span className=\"text-xs text-text-muted\">\n {formatDateTime(record.createdAt).split(' ')[1]}\n </span>\n </div>\n ),\n },\n {\n key: 'status',\n header: 'Status',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => {\n const StatusIcon = STATUS_CONFIG[record.status].icon;\n return (\n <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${getStatusBadgeClasses(\n STATUS_CONFIG[record.status].color\n )}`}\n >\n <StatusIcon className=\"size-3\" />\n <span className=\"hidden sm:inline\">{STATUS_CONFIG[record.status].label}</span>\n </span>\n );\n },\n },\n {\n key: 'source',\n header: 'Source',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm text-text-muted\">{SOURCE_LABELS[record.source]}</span>\n ),\n },\n {\n key: 'provider',\n header: 'Provider',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm text-text-primary capitalize\">{record.provider}</span>\n ),\n },\n {\n key: 'paymentId',\n header: 'Payment ID',\n cellClassName: 'whitespace-nowrap max-w-[120px] sm:max-w-[200px] truncate',\n cell: (record) => (\n <span className=\"text-sm font-mono text-text-muted\">{record.gatewayPaymentId}</span>\n ),\n },\n {\n key: 'amount',\n header: 'Amount',\n headerClassName: 'text-right',\n cellClassName: 'text-right whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm font-medium tabular-nums\">\n {formatCurrency(record.amount, record.currency)}\n </span>\n ),\n },\n {\n key: 'reason',\n header: 'Reason',\n priority: 'hidden-on-mobile',\n cellClassName: 'max-w-[200px] truncate',\n cell: (record) => <span className=\"text-sm text-text-muted\">{record.reason || '-'}</span>,\n },\n ];\n\n const renderRefundActions = (record: RefundRecord) => (\n <RefundActionsDropdown\n record={record}\n onViewDetails={() => setSelectedRecord(record)}\n onProcess={() => handleProcess(record)}\n onSkip={() => {\n setRecordToSkip(record);\n setShowSkipModal(true);\n }}\n onRetry={() => handleRetry(record)}\n isProcessing={actionLoadingId === record.id}\n />\n );\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=\"grid grid-cols-5 gap-4\">\n {[1, 2, 3, 4, 5].map((i) => (\n <div key={i} className=\"h-24 bg-bg-sunken animate-pulse rounded-lg\" />\n ))}\n </div>\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 return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title={tr('billing.refunds.title', 'Refund Management')}\n description=\"View and manage refund records\"\n actions={\n <CTAOverflowMenu\n primary={{\n label: 'Create Manual Refund',\n onSelect: () => setShowCreateModal(true),\n icon: <Plus className=\"size-4\" />,\n }}\n actions={\n [\n ...(unprocessedWebhooks.length > 0\n ? [\n {\n label: `Import ${unprocessedWebhooks.length} Failed Webhooks`,\n onSelect: handleCreateFromWebhooks,\n disabled: isCreatingFromWebhooks,\n icon: isCreatingFromWebhooks ? (\n <Loader2 className=\"size-4 animate-spin\" />\n ) : (\n <Download className=\"size-4\" />\n ),\n },\n ]\n : []),\n {\n label: 'Refresh',\n onSelect: () => {\n void refetch();\n },\n icon: <RefreshCw className=\"size-4\" />,\n },\n ] satisfies CTAAction[]\n }\n />\n }\n />\n\n <PagePurpose>\n {tr(\n 'billing.refunds.purpose',\n 'Track and act on every refund across all billing accounts — manual refunds, ones triggered by cancellations, and those recovered from failed payment-gateway webhooks. Use it to process pending refunds, retry failed ones, skip a refund with a reason, or create a manual refund for a specific transaction.'\n )}\n </PagePurpose>\n\n {/* Tabs Navigation */}\n <div className=\"border-b border-border-subtle\">\n <div className=\"flex flex-wrap gap-2 sm:gap-0\">\n {(Object.keys(TAB_CONFIG) as TabStatus[]).map((tab) => {\n const isActive = activeTab === tab;\n const count = stats\n ? tab === 'PENDING'\n ? stats.pending\n : tab === 'COMPLETED'\n ? stats.completed\n : tab === 'FAILED'\n ? stats.failed\n : stats.pending +\n stats.processing +\n stats.completed +\n stats.failed +\n stats.skipped\n : 0;\n\n return (\n <button\n type=\"button\"\n key={tab}\n onClick={() => handleTabChange(tab)}\n className={`flex items-center gap-2 px-4 py-3 text-sm font-medium border-b-2 transition-colors whitespace-nowrap ${\n isActive\n ? 'border-action-primary-bg text-text-link'\n : 'border-transparent text-text-muted hover:text-text-primary hover:border-border-subtle'\n }`}\n >\n {TAB_CONFIG[tab].label}\n {stats && (\n <span\n className={`px-2 py-0.5 text-xs rounded-full ${\n isActive\n ? 'bg-[var(--color-accent-soft)] text-text-link'\n : 'bg-bg-sunken text-text-muted'\n }`}\n >\n {count}\n </span>\n )}\n </button>\n );\n })}\n </div>\n </div>\n\n {/* Filters */}\n <div className=\"flex flex-col sm:flex-row gap-4\">\n {/* Search */}\n <div className=\"relative flex-1\">\n <Search className=\"absolute left-3 top-1/2 -translate-y-1/2 size-4 text-text-muted\" />\n <input\n type=\"text\"\n placeholder=\"Search by payment ID, order ID, or reason...\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n className=\"w-full pl-[3.25rem] pr-4 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n />\n </div>\n\n {/* Filter Toggle */}\n <button\n type=\"button\"\n onClick={() => setShowFilters(!showFilters)}\n className={`inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md border transition-colors ${\n showFilters\n ? 'border-action-primary-bg bg-[var(--color-accent-soft)] text-text-link'\n : 'border-border-subtle bg-bg-surface hover:bg-bg-sunken/50'\n }`}\n >\n <Filter className=\"size-4\" />\n Filters\n {Object.values(filters).filter(Boolean).length > 0 && (\n <span className=\"px-1.5 py-0.5 text-xs rounded-full bg-action-primary-bg text-action-primary-text\">\n {Object.values(filters).filter(Boolean).length}\n </span>\n )}\n </button>\n\n {/* Clear Filters */}\n {Object.values(filters).filter(Boolean).length > 0 && (\n <button\n type=\"button\"\n onClick={() => {\n setFilters({});\n updateFilters({});\n }}\n className=\"inline-flex items-center gap-2 px-3 py-2 text-sm font-medium rounded-md text-text-muted hover:text-text-primary transition-colors\"\n >\n <X className=\"size-4\" />\n Clear\n </button>\n )}\n </div>\n\n {/* Expanded Filters */}\n {showFilters && (\n <div className=\"p-4 border border-border-subtle rounded-lg bg-bg-sunken/30 grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\">\n <div>\n <label className=\"block text-sm font-medium mb-1\">Source</label>\n <select\n value={filters.source || ''}\n onChange={(e) => handleFilterChange('source', e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All Sources</option>\n {Object.entries(SOURCE_LABELS).map(([key, label]) => (\n <option key={key} value={key}>\n {label}\n </option>\n ))}\n </select>\n </div>\n\n <div>\n <label className=\"block text-sm font-medium mb-1\">Provider</label>\n <select\n value={filters.provider || ''}\n onChange={(e) => handleFilterChange('provider', e.target.value)}\n className=\"w-full px-3 py-2 border border-border-subtle rounded-md bg-bg-surface\"\n >\n <option value=\"\">All Providers</option>\n {PROVIDER_OPTIONS.map((p) => (\n <option key={p} value={p}>\n {p.charAt(0).toUpperCase() + p.slice(1)}\n </option>\n ))}\n </select>\n </div>\n\n <div className=\"sm:col-span-2 lg:col-span-1\">\n <label className=\"block text-sm font-medium mb-1\">Date Range</label>\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-2\">\n <input\n type=\"date\"\n value={filters.fromDate ? filters.fromDate.split('T')[0] : ''}\n onChange={(e) =>\n handleFilterChange(\n 'fromDate',\n e.target.value ? new Date(e.target.value).toISOString() : undefined\n )\n }\n className=\"w-full sm:flex-1 p-2 border border-border-subtle rounded-md bg-bg-surface text-sm\"\n />\n <span className=\"text-text-muted hidden sm:inline\">-</span>\n <input\n type=\"date\"\n value={filters.toDate ? filters.toDate.split('T')[0] : ''}\n onChange={(e) =>\n handleFilterChange(\n 'toDate',\n e.target.value ? new Date(e.target.value).toISOString() : undefined\n )\n }\n className=\"w-full sm:flex-1 p-2 border border-border-subtle rounded-md bg-bg-surface text-sm\"\n />\n </div>\n </div>\n </div>\n )}\n\n {/* Table */}\n {filteredRecords.length === 0 ? (\n <IllustratedEmptyState\n illustration={\n Object.values(filters).filter(Boolean).length > 0 ? 'empty-search' : 'empty-data'\n }\n title=\"No refund records found\"\n description={\n Object.values(filters).filter(Boolean).length > 0\n ? 'Try adjusting your filters.'\n : activeTab === 'PENDING'\n ? 'No pending refunds to process.'\n : 'Refund records will appear here when created.'\n }\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<RefundRecord>\n columns={refundColumns}\n data={filteredRecords}\n rowKey={(record) => record.id}\n rowActions={renderRefundActions}\n />\n </div>\n )}\n\n {/* Pagination */}\n {pagination.totalCount > 0 && (\n <div className=\"flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4\">\n <p className=\"text-sm text-text-muted text-center sm:text-left\">\n Showing {pagination.page * pagination.pageSize + 1} to{' '}\n {Math.min((pagination.page + 1) * pagination.pageSize, pagination.totalCount)} of{' '}\n {pagination.totalCount} refunds\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-button 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 <span className=\"hidden sm:inline\">Previous</span>\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n <span className=\"hidden sm:inline\">Page </span>\n {pagination.page + 1} <span className=\"hidden sm:inline\">of</span>\n <span className=\"sm:hidden\">/</span>{' '}\n {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-button border border-border-subtle bg-bg-surface hover:bg-bg-sunken/50 disabled:opacity-50 disabled:cursor-not-allowed transition-colors\"\n >\n <span className=\"hidden sm:inline\">Next</span>\n <ChevronRight className=\"size-4\" />\n </button>\n </div>\n </div>\n )}\n\n {/* Modals */}\n <CreateRefundModal\n isOpen={showCreateModal}\n onClose={() => setShowCreateModal(false)}\n onSubmit={handleCreateRefund}\n isSubmitting={isCreating}\n />\n\n <SkipRefundModal\n isOpen={showSkipModal}\n record={recordToSkip}\n onClose={() => {\n setShowSkipModal(false);\n setRecordToSkip(null);\n }}\n onSubmit={handleSkip}\n isSubmitting={isProcessing}\n />\n\n <RefundDetailsModal record={selectedRecord} onClose={() => setSelectedRecord(null)} />\n </div>\n );\n};\n"],"mappings":"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"}
|
|
@@ -285,20 +285,21 @@ function u(u = {}) {
|
|
|
285
285
|
}
|
|
286
286
|
}
|
|
287
287
|
if (a === "CREDITS") {
|
|
288
|
-
let e = _.filter((e) => e.type === "ADDON"),
|
|
288
|
+
let e = _.filter((e) => e.type === "ADDON"), n = _.find((e) => e.type === "PLAN"), { data: a } = await fe({ variables: { input: {
|
|
289
289
|
billingAccountId: y.id,
|
|
290
|
-
|
|
290
|
+
orderId: t === "ORDER" ? r : void 0,
|
|
291
|
+
planId: n ? n.metadata?.planId || n.id : void 0,
|
|
291
292
|
addOnIds: e.length > 0 ? e.map((e) => ({
|
|
292
293
|
id: e.metadata?.addonId || e.id,
|
|
293
294
|
quantity: e.quantity
|
|
294
295
|
})) : void 0,
|
|
295
|
-
isAnnual:
|
|
296
|
+
isAnnual: n?.metadata?.duration === "yearly",
|
|
296
297
|
upgradeFromSubscriptionId: m || void 0,
|
|
297
298
|
downgradeFromSubscriptionId: h || void 0,
|
|
298
299
|
subscriptionId: e.length > 0 ? W : void 0,
|
|
299
|
-
seatCount:
|
|
300
|
-
} } }),
|
|
301
|
-
return
|
|
300
|
+
seatCount: n?.metadata?.seatCount
|
|
301
|
+
} } }), o = a?.spendCredits;
|
|
302
|
+
return o?.creditTransactionID ? (P("success"), {
|
|
302
303
|
success: !0,
|
|
303
304
|
provider: "CREDITS",
|
|
304
305
|
subtotal: i,
|
|
@@ -308,7 +309,7 @@ function u(u = {}) {
|
|
|
308
309
|
taxType: "NONE",
|
|
309
310
|
jurisdiction: "UNKNOWN",
|
|
310
311
|
currency: S,
|
|
311
|
-
orderId:
|
|
312
|
+
orderId: o.creditTransactionID
|
|
312
313
|
}) : (P("error"), F("Failed to process credit payment"), {
|
|
313
314
|
success: !1,
|
|
314
315
|
provider: "CREDITS",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useCheckout.ts"],"sourcesContent":["/**\n * Main checkout hook for managing checkout state and payment processing\n */\n\nimport { useState, useCallback, useMemo, useEffect } from 'react';\nimport {\n useInitiatePaymentCheckoutMutation,\n useSpendCreditsCheckoutMutation,\n useConvertCurrencyLazyQuery,\n useValidateCouponLazyQuery,\n} from '../../../../generated/global-operations';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useEnsureStoreOrderBillingOrder } from './useStoreShipping';\nimport type {\n CheckoutItem,\n CheckoutBillingAccount,\n PaymentProvider,\n Currency,\n CheckoutState,\n PaymentInitResponse,\n PromoCodeResult,\n PurchaseType,\n} from '../types';\n\n/**\n * Credit conversion rate: How many credits equal 1 USD\n * This should match the backend creditRate.rateUSD (default 0.01 means 100 credits = $1)\n */\n// Exported so callers that need a per-item (rather than aggregate) credits\n// amount — e.g. the success page's itemized \"Items Purchased\" list — convert\n// consistently with creditsRequired/hasEnoughCredits below, instead of each\n// re-declaring their own copy of this rate.\nexport const CREDITS_PER_USD = 100;\n\ninterface UseCheckoutOptions {\n billingAccountId?: string;\n defaultCurrency?: Currency;\n defaultSaveCard?: boolean;\n upgradeFromSubscriptionId?: string;\n /** For plan downgrades — old sub auto-renewal cancelled, runs to natural expiry */\n downgradeFromSubscriptionId?: string;\n /** For addon purchases - pre-selected subscription ID */\n defaultSubscriptionId?: string;\n}\n\ninterface UseCheckoutResult {\n // State\n items: CheckoutItem[];\n billingAccount: CheckoutBillingAccount | null;\n selectedProvider: PaymentProvider;\n selectedCurrency: Currency;\n promoCode: string;\n promoDiscount: number;\n promoDiscountType: 'PERCENTAGE' | 'FIXED';\n useCredits: boolean;\n creditsToUse: number;\n checkoutState: CheckoutState;\n error: string | null;\n\n // Saved card state\n selectedPaymentMethodId: string | null;\n saveCard: boolean;\n\n // Auto-pay state (for subscription purchases)\n autoRenew: boolean;\n autoRenewPaymentMethodId: string | null;\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId: string | null;\n\n // Computed values\n subtotal: number;\n subtotalUSD: number;\n discountAmount: number;\n taxAmount: number;\n taxRate: number;\n taxType: string;\n total: number;\n totalInSelectedCurrency: number;\n availableCredits: number;\n /** subtotalUSD converted to credits (CREDITS_PER_USD) — the actual amount a\n * CREDITS-provider purchase charges, in credits. total/totalInSelectedCurrency\n * are in the selected display currency and must never be shown/used as a\n * credits amount (they're a different number). */\n creditsRequired: number;\n hasEnoughCredits: boolean;\n canPayWithCredits: boolean;\n isLoading: boolean;\n isProcessing: boolean;\n isCalculatingTax: boolean;\n\n // Currency conversion\n conversionRate: number;\n convertedAmount: number | null;\n\n // Actions\n addItem: (item: CheckoutItem) => void;\n removeItem: (itemId: string) => void;\n updateItemQuantity: (itemId: string, quantity: number) => void;\n updateItemPrice: (itemId: string, price: number, currency?: Currency) => void;\n clearItems: () => void;\n setBillingAccount: (account: CheckoutBillingAccount | null) => void;\n setPaymentProvider: (provider: PaymentProvider) => void;\n setCurrency: (currency: Currency) => void;\n setPromoCode: (code: string) => void;\n validatePromoCode: () => Promise<PromoCodeResult>;\n clearPromoCode: () => void;\n setUseCredits: (use: boolean) => void;\n setCreditsToUse: (amount: number) => void;\n setSelectedPaymentMethodId: (id: string | null) => void;\n setSaveCard: (save: boolean) => void;\n setAutoRenew: (enabled: boolean) => void;\n setAutoRenewPaymentMethodId: (id: string | null) => void;\n setSelectedSubscriptionId: (id: string | null) => void;\n initiatePayment: () => Promise<PaymentInitResponse>;\n reset: () => void;\n}\n\nexport function useCheckout(options: UseCheckoutOptions = {}): UseCheckoutResult {\n const { currentUser } = useBilling();\n const {\n defaultCurrency = 'USD',\n defaultSaveCard = false, // CRITICAL: Never save cards without explicit user consent\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n defaultSubscriptionId,\n } = options;\n\n // Core state\n const [items, setItems] = useState<CheckoutItem[]>([]);\n const [billingAccount, setBillingAccount] = useState<CheckoutBillingAccount | null>(null);\n const [selectedProvider, setSelectedProvider] = useState<PaymentProvider>('AUTO');\n const [selectedCurrency, setSelectedCurrency] = useState<Currency>(defaultCurrency);\n const [promoCode, setPromoCode] = useState('');\n const [promoDiscount, setPromoDiscount] = useState(0);\n const [promoDiscountType, setPromoDiscountType] = useState<'PERCENTAGE' | 'FIXED'>('PERCENTAGE');\n const [useCredits, setUseCredits] = useState(false);\n const [creditsToUse, setCreditsToUse] = useState(0);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [taxAmount, setTaxAmount] = useState(0);\n const [taxRate, setTaxRate] = useState(0);\n const [taxType, setTaxType] = useState<string>('NONE');\n\n // Saved card state\n const [selectedPaymentMethodId, setSelectedPaymentMethodId] = useState<string | null>(null);\n const [saveCard, setSaveCard] = useState(defaultSaveCard);\n\n // Auto-pay state (for subscription purchases)\n const [autoRenew, setAutoRenew] = useState(false);\n const [autoRenewPaymentMethodId, setAutoRenewPaymentMethodId] = useState<string | null>(null);\n\n // Subscription state (for addon purchases)\n const [selectedSubscriptionId, setSelectedSubscriptionId] = useState<string | null>(\n defaultSubscriptionId ?? null\n );\n\n // GraphQL mutations and queries\n const [initiatePaymentMutation] = useInitiatePaymentCheckoutMutation();\n const [ensureStoreOrderBillingOrderMutation] = useEnsureStoreOrderBillingOrder();\n const [spendCreditsMutation] = useSpendCreditsCheckoutMutation();\n const [convertCurrency, { data: currencyData, loading: currencyLoading }] =\n useConvertCurrencyLazyQuery();\n const [validateCoupon] = useValidateCouponLazyQuery();\n\n // Computed values\n\n // The currency `total`/`subtotal` are actually denominated in. Plans,\n // addons, and credits are priced in USD; store orders are priced in the\n // order's own currency (item.currency), which may not be USD. Converting\n // `total` to the selected currency must pivot FROM this currency, not\n // always assume USD — see BOFF-7127 (a ₹50 item was shown as $4,748.50\n // because the conversion always requested a USD→X rate and multiplied,\n // which is only correct when the source amount actually is in USD).\n const nativeCurrency = useMemo<Currency>(() => items[0]?.currency ?? 'USD', [items]);\n\n const subtotal = useMemo(() => {\n return items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n }, [items]);\n\n // USD subtotal — always use priceUSD (base price) for credit calculations.\n // item.price may be in a local currency (INR, EUR, etc.) when currencyPrices is set.\n const subtotalUSD = useMemo(() => {\n return items.reduce((sum, item) => sum + (item.priceUSD ?? item.price) * item.quantity, 0);\n }, [items]);\n\n const discountAmount = useMemo(() => {\n if (!promoDiscount) return 0;\n // promoDiscount now contains the actual discount amount from backend validation\n // (not the percentage value), so use it directly\n return Math.min(promoDiscount, subtotal);\n }, [subtotal, promoDiscount]);\n\n // Total includes tax (calculated via calculateTax query)\n const total = useMemo(() => {\n const afterDiscount = subtotal - discountAmount;\n const withCredits = useCredits ? Math.max(0, afterDiscount - creditsToUse) : afterDiscount;\n return withCredits + taxAmount;\n }, [subtotal, discountAmount, useCredits, creditsToUse, taxAmount]);\n\n const availableCredits = billingAccount?.creditAmount ?? 0;\n\n // Use subtotalUSD so this works correctly for any display currency\n const creditsRequired = useMemo(() => subtotalUSD * CREDITS_PER_USD, [subtotalUSD]);\n\n const hasEnoughCredits = useMemo(\n () => availableCredits >= creditsRequired,\n [availableCredits, creditsRequired]\n );\n\n // Can only pay with credits if not buying credits\n const canPayWithCredits = useMemo(() => {\n const hasCreditsPurchase = items.some((item) => item.type === 'CREDITS');\n return !hasCreditsPurchase && availableCredits > 0;\n }, [items, availableCredits]);\n\n // Currency conversion result\n const convertedAmount = currencyData?.convertCurrency?.amount ?? null;\n const conversionRate = currencyData?.convertCurrency?.exchangeRate ?? 1;\n const totalInSelectedCurrency = convertedAmount ?? total;\n\n const isLoading = checkoutState === 'loading' || currencyLoading;\n const isProcessing = checkoutState === 'processing';\n\n // Effect to trigger currency conversion when total or currency changes\n // This ensures the converted amount is always up-to-date\n useEffect(() => {\n // Skip conversion for credit payments (no currency conversion needed)\n if (selectedProvider === 'CREDITS') {\n return;\n }\n\n // Skip if no items yet (cart is still loading)\n if (items.length === 0) {\n return;\n }\n\n // Skip conversion if items already have prices in selected currency\n // (Plans/Addons with multi-currency pricing have currencyPrices stored)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === selectedCurrency || item.type === 'CREDITS'\n );\n\n if (allItemsHaveLocalizedPrices) {\n // Items are already priced in the selected currency (from currencyPrices)\n // No conversion needed - subtotal and total are already correct!\n return;\n }\n\n // Only fetch if we have a valid total and it needs converting at all.\n if (total > 0 && nativeCurrency !== selectedCurrency) {\n // For INR, wait until tax has been calculated (taxAmount > 0 or taxRate is set)\n // This prevents double API calls - one before tax, one after\n if (selectedCurrency === 'INR' && taxAmount === 0 && subtotal > 0) {\n // Tax hasn't been calculated yet, skip this conversion\n // The effect will run again when taxAmount changes\n return;\n }\n\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: selectedCurrency,\n fromCurrency: nativeCurrency,\n },\n });\n }\n }, [\n total,\n selectedCurrency,\n selectedProvider,\n convertCurrency,\n taxAmount,\n subtotal,\n items,\n nativeCurrency,\n ]);\n\n // Effect to calculate tax when billing account, subtotal, or currency changes\n // Tax is only applied for INR payments (18% GST)\n useEffect(() => {\n // Only calculate tax if we have a billing account and items\n if (!billingAccount || subtotal <= 0) {\n // Reset tax if no billing account or empty cart\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Skip if paying with credits (no tax on credit payments)\n if (selectedProvider === 'CREDITS') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Only apply tax for INR payments\n if (selectedCurrency !== 'INR') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // For INR: Calculate 18% GST on the USD amount, then we'll convert for display\n const afterDiscount = subtotal - discountAmount;\n const GST_RATE = 0.18;\n const taxInUSD = afterDiscount * GST_RATE;\n\n setTaxAmount(taxInUSD);\n setTaxRate(GST_RATE);\n setTaxType('GST');\n }, [billingAccount, subtotal, discountAmount, selectedCurrency, selectedProvider]);\n\n // Actions\n const addItem = useCallback((item: CheckoutItem) => {\n setItems((prev) => {\n // Plans can only be purchased once - quantity is always 1\n if (item.type === 'PLAN') {\n // Check if any plan already exists in cart\n const existingPlan = prev.find((i) => i.type === 'PLAN');\n if (existingPlan) {\n // Replace existing plan with new one (can only have 1 plan)\n return prev.map((i) => (i.type === 'PLAN' ? { ...item, quantity: 1 } : i));\n }\n // Add new plan with quantity forced to 1\n return [...prev, { ...item, quantity: 1 }];\n }\n\n // For addons and credits, allow quantity increment\n const existing = prev.find((i) => i.id === item.id);\n if (existing) {\n return prev.map((i) =>\n i.id === item.id ? { ...i, quantity: i.quantity + item.quantity } : i\n );\n }\n return [...prev, item];\n });\n }, []);\n\n const removeItem = useCallback((itemId: string) => {\n setItems((prev) => prev.filter((i) => i.id !== itemId));\n }, []);\n\n const updateItemQuantity = useCallback(\n (itemId: string, quantity: number) => {\n if (quantity <= 0) {\n removeItem(itemId);\n return;\n }\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Plans can only have quantity 1\n if (i.type === 'PLAN') {\n return { ...i, quantity: 1 };\n }\n return { ...i, quantity };\n })\n );\n },\n [removeItem]\n );\n\n const updateItemPrice = useCallback((itemId: string, price: number, currency?: Currency) => {\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Update price and optionally currency\n const updates: Partial<CheckoutItem> = { price };\n if (currency) {\n updates.currency = currency;\n }\n return { ...i, ...updates };\n })\n );\n }, []);\n\n const clearItems = useCallback(() => {\n setItems([]);\n }, []);\n\n const handleSetBillingAccount = useCallback((account: CheckoutBillingAccount | null) => {\n setBillingAccount(account);\n // Reset credits usage when billing account changes — different accounts\n // carry different credit balances, so a CREDITS selection made against\n // the previous account may no longer be valid (or even offered) for\n // this one. Without also resetting the provider, the order summary kept\n // displaying amounts in Credits (and checkout could still submit with\n // provider: 'CREDITS') even after the Credits option had disappeared\n // from the payment method list for the new account.\n setUseCredits(false);\n setCreditsToUse(0);\n setSelectedProvider((prev) => (prev === 'CREDITS' ? 'AUTO' : prev));\n }, []);\n\n const handleSetCurrency = useCallback(\n (currency: Currency) => {\n setSelectedCurrency(currency);\n // Skip conversion if items already have localized prices\n // (Plans/Addons with multi-currency pricing don't need conversion)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === currency || item.type === 'CREDITS'\n );\n\n // Only fetch conversion rate if items don't have localized prices\n if (total > 0 && nativeCurrency !== currency && !allItemsHaveLocalizedPrices) {\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: currency,\n fromCurrency: nativeCurrency,\n },\n });\n }\n },\n [total, convertCurrency, items, nativeCurrency]\n );\n\n const handleSetPaymentProvider = useCallback(\n (provider: PaymentProvider) => {\n setSelectedProvider(provider);\n // Razorpay only supports INR\n if (provider === 'RAZORPAY' && selectedCurrency !== 'INR') {\n setSelectedCurrency('INR');\n }\n // Credits purchases don't support coupons yet (BOFF-7192) — clear any\n // promo applied under a card provider so it can't leak into the\n // credits success screen's displayed total.\n if (provider === 'CREDITS') {\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n }\n },\n [selectedCurrency]\n );\n\n const validatePromoCode = useCallback(async (): Promise<PromoCodeResult> => {\n if (!promoCode.trim()) {\n return {\n valid: false,\n code: '',\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Please enter a promo code',\n };\n }\n\n try {\n setCheckoutState('loading');\n\n // Build validation input\n const validationInput = {\n code: promoCode.trim().toUpperCase(),\n userId: currentUser?.id ?? '',\n userEmail: currentUser?.email,\n amount: subtotal,\n currency: selectedCurrency,\n };\n\n const { data, error: queryError } = await validateCoupon({\n variables: {\n input: validationInput,\n },\n });\n\n if (queryError || !data?.validateCoupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n\n const response = data.validateCoupon;\n\n if (!response.valid) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: response.error || 'Invalid coupon code',\n };\n }\n\n // Extract discount details from coupon\n const coupon = response.coupon;\n if (!coupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Coupon data not found',\n };\n }\n\n // Use the ACTUAL discount amount from validation (includes caps and currency conversion)\n const actualDiscountAmount = response.discountAmount || 0;\n\n const discountType = coupon.type === 'FIXED_AMOUNT' ? 'FIXED' : 'PERCENTAGE';\n setPromoDiscount(actualDiscountAmount); // Use actual discount, not coupon.value\n setPromoDiscountType(discountType);\n setCheckoutState('idle');\n\n return {\n valid: true,\n code: coupon.code,\n discountType,\n discountValue: actualDiscountAmount, // Use actual discount, not coupon.value\n };\n } catch {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n }, [currentUser?.email, currentUser?.id, promoCode, selectedCurrency, subtotal, validateCoupon]);\n\n const clearPromoCode = useCallback(() => {\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n }, []);\n\n const handleSetCreditsToUse = useCallback(\n (amount: number) => {\n const maxCredits = Math.min(amount, availableCredits, subtotal - discountAmount);\n setCreditsToUse(Math.max(0, maxCredits));\n },\n [availableCredits, subtotal, discountAmount]\n );\n\n const initiatePayment = useCallback(\n async (): Promise<PaymentInitResponse> => {\n if (!billingAccount) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Please select a billing account',\n };\n }\n\n if (items.length === 0) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'No items in checkout',\n };\n }\n\n try {\n setCheckoutState('processing');\n setError(null);\n\n // Determine purchase type from first item\n const firstItem = items[0];\n let purchaseType: PurchaseType = firstItem.type;\n\n // Check if this is a store order checkout (detect early)\n const storeOrderItem = items.find((item) => item.metadata?.storeOrderId);\n const storeOrderId = storeOrderItem?.metadata?.storeOrderId as string | undefined;\n let billingOrderId =\n (items.find((item) => item.metadata?.billingOrderId)?.metadata?.billingOrderId as\n string | undefined) || undefined;\n\n // For store orders, use 'ORDER' purchase type\n if (storeOrderId) {\n purchaseType = 'ORDER' as PurchaseType;\n }\n\n // Calculate amount to charge\n const chargeAmount = subtotal - discountAmount;\n\n // Determine provider\n let provider = selectedProvider;\n if (provider === 'AUTO') {\n provider = selectedCurrency === 'INR' ? 'RAZORPAY' : 'STRIPE';\n }\n\n // If this is a store order without a billing order yet, let the store service\n // create it from its authoritative order snapshot. This persists the billing\n // order ID and registers the signed callback on the same service that owns\n // the order; browser line-item prices and shipping values are never sent to\n // billing as the order snapshot.\n if (purchaseType === 'ORDER' && !billingOrderId && storeOrderId && billingAccount) {\n const { data: billingOrderResult } = await ensureStoreOrderBillingOrderMutation({\n variables: {\n storeOrderId,\n billingAccountId: billingAccount.id,\n currency: selectedCurrency,\n },\n });\n const ensureResult = billingOrderResult?.ensureStoreOrderBillingOrder;\n\n if (ensureResult?.success && ensureResult.billingOrderId) {\n billingOrderId = ensureResult.billingOrderId;\n } else {\n const errMsg = ensureResult?.error ?? 'Failed to create billing order';\n setCheckoutState('error');\n setError(errMsg);\n return {\n success: false,\n provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errMsg,\n };\n }\n }\n\n // ========================================\n // CREDITS PAYMENT - use spendCredits mutation\n // Supports multiple addons\n // ========================================\n if (provider === 'CREDITS') {\n const addonItems = items.filter((item) => item.type === 'ADDON');\n const planItem = items.find((item) => item.type === 'PLAN');\n\n const { data } = await spendCreditsMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n planId: planItem ? (planItem.metadata?.planId as string) || planItem.id : undefined,\n // Multiple addons supported via addOnIds array\n addOnIds:\n addonItems.length > 0\n ? addonItems.map((item) => ({\n id: (item.metadata?.addonId as string) || item.id,\n quantity: item.quantity,\n }))\n : undefined,\n isAnnual: planItem?.metadata?.duration === 'yearly',\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // For addon purchases - subscription to attach to\n subscriptionId: addonItems.length > 0 ? selectedSubscriptionId : undefined,\n // For PER_SEAT plans - pass seat count for credit calculation + subscription storage\n seatCount: planItem?.metadata?.seatCount as number | undefined,\n },\n },\n });\n\n const response = data?.spendCredits;\n\n if (!response?.creditTransactionID) {\n setCheckoutState('error');\n setError('Failed to process credit payment');\n return {\n success: false,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Failed to process credit payment',\n };\n }\n\n setCheckoutState('success');\n return {\n success: true,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n // Return transaction ID for success handling\n orderId: response.creditTransactionID,\n };\n }\n\n // ========================================\n // CARD PAYMENT (Stripe/Razorpay) - use initiatePayment\n // Supports single or multiple addons\n // Supports store orders (via orderId parameter)\n // ========================================\n const addonItems = items.filter((item) => item.type === 'ADDON');\n\n // Build addon IDs and quantities for the mutation\n const addonIds =\n addonItems.length > 0\n ? addonItems.map((item) => (item.metadata?.addonId as string) || item.id)\n : undefined;\n const quantities =\n addonItems.length > 0 ? addonItems.map((item) => item.quantity) : undefined;\n\n // Determine if this is an annual plan purchase\n const planItem = items.find((item) => item.type === 'PLAN');\n const isAnnualPlan = planItem?.metadata?.duration === 'yearly';\n\n // Determine auto-renew payment method\n const autoRenewPMId =\n purchaseType === 'PLAN' && autoRenew\n ? autoRenewPaymentMethodId || selectedPaymentMethodId || undefined\n : undefined;\n\n const { data } = await initiatePaymentMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n amount: chargeAmount, // Note: Backend IGNORES this and calculates from DB\n currency: selectedCurrency,\n purchaseType,\n provider: provider,\n // Store order - use orderId parameter (billing order ID)\n orderId: billingOrderId || undefined,\n creditAmount: purchaseType === 'CREDITS' ? firstItem.quantity : undefined,\n planId:\n purchaseType === 'PLAN'\n ? (firstItem.metadata?.planId as string) || firstItem.id\n : undefined,\n // Single addon (backwards compatible) - skip if store order\n addonId:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? (addonItems[0].metadata?.addonId as string) || addonItems[0].id\n : undefined,\n quantity:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? addonItems[0].quantity\n : undefined,\n // Multiple addons (cart checkout) - skip if store order\n addonIds:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? addonIds\n : undefined,\n quantities:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? quantities\n : undefined,\n // For addon purchases - subscription to attach to (skip for store orders)\n subscriptionId:\n purchaseType === 'ADDON' && !billingOrderId ? selectedSubscriptionId : undefined,\n // Saved card support\n paymentMethodId: selectedPaymentMethodId || undefined,\n // Save card if: user checked save card OR auto-renewal is enabled (which requires a saved card)\n // Only save if not using existing card (selectedPaymentMethodId is null)\n saveCard: !selectedPaymentMethodId ? saveCard || autoRenew : undefined,\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // Annual plan flag (for pricing calculation)\n isAnnual: isAnnualPlan || undefined,\n // Promo code (validated and applied on backend)\n promoCode: promoCode || undefined,\n // Auto-renewal (for plan purchases only)\n autoRenew: purchaseType === 'PLAN' && autoRenew ? true : undefined,\n autoRenewPaymentMethodId: autoRenewPMId,\n },\n },\n });\n\n const response = data?.initiatePayment;\n\n if (!response?.success) {\n setCheckoutState('error');\n setError(response?.error ?? 'Payment initiation failed');\n return {\n success: false,\n provider: provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: response?.error ?? 'Payment initiation failed',\n };\n }\n\n // Note: Tax is already calculated upfront in the effect, don't overwrite\n // The response tax is in converted currency which would mess up display\n setCheckoutState('success');\n\n return response;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'An error occurred';\n setCheckoutState('error');\n setError(errorMessage);\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errorMessage,\n };\n }\n },\n [\n billingAccount,\n items,\n selectedCurrency,\n selectedProvider,\n subtotal,\n discountAmount,\n promoCode,\n selectedPaymentMethodId,\n saveCard,\n autoRenew,\n autoRenewPaymentMethodId,\n selectedSubscriptionId,\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n initiatePaymentMutation,\n ensureStoreOrderBillingOrderMutation,\n spendCreditsMutation,\n ]\n );\n\n const reset = useCallback(() => {\n setItems([]);\n setBillingAccount(null);\n setSelectedProvider('AUTO');\n setSelectedCurrency(defaultCurrency);\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n setUseCredits(false);\n setCreditsToUse(0);\n setCheckoutState('idle');\n setError(null);\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n setSelectedPaymentMethodId(null);\n setSaveCard(defaultSaveCard);\n setAutoRenew(false);\n setAutoRenewPaymentMethodId(null);\n setSelectedSubscriptionId(defaultSubscriptionId ?? null);\n }, [defaultCurrency, defaultSaveCard, defaultSubscriptionId]);\n\n return {\n // State\n items,\n billingAccount,\n selectedProvider,\n selectedCurrency,\n promoCode,\n promoDiscount,\n promoDiscountType,\n useCredits,\n creditsToUse,\n checkoutState,\n error,\n\n // Saved card state\n selectedPaymentMethodId,\n saveCard,\n\n // Auto-pay state\n autoRenew,\n autoRenewPaymentMethodId,\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId,\n\n // Computed\n subtotal,\n subtotalUSD,\n discountAmount,\n taxAmount,\n taxRate,\n taxType,\n total,\n totalInSelectedCurrency,\n availableCredits,\n creditsRequired,\n hasEnoughCredits,\n canPayWithCredits,\n isLoading,\n isProcessing,\n isCalculatingTax: false, // Tax is calculated synchronously now\n conversionRate,\n convertedAmount,\n\n // Actions\n addItem,\n removeItem,\n updateItemQuantity,\n updateItemPrice,\n clearItems,\n setBillingAccount: handleSetBillingAccount,\n setPaymentProvider: handleSetPaymentProvider,\n setCurrency: handleSetCurrency,\n setPromoCode,\n validatePromoCode,\n clearPromoCode,\n setUseCredits,\n setCreditsToUse: handleSetCreditsToUse,\n setSelectedPaymentMethodId,\n setSaveCard,\n setAutoRenew,\n setAutoRenewPaymentMethodId,\n setSelectedSubscriptionId,\n initiatePayment,\n reset,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"useCheckout.js","names":[],"sources":["../../../../../src/billing/modules/checkout/hooks/useCheckout.ts"],"sourcesContent":["/**\n * Main checkout hook for managing checkout state and payment processing\n */\n\nimport { useState, useCallback, useMemo, useEffect } from 'react';\nimport {\n useInitiatePaymentCheckoutMutation,\n useSpendCreditsCheckoutMutation,\n useConvertCurrencyLazyQuery,\n useValidateCouponLazyQuery,\n} from '../../../../generated/global-operations';\nimport { useBilling } from '../../../providers/BillingProvider';\nimport { useEnsureStoreOrderBillingOrder } from './useStoreShipping';\nimport type {\n CheckoutItem,\n CheckoutBillingAccount,\n PaymentProvider,\n Currency,\n CheckoutState,\n PaymentInitResponse,\n PromoCodeResult,\n PurchaseType,\n} from '../types';\n\n/**\n * Credit conversion rate: How many credits equal 1 USD\n * This should match the backend creditRate.rateUSD (default 0.01 means 100 credits = $1)\n */\n// Exported so callers that need a per-item (rather than aggregate) credits\n// amount — e.g. the success page's itemized \"Items Purchased\" list — convert\n// consistently with creditsRequired/hasEnoughCredits below, instead of each\n// re-declaring their own copy of this rate.\nexport const CREDITS_PER_USD = 100;\n\ninterface UseCheckoutOptions {\n billingAccountId?: string;\n defaultCurrency?: Currency;\n defaultSaveCard?: boolean;\n upgradeFromSubscriptionId?: string;\n /** For plan downgrades — old sub auto-renewal cancelled, runs to natural expiry */\n downgradeFromSubscriptionId?: string;\n /** For addon purchases - pre-selected subscription ID */\n defaultSubscriptionId?: string;\n}\n\ninterface UseCheckoutResult {\n // State\n items: CheckoutItem[];\n billingAccount: CheckoutBillingAccount | null;\n selectedProvider: PaymentProvider;\n selectedCurrency: Currency;\n promoCode: string;\n promoDiscount: number;\n promoDiscountType: 'PERCENTAGE' | 'FIXED';\n useCredits: boolean;\n creditsToUse: number;\n checkoutState: CheckoutState;\n error: string | null;\n\n // Saved card state\n selectedPaymentMethodId: string | null;\n saveCard: boolean;\n\n // Auto-pay state (for subscription purchases)\n autoRenew: boolean;\n autoRenewPaymentMethodId: string | null;\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId: string | null;\n\n // Computed values\n subtotal: number;\n subtotalUSD: number;\n discountAmount: number;\n taxAmount: number;\n taxRate: number;\n taxType: string;\n total: number;\n totalInSelectedCurrency: number;\n availableCredits: number;\n /** subtotalUSD converted to credits (CREDITS_PER_USD) — the actual amount a\n * CREDITS-provider purchase charges, in credits. total/totalInSelectedCurrency\n * are in the selected display currency and must never be shown/used as a\n * credits amount (they're a different number). */\n creditsRequired: number;\n hasEnoughCredits: boolean;\n canPayWithCredits: boolean;\n isLoading: boolean;\n isProcessing: boolean;\n isCalculatingTax: boolean;\n\n // Currency conversion\n conversionRate: number;\n convertedAmount: number | null;\n\n // Actions\n addItem: (item: CheckoutItem) => void;\n removeItem: (itemId: string) => void;\n updateItemQuantity: (itemId: string, quantity: number) => void;\n updateItemPrice: (itemId: string, price: number, currency?: Currency) => void;\n clearItems: () => void;\n setBillingAccount: (account: CheckoutBillingAccount | null) => void;\n setPaymentProvider: (provider: PaymentProvider) => void;\n setCurrency: (currency: Currency) => void;\n setPromoCode: (code: string) => void;\n validatePromoCode: () => Promise<PromoCodeResult>;\n clearPromoCode: () => void;\n setUseCredits: (use: boolean) => void;\n setCreditsToUse: (amount: number) => void;\n setSelectedPaymentMethodId: (id: string | null) => void;\n setSaveCard: (save: boolean) => void;\n setAutoRenew: (enabled: boolean) => void;\n setAutoRenewPaymentMethodId: (id: string | null) => void;\n setSelectedSubscriptionId: (id: string | null) => void;\n initiatePayment: () => Promise<PaymentInitResponse>;\n reset: () => void;\n}\n\nexport function useCheckout(options: UseCheckoutOptions = {}): UseCheckoutResult {\n const { currentUser } = useBilling();\n const {\n defaultCurrency = 'USD',\n defaultSaveCard = false, // CRITICAL: Never save cards without explicit user consent\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n defaultSubscriptionId,\n } = options;\n\n // Core state\n const [items, setItems] = useState<CheckoutItem[]>([]);\n const [billingAccount, setBillingAccount] = useState<CheckoutBillingAccount | null>(null);\n const [selectedProvider, setSelectedProvider] = useState<PaymentProvider>('AUTO');\n const [selectedCurrency, setSelectedCurrency] = useState<Currency>(defaultCurrency);\n const [promoCode, setPromoCode] = useState('');\n const [promoDiscount, setPromoDiscount] = useState(0);\n const [promoDiscountType, setPromoDiscountType] = useState<'PERCENTAGE' | 'FIXED'>('PERCENTAGE');\n const [useCredits, setUseCredits] = useState(false);\n const [creditsToUse, setCreditsToUse] = useState(0);\n const [checkoutState, setCheckoutState] = useState<CheckoutState>('idle');\n const [error, setError] = useState<string | null>(null);\n const [taxAmount, setTaxAmount] = useState(0);\n const [taxRate, setTaxRate] = useState(0);\n const [taxType, setTaxType] = useState<string>('NONE');\n\n // Saved card state\n const [selectedPaymentMethodId, setSelectedPaymentMethodId] = useState<string | null>(null);\n const [saveCard, setSaveCard] = useState(defaultSaveCard);\n\n // Auto-pay state (for subscription purchases)\n const [autoRenew, setAutoRenew] = useState(false);\n const [autoRenewPaymentMethodId, setAutoRenewPaymentMethodId] = useState<string | null>(null);\n\n // Subscription state (for addon purchases)\n const [selectedSubscriptionId, setSelectedSubscriptionId] = useState<string | null>(\n defaultSubscriptionId ?? null\n );\n\n // GraphQL mutations and queries\n const [initiatePaymentMutation] = useInitiatePaymentCheckoutMutation();\n const [ensureStoreOrderBillingOrderMutation] = useEnsureStoreOrderBillingOrder();\n const [spendCreditsMutation] = useSpendCreditsCheckoutMutation();\n const [convertCurrency, { data: currencyData, loading: currencyLoading }] =\n useConvertCurrencyLazyQuery();\n const [validateCoupon] = useValidateCouponLazyQuery();\n\n // Computed values\n\n // The currency `total`/`subtotal` are actually denominated in. Plans,\n // addons, and credits are priced in USD; store orders are priced in the\n // order's own currency (item.currency), which may not be USD. Converting\n // `total` to the selected currency must pivot FROM this currency, not\n // always assume USD — see BOFF-7127 (a ₹50 item was shown as $4,748.50\n // because the conversion always requested a USD→X rate and multiplied,\n // which is only correct when the source amount actually is in USD).\n const nativeCurrency = useMemo<Currency>(() => items[0]?.currency ?? 'USD', [items]);\n\n const subtotal = useMemo(() => {\n return items.reduce((sum, item) => sum + item.price * item.quantity, 0);\n }, [items]);\n\n // USD subtotal — always use priceUSD (base price) for credit calculations.\n // item.price may be in a local currency (INR, EUR, etc.) when currencyPrices is set.\n const subtotalUSD = useMemo(() => {\n return items.reduce((sum, item) => sum + (item.priceUSD ?? item.price) * item.quantity, 0);\n }, [items]);\n\n const discountAmount = useMemo(() => {\n if (!promoDiscount) return 0;\n // promoDiscount now contains the actual discount amount from backend validation\n // (not the percentage value), so use it directly\n return Math.min(promoDiscount, subtotal);\n }, [subtotal, promoDiscount]);\n\n // Total includes tax (calculated via calculateTax query)\n const total = useMemo(() => {\n const afterDiscount = subtotal - discountAmount;\n const withCredits = useCredits ? Math.max(0, afterDiscount - creditsToUse) : afterDiscount;\n return withCredits + taxAmount;\n }, [subtotal, discountAmount, useCredits, creditsToUse, taxAmount]);\n\n const availableCredits = billingAccount?.creditAmount ?? 0;\n\n // Use subtotalUSD so this works correctly for any display currency\n const creditsRequired = useMemo(() => subtotalUSD * CREDITS_PER_USD, [subtotalUSD]);\n\n const hasEnoughCredits = useMemo(\n () => availableCredits >= creditsRequired,\n [availableCredits, creditsRequired]\n );\n\n // Can only pay with credits if not buying credits\n const canPayWithCredits = useMemo(() => {\n const hasCreditsPurchase = items.some((item) => item.type === 'CREDITS');\n return !hasCreditsPurchase && availableCredits > 0;\n }, [items, availableCredits]);\n\n // Currency conversion result\n const convertedAmount = currencyData?.convertCurrency?.amount ?? null;\n const conversionRate = currencyData?.convertCurrency?.exchangeRate ?? 1;\n const totalInSelectedCurrency = convertedAmount ?? total;\n\n const isLoading = checkoutState === 'loading' || currencyLoading;\n const isProcessing = checkoutState === 'processing';\n\n // Effect to trigger currency conversion when total or currency changes\n // This ensures the converted amount is always up-to-date\n useEffect(() => {\n // Skip conversion for credit payments (no currency conversion needed)\n if (selectedProvider === 'CREDITS') {\n return;\n }\n\n // Skip if no items yet (cart is still loading)\n if (items.length === 0) {\n return;\n }\n\n // Skip conversion if items already have prices in selected currency\n // (Plans/Addons with multi-currency pricing have currencyPrices stored)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === selectedCurrency || item.type === 'CREDITS'\n );\n\n if (allItemsHaveLocalizedPrices) {\n // Items are already priced in the selected currency (from currencyPrices)\n // No conversion needed - subtotal and total are already correct!\n return;\n }\n\n // Only fetch if we have a valid total and it needs converting at all.\n if (total > 0 && nativeCurrency !== selectedCurrency) {\n // For INR, wait until tax has been calculated (taxAmount > 0 or taxRate is set)\n // This prevents double API calls - one before tax, one after\n if (selectedCurrency === 'INR' && taxAmount === 0 && subtotal > 0) {\n // Tax hasn't been calculated yet, skip this conversion\n // The effect will run again when taxAmount changes\n return;\n }\n\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: selectedCurrency,\n fromCurrency: nativeCurrency,\n },\n });\n }\n }, [\n total,\n selectedCurrency,\n selectedProvider,\n convertCurrency,\n taxAmount,\n subtotal,\n items,\n nativeCurrency,\n ]);\n\n // Effect to calculate tax when billing account, subtotal, or currency changes\n // Tax is only applied for INR payments (18% GST)\n useEffect(() => {\n // Only calculate tax if we have a billing account and items\n if (!billingAccount || subtotal <= 0) {\n // Reset tax if no billing account or empty cart\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Skip if paying with credits (no tax on credit payments)\n if (selectedProvider === 'CREDITS') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // Only apply tax for INR payments\n if (selectedCurrency !== 'INR') {\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n return;\n }\n\n // For INR: Calculate 18% GST on the USD amount, then we'll convert for display\n const afterDiscount = subtotal - discountAmount;\n const GST_RATE = 0.18;\n const taxInUSD = afterDiscount * GST_RATE;\n\n setTaxAmount(taxInUSD);\n setTaxRate(GST_RATE);\n setTaxType('GST');\n }, [billingAccount, subtotal, discountAmount, selectedCurrency, selectedProvider]);\n\n // Actions\n const addItem = useCallback((item: CheckoutItem) => {\n setItems((prev) => {\n // Plans can only be purchased once - quantity is always 1\n if (item.type === 'PLAN') {\n // Check if any plan already exists in cart\n const existingPlan = prev.find((i) => i.type === 'PLAN');\n if (existingPlan) {\n // Replace existing plan with new one (can only have 1 plan)\n return prev.map((i) => (i.type === 'PLAN' ? { ...item, quantity: 1 } : i));\n }\n // Add new plan with quantity forced to 1\n return [...prev, { ...item, quantity: 1 }];\n }\n\n // For addons and credits, allow quantity increment\n const existing = prev.find((i) => i.id === item.id);\n if (existing) {\n return prev.map((i) =>\n i.id === item.id ? { ...i, quantity: i.quantity + item.quantity } : i\n );\n }\n return [...prev, item];\n });\n }, []);\n\n const removeItem = useCallback((itemId: string) => {\n setItems((prev) => prev.filter((i) => i.id !== itemId));\n }, []);\n\n const updateItemQuantity = useCallback(\n (itemId: string, quantity: number) => {\n if (quantity <= 0) {\n removeItem(itemId);\n return;\n }\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Plans can only have quantity 1\n if (i.type === 'PLAN') {\n return { ...i, quantity: 1 };\n }\n return { ...i, quantity };\n })\n );\n },\n [removeItem]\n );\n\n const updateItemPrice = useCallback((itemId: string, price: number, currency?: Currency) => {\n setItems((prev) =>\n prev.map((i) => {\n if (i.id !== itemId) return i;\n // Update price and optionally currency\n const updates: Partial<CheckoutItem> = { price };\n if (currency) {\n updates.currency = currency;\n }\n return { ...i, ...updates };\n })\n );\n }, []);\n\n const clearItems = useCallback(() => {\n setItems([]);\n }, []);\n\n const handleSetBillingAccount = useCallback((account: CheckoutBillingAccount | null) => {\n setBillingAccount(account);\n // Reset credits usage when billing account changes — different accounts\n // carry different credit balances, so a CREDITS selection made against\n // the previous account may no longer be valid (or even offered) for\n // this one. Without also resetting the provider, the order summary kept\n // displaying amounts in Credits (and checkout could still submit with\n // provider: 'CREDITS') even after the Credits option had disappeared\n // from the payment method list for the new account.\n setUseCredits(false);\n setCreditsToUse(0);\n setSelectedProvider((prev) => (prev === 'CREDITS' ? 'AUTO' : prev));\n }, []);\n\n const handleSetCurrency = useCallback(\n (currency: Currency) => {\n setSelectedCurrency(currency);\n // Skip conversion if items already have localized prices\n // (Plans/Addons with multi-currency pricing don't need conversion)\n const allItemsHaveLocalizedPrices = items.every(\n (item) => item.currency === currency || item.type === 'CREDITS'\n );\n\n // Only fetch conversion rate if items don't have localized prices\n if (total > 0 && nativeCurrency !== currency && !allItemsHaveLocalizedPrices) {\n convertCurrency({\n variables: {\n amount: total,\n toCurrency: currency,\n fromCurrency: nativeCurrency,\n },\n });\n }\n },\n [total, convertCurrency, items, nativeCurrency]\n );\n\n const handleSetPaymentProvider = useCallback(\n (provider: PaymentProvider) => {\n setSelectedProvider(provider);\n // Razorpay only supports INR\n if (provider === 'RAZORPAY' && selectedCurrency !== 'INR') {\n setSelectedCurrency('INR');\n }\n // Credits purchases don't support coupons yet (BOFF-7192) — clear any\n // promo applied under a card provider so it can't leak into the\n // credits success screen's displayed total.\n if (provider === 'CREDITS') {\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n }\n },\n [selectedCurrency]\n );\n\n const validatePromoCode = useCallback(async (): Promise<PromoCodeResult> => {\n if (!promoCode.trim()) {\n return {\n valid: false,\n code: '',\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Please enter a promo code',\n };\n }\n\n try {\n setCheckoutState('loading');\n\n // Build validation input\n const validationInput = {\n code: promoCode.trim().toUpperCase(),\n userId: currentUser?.id ?? '',\n userEmail: currentUser?.email,\n amount: subtotal,\n currency: selectedCurrency,\n };\n\n const { data, error: queryError } = await validateCoupon({\n variables: {\n input: validationInput,\n },\n });\n\n if (queryError || !data?.validateCoupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n\n const response = data.validateCoupon;\n\n if (!response.valid) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: response.error || 'Invalid coupon code',\n };\n }\n\n // Extract discount details from coupon\n const coupon = response.coupon;\n if (!coupon) {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Coupon data not found',\n };\n }\n\n // Use the ACTUAL discount amount from validation (includes caps and currency conversion)\n const actualDiscountAmount = response.discountAmount || 0;\n\n const discountType = coupon.type === 'FIXED_AMOUNT' ? 'FIXED' : 'PERCENTAGE';\n setPromoDiscount(actualDiscountAmount); // Use actual discount, not coupon.value\n setPromoDiscountType(discountType);\n setCheckoutState('idle');\n\n return {\n valid: true,\n code: coupon.code,\n discountType,\n discountValue: actualDiscountAmount, // Use actual discount, not coupon.value\n };\n } catch {\n setPromoDiscount(0);\n setCheckoutState('idle');\n return {\n valid: false,\n code: promoCode,\n discountType: 'PERCENTAGE',\n discountValue: 0,\n error: 'Failed to validate coupon',\n };\n }\n }, [currentUser?.email, currentUser?.id, promoCode, selectedCurrency, subtotal, validateCoupon]);\n\n const clearPromoCode = useCallback(() => {\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n }, []);\n\n const handleSetCreditsToUse = useCallback(\n (amount: number) => {\n const maxCredits = Math.min(amount, availableCredits, subtotal - discountAmount);\n setCreditsToUse(Math.max(0, maxCredits));\n },\n [availableCredits, subtotal, discountAmount]\n );\n\n const initiatePayment = useCallback(\n async (): Promise<PaymentInitResponse> => {\n if (!billingAccount) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Please select a billing account',\n };\n }\n\n if (items.length === 0) {\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'No items in checkout',\n };\n }\n\n try {\n setCheckoutState('processing');\n setError(null);\n\n // Determine purchase type from first item\n const firstItem = items[0];\n let purchaseType: PurchaseType = firstItem.type;\n\n // Check if this is a store order checkout (detect early)\n const storeOrderItem = items.find((item) => item.metadata?.storeOrderId);\n const storeOrderId = storeOrderItem?.metadata?.storeOrderId as string | undefined;\n let billingOrderId =\n (items.find((item) => item.metadata?.billingOrderId)?.metadata?.billingOrderId as\n string | undefined) || undefined;\n\n // For store orders, use 'ORDER' purchase type\n if (storeOrderId) {\n purchaseType = 'ORDER' as PurchaseType;\n }\n\n // Calculate amount to charge\n const chargeAmount = subtotal - discountAmount;\n\n // Determine provider\n let provider = selectedProvider;\n if (provider === 'AUTO') {\n provider = selectedCurrency === 'INR' ? 'RAZORPAY' : 'STRIPE';\n }\n\n // If this is a store order without a billing order yet, let the store service\n // create it from its authoritative order snapshot. This persists the billing\n // order ID and registers the signed callback on the same service that owns\n // the order; browser line-item prices and shipping values are never sent to\n // billing as the order snapshot.\n if (purchaseType === 'ORDER' && !billingOrderId && storeOrderId && billingAccount) {\n const { data: billingOrderResult } = await ensureStoreOrderBillingOrderMutation({\n variables: {\n storeOrderId,\n billingAccountId: billingAccount.id,\n currency: selectedCurrency,\n },\n });\n const ensureResult = billingOrderResult?.ensureStoreOrderBillingOrder;\n\n if (ensureResult?.success && ensureResult.billingOrderId) {\n billingOrderId = ensureResult.billingOrderId;\n } else {\n const errMsg = ensureResult?.error ?? 'Failed to create billing order';\n setCheckoutState('error');\n setError(errMsg);\n return {\n success: false,\n provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errMsg,\n };\n }\n }\n\n // ========================================\n // CREDITS PAYMENT - use spendCredits mutation\n // Supports multiple addons\n // ========================================\n if (provider === 'CREDITS') {\n const addonItems = items.filter((item) => item.type === 'ADDON');\n const planItem = items.find((item) => item.type === 'PLAN');\n\n const { data } = await spendCreditsMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n // Store order purchase: the only one of planId/addOnIds/orderId\n // spendCredits actually needs for this purchase type. Without\n // it, the mutation has nothing to charge and the backend\n // rejects it outright (BOFF-7192).\n orderId: purchaseType === 'ORDER' ? billingOrderId : undefined,\n planId: planItem ? (planItem.metadata?.planId as string) || planItem.id : undefined,\n // Multiple addons supported via addOnIds array\n addOnIds:\n addonItems.length > 0\n ? addonItems.map((item) => ({\n id: (item.metadata?.addonId as string) || item.id,\n quantity: item.quantity,\n }))\n : undefined,\n isAnnual: planItem?.metadata?.duration === 'yearly',\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // For addon purchases - subscription to attach to\n subscriptionId: addonItems.length > 0 ? selectedSubscriptionId : undefined,\n // For PER_SEAT plans - pass seat count for credit calculation + subscription storage\n seatCount: planItem?.metadata?.seatCount as number | undefined,\n },\n },\n });\n\n const response = data?.spendCredits;\n\n if (!response?.creditTransactionID) {\n setCheckoutState('error');\n setError('Failed to process credit payment');\n return {\n success: false,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: 'Failed to process credit payment',\n };\n }\n\n setCheckoutState('success');\n return {\n success: true,\n provider: 'CREDITS',\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n // Return transaction ID for success handling\n orderId: response.creditTransactionID,\n };\n }\n\n // ========================================\n // CARD PAYMENT (Stripe/Razorpay) - use initiatePayment\n // Supports single or multiple addons\n // Supports store orders (via orderId parameter)\n // ========================================\n const addonItems = items.filter((item) => item.type === 'ADDON');\n\n // Build addon IDs and quantities for the mutation\n const addonIds =\n addonItems.length > 0\n ? addonItems.map((item) => (item.metadata?.addonId as string) || item.id)\n : undefined;\n const quantities =\n addonItems.length > 0 ? addonItems.map((item) => item.quantity) : undefined;\n\n // Determine if this is an annual plan purchase\n const planItem = items.find((item) => item.type === 'PLAN');\n const isAnnualPlan = planItem?.metadata?.duration === 'yearly';\n\n // Determine auto-renew payment method\n const autoRenewPMId =\n purchaseType === 'PLAN' && autoRenew\n ? autoRenewPaymentMethodId || selectedPaymentMethodId || undefined\n : undefined;\n\n const { data } = await initiatePaymentMutation({\n variables: {\n input: {\n billingAccountId: billingAccount.id,\n amount: chargeAmount, // Note: Backend IGNORES this and calculates from DB\n currency: selectedCurrency,\n purchaseType,\n provider: provider,\n // Store order - use orderId parameter (billing order ID)\n orderId: billingOrderId || undefined,\n creditAmount: purchaseType === 'CREDITS' ? firstItem.quantity : undefined,\n planId:\n purchaseType === 'PLAN'\n ? (firstItem.metadata?.planId as string) || firstItem.id\n : undefined,\n // Single addon (backwards compatible) - skip if store order\n addonId:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? (addonItems[0].metadata?.addonId as string) || addonItems[0].id\n : undefined,\n quantity:\n purchaseType === 'ADDON' && addonItems.length === 1 && !billingOrderId\n ? addonItems[0].quantity\n : undefined,\n // Multiple addons (cart checkout) - skip if store order\n addonIds:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? addonIds\n : undefined,\n quantities:\n purchaseType === 'ADDON' && addonItems.length > 1 && !billingOrderId\n ? quantities\n : undefined,\n // For addon purchases - subscription to attach to (skip for store orders)\n subscriptionId:\n purchaseType === 'ADDON' && !billingOrderId ? selectedSubscriptionId : undefined,\n // Saved card support\n paymentMethodId: selectedPaymentMethodId || undefined,\n // Save card if: user checked save card OR auto-renewal is enabled (which requires a saved card)\n // Only save if not using existing card (selectedPaymentMethodId is null)\n saveCard: !selectedPaymentMethodId ? saveCard || autoRenew : undefined,\n // Include upgrade context if changing plans\n upgradeFromSubscriptionId: upgradeFromSubscriptionId || undefined,\n // Include downgrade context if downgrading\n downgradeFromSubscriptionId: downgradeFromSubscriptionId || undefined,\n // Annual plan flag (for pricing calculation)\n isAnnual: isAnnualPlan || undefined,\n // Promo code (validated and applied on backend)\n promoCode: promoCode || undefined,\n // Auto-renewal (for plan purchases only)\n autoRenew: purchaseType === 'PLAN' && autoRenew ? true : undefined,\n autoRenewPaymentMethodId: autoRenewPMId,\n },\n },\n });\n\n const response = data?.initiatePayment;\n\n if (!response?.success) {\n setCheckoutState('error');\n setError(response?.error ?? 'Payment initiation failed');\n return {\n success: false,\n provider: provider,\n subtotal: chargeAmount,\n taxAmount: 0,\n taxRate: 0,\n total: chargeAmount,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: response?.error ?? 'Payment initiation failed',\n };\n }\n\n // Note: Tax is already calculated upfront in the effect, don't overwrite\n // The response tax is in converted currency which would mess up display\n setCheckoutState('success');\n\n return response;\n } catch (err) {\n const errorMessage = err instanceof Error ? err.message : 'An error occurred';\n setCheckoutState('error');\n setError(errorMessage);\n return {\n success: false,\n provider: 'AUTO',\n subtotal: 0,\n taxAmount: 0,\n taxRate: 0,\n total: 0,\n taxType: 'NONE',\n jurisdiction: 'UNKNOWN',\n currency: selectedCurrency,\n error: errorMessage,\n };\n }\n },\n [\n billingAccount,\n items,\n selectedCurrency,\n selectedProvider,\n subtotal,\n discountAmount,\n promoCode,\n selectedPaymentMethodId,\n saveCard,\n autoRenew,\n autoRenewPaymentMethodId,\n selectedSubscriptionId,\n upgradeFromSubscriptionId,\n downgradeFromSubscriptionId,\n initiatePaymentMutation,\n ensureStoreOrderBillingOrderMutation,\n spendCreditsMutation,\n ]\n );\n\n const reset = useCallback(() => {\n setItems([]);\n setBillingAccount(null);\n setSelectedProvider('AUTO');\n setSelectedCurrency(defaultCurrency);\n setPromoCode('');\n setPromoDiscount(0);\n setPromoDiscountType('PERCENTAGE');\n setUseCredits(false);\n setCreditsToUse(0);\n setCheckoutState('idle');\n setError(null);\n setTaxAmount(0);\n setTaxRate(0);\n setTaxType('NONE');\n setSelectedPaymentMethodId(null);\n setSaveCard(defaultSaveCard);\n setAutoRenew(false);\n setAutoRenewPaymentMethodId(null);\n setSelectedSubscriptionId(defaultSubscriptionId ?? null);\n }, [defaultCurrency, defaultSaveCard, defaultSubscriptionId]);\n\n return {\n // State\n items,\n billingAccount,\n selectedProvider,\n selectedCurrency,\n promoCode,\n promoDiscount,\n promoDiscountType,\n useCredits,\n creditsToUse,\n checkoutState,\n error,\n\n // Saved card state\n selectedPaymentMethodId,\n saveCard,\n\n // Auto-pay state\n autoRenew,\n autoRenewPaymentMethodId,\n\n // Subscription state (for addon purchases)\n selectedSubscriptionId,\n\n // Computed\n subtotal,\n subtotalUSD,\n discountAmount,\n taxAmount,\n taxRate,\n taxType,\n total,\n totalInSelectedCurrency,\n availableCredits,\n creditsRequired,\n hasEnoughCredits,\n canPayWithCredits,\n isLoading,\n isProcessing,\n isCalculatingTax: false, // Tax is calculated synchronously now\n conversionRate,\n convertedAmount,\n\n // Actions\n addItem,\n removeItem,\n updateItemQuantity,\n updateItemPrice,\n clearItems,\n setBillingAccount: handleSetBillingAccount,\n setPaymentProvider: handleSetPaymentProvider,\n setCurrency: handleSetCurrency,\n setPromoCode,\n validatePromoCode,\n clearPromoCode,\n setUseCredits,\n setCreditsToUse: handleSetCreditsToUse,\n setSelectedPaymentMethodId,\n setSaveCard,\n setAutoRenew,\n setAutoRenewPaymentMethodId,\n setSelectedSubscriptionId,\n initiatePayment,\n reset,\n };\n}\n"],"mappings":"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"}
|