@burdenoff/microfe-billing 2026.912.5 → 2026.913.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"BillingAdminRoutes.js","names":[],"sources":["../../src/billing/BillingAdminRoutes.tsx"],"sourcesContent":["import { lazy, Suspense, useEffect, type ReactNode } from 'react';\nimport { Navigate, Route, Routes, useNavigate } from 'react-router';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useBillingEventEmitter, type BillingDomainEvent } from './hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from './hooks/useBillingPermissions';\nimport { useBilling } from './providers/BillingProvider';\nimport { AccessDenied } from './shared/components';\n\n// Lazy loaded pages - Plans (full CRUD)\nconst PlansListPage = lazy(() =>\n import('./modules/plans').then((m) => ({ default: m.PlansListPage }))\n);\nconst PlanDetailPage = lazy(() =>\n import('./modules/plans').then((m) => ({ default: m.PlanDetailPage }))\n);\nconst PlanEditPage = lazy(() =>\n import('./modules/plans').then((m) => ({ default: m.PlanEditPage }))\n);\nconst PlanCreatePage = lazy(() =>\n import('./modules/plans').then((m) => ({ default: m.PlanCreatePage }))\n);\nconst PlanAssignFreePage = lazy(() =>\n import('./modules/plans').then((m) => ({ default: m.PlanAssignFreePage }))\n);\n\n// Lazy loaded pages - Addons (full CRUD)\nconst AddonsListPage = lazy(() =>\n import('./modules/addons').then((m) => ({ default: m.AddonsListPage }))\n);\nconst AddonDetailPage = lazy(() =>\n import('./modules/addons').then((m) => ({ default: m.AddonDetailPage }))\n);\nconst AddonEditPage = lazy(() =>\n import('./modules/addons').then((m) => ({ default: m.AddonEditPage }))\n);\nconst AddonCreatePage = lazy(() =>\n import('./modules/addons').then((m) => ({ default: m.AddonCreatePage }))\n);\n\n// Lazy loaded pages - Quota Templates (full CRUD)\nconst QuotaTemplatesListPage = lazy(() =>\n import('./modules/quota').then((m) => ({ default: m.QuotaTemplatesListPage }))\n);\nconst QuotaTemplateDetailPage = lazy(() =>\n import('./modules/quota').then((m) => ({ default: m.QuotaTemplateDetailPage }))\n);\nconst QuotaTemplateCreatePage = lazy(() =>\n import('./modules/quota').then((m) => ({ default: m.QuotaTemplateCreatePage }))\n);\nconst QuotaTemplateEditPage = lazy(() =>\n import('./modules/quota').then((m) => ({ default: m.QuotaTemplateEditPage }))\n);\nconst QuotaAssignFreePage = lazy(() =>\n import('./modules/quota').then((m) => ({ default: m.QuotaAssignFreePage }))\n);\n\n// Lazy loaded pages - Coupons\nconst CouponsListPage = lazy(() =>\n import('./modules/coupons').then((m) => ({ default: m.CouponsListPage }))\n);\nconst CreateCouponPage = lazy(() =>\n import('./modules/coupons').then((m) => ({ default: m.CreateCouponPage }))\n);\nconst EditCouponPage = lazy(() =>\n import('./modules/coupons').then((m) => ({ default: m.EditCouponPage }))\n);\nconst CouponDetailPage = lazy(() =>\n import('./modules/coupons').then((m) => ({ default: m.CouponDetailPage }))\n);\n\n// Lazy loaded pages - Promotions\nconst PromotionsListPage = lazy(() =>\n import('./modules/promotions').then((m) => ({ default: m.PromotionsListPage }))\n);\nconst CreatePromotionPage = lazy(() =>\n import('./modules/promotions').then((m) => ({ default: m.CreatePromotionPage }))\n);\n\n// Lazy loaded pages - Discounts\nconst DiscountsListPage = lazy(() =>\n import('./modules/discounts').then((m) => ({ default: m.DiscountsListPage }))\n);\n\n// Lazy loaded pages - Refunds\nconst RefundsPage = lazy(() =>\n import('./modules/billing').then((m) => ({ default: m.RefundsPage }))\n);\n\n// Lazy loaded pages - Billing webhook logs\nconst BillingWebhookLogsPage = lazy(() =>\n import('./modules/billing').then((m) => ({ default: m.BillingWebhookLogsPage }))\n);\n\n// Lazy loaded pages - Order callback logs\nconst OrderCallbackLogsPage = lazy(() =>\n import('./modules/billing').then((m) => ({ default: m.OrderCallbackLogsPage }))\n);\n\n// Lazy loaded pages - Credits admin\nconst AdminCreditsPage = lazy(() =>\n import('./modules/credits').then((m) => ({ default: m.AdminCreditsPage }))\n);\n\n// Lazy loaded pages - Analytics\nconst AnalyticsPage = lazy(() =>\n import('./modules/analytics').then((m) => ({ default: m.AnalyticsPage }))\n);\nconst DeveloperRevenueAdminPage = lazy(() =>\n import('./modules/earnings').then((m) => ({ default: m.DeveloperRevenueAdminPage }))\n);\nconst PayoutAdminPage = lazy(() =>\n import('./modules/earnings').then((m) => ({ default: m.PayoutAdminPage }))\n);\n\nfunction DefaultRedirect() {\n const { basePath } = useBilling();\n const routerNavigate = useNavigate();\n const base = basePath && basePath !== '/' ? basePath.replace(/\\/$/, '') : '';\n useEffect(() => {\n routerNavigate(`${base}/plans`, { replace: true });\n }, [base, routerNavigate]);\n return null;\n}\n\nfunction PageLoader() {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"size-8 animate-spin rounded-full border-4 border-action-primary-bg border-t-transparent\" />\n </div>\n );\n}\n\ntype BillingPermissions = ReturnType<typeof useBillingPermissions>;\ntype BillingPermissionKey = Exclude<\n {\n [K in keyof BillingPermissions]: BillingPermissions[K] extends boolean ? K : never;\n }[keyof BillingPermissions],\n 'isLoading'\n>;\n\ninterface BillingAdminRouteGateProps {\n title: string;\n featureFlag: string;\n permission: BillingPermissionKey;\n eventName: BillingDomainEvent;\n children: ReactNode;\n}\n\nfunction FeatureDisabled({ title, featureFlag }: { title: string; featureFlag: string }) {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center p-6\">\n <div className=\"max-w-md rounded-card border border-border-seam bg-bg-surface p-6 text-center shadow-[var(--shadow-elevation-1)]\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title} is not enabled</h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n Ask an administrator to enable the{' '}\n <span className=\"font-mono text-xs text-text-secondary bg-bg-sunken/60 rounded px-1.5 py-0.5\">\n {featureFlag}\n </span>{' '}\n feature flag for this organization.\n </p>\n </div>\n </div>\n );\n}\n\nfunction BillingAdminRouteGate({\n title,\n featureFlag,\n permission,\n eventName,\n children,\n}: BillingAdminRouteGateProps) {\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const hasPermission = Boolean(permissions[permission]);\n\n useEffect(() => {\n if (hasPermission) {\n emit(eventName, { source: 'BillingAdminRoutes' });\n }\n }, [emit, eventName, hasPermission]);\n\n if (permissions.isLoading) {\n return <PageLoader />;\n }\n\n return (\n <FeatureGate\n flag={featureFlag}\n defaultValue\n fallback={<FeatureDisabled title={title} featureFlag={featureFlag} />}\n >\n {hasPermission ? (\n children\n ) : (\n <AccessDenied message={`You don't have permission to access ${title}.`} />\n )}\n </FeatureGate>\n );\n}\n\nfunction adminPage(props: BillingAdminRouteGateProps): ReactNode {\n return <BillingAdminRouteGate {...props} />;\n}\n\n/**\n * Admin-only billing routes.\n * Includes plans/addons/quota-templates CRUD, coupons, discounts, refunds, and analytics.\n */\nexport function BillingAdminRoutes() {\n return (\n <Routes>\n <Route index element={<Navigate to=\"plans\" replace />} />\n\n {/* Plans - full CRUD */}\n <Route\n path=\"plans\"\n element={adminPage({\n title: 'Plans',\n featureFlag: 'billing.plans',\n permission: 'canViewPlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PlansListPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"plans/new\"\n element={adminPage({\n title: 'Create plan',\n featureFlag: 'billing.plans.manage',\n permission: 'canManagePlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PlanCreatePage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"plans/assign-free\"\n element={adminPage({\n title: 'Assign plan for free',\n featureFlag: 'billing.plans.manage',\n permission: 'canManagePlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PlanAssignFreePage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"plans/:planId\"\n element={adminPage({\n title: 'Plan details',\n featureFlag: 'billing.plans',\n permission: 'canViewPlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PlanDetailPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"plans/:planId/edit\"\n element={adminPage({\n title: 'Edit plan',\n featureFlag: 'billing.plans.manage',\n permission: 'canManagePlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PlanEditPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Addons - full CRUD */}\n <Route\n path=\"addons\"\n element={adminPage({\n title: 'Addons',\n featureFlag: 'billing.addons',\n permission: 'canViewAddons',\n eventName: 'billing.addon.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AddonsListPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"addons/new\"\n element={adminPage({\n title: 'Create addon',\n featureFlag: 'billing.addons.manage',\n permission: 'canManageAddons',\n eventName: 'billing.addon.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AddonCreatePage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"addons/:addonId\"\n element={adminPage({\n title: 'Addon details',\n featureFlag: 'billing.addons',\n permission: 'canViewAddons',\n eventName: 'billing.addon.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AddonDetailPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"addons/:addonId/edit\"\n element={adminPage({\n title: 'Edit addon',\n featureFlag: 'billing.addons.manage',\n permission: 'canManageAddons',\n eventName: 'billing.addon.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AddonEditPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Quota Templates - full CRUD */}\n <Route\n path=\"quotas\"\n element={adminPage({\n title: 'Quota templates',\n featureFlag: 'billing.quotas',\n permission: 'canViewQuotas',\n eventName: 'billing.quota.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <QuotaTemplatesListPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"quotas/new\"\n element={adminPage({\n title: 'Create quota template',\n featureFlag: 'billing.quotas.manage',\n permission: 'canManageQuotas',\n eventName: 'billing.quota.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <QuotaTemplateCreatePage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"quotas/:quotaId\"\n element={adminPage({\n title: 'Quota template details',\n featureFlag: 'billing.quotas',\n permission: 'canViewQuotas',\n eventName: 'billing.quota.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <QuotaTemplateDetailPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"quotas/:quotaId/edit\"\n element={adminPage({\n title: 'Edit quota template',\n featureFlag: 'billing.quotas.manage',\n permission: 'canManageQuotas',\n eventName: 'billing.quota.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <QuotaTemplateEditPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"quotas/assign-free\"\n element={adminPage({\n title: 'Assign quota for free',\n featureFlag: 'billing.quotas.manage',\n permission: 'canManageQuotas',\n eventName: 'billing.quota.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <QuotaAssignFreePage />\n </Suspense>\n ),\n })}\n />\n\n {/* Coupons */}\n <Route\n path=\"coupons\"\n element={adminPage({\n title: 'Coupons',\n featureFlag: 'billing.discounts',\n permission: 'canManageDiscounts',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <CouponsListPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"coupons/create\"\n element={adminPage({\n title: 'Create coupon',\n featureFlag: 'billing.discounts',\n permission: 'canManageDiscounts',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <CreateCouponPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"coupons/:id\"\n element={adminPage({\n title: 'Coupon details',\n featureFlag: 'billing.discounts',\n permission: 'canManageDiscounts',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <CouponDetailPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"coupons/:id/edit\"\n element={adminPage({\n title: 'Edit coupon',\n featureFlag: 'billing.discounts',\n permission: 'canManageDiscounts',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <EditCouponPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Promotions */}\n <Route\n path=\"promotions\"\n element={adminPage({\n title: 'Promotions',\n featureFlag: 'billing.plans.manage',\n permission: 'canManagePlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PromotionsListPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"promotions/create\"\n element={adminPage({\n title: 'Create Promotion',\n featureFlag: 'billing.plans.manage',\n permission: 'canManagePlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <CreatePromotionPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Discounts */}\n <Route\n path=\"discounts\"\n element={adminPage({\n title: 'Discounts',\n featureFlag: 'billing.discounts',\n permission: 'canManageDiscounts',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <DiscountsListPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Credit grants */}\n <Route\n path=\"credits\"\n element={adminPage({\n title: 'Credit grants',\n featureFlag: 'billing.credits.grants',\n permission: 'canGrantCredits',\n eventName: 'billing.credit.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AdminCreditsPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Refunds */}\n <Route\n path=\"refunds\"\n element={adminPage({\n title: 'Refunds',\n featureFlag: 'billing.refunds',\n permission: 'canProcessRefund',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <RefundsPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Billing webhook logs */}\n <Route\n path=\"webhook-logs\"\n element={adminPage({\n title: 'Billing webhook logs',\n featureFlag: 'billing.webhook-logs',\n permission: 'isBillingAdmin',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <BillingWebhookLogsPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Order callback logs */}\n <Route\n path=\"order-callback-logs\"\n element={adminPage({\n title: 'Order callback logs',\n featureFlag: 'billing.order-callback-logs',\n permission: 'isBillingAdmin',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <OrderCallbackLogsPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Analytics */}\n <Route\n path=\"analytics\"\n element={adminPage({\n title: 'Billing analytics',\n featureFlag: 'billing.analytics',\n permission: 'canViewAnalytics',\n eventName: 'billing.usage.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AnalyticsPage />\n </Suspense>\n ),\n })}\n />\n\n <Route\n path=\"developer-revenue\"\n element={adminPage({\n title: 'Developer revenue',\n featureFlag: 'billing.payouts.admin',\n permission: 'isBillingAdmin',\n eventName: 'billing.payout.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <DeveloperRevenueAdminPage />\n </Suspense>\n ),\n })}\n />\n\n <Route\n path=\"payouts\"\n element={adminPage({\n title: 'Payout administration',\n featureFlag: 'billing.payouts.admin',\n permission: 'isBillingAdmin',\n eventName: 'billing.payout.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PayoutAdminPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Catch-all — uses absolute path to avoid relative-navigation loop */}\n <Route path=\"*\" element={<DefaultRedirect />} />\n </Routes>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"BillingAdminRoutes.js","names":[],"sources":["../../src/billing/BillingAdminRoutes.tsx"],"sourcesContent":["import { lazy, Suspense, useEffect, type ReactNode } from 'react';\nimport { Navigate, Route, Routes, useNavigate } from 'react-router';\nimport { FeatureGate } from '@burdenoff/fe-libs/shared/feature-flags';\nimport { useBillingEventEmitter, type BillingDomainEvent } from './hooks/useBillingEventEmitter';\nimport { useBillingPermissions } from './hooks/useBillingPermissions';\nimport { useBilling } from './providers/BillingProvider';\nimport { AccessDenied } from './shared/components';\n\n// Lazy loaded pages - Plans (full CRUD)\nconst PlansListPage = lazy(() =>\n import('./modules/plans').then((m) => ({ default: m.PlansListPage }))\n);\nconst PlanDetailPage = lazy(() =>\n import('./modules/plans').then((m) => ({ default: m.PlanDetailPage }))\n);\nconst PlanEditPage = lazy(() =>\n import('./modules/plans').then((m) => ({ default: m.PlanEditPage }))\n);\nconst PlanCreatePage = lazy(() =>\n import('./modules/plans').then((m) => ({ default: m.PlanCreatePage }))\n);\nconst PlanAssignFreePage = lazy(() =>\n import('./modules/plans').then((m) => ({ default: m.PlanAssignFreePage }))\n);\n\n// Lazy loaded pages - Addons (full CRUD)\nconst AddonsListPage = lazy(() =>\n import('./modules/addons').then((m) => ({ default: m.AddonsListPage }))\n);\nconst AddonDetailPage = lazy(() =>\n import('./modules/addons').then((m) => ({ default: m.AddonDetailPage }))\n);\nconst AddonEditPage = lazy(() =>\n import('./modules/addons').then((m) => ({ default: m.AddonEditPage }))\n);\nconst AddonCreatePage = lazy(() =>\n import('./modules/addons').then((m) => ({ default: m.AddonCreatePage }))\n);\n\n// Lazy loaded pages - Quota Templates (full CRUD)\nconst QuotaTemplatesListPage = lazy(() =>\n import('./modules/quota').then((m) => ({ default: m.QuotaTemplatesListPage }))\n);\nconst QuotaTemplateDetailPage = lazy(() =>\n import('./modules/quota').then((m) => ({ default: m.QuotaTemplateDetailPage }))\n);\nconst QuotaTemplateCreatePage = lazy(() =>\n import('./modules/quota').then((m) => ({ default: m.QuotaTemplateCreatePage }))\n);\nconst QuotaTemplateEditPage = lazy(() =>\n import('./modules/quota').then((m) => ({ default: m.QuotaTemplateEditPage }))\n);\nconst QuotaAssignFreePage = lazy(() =>\n import('./modules/quota').then((m) => ({ default: m.QuotaAssignFreePage }))\n);\n\n// Lazy loaded pages - Coupons\nconst CouponsListPage = lazy(() =>\n import('./modules/coupons').then((m) => ({ default: m.CouponsListPage }))\n);\nconst CreateCouponPage = lazy(() =>\n import('./modules/coupons').then((m) => ({ default: m.CreateCouponPage }))\n);\nconst EditCouponPage = lazy(() =>\n import('./modules/coupons').then((m) => ({ default: m.EditCouponPage }))\n);\nconst CouponDetailPage = lazy(() =>\n import('./modules/coupons').then((m) => ({ default: m.CouponDetailPage }))\n);\n\n// Lazy loaded pages - Promotions\nconst PromotionsListPage = lazy(() =>\n import('./modules/promotions').then((m) => ({ default: m.PromotionsListPage }))\n);\nconst CreatePromotionPage = lazy(() =>\n import('./modules/promotions').then((m) => ({ default: m.CreatePromotionPage }))\n);\n\n// Lazy loaded pages - Discounts\nconst DiscountsListPage = lazy(() =>\n import('./modules/discounts').then((m) => ({ default: m.DiscountsListPage }))\n);\n\n// Lazy loaded pages - Refunds\nconst RefundsPage = lazy(() =>\n import('./modules/billing').then((m) => ({ default: m.RefundsPage }))\n);\nconst RefundIssueLogsPage = lazy(() =>\n import('./modules/billing').then((m) => ({ default: m.RefundIssueLogsPage }))\n);\n\n// Lazy loaded pages - Billing webhook logs\nconst BillingWebhookLogsPage = lazy(() =>\n import('./modules/billing').then((m) => ({ default: m.BillingWebhookLogsPage }))\n);\n\n// Lazy loaded pages - Order callback logs\nconst OrderCallbackLogsPage = lazy(() =>\n import('./modules/billing').then((m) => ({ default: m.OrderCallbackLogsPage }))\n);\n\n// Lazy loaded pages - Credits admin\nconst AdminCreditsPage = lazy(() =>\n import('./modules/credits').then((m) => ({ default: m.AdminCreditsPage }))\n);\n\n// Lazy loaded pages - Analytics\nconst AnalyticsPage = lazy(() =>\n import('./modules/analytics').then((m) => ({ default: m.AnalyticsPage }))\n);\nconst DeveloperRevenueAdminPage = lazy(() =>\n import('./modules/earnings').then((m) => ({ default: m.DeveloperRevenueAdminPage }))\n);\nconst PayoutAdminPage = lazy(() =>\n import('./modules/earnings').then((m) => ({ default: m.PayoutAdminPage }))\n);\n\nfunction DefaultRedirect() {\n const { basePath } = useBilling();\n const routerNavigate = useNavigate();\n const base = basePath && basePath !== '/' ? basePath.replace(/\\/$/, '') : '';\n useEffect(() => {\n routerNavigate(`${base}/plans`, { replace: true });\n }, [base, routerNavigate]);\n return null;\n}\n\nfunction PageLoader() {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center\">\n <div className=\"size-8 animate-spin rounded-full border-4 border-action-primary-bg border-t-transparent\" />\n </div>\n );\n}\n\ntype BillingPermissions = ReturnType<typeof useBillingPermissions>;\ntype BillingPermissionKey = Exclude<\n {\n [K in keyof BillingPermissions]: BillingPermissions[K] extends boolean ? K : never;\n }[keyof BillingPermissions],\n 'isLoading'\n>;\n\ninterface BillingAdminRouteGateProps {\n title: string;\n featureFlag: string;\n permission: BillingPermissionKey;\n eventName: BillingDomainEvent;\n children: ReactNode;\n}\n\nfunction FeatureDisabled({ title, featureFlag }: { title: string; featureFlag: string }) {\n return (\n <div className=\"flex min-h-[400px] items-center justify-center p-6\">\n <div className=\"max-w-md rounded-card border border-border-seam bg-bg-surface p-6 text-center shadow-[var(--shadow-elevation-1)]\">\n <h2 className=\"text-base font-semibold text-text-primary\">{title} is not enabled</h2>\n <p className=\"mt-2 text-sm text-text-secondary\">\n Ask an administrator to enable the{' '}\n <span className=\"font-mono text-xs text-text-secondary bg-bg-sunken/60 rounded px-1.5 py-0.5\">\n {featureFlag}\n </span>{' '}\n feature flag for this organization.\n </p>\n </div>\n </div>\n );\n}\n\nfunction BillingAdminRouteGate({\n title,\n featureFlag,\n permission,\n eventName,\n children,\n}: BillingAdminRouteGateProps) {\n const permissions = useBillingPermissions();\n const { emit } = useBillingEventEmitter();\n const hasPermission = Boolean(permissions[permission]);\n\n useEffect(() => {\n if (hasPermission) {\n emit(eventName, { source: 'BillingAdminRoutes' });\n }\n }, [emit, eventName, hasPermission]);\n\n if (permissions.isLoading) {\n return <PageLoader />;\n }\n\n return (\n <FeatureGate\n flag={featureFlag}\n defaultValue\n fallback={<FeatureDisabled title={title} featureFlag={featureFlag} />}\n >\n {hasPermission ? (\n children\n ) : (\n <AccessDenied message={`You don't have permission to access ${title}.`} />\n )}\n </FeatureGate>\n );\n}\n\nfunction adminPage(props: BillingAdminRouteGateProps): ReactNode {\n return <BillingAdminRouteGate {...props} />;\n}\n\n/**\n * Admin-only billing routes.\n * Includes plans/addons/quota-templates CRUD, coupons, discounts, refunds, and analytics.\n */\nexport function BillingAdminRoutes() {\n return (\n <Routes>\n <Route index element={<Navigate to=\"plans\" replace />} />\n\n {/* Plans - full CRUD */}\n <Route\n path=\"plans\"\n element={adminPage({\n title: 'Plans',\n featureFlag: 'billing.plans',\n permission: 'canViewPlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PlansListPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"plans/new\"\n element={adminPage({\n title: 'Create plan',\n featureFlag: 'billing.plans.manage',\n permission: 'canManagePlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PlanCreatePage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"plans/assign-free\"\n element={adminPage({\n title: 'Assign plan for free',\n featureFlag: 'billing.plans.manage',\n permission: 'canManagePlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PlanAssignFreePage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"plans/:planId\"\n element={adminPage({\n title: 'Plan details',\n featureFlag: 'billing.plans',\n permission: 'canViewPlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PlanDetailPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"plans/:planId/edit\"\n element={adminPage({\n title: 'Edit plan',\n featureFlag: 'billing.plans.manage',\n permission: 'canManagePlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PlanEditPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Addons - full CRUD */}\n <Route\n path=\"addons\"\n element={adminPage({\n title: 'Addons',\n featureFlag: 'billing.addons',\n permission: 'canViewAddons',\n eventName: 'billing.addon.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AddonsListPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"addons/new\"\n element={adminPage({\n title: 'Create addon',\n featureFlag: 'billing.addons.manage',\n permission: 'canManageAddons',\n eventName: 'billing.addon.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AddonCreatePage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"addons/:addonId\"\n element={adminPage({\n title: 'Addon details',\n featureFlag: 'billing.addons',\n permission: 'canViewAddons',\n eventName: 'billing.addon.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AddonDetailPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"addons/:addonId/edit\"\n element={adminPage({\n title: 'Edit addon',\n featureFlag: 'billing.addons.manage',\n permission: 'canManageAddons',\n eventName: 'billing.addon.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AddonEditPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Quota Templates - full CRUD */}\n <Route\n path=\"quotas\"\n element={adminPage({\n title: 'Quota templates',\n featureFlag: 'billing.quotas',\n permission: 'canViewQuotas',\n eventName: 'billing.quota.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <QuotaTemplatesListPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"quotas/new\"\n element={adminPage({\n title: 'Create quota template',\n featureFlag: 'billing.quotas.manage',\n permission: 'canManageQuotas',\n eventName: 'billing.quota.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <QuotaTemplateCreatePage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"quotas/:quotaId\"\n element={adminPage({\n title: 'Quota template details',\n featureFlag: 'billing.quotas',\n permission: 'canViewQuotas',\n eventName: 'billing.quota.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <QuotaTemplateDetailPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"quotas/:quotaId/edit\"\n element={adminPage({\n title: 'Edit quota template',\n featureFlag: 'billing.quotas.manage',\n permission: 'canManageQuotas',\n eventName: 'billing.quota.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <QuotaTemplateEditPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"quotas/assign-free\"\n element={adminPage({\n title: 'Assign quota for free',\n featureFlag: 'billing.quotas.manage',\n permission: 'canManageQuotas',\n eventName: 'billing.quota.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <QuotaAssignFreePage />\n </Suspense>\n ),\n })}\n />\n\n {/* Coupons */}\n <Route\n path=\"coupons\"\n element={adminPage({\n title: 'Coupons',\n featureFlag: 'billing.discounts',\n permission: 'canManageDiscounts',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <CouponsListPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"coupons/create\"\n element={adminPage({\n title: 'Create coupon',\n featureFlag: 'billing.discounts',\n permission: 'canManageDiscounts',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <CreateCouponPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"coupons/:id\"\n element={adminPage({\n title: 'Coupon details',\n featureFlag: 'billing.discounts',\n permission: 'canManageDiscounts',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <CouponDetailPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"coupons/:id/edit\"\n element={adminPage({\n title: 'Edit coupon',\n featureFlag: 'billing.discounts',\n permission: 'canManageDiscounts',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <EditCouponPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Promotions */}\n <Route\n path=\"promotions\"\n element={adminPage({\n title: 'Promotions',\n featureFlag: 'billing.plans.manage',\n permission: 'canManagePlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PromotionsListPage />\n </Suspense>\n ),\n })}\n />\n <Route\n path=\"promotions/create\"\n element={adminPage({\n title: 'Create Promotion',\n featureFlag: 'billing.plans.manage',\n permission: 'canManagePlans',\n eventName: 'billing.plan.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <CreatePromotionPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Discounts */}\n <Route\n path=\"discounts\"\n element={adminPage({\n title: 'Discounts',\n featureFlag: 'billing.discounts',\n permission: 'canManageDiscounts',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <DiscountsListPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Credit grants */}\n <Route\n path=\"credits\"\n element={adminPage({\n title: 'Credit grants',\n featureFlag: 'billing.credits.grants',\n permission: 'canGrantCredits',\n eventName: 'billing.credit.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AdminCreditsPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Refunds */}\n <Route\n path=\"refunds\"\n element={adminPage({\n title: 'Refunds',\n featureFlag: 'billing.refunds',\n permission: 'canProcessRefund',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <RefundsPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Refund issue log — refunds that completed at the gateway but left an\n unresolved accounting/reconciliation issue (BOFF-7192) */}\n <Route\n path=\"refunds/logs\"\n element={adminPage({\n title: 'Refund Issue Log',\n featureFlag: 'billing.refunds',\n permission: 'canProcessRefund',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <RefundIssueLogsPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Billing webhook logs */}\n <Route\n path=\"webhook-logs\"\n element={adminPage({\n title: 'Billing webhook logs',\n featureFlag: 'billing.webhook-logs',\n permission: 'isBillingAdmin',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <BillingWebhookLogsPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Order callback logs */}\n <Route\n path=\"order-callback-logs\"\n element={adminPage({\n title: 'Order callback logs',\n featureFlag: 'billing.order-callback-logs',\n permission: 'isBillingAdmin',\n eventName: 'billing.transaction.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <OrderCallbackLogsPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Analytics */}\n <Route\n path=\"analytics\"\n element={adminPage({\n title: 'Billing analytics',\n featureFlag: 'billing.analytics',\n permission: 'canViewAnalytics',\n eventName: 'billing.usage.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <AnalyticsPage />\n </Suspense>\n ),\n })}\n />\n\n <Route\n path=\"developer-revenue\"\n element={adminPage({\n title: 'Developer revenue',\n featureFlag: 'billing.payouts.admin',\n permission: 'isBillingAdmin',\n eventName: 'billing.payout.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <DeveloperRevenueAdminPage />\n </Suspense>\n ),\n })}\n />\n\n <Route\n path=\"payouts\"\n element={adminPage({\n title: 'Payout administration',\n featureFlag: 'billing.payouts.admin',\n permission: 'isBillingAdmin',\n eventName: 'billing.payout.viewed',\n children: (\n <Suspense fallback={<PageLoader />}>\n <PayoutAdminPage />\n </Suspense>\n ),\n })}\n />\n\n {/* Catch-all — uses absolute path to avoid relative-navigation loop */}\n <Route path=\"*\" element={<DefaultRedirect />} />\n </Routes>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"useRefunds.js","names":[],"sources":["../../../../../src/billing/modules/billing/hooks/useRefunds.ts"],"sourcesContent":["/**\n * Billing Module - useRefunds Hook\n * Provides access to refund records, statistics, and mutations\n */\n\nimport { useApolloClient } from '@apollo/client/react';\nimport { useCallback, useState, useMemo, useEffect } from 'react';\nimport {\n useGetRefundRecordsQuery,\n useGetPendingRefundsQuery,\n useGetRefundRecordQuery,\n useGetRefundStatsQuery,\n useGetUnprocessedFailedWebhooksQuery,\n useGetBillingWebhookLogsQuery,\n useCreateRefundRecordMutation,\n useProcessRefundMutation,\n useSkipRefundMutation,\n useRetryRefundMutation,\n useCreateRefundRecordsFromFailedWebhooksMutation,\n useCreateRefundRecordFromWebhookMutation,\n GetRefundRecordsDocument,\n GetPendingRefundsDocument,\n GetRefundStatsDocument,\n GetUnprocessedFailedWebhooksDocument,\n GetBillingWebhookLogsDocument,\n} from '../../../../generated/global-operations';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type RefundRecordStatus = 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED' | 'SKIPPED';\nexport type RefundSource =\n 'MANUAL' | 'WEBHOOK_RECOVERY' | 'SUBSCRIPTION_CANCEL' | 'DUPLICATE_PAYMENT';\n\nexport interface RefundRecord {\n id: string;\n orgId: string;\n billingAccountId: string | null;\n source: RefundSource;\n status: RefundRecordStatus;\n provider: string;\n gatewayPaymentId: string;\n gatewayOrderId: string | null;\n amount: number;\n currency: string;\n purchaseType: string | null;\n purchaseDetail: string | null;\n reason: string | null;\n notes: string | null;\n webhookLogId: string | null;\n transactionId: string | null;\n processedBy: string | null;\n processedAt: string | null;\n gatewayRefundId: string | null;\n errorMessage: string | null;\n retryCount: number;\n metadata: Record<string, unknown> | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface RefundStats {\n pending: number;\n processing: number;\n completed: number;\n failed: number;\n skipped: number;\n totalPendingAmount: number;\n totalCompletedAmount: number;\n}\n\nexport interface WebhookLog {\n id: string;\n event: string;\n provider: string;\n payload: Record<string, unknown>;\n processed: boolean;\n error: string | null;\n createdAt: string;\n}\n\nexport interface RefundRecordFilters {\n status?: RefundRecordStatus;\n source?: RefundSource;\n provider?: string;\n billingAccountId?: string;\n fromDate?: string;\n toDate?: string;\n}\n\nexport interface CreateRefundRecordInput {\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 metadata?: Record<string, unknown>;\n}\n\nexport interface ProcessRefundResult {\n success: boolean;\n refundRecord: RefundRecord | null;\n gatewayRefundId: string | null;\n error: string | null;\n}\n\ninterface PaginationInfo {\n totalCount: number;\n hasMore: boolean;\n page: number;\n pageSize: number;\n hasPrevious: boolean;\n}\n\n// ============================================================================\n// Hook Results\n// ============================================================================\n\ninterface UseRefundRecordsOptions {\n pageSize?: number;\n filters?: RefundRecordFilters;\n}\n\ninterface UseRefundRecordsResult {\n records: RefundRecord[];\n pagination: PaginationInfo;\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n goToNextPage: () => void;\n goToPreviousPage: () => void;\n goToPage: (page: number) => void;\n setFilters: (filters: RefundRecordFilters) => void;\n}\n\ninterface UseRefundStatsResult {\n stats: RefundStats | null;\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\ninterface UseRefundRecordResult {\n record: RefundRecord | null;\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\ninterface UseUnprocessedWebhooksResult {\n webhooks: WebhookLog[];\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\nexport interface BillingWebhookLogFilters {\n processed?: boolean;\n provider?: string;\n hasError?: boolean;\n}\n\ninterface UseBillingWebhookLogsResult {\n logs: WebhookLog[];\n totalCount: number;\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\ninterface UseRefundMutationsResult {\n createRefundRecord: (input: CreateRefundRecordInput) => Promise<RefundRecord>;\n processRefund: (id: string, notes?: string) => Promise<ProcessRefundResult>;\n skipRefund: (id: string, reason: string) => Promise<RefundRecord>;\n retryRefund: (id: string) => Promise<ProcessRefundResult>;\n createRefundRecordsFromFailedWebhooks: () => Promise<RefundRecord[]>;\n createRefundRecordFromWebhook: (webhookLogId: string) => Promise<RefundRecord | null>;\n isCreating: boolean;\n isProcessing: boolean;\n isSkipping: boolean;\n isRetrying: boolean;\n isCreatingFromWebhooks: boolean;\n isCreatingFromWebhook: boolean;\n}\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\nconst DEFAULT_PAGE_SIZE = 20;\n\n/**\n * Hook for fetching paginated refund records with filters\n */\nexport function useRefundRecords(options: UseRefundRecordsOptions = {}): UseRefundRecordsResult {\n const pageSize = options.pageSize || DEFAULT_PAGE_SIZE;\n const [currentPage, setCurrentPage] = useState(0);\n const [filters, setFilters] = useState<RefundRecordFilters>(options.filters || {});\n\n // Update filters when options change (e.g., when tab changes)\n useEffect(() => {\n setFilters(options.filters || {});\n setCurrentPage(0); // Reset to first page when filters change externally\n }, [options.filters]);\n\n const { data, loading, error, refetch } = useGetRefundRecordsQuery({\n variables: {\n ...filters,\n page: currentPage,\n pageSize,\n },\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n const result = data?.getRefundRecords;\n\n const goToNextPage = useCallback(() => {\n if (result?.hasMore) {\n setCurrentPage((prev) => prev + 1);\n }\n }, [result?.hasMore]);\n\n const goToPreviousPage = useCallback(() => {\n if (currentPage > 0) {\n setCurrentPage((prev) => prev - 1);\n }\n }, [currentPage]);\n\n const goToPage = useCallback(\n (page: number) => {\n const totalPages = result ? Math.ceil(result.totalCount / pageSize) : 0;\n if (page >= 0 && page < totalPages) {\n setCurrentPage(page);\n }\n },\n [result, pageSize]\n );\n\n const handleSetFilters = useCallback((newFilters: RefundRecordFilters) => {\n setFilters(newFilters);\n setCurrentPage(0); // Reset to first page when filters change\n }, []);\n\n return {\n records: result?.records || [],\n pagination: {\n totalCount: result?.totalCount || 0,\n hasMore: result?.hasMore || false,\n page: currentPage,\n pageSize,\n hasPrevious: currentPage > 0,\n },\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n goToNextPage,\n goToPreviousPage,\n goToPage,\n setFilters: handleSetFilters,\n };\n}\n\n/**\n * Hook for fetching pending refund records (shortcut)\n */\nexport function usePendingRefunds(pageSize = DEFAULT_PAGE_SIZE): UseRefundRecordsResult {\n const [currentPage, setCurrentPage] = useState(0);\n\n const { data, loading, error, refetch } = useGetPendingRefundsQuery({\n variables: { page: currentPage, pageSize },\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n const result = data?.getPendingRefunds;\n\n const goToNextPage = useCallback(() => {\n if (result?.hasMore) {\n setCurrentPage((prev) => prev + 1);\n }\n }, [result?.hasMore]);\n\n const goToPreviousPage = useCallback(() => {\n if (currentPage > 0) {\n setCurrentPage((prev) => prev - 1);\n }\n }, [currentPage]);\n\n const goToPage = useCallback(\n (page: number) => {\n const totalPages = result ? Math.ceil(result.totalCount / pageSize) : 0;\n if (page >= 0 && page < totalPages) {\n setCurrentPage(page);\n }\n },\n [result, pageSize]\n );\n\n return {\n records: result?.records || [],\n pagination: {\n totalCount: result?.totalCount || 0,\n hasMore: result?.hasMore || false,\n page: currentPage,\n pageSize,\n hasPrevious: currentPage > 0,\n },\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n goToNextPage,\n goToPreviousPage,\n goToPage,\n setFilters: () => {}, // No-op for pending refunds\n };\n}\n\n/**\n * Hook for fetching refund statistics\n */\nexport function useRefundStats(): UseRefundStatsResult {\n const { data, loading, error, refetch } = useGetRefundStatsQuery({\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n stats: data?.getRefundStats || null,\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\n/**\n * Hook for fetching a single refund record by ID\n */\nexport function useRefundRecord(id: string | undefined): UseRefundRecordResult {\n const { data, loading, error, refetch } = useGetRefundRecordQuery({\n variables: { id: id! },\n skip: !id,\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n record: data?.getRefundRecord || null,\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\n/**\n * Hook for fetching unprocessed failed webhooks\n */\nexport function useUnprocessedFailedWebhooks(): UseUnprocessedWebhooksResult {\n const { data, loading, error, refetch } = useGetUnprocessedFailedWebhooksQuery({\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n webhooks: data?.getUnprocessedFailedWebhooks || [],\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\n/**\n * Hook for the admin billing-webhook-log viewer — paginated, filterable view\n * over ALL webhook log rows (unlike useUnprocessedFailedWebhooks, which only\n * scans the narrow refund-candidate subset).\n */\nexport function useBillingWebhookLogs(\n filters: BillingWebhookLogFilters,\n page: number,\n pageSize: number\n): UseBillingWebhookLogsResult {\n const { data, loading, error, refetch } = useGetBillingWebhookLogsQuery({\n variables: {\n limit: pageSize,\n offset: page * pageSize,\n processed: filters.processed,\n provider: filters.provider,\n hasError: filters.hasError,\n },\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n logs: data?.getBillingWebhookLogs.logs || [],\n totalCount: data?.getBillingWebhookLogs.totalCount || 0,\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\n/**\n * Hook for refund mutations\n */\nexport function useRefundMutations(): UseRefundMutationsResult {\n const client = useApolloClient();\n\n const [createRefundRecordMutation, { loading: isCreating }] = useCreateRefundRecordMutation();\n const [processRefundMutation, { loading: isProcessing }] = useProcessRefundMutation();\n const [skipRefundMutation, { loading: isSkipping }] = useSkipRefundMutation();\n const [retryRefundMutation, { loading: isRetrying }] = useRetryRefundMutation();\n const [createFromWebhooksMutation, { loading: isCreatingFromWebhooks }] =\n useCreateRefundRecordsFromFailedWebhooksMutation();\n const [createFromWebhookMutation, { loading: isCreatingFromWebhook }] =\n useCreateRefundRecordFromWebhookMutation();\n\n const refetchQueries = useCallback(async () => {\n await client.refetchQueries({\n include: [GetRefundRecordsDocument, GetPendingRefundsDocument, GetRefundStatsDocument],\n });\n }, [client]);\n\n const createRefundRecord = useCallback(\n async (input: CreateRefundRecordInput): Promise<RefundRecord> => {\n const { data } = await createRefundRecordMutation({\n variables: input,\n });\n await refetchQueries();\n return data.createRefundRecord;\n },\n [createRefundRecordMutation, refetchQueries]\n );\n\n const processRefund = useCallback(\n async (id: string, notes?: string): Promise<ProcessRefundResult> => {\n const { data } = await processRefundMutation({\n variables: { id, notes },\n });\n await refetchQueries();\n return data.processRefund;\n },\n [processRefundMutation, refetchQueries]\n );\n\n const skipRefund = useCallback(\n async (id: string, reason: string): Promise<RefundRecord> => {\n const { data } = await skipRefundMutation({\n variables: { id, reason },\n });\n await refetchQueries();\n return data.skipRefund;\n },\n [skipRefundMutation, refetchQueries]\n );\n\n const retryRefund = useCallback(\n async (id: string): Promise<ProcessRefundResult> => {\n const { data } = await retryRefundMutation({\n variables: { id },\n });\n await refetchQueries();\n return data.retryRefund;\n },\n [retryRefundMutation, refetchQueries]\n );\n\n const createRefundRecordsFromFailedWebhooks = useCallback(async (): Promise<RefundRecord[]> => {\n const { data } = await createFromWebhooksMutation();\n await client.refetchQueries({\n include: [\n GetRefundRecordsDocument,\n GetPendingRefundsDocument,\n GetRefundStatsDocument,\n GetUnprocessedFailedWebhooksDocument,\n GetBillingWebhookLogsDocument,\n ],\n });\n return data!.createRefundRecordsFromFailedWebhooks;\n }, [createFromWebhooksMutation, client]);\n\n const createRefundRecordFromWebhook = useCallback(\n async (webhookLogId: string): Promise<RefundRecord | null> => {\n const { data } = await createFromWebhookMutation({\n variables: { webhookLogId },\n });\n await client.refetchQueries({\n include: [\n GetRefundRecordsDocument,\n GetPendingRefundsDocument,\n GetRefundStatsDocument,\n GetUnprocessedFailedWebhooksDocument,\n ],\n });\n return data!.createRefundRecordFromWebhook;\n },\n [createFromWebhookMutation, client]\n );\n\n return {\n createRefundRecord,\n processRefund,\n skipRefund,\n retryRefund,\n createRefundRecordsFromFailedWebhooks,\n createRefundRecordFromWebhook,\n isCreating,\n isProcessing,\n isSkipping,\n isRetrying,\n isCreatingFromWebhooks,\n isCreatingFromWebhook,\n };\n}\n\n/**\n * Combined hook for refund page with all data and mutations\n */\nexport function useRefunds(options: UseRefundRecordsOptions = {}) {\n const recordsHook = useRefundRecords(options);\n const statsHook = useRefundStats();\n const mutationsHook = useRefundMutations();\n const webhooksHook = useUnprocessedFailedWebhooks();\n\n const isLoading = useMemo(\n () => recordsHook.isLoading || statsHook.isLoading,\n [recordsHook.isLoading, statsHook.isLoading]\n );\n\n const refetchAll = useCallback(async () => {\n await Promise.all([recordsHook.refetch(), statsHook.refetch(), webhooksHook.refetch()]);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Intentional: accessing stable refetch methods from hooks\n }, [recordsHook.refetch, statsHook.refetch, webhooksHook.refetch]);\n\n return {\n // Records\n records: recordsHook.records,\n pagination: recordsHook.pagination,\n goToNextPage: recordsHook.goToNextPage,\n goToPreviousPage: recordsHook.goToPreviousPage,\n goToPage: recordsHook.goToPage,\n setFilters: recordsHook.setFilters,\n\n // Stats\n stats: statsHook.stats,\n\n // Failed webhooks\n unprocessedWebhooks: webhooksHook.webhooks,\n webhooksLoading: webhooksHook.isLoading,\n\n // Loading states\n isLoading,\n recordsError: recordsHook.error,\n statsError: statsHook.error,\n\n // Mutations\n ...mutationsHook,\n\n // Refetch\n refetch: refetchAll,\n refetchRecords: recordsHook.refetch,\n refetchStats: statsHook.refetch,\n refetchWebhooks: webhooksHook.refetch,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"useRefunds.js","names":[],"sources":["../../../../../src/billing/modules/billing/hooks/useRefunds.ts"],"sourcesContent":["/**\n * Billing Module - useRefunds Hook\n * Provides access to refund records, statistics, and mutations\n */\n\nimport { useApolloClient } from '@apollo/client/react';\nimport { useCallback, useState, useMemo, useEffect } from 'react';\nimport {\n useGetRefundRecordsQuery,\n useGetPendingRefundsQuery,\n useGetRefundRecordQuery,\n useGetRefundStatsQuery,\n useGetUnprocessedFailedWebhooksQuery,\n useGetBillingWebhookLogsQuery,\n useCreateRefundRecordMutation,\n useProcessRefundMutation,\n useSkipRefundMutation,\n useRetryRefundMutation,\n useCreateRefundRecordsFromFailedWebhooksMutation,\n useCreateRefundRecordFromWebhookMutation,\n GetRefundRecordsDocument,\n GetPendingRefundsDocument,\n GetRefundStatsDocument,\n GetUnprocessedFailedWebhooksDocument,\n GetBillingWebhookLogsDocument,\n} from '../../../../generated/global-operations';\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport type RefundRecordStatus = 'PENDING' | 'PROCESSING' | 'COMPLETED' | 'FAILED' | 'SKIPPED';\nexport type RefundSource =\n 'MANUAL' | 'WEBHOOK_RECOVERY' | 'SUBSCRIPTION_CANCEL' | 'DUPLICATE_PAYMENT';\n\nexport interface RefundRecord {\n id: string;\n orgId: string;\n billingAccountId: string | null;\n source: RefundSource;\n status: RefundRecordStatus;\n provider: string;\n gatewayPaymentId: string;\n gatewayOrderId: string | null;\n amount: number;\n currency: string;\n purchaseType: string | null;\n purchaseDetail: string | null;\n reason: string | null;\n notes: string | null;\n webhookLogId: string | null;\n transactionId: string | null;\n processedBy: string | null;\n processedAt: string | null;\n gatewayRefundId: string | null;\n errorMessage: string | null;\n retryCount: number;\n metadata: Record<string, unknown> | null;\n createdAt: string;\n updatedAt: string;\n}\n\nexport interface RefundStats {\n pending: number;\n processing: number;\n completed: number;\n failed: number;\n skipped: number;\n totalPendingAmount: number;\n totalCompletedAmount: number;\n}\n\nexport interface WebhookLog {\n id: string;\n event: string;\n provider: string;\n payload: Record<string, unknown>;\n processed: boolean;\n error: string | null;\n createdAt: string;\n}\n\nexport interface RefundRecordFilters {\n status?: RefundRecordStatus;\n source?: RefundSource;\n provider?: string;\n billingAccountId?: string;\n fromDate?: string;\n toDate?: string;\n /** true → only refunds carrying a non-null errorMessage (an unresolved\n * accounting/reconciliation issue). Powers the admin refund issue log. */\n hasError?: boolean;\n}\n\nexport interface CreateRefundRecordInput {\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 metadata?: Record<string, unknown>;\n}\n\nexport interface ProcessRefundResult {\n success: boolean;\n refundRecord: RefundRecord | null;\n gatewayRefundId: string | null;\n error: string | null;\n}\n\ninterface PaginationInfo {\n totalCount: number;\n hasMore: boolean;\n page: number;\n pageSize: number;\n hasPrevious: boolean;\n}\n\n// ============================================================================\n// Hook Results\n// ============================================================================\n\ninterface UseRefundRecordsOptions {\n pageSize?: number;\n filters?: RefundRecordFilters;\n}\n\ninterface UseRefundRecordsResult {\n records: RefundRecord[];\n pagination: PaginationInfo;\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n goToNextPage: () => void;\n goToPreviousPage: () => void;\n goToPage: (page: number) => void;\n setFilters: (filters: RefundRecordFilters) => void;\n}\n\ninterface UseRefundStatsResult {\n stats: RefundStats | null;\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\ninterface UseRefundRecordResult {\n record: RefundRecord | null;\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\ninterface UseUnprocessedWebhooksResult {\n webhooks: WebhookLog[];\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\nexport interface BillingWebhookLogFilters {\n processed?: boolean;\n provider?: string;\n hasError?: boolean;\n}\n\ninterface UseBillingWebhookLogsResult {\n logs: WebhookLog[];\n totalCount: number;\n isLoading: boolean;\n error: Error | undefined;\n refetch: () => Promise<void>;\n}\n\ninterface UseRefundMutationsResult {\n createRefundRecord: (input: CreateRefundRecordInput) => Promise<RefundRecord>;\n processRefund: (id: string, notes?: string) => Promise<ProcessRefundResult>;\n skipRefund: (id: string, reason: string) => Promise<RefundRecord>;\n retryRefund: (id: string) => Promise<ProcessRefundResult>;\n createRefundRecordsFromFailedWebhooks: () => Promise<RefundRecord[]>;\n createRefundRecordFromWebhook: (webhookLogId: string) => Promise<RefundRecord | null>;\n isCreating: boolean;\n isProcessing: boolean;\n isSkipping: boolean;\n isRetrying: boolean;\n isCreatingFromWebhooks: boolean;\n isCreatingFromWebhook: boolean;\n}\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\nconst DEFAULT_PAGE_SIZE = 20;\n\n/**\n * Hook for fetching paginated refund records with filters\n */\nexport function useRefundRecords(options: UseRefundRecordsOptions = {}): UseRefundRecordsResult {\n const pageSize = options.pageSize || DEFAULT_PAGE_SIZE;\n const [currentPage, setCurrentPage] = useState(0);\n const [filters, setFilters] = useState<RefundRecordFilters>(options.filters || {});\n\n // Update filters when options change (e.g., when tab changes)\n useEffect(() => {\n setFilters(options.filters || {});\n setCurrentPage(0); // Reset to first page when filters change externally\n }, [options.filters]);\n\n const { data, loading, error, refetch } = useGetRefundRecordsQuery({\n variables: {\n ...filters,\n page: currentPage,\n pageSize,\n },\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n const result = data?.getRefundRecords;\n\n const goToNextPage = useCallback(() => {\n if (result?.hasMore) {\n setCurrentPage((prev) => prev + 1);\n }\n }, [result?.hasMore]);\n\n const goToPreviousPage = useCallback(() => {\n if (currentPage > 0) {\n setCurrentPage((prev) => prev - 1);\n }\n }, [currentPage]);\n\n const goToPage = useCallback(\n (page: number) => {\n const totalPages = result ? Math.ceil(result.totalCount / pageSize) : 0;\n if (page >= 0 && page < totalPages) {\n setCurrentPage(page);\n }\n },\n [result, pageSize]\n );\n\n const handleSetFilters = useCallback((newFilters: RefundRecordFilters) => {\n setFilters(newFilters);\n setCurrentPage(0); // Reset to first page when filters change\n }, []);\n\n return {\n records: result?.records || [],\n pagination: {\n totalCount: result?.totalCount || 0,\n hasMore: result?.hasMore || false,\n page: currentPage,\n pageSize,\n hasPrevious: currentPage > 0,\n },\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n goToNextPage,\n goToPreviousPage,\n goToPage,\n setFilters: handleSetFilters,\n };\n}\n\n/**\n * Hook for fetching pending refund records (shortcut)\n */\nexport function usePendingRefunds(pageSize = DEFAULT_PAGE_SIZE): UseRefundRecordsResult {\n const [currentPage, setCurrentPage] = useState(0);\n\n const { data, loading, error, refetch } = useGetPendingRefundsQuery({\n variables: { page: currentPage, pageSize },\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n const result = data?.getPendingRefunds;\n\n const goToNextPage = useCallback(() => {\n if (result?.hasMore) {\n setCurrentPage((prev) => prev + 1);\n }\n }, [result?.hasMore]);\n\n const goToPreviousPage = useCallback(() => {\n if (currentPage > 0) {\n setCurrentPage((prev) => prev - 1);\n }\n }, [currentPage]);\n\n const goToPage = useCallback(\n (page: number) => {\n const totalPages = result ? Math.ceil(result.totalCount / pageSize) : 0;\n if (page >= 0 && page < totalPages) {\n setCurrentPage(page);\n }\n },\n [result, pageSize]\n );\n\n return {\n records: result?.records || [],\n pagination: {\n totalCount: result?.totalCount || 0,\n hasMore: result?.hasMore || false,\n page: currentPage,\n pageSize,\n hasPrevious: currentPage > 0,\n },\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n goToNextPage,\n goToPreviousPage,\n goToPage,\n setFilters: () => {}, // No-op for pending refunds\n };\n}\n\n/**\n * Hook for fetching refund statistics\n */\nexport function useRefundStats(): UseRefundStatsResult {\n const { data, loading, error, refetch } = useGetRefundStatsQuery({\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n stats: data?.getRefundStats || null,\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\n/**\n * Hook for fetching a single refund record by ID\n */\nexport function useRefundRecord(id: string | undefined): UseRefundRecordResult {\n const { data, loading, error, refetch } = useGetRefundRecordQuery({\n variables: { id: id! },\n skip: !id,\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n record: data?.getRefundRecord || null,\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\n/**\n * Hook for fetching unprocessed failed webhooks\n */\nexport function useUnprocessedFailedWebhooks(): UseUnprocessedWebhooksResult {\n const { data, loading, error, refetch } = useGetUnprocessedFailedWebhooksQuery({\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n webhooks: data?.getUnprocessedFailedWebhooks || [],\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\n/**\n * Hook for the admin billing-webhook-log viewer — paginated, filterable view\n * over ALL webhook log rows (unlike useUnprocessedFailedWebhooks, which only\n * scans the narrow refund-candidate subset).\n */\nexport function useBillingWebhookLogs(\n filters: BillingWebhookLogFilters,\n page: number,\n pageSize: number\n): UseBillingWebhookLogsResult {\n const { data, loading, error, refetch } = useGetBillingWebhookLogsQuery({\n variables: {\n limit: pageSize,\n offset: page * pageSize,\n processed: filters.processed,\n provider: filters.provider,\n hasError: filters.hasError,\n },\n fetchPolicy: 'cache-and-network',\n });\n\n const handleRefetch = useCallback(async () => {\n await refetch();\n }, [refetch]);\n\n return {\n logs: data?.getBillingWebhookLogs.logs || [],\n totalCount: data?.getBillingWebhookLogs.totalCount || 0,\n isLoading: loading,\n error: error as Error | undefined,\n refetch: handleRefetch,\n };\n}\n\n/**\n * Hook for refund mutations\n */\nexport function useRefundMutations(): UseRefundMutationsResult {\n const client = useApolloClient();\n\n const [createRefundRecordMutation, { loading: isCreating }] = useCreateRefundRecordMutation();\n const [processRefundMutation, { loading: isProcessing }] = useProcessRefundMutation();\n const [skipRefundMutation, { loading: isSkipping }] = useSkipRefundMutation();\n const [retryRefundMutation, { loading: isRetrying }] = useRetryRefundMutation();\n const [createFromWebhooksMutation, { loading: isCreatingFromWebhooks }] =\n useCreateRefundRecordsFromFailedWebhooksMutation();\n const [createFromWebhookMutation, { loading: isCreatingFromWebhook }] =\n useCreateRefundRecordFromWebhookMutation();\n\n const refetchQueries = useCallback(async () => {\n await client.refetchQueries({\n include: [GetRefundRecordsDocument, GetPendingRefundsDocument, GetRefundStatsDocument],\n });\n }, [client]);\n\n const createRefundRecord = useCallback(\n async (input: CreateRefundRecordInput): Promise<RefundRecord> => {\n const { data } = await createRefundRecordMutation({\n variables: input,\n });\n await refetchQueries();\n return data.createRefundRecord;\n },\n [createRefundRecordMutation, refetchQueries]\n );\n\n const processRefund = useCallback(\n async (id: string, notes?: string): Promise<ProcessRefundResult> => {\n const { data } = await processRefundMutation({\n variables: { id, notes },\n });\n await refetchQueries();\n return data.processRefund;\n },\n [processRefundMutation, refetchQueries]\n );\n\n const skipRefund = useCallback(\n async (id: string, reason: string): Promise<RefundRecord> => {\n const { data } = await skipRefundMutation({\n variables: { id, reason },\n });\n await refetchQueries();\n return data.skipRefund;\n },\n [skipRefundMutation, refetchQueries]\n );\n\n const retryRefund = useCallback(\n async (id: string): Promise<ProcessRefundResult> => {\n const { data } = await retryRefundMutation({\n variables: { id },\n });\n await refetchQueries();\n return data.retryRefund;\n },\n [retryRefundMutation, refetchQueries]\n );\n\n const createRefundRecordsFromFailedWebhooks = useCallback(async (): Promise<RefundRecord[]> => {\n const { data } = await createFromWebhooksMutation();\n await client.refetchQueries({\n include: [\n GetRefundRecordsDocument,\n GetPendingRefundsDocument,\n GetRefundStatsDocument,\n GetUnprocessedFailedWebhooksDocument,\n GetBillingWebhookLogsDocument,\n ],\n });\n return data!.createRefundRecordsFromFailedWebhooks;\n }, [createFromWebhooksMutation, client]);\n\n const createRefundRecordFromWebhook = useCallback(\n async (webhookLogId: string): Promise<RefundRecord | null> => {\n const { data } = await createFromWebhookMutation({\n variables: { webhookLogId },\n });\n await client.refetchQueries({\n include: [\n GetRefundRecordsDocument,\n GetPendingRefundsDocument,\n GetRefundStatsDocument,\n GetUnprocessedFailedWebhooksDocument,\n ],\n });\n return data!.createRefundRecordFromWebhook;\n },\n [createFromWebhookMutation, client]\n );\n\n return {\n createRefundRecord,\n processRefund,\n skipRefund,\n retryRefund,\n createRefundRecordsFromFailedWebhooks,\n createRefundRecordFromWebhook,\n isCreating,\n isProcessing,\n isSkipping,\n isRetrying,\n isCreatingFromWebhooks,\n isCreatingFromWebhook,\n };\n}\n\n/**\n * Combined hook for refund page with all data and mutations\n */\nexport function useRefunds(options: UseRefundRecordsOptions = {}) {\n const recordsHook = useRefundRecords(options);\n const statsHook = useRefundStats();\n const mutationsHook = useRefundMutations();\n const webhooksHook = useUnprocessedFailedWebhooks();\n\n const isLoading = useMemo(\n () => recordsHook.isLoading || statsHook.isLoading,\n [recordsHook.isLoading, statsHook.isLoading]\n );\n\n const refetchAll = useCallback(async () => {\n await Promise.all([recordsHook.refetch(), statsHook.refetch(), webhooksHook.refetch()]);\n // eslint-disable-next-line react-hooks/exhaustive-deps -- Intentional: accessing stable refetch methods from hooks\n }, [recordsHook.refetch, statsHook.refetch, webhooksHook.refetch]);\n\n return {\n // Records\n records: recordsHook.records,\n pagination: recordsHook.pagination,\n goToNextPage: recordsHook.goToNextPage,\n goToPreviousPage: recordsHook.goToPreviousPage,\n goToPage: recordsHook.goToPage,\n setFilters: recordsHook.setFilters,\n\n // Stats\n stats: statsHook.stats,\n\n // Failed webhooks\n unprocessedWebhooks: webhooksHook.webhooks,\n webhooksLoading: webhooksHook.isLoading,\n\n // Loading states\n isLoading,\n recordsError: recordsHook.error,\n statsError: statsHook.error,\n\n // Mutations\n ...mutationsHook,\n\n // Refetch\n refetch: refetchAll,\n refetchRecords: recordsHook.refetch,\n refetchStats: statsHook.refetch,\n refetchWebhooks: webhooksHook.refetch,\n };\n}\n"],"mappings":"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"}
@@ -8,8 +8,9 @@ import "./hooks/index.js";
8
8
  import { TransactionsPage as n } from "./pages/TransactionsPage.js";
9
9
  import { PaymentMethodsPage as r } from "./pages/PaymentMethodsPage.js";
10
10
  import { RefundsPage as i } from "./pages/RefundsPage.js";
11
- import { UserRefundsPage as a } from "./pages/UserRefundsPage.js";
12
- import { BillingWebhookLogsPage as o } from "./pages/BillingWebhookLogsPage.js";
13
- import { OrderCallbackLogsPage as s } from "./pages/OrderCallbackLogsPage.js";
11
+ import { RefundIssueLogsPage as a } from "./pages/RefundIssueLogsPage.js";
12
+ import { UserRefundsPage as o } from "./pages/UserRefundsPage.js";
13
+ import { BillingWebhookLogsPage as s } from "./pages/BillingWebhookLogsPage.js";
14
+ import { OrderCallbackLogsPage as c } from "./pages/OrderCallbackLogsPage.js";
14
15
  import "./pages/index.js";
15
- export { o as BillingWebhookLogsPage, t as InvoiceDetailPage, e as InvoicesListPage, s as OrderCallbackLogsPage, r as PaymentMethodsPage, i as RefundsPage, n as TransactionsPage, a as UserRefundsPage };
16
+ export { s as BillingWebhookLogsPage, t as InvoiceDetailPage, e as InvoicesListPage, c as OrderCallbackLogsPage, r as PaymentMethodsPage, a as RefundIssueLogsPage, i as RefundsPage, n as TransactionsPage, o as UserRefundsPage };
@@ -0,0 +1,201 @@
1
+ import { AccessDenied as e } from "../../../shared/components/AccessDenied.js";
2
+ import { PageHeader as t } from "../../../shared/components/PageHeader.js";
3
+ import { statusTokens as n } from "../../../shared/utils/tokens.js";
4
+ import { ServerError as r } from "../../../shared/components/ServerError.js";
5
+ import "../../../shared/components/index.js";
6
+ import { formatCurrency as i, formatDateTime as a } from "../../../shared/utils/format.js";
7
+ import { isServerError as o } from "../../../shared/utils/error.js";
8
+ import "../../../shared/utils/index.js";
9
+ import { useBillingPermissions as s } from "../../../hooks/useBillingPermissions.js";
10
+ import { useRefundRecords as c } from "../hooks/useRefunds.js";
11
+ import "../hooks/index.js";
12
+ import { AlertTriangle as l, ArrowLeft as u, RefreshCw as d } from "lucide-react";
13
+ import { Link as f } from "react-router";
14
+ import { jsx as p, jsxs as m } from "react/jsx-runtime";
15
+ import { IllustratedEmptyState as h, ResponsiveTable as g } from "@burdenoff/fe-libs/ui";
16
+ //#region src/billing/modules/billing/pages/RefundIssueLogsPage.tsx
17
+ var _ = 25, v = { hasError: !0 }, y = () => {
18
+ let y = s(), { records: b, pagination: x, isLoading: S, error: C, refetch: w, goToNextPage: T, goToPreviousPage: E } = c({
19
+ pageSize: _,
20
+ filters: v
21
+ });
22
+ if (!y.canViewPayments) return /* @__PURE__ */ p(e, { message: "You don't have permission to view refund logs." });
23
+ if (C && o(C)) return /* @__PURE__ */ p("div", {
24
+ className: "p-6",
25
+ children: /* @__PURE__ */ p(r, {
26
+ title: "Server Unavailable",
27
+ message: "Unable to load refund issue logs. The server might be down or experiencing issues.",
28
+ onRetry: () => w(),
29
+ showRetry: !0
30
+ })
31
+ });
32
+ let D = [
33
+ {
34
+ key: "date",
35
+ header: "Date",
36
+ priority: "primary",
37
+ cellClassName: "whitespace-nowrap",
38
+ cell: (e) => /* @__PURE__ */ m("div", {
39
+ className: "flex flex-col",
40
+ children: [/* @__PURE__ */ p("span", {
41
+ className: "text-sm text-text-primary",
42
+ children: a(e.createdAt).split(" ")[0]
43
+ }), /* @__PURE__ */ p("span", {
44
+ className: "text-xs text-text-muted",
45
+ children: a(e.createdAt).split(" ")[1]
46
+ })]
47
+ })
48
+ },
49
+ {
50
+ key: "status",
51
+ header: "Status",
52
+ cellClassName: "whitespace-nowrap",
53
+ cell: (e) => /* @__PURE__ */ p("span", {
54
+ className: `inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${n.success.bg} ${n.success.text}`,
55
+ children: e.status
56
+ })
57
+ },
58
+ {
59
+ key: "provider",
60
+ header: "Provider",
61
+ priority: "hidden-on-mobile",
62
+ cellClassName: "whitespace-nowrap",
63
+ cell: (e) => /* @__PURE__ */ p("span", {
64
+ className: "text-sm text-text-primary capitalize",
65
+ children: e.provider
66
+ })
67
+ },
68
+ {
69
+ key: "paymentId",
70
+ header: "Payment ID",
71
+ cellClassName: "whitespace-nowrap max-w-[140px] truncate",
72
+ cell: (e) => /* @__PURE__ */ p("span", {
73
+ className: "text-sm font-mono text-text-muted",
74
+ children: e.gatewayPaymentId
75
+ })
76
+ },
77
+ {
78
+ key: "amount",
79
+ header: "Amount",
80
+ headerClassName: "text-right",
81
+ cellClassName: "text-right whitespace-nowrap",
82
+ cell: (e) => /* @__PURE__ */ p("span", {
83
+ className: "text-sm font-medium tabular-nums",
84
+ children: i(e.amount, e.currency)
85
+ })
86
+ },
87
+ {
88
+ key: "purchaseType",
89
+ header: "Type",
90
+ priority: "hidden-on-mobile",
91
+ cellClassName: "whitespace-nowrap",
92
+ cell: (e) => /* @__PURE__ */ p("span", {
93
+ className: "text-sm text-text-muted",
94
+ children: e.purchaseType || "-"
95
+ })
96
+ },
97
+ {
98
+ key: "issue",
99
+ header: "Issue",
100
+ cellClassName: "max-w-[360px]",
101
+ cell: (e) => /* @__PURE__ */ m("div", {
102
+ className: "flex items-start gap-1.5 py-1",
103
+ children: [/* @__PURE__ */ p(l, { className: `size-3.5 mt-0.5 shrink-0 ${n.warning.icon}` }), /* @__PURE__ */ p("span", {
104
+ className: "text-sm text-status-warning-text",
105
+ children: e.errorMessage
106
+ })]
107
+ })
108
+ }
109
+ ], O = Math.max(1, Math.ceil(x.totalCount / _));
110
+ return /* @__PURE__ */ m("div", {
111
+ className: "space-y-6 p-6",
112
+ children: [
113
+ /* @__PURE__ */ p(t, {
114
+ title: "Refund Issue Log",
115
+ description: "Refunds that succeeded with the payment gateway but left an accounting or reconciliation issue behind — e.g. a Zoho credit note that couldn't be recorded. The customer was refunded; these still need manual follow-up.",
116
+ actions: /* @__PURE__ */ m("div", {
117
+ className: "flex items-center gap-2",
118
+ children: [/* @__PURE__ */ m(f, {
119
+ to: "../refunds",
120
+ 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",
121
+ children: [/* @__PURE__ */ p(u, { className: "size-4" }), "Back to Refunds"]
122
+ }), /* @__PURE__ */ m("button", {
123
+ type: "button",
124
+ onClick: () => w(),
125
+ 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",
126
+ children: [/* @__PURE__ */ p(d, { className: "size-4" }), "Refresh"]
127
+ })]
128
+ })
129
+ }),
130
+ S && b.length === 0 ? /* @__PURE__ */ p("div", {
131
+ className: "space-y-2",
132
+ children: [
133
+ 1,
134
+ 2,
135
+ 3,
136
+ 4,
137
+ 5
138
+ ].map((e) => /* @__PURE__ */ p("div", { className: "h-16 bg-bg-sunken animate-pulse rounded" }, e))
139
+ }) : b.length === 0 ? /* @__PURE__ */ p(h, {
140
+ illustration: "empty-data",
141
+ title: "No open refund issues",
142
+ description: "Every completed refund reconciled cleanly — nothing here needs follow-up right now."
143
+ }) : /* @__PURE__ */ p("div", {
144
+ className: "border border-border-subtle rounded-lg overflow-hidden",
145
+ children: /* @__PURE__ */ p(g, {
146
+ columns: D,
147
+ data: b,
148
+ rowKey: (e) => e.id
149
+ })
150
+ }),
151
+ x.totalCount > 0 && /* @__PURE__ */ m("div", {
152
+ className: "flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border-subtle pt-4",
153
+ children: [/* @__PURE__ */ m("p", {
154
+ className: "text-sm text-text-muted text-center sm:text-left",
155
+ children: [
156
+ "Showing ",
157
+ x.page * _ + 1,
158
+ " to",
159
+ " ",
160
+ Math.min((x.page + 1) * _, x.totalCount),
161
+ " of",
162
+ " ",
163
+ x.totalCount,
164
+ " refunds with an open issue"
165
+ ]
166
+ }), /* @__PURE__ */ m("div", {
167
+ className: "flex items-center gap-2",
168
+ children: [
169
+ /* @__PURE__ */ p("button", {
170
+ type: "button",
171
+ onClick: E,
172
+ disabled: !x.hasPrevious,
173
+ 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",
174
+ children: "Previous"
175
+ }),
176
+ /* @__PURE__ */ m("span", {
177
+ className: "text-sm text-text-muted px-2",
178
+ children: [
179
+ "Page ",
180
+ x.page + 1,
181
+ " of ",
182
+ O
183
+ ]
184
+ }),
185
+ /* @__PURE__ */ p("button", {
186
+ type: "button",
187
+ onClick: T,
188
+ disabled: !x.hasMore,
189
+ 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",
190
+ children: "Next"
191
+ })
192
+ ]
193
+ })]
194
+ })
195
+ ]
196
+ });
197
+ };
198
+ //#endregion
199
+ export { y as RefundIssueLogsPage };
200
+
201
+ //# sourceMappingURL=RefundIssueLogsPage.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"RefundIssueLogsPage.js","names":[],"sources":["../../../../../src/billing/modules/billing/pages/RefundIssueLogsPage.tsx"],"sourcesContent":["/**\n * Billing Module - Refund Issue Logs Page\n * Admin visibility into refunds that completed at the payment gateway but\n * carry an unresolved accounting/reconciliation issue (RefundRecord.errorMessage)\n * — e.g. a Zoho credit-note refund that couldn't be recorded because no bank\n * account is configured. These refunds are otherwise invisible: the customer\n * got their money back, so they don't show up as Failed, and the warning is\n * normally only visible by opening one refund's Details modal at a time.\n * This page surfaces every one of them in a single scannable list.\n */\n\nimport { type FC } from 'react';\nimport { AlertTriangle, ArrowLeft, RefreshCw } from 'lucide-react';\nimport { Link } from 'react-router';\nimport { useBillingPermissions } from '../../../hooks/useBillingPermissions';\nimport { useRefundRecords, type RefundRecord, type RefundRecordFilters } from '../hooks';\nimport { AccessDenied, PageHeader, ServerError } from '../../../shared/components';\nimport { formatCurrency, 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;\n\n// Module-level constant, not an inline object literal: useRefundRecords has a\n// useEffect keyed on options.filters, so a new object every render would\n// trigger it unconditionally on every render — an infinite update loop (the\n// same failure mode RefundsPage's tabFilters memoization already guards\n// against). This filter never changes, so a stable reference is sufficient.\nconst REFUND_ISSUE_FILTERS: RefundRecordFilters = { hasError: true };\n\nexport const RefundIssueLogsPage: FC = () => {\n const permissions = useBillingPermissions();\n\n const { records, pagination, isLoading, error, refetch, goToNextPage, goToPreviousPage } =\n useRefundRecords({\n pageSize: PAGE_SIZE,\n filters: REFUND_ISSUE_FILTERS,\n });\n\n if (!permissions.canViewPayments) {\n return <AccessDenied message=\"You don't have permission to view refund 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 refund issue logs. The server might be down or experiencing issues.\"\n onRetry={() => refetch()}\n showRetry\n />\n </div>\n );\n }\n\n const columns: 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 <span\n className={`inline-flex items-center gap-1 px-2 py-0.5 text-xs font-medium rounded ${statusTokens.success.bg} ${statusTokens.success.text}`}\n >\n {record.status}\n </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-[140px] 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: 'purchaseType',\n header: 'Type',\n priority: 'hidden-on-mobile',\n cellClassName: 'whitespace-nowrap',\n cell: (record) => (\n <span className=\"text-sm text-text-muted\">{record.purchaseType || '-'}</span>\n ),\n },\n {\n key: 'issue',\n header: 'Issue',\n cellClassName: 'max-w-[360px]',\n cell: (record) => (\n <div className=\"flex items-start gap-1.5 py-1\">\n <AlertTriangle className={`size-3.5 mt-0.5 shrink-0 ${statusTokens.warning.icon}`} />\n <span className=\"text-sm text-status-warning-text\">{record.errorMessage}</span>\n </div>\n ),\n },\n ];\n\n const totalPages = Math.max(1, Math.ceil(pagination.totalCount / PAGE_SIZE));\n\n return (\n <div className=\"space-y-6 p-6\">\n <PageHeader\n title=\"Refund Issue Log\"\n description=\"Refunds that succeeded with the payment gateway but left an accounting or reconciliation issue behind — e.g. a Zoho credit note that couldn't be recorded. The customer was refunded; these still need manual follow-up.\"\n actions={\n <div className=\"flex items-center gap-2\">\n <Link\n to=\"../refunds\"\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 <ArrowLeft className=\"size-4\" />\n Back to Refunds\n </Link>\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 </div>\n }\n />\n\n {isLoading && records.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 ) : records.length === 0 ? (\n <IllustratedEmptyState\n illustration=\"empty-data\"\n title=\"No open refund issues\"\n description=\"Every completed refund reconciled cleanly — nothing here needs follow-up right now.\"\n />\n ) : (\n <div className=\"border border-border-subtle rounded-lg overflow-hidden\">\n <ResponsiveTable<RefundRecord>\n columns={columns}\n data={records}\n rowKey={(record) => record.id}\n />\n </div>\n )}\n\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 * PAGE_SIZE + 1} to{' '}\n {Math.min((pagination.page + 1) * PAGE_SIZE, pagination.totalCount)} of{' '}\n {pagination.totalCount} refunds with an open issue\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 Previous\n </button>\n <span className=\"text-sm text-text-muted px-2\">\n Page {pagination.page + 1} of {totalPages}\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 Next\n </button>\n </div>\n </div>\n )}\n </div>\n );\n};\n"],"mappings":"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"}