Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 25 additions & 37 deletions apps/web/app/api/customers/[id]/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,15 @@ export const GET = withWorkspace(
async ({ workspace, params }) => {
const { id } = params;

const customer = await getCustomerOrThrow({
id,
workspaceId: workspace.id,
});
const customer = await getCustomerOrThrow(
{
id,
workspaceId: workspace.id,
},
{
expand: ["link"],
},
);

return NextResponse.json(CustomerSchema.parse(customer));
},
Expand All @@ -35,32 +40,29 @@ export const PATCH = withWorkspace(
await parseRequestBody(req),
);

await getCustomerOrThrow({
const customer = await getCustomerOrThrow({
id,
workspaceId: workspace.id,
});

try {
const customer = await prisma.customer.update({
const updatedCustomer = await prisma.customer.update({
where: {
id,
id: customer.id,
},
data: { name, email, avatar, externalId },
include: {
link: true,
},
});

return NextResponse.json(CustomerSchema.parse(customer));
return NextResponse.json(CustomerSchema.parse(updatedCustomer));
} catch (error) {
if (error.code === "P2002") {
throw new DubApiError({
code: "conflict",
message: "A customer with this external ID already exists.",
});
} else if (error.code === "P2025") {
throw new DubApiError({
code: "not_found",
message:
"Customer not found. Make sure you're using the correct external ID.",
});
}

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

await getCustomerOrThrow({
const customer = await getCustomerOrThrow({
id,
workspaceId: workspace.id,
});

try {
await prisma.customer.delete({
where: {
id,
},
});

return NextResponse.json({
id,
});
} catch (error) {
if (error.code === "P2025") {
throw new DubApiError({
code: "not_found",
message: "Customer not found",
});
}
await prisma.customer.delete({
where: {
id: customer.id,
},
});

throw new DubApiError({
code: "unprocessable_entity",
message: error.message,
});
}
return NextResponse.json({
id: customer.id,
});
},
{
requiredAddOn: "conversion",
Expand Down
3 changes: 3 additions & 0 deletions apps/web/app/api/customers/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,9 @@ export const POST = withWorkspace(
projectId: workspace.id,
projectConnectId: workspace.stripeConnectId,
},
include: {
link: true,
},
});

return NextResponse.json(CustomerSchema.parse(customer), {
Expand Down
10 changes: 3 additions & 7 deletions apps/web/app/api/stripe/integration/webhook/customer-created.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,8 +35,8 @@ export async function customerCreated(event: Stripe.Event) {
},
});

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

// Check the customer is not already created
Expand All @@ -61,15 +61,11 @@ export async function customerCreated(event: Stripe.Event) {
stripeCustomerId: stripeCustomer.id,
projectConnectId: stripeAccountId,
externalId,
projectId: link.projectId,
linkId,
clickId,
clickedAt: new Date(clickData.timestamp + "Z"),
country: clickData.country,
project: {
connect: {
stripeConnectId: stripeAccountId,
},
},
},
});

Expand Down
34 changes: 25 additions & 9 deletions apps/web/lib/api/customers/get-customer-or-throw.ts
Original file line number Diff line number Diff line change
@@ -1,21 +1,37 @@
import { prisma } from "@dub/prisma";
import { DubApiError } from "../errors";

export const getCustomerOrThrow = async ({
id,
workspaceId,
}: {
id: string;
workspaceId: string;
}) => {
export const getCustomerOrThrow = async (
{
id,
workspaceId,
}: {
id: string;
workspaceId: string;
},
{ expand }: { expand?: ("link" | "project")[] } = {},
) => {
const customer = await prisma.customer.findUnique({
where: { id },
where: {
...(id.startsWith("ext_")
? {
projectId_externalId: {
projectId: workspaceId,
externalId: id.replace("ext_", ""),
},
}
: { id }),
},
include: {
link: expand?.includes("link"),
},
});

if (!customer || customer.projectId !== workspaceId) {
throw new DubApiError({
code: "not_found",
message: "Customer not found.",
message:
"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_`).",
});
}

Expand Down
7 changes: 7 additions & 0 deletions apps/web/lib/zod/schemas/customers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ export const CustomerSchema = z.object({
avatar: z.string().nullish().describe("Avatar URL of the customer."),
country: z.string().nullish().describe("Country of the customer."),
createdAt: z.date().describe("The date the customer was created."),
link: LinkSchema.pick({
id: true,
domain: true,
key: true,
shortLink: true,
programId: true,
}).nullish(),
});

export const CUSTOMERS_MAX_PAGE_SIZE = 100;
Expand Down
1 change: 1 addition & 0 deletions apps/web/tests/customers/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ const expectedCustomer = {
email: customerRecord.email,
avatar: customerRecord.avatar,
country: null,
link: null,
createdAt: expect.any(String),
};

Expand Down
15 changes: 10 additions & 5 deletions packages/prisma/schema/customer.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -4,22 +4,27 @@ model Customer {
email String?
avatar String? @db.LongText
externalId String?
stripeCustomerId String? @unique

linkId String?
clickId String?
clickedAt DateTime?
country String?
project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)

projectId String
projectConnectId String?
stripeCustomerId String? @unique
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt


project Project @relation(fields: [projectId], references: [id], onDelete: Cascade)
link Link? @relation(fields: [linkId], references: [id])
sales Sale[]

@@unique([projectId, externalId])
@@unique([projectConnectId, externalId])
@@index(projectId)
@@index(projectConnectId)
@@index(externalId)
@@index([projectId])
@@index([projectConnectId])
@@index([externalId])
@@index([linkId])
}
1 change: 1 addition & 0 deletions packages/prisma/schema/link.prisma
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ model Link {

programId String?
program Program? @relation(fields: [programId], references: [id])
customers Customer[]

@@unique([domain, key])
@@unique([projectId, externalId])
Expand Down
Loading