浏览代码

zen: redeem go

Frank 4 月之前
父节点
当前提交
54b3b3fe05

+ 0 - 1
infra/console.ts

@@ -236,7 +236,6 @@ new sst.cloudflare.x.SolidStart("Console", {
     SALESFORCE_INSTANCE_URL,
     SALESFORCE_INSTANCE_URL,
     ZEN_BLACK_PRICE,
     ZEN_BLACK_PRICE,
     ZEN_LITE_PRICE,
     ZEN_LITE_PRICE,
-    new sst.Secret("ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES"),
     new sst.Secret("ZEN_LIMITS"),
     new sst.Secret("ZEN_LIMITS"),
     new sst.Secret("ZEN_SESSION_SECRET"),
     new sst.Secret("ZEN_SESSION_SECRET"),
     ...ZEN_MODELS,
     ...ZEN_MODELS,

+ 7 - 0
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 { Resource } from "@opencode-ai/console-resource"
 import { LiteData } from "@opencode-ai/console-core/lite.js"
 import { LiteData } from "@opencode-ai/console-core/lite.js"
 import { BlackData } from "@opencode-ai/console-core/black.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) {
 export async function POST(input: APIEvent) {
   const body = await Billing.stripe().webhooks.constructEventAsync(
   const body = await Billing.stripe().webhooks.constructEventAsync(
@@ -109,6 +110,8 @@ export async function POST(input: APIEvent) {
       if (type === "lite") {
       if (type === "lite") {
         const workspaceID = body.data.object.metadata?.workspaceID
         const workspaceID = body.data.object.metadata?.workspaceID
         const userID = body.data.object.metadata?.userID
         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 customerID = body.data.object.customer as string
         const invoiceID = body.data.object.latest_invoice as string
         const invoiceID = body.data.object.latest_invoice as string
         const subscriptionID = body.data.object.id as string
         const subscriptionID = body.data.object.id as string
@@ -156,6 +159,10 @@ export async function POST(input: APIEvent) {
               id: Identifier.create("lite"),
               id: Identifier.create("lite"),
               userID: userID,
               userID: userID,
             })
             })
+
+            if (userEmail && coupon === LiteData.firstMonth100Coupon) {
+              await Billing.redeemCoupon(userEmail, "GOFREEMONTH")
+            }
           })
           })
         })
         })
       }
       }

+ 1 - 1
packages/console/app/src/routes/workspace/[id]/billing/index.tsx

@@ -25,9 +25,9 @@ export default function () {
           <Show when={billingInfo()?.customerID}>
           <Show when={billingInfo()?.customerID}>
             <ReloadSection />
             <ReloadSection />
             <MonthlyLimitSection />
             <MonthlyLimitSection />
+            <RedeemSection />
             <PaymentSection />
             <PaymentSection />
           </Show>
           </Show>
-          <RedeemSection />
         </Show>
         </Show>
       </div>
       </div>
     </div>
     </div>

+ 19 - 4
packages/console/core/src/billing.ts

@@ -1,5 +1,5 @@
 import { Stripe } from "stripe"
 import { Stripe } from "stripe"
-import { and, Database, eq, sql } from "./drizzle"
+import { and, Database, eq, isNull, sql } from "./drizzle"
 import {
 import {
   BillingTable,
   BillingTable,
   CouponTable,
   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) => {
   export const setMonthlyLimit = fn(z.number(), async (input) => {
     return await Database.use((tx) =>
     return await Database.use((tx) =>
       tx
       tx
@@ -274,16 +284,19 @@ export namespace Billing {
       const user = Actor.assert("user")
       const user = Actor.assert("user")
       const { successUrl, cancelUrl, method } = input
       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()
       const billing = await Billing.get()
 
 
       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 coupon = (await Billing.hasCoupon(email, "GOFREEMONTH"))
+        ? LiteData.firstMonth100Coupon
+        : LiteData.firstMonth50Coupon
       const createSession = () =>
       const createSession = () =>
         Billing.stripe().checkout.sessions.create({
         Billing.stripe().checkout.sessions.create({
           mode: "subscription",
           mode: "subscription",
-          discounts: [{ coupon: LiteData.firstMonthCoupon(email!) }],
+          discounts: [{ coupon }],
           ...(billing.customerID
           ...(billing.customerID
             ? {
             ? {
                 customer: billing.customerID,
                 customer: billing.customerID,
@@ -293,7 +306,7 @@ export namespace Billing {
                 },
                 },
               }
               }
             : {
             : {
-                customer_email: email!,
+                customer_email: email,
               }),
               }),
           ...(() => {
           ...(() => {
             if (method === "alipay") {
             if (method === "alipay") {
@@ -341,6 +354,8 @@ export namespace Billing {
             metadata: {
             metadata: {
               workspaceID: Actor.workspace(),
               workspaceID: Actor.workspace(),
               userID: user.properties.userID,
               userID: user.properties.userID,
+              userEmail: email,
+              coupon,
               type: "lite",
               type: "lite",
             },
             },
           },
           },

+ 2 - 6
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 productID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.product)
   export const priceID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.price)
   export const priceID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.price)
   export const priceInr = fn(z.void(), () => Resource.ZEN_LITE_PRICE.priceInr)
   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")
   export const planName = fn(z.void(), () => "lite")
 }
 }

+ 0 - 4
packages/console/core/sst-env.d.ts

@@ -142,10 +142,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "type": "sst.sst.Secret"
       "value": string
       "value": string
     }
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string
       "firstMonth50Coupon": string

+ 0 - 4
packages/console/function/sst-env.d.ts

@@ -142,10 +142,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "type": "sst.sst.Secret"
       "value": string
       "value": string
     }
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string
       "firstMonth50Coupon": string

+ 0 - 4
packages/console/resource/sst-env.d.ts

@@ -142,10 +142,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "type": "sst.sst.Secret"
       "value": string
       "value": string
     }
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string
       "firstMonth50Coupon": string

+ 0 - 4
packages/enterprise/sst-env.d.ts

@@ -142,10 +142,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "type": "sst.sst.Secret"
       "value": string
       "value": string
     }
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string
       "firstMonth50Coupon": string

+ 0 - 4
packages/function/sst-env.d.ts

@@ -142,10 +142,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "type": "sst.sst.Secret"
       "value": string
       "value": string
     }
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string
       "firstMonth50Coupon": string

+ 0 - 4
sst-env.d.ts

@@ -168,10 +168,6 @@ declare module "sst" {
       "type": "sst.sst.Secret"
       "type": "sst.sst.Secret"
       "value": string
       "value": string
     }
     }
-    "ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
-      "type": "sst.sst.Secret"
-      "value": string
-    }
     "ZEN_LITE_PRICE": {
     "ZEN_LITE_PRICE": {
       "firstMonth100Coupon": string
       "firstMonth100Coupon": string
       "firstMonth50Coupon": string
       "firstMonth50Coupon": string