diff --git a/infra/console.ts b/infra/console.ts
index 8925f37d5a..f1f5692b7a 100644
--- a/infra/console.ts
+++ b/infra/console.ts
@@ -236,7 +236,6 @@ new sst.cloudflare.x.SolidStart("Console", {
SALESFORCE_INSTANCE_URL,
ZEN_BLACK_PRICE,
ZEN_LITE_PRICE,
- new sst.Secret("ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES"),
new sst.Secret("ZEN_LIMITS"),
new sst.Secret("ZEN_SESSION_SECRET"),
...ZEN_MODELS,
diff --git a/packages/console/app/src/routes/stripe/webhook.ts b/packages/console/app/src/routes/stripe/webhook.ts
index 0d8cf61cfa..c28d9ebbb9 100644
--- a/packages/console/app/src/routes/stripe/webhook.ts
+++ b/packages/console/app/src/routes/stripe/webhook.ts
@@ -9,6 +9,7 @@ import { Actor } from "@opencode-ai/console-core/actor.js"
import { Resource } from "@opencode-ai/console-resource"
import { LiteData } from "@opencode-ai/console-core/lite.js"
import { BlackData } from "@opencode-ai/console-core/black.js"
+import { User } from "@opencode-ai/console-core/user.js"
export async function POST(input: APIEvent) {
const body = await Billing.stripe().webhooks.constructEventAsync(
@@ -109,6 +110,8 @@ export async function POST(input: APIEvent) {
if (type === "lite") {
const workspaceID = body.data.object.metadata?.workspaceID
const userID = body.data.object.metadata?.userID
+ const userEmail = body.data.object.metadata?.userEmail
+ const coupon = body.data.object.metadata?.coupon
const customerID = body.data.object.customer as string
const invoiceID = body.data.object.latest_invoice as string
const subscriptionID = body.data.object.id as string
@@ -156,6 +159,10 @@ export async function POST(input: APIEvent) {
id: Identifier.create("lite"),
userID: userID,
})
+
+ if (userEmail && coupon === LiteData.firstMonth100Coupon) {
+ await Billing.redeemCoupon(userEmail, "GOFREEMONTH")
+ }
})
})
}
diff --git a/packages/console/app/src/routes/workspace/[id]/billing/index.tsx b/packages/console/app/src/routes/workspace/[id]/billing/index.tsx
index fb48485354..11185436be 100644
--- a/packages/console/app/src/routes/workspace/[id]/billing/index.tsx
+++ b/packages/console/app/src/routes/workspace/[id]/billing/index.tsx
@@ -25,9 +25,9 @@ export default function () {
+
-
diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts
index d96b19ad79..a3252f1d78 100644
--- a/packages/console/core/src/billing.ts
+++ b/packages/console/core/src/billing.ts
@@ -1,5 +1,5 @@
import { Stripe } from "stripe"
-import { and, Database, eq, sql } from "./drizzle"
+import { and, Database, eq, isNull, sql } from "./drizzle"
import {
BillingTable,
CouponTable,
@@ -176,6 +176,16 @@ export namespace Billing {
)
}
+ export const hasCoupon = async (email: string, type: (typeof CouponType)[number]) => {
+ return await Database.use((tx) =>
+ tx
+ .select()
+ .from(CouponTable)
+ .where(and(eq(CouponTable.email, email), eq(CouponTable.type, type), isNull(CouponTable.timeRedeemed)))
+ .then((rows) => rows.length > 0),
+ )
+ }
+
export const setMonthlyLimit = fn(z.number(), async (input) => {
return await Database.use((tx) =>
tx
@@ -274,16 +284,19 @@ export namespace Billing {
const user = Actor.assert("user")
const { successUrl, cancelUrl, method } = input
- const email = await User.getAuthEmail(user.properties.userID)
+ const email = (await User.getAuthEmail(user.properties.userID))!
const billing = await Billing.get()
if (billing.subscriptionID) throw new Error("Already subscribed to Black")
if (billing.liteSubscriptionID) throw new Error("Already subscribed to Lite")
+ const coupon = (await Billing.hasCoupon(email, "GOFREEMONTH"))
+ ? LiteData.firstMonth100Coupon
+ : LiteData.firstMonth50Coupon
const createSession = () =>
Billing.stripe().checkout.sessions.create({
mode: "subscription",
- discounts: [{ coupon: LiteData.firstMonthCoupon(email!) }],
+ discounts: [{ coupon }],
...(billing.customerID
? {
customer: billing.customerID,
@@ -293,7 +306,7 @@ export namespace Billing {
},
}
: {
- customer_email: email!,
+ customer_email: email,
}),
...(() => {
if (method === "alipay") {
@@ -341,6 +354,8 @@ export namespace Billing {
metadata: {
workspaceID: Actor.workspace(),
userID: user.properties.userID,
+ userEmail: email,
+ coupon,
type: "lite",
},
},
diff --git a/packages/console/core/src/lite.ts b/packages/console/core/src/lite.ts
index 3343192c19..c049776643 100644
--- a/packages/console/core/src/lite.ts
+++ b/packages/console/core/src/lite.ts
@@ -11,11 +11,7 @@ export namespace LiteData {
export const productID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.product)
export const priceID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.price)
export const priceInr = fn(z.void(), () => Resource.ZEN_LITE_PRICE.priceInr)
- export const firstMonthCoupon = fn(z.string(), (email) => {
- const invitees = Resource.ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES.value.split(",")
- return invitees.includes(email)
- ? Resource.ZEN_LITE_PRICE.firstMonth100Coupon
- : Resource.ZEN_LITE_PRICE.firstMonth50Coupon
- })
+ export const firstMonth100Coupon = Resource.ZEN_LITE_PRICE.firstMonth100Coupon
+ export const firstMonth50Coupon = Resource.ZEN_LITE_PRICE.firstMonth50Coupon
export const planName = fn(z.void(), () => "lite")
}
diff --git a/packages/console/core/sst-env.d.ts b/packages/console/core/sst-env.d.ts
index b77ee3c5bf..bfba1b8f2e 100644
--- a/packages/console/core/sst-env.d.ts
+++ b/packages/console/core/sst-env.d.ts
@@ -142,10 +142,6 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
- "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
- "type": "sst.sst.Secret"
- "value": string
- }
"ZEN_LITE_PRICE": {
"firstMonth100Coupon": string
"firstMonth50Coupon": string
diff --git a/packages/console/function/sst-env.d.ts b/packages/console/function/sst-env.d.ts
index b77ee3c5bf..bfba1b8f2e 100644
--- a/packages/console/function/sst-env.d.ts
+++ b/packages/console/function/sst-env.d.ts
@@ -142,10 +142,6 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
- "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
- "type": "sst.sst.Secret"
- "value": string
- }
"ZEN_LITE_PRICE": {
"firstMonth100Coupon": string
"firstMonth50Coupon": string
diff --git a/packages/console/resource/sst-env.d.ts b/packages/console/resource/sst-env.d.ts
index b77ee3c5bf..bfba1b8f2e 100644
--- a/packages/console/resource/sst-env.d.ts
+++ b/packages/console/resource/sst-env.d.ts
@@ -142,10 +142,6 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
- "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
- "type": "sst.sst.Secret"
- "value": string
- }
"ZEN_LITE_PRICE": {
"firstMonth100Coupon": string
"firstMonth50Coupon": string
diff --git a/packages/enterprise/sst-env.d.ts b/packages/enterprise/sst-env.d.ts
index b77ee3c5bf..bfba1b8f2e 100644
--- a/packages/enterprise/sst-env.d.ts
+++ b/packages/enterprise/sst-env.d.ts
@@ -142,10 +142,6 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
- "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
- "type": "sst.sst.Secret"
- "value": string
- }
"ZEN_LITE_PRICE": {
"firstMonth100Coupon": string
"firstMonth50Coupon": string
diff --git a/packages/function/sst-env.d.ts b/packages/function/sst-env.d.ts
index b77ee3c5bf..bfba1b8f2e 100644
--- a/packages/function/sst-env.d.ts
+++ b/packages/function/sst-env.d.ts
@@ -142,10 +142,6 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
- "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
- "type": "sst.sst.Secret"
- "value": string
- }
"ZEN_LITE_PRICE": {
"firstMonth100Coupon": string
"firstMonth50Coupon": string
diff --git a/sst-env.d.ts b/sst-env.d.ts
index 2a40a9f3c9..e0c3665a96 100644
--- a/sst-env.d.ts
+++ b/sst-env.d.ts
@@ -168,10 +168,6 @@ declare module "sst" {
"type": "sst.sst.Secret"
"value": string
}
- "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
- "type": "sst.sst.Secret"
- "value": string
- }
"ZEN_LITE_PRICE": {
"firstMonth100Coupon": string
"firstMonth50Coupon": string