Skip to content

Commit 01c5eb6

Browse files
committed
go: support coupon
1 parent 41612b3 commit 01c5eb6

10 files changed

Lines changed: 53 additions & 27 deletions

File tree

infra/console.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,12 @@ const zenLiteCouponFirstMonth50 = new stripe.Coupon("ZenLiteCouponFirstMonth50",
109109
appliesToProducts: [zenLiteProduct.id],
110110
duration: "once",
111111
})
112+
const zenLiteCouponFirstMonth100 = new stripe.Coupon("ZenLiteCouponFirstMonth100", {
113+
name: "First month 100% off",
114+
percentOff: 100,
115+
appliesToProducts: [zenLiteProduct.id],
116+
duration: "once",
117+
})
112118
const zenLitePrice = new stripe.Price("ZenLitePrice", {
113119
product: zenLiteProduct.id,
114120
currency: "usd",
@@ -124,6 +130,7 @@ const ZEN_LITE_PRICE = new sst.Linkable("ZEN_LITE_PRICE", {
124130
price: zenLitePrice.id,
125131
priceInr: 92900,
126132
firstMonth50Coupon: zenLiteCouponFirstMonth50.id,
133+
firstMonth100Coupon: zenLiteCouponFirstMonth100.id,
127134
},
128135
})
129136

@@ -229,6 +236,7 @@ new sst.cloudflare.x.SolidStart("Console", {
229236
SALESFORCE_INSTANCE_URL,
230237
ZEN_BLACK_PRICE,
231238
ZEN_LITE_PRICE,
239+
new sst.Secret("ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES"),
232240
new sst.Secret("ZEN_LIMITS"),
233241
new sst.Secret("ZEN_SESSION_SECRET"),
234242
...ZEN_MODELS,

packages/console/app/src/routes/stripe/webhook.ts

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import type { Stripe } from "stripe"
12
import { Billing } from "@opencode-ai/console-core/billing.js"
23
import type { APIEvent } from "@solidjs/start/server"
34
import { and, Database, eq, sql } from "@opencode-ai/console-core/drizzle/index.js"
@@ -111,27 +112,17 @@ export async function POST(input: APIEvent) {
111112
const customerID = body.data.object.customer as string
112113
const invoiceID = body.data.object.latest_invoice as string
113114
const subscriptionID = body.data.object.id as string
115+
const paymentMethodID = body.data.object.default_payment_method as string
114116

115117
if (!workspaceID) throw new Error("Workspace ID not found")
116118
if (!userID) throw new Error("User ID not found")
117119
if (!customerID) throw new Error("Customer ID not found")
118120
if (!invoiceID) throw new Error("Invoice ID not found")
119121
if (!subscriptionID) throw new Error("Subscription ID not found")
120-
121-
// get payment id from invoice
122-
const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
123-
expand: ["payments"],
124-
})
125-
const paymentID = invoice.payments?.data[0].payment.payment_intent as string
126-
if (!paymentID) throw new Error("Payment ID not found")
122+
if (!paymentMethodID) throw new Error("Payment method ID not found")
127123

128124
// get payment method for the payment intent
129-
const paymentIntent = await Billing.stripe().paymentIntents.retrieve(paymentID, {
130-
expand: ["payment_method"],
131-
})
132-
const paymentMethod = paymentIntent.payment_method
133-
if (!paymentMethod || typeof paymentMethod === "string") throw new Error("Payment method not expanded")
134-
125+
const paymentMethod = await Billing.stripe().paymentMethods.retrieve(paymentMethodID)
135126
await Actor.provide("system", { workspaceID }, async () => {
136127
// look up current billing
137128
const billing = await Billing.get()
@@ -200,26 +191,18 @@ export async function POST(input: APIEvent) {
200191
const amountInCents = body.data.object.amount_paid
201192
const customerID = body.data.object.customer as string
202193
const subscriptionID = body.data.object.parent?.subscription_details?.subscription as string
194+
const productID = body.data.object.lines?.data[0].pricing?.price_details?.product as string
203195

204196
if (!customerID) throw new Error("Customer ID not found")
205197
if (!invoiceID) throw new Error("Invoice ID not found")
206198
if (!subscriptionID) throw new Error("Subscription ID not found")
207199

208200
// get coupon id from subscription
209-
const subscriptionData = await Billing.stripe().subscriptions.retrieve(subscriptionID, {
210-
expand: ["discounts"],
211-
})
212-
const couponID =
213-
typeof subscriptionData.discounts[0] === "string"
214-
? subscriptionData.discounts[0]
215-
: subscriptionData.discounts[0]?.coupon?.id
216-
const productID = subscriptionData.items.data[0].price.product as string
217-
218-
// get payment id from invoice
219201
const invoice = await Billing.stripe().invoices.retrieve(invoiceID, {
220-
expand: ["payments"],
202+
expand: ["discounts", "payments"],
221203
})
222-
const paymentID = invoice.payments?.data[0].payment.payment_intent as string
204+
const paymentID = invoice.payments?.data[0]?.payment.payment_intent as string
205+
const couponID = (invoice.discounts[0] as Stripe.Discount).coupon?.id as string
223206
if (!paymentID) {
224207
// payment id can be undefined when using coupon
225208
if (!couponID) throw new Error("Payment ID not found")

packages/console/core/src/billing.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -254,7 +254,7 @@ export namespace Billing {
254254
const createSession = () =>
255255
Billing.stripe().checkout.sessions.create({
256256
mode: "subscription",
257-
discounts: [{ coupon: LiteData.firstMonth50Coupon() }],
257+
discounts: [{ coupon: LiteData.firstMonthCoupon(email!) }],
258258
...(billing.customerID
259259
? {
260260
customer: billing.customerID,

packages/console/core/src/lite.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,11 @@ export namespace LiteData {
1111
export const productID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.product)
1212
export const priceID = fn(z.void(), () => Resource.ZEN_LITE_PRICE.price)
1313
export const priceInr = fn(z.void(), () => Resource.ZEN_LITE_PRICE.priceInr)
14-
export const firstMonth50Coupon = fn(z.void(), () => Resource.ZEN_LITE_PRICE.firstMonth50Coupon)
14+
export const firstMonthCoupon = fn(z.string(), (email) => {
15+
const invitees = Resource.ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES.value.split(",")
16+
return invitees.includes(email)
17+
? Resource.ZEN_LITE_PRICE.firstMonth100Coupon
18+
: Resource.ZEN_LITE_PRICE.firstMonth50Coupon
19+
})
1520
export const planName = fn(z.void(), () => "lite")
1621
}

packages/console/core/sst-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,12 @@ declare module "sst" {
142142
"type": "sst.sst.Secret"
143143
"value": string
144144
}
145+
"ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
146+
"type": "sst.sst.Secret"
147+
"value": string
148+
}
145149
"ZEN_LITE_PRICE": {
150+
"firstMonth100Coupon": string
146151
"firstMonth50Coupon": string
147152
"price": string
148153
"priceInr": number

packages/console/function/sst-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,12 @@ declare module "sst" {
142142
"type": "sst.sst.Secret"
143143
"value": string
144144
}
145+
"ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
146+
"type": "sst.sst.Secret"
147+
"value": string
148+
}
145149
"ZEN_LITE_PRICE": {
150+
"firstMonth100Coupon": string
146151
"firstMonth50Coupon": string
147152
"price": string
148153
"priceInr": number

packages/console/resource/sst-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,12 @@ declare module "sst" {
142142
"type": "sst.sst.Secret"
143143
"value": string
144144
}
145+
"ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
146+
"type": "sst.sst.Secret"
147+
"value": string
148+
}
145149
"ZEN_LITE_PRICE": {
150+
"firstMonth100Coupon": string
146151
"firstMonth50Coupon": string
147152
"price": string
148153
"priceInr": number

packages/enterprise/sst-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,12 @@ declare module "sst" {
142142
"type": "sst.sst.Secret"
143143
"value": string
144144
}
145+
"ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
146+
"type": "sst.sst.Secret"
147+
"value": string
148+
}
145149
"ZEN_LITE_PRICE": {
150+
"firstMonth100Coupon": string
146151
"firstMonth50Coupon": string
147152
"price": string
148153
"priceInr": number

packages/function/sst-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,7 +142,12 @@ declare module "sst" {
142142
"type": "sst.sst.Secret"
143143
"value": string
144144
}
145+
"ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
146+
"type": "sst.sst.Secret"
147+
"value": string
148+
}
145149
"ZEN_LITE_PRICE": {
150+
"firstMonth100Coupon": string
146151
"firstMonth50Coupon": string
147152
"price": string
148153
"priceInr": number

sst-env.d.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,7 +168,12 @@ declare module "sst" {
168168
"type": "sst.sst.Secret"
169169
"value": string
170170
}
171+
"ZEN_LITE_COUPON_FIRST_MONTH_100_INVITEES": {
172+
"type": "sst.sst.Secret"
173+
"value": string
174+
}
171175
"ZEN_LITE_PRICE": {
176+
"firstMonth100Coupon": string
172177
"firstMonth50Coupon": string
173178
"price": string
174179
"priceInr": number

0 commit comments

Comments
 (0)