Skip to content

Commit de4d002

Browse files
authored
Merge pull request #2722 from trycompai/main
[comp] Production Deploy
2 parents 42e66a8 + 7332410 commit de4d002

6 files changed

Lines changed: 97 additions & 64 deletions

File tree

‎apps/api/src/background-checks/background-check-billing.service.ts‎

Lines changed: 46 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ export class BackgroundCheckBillingService {
6060
});
6161

6262
if (!session.url) {
63-
throw new BadRequestException('Failed to create Stripe Checkout session.');
63+
throw new BadRequestException(
64+
'Failed to create Stripe Checkout session.',
65+
);
6466
}
6567

6668
return { url: session.url };
@@ -82,8 +84,13 @@ export class BackgroundCheckBillingService {
8284
throw new BadRequestException('Checkout session is not complete.');
8385
}
8486

85-
if (session.metadata?.organizationId && session.metadata.organizationId !== organizationId) {
86-
throw new BadRequestException('Checkout session does not belong to this organization.');
87+
if (
88+
session.metadata?.organizationId &&
89+
session.metadata.organizationId !== organizationId
90+
) {
91+
throw new BadRequestException(
92+
'Checkout session does not belong to this organization.',
93+
);
8794
}
8895

8996
const stripeCustomerId = this.extractStripeId(session.customer);
@@ -98,12 +105,16 @@ export class BackgroundCheckBillingService {
98105

99106
const setupIntent = session.setup_intent;
100107
if (!setupIntent || typeof setupIntent === 'string') {
101-
throw new BadRequestException('Checkout session is missing a setup intent.');
108+
throw new BadRequestException(
109+
'Checkout session is missing a setup intent.',
110+
);
102111
}
103112

104113
const paymentMethodId = this.extractStripeId(setupIntent.payment_method);
105114
if (!paymentMethodId) {
106-
throw new BadRequestException('Setup intent is missing a payment method.');
115+
throw new BadRequestException(
116+
'Setup intent is missing a payment method.',
117+
);
107118
}
108119

109120
await stripe.customers.update(stripeCustomerId, {
@@ -146,7 +157,9 @@ export class BackgroundCheckBillingService {
146157
});
147158

148159
if (!billing) {
149-
throw new NotFoundException('No billing record found for this organization.');
160+
throw new NotFoundException(
161+
'No billing record found for this organization.',
162+
);
150163
}
151164

152165
const portalSession = await stripe.billingPortal.sessions.create({
@@ -192,16 +205,24 @@ export class BackgroundCheckBillingService {
192205
return customer.id;
193206
}
194207

195-
async getBackgroundCheckPrice(): Promise<{ id: string; unitAmount: number; currency: string }> {
208+
async getBackgroundCheckPrice(): Promise<{
209+
id: string;
210+
unitAmount: number;
211+
currency: string;
212+
}> {
196213
const priceId = process.env.STRIPE_BACKGROUND_CHECK_PRICE_ID;
197214
if (!priceId) {
198-
throw new BadRequestException('Background check pricing is not configured. Contact support.');
215+
throw new BadRequestException(
216+
'Background check pricing is not configured. Contact support.',
217+
);
199218
}
200219

201220
const stripe = this.stripeService.getClient();
202221
const price = await stripe.prices.retrieve(priceId);
203222
if (price.unit_amount === null || price.unit_amount === undefined) {
204-
throw new BadRequestException('Background check pricing is not configured. Contact support.');
223+
throw new BadRequestException(
224+
'Background check pricing is not configured. Contact support.',
225+
);
205226
}
206227

207228
return {
@@ -213,7 +234,9 @@ export class BackgroundCheckBillingService {
213234

214235
private validateRedirectUrl(url: string): void {
215236
const appUrl =
216-
process.env.NEXT_PUBLIC_APP_URL || process.env.APP_URL || process.env.BETTER_AUTH_URL;
237+
process.env.NEXT_PUBLIC_APP_URL ||
238+
process.env.APP_URL ||
239+
process.env.BETTER_AUTH_URL;
217240
if (!appUrl) {
218241
throw new BadRequestException('App URL is not configured on the server.');
219242
}
@@ -226,11 +249,15 @@ export class BackgroundCheckBillingService {
226249
}
227250

228251
if (parsed.origin !== new URL(appUrl).origin) {
229-
throw new BadRequestException('Redirect URL must belong to the application origin.');
252+
throw new BadRequestException(
253+
'Redirect URL must belong to the application origin.',
254+
);
230255
}
231256
}
232257

233-
private extractStripeId(value: string | { id?: string } | null): string | null {
258+
private extractStripeId(
259+
value: string | { id?: string } | null,
260+
): string | null {
234261
if (!value) return null;
235262
if (typeof value === 'string') return value;
236263
return value.id ?? null;
@@ -254,8 +281,13 @@ export class BackgroundCheckBillingService {
254281

255282
const stripe = this.stripeService.getClient();
256283
const customer = await stripe.customers.retrieve(stripeCustomerId);
257-
if (customer.deleted || customer.metadata?.organizationId !== organizationId) {
258-
throw new BadRequestException('Checkout session does not belong to this organization.');
284+
if (
285+
customer.deleted ||
286+
customer.metadata?.organizationId !== organizationId
287+
) {
288+
throw new BadRequestException(
289+
'Checkout session does not belong to this organization.',
290+
);
259291
}
260292
}
261293
}

‎apps/api/src/background-checks/background-check-payment.service.ts‎

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,12 @@ export class BackgroundCheckPaymentService {
1515
async charge(params: {
1616
organizationId: string;
1717
memberId: string;
18-
}): Promise<{ paymentIntentId: string; status: string; amount: number; currency: string }> {
18+
}): Promise<{
19+
paymentIntentId: string;
20+
status: string;
21+
amount: number;
22+
currency: string;
23+
}> {
1924
const billing = await db.organizationBilling.findUnique({
2025
where: { organizationId: params.organizationId },
2126
select: {

‎apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/BackgroundCheckStatusView.tsx‎

Lines changed: 11 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,9 @@ import { toast } from 'sonner';
66
import useSWR from 'swr';
77
import { BackgroundCheckReport } from './BackgroundCheckReport';
88
import {
9-
type CustomBackgroundCheckAttachment,
109
type BackgroundCheckRecord,
1110
type BackgroundCheckStatus,
11+
type CustomBackgroundCheckAttachment,
1212
isCompletedBackgroundCheck,
1313
} from './backgroundCheckTypes';
1414

@@ -48,17 +48,13 @@ export function BackgroundCheckStatusView({
4848
CustomBackgroundCheckAttachment[],
4949
Error,
5050
readonly [string, string] | null
51-
>(
52-
customAttachmentsKey,
53-
async ([endpoint, orgId]) => {
54-
const response = await apiClient.get<CustomBackgroundCheckAttachment[]>(
55-
endpoint,
56-
orgId,
57-
);
58-
if (response.error) throw new Error(response.error);
59-
return response.data ?? [];
60-
},
61-
);
51+
>(customAttachmentsKey, async ([endpoint, orgId]) => {
52+
const response = await apiClient.get<CustomBackgroundCheckAttachment[]>(endpoint, orgId);
53+
if (response.error) {
54+
throw new Error('Failed to load custom background check attachments');
55+
}
56+
return response.data ?? [];
57+
});
6258

6359
const handleCopyCandidateLink = async () => {
6460
if (!backgroundCheck.candidateUrl) return;
@@ -152,7 +148,7 @@ function CustomReportAttachments({
152148
);
153149

154150
if (response.error || !response.data?.downloadUrl) {
155-
toast.error(response.error ?? 'Failed to open background check');
151+
toast.error('Failed to open background check');
156152
return;
157153
}
158154

@@ -177,11 +173,7 @@ function CustomReportAttachments({
177173
Uploaded {new Date(attachment.createdAt).toLocaleString()}
178174
</Text>
179175
</Stack>
180-
<Button
181-
type="button"
182-
variant="outline"
183-
onClick={() => handleDownload(attachment.id)}
184-
>
176+
<Button type="button" variant="outline" onClick={() => handleDownload(attachment.id)}>
185177
Open
186178
</Button>
187179
</HStack>
@@ -192,11 +184,7 @@ function CustomReportAttachments({
192184
);
193185
}
194186

195-
function ComponentStatuses({
196-
backgroundCheck,
197-
}: {
198-
backgroundCheck: BackgroundCheckRecord;
199-
}) {
187+
function ComponentStatuses({ backgroundCheck }: { backgroundCheck: BackgroundCheckRecord }) {
200188
const statuses = COMPONENT_LABELS.flatMap(([key, label]) => {
201189
const value = backgroundCheck[key];
202190
return value ? [{ label, value }] : [];

‎apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/CustomBackgroundCheckUpload.tsx‎

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,16 @@ export function CustomBackgroundCheckUpload({
5757
);
5858

5959
if (response.error || !response.data) {
60-
toast.error(response.error ?? 'Failed to upload background check');
60+
toast.error('Failed to upload background check');
6161
return;
6262
}
6363

6464
toast.success('Custom background check attached');
6565
setSelectedFile(null);
6666
if (inputRef.current) inputRef.current.value = '';
6767
await onUploaded(response.data);
68-
} catch (error) {
69-
toast.error(error instanceof Error ? error.message : 'Failed to upload background check');
68+
} catch {
69+
toast.error('Failed to upload background check');
7070
} finally {
7171
setIsUploading(false);
7272
}

‎apps/app/src/app/(app)/[orgId]/people/[employeeId]/components/EmployeeBackgroundCheck.test.tsx‎

Lines changed: 16 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,8 @@ function renderSection(props?: Partial<Parameters<typeof EmployeeBackgroundCheck
7777
describe('EmployeeBackgroundCheck', () => {
7878
beforeEach(() => {
7979
vi.clearAllMocks();
80+
vi.mocked(apiClient.get).mockReset();
81+
vi.mocked(apiClient.post).mockReset();
8082
window.sessionStorage.clear();
8183
navigationMock.pathname = '/org_1/people/mem_1';
8284
navigationMock.searchParams = new URLSearchParams();
@@ -188,9 +190,9 @@ describe('EmployeeBackgroundCheck', () => {
188190
}),
189191
'org_1',
190192
);
191-
expect(window.sessionStorage.getItem('background-check:org_1:mem_1:pending-request')).toContain(
192-
'ada@example.com',
193-
);
193+
expect(
194+
window.sessionStorage.getItem('background-check:org_1:mem_1:pending-request'),
195+
).not.toContain('ada@example.com');
194196
});
195197

196198
it('restores the pending check after Stripe setup before completing it', async () => {
@@ -204,8 +206,6 @@ describe('EmployeeBackgroundCheck', () => {
204206
JSON.stringify({
205207
organizationId: 'org_1',
206208
memberId: 'mem_1',
207-
employeeName: 'Ada Lovelace',
208-
employeeEmail: 'ada@example.com',
209209
requesterNotes: 'Recruiting requested an expedited check.',
210210
}),
211211
);
@@ -254,11 +254,15 @@ describe('EmployeeBackgroundCheck', () => {
254254
'org_1',
255255
);
256256
expect(await screen.findByText('Payment method saved')).toBeInTheDocument();
257-
expect(screen.getByDisplayValue('ada@example.com')).toBeInTheDocument();
257+
expect(screen.getByLabelText('Personal email')).toHaveValue('');
258+
expect(
259+
screen.getByDisplayValue('Recruiting requested an expedited check.'),
260+
).toBeInTheDocument();
258261
expect(window.sessionStorage.getItem('background-check:org_1:mem_1:pending-request')).toContain(
259-
'ada@example.com',
262+
'Recruiting requested an expedited check.',
260263
);
261264

265+
await user.type(screen.getByLabelText('Personal email'), 'ada@example.com');
262266
await user.click(screen.getByRole('button', { name: /complete/i }));
263267

264268
await waitFor(() => {
@@ -280,7 +284,7 @@ describe('EmployeeBackgroundCheck', () => {
280284
const user = userEvent.setup();
281285
vi.mocked(apiClient.post)
282286
.mockResolvedValueOnce({
283-
error: 'Background check payment failed. Update billing and try again.',
287+
error: 'Invalid API Key provided: PLACEHOLDER',
284288
status: 402,
285289
})
286290
.mockResolvedValueOnce({ data: {}, status: 200 });
@@ -292,6 +296,10 @@ describe('EmployeeBackgroundCheck', () => {
292296
expect(
293297
await screen.findByRole('heading', { name: /update payment method/i }),
294298
).toBeInTheDocument();
299+
expect(screen.queryByText(/PLACEHOLDER/)).not.toBeInTheDocument();
300+
expect(
301+
screen.getByText('Payment failed. Update payment method and try again.'),
302+
).toBeInTheDocument();
295303

296304
await user.click(screen.getByRole('button', { name: /update payment method/i }));
297305

0 commit comments

Comments
 (0)