Frank 1 неделя назад
Родитель
Сommit
01c5eb679c

+ 8 - 0
infra/console.ts

@@ -109,6 +109,12 @@ const zenLiteCouponFirstMonth50 = new stripe.Coupon("ZenLiteCouponFirstMonth50",
   appliesToProducts: [zenLiteProduct.id],
   duration: "once",
 })
+const zenLiteCouponFirstMonth100 = new stripe.Coupon("ZenLiteCouponFirstMonth100", {
+  name: "First month 100% off",
+  percentOff: 100,
+  appliesToProducts: [zenLiteProduct.id],
+  duration: "once",
+})
 const zenLitePrice = new stripe.Price("ZenLitePrice", {
   product: zenLiteProduct.id,
   currency: "usd",
@@ -124,6 +130,7 @@ const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", {
     price: zenLitePrice.id,
     priceInr: 92900,
     firstMonth50Coupon: zenLiteCouponFirstMonth50.id,
+    firstMonth100Coupon: zenLiteCouponFirstMonth100.id,
   },
 })
 
@@ -229,6 +236,7 @@ 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,

+ 8 - 25
packages/console/app/src/routes/stripe/webhook.ts

@@ -1,3 +1,4 @@
+import type { Stripe } from "stripe"
 import { Billing } from "@opencode-ai/console-core/billing.js"
 import type { APIEvent } from "@solidjs/start/server"
 import { and, Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js"
@@ -111,27 +112,17 @@ export async function POST(input: APIEvent) {
         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
+        const paymentMethodID = body.data.object.default_payment_method as string
 
         if (!workspaceID) throw new Error("Workspace ID not found")
         if (!userID) throw new Error("User ID not found")
         if (!customerID) throw new Error("Customer ID not found")
         if (!invoiceID) throw new Error("Invoice ID not found")
         if (!subscriptionID) throw new Error("Subscription ID not found")
-
-        // get payment id from invoice
-        const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
-          expand: ["payments"],
-        })
-        const paymentID = invoice.payments?.data[0].payment.payment_intent as string
-        if (!paymentID) throw new Error("Payment ID not found")
+        if (!paymentMethodID) throw new Error("Payment method ID not found")
 
         // get payment method for the payment intent
-        const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
-          expand: ["payment_method"],
-        })
-        const paymentMethod = paymentIntent.payment_method
-        if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
-
+        const paymentMethod = await Billing.stripe().paymentMethods.retrieve(paymentMethodID)
         await Actor.provide("system", { workspaceID }, async () => {
           // look up current billing
           const billing = await Billing.get()
@@ -200,26 +191,18 @@ export async function POST(input: APIEvent) {
         const amountInCents = body.data.object.amount_paid
         const customerID = body.data.object.customer as string
         const subscriptionID = body.data.object.parent?.subscription_details?.subscription as string
+        const productID = body.data.object.lines?.data[0].pricing?.price_details?.product as string
 
         if (!customerID) throw new Error("Customer ID not found")
         if (!invoiceID) throw new Error("Invoice ID not found")
         if (!subscriptionID) throw new Error("Subscription ID not found")
 
         // get coupon id from subscription
-        const subscriptionData = await Billing.stripe().subscriptions.retrieve(subscriptionID, {
-          expand: ["discounts"],
-        })
-        const couponID =
-          typeof subscriptionData.discounts[0] === "string"
-            ? subscriptionData.discounts[0]
-            : subscriptionData.discounts[0]?.coupon?.id
-        const productID = subscriptionData.items.data[0].price.product as string
-
-        // get payment id from invoice
         const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
-          expand: ["payments"],
+          expand: ["discounts", "payments"],
         })
-        const paymentID = invoice.payments?.data[0].payment.payment_intent as string
+        const paymentID = invoice.payments?.data[0]?.payment.payment_intent as string
+        const couponID = (invoice.discounts[0] as Stripe.Discount).coupon?.id as string
         if (!paymentID) {
           // payment id can be undefined when using coupon
           if (!couponID) throw new Error("Payment ID not found")

+ 1 - 1
packages/console/core/src/billing.ts

@@ -254,7 +254,7 @@ export namespace Billing {
       const createSession = () =>
         Billing.stripe().checkout.sessions.create({
           mode: "subscription",
-          discounts: [{ coupon: LiteData.firstMonth50Coupon() }],
+          discounts: [{ coupon: LiteData.firstMonthCoupon(email!) }],
           ...(billing.customerID
             ? {
                 customer: billing.customerID,

+ 6 - 1
packages/console/core/src/lite.ts

@@ -11,6 +11,11 @@ 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 firstMonth50Coupon = fn(z.void(), () => Resource.ZEN_LITE_PRICE.firstMonth50Coupon)
+  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 planName = fn(z.void(), () => "lite")
 }

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

@@ -142,7 +142,12 @@ 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
       "price": string
       "priceInr": number

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

@@ -142,7 +142,12 @@ 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
       "price": string
       "priceInr": number

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

@@ -142,7 +142,12 @@ 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
       "price": string
       "priceInr": number

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

@@ -142,7 +142,12 @@ 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
       "price": string
       "priceInr": number

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

@@ -142,7 +142,12 @@ 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
       "price": string
       "priceInr": number

+ 5 - 0
sst-env.d.ts

@@ -168,7 +168,12 @@ 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
       "price": string
       "priceInr": number