diff options
Diffstat (limited to 'packages/console/core/script/black-transfer.ts')
| -rw-r--r-- | packages/console/core/script/black-transfer.ts | 163 |
1 files changed, 163 insertions, 0 deletions
diff --git a/packages/console/core/script/black-transfer.ts b/packages/console/core/script/black-transfer.ts new file mode 100644 index 000000000..a7947fe72 --- /dev/null +++ b/packages/console/core/script/black-transfer.ts @@ -0,0 +1,163 @@ +import { Billing } from "../src/billing.js" +import { and, Database, desc, eq, isNotNull, lt, sql } from "../src/drizzle/index.js" +import { BillingTable, PaymentTable, SubscriptionTable } from "../src/schema/billing.sql.js" + +const fromWrkID = process.argv[2] +const toWrkID = process.argv[3] + +if (!fromWrkID || !toWrkID) { + console.error("Usage: bun foo.ts <fromWrkID> <toWrkID>") + process.exit(1) +} + +console.log(`Transferring subscription from ${fromWrkID} to ${toWrkID}`) + +// Look up the FROM workspace billing +const fromBilling = await Database.use((tx) => + tx + .select({ + customerID: BillingTable.customerID, + subscriptionID: BillingTable.subscriptionID, + subscriptionCouponID: BillingTable.subscriptionCouponID, + paymentMethodID: BillingTable.paymentMethodID, + paymentMethodType: BillingTable.paymentMethodType, + paymentMethodLast4: BillingTable.paymentMethodLast4, + }) + .from(BillingTable) + .where(eq(BillingTable.workspaceID, fromWrkID)) + .then((rows) => rows[0]), +) +if (!fromBilling) throw new Error(`Error: FROM workspace has no billing record`) +if (!fromBilling.customerID) throw new Error(`Error: FROM workspace has no Stripe customer ID`) +if (!fromBilling.subscriptionID) throw new Error(`Error: FROM workspace has no subscription`) + +const fromSubscription = await Database.use((tx) => + tx + .select({ userID: SubscriptionTable.userID }) + .from(SubscriptionTable) + .where(eq(SubscriptionTable.workspaceID, fromWrkID)) + .then((rows) => rows[0]), +) +if (!fromSubscription) throw new Error(`Error: FROM workspace has no subscription`) + +// Look up the previous customer ID in FROM workspace +const subscriptionPayment = await Database.use((tx) => + tx + .select({ + customerID: PaymentTable.customerID, + timeCreated: PaymentTable.timeCreated, + }) + .from(PaymentTable) + .where(and(eq(PaymentTable.workspaceID, fromWrkID), sql`JSON_EXTRACT(enrichment, '$.type') = 'subscription'`)) + .then((rows) => { + if (rows.length > 1) { + console.error(`Error: Multiple subscription payments found for workspace ${fromWrkID}`) + process.exit(1) + } + return rows[0] + }), +) +const fromPrevPayment = await Database.use((tx) => + tx + .select({ customerID: PaymentTable.customerID }) + .from(PaymentTable) + .where( + and( + eq(PaymentTable.workspaceID, fromWrkID), + isNotNull(PaymentTable.customerID), + lt(PaymentTable.timeCreated, subscriptionPayment.timeCreated), + ), + ) + .orderBy(desc(PaymentTable.timeCreated)) + .limit(1) + .then((rows) => rows[0]), +) +if (!fromPrevPayment?.customerID) throw new Error(`Error: FROM workspace has no previous Stripe customer to revert to`) +if (fromPrevPayment.customerID === fromBilling.customerID) + throw new Error(`Error: FROM workspace has the same Stripe customer ID as the current one`) + +const fromPrevPaymentMethods = await Billing.stripe().customers.listPaymentMethods(fromPrevPayment.customerID, {}) +if (fromPrevPaymentMethods.data.length === 0) + throw new Error(`Error: FROM workspace has no previous Stripe payment methods`) + +// Look up the TO workspace billing +const toBilling = await Database.use((tx) => + tx + .select({ + customerID: BillingTable.customerID, + subscriptionID: BillingTable.subscriptionID, + }) + .from(BillingTable) + .where(eq(BillingTable.workspaceID, toWrkID)) + .then((rows) => rows[0]), +) +if (!toBilling) throw new Error(`Error: TO workspace has no billing record`) +if (toBilling.subscriptionID) throw new Error(`Error: TO workspace already has a subscription`) + +console.log(`FROM:`) +console.log(` Old Customer ID: ${fromBilling.customerID}`) +console.log(` New Customer ID: ${fromPrevPayment.customerID}`) +console.log(`TO:`) +console.log(` Old Customer ID: ${toBilling.customerID}`) +console.log(` New Customer ID: ${fromBilling.customerID}`) + +// Clear workspaceID from Stripe customer metadata +await Billing.stripe().customers.update(fromPrevPayment.customerID, { + metadata: { + workspaceID: fromWrkID, + }, +}) +await Billing.stripe().customers.update(fromBilling.customerID, { + metadata: { + workspaceID: toWrkID, + }, +}) + +await Database.transaction(async (tx) => { + await tx + .update(BillingTable) + .set({ + customerID: fromPrevPayment.customerID, + subscriptionID: null, + subscriptionCouponID: null, + paymentMethodID: fromPrevPaymentMethods.data[0].id, + paymentMethodLast4: fromPrevPaymentMethods.data[0].card?.last4 ?? null, + paymentMethodType: fromPrevPaymentMethods.data[0].type, + }) + .where(eq(BillingTable.workspaceID, fromWrkID)) + + await tx + .update(BillingTable) + .set({ + customerID: fromBilling.customerID, + subscriptionID: fromBilling.subscriptionID, + subscriptionCouponID: fromBilling.subscriptionCouponID, + paymentMethodID: fromBilling.paymentMethodID, + paymentMethodLast4: fromBilling.paymentMethodLast4, + paymentMethodType: fromBilling.paymentMethodType, + }) + .where(eq(BillingTable.workspaceID, toWrkID)) + + await tx + .update(SubscriptionTable) + .set({ + workspaceID: toWrkID, + userID: fromSubscription.userID, + }) + .where(eq(SubscriptionTable.workspaceID, fromWrkID)) + + await tx + .update(PaymentTable) + .set({ + workspaceID: toWrkID, + }) + .where( + and( + eq(PaymentTable.workspaceID, fromWrkID), + sql`JSON_EXTRACT(enrichment, '$.type') = 'subscription'`, + eq(PaymentTable.amount, 20000000000), + ), + ) +}) + +console.log(`done`) |
