diff options
| author | Frank <[email protected]> | 2026-01-22 16:59:32 -0500 |
|---|---|---|
| committer | Frank <[email protected]> | 2026-01-22 17:02:46 -0500 |
| commit | 5f3ab9395fc8f8543724dcda914d38fba0809049 (patch) | |
| tree | 3be83aa5cc4e4556b62f9ff6b23933f8699eea63 /packages/console/core/src | |
| parent | fdac21688c9acc4087b83e71cb7e0fd1d2d57f00 (diff) | |
| download | opencode-5f3ab9395fc8f8543724dcda914d38fba0809049.tar.gz opencode-5f3ab9395fc8f8543724dcda914d38fba0809049.zip | |
wip: zen black
Diffstat (limited to 'packages/console/core/src')
| -rw-r--r-- | packages/console/core/src/billing.ts | 65 | ||||
| -rw-r--r-- | packages/console/core/src/black.ts | 33 | ||||
| -rw-r--r-- | packages/console/core/src/schema/billing.sql.ts | 1 |
3 files changed, 88 insertions, 11 deletions
diff --git a/packages/console/core/src/billing.ts b/packages/console/core/src/billing.ts index 36e8a76b7..373f0c595 100644 --- a/packages/console/core/src/billing.ts +++ b/packages/console/core/src/billing.ts @@ -1,6 +1,6 @@ import { Stripe } from "stripe" import { Database, eq, sql } from "./drizzle" -import { BillingTable, PaymentTable, UsageTable } from "./schema/billing.sql" +import { BillingTable, PaymentTable, SubscriptionTable, UsageTable } from "./schema/billing.sql" import { Actor } from "./actor" import { fn } from "./util/fn" import { z } from "zod" @@ -8,6 +8,7 @@ import { Resource } from "@opencode-ai/console-resource" import { Identifier } from "./identifier" import { centsToMicroCents } from "./util/price" import { User } from "./user" +import { BlackData } from "./black" export namespace Billing { export const ITEM_CREDIT_NAME = "opencode credits" @@ -288,4 +289,66 @@ export namespace Billing { return charge.receipt_url }, ) + + export const subscribe = fn(z.object({ + seats: z.number(), + coupon: z.string().optional(), + }), async ({ seats, coupon }) => { + const user = Actor.assert("user") + const billing = await Database.use((tx) => + tx + .select({ + customerID: BillingTable.customerID, + paymentMethodID: BillingTable.paymentMethodID, + subscriptionID: BillingTable.subscriptionID, + subscriptionPlan: BillingTable.subscriptionPlan, + timeSubscriptionSelected: BillingTable.timeSubscriptionSelected, + }) + .from(BillingTable) + .where(eq(BillingTable.workspaceID, Actor.workspace())) + .then((rows) => rows[0]), + ) + + if (!billing) throw new Error("Billing record not found") + if (!billing.timeSubscriptionSelected) throw new Error("Not selected for subscription") + if (billing.subscriptionID) throw new Error("Already subscribed") + if (!billing.customerID) throw new Error("No customer ID") + if (!billing.paymentMethodID) throw new Error("No payment method") + if (!billing.subscriptionPlan) throw new Error("No subscription plan") + + const subscription = await Billing.stripe().subscriptions.create({ + customer: billing.customerID, + default_payment_method: billing.paymentMethodID, + items: [{ price: BlackData.planToPriceID({ plan: billing.subscriptionPlan }) }], + metadata: { + workspaceID: Actor.workspace(), + }, + }) + + await Database.transaction(async (tx) => { + await tx + .update(BillingTable) + .set({ + subscriptionID: subscription.id, + subscription: { + status: "subscribed", + coupon, + seats, + plan: billing.subscriptionPlan!, + }, + subscriptionPlan: null, + timeSubscriptionBooked: null, + timeSubscriptionSelected: null, + }) + .where(eq(BillingTable.workspaceID, Actor.workspace())) + + await tx.insert(SubscriptionTable).values({ + workspaceID: Actor.workspace(), + id: Identifier.create("subscription"), + userID: user.properties.userID, + }) + }) + + return subscription.id + }) } diff --git a/packages/console/core/src/black.ts b/packages/console/core/src/black.ts index 13314a62d..93134de55 100644 --- a/packages/console/core/src/black.ts +++ b/packages/console/core/src/black.ts @@ -28,15 +28,28 @@ export namespace BlackData { return input }) - export const get = fn( - z.object({ + export const getLimits = fn(z.object({ plan: z.enum(SubscriptionPlan), - }), - ({ plan }) => { - const json = JSON.parse(Resource.ZEN_BLACK_LIMITS.value) - return Schema.parse(json)[plan] - }, - ) + }), ({ plan }) => { + const json = JSON.parse(Resource.ZEN_BLACK_LIMITS.value) + return Schema.parse(json)[plan] + }) + + export const planToPriceID = fn(z.object({ + plan: z.enum(SubscriptionPlan), + }), ({ plan }) => { + if (plan === "200") return Resource.ZEN_BLACK_PRICE.plan200 + if (plan === "100") return Resource.ZEN_BLACK_PRICE.plan100 + return Resource.ZEN_BLACK_PRICE.plan20 + }) + + export const priceIDToPlan = fn(z.object({ + priceID: z.string(), + }), ({ priceID }) => { + if (priceID === Resource.ZEN_BLACK_PRICE.plan200) return "200" + if (priceID === Resource.ZEN_BLACK_PRICE.plan100) return "100" + return "20" + }) } export namespace Black { @@ -48,7 +61,7 @@ export namespace Black { }), ({ plan, usage, timeUpdated }) => { const now = new Date() - const black = BlackData.get({ plan }) + const black = BlackData.getLimits({ plan }) const rollingWindowMs = black.rollingWindow * 3600 * 1000 const rollingLimitInMicroCents = centsToMicroCents(black.rollingLimit * 100) const windowStart = new Date(now.getTime() - rollingWindowMs) @@ -83,7 +96,7 @@ export namespace Black { timeUpdated: z.date(), }), ({ plan, usage, timeUpdated }) => { - const black = BlackData.get({ plan }) + const black = BlackData.getLimits({ plan }) const now = new Date() const week = getWeekBounds(now) const fixedLimitInMicroCents = centsToMicroCents(black.fixedLimit * 100) diff --git a/packages/console/core/src/schema/billing.sql.ts b/packages/console/core/src/schema/billing.sql.ts index ae32ed5ce..6f5c55850 100644 --- a/packages/console/core/src/schema/billing.sql.ts +++ b/packages/console/core/src/schema/billing.sql.ts @@ -31,6 +31,7 @@ export const BillingTable = mysqlTable( subscriptionID: varchar("subscription_id", { length: 28 }), subscriptionPlan: mysqlEnum("subscription_plan", SubscriptionPlan), timeSubscriptionBooked: utc("time_subscription_booked"), + timeSubscriptionSelected: utc("time_subscription_selected"), }, (table) => [ ...workspaceIndexes(table), |
