This commit is contained in:
Frank
2026-05-17 04:04:39 -04:00
parent 0b8050d453
commit de247b7aae
5 changed files with 2874 additions and 35 deletions
@@ -161,7 +161,9 @@ export async function POST(input: APIEvent) {
}) })
if (userEmail) { if (userEmail) {
if (coupon === LiteData.firstMonth100Coupon) { if (coupon === LiteData.firstMonth50Coupon) {
await Billing.redeemCoupon(userEmail, "GO1MONTH50")
} else if (coupon === LiteData.firstMonth100Coupon) {
await Billing.redeemCoupon(userEmail, "GOFREEMONTH") await Billing.redeemCoupon(userEmail, "GOFREEMONTH")
} else if (coupon === LiteData.threeMonths100Coupon) { } else if (coupon === LiteData.threeMonths100Coupon) {
await Billing.redeemCoupon(userEmail, "GO3MONTHS100") await Billing.redeemCoupon(userEmail, "GO3MONTHS100")
@@ -0,0 +1 @@
ALTER TABLE `coupon` MODIFY COLUMN `type` enum('BUILDATHON','GO1MONTH50','GOFREEMONTH','GO3MONTHS100','GO6MONTHS100','GO12MONTHS100') NOT NULL;
File diff suppressed because it is too large Load Diff
+41 -33
View File
@@ -156,33 +156,32 @@ export namespace Billing {
} }
export const redeemCoupon = async (email: string, type: (typeof CouponType)[number]) => { export const redeemCoupon = async (email: string, type: (typeof CouponType)[number]) => {
const coupon = await Database.use((tx) => // validate coupon type
tx await (async () => {
.select() if (type === "GO1MONTH50") return
.from(CouponTable) const coupon = await Database.use((tx) =>
.where(and(eq(CouponTable.email, email), eq(CouponTable.type, type))) tx
.then((rows) => rows[0]), .select()
) .from(CouponTable)
if (!coupon) throw new Error("Invalid coupon code") .where(and(eq(CouponTable.email, email), eq(CouponTable.type, type)))
if (coupon.timeRedeemed) throw new Error("Coupon already redeemed") .then((rows) => rows[0]),
)
if (!coupon) throw new Error("Invalid coupon code")
if (coupon.timeRedeemed) throw new Error("Coupon already redeemed")
})()
// handle coupon type
if (type === "BUILDATHON") await grantCredit(Actor.workspace(), 500) if (type === "BUILDATHON") await grantCredit(Actor.workspace(), 500)
await Database.use((tx) => await Database.use((tx) =>
tx tx
.update(CouponTable) .insert(CouponTable)
.set({ timeRedeemed: sql`now()` }) .values({ email, type, timeRedeemed: sql`now()` })
.where(and(eq(CouponTable.email, email), eq(CouponTable.type, type))), .onDuplicateKeyUpdate({
) set: {
} timeRedeemed: sql`now()`,
},
export const getCoupons = async (email: string) => { }),
return await Database.use((tx) =>
tx
.select({ type: CouponTable.type, timeRedeemed: CouponTable.timeRedeemed })
.from(CouponTable)
.where(and(eq(CouponTable.email, email), isNull(CouponTable.timeRedeemed)))
.then((rows) => rows.map((row) => row.type)),
) )
} }
@@ -290,20 +289,29 @@ export namespace Billing {
if (billing.subscriptionID) throw new Error("Already subscribed to Black") if (billing.subscriptionID) throw new Error("Already subscribed to Black")
if (billing.liteSubscriptionID) throw new Error("Already subscribed to Lite") if (billing.liteSubscriptionID) throw new Error("Already subscribed to Lite")
const coupons = await Billing.getCoupons(email) const coupons = await Database.use((tx) =>
const coupon = coupons.includes("GO12MONTHS100") tx
? LiteData.twelveMonths100Coupon .select({ type: CouponTable.type, timeRedeemed: CouponTable.timeRedeemed })
: coupons.includes("GO6MONTHS100") .from(CouponTable)
? LiteData.sixMonths100Coupon .where(eq(CouponTable.email, email)),
: coupons.includes("GO3MONTHS100") )
? LiteData.threeMonths100Coupon
: coupons.includes("GOFREEMONTH") const coupon = (() => {
? LiteData.firstMonth100Coupon if (coupons.some((coupon) => coupon.type === "GO12MONTHS100" && !coupon.timeRedeemed))
: LiteData.firstMonth50Coupon return LiteData.twelveMonths100Coupon
if (coupons.some((coupon) => coupon.type === "GO6MONTHS100" && !coupon.timeRedeemed))
return LiteData.sixMonths100Coupon
if (coupons.some((coupon) => coupon.type === "GO3MONTHS100" && !coupon.timeRedeemed))
return LiteData.threeMonths100Coupon
if (coupons.some((coupon) => coupon.type === "GOFREEMONTH" && !coupon.timeRedeemed))
return LiteData.firstMonth100Coupon
if (!coupons.some((coupon) => coupon.type === "GO1MONTH50")) return LiteData.firstMonth50Coupon
return undefined
})()
const createSession = () => const createSession = () =>
Billing.stripe().checkout.sessions.create({ Billing.stripe().checkout.sessions.create({
mode: "subscription", mode: "subscription",
discounts: [{ coupon }], discounts: coupon ? [{ coupon }] : undefined,
...(billing.customerID ...(billing.customerID
? { ? {
customer: billing.customerID, customer: billing.customerID,
@@ -133,7 +133,14 @@ export const UsageTable = mysqlTable(
(table) => [...workspaceIndexes(table), index("usage_time_created").on(table.workspaceID, table.timeCreated)], (table) => [...workspaceIndexes(table), index("usage_time_created").on(table.workspaceID, table.timeCreated)],
) )
export const CouponType = ["BUILDATHON", "GOFREEMONTH", "GO3MONTHS100", "GO6MONTHS100", "GO12MONTHS100"] as const export const CouponType = [
"BUILDATHON",
"GO1MONTH50",
"GOFREEMONTH",
"GO3MONTHS100",
"GO6MONTHS100",
"GO12MONTHS100",
] as const
export const CouponTable = mysqlTable( export const CouponTable = mysqlTable(
"coupon", "coupon",
{ {