164 lines
5.4 KiB
TypeScript
164 lines
5.4 KiB
TypeScript
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,
|
|
subscription: BillingTable.subscription,
|
|
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,
|
|
subscription: 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,
|
|
subscription: fromBilling.subscription,
|
|
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`)
|