summaryrefslogtreecommitdiffhomepage
path: root/cloud/core/src/billing.ts
diff options
context:
space:
mode:
authorFrank <[email protected]>2025-08-28 16:44:55 -0400
committerFrank <[email protected]>2025-08-28 16:44:55 -0400
commitc6ef92634d0ae026a59e023e69847b481975462b (patch)
treec88bfbbabf1f46ca922e3212dd929b34d6229a1a /cloud/core/src/billing.ts
parentf97fdceb01c69ca563e755c6d50312ef7352f663 (diff)
downloadopencode-c6ef92634d0ae026a59e023e69847b481975462b.tar.gz
opencode-c6ef92634d0ae026a59e023e69847b481975462b.zip
wip cloud
Diffstat (limited to 'cloud/core/src/billing.ts')
-rw-r--r--cloud/core/src/billing.ts93
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
+ },
+ )
}