Buckets:
| import { Stripe } from "stripe" | |
| import { and, Database, eq, isNull, sql } from "./drizzle" | |
| import { | |
| BillingTable, | |
| CouponTable, | |
| CouponType, | |
| LiteTable, | |
| PaymentTable, | |
| SubscriptionTable, | |
| UsageTable, | |
| } from "./schema/billing.sql" | |
| import { Actor } from "./actor" | |
| import { fn } from "./util/fn" | |
| import { z } from "zod" | |
| import { Resource } from "@opencode-ai/console-resource" | |
| import { Identifier } from "./identifier" | |
| import { centsToMicroCents } from "./util/price" | |
| import { User } from "./user" | |
| import { BlackData } from "./black" | |
| import { LiteData } from "./lite" | |
| export namespace Billing { | |
| export const ITEM_CREDIT_NAME = "opencode credits" | |
| export const ITEM_FEE_NAME = "processing fee" | |
| export const RELOAD_AMOUNT = 20 | |
| export const RELOAD_AMOUNT_MIN = 10 | |
| export const RELOAD_TRIGGER = 5 | |
| export const RELOAD_TRIGGER_MIN = 5 | |
| export const stripe = () => | |
| new Stripe(Resource.STRIPE_SECRET_KEY.value, { | |
| apiVersion: "2025-03-31.basil", | |
| httpClient: Stripe.createFetchHttpClient(), | |
| }) | |
| export const get = async () => { | |
| return Database.use(async (tx) => | |
| tx | |
| .select() | |
| .from(BillingTable) | |
| .where(eq(BillingTable.workspaceID, Actor.workspace())) | |
| .then((r) => r[0]), | |
| ) | |
| } | |
| 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 (page = 0, pageSize = 50) => { | |
| return await Database.use((tx) => | |
| tx | |
| .select() | |
| .from(UsageTable) | |
| .where(eq(UsageTable.workspaceID, Actor.workspace())) | |
| .orderBy(sql`${UsageTable.timeCreated} DESC`) | |
| .limit(pageSize) | |
| .offset(page * pageSize), | |
| ) | |
| } | |
| export const calculateFeeInCents = (x: number) => { | |
| // math: x = total - (total * 0.044 + 0.30) | |
| // math: x = total * (1-0.044) - 0.30 | |
| // math: (x + 0.30) / 0.956 = total | |
| return Math.round(((x + 30) / 0.956) * 0.044 + 30) | |
| } | |
| export const reload = async () => { | |
| const billing = await Database.use((tx) => | |
| tx | |
| .select({ | |
| customerID: BillingTable.customerID, | |
| paymentMethodID: BillingTable.paymentMethodID, | |
| reloadAmount: BillingTable.reloadAmount, | |
| }) | |
| .from(BillingTable) | |
| .where(eq(BillingTable.workspaceID, Actor.workspace())) | |
| .then((rows) => rows[0]), | |
| ) | |
| const customerID = billing.customerID | |
| const paymentMethodID = billing.paymentMethodID | |
| const amountInCents = (billing.reloadAmount ?? Billing.RELOAD_AMOUNT) * 100 | |
| try { | |
| const draft = await Billing.stripe().invoices.create({ | |
| customer: customerID!, | |
| auto_advance: false, | |
| default_payment_method: paymentMethodID!, | |
| collection_method: "charge_automatically", | |
| currency: "usd", | |
| metadata: { | |
| workspaceID: Actor.workspace(), | |
| amount: amountInCents.toString(), | |
| }, | |
| }) | |
| await Billing.stripe().invoiceItems.create({ | |
| amount: amountInCents, | |
| currency: "usd", | |
| customer: customerID!, | |
| invoice: draft.id!, | |
| description: ITEM_CREDIT_NAME, | |
| }) | |
| await Billing.stripe().invoiceItems.create({ | |
| amount: calculateFeeInCents(amountInCents), | |
| currency: "usd", | |
| customer: customerID!, | |
| invoice: draft.id!, | |
| description: ITEM_FEE_NAME, | |
| }) | |
| await Billing.stripe().invoices.finalizeInvoice(draft.id!) | |
| await Billing.stripe().invoices.pay(draft.id!, { | |
| off_session: true, | |
| payment_method: paymentMethodID!, | |
| }) | |
| } catch (e: any) { | |
| console.error(e) | |
| await Database.use((tx) => | |
| tx | |
| .update(BillingTable) | |
| .set({ | |
| reload: false, | |
| reloadError: e.message ?? "Payment failed.", | |
| timeReloadError: sql`now()`, | |
| }) | |
| .where(eq(BillingTable.workspaceID, Actor.workspace())), | |
| ) | |
| return | |
| } | |
| } | |
| export const grantCredit = async (workspaceID: string, dollarAmount: number) => { | |
| const amountInMicroCents = centsToMicroCents(dollarAmount * 100) | |
| await Database.transaction(async (tx) => { | |
| await tx | |
| .update(BillingTable) | |
| .set({ | |
| balance: sql`${BillingTable.balance} + ${amountInMicroCents}`, | |
| }) | |
| .where(eq(BillingTable.workspaceID, workspaceID)) | |
| await tx.insert(PaymentTable).values({ | |
| workspaceID, | |
| id: Identifier.create("payment"), | |
| amount: amountInMicroCents, | |
| enrichment: { | |
| type: "credit", | |
| }, | |
| }) | |
| }) | |
| return amountInMicroCents | |
| } | |
| export const subtractLiteUsage = async (workspaceID: string, amountInMicroCents: number) => { | |
| await Database.transaction(async (tx) => { | |
| const lite = await tx | |
| .select({ id: LiteTable.id }) | |
| .from(LiteTable) | |
| .where(and(eq(LiteTable.workspaceID, workspaceID), isNull(LiteTable.timeDeleted))) | |
| .then((rows) => rows[0]) | |
| if (!lite) throw new Error("Subscribe to Go before applying referral rewards") | |
| await tx | |
| .update(LiteTable) | |
| .set({ | |
| monthlyUsage: sql`GREATEST(0, COALESCE(${LiteTable.monthlyUsage}, 0) - ${amountInMicroCents})`, | |
| weeklyUsage: sql`GREATEST(0, COALESCE(${LiteTable.weeklyUsage}, 0) - ${amountInMicroCents})`, | |
| rollingUsage: sql`GREATEST(0, COALESCE(${LiteTable.rollingUsage}, 0) - ${amountInMicroCents})`, | |
| }) | |
| .where(and(eq(LiteTable.workspaceID, workspaceID), isNull(LiteTable.timeDeleted))) | |
| }) | |
| } | |
| export const redeemCoupon = async (email: string, type: (typeof CouponType)[number]) => { | |
| // validate coupon type | |
| await (async () => { | |
| if (type === "GO1MONTH50") return | |
| const coupon = await Database.use((tx) => | |
| tx | |
| .select() | |
| .from(CouponTable) | |
| .where(and(eq(CouponTable.email, email), eq(CouponTable.type, type))) | |
| .then((rows) => rows[0]), | |
| ) | |
| if (!coupon) throw new Error("Invalid coupon code") | |
| if (coupon.timeRedeemed) throw new Error("Coupon already redeemed") | |
| })() | |
| // handle coupon type | |
| if (type === "BUILDATHON") await grantCredit(Actor.workspace(), 500) | |
| await Database.use((tx) => | |
| tx | |
| .insert(CouponTable) | |
| .values({ email, type, timeRedeemed: sql`now()` }) | |
| .onDuplicateKeyUpdate({ | |
| set: { | |
| timeRedeemed: sql`now()`, | |
| }, | |
| }), | |
| ) | |
| } | |
| export const setMonthlyLimit = fn(z.number(), async (input) => { | |
| return await Database.use((tx) => | |
| tx | |
| .update(BillingTable) | |
| .set({ | |
| monthlyLimit: input, | |
| }) | |
| .where(eq(BillingTable.workspaceID, Actor.workspace())), | |
| ) | |
| }) | |
| export const generateCheckoutUrl = fn( | |
| z.object({ | |
| successUrl: z.string(), | |
| cancelUrl: z.string(), | |
| amount: z.number().optional(), | |
| }), | |
| async (input) => { | |
| const user = Actor.assert("user") | |
| const { successUrl, cancelUrl, amount } = input | |
| if (amount !== undefined && amount < Billing.RELOAD_AMOUNT_MIN) { | |
| throw new Error(`Amount must be at least $${Billing.RELOAD_AMOUNT_MIN}`) | |
| } | |
| const email = await User.getAuthEmail(user.properties.userID) | |
| const customer = await Billing.get() | |
| const amountInCents = (amount ?? customer.reloadAmount ?? Billing.RELOAD_AMOUNT) * 100 | |
| const session = await Billing.stripe().checkout.sessions.create({ | |
| mode: "payment", | |
| billing_address_collection: "required", | |
| line_items: [ | |
| { | |
| price_data: { | |
| currency: "usd", | |
| product_data: { name: ITEM_CREDIT_NAME }, | |
| unit_amount: amountInCents, | |
| }, | |
| quantity: 1, | |
| }, | |
| { | |
| price_data: { | |
| currency: "usd", | |
| product_data: { name: ITEM_FEE_NAME }, | |
| unit_amount: calculateFeeInCents(amountInCents), | |
| }, | |
| quantity: 1, | |
| }, | |
| ], | |
| ...(customer.customerID | |
| ? { | |
| customer: customer.customerID, | |
| customer_update: { | |
| name: "auto", | |
| address: "auto", | |
| }, | |
| } | |
| : { | |
| customer_email: email!, | |
| customer_creation: "always", | |
| }), | |
| currency: "usd", | |
| invoice_creation: { | |
| enabled: true, | |
| }, | |
| payment_method_options: { | |
| card: { | |
| setup_future_usage: "off_session", | |
| }, | |
| link: { | |
| setup_future_usage: "off_session", | |
| }, | |
| }, | |
| //payment_method_data: { | |
| // allow_redisplay: "always", | |
| //}, | |
| tax_id_collection: { | |
| enabled: true, | |
| }, | |
| metadata: { | |
| workspaceID: Actor.workspace(), | |
| amount: amountInCents.toString(), | |
| }, | |
| success_url: successUrl, | |
| cancel_url: cancelUrl, | |
| }) | |
| return session.url | |
| }, | |
| ) | |
| export const generateLiteCheckoutUrl = fn( | |
| z.object({ | |
| successUrl: z.string(), | |
| cancelUrl: z.string(), | |
| method: z.enum(["alipay", "upi"]).optional(), | |
| }), | |
| async (input) => { | |
| const user = Actor.assert("user") | |
| const { successUrl, cancelUrl, method } = input | |
| const email = (await User.getAuthEmail(user.properties.userID))! | |
| const billing = await Billing.get() | |
| if (billing.subscriptionID) throw new Error("Already subscribed to Black") | |
| if (billing.liteSubscriptionID) throw new Error("Already subscribed to Lite") | |
| const coupons = await Database.use((tx) => | |
| tx | |
| .select({ type: CouponTable.type, timeRedeemed: CouponTable.timeRedeemed }) | |
| .from(CouponTable) | |
| .where(eq(CouponTable.email, email)), | |
| ) | |
| const coupon = (() => { | |
| if (coupons.some((coupon) => coupon.type === "GO12MONTHS100" && !coupon.timeRedeemed)) | |
| return LiteData.twelveMonths100Coupon | |
| if (coupons.some((coupon) => coupon.type === "GO6MONTHS100" && !coupon.timeRedeemed)) | |
| return LiteData.sixMonths100Coupon | |
| if (coupons.some((coupon) => coupon.type === "GO3MONTHS100" && !coupon.timeRedeemed)) | |
| return LiteData.threeMonths100Coupon | |
| if (coupons.some((coupon) => coupon.type === "GOFREEMONTH" && !coupon.timeRedeemed)) | |
| return LiteData.firstMonth100Coupon | |
| if (!coupons.some((coupon) => coupon.type === "GO1MONTH50")) return LiteData.firstMonth50Coupon | |
| return undefined | |
| })() | |
| const createSession = () => | |
| 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, | |
| }), | |
| ...(() => { | |
| if (method === "alipay") { | |
| return { | |
| line_items: [{ price: LiteData.priceID(), quantity: 1 }], | |
| payment_method_types: ["alipay"], | |
| adaptive_pricing: { | |
| enabled: false, | |
| }, | |
| } | |
| } | |
| if (method === "upi") { | |
| return { | |
| line_items: [ | |
| { | |
| price_data: { | |
| currency: "inr", | |
| product: LiteData.productID(), | |
| recurring: { | |
| interval: "month", | |
| interval_count: 1, | |
| }, | |
| unit_amount: LiteData.priceInr(), | |
| }, | |
| quantity: 1, | |
| }, | |
| ], | |
| payment_method_types: ["upi"] as any, | |
| adaptive_pricing: { | |
| enabled: false, | |
| }, | |
| } | |
| } | |
| return { | |
| line_items: [{ price: LiteData.priceID(), quantity: 1 }], | |
| billing_address_collection: "required", | |
| } | |
| })(), | |
| tax_id_collection: { | |
| enabled: true, | |
| }, | |
| success_url: successUrl, | |
| cancel_url: cancelUrl, | |
| subscription_data: { | |
| metadata: { | |
| workspaceID: Actor.workspace(), | |
| userID: user.properties.userID, | |
| userEmail: email, | |
| coupon, | |
| type: "lite", | |
| }, | |
| }, | |
| }) | |
| try { | |
| const session = await createSession() | |
| return session.url | |
| } catch (e: any) { | |
| if ( | |
| e.type !== "StripeInvalidRequestError" || | |
| !e.message.includes("You cannot combine currencies on a single customer") | |
| ) | |
| throw e | |
| // get pending payment intent | |
| const intents = await Billing.stripe().paymentIntents.search({ | |
| query: `-status:'canceled' AND -status:'processing' AND -status:'succeeded' AND customer:'${billing.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!, | |
| payment_intent: intent.id, | |
| }) | |
| // delete pending payment intent | |
| await Billing.stripe().checkout.sessions.expire(sessions.data[0].id) | |
| } | |
| const session = await createSession() | |
| return session.url | |
| } | |
| }, | |
| ) | |
| export const generateSessionUrl = 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 | |
| }, | |
| ) | |
| export const generateReceiptUrl = fn( | |
| z.object({ | |
| paymentID: z.string(), | |
| }), | |
| async (input) => { | |
| const { paymentID } = input | |
| const intent = await Billing.stripe().paymentIntents.retrieve(paymentID) | |
| if (!intent.latest_charge) throw new Error("No charge found") | |
| const charge = await Billing.stripe().charges.retrieve(intent.latest_charge as string) | |
| if (!charge.receipt_url) throw new Error("No receipt URL found") | |
| return charge.receipt_url | |
| }, | |
| ) | |
| export const subscribeBlack = 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 | |
| }, | |
| ) | |
| export const unsubscribeBlack = fn( | |
| z.object({ | |
| subscriptionID: z.string(), | |
| }), | |
| async ({ subscriptionID }) => { | |
| const workspaceID = await Database.use((tx) => | |
| tx | |
| .select({ workspaceID: BillingTable.workspaceID }) | |
| .from(BillingTable) | |
| .where(eq(BillingTable.subscriptionID, subscriptionID)) | |
| .then((rows) => rows[0]?.workspaceID), | |
| ) | |
| if (!workspaceID) throw new Error("Workspace ID not found for subscription") | |
| await Database.transaction(async (tx) => { | |
| await tx | |
| .update(BillingTable) | |
| .set({ subscriptionID: null, subscription: null }) | |
| .where(eq(BillingTable.workspaceID, workspaceID)) | |
| await tx.delete(SubscriptionTable).where(eq(SubscriptionTable.workspaceID, workspaceID)) | |
| }) | |
| }, | |
| ) | |
| export const unsubscribeLite = fn( | |
| z.object({ | |
| subscriptionID: z.string(), | |
| }), | |
| async ({ subscriptionID }) => { | |
| const workspaceID = await Database.use((tx) => | |
| tx | |
| .select({ workspaceID: BillingTable.workspaceID }) | |
| .from(BillingTable) | |
| .where(eq(BillingTable.liteSubscriptionID, subscriptionID)) | |
| .then((rows) => rows[0]?.workspaceID), | |
| ) | |
| if (!workspaceID) throw new Error("Workspace ID not found for subscription") | |
| await Database.transaction(async (tx) => { | |
| await tx | |
| .update(BillingTable) | |
| .set({ liteSubscriptionID: null, lite: null }) | |
| .where(eq(BillingTable.workspaceID, workspaceID)) | |
| await tx.delete(LiteTable).where(eq(LiteTable.workspaceID, workspaceID)) | |
| }) | |
| }, | |
| ) | |
| } | |
Xet Storage Details
- Size:
- 18.8 kB
- Xet hash:
- c5814d055b761ccf406186fa45466e86c9bcdacbb49152f88404438bd2845358
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.