| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173 |
- import { Billing } from "../src/billing.js"
- import { and, Database, eq, isNull, sql } from "../src/drizzle/index.js"
- import { UserTable } from "../src/schema/user.sql.js"
- import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
- import { Identifier } from "../src/identifier.js"
- import { centsToMicroCents } from "../src/util/price.js"
- import { AuthTable } from "../src/schema/auth.sql.js"
- const workspaceID = process.argv[2]
- const email = process.argv[3]
- console.log(`Onboarding workspace ${workspaceID} for email ${email}`)
- if (!workspaceID || !email) {
- console.error("Usage: bun foo.ts <workspaceID> <email>")
- process.exit(1)
- }
- // Look up the Stripe customer by email
- const customers = await Billing.stripe().customers.list({ email, limit: 10, expand: ["data.subscriptions"] })
- if (!customers.data) {
- console.error(`Error: No Stripe customer found for email ${email}`)
- process.exit(1)
- }
- const customer = customers.data.find((c) => c.subscriptions?.data[0]?.items.data[0]?.price.unit_amount === 20000)
- if (!customer) {
- console.error(`Error: No Stripe customer found for email ${email} with $200 subscription`)
- process.exit(1)
- }
- const customerID = customer.id
- const subscription = customer.subscriptions!.data[0]
- const subscriptionID = subscription.id
- // Validate the subscription is $200
- const amountInCents = subscription.items.data[0]?.price.unit_amount ?? 0
- if (amountInCents !== 20000) {
- console.error(`Error: Subscription amount is $${amountInCents / 100}, expected $200`)
- process.exit(1)
- }
- const subscriptionData = await Billing.stripe().subscriptions.retrieve(subscription.id, { expand: ["discounts"] })
- const couponID =
- typeof subscriptionData.discounts[0] === "string"
- ? subscriptionData.discounts[0]
- : subscriptionData.discounts[0]?.coupon?.id
- // Check if subscription is already tied to another workspace
- const existingSubscription = await Database.use((tx) =>
- tx
- .select({ workspaceID: BillingTable.workspaceID })
- .from(BillingTable)
- .where(sql`JSON_EXTRACT(${BillingTable.subscription}, '$.id') = ${subscriptionID}`)
- .then((rows) => rows[0]),
- )
- if (existingSubscription) {
- console.error(
- `Error: Subscription ${subscriptionID} is already tied to workspace ${existingSubscription.workspaceID}`,
- )
- process.exit(1)
- }
- // Look up the workspace billing and check if it already has a customer id or subscription
- const billing = await Database.use((tx) =>
- tx
- .select({ customerID: BillingTable.customerID, subscriptionID: BillingTable.subscriptionID })
- .from(BillingTable)
- .where(eq(BillingTable.workspaceID, workspaceID))
- .then((rows) => rows[0]),
- )
- if (billing?.subscriptionID) {
- console.error(`Error: Workspace ${workspaceID} already has a subscription: ${billing.subscriptionID}`)
- process.exit(1)
- }
- if (billing?.customerID) {
- console.warn(
- `Warning: Workspace ${workspaceID} already has a customer id: ${billing.customerID}, replacing with ${customerID}`,
- )
- }
- // Get the latest invoice and payment from the subscription
- const invoices = await Billing.stripe().invoices.list({
- subscription: subscriptionID,
- limit: 1,
- expand: ["data.payments"],
- })
- const invoice = invoices.data[0]
- const invoiceID = invoice?.id
- const paymentID = invoice?.payments?.data[0]?.payment.payment_intent as string | undefined
- // Get the default payment method from the customer
- const paymentMethodID = (customer.invoice_settings.default_payment_method ?? subscription.default_payment_method) as
- | string
- | null
- const paymentMethod = paymentMethodID ? await Billing.stripe().paymentMethods.retrieve(paymentMethodID) : null
- const paymentMethodLast4 = paymentMethod?.card?.last4 ?? null
- const paymentMethodType = paymentMethod?.type ?? null
- // Look up the user in the workspace
- const users = await Database.use((tx) =>
- tx
- .select({ id: UserTable.id, email: AuthTable.subject })
- .from(UserTable)
- .innerJoin(AuthTable, and(eq(AuthTable.accountID, UserTable.accountID), eq(AuthTable.provider, "email")))
- .where(and(eq(UserTable.workspaceID, workspaceID), isNull(UserTable.timeDeleted))),
- )
- if (users.length === 0) {
- console.error(`Error: No users found in workspace ${workspaceID}`)
- process.exit(1)
- }
- const user = users.length === 1 ? users[0] : users.find((u) => u.email === email)
- if (!user) {
- console.error(`Error: User with email ${email} not found in workspace ${workspaceID}`)
- process.exit(1)
- }
- // Set workspaceID in Stripe customer metadata
- await Billing.stripe().customers.update(customerID, {
- metadata: {
- workspaceID,
- },
- })
- await Database.transaction(async (tx) => {
- // Set customer id, subscription id, and payment method on workspace billing
- await tx
- .update(BillingTable)
- .set({
- customerID,
- subscriptionID,
- paymentMethodID,
- paymentMethodLast4,
- paymentMethodType,
- subscription: {
- status: "subscribed",
- coupon: couponID,
- seats: 1,
- plan: "200",
- },
- })
- .where(eq(BillingTable.workspaceID, workspaceID))
- // Create a row in subscription table
- await tx.insert(SubscriptionTable).values({
- workspaceID,
- id: Identifier.create("subscription"),
- userID: user.id,
- })
- // Create a row in payments table
- await tx.insert(PaymentTable).values({
- workspaceID,
- id: Identifier.create("payment"),
- amount: centsToMicroCents(amountInCents),
- customerID,
- invoiceID,
- paymentID,
- enrichment: {
- type: "subscription",
- couponID,
- },
- })
- })
- console.log(`Successfully onboarded workspace ${workspaceID}`)
- console.log(` Customer ID: ${customerID}`)
- console.log(` Subscription ID: ${subscriptionID}`)
- console.log(
- ` Payment Method: ${paymentMethodID ?? "(none)"} (${paymentMethodType ?? "unknown"} ending in ${paymentMethodLast4 ?? "????"})`,
- )
- console.log(` User ID: ${user.id}`)
- console.log(` Invoice ID: ${invoiceID ?? "(none)"}`)
- console.log(` Payment ID: ${paymentID ?? "(none)"}`)
|