black-transfer.ts 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163
  1. import { Billing } from "../src/billing.js"
  2. import { and, Database, desc, eq, isNotNull, lt, sql } from "../src/drizzle/index.js"
  3. import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js"
  4. const fromWrkID = process.argv[2]
  5. const toWrkID = process.argv[3]
  6. if (!fromWrkID || !toWrkID) {
  7. console.error("Usage: bun foo.ts <fromWrkID> <toWrkID>")
  8. process.exit(1)
  9. }
  10. console.log(`Transferring subscription from ${fromWrkID} to ${toWrkID}`)
  11. // Look up the FROM workspace billing
  12. const fromBilling = await Database.use((tx) =>
  13. tx
  14. .select({
  15. customerID: BillingTable.customerID,
  16. subscriptionID: BillingTable.subscriptionID,
  17. subscription: BillingTable.subscription,
  18. paymentMethodID: BillingTable.paymentMethodID,
  19. paymentMethodType: BillingTable.paymentMethodType,
  20. paymentMethodLast4: BillingTable.paymentMethodLast4,
  21. })
  22. .from(BillingTable)
  23. .where(eq(BillingTable.workspaceID, fromWrkID))
  24. .then((rows) => rows[0]),
  25. )
  26. if (!fromBilling) throw new Error(`Error: FROM workspace has no billing record`)
  27. if (!fromBilling.customerID) throw new Error(`Error: FROM workspace has no Stripe customer ID`)
  28. if (!fromBilling.subscriptionID) throw new Error(`Error: FROM workspace has no subscription`)
  29. const fromSubscription = await Database.use((tx) =>
  30. tx
  31. .select({ userID: SubscriptionTable.userID })
  32. .from(SubscriptionTable)
  33. .where(eq(SubscriptionTable.workspaceID, fromWrkID))
  34. .then((rows) => rows[0]),
  35. )
  36. if (!fromSubscription) throw new Error(`Error: FROM workspace has no subscription`)
  37. // Look up the previous customer ID in FROM workspace
  38. const subscriptionPayment = await Database.use((tx) =>
  39. tx
  40. .select({
  41. customerID: PaymentTable.customerID,
  42. timeCreated: PaymentTable.timeCreated,
  43. })
  44. .from(PaymentTable)
  45. .where(and(eq(PaymentTable.workspaceID, fromWrkID), sql`JSON_EXTRACT(enrichment, '$.type') = 'subscription'`))
  46. .then((rows) => {
  47. if (rows.length > 1) {
  48. console.error(`Error: Multiple subscription payments found for workspace ${fromWrkID}`)
  49. process.exit(1)
  50. }
  51. return rows[0]
  52. }),
  53. )
  54. const fromPrevPayment = await Database.use((tx) =>
  55. tx
  56. .select({ customerID: PaymentTable.customerID })
  57. .from(PaymentTable)
  58. .where(
  59. and(
  60. eq(PaymentTable.workspaceID, fromWrkID),
  61. isNotNull(PaymentTable.customerID),
  62. lt(PaymentTable.timeCreated, subscriptionPayment.timeCreated),
  63. ),
  64. )
  65. .orderBy(desc(PaymentTable.timeCreated))
  66. .limit(1)
  67. .then((rows) => rows[0]),
  68. )
  69. if (!fromPrevPayment?.customerID) throw new Error(`Error: FROM workspace has no previous Stripe customer to revert to`)
  70. if (fromPrevPayment.customerID === fromBilling.customerID)
  71. throw new Error(`Error: FROM workspace has the same Stripe customer ID as the current one`)
  72. const fromPrevPaymentMethods = await Billing.stripe().customers.listPaymentMethods(fromPrevPayment.customerID, {})
  73. if (fromPrevPaymentMethods.data.length === 0)
  74. throw new Error(`Error: FROM workspace has no previous Stripe payment methods`)
  75. // Look up the TO workspace billing
  76. const toBilling = await Database.use((tx) =>
  77. tx
  78. .select({
  79. customerID: BillingTable.customerID,
  80. subscriptionID: BillingTable.subscriptionID,
  81. })
  82. .from(BillingTable)
  83. .where(eq(BillingTable.workspaceID, toWrkID))
  84. .then((rows) => rows[0]),
  85. )
  86. if (!toBilling) throw new Error(`Error: TO workspace has no billing record`)
  87. if (toBilling.subscriptionID) throw new Error(`Error: TO workspace already has a subscription`)
  88. console.log(`FROM:`)
  89. console.log(` Old Customer ID: ${fromBilling.customerID}`)
  90. console.log(` New Customer ID: ${fromPrevPayment.customerID}`)
  91. console.log(`TO:`)
  92. console.log(` Old Customer ID: ${toBilling.customerID}`)
  93. console.log(` New Customer ID: ${fromBilling.customerID}`)
  94. // Clear workspaceID from Stripe customer metadata
  95. await Billing.stripe().customers.update(fromPrevPayment.customerID, {
  96. metadata: {
  97. workspaceID: fromWrkID,
  98. },
  99. })
  100. await Billing.stripe().customers.update(fromBilling.customerID, {
  101. metadata: {
  102. workspaceID: toWrkID,
  103. },
  104. })
  105. await Database.transaction(async (tx) => {
  106. await tx
  107. .update(BillingTable)
  108. .set({
  109. customerID: fromPrevPayment.customerID,
  110. subscriptionID: null,
  111. subscription: null,
  112. paymentMethodID: fromPrevPaymentMethods.data[0].id,
  113. paymentMethodLast4: fromPrevPaymentMethods.data[0].card?.last4 ?? null,
  114. paymentMethodType: fromPrevPaymentMethods.data[0].type,
  115. })
  116. .where(eq(BillingTable.workspaceID, fromWrkID))
  117. await tx
  118. .update(BillingTable)
  119. .set({
  120. customerID: fromBilling.customerID,
  121. subscriptionID: fromBilling.subscriptionID,
  122. subscription: fromBilling.subscription,
  123. paymentMethodID: fromBilling.paymentMethodID,
  124. paymentMethodLast4: fromBilling.paymentMethodLast4,
  125. paymentMethodType: fromBilling.paymentMethodType,
  126. })
  127. .where(eq(BillingTable.workspaceID, toWrkID))
  128. await tx
  129. .update(SubscriptionTable)
  130. .set({
  131. workspaceID: toWrkID,
  132. userID: fromSubscription.userID,
  133. })
  134. .where(eq(SubscriptionTable.workspaceID, fromWrkID))
  135. await tx
  136. .update(PaymentTable)
  137. .set({
  138. workspaceID: toWrkID,
  139. })
  140. .where(
  141. and(
  142. eq(PaymentTable.workspaceID, fromWrkID),
  143. sql`JSON_EXTRACT(enrichment, '$.type') = 'subscription'`,
  144. eq(PaymentTable.amount, 20000000000),
  145. ),
  146. )
  147. })
  148. console.log(`done`)