Compare commits

...

1 Commits

Author SHA1 Message Date
Jack Jiang c4219eb9a7 fix(console): reuse workspace Stripe customers 2026-08-05 18:15:26 +00:00
2 changed files with 50 additions and 45 deletions
@@ -87,24 +87,7 @@ const createSetupIntent = async (input: { plan: string; workspaceID: string }) =
return { error: formError.alreadySubscribed }
}
let customerID = customer?.customerID
if (!customerID) {
const customer = await Billing.stripe().customers.create({
email,
metadata: {
workspaceID,
},
})
customerID = customer.id
await Database.use((tx) =>
tx
.update(BillingTable)
.set({
customerID,
})
.where(eq(BillingTable.workspaceID, workspaceID)),
)
}
const customerID = await Billing.ensureCustomer(email)
const intent = await Billing.stripe().setupIntents.create({
customer: customerID,
+49 -27
View File
@@ -42,6 +42,39 @@ export namespace Billing {
)
}
export const ensureCustomer = async (email?: string) => {
const billing = await get()
if (billing?.customerID) return billing.customerID
const workspaceID = Actor.workspace()
const stripe = Billing.stripe()
const created = await stripe.customers.create(
{
metadata: {
workspaceID,
},
},
{
idempotencyKey: `opencode-workspace-customer:${workspaceID}`,
},
)
await Database.use((tx) =>
tx
.update(BillingTable)
.set({
customerID: created.id,
})
.where(and(eq(BillingTable.workspaceID, workspaceID), isNull(BillingTable.customerID))),
)
const customerID = (await get())?.customerID
if (!customerID) throw new Error(`Workspace with ID ${workspaceID} not found`)
if (customerID === created.id && email) {
await stripe.customers.update(customerID, { email })
}
return customerID
}
export const payments = async () => {
return await Database.use((tx) =>
tx
@@ -231,8 +264,9 @@ export namespace Billing {
}
const email = await User.getAuthEmail(user.properties.userID)
const customer = await Billing.get()
const amountInCents = (amount ?? customer.reloadAmount ?? Billing.RELOAD_AMOUNT) * 100
const billing = await Billing.get()
const customerID = await Billing.ensureCustomer(email ?? undefined)
const amountInCents = (amount ?? billing.reloadAmount ?? Billing.RELOAD_AMOUNT) * 100
const session = await Billing.stripe().checkout.sessions.create({
mode: "payment",
billing_address_collection: "required",
@@ -254,18 +288,11 @@ export namespace Billing {
quantity: 1,
},
],
...(customer.customerID
? {
customer: customer.customerID,
customer_update: {
name: "auto",
address: "auto",
},
}
: {
customer_email: email!,
customer_creation: "always",
}),
customer: customerID,
customer_update: {
name: "auto",
address: "auto",
},
currency: "usd",
invoice_creation: {
enabled: true,
@@ -311,6 +338,7 @@ export namespace Billing {
if (billing.subscriptionID) throw new Error("Already subscribed to Black")
if (billing.liteSubscriptionID) throw new Error("Already subscribed to Lite")
const customerID = await Billing.ensureCustomer(email)
const coupons = await Database.use((tx) =>
tx
@@ -335,17 +363,11 @@ export namespace Billing {
Billing.stripe().checkout.sessions.create({
mode: "subscription",
discounts: coupon ? [{ coupon }] : undefined,
...(billing.customerID
? {
customer: billing.customerID,
customer_update: {
name: "auto",
address: "auto",
},
}
: {
customer_email: email,
}),
customer: customerID,
customer_update: {
name: "auto",
address: "auto",
},
...(() => {
if (method === "alipay") {
return {
@@ -411,14 +433,14 @@ export namespace Billing {
// get pending payment intent
const intents = await Billing.stripe().paymentIntents.search({
query: `-status:'canceled' AND -status:'processing' AND -status:'succeeded' AND customer:'${billing.customerID}'`,
query: `-status:'canceled' AND -status:'processing' AND -status:'succeeded' AND customer:'${customerID}'`,
})
if (intents.data.length === 0) throw e
for (const intent of intents.data) {
// get checkout session
const sessions = await Billing.stripe().checkout.sessions.list({
customer: billing.customerID!,
customer: customerID,
payment_intent: intent.id,
})