From 864625c719c21b568d8109e6bea83211ee0541cf Mon Sep 17 00:00:00 2001 From: Lewis Carhart Date: Thu, 30 Apr 2026 13:33:46 +0100 Subject: [PATCH] refactor(api): update error messages and improve code formatting in background check services --- .../background-check-billing.service.ts | 51 +- .../background-check-payment.service.ts | 7 +- .../components/BackgroundCheckStatusView.tsx | 34 +- .../CustomBackgroundCheckUpload.tsx | 6 +- .../EmployeeBackgroundCheck.test.tsx | 6 +- .../components/EmployeeBackgroundCheck.tsx | 548 +++++++++--------- 6 files changed, 348 insertions(+), 304 deletions(-) diff --git a/apps/api/src/background-checks/background-check-billing.service.ts b/apps/api/src/background-checks/background-check-billing.service.ts index 676fe5f581..356e8a5367 100644 --- a/apps/api/src/background-checks/background-check-billing.service.ts +++ b/apps/api/src/background-checks/background-check-billing.service.ts @@ -60,7 +60,9 @@ export class BackgroundCheckBillingService { }); if (!session.url) { - throw new BadRequestException('Failed to create Stripe Checkout session.'); + throw new BadRequestException( + 'Failed to create Stripe Checkout session.', + ); } return { url: session.url }; @@ -90,12 +92,16 @@ export class BackgroundCheckBillingService { const setupIntent = session.setup_intent; if (!setupIntent || typeof setupIntent === 'string') { - throw new BadRequestException('Checkout session is missing a setup intent.'); + throw new BadRequestException( + 'Checkout session is missing a setup intent.', + ); } const paymentMethodId = this.extractStripeId(setupIntent.payment_method); if (!paymentMethodId) { - throw new BadRequestException('Setup intent is missing a payment method.'); + throw new BadRequestException( + 'Setup intent is missing a payment method.', + ); } await stripe.customers.update(stripeCustomerId, { @@ -138,7 +144,9 @@ export class BackgroundCheckBillingService { }); if (!billing) { - throw new NotFoundException('No billing record found for this organization.'); + throw new NotFoundException( + 'No billing record found for this organization.', + ); } const portalSession = await stripe.billingPortal.sessions.create({ @@ -184,16 +192,24 @@ export class BackgroundCheckBillingService { return customer.id; } - async getBackgroundCheckPrice(): Promise<{ id: string; unitAmount: number; currency: string }> { + async getBackgroundCheckPrice(): Promise<{ + id: string; + unitAmount: number; + currency: string; + }> { const priceId = process.env.STRIPE_BACKGROUND_CHECK_PRICE_ID; if (!priceId) { - throw new BadRequestException('STRIPE_BACKGROUND_CHECK_PRICE_ID is not configured.'); + throw new BadRequestException( + 'Background check billing is not configured.', + ); } const stripe = this.stripeService.getClient(); const price = await stripe.prices.retrieve(priceId); if (!price.unit_amount) { - throw new BadRequestException('Background check price has no unit amount.'); + throw new BadRequestException( + 'Background check price has no unit amount.', + ); } return { @@ -205,7 +221,9 @@ export class BackgroundCheckBillingService { private validateRedirectUrl(url: string): void { const appUrl = - process.env.NEXT_PUBLIC_APP_URL || process.env.APP_URL || process.env.BETTER_AUTH_URL; + process.env.NEXT_PUBLIC_APP_URL || + process.env.APP_URL || + process.env.BETTER_AUTH_URL; if (!appUrl) { throw new BadRequestException('App URL is not configured on the server.'); } @@ -218,11 +236,15 @@ export class BackgroundCheckBillingService { } if (parsed.origin !== new URL(appUrl).origin) { - throw new BadRequestException('Redirect URL must belong to the application origin.'); + throw new BadRequestException( + 'Redirect URL must belong to the application origin.', + ); } } - private extractStripeId(value: string | { id?: string } | null): string | null { + private extractStripeId( + value: string | { id?: string } | null, + ): string | null { if (!value) return null; if (typeof value === 'string') return value; return value.id ?? null; @@ -246,8 +268,13 @@ export class BackgroundCheckBillingService { const stripe = this.stripeService.getClient(); const customer = await stripe.customers.retrieve(stripeCustomerId); - if (customer.deleted || customer.metadata?.organizationId !== organizationId) { - throw new BadRequestException('Checkout session does not belong to this organization.'); + if ( + customer.deleted || + customer.metadata?.organizationId !== organizationId + ) { + throw new BadRequestException( + 'Checkout session does not belong to this organization.', + ); } } } diff --git a/apps/api/src/background-checks/background-check-payment.service.ts b/apps/api/src/background-checks/background-check-payment.service.ts index 79f0f7f270..847f5f6c33 100644 --- a/apps/api/src/background-checks/background-check-payment.service.ts +++ b/apps/api/src/background-checks/background-check-payment.service.ts @@ -15,7 +15,12 @@ export class BackgroundCheckPaymentService { async charge(params: { organizationId: string; memberId: string; - }): Promise<{ paymentIntentId: string; status: string; amount: number; currency: string }> { + }): Promise<{ + paymentIntentId: string; + status: string; + amount: number; + currency: string; + }> { const billing = await db.organizationBilling.findUnique({ where: { organizationId: params.organizationId }, select: { diff --git a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/BackgroundCheckStatusView.tsx b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/BackgroundCheckStatusView.tsx index 03371daa80..83a284940a 100644 --- a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/BackgroundCheckStatusView.tsx +++ b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/BackgroundCheckStatusView.tsx @@ -6,9 +6,9 @@ import { toast } from 'sonner'; import useSWR from 'swr'; import { BackgroundCheckReport } from './BackgroundCheckReport'; import { - type CustomBackgroundCheckAttachment, type BackgroundCheckRecord, type BackgroundCheckStatus, + type CustomBackgroundCheckAttachment, isCompletedBackgroundCheck, } from './backgroundCheckTypes'; @@ -48,17 +48,13 @@ export function BackgroundCheckStatusView({ CustomBackgroundCheckAttachment[], Error, readonly [string, string] | null - >( - customAttachmentsKey, - async ([endpoint, orgId]) => { - const response = await apiClient.get( - endpoint, - orgId, - ); - if (response.error) throw new Error(response.error); - return response.data ?? []; - }, - ); + >(customAttachmentsKey, async ([endpoint, orgId]) => { + const response = await apiClient.get(endpoint, orgId); + if (response.error) { + throw new Error('Failed to load custom background check attachments'); + } + return response.data ?? []; + }); const handleCopyCandidateLink = async () => { if (!backgroundCheck.candidateUrl) return; @@ -150,7 +146,7 @@ function CustomReportAttachments({ ); if (response.error || !response.data?.downloadUrl) { - toast.error(response.error ?? 'Failed to open background check'); + toast.error('Failed to open background check'); return; } @@ -175,11 +171,7 @@ function CustomReportAttachments({ Uploaded {new Date(attachment.createdAt).toLocaleString()} - @@ -190,11 +182,7 @@ function CustomReportAttachments({ ); } -function ComponentStatuses({ - backgroundCheck, -}: { - backgroundCheck: BackgroundCheckRecord; -}) { +function ComponentStatuses({ backgroundCheck }: { backgroundCheck: BackgroundCheckRecord }) { const statuses = COMPONENT_LABELS.flatMap(([key, label]) => { const value = backgroundCheck[key]; return value ? [{ label, value }] : []; diff --git a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/CustomBackgroundCheckUpload.tsx b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/CustomBackgroundCheckUpload.tsx index a120f5fb32..869f6476db 100644 --- a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/CustomBackgroundCheckUpload.tsx +++ b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/CustomBackgroundCheckUpload.tsx @@ -57,7 +57,7 @@ export function CustomBackgroundCheckUpload({ ); if (response.error || !response.data) { - toast.error(response.error ?? 'Failed to upload background check'); + toast.error('Failed to upload background check'); return; } @@ -65,8 +65,8 @@ export function CustomBackgroundCheckUpload({ setSelectedFile(null); if (inputRef.current) inputRef.current.value = ''; await onUploaded(response.data); - } catch (error) { - toast.error(error instanceof Error ? error.message : 'Failed to upload background check'); + } catch { + toast.error('Failed to upload background check'); } finally { setIsUploading(false); } diff --git a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeBackgroundCheck.test.tsx b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeBackgroundCheck.test.tsx index cac86c5f26..2a62ca62b4 100644 --- a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeBackgroundCheck.test.tsx +++ b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeBackgroundCheck.test.tsx @@ -280,7 +280,7 @@ describe('EmployeeBackgroundCheck', () => { const user = userEvent.setup(); vi.mocked(apiClient.post) .mockResolvedValueOnce({ - error: 'Background check payment failed. Update billing and try again.', + error: 'Invalid API Key provided: PLACEHOLDER', status: 402, }) .mockResolvedValueOnce({ data: {}, status: 200 }); @@ -292,6 +292,10 @@ describe('EmployeeBackgroundCheck', () => { expect( await screen.findByRole('heading', { name: /update payment method/i }), ).toBeInTheDocument(); + expect(screen.queryByText(/PLACEHOLDER/)).not.toBeInTheDocument(); + expect( + screen.getByText('Payment failed. Update payment method and try again.'), + ).toBeInTheDocument(); await user.click(screen.getByRole('button', { name: /update payment method/i })); diff --git a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeBackgroundCheck.tsx b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeBackgroundCheck.tsx index 32d52eb2a7..ab2a3bc8b3 100644 --- a/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeBackgroundCheck.tsx +++ b/apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeBackgroundCheck.tsx @@ -1,297 +1,317 @@ -'use client'; +"use client"; -import { usePermissions } from '@/hooks/use-permissions'; -import { apiClient } from '@/lib/api-client'; -import type { Member, User } from '@db'; -import { zodResolver } from '@hookform/resolvers/zod'; -import { usePathname, useRouter, useSearchParams } from 'next/navigation'; -import { useCallback, useEffect, useRef, useState } from 'react'; -import { useForm } from 'react-hook-form'; -import { toast } from 'sonner'; -import useSWR from 'swr'; -import { BackgroundCheckDetailsForm } from './BackgroundCheckDetailsForm'; -import { BackgroundCheckStatusView } from './BackgroundCheckStatusView'; -import { OverviewStep } from './BackgroundCheckWizardParts'; -import { CustomBackgroundCheckUpload } from './CustomBackgroundCheckUpload'; -import { PaymentMethodUpdateDialog } from './PaymentMethodUpdateDialog'; +import type { Member, User } from "@db"; +import { zodResolver } from "@hookform/resolvers/zod"; +import { usePathname, useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useEffect, useRef, useState } from "react"; +import { useForm } from "react-hook-form"; +import { toast } from "sonner"; +import useSWR from "swr"; +import { usePermissions } from "@/hooks/use-permissions"; +import { apiClient } from "@/lib/api-client"; +import { BackgroundCheckDetailsForm } from "./BackgroundCheckDetailsForm"; +import { BackgroundCheckStatusView } from "./BackgroundCheckStatusView"; +import { OverviewStep } from "./BackgroundCheckWizardParts"; import { - backgroundCheckSchema, - clearPendingBackgroundCheckRequest, - readPendingBackgroundCheckRequest, - writePendingBackgroundCheckRequest, - type BackgroundCheckFormValues, -} from './backgroundCheckForm'; -import type { BackgroundCheckBillingStatus, BackgroundCheckRecord } from './backgroundCheckTypes'; + type BackgroundCheckFormValues, + backgroundCheckSchema, + clearPendingBackgroundCheckRequest, + readPendingBackgroundCheckRequest, + writePendingBackgroundCheckRequest, +} from "./backgroundCheckForm"; +import type { + BackgroundCheckBillingStatus, + BackgroundCheckRecord, +} from "./backgroundCheckTypes"; +import { CustomBackgroundCheckUpload } from "./CustomBackgroundCheckUpload"; +import { PaymentMethodUpdateDialog } from "./PaymentMethodUpdateDialog"; interface EmployeeBackgroundCheckProps { - employee: Member & { user: User }; - organizationId: string; - initialBackgroundCheck: BackgroundCheckRecord | null; - initialBillingStatus: BackgroundCheckBillingStatus; + employee: Member & { user: User }; + organizationId: string; + initialBackgroundCheck: BackgroundCheckRecord | null; + initialBillingStatus: BackgroundCheckBillingStatus; } export function EmployeeBackgroundCheck({ - employee, - organizationId, - initialBackgroundCheck, - initialBillingStatus, + employee, + organizationId, + initialBackgroundCheck, + initialBillingStatus, }: EmployeeBackgroundCheckProps) { - const router = useRouter(); - const pathname = usePathname(); - const searchParams = useSearchParams(); - const handledSessionId = useRef(null); - const [wizardStep, setWizardStep] = useState<'overview' | 'details'>(() => { - if (searchParams.get('background_check_step') === 'details') return 'details'; - return initialBillingStatus.hasPaymentMethod ? 'details' : 'overview'; - }); - const [isRequesting, setIsRequesting] = useState(false); - const [isOpeningBilling, setIsOpeningBilling] = useState(false); - const [billingSetupComplete, setBillingSetupComplete] = useState(false); - const [paymentIssue, setPaymentIssue] = useState(null); - const [requestConfirmation, setRequestConfirmation] = useState(null); - const { hasPermission } = usePermissions(); + const router = useRouter(); + const pathname = usePathname(); + const searchParams = useSearchParams(); + const handledSessionId = useRef(null); + const [wizardStep, setWizardStep] = useState<"overview" | "details">(() => { + if (searchParams.get("background_check_step") === "details") + return "details"; + return initialBillingStatus.hasPaymentMethod ? "details" : "overview"; + }); + const [isRequesting, setIsRequesting] = useState(false); + const [isOpeningBilling, setIsOpeningBilling] = useState(false); + const [billingSetupComplete, setBillingSetupComplete] = useState(false); + const [paymentIssue, setPaymentIssue] = useState(null); + const [requestConfirmation, setRequestConfirmation] = useState( + null, + ); + const { hasPermission } = usePermissions(); - const { data: backgroundCheck, mutate: mutateBackgroundCheck } = - useSWR( - [`/v1/people/${employee.id}/background-check`, organizationId], - async ([endpoint]) => { - const response = await apiClient.get( - endpoint, - organizationId, - ); - if (response.error) throw new Error(response.error); - return response.data ?? null; - }, - { fallbackData: initialBackgroundCheck }, - ); + const { data: backgroundCheck, mutate: mutateBackgroundCheck } = + useSWR( + [`/v1/people/${employee.id}/background-check`, organizationId], + async ([endpoint]) => { + const response = await apiClient.get( + endpoint, + organizationId, + ); + if (response.error) throw new Error("Failed to load background check"); + return response.data ?? null; + }, + { fallbackData: initialBackgroundCheck }, + ); - const { data: billingStatus, mutate: mutateBillingStatus } = useSWR( - ['/v1/background-check-billing/status', organizationId], - async ([endpoint]) => { - const response = await apiClient.get(endpoint, organizationId); - if (response.error || !response.data) { - throw new Error(response.error ?? 'Failed to load billing status'); - } - return response.data; - }, - { fallbackData: initialBillingStatus }, - ); + const { data: billingStatus, mutate: mutateBillingStatus } = + useSWR( + ["/v1/background-check-billing/status", organizationId], + async ([endpoint]) => { + const response = await apiClient.get( + endpoint, + organizationId, + ); + if (response.error || !response.data) { + throw new Error("Failed to load billing status"); + } + return response.data; + }, + { fallbackData: initialBillingStatus }, + ); - const form = useForm({ - resolver: zodResolver(backgroundCheckSchema), - defaultValues: { - employeeName: backgroundCheck?.employeeName ?? employee.user.name ?? '', - employeeEmail: backgroundCheck?.employeeEmail ?? '', - requesterNotes: backgroundCheck?.requesterNotes ?? '', - }, - }); + const form = useForm({ + resolver: zodResolver(backgroundCheckSchema), + defaultValues: { + employeeName: backgroundCheck?.employeeName ?? employee.user.name ?? "", + employeeEmail: backgroundCheck?.employeeEmail ?? "", + requesterNotes: backgroundCheck?.requesterNotes ?? "", + }, + }); - useEffect(() => { - form.reset({ - employeeName: backgroundCheck?.employeeName ?? employee.user.name ?? '', - employeeEmail: backgroundCheck?.employeeEmail ?? '', - requesterNotes: backgroundCheck?.requesterNotes ?? '', - }); - }, [backgroundCheck, employee.user.name, form]); + useEffect(() => { + form.reset({ + employeeName: backgroundCheck?.employeeName ?? employee.user.name ?? "", + employeeEmail: backgroundCheck?.employeeEmail ?? "", + requesterNotes: backgroundCheck?.requesterNotes ?? "", + }); + }, [backgroundCheck, employee.user.name, form]); - const canRequest = hasPermission('member', 'update'); - const canManageBilling = hasPermission('organization', 'update'); - const hasPaymentMethod = billingStatus?.hasPaymentMethod === true; - const visibleWizardStep = hasPaymentMethod ? 'details' : wizardStep; + const canRequest = hasPermission("member", "update"); + const canManageBilling = hasPermission("organization", "update"); + const hasPaymentMethod = billingStatus?.hasPaymentMethod === true; + const visibleWizardStep = hasPaymentMethod ? "details" : wizardStep; - const writePendingRequest = useCallback( - (values: BackgroundCheckFormValues) => { - writePendingBackgroundCheckRequest({ - organizationId, - memberId: employee.id, - values, - }); - }, - [employee.id, organizationId], - ); + const writePendingRequest = useCallback( + (values: BackgroundCheckFormValues) => { + writePendingBackgroundCheckRequest({ + organizationId, + memberId: employee.id, + values, + }); + }, + [employee.id, organizationId], + ); - const clearPendingRequest = useCallback(() => { - clearPendingBackgroundCheckRequest({ - organizationId, - memberId: employee.id, - }); - }, [employee.id, organizationId]); + const clearPendingRequest = useCallback(() => { + clearPendingBackgroundCheckRequest({ + organizationId, + memberId: employee.id, + }); + }, [employee.id, organizationId]); - const requestBackgroundCheck = useCallback( - async (values: BackgroundCheckFormValues): Promise => { - setPaymentIssue(null); - setIsRequesting(true); - const response = await apiClient.post( - `/v1/people/${employee.id}/background-check`, - { - employeeName: values.employeeName, - employeeEmail: values.employeeEmail, - requesterNotes: values.requesterNotes?.trim() || undefined, - }, - organizationId, - ); - setIsRequesting(false); + const requestBackgroundCheck = useCallback( + async (values: BackgroundCheckFormValues): Promise => { + setPaymentIssue(null); + setIsRequesting(true); + const response = await apiClient.post( + `/v1/people/${employee.id}/background-check`, + { + employeeName: values.employeeName, + employeeEmail: values.employeeEmail, + requesterNotes: values.requesterNotes?.trim() || undefined, + }, + organizationId, + ); + setIsRequesting(false); - if (response.error || !response.data) { - if (response.status === 402) { - setPaymentIssue( - response.error ?? 'Payment failed. Update billing details and try again.', - ); - return false; - } - toast.error(response.error ?? 'Failed to request background check'); - return false; - } + if (response.error || !response.data) { + if (response.status === 402) { + setPaymentIssue( + "Payment failed. Update payment method and try again.", + ); + return false; + } + toast.error("Failed to request background check"); + return false; + } - setRequestConfirmation( - 'The saved payment method was charged and the candidate invite has been sent.', - ); - toast.success('Background check invite sent'); - await mutateBackgroundCheck(response.data, { revalidate: false }); - clearPendingRequest(); - return true; - }, - [clearPendingRequest, employee.id, mutateBackgroundCheck, organizationId], - ); + setRequestConfirmation( + "The saved payment method was charged and the candidate invite has been sent.", + ); + toast.success("Background check invite sent"); + await mutateBackgroundCheck(response.data, { revalidate: false }); + clearPendingRequest(); + return true; + }, + [clearPendingRequest, employee.id, mutateBackgroundCheck, organizationId], + ); - useEffect(() => { - const sessionId = searchParams.get('session_id'); - const setupSucceeded = searchParams.get('background_check_billing') === 'success'; - if (!sessionId || !setupSucceeded || handledSessionId.current === sessionId) return; + useEffect(() => { + const sessionId = searchParams.get("session_id"); + const setupSucceeded = + searchParams.get("background_check_billing") === "success"; + if (!sessionId || !setupSucceeded || handledSessionId.current === sessionId) + return; - handledSessionId.current = sessionId; - setWizardStep('details'); - void (async () => { - const setupResponse = await apiClient.post<{ success: true }>( - '/v1/background-check-billing/setup-success', - { sessionId }, - organizationId, - ); - if (setupResponse.error) { - toast.error(setupResponse.error); - router.replace(pathname, { scroll: false }); - return; - } + handledSessionId.current = sessionId; + setWizardStep("details"); + void (async () => { + const setupResponse = await apiClient.post<{ success: true }>( + "/v1/background-check-billing/setup-success", + { sessionId }, + organizationId, + ); + if (setupResponse.error) { + toast.error("Failed to save payment method"); + router.replace(pathname, { scroll: false }); + return; + } - setBillingSetupComplete(true); - setPaymentIssue(null); - toast.success('Payment method saved'); - await mutateBillingStatus( - { hasPaymentMethod: true, setupAt: new Date().toISOString() }, - { revalidate: true }, - ); + setBillingSetupComplete(true); + setPaymentIssue(null); + toast.success("Payment method saved"); + await mutateBillingStatus( + { hasPaymentMethod: true, setupAt: new Date().toISOString() }, + { revalidate: true }, + ); - const pendingRequest = readPendingBackgroundCheckRequest({ - organizationId, - memberId: employee.id, - }); - if (!pendingRequest) { - router.replace(pathname, { scroll: false }); - return; - } + const pendingRequest = readPendingBackgroundCheckRequest({ + organizationId, + memberId: employee.id, + }); + if (!pendingRequest) { + router.replace(pathname, { scroll: false }); + return; + } - form.reset({ - employeeName: pendingRequest.employeeName, - employeeEmail: pendingRequest.employeeEmail, - requesterNotes: pendingRequest.requesterNotes ?? '', - }); - setBillingSetupComplete(true); + form.reset({ + employeeName: pendingRequest.employeeName, + employeeEmail: pendingRequest.employeeEmail, + requesterNotes: pendingRequest.requesterNotes ?? "", + }); + setBillingSetupComplete(true); - router.replace(pathname, { scroll: false }); - })(); - }, [ - clearPendingRequest, - form, - employee.id, - mutateBillingStatus, - organizationId, - pathname, - router, - searchParams, - ]); + router.replace(pathname, { scroll: false }); + })(); + }, [ + form, + employee.id, + mutateBillingStatus, + organizationId, + pathname, + router, + searchParams, + ]); - const handleOpenBilling = async (values?: BackgroundCheckFormValues) => { - setIsOpeningBilling(true); - if (values) writePendingRequest(values); + const handleOpenBilling = async (values?: BackgroundCheckFormValues) => { + setIsOpeningBilling(true); + if (values) writePendingRequest(values); - const backgroundCheckPath = `/${organizationId}/people/${employee.id}`; - const returnUrl = `${window.location.origin}${backgroundCheckPath}`; - const endpoint = hasPaymentMethod - ? '/v1/background-check-billing/portal' - : '/v1/background-check-billing/setup-session'; - const body = hasPaymentMethod - ? { returnUrl } - : { - successUrl: `${returnUrl}?background_check_billing=success&background_check_step=details&session_id={CHECKOUT_SESSION_ID}`, - cancelUrl: `${returnUrl}?background_check_step=details`, - }; - const response = await apiClient.post<{ url: string }>(endpoint, body, organizationId); + const backgroundCheckPath = `/${organizationId}/people/${employee.id}`; + const returnUrl = `${window.location.origin}${backgroundCheckPath}`; + const endpoint = hasPaymentMethod + ? "/v1/background-check-billing/portal" + : "/v1/background-check-billing/setup-session"; + const body = hasPaymentMethod + ? { returnUrl } + : { + successUrl: `${returnUrl}?background_check_billing=success&background_check_step=details&session_id={CHECKOUT_SESSION_ID}`, + cancelUrl: `${returnUrl}?background_check_step=details`, + }; + const response = await apiClient.post<{ url: string }>( + endpoint, + body, + organizationId, + ); - if (response.data?.url) { - window.location.href = response.data.url; - return; - } + if (response.data?.url) { + window.location.href = response.data.url; + return; + } - toast.error(response.error ?? 'Failed to open billing'); - setIsOpeningBilling(false); - }; + toast.error("Failed to open billing"); + setIsOpeningBilling(false); + }; - const handleComplete = async (values: BackgroundCheckFormValues) => { - if (!hasPaymentMethod) { - await handleOpenBilling(values); - return; - } + const handleComplete = async (values: BackgroundCheckFormValues) => { + if (!hasPaymentMethod) { + await handleOpenBilling(values); + return; + } - await requestBackgroundCheck(values); - }; + await requestBackgroundCheck(values); + }; - if (backgroundCheck) { - return ( - - ); - } + if (backgroundCheck) { + return ( + + ); + } - return ( - <> - {visibleWizardStep === 'overview' && ( - setWizardStep('details')} /> - )} - {visibleWizardStep === 'details' && ( - setWizardStep('overview')} - onSubmit={handleComplete} - /> - )} - !open && setPaymentIssue(null)} - onUpdatePaymentMethod={() => void handleOpenBilling(form.getValues())} - /> - { - await mutateBackgroundCheck(uploadedBackgroundCheck, { revalidate: false }); - }} - /> - - ); + return ( + <> + {visibleWizardStep === "overview" && ( + setWizardStep("details")} + /> + )} + {visibleWizardStep === "details" && ( + setWizardStep("overview")} + onSubmit={handleComplete} + /> + )} + !open && setPaymentIssue(null)} + onUpdatePaymentMethod={() => void handleOpenBilling(form.getValues())} + /> + { + await mutateBackgroundCheck(uploadedBackgroundCheck, { + revalidate: false, + }); + }} + /> + + ); }