onboard-zen-black.ts 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. import { Billing } from "../src/billing.js"
  2. import { Database, eq, and, sql } from "../src/drizzle/index.js"
  3. import { AuthTable } from "../src/schema/auth.sql.js"
  4. import { UserTable } from "../src/schema/user.sql.js"
  5. import { BillingTable, PaymentTable } from "../src/schema/billing.sql.js"
  6. import { Identifier } from "../src/identifier.js"
  7. import { centsToMicroCents } from "../src/util/price.js"
  8. const workspaceID = process.argv[2]
  9. const email = process.argv[3]
  10. if (!workspaceID || !email) {
  11. console.error("Usage: bun onboard-zen-black.ts <workspaceID> <email>")
  12. process.exit(1)
  13. }
  14. // Look up the Stripe customer by email
  15. const customers = await Billing.stripe().customers.list({ email, limit: 1 })
  16. const customer = customers.data[0]
  17. if (!customer) {
  18. console.error(`Error: No Stripe customer found for email ${email}`)
  19. process.exit(1)
  20. }
  21. const customerID = customer.id
  22. // Get the subscription id
  23. const subscriptions = await Billing.stripe().subscriptions.list({ customer: customerID, limit: 1 })
  24. const subscription = subscriptions.data[0]
  25. if (!subscription) {
  26. console.error(`Error: Customer ${customerID} does not have a subscription`)
  27. process.exit(1)
  28. }
  29. const subscriptionID = subscription.id
  30. // Validate the subscription is $200
  31. const amountInCents = subscription.items.data[0]?.price.unit_amount ?? 0
  32. if (amountInCents !== 20000) {
  33. console.error(`Error: Subscription amount is $${amountInCents / 100}, expected $200`)
  34. process.exit(1)
  35. }
  36. // Check if subscription is already tied to another workspace
  37. const existingSubscription = await Database.use((tx) =>
  38. tx
  39. .select({ workspaceID: BillingTable.workspaceID })
  40. .from(BillingTable)
  41. .where(eq(BillingTable.subscriptionID, subscriptionID))
  42. .then((rows) => rows[0]),
  43. )
  44. if (existingSubscription) {
  45. console.error(
  46. `Error: Subscription ${subscriptionID} is already tied to workspace ${existingSubscription.workspaceID}`,
  47. )
  48. process.exit(1)
  49. }
  50. // Look up the workspace billing and check if it already has a customer id or subscription
  51. const billing = await Database.use((tx) =>
  52. tx
  53. .select({ customerID: BillingTable.customerID, subscriptionID: BillingTable.subscriptionID })
  54. .from(BillingTable)
  55. .where(eq(BillingTable.workspaceID, workspaceID))
  56. .then((rows) => rows[0]),
  57. )
  58. if (billing?.subscriptionID) {
  59. console.error(`Error: Workspace ${workspaceID} already has a subscription: ${billing.subscriptionID}`)
  60. process.exit(1)
  61. }
  62. if (billing?.customerID) {
  63. console.warn(
  64. `Warning: Workspace ${workspaceID} already has a customer id: ${billing.customerID}, replacing with ${customerID}`,
  65. )
  66. }
  67. // Get the latest invoice and payment from the subscription
  68. const invoices = await Billing.stripe().invoices.list({
  69. subscription: subscriptionID,
  70. limit: 1,
  71. expand: ["data.payments"],
  72. })
  73. const invoice = invoices.data[0]
  74. const invoiceID = invoice?.id
  75. const paymentID = invoice?.payments?.data[0]?.payment.payment_intent as string | undefined
  76. // Get the default payment method from the customer
  77. const paymentMethodID = (customer.invoice_settings.default_payment_method ?? subscription.default_payment_method) as
  78. | string
  79. | null
  80. const paymentMethod = paymentMethodID ? await Billing.stripe().paymentMethods.retrieve(paymentMethodID) : null
  81. const paymentMethodLast4 = paymentMethod?.card?.last4 ?? null
  82. const paymentMethodType = paymentMethod?.type ?? null
  83. // Look up the user by email via AuthTable
  84. const auth = await Database.use((tx) =>
  85. tx
  86. .select({ accountID: AuthTable.accountID })
  87. .from(AuthTable)
  88. .where(and(eq(AuthTable.provider, "email"), eq(AuthTable.subject, email)))
  89. .then((rows) => rows[0]),
  90. )
  91. if (!auth) {
  92. console.error(`Error: No user found with email ${email}`)
  93. process.exit(1)
  94. }
  95. // Look up the user in the workspace
  96. const user = await Database.use((tx) =>
  97. tx
  98. .select({ id: UserTable.id })
  99. .from(UserTable)
  100. .where(and(eq(UserTable.workspaceID, workspaceID), eq(UserTable.accountID, auth.accountID)))
  101. .then((rows) => rows[0]),
  102. )
  103. if (!user) {
  104. console.error(`Error: User with email ${email} is not a member of workspace ${workspaceID}`)
  105. process.exit(1)
  106. }
  107. // Set workspaceID in Stripe customer metadata
  108. await Billing.stripe().customers.update(customerID, {
  109. metadata: {
  110. workspaceID,
  111. },
  112. })
  113. await Database.transaction(async (tx) => {
  114. // Set customer id, subscription id, and payment method on workspace billing
  115. await tx
  116. .update(BillingTable)
  117. .set({
  118. customerID,
  119. subscriptionID,
  120. paymentMethodID,
  121. paymentMethodLast4,
  122. paymentMethodType,
  123. })
  124. .where(eq(BillingTable.workspaceID, workspaceID))
  125. // Set current time as timeSubscribed on user
  126. await tx
  127. .update(UserTable)
  128. .set({
  129. timeSubscribed: sql`now()`,
  130. })
  131. .where(eq(UserTable.id, user.id))
  132. // Create a row in payments table
  133. await tx.insert(PaymentTable).values({
  134. workspaceID,
  135. id: Identifier.create("payment"),
  136. amount: centsToMicroCents(amountInCents),
  137. customerID,
  138. invoiceID,
  139. paymentID,
  140. })
  141. })
  142. console.log(`Successfully onboarded workspace ${workspaceID}`)
  143. console.log(` Customer ID: ${customerID}`)
  144. console.log(` Subscription ID: ${subscriptionID}`)
  145. console.log(
  146. ` Payment Method: ${paymentMethodID ?? "(none)"} (${paymentMethodType ?? "unknown"} ending in ${paymentMethodLast4 ?? "????"})`,
  147. )
  148. console.log(` User ID: ${user.id}`)
  149. console.log(` Invoice ID: ${invoiceID ?? "(none)"}`)
  150. console.log(` Payment ID: ${paymentID ?? "(none)"}`)