diff options
| author | Frank <[email protected]> | 2025-08-28 16:44:55 -0400 |
|---|---|---|
| committer | Frank <[email protected]> | 2025-08-28 16:44:55 -0400 |
| commit | c6ef92634d0ae026a59e023e69847b481975462b (patch) | |
| tree | c88bfbbabf1f46ca922e3212dd929b34d6229a1a /cloud/core/src/billing.ts | |
| parent | f97fdceb01c69ca563e755c6d50312ef7352f663 (diff) | |
| download | opencode-c6ef92634d0ae026a59e023e69847b481975462b.tar.gz opencode-c6ef92634d0ae026a59e023e69847b481975462b.zip | |
wip cloud
Diffstat (limited to 'cloud/core/src/billing.ts')
| -rw-r--r-- | cloud/core/src/billing.ts | 93 |
1 files changed, 92 insertions, 1 deletions
diff --git a/cloud/core/src/billing.ts b/cloud/core/src/billing.ts index 1a7bb2946..94ba23b83 100644 --- a/cloud/core/src/billing.ts +++ b/cloud/core/src/billing.ts @@ -1,12 +1,13 @@ import { Resource } from "sst" import { Stripe } from "stripe" import { Database, eq, sql } from "./drizzle" -import { BillingTable, UsageTable } from "./schema/billing.sql" +import { BillingTable, PaymentTable, UsageTable } from "./schema/billing.sql" import { Actor } from "./actor" import { fn } from "./util/fn" import { z } from "zod" import { Identifier } from "./identifier" import { centsToMicroCents } from "./util/price" +import { User } from "./user" export namespace Billing { export const stripe = () => @@ -29,6 +30,28 @@ export namespace Billing { ) } + export const payments = async () => { + return await Database.use((tx) => + tx + .select() + .from(PaymentTable) + .where(eq(PaymentTable.workspaceID, Actor.workspace())) + .orderBy(sql`${PaymentTable.timeCreated} DESC`) + .limit(100), + ) + } + + export const usages = async () => { + return await Database.use((tx) => + tx + .select() + .from(UsageTable) + .where(eq(UsageTable.workspaceID, Actor.workspace())) + .orderBy(sql`${UsageTable.timeCreated} DESC`) + .limit(100), + ) + } + export const consume = fn( z.object({ requestID: z.string().optional(), @@ -68,4 +91,72 @@ export namespace Billing { }) }, ) + + export const generateCheckoutUrl = fn( + z.object({ + successUrl: z.string(), + cancelUrl: z.string(), + }), + async (input) => { + const account = Actor.assert("user") + const { successUrl, cancelUrl } = input + + const user = await User.fromID(account.properties.userID) + const customer = await Billing.get() + const session = await Billing.stripe().checkout.sessions.create({ + mode: "payment", + line_items: [ + { + price_data: { + currency: "usd", + product_data: { + name: "opencode credits", + }, + unit_amount: 2000, // $20 minimum + }, + quantity: 1, + }, + ], + payment_intent_data: { + setup_future_usage: "on_session", + }, + ...(customer.customerID + ? { customer: customer.customerID } + : { + customer_email: user.email, + customer_creation: "always", + }), + metadata: { + workspaceID: Actor.workspace(), + }, + currency: "usd", + payment_method_types: ["card"], + success_url: successUrl, + cancel_url: cancelUrl, + }) + + return session.url + }, + ) + + export const generatePortalUrl = fn( + z.object({ + returnUrl: z.string(), + }), + async (input) => { + const { returnUrl } = input + + const customer = await Billing.get() + if (!customer?.customerID) { + throw new Error("No stripe customer ID") + } + + const session = await Billing.stripe().billingPortal.sessions.create({ + customer: customer.customerID, + return_url: returnUrl, + }) + + return session.url + }, + ) } |
