Skip to content

Commit 8c854a4

Browse files
authored
Fix discount code Stripe/DB drift on creation failure (#3985)
1 parent 94fb773 commit 8c854a4

2 files changed

Lines changed: 81 additions & 23 deletions

File tree

apps/web/app/(ee)/api/discount-codes/route.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ export const POST = withWorkspace(
9696
const duplicateByCode = await prisma.discountCode.findUnique({
9797
where: {
9898
programId_code: {
99-
programId,
99+
programId: discount.programId,
100100
code,
101101
},
102102
},
Lines changed: 80 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import { createId } from "@/lib/api/create-id";
2+
import { DubApiError } from "@/lib/api/errors";
23
import { prisma } from "@dub/prisma";
3-
import { Discount, Link, Partner, Project } from "@dub/prisma/client";
4+
import { Discount, Link, Partner, Prisma, Project } from "@dub/prisma/client";
45
import { constructDiscountCode } from "./construct-discount-code";
56
import { getDiscountProvider } from "./discount-provider";
67

@@ -19,33 +20,90 @@ export async function createDiscountCode({
1920
discount,
2021
code,
2122
}: CreateDiscountCodeArgs) {
22-
let finalCode = code;
23-
24-
// Construct the discount code if no code is provided
25-
if (!finalCode) {
26-
finalCode = constructDiscountCode({
23+
const finalCode =
24+
code ||
25+
constructDiscountCode({
2726
partner,
2827
discount,
2928
});
29+
30+
const linkWithCode = await prisma.link.findUnique({
31+
where: { id: link.id },
32+
select: { discountCode: { select: { code: true } } },
33+
});
34+
35+
if (linkWithCode?.discountCode) {
36+
throw new DubApiError({
37+
code: "bad_request",
38+
message: `This link already has a discount code (${linkWithCode.discountCode.code}) assigned.`,
39+
});
3040
}
3141

3242
const discountProvider = getDiscountProvider(discount.provider);
3343

34-
const discountCode = await discountProvider.createDiscountCode({
35-
workspace,
36-
discount,
37-
code: finalCode,
38-
shouldRetry: code ? false : true,
39-
});
44+
let externalDiscountCode: Awaited<
45+
ReturnType<typeof discountProvider.createDiscountCode>
46+
>;
4047

41-
return await prisma.discountCode.create({
42-
data: {
43-
id: createId({ prefix: "dcode_" }),
44-
code: discountCode.code,
45-
programId: discount.programId,
46-
partnerId: partner.id,
47-
linkId: link.id,
48-
discountId: discount.id,
49-
},
50-
});
48+
try {
49+
externalDiscountCode = await discountProvider.createDiscountCode({
50+
workspace,
51+
discount,
52+
code: finalCode,
53+
shouldRetry: code ? false : true,
54+
});
55+
} catch (error) {
56+
const message = error?.raw?.message || error?.message || "";
57+
const isDuplicateCode =
58+
message.includes("already exists") ||
59+
error?.code === "TAKEN" ||
60+
error?.code === "DUPLICATE";
61+
62+
if (isDuplicateCode) {
63+
throw new DubApiError({
64+
code: "conflict",
65+
message: `The discount code ${finalCode} is already in use. Please choose a different code.`,
66+
});
67+
}
68+
69+
throw error;
70+
}
71+
72+
try {
73+
return await prisma.discountCode.create({
74+
data: {
75+
id: createId({ prefix: "dcode_" }),
76+
code: externalDiscountCode.code,
77+
programId: discount.programId,
78+
partnerId: partner.id,
79+
linkId: link.id,
80+
discountId: discount.id,
81+
},
82+
});
83+
} catch (error) {
84+
try {
85+
await discountProvider.disableDiscountCode({
86+
workspace,
87+
code: externalDiscountCode.code,
88+
});
89+
} catch (rollbackError) {
90+
console.error("Failed to rollback external discount code", {
91+
code: externalDiscountCode.code,
92+
rollbackError,
93+
});
94+
}
95+
96+
if (
97+
error instanceof Prisma.PrismaClientKnownRequestError &&
98+
error.code === "P2002"
99+
) {
100+
throw new DubApiError({
101+
code: "conflict",
102+
message:
103+
"This discount code is already in use, or this link already has a code. Please refresh and try again.",
104+
});
105+
}
106+
107+
throw error;
108+
}
51109
}

0 commit comments

Comments
 (0)