Skip to content

Commit 20f62fa

Browse files
authored
Merge pull request #1819 from dubinc/customer-externalId
Allow querying customers by externalId
2 parents e9706a0 + ce76850 commit 20f62fa

File tree

8 files changed

+75
-58
lines changed

8 files changed

+75
-58
lines changed

apps/web/app/api/customers/[id]/route.ts

Lines changed: 25 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -14,10 +14,15 @@ export const GET = withWorkspace(
1414
async ({ workspace, params }) => {
1515
const { id } = params;
1616

17-
const customer = await getCustomerOrThrow({
18-
id,
19-
workspaceId: workspace.id,
20-
});
17+
const customer = await getCustomerOrThrow(
18+
{
19+
id,
20+
workspaceId: workspace.id,
21+
},
22+
{
23+
expand: ["link"],
24+
},
25+
);
2126

2227
return NextResponse.json(CustomerSchema.parse(customer));
2328
},
@@ -35,32 +40,29 @@ export const PATCH = withWorkspace(
3540
await parseRequestBody(req),
3641
);
3742

38-
await getCustomerOrThrow({
43+
const customer = await getCustomerOrThrow({
3944
id,
4045
workspaceId: workspace.id,
4146
});
4247

4348
try {
44-
const customer = await prisma.customer.update({
49+
const updatedCustomer = await prisma.customer.update({
4550
where: {
46-
id,
51+
id: customer.id,
4752
},
4853
data: { name, email, avatar, externalId },
54+
include: {
55+
link: true,
56+
},
4957
});
5058

51-
return NextResponse.json(CustomerSchema.parse(customer));
59+
return NextResponse.json(CustomerSchema.parse(updatedCustomer));
5260
} catch (error) {
5361
if (error.code === "P2002") {
5462
throw new DubApiError({
5563
code: "conflict",
5664
message: "A customer with this external ID already exists.",
5765
});
58-
} else if (error.code === "P2025") {
59-
throw new DubApiError({
60-
code: "not_found",
61-
message:
62-
"Customer not found. Make sure you're using the correct external ID.",
63-
});
6466
}
6567

6668
throw new DubApiError({
@@ -79,34 +81,20 @@ export const DELETE = withWorkspace(
7981
async ({ workspace, params }) => {
8082
const { id } = params;
8183

82-
await getCustomerOrThrow({
84+
const customer = await getCustomerOrThrow({
8385
id,
8486
workspaceId: workspace.id,
8587
});
8688

87-
try {
88-
await prisma.customer.delete({
89-
where: {
90-
id,
91-
},
92-
});
93-
94-
return NextResponse.json({
95-
id,
96-
});
97-
} catch (error) {
98-
if (error.code === "P2025") {
99-
throw new DubApiError({
100-
code: "not_found",
101-
message: "Customer not found",
102-
});
103-
}
89+
await prisma.customer.delete({
90+
where: {
91+
id: customer.id,
92+
},
93+
});
10494

105-
throw new DubApiError({
106-
code: "unprocessable_entity",
107-
message: error.message,
108-
});
109-
}
95+
return NextResponse.json({
96+
id: customer.id,
97+
});
11098
},
11199
{
112100
requiredAddOn: "conversion",

apps/web/app/api/customers/route.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,9 @@ export const POST = withWorkspace(
4848
projectId: workspace.id,
4949
projectConnectId: workspace.stripeConnectId,
5050
},
51+
include: {
52+
link: true,
53+
},
5154
});
5255

5356
return NextResponse.json(CustomerSchema.parse(customer), {

apps/web/app/api/stripe/integration/webhook/customer-created.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,8 @@ export async function customerCreated(event: Stripe.Event) {
3535
},
3636
});
3737

38-
if (!link) {
39-
return `Link with ID ${linkId} not found, skipping...`;
38+
if (!link || !link.projectId) {
39+
return `Link with ID ${linkId} not found or does not have a project, skipping...`;
4040
}
4141

4242
// Check the customer is not already created
@@ -61,15 +61,11 @@ export async function customerCreated(event: Stripe.Event) {
6161
stripeCustomerId: stripeCustomer.id,
6262
projectConnectId: stripeAccountId,
6363
externalId,
64+
projectId: link.projectId,
6465
linkId,
6566
clickId,
6667
clickedAt: new Date(clickData.timestamp + "Z"),
6768
country: clickData.country,
68-
project: {
69-
connect: {
70-
stripeConnectId: stripeAccountId,
71-
},
72-
},
7369
},
7470
});
7571

apps/web/lib/api/customers/get-customer-or-throw.ts

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,37 @@
11
import { prisma } from "@dub/prisma";
22
import { DubApiError } from "../errors";
33

4-
export const getCustomerOrThrow = async ({
5-
id,
6-
workspaceId,
7-
}: {
8-
id: string;
9-
workspaceId: string;
10-
}) => {
4+
export const getCustomerOrThrow = async (
5+
{
6+
id,
7+
workspaceId,
8+
}: {
9+
id: string;
10+
workspaceId: string;
11+
},
12+
{ expand }: { expand?: ("link" | "project")[] } = {},
13+
) => {
1114
const customer = await prisma.customer.findUnique({
12-
where: { id },
15+
where: {
16+
...(id.startsWith("ext_")
17+
? {
18+
projectId_externalId: {
19+
projectId: workspaceId,
20+
externalId: id.replace("ext_", ""),
21+
},
22+
}
23+
: { id }),
24+
},
25+
include: {
26+
link: expand?.includes("link"),
27+
},
1328
});
1429

1530
if (!customer || customer.projectId !== workspaceId) {
1631
throw new DubApiError({
1732
code: "not_found",
18-
message: "Customer not found.",
33+
message:
34+
"Customer not found. Make sure you're using the correct customer ID (e.g. `cus_3TagGjzRzmsFJdH8od2BNCsc`) or external ID (has to be prefixed with `ext_`).",
1935
});
2036
}
2137

apps/web/lib/zod/schemas/customers.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,13 @@ export const CustomerSchema = z.object({
3737
avatar: z.string().nullish().describe("Avatar URL of the customer."),
3838
country: z.string().nullish().describe("Country of the customer."),
3939
createdAt: z.date().describe("The date the customer was created."),
40+
link: LinkSchema.pick({
41+
id: true,
42+
domain: true,
43+
key: true,
44+
shortLink: true,
45+
programId: true,
46+
}).nullish(),
4047
});
4148

4249
export const CUSTOMERS_MAX_PAGE_SIZE = 100;

apps/web/tests/customers/index.test.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ const expectedCustomer = {
1919
email: customerRecord.email,
2020
avatar: customerRecord.avatar,
2121
country: null,
22+
link: null,
2223
createdAt: expect.any(String),
2324
};
2425

packages/prisma/schema/customer.prisma

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,22 +4,27 @@ model Customer {
44
email String?
55
avatar String? @db.LongText
66
externalId String?
7+
stripeCustomerId String? @unique
8+
79
linkId String?
810
clickId String?
911
clickedAt DateTime?
1012
country String?
11-
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
13+
1214
projectId String
1315
projectConnectId String?
14-
stripeCustomerId String? @unique
1516
createdAt DateTime @default(now())
1617
updatedAt DateTime @updatedAt
1718
19+
20+
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
21+
link Link? @relation(fields: [linkId], references: [id])
1822
sales Sale[]
1923
2024
@@unique([projectId, externalId])
2125
@@unique([projectConnectId, externalId])
22-
@@index(projectId)
23-
@@index(projectConnectId)
24-
@@index(externalId)
26+
@@index([projectId])
27+
@@index([projectConnectId])
28+
@@index([externalId])
29+
@@index([linkId])
2530
}

packages/prisma/schema/link.prisma

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ model Link {
6868
6969
programId String?
7070
program Program? @relation(fields: [programId], references: [id])
71+
customers Customer[]
7172
7273
@@unique([domain, key])
7374
@@unique([projectId, externalId])

0 commit comments

Comments
 (0)