[web] rm unnecessary scripts (#31631)

GitOrigin-RevId: c6388685bd9cd43d8d22e38f25db4ee579def808
This commit is contained in:
Kristina
2026-02-19 09:06:45 +00:00
committed by Copybot
parent 95efb60fb5
commit 8872d851ec
4 changed files with 0 additions and 1084 deletions
@@ -1,255 +0,0 @@
#!/usr/bin/env node
/**
* This script iterates through all Stripe subscriptions, checks if they have metadata adminUserId,
* and sets it to customer metadata "userId" if present.
*
* Usage:
* node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=us [options]
* node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=uk [options]
*
* Options:
* --region=us|uk Required. Stripe region to process (us or uk)
* --commit Actually perform the updates (default: dry-run mode)
* --verbose Enable verbose logging
* --limit=N Limit processing to N subscriptions (for testing)
*
* Examples:
* # Dry run for US region with verbose output
* node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=us --verbose
*
* # Commit changes for UK region
* node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=uk --commit
*
* # Test with limited subscriptions
* node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=us --limit=10 --verbose
*/
import minimist from 'minimist'
import { z } from '../../app/src/infrastructure/Validation.mjs'
import { scriptRunner } from '../lib/ScriptRunner.mjs'
import {
getRegionClient,
CUSTOMER_SEGMENT_MAPPING,
} from '../../modules/subscriptions/app/src/StripeClient.mjs'
const paramsSchema = z.object({
region: z.enum(['us', 'uk']),
commit: z.boolean().default(false),
verbose: z.boolean().default(false),
limit: z.number().int().min(1).optional(),
})
let processedCount = 0
let updatedCount = 0
let errorCount = 0
/**
* Sleep function to respect Stripe rate limits (100 requests per second)
* We'll be conservative and sleep for 50ms between requests to stay well under the limit
*/
async function rateLimitSleep() {
return new Promise(resolve => setTimeout(resolve, 50))
}
/**
* Process a single subscription and update customer metadata if needed
*/
async function processSubscription(
subscription,
stripeClient,
commit,
verbose
) {
try {
processedCount++
// Check if subscription has adminUserId metadata
const adminUserId = subscription.metadata?.adminUserId
if (verbose) {
console.info(
`Processing subscription ${subscription.id} (customer: ${subscription.customer.id}) - adminUserId: ${adminUserId || 'none'}`
)
}
if (!adminUserId) {
// No adminUserId to migrate
return
}
// Get customer details to check current metadata
const customer = subscription.customer
if (customer.deleted) {
if (verbose) {
console.info(`Customer ${customer.id} is deleted, skipping`)
}
return
}
if (customer.metadata?.userId === adminUserId) {
if (verbose) {
console.info(
`Customer ${customer.id} already has userId=${adminUserId}, skipping`
)
}
return
}
if (customer.metadata?.userId && customer.metadata.userId !== adminUserId) {
console.warn(
`Customer ${customer.id} has existing userId=${customer.metadata.userId} which differs from adminUserId=${adminUserId}, skipping to avoid overwrite`
)
return
}
if (commit) {
// Update customer metadata using the StripeClient method
await stripeClient.updateCustomerMetadata(customer.id, {
...customer.metadata,
userId: adminUserId,
segment: CUSTOMER_SEGMENT_MAPPING.B2C,
})
console.info(
`Updated customer ${customer.id} metadata: userId=${adminUserId}`
)
} else {
console.info(
`DRY RUN: Would update customer ${customer.id} metadata: userId=${adminUserId}`
)
}
updatedCount++
} catch (error) {
errorCount++
console.log(error)
}
// Respect rate limits
await rateLimitSleep()
}
/**
* Main script function
*/
async function main(trackProgress) {
const parseResult = paramsSchema.safeParse(
minimist(process.argv.slice(2), {
boolean: ['commit', 'verbose'],
string: ['region'],
number: ['limit'],
})
)
if (!parseResult.success) {
throw new Error(`Invalid parameters: ${parseResult.error.message}`)
}
const { region, commit, verbose, limit } = parseResult.data
const mode = commit ? 'COMMIT MODE' : 'DRY RUN MODE'
await trackProgress(
`Starting script in ${mode} for Stripe ${region.toUpperCase()} region`
)
if (limit) {
await trackProgress(`Processing limited to ${limit} subscriptions`)
}
// Get Stripe client for the specified region
const stripeClient = getRegionClient(region)
// Reset counters
processedCount = 0
updatedCount = 0
errorCount = 0
await trackProgress('Starting to iterate through Stripe subscriptions...')
const listParams = {
limit: 100, // Stripe's maximum limit per request
expand: ['data.customer'], // Expand customer data to reduce additional API calls
status: 'all', // Include subscriptions in all statuses (active, past_due, unpaid, canceled, etc.)
}
let hasMore = true
let startingAfter = null
let totalProcessed = 0
while (hasMore) {
const params = { ...listParams }
if (startingAfter) {
params.starting_after = startingAfter
}
// Get batch of subscriptions
const subscriptions = await stripeClient.stripe.subscriptions.list(params)
await trackProgress(
`Retrieved ${subscriptions.data.length} subscriptions (total processed so far: ${totalProcessed})`
)
// Process each subscription in the batch
for (const subscription of subscriptions.data) {
await processSubscription(subscription, stripeClient, commit, verbose)
totalProcessed++
// Check if we've hit the limit
if (limit && totalProcessed >= limit) {
await trackProgress(`Reached limit of ${limit} subscriptions, stopping`)
hasMore = false
break
}
// Progress update every 50 subscriptions
if (totalProcessed % 50 === 0) {
await trackProgress(
`Progress: ${totalProcessed} processed, ${updatedCount} customers updated, ${errorCount} errors`
)
}
}
// Check if there are more subscriptions to process
hasMore = hasMore && subscriptions.has_more
if (hasMore && subscriptions.data.length > 0) {
startingAfter = subscriptions.data[subscriptions.data.length - 1].id
}
// Rate limit between batch requests
await rateLimitSleep()
}
// Final summary
await trackProgress('FINAL SUMMARY:')
await trackProgress(` Total subscriptions processed: ${processedCount}`)
await trackProgress(
` Customers ${commit ? 'updated' : 'would be updated'}: ${updatedCount}`
)
await trackProgress(` Errors encountered: ${errorCount}`)
if (!commit && updatedCount > 0) {
await trackProgress('')
await trackProgress(
'To actually perform the updates, run the script with --commit flag'
)
}
if (errorCount > 0) {
await trackProgress(
'Some errors were encountered. Check the logs above for details.'
)
}
await trackProgress(`Script completed successfully in ${mode}`)
}
// Execute the script using the runner
try {
await scriptRunner(main)
process.exit(0)
} catch (error) {
console.error('Script failed:', error.message)
process.exit(1)
}
@@ -1,358 +0,0 @@
#!/usr/bin/env node
/**
* This script validates subscriptions that are canceled or expired and have migration metadata set to "in_progress",
* then updates the metadata to "cancelled" if validation passes.
*
* TODO: This script can be deleted after being run in production.
*
* Usage:
* node scripts/stripe/bulk-update-migration-status.mjs [OPTS] [INPUT-FILE]
*
* Options:
* --output PATH Output file path (default: /tmp/bulk_update_output_<timestamp>.csv)
* Use '-' to write to stdout
* --commit Apply changes (without this flag, runs in dry-run mode)
* --concurrency N Number of subscriptions to process concurrently (default: 10)
* --stripe-rate-limit N Requests per second for Stripe (default: 50)
* --stripe-api-retries N Number of retries on Stripe 429s (default: 5)
* --stripe-retry-delay-ms N Delay between Stripe retries in ms (default: 1000)
* --help Show a help message
*
* CSV Input Format:
* The CSV must have the following columns:
* - subscription_id: Stripe subscription id
* - target_stripe_account: Either 'stripe-uk' or 'stripe-us'
*
* Output:
* Writes a CSV with columns:
* - subscription_id: The subscription id processed
* - target_stripe_account: The Stripe account
* - status: Result status (validated, updated, invalid-status, invalid-metadata, or error)
* - note: Additional information about the status
*/
import fs from 'node:fs'
import path from 'node:path'
import * as csv from 'csv'
import minimist from 'minimist'
import PQueue from 'p-queue'
import { z } from '../../app/src/infrastructure/Validation.mjs'
import { scriptRunner } from '../lib/ScriptRunner.mjs'
import { getRegionClient } from '../../modules/subscriptions/app/src/StripeClient.mjs'
import { ReportError } from './helpers.mjs'
import {
createRateLimitedApiWrappers,
DEFAULT_STRIPE_RATE_LIMIT,
DEFAULT_STRIPE_API_RETRIES,
DEFAULT_STRIPE_RETRY_DELAY_MS,
} from './RateLimiter.mjs'
const DEFAULT_CONCURRENCY = 10
// rate limiters - initialized in main()
let rateLimiters
function usage() {
console.error(`Usage: node scripts/stripe/bulk-update-migration-status.mjs [OPTS] [INPUT-FILE]
Options:
--output PATH Output file path (default: /tmp/bulk_update_output_<timestamp>.csv)
Use '-' to write to stdout
--commit Apply changes (without this, runs in dry-run mode)
--concurrency N Number of subscriptions to process concurrently (default: ${DEFAULT_CONCURRENCY})
--stripe-rate-limit N Requests per second for Stripe (default: ${DEFAULT_STRIPE_RATE_LIMIT})
--stripe-api-retries N Number of retries on Stripe 429s (default: ${DEFAULT_STRIPE_API_RETRIES})
--stripe-retry-delay-ms N Delay between Stripe retries in ms (default: ${DEFAULT_STRIPE_RETRY_DELAY_MS})
--help Show this help message
`)
}
async function main(trackProgress) {
const opts = parseArgs()
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const outputFile = opts.output ?? `/tmp/bulk_update_output_${timestamp}.csv`
// initialize rate limiters
rateLimiters = createRateLimitedApiWrappers({
stripeRateLimit: opts.stripeRateLimit,
stripeApiRetries: opts.stripeApiRetries,
stripeRetryDelayMs: opts.stripeRetryDelayMs,
})
await trackProgress(
'Starting bulk validation and update of subscription metadata'
)
await trackProgress(`Run mode: ${opts.commit ? 'COMMIT' : 'DRY RUN'}`)
await trackProgress(`Rate limit: Stripe ${opts.stripeRateLimit}/s`)
await trackProgress(`Concurrency: ${opts.concurrency}`)
const inputStream = opts.inputFile
? fs.createReadStream(opts.inputFile)
: process.stdin
const csvReader = getCsvReader(inputStream)
const csvWriter = getCsvWriter(outputFile)
await trackProgress(`Output: ${outputFile === '-' ? 'stdout' : outputFile}`)
let processedCount = 0
let successCount = 0
let errorCount = 0
const queue = new PQueue({ concurrency: opts.concurrency })
const maxQueueSize = opts.concurrency
try {
for await (const input of csvReader) {
if (queue.size >= maxQueueSize) {
await queue.onSizeLessThan(maxQueueSize)
}
queue.add(async () => {
try {
const result = await processValidation(input, opts.commit)
csvWriter.write({
subscription_id: input.subscription_id,
target_stripe_account: input.target_stripe_account,
status: result.status,
note:
result.note ||
(opts.commit ? '' : 'dry run - no changes applied'),
})
if (result.status === 'updated' || result.status === 'validated') {
successCount++
} else {
errorCount++
}
} catch (err) {
errorCount++
if (err instanceof ReportError) {
csvWriter.write({
subscription_id: input.subscription_id,
target_stripe_account: input.target_stripe_account,
status: err.status,
note: err.message,
})
} else {
csvWriter.write({
subscription_id: input.subscription_id,
target_stripe_account: input.target_stripe_account,
status: 'error',
note: err.message,
})
await trackProgress(
`Error processing ${input.subscription_id}: ${err.message}`
)
}
}
processedCount++
if (processedCount % 10 === 0) {
await trackProgress(
`Processed ${processedCount} subscriptions (${successCount} ${opts.commit ? 'updated' : 'validated'}, ${errorCount} errors)`
)
}
})
}
} finally {
await queue.onIdle()
}
await trackProgress(`✅ Total processed: ${processedCount}`)
if (opts.commit) {
await trackProgress(`✅ Successfully updated: ${successCount}`)
} else {
await trackProgress(`✅ Successfully validated: ${successCount}`)
await trackProgress('️ DRY RUN: No changes were applied')
}
await trackProgress(`❌ Errors: ${errorCount}`)
await trackProgress('🎉 Script completed!')
csvWriter.end()
}
function parseArgs() {
const args = minimist(process.argv.slice(2), {
string: [
'output',
'concurrency',
'stripe-rate-limit',
'stripe-api-retries',
'stripe-retry-delay-ms',
],
boolean: ['commit', 'help'],
default: {
commit: false,
concurrency: DEFAULT_CONCURRENCY,
'stripe-rate-limit': DEFAULT_STRIPE_RATE_LIMIT,
'stripe-api-retries': DEFAULT_STRIPE_API_RETRIES,
'stripe-retry-delay-ms': DEFAULT_STRIPE_RETRY_DELAY_MS,
},
unknown: arg => {
if (arg.startsWith('-')) {
console.error(`Unknown option: ${arg}`)
usage()
process.exit(1)
}
return true
},
})
if (args.help) {
usage()
process.exit(0)
}
const inputFile = args._[0]
const paramsSchema = z.object({
output: z.string().optional(),
commit: z.boolean(),
concurrency: z.number().int().positive(),
stripeRateLimit: z.number().positive(),
stripeApiRetries: z.number().int().nonnegative(),
stripeRetryDelayMs: z.number().int().nonnegative(),
inputFile: z.string().optional(),
})
try {
return paramsSchema.parse({
output: args.output,
commit: args.commit,
concurrency: Number(args.concurrency),
stripeRateLimit: Number(args['stripe-rate-limit']),
stripeApiRetries: Number(args['stripe-api-retries']),
stripeRetryDelayMs: Number(args['stripe-retry-delay-ms']),
inputFile,
})
} catch (err) {
console.error('Invalid arguments:', err.message)
usage()
process.exit(1)
}
}
function getCsvReader(inputStream) {
const parser = csv.parse({ columns: true })
inputStream.pipe(parser)
return parser
}
function getCsvWriter(outputFile) {
if (outputFile === '-') {
const writer = csv.stringify({
columns: ['subscription_id', 'target_stripe_account', 'status', 'note'],
header: true,
})
writer.on('error', err => {
console.error(err)
process.exit(1)
})
writer.pipe(process.stdout)
return writer
}
fs.mkdirSync(path.dirname(outputFile), { recursive: true })
const outputStream = fs.createWriteStream(outputFile)
const writer = csv.stringify({
columns: ['subscription_id', 'target_stripe_account', 'status', 'note'],
header: true,
})
writer.on('error', err => {
console.error(err)
process.exit(1)
})
writer.pipe(outputStream)
return writer
}
async function processValidation(input, commit) {
const {
subscription_id: subscriptionId,
target_stripe_account: targetStripeAccount,
} = input
// get Stripe client for the target account (strip 'stripe-' prefix if present)
const region = targetStripeAccount.replace(/^stripe-/, '')
const stripeClient = getRegionClient(region)
// fetch subscription
let subscription
try {
subscription = await rateLimiters.requestWithRetries(
stripeClient.serviceName,
() => stripeClient.stripe.subscriptions.retrieve(subscriptionId),
{
operation: 'subscriptions.retrieve',
subscriptionId,
region: stripeClient.serviceName,
}
)
} catch (err) {
throw new ReportError(
'subscription-not-found',
`Subscription not found: ${err.message}`
)
}
const validStatuses = ['canceled']
if (!validStatuses.includes(subscription.status)) {
throw new ReportError(
'invalid-status',
`Subscription status is ${subscription.status}, expected canceled`
)
}
if (
subscription.metadata?.recurly_to_stripe_migration_status !== 'in_progress'
) {
throw new ReportError(
'invalid-metadata',
`Migration status is ${subscription.metadata?.recurly_to_stripe_migration_status}, expected in_progress`
)
}
if (!commit) {
return {
status: 'validated',
note: 'Subscription is valid for update',
}
}
try {
await rateLimiters.requestWithRetries(
stripeClient.serviceName,
() =>
stripeClient.updateSubscriptionMetadata(subscriptionId, {
recurly_to_stripe_migration_status: 'cancelled',
}),
{
operation: 'updateSubscriptionMetadata',
subscriptionId,
region: stripeClient.serviceName,
}
)
return {
status: 'updated',
note: `Updated metadata for subscription ${subscriptionId}`,
}
} catch (err) {
throw new ReportError(
'update-failed',
`Failed to update metadata: ${err.message}`
)
}
}
try {
await scriptRunner(main)
process.exit(0)
} catch (error) {
console.error(error)
process.exit(1)
}
@@ -1,244 +0,0 @@
#!/usr/bin/env node
/**
* This script registers analytics account mapping for subscriptions migrated to Stripe.
*
* // TODO: delete this when the migration is complete
*
* Usage:
* node scripts/stripe/register-analytics-mapping.mjs [OPTS] [INPUT-FILE]
*
* Options:
* --output PATH Output file path (default: /tmp/register_output_<timestamp>.csv)
* --commit Apply changes (without this, runs in dry-run mode)
* --help Show help message
*
* CSV Input Format:
* recurly_account_code,target_stripe_account,stripe_customer_id
* 507f1f77bcf86cd799439011,stripe-uk,cus_1234567890abcdef
*
* CSV Output Format:
* recurly_account_code,target_stripe_account,stripe_customer_id,status,note
*/
import fs from 'node:fs'
import path from 'node:path'
import * as csv from 'csv'
import minimist from 'minimist'
import { z } from '../../app/src/infrastructure/Validation.mjs'
import { scriptRunner } from '../lib/ScriptRunner.mjs'
import { Subscription } from '../../app/src/models/Subscription.mjs'
import AnalyticsManager from '../../app/src/Features/Analytics/AnalyticsManager.mjs'
import AccountMappingHelper from '../../app/src/Features/Analytics/AccountMappingHelper.mjs'
import { ReportError } from './helpers.mjs'
function usage() {
console.error(`Usage: node scripts/stripe/register-analytics-mapping.mjs [OPTS] [INPUT-FILE]
Options:
--output PATH Output file path (default: /tmp/register_output_<timestamp>.csv)
--commit Apply changes (without this, runs in dry-run mode)
--help Show this help message
`)
}
async function main(trackProgress) {
const opts = parseArgs()
const timestamp = new Date().toISOString().replace(/[:.]/g, '-')
const outputFile = opts.output ?? `/tmp/register_output_${timestamp}.csv`
await trackProgress('Starting analytics mapping registration')
await trackProgress(`Run mode: ${opts.commit ? 'COMMIT' : 'DRY RUN'}`)
const inputStream = opts.inputFile
? fs.createReadStream(opts.inputFile)
: process.stdin
const csvReader = getCsvReader(inputStream)
const csvWriter = getCsvWriter(outputFile)
await trackProgress(`Output: ${outputFile}`)
let processedCount = 0
let successCount = 0
let errorCount = 0
for await (const input of csvReader) {
processedCount++
try {
const result = await processRow(input, opts.commit)
csvWriter.write({
recurly_account_code: input.recurly_account_code,
target_stripe_account: input.target_stripe_account,
stripe_customer_id: input.stripe_customer_id,
status: result.status,
note: result.note,
})
if (result.status === 'registered' || result.status === 'dry-run') {
successCount++
} else {
errorCount++
}
if (processedCount % 25 === 0) {
await trackProgress(
`Progress: ${processedCount} processed, ${successCount} successful, ${errorCount} errors`
)
}
} catch (err) {
errorCount++
if (err instanceof ReportError) {
csvWriter.write({
recurly_account_code: input.recurly_account_code,
target_stripe_account: input.target_stripe_account,
stripe_customer_id: input.stripe_customer_id,
status: err.status,
note: err.message,
})
} else {
csvWriter.write({
recurly_account_code: input.recurly_account_code,
target_stripe_account: input.target_stripe_account,
stripe_customer_id: input.stripe_customer_id,
status: 'error',
note: err.message,
})
}
}
}
await trackProgress(`✅ Total processed: ${processedCount}`)
if (opts.commit) {
await trackProgress(`✅ Successfully registered: ${successCount}`)
} else {
await trackProgress(`✅ Successfully validated: ${successCount}`)
await trackProgress('️ DRY RUN: No changes were applied')
}
await trackProgress(`❌ Errors: ${errorCount}`)
await trackProgress('🎉 Script completed!')
csvWriter.end()
}
function getCsvReader(inputStream) {
const parser = csv.parse({ columns: true })
inputStream.pipe(parser)
return parser
}
function getCsvWriter(outputFile) {
fs.mkdirSync(path.dirname(outputFile), { recursive: true })
const outputStream = fs.createWriteStream(outputFile)
const writer = csv.stringify({
columns: [
'recurly_account_code',
'target_stripe_account',
'stripe_customer_id',
'status',
'note',
],
header: true,
})
writer.on('error', err => {
console.error(err)
process.exit(1)
})
writer.pipe(outputStream)
return writer
}
async function processRow(input, commit) {
const {
recurly_account_code: accountCode,
target_stripe_account: targetStripeAccount,
} = input
// 1. Fetch Mongo subscription
const mongoSubscription = await Subscription.findOne({
admin_id: accountCode,
}).exec()
if (!mongoSubscription) {
throw new ReportError(
'no-mongo-subscription',
'No subscription found in Mongo'
)
}
// 2. Check if migrated to Stripe
if (!mongoSubscription.paymentProvider?.service?.includes('stripe')) {
throw new ReportError('not-stripe', 'Subscription not using Stripe')
}
const subscriptionId = mongoSubscription.paymentProvider.subscriptionId
if (!subscriptionId) {
throw new ReportError(
'no-subscription-id',
'No Stripe subscription ID in Mongo'
)
}
// 3. Register analytics mapping
if (commit) {
AnalyticsManager.registerAccountMapping(
AccountMappingHelper.generateSubscriptionToStripeMapping(
mongoSubscription._id,
subscriptionId,
targetStripeAccount
)
)
return {
status: 'registered',
note: 'Analytics mapping registered',
}
} else {
return {
status: 'dry-run',
note: 'DRY RUN: Would register analytics mapping',
}
}
}
function parseArgs() {
const args = minimist(process.argv.slice(2), {
string: ['output'],
boolean: ['commit', 'help'],
default: { commit: false },
})
if (args.help) {
usage()
process.exit(0)
}
const inputFile = args._[0]
const paramsSchema = z.object({
output: z.string().optional(),
commit: z.boolean(),
inputFile: z.string().optional(),
})
try {
return paramsSchema.parse({
output: args.output,
commit: args.commit,
inputFile,
})
} catch (err) {
console.error('Invalid arguments:', err.message)
usage()
process.exit(1)
}
}
try {
await scriptRunner(main)
process.exit(0)
} catch (error) {
console.error(error)
process.exit(1)
}
@@ -1,227 +0,0 @@
#!/usr/bin/env node
/**
* This script iterates through all Stripe subscriptions and removes the adminUserId metadata
* from subscription objects that have it.
*
* Usage:
* node scripts/stripe/remove_admin_user_id_from_stripe_subscription.mjs --region=us [options]
* node scripts/stripe/remove_admin_user_id_from_stripe_subscription.mjs --region=uk [options]
*
* Options:
* --region=us|uk Required. Stripe region to process (us or uk)
* --commit Actually perform the updates (default: dry-run mode)
* --verbose Enable verbose logging
* --limit=N Limit processing to N subscriptions (for testing)
*
* Examples:
* # Dry run for US region with verbose output
* node scripts/stripe/remove_admin_user_id_from_stripe_subscription.mjs --region=us --verbose
*
* # Commit changes for UK region
* node scripts/stripe/remove_admin_user_id_from_stripe_subscription.mjs --region=uk --commit
*
* # Test with limited subscriptions
* node scripts/stripe/remove_admin_user_id_from_stripe_subscription.mjs --region=us --limit=10 --verbose
*/
import minimist from 'minimist'
import { z } from '../../app/src/infrastructure/Validation.mjs'
import { scriptRunner } from '../lib/ScriptRunner.mjs'
import { getRegionClient } from '../../modules/subscriptions/app/src/StripeClient.mjs'
const paramsSchema = z.object({
region: z.enum(['us', 'uk']),
commit: z.boolean().default(false),
verbose: z.boolean().default(false),
limit: z.number().int().min(1).optional(),
})
let processedCount = 0
let updatedCount = 0
let errorCount = 0
/**
* Sleep function to respect Stripe rate limits (100 requests per second)
* We'll be conservative and sleep for 50ms between requests to stay well under the limit
*/
async function rateLimitSleep() {
return new Promise(resolve => setTimeout(resolve, 50))
}
/**
* Process a single subscription and remove adminUserId metadata if present
*/
async function processSubscription(
subscription,
stripeClient,
commit,
verbose
) {
try {
processedCount++
// Check if subscription has adminUserId metadata
const adminUserId = subscription.metadata?.adminUserId
if (verbose) {
console.info(
`Processing subscription ${subscription.id} - adminUserId: ${adminUserId || 'none'}`
)
}
if (!adminUserId) {
// No adminUserId to remove
return
}
if (commit) {
// Create a new metadata object that will remove adminUserId
const updatedMetadata = { ...subscription.metadata }
updatedMetadata.adminUserId = ''
// Update subscription metadata using Stripe API directly
await stripeClient.stripe.subscriptions.update(subscription.id, {
metadata: updatedMetadata,
})
console.info(
`Removed adminUserId metadata from subscription ${subscription.id}`
)
} else {
console.info(
`DRY RUN: Would remove adminUserId metadata from subscription ${subscription.id}`
)
}
updatedCount++
} catch (error) {
errorCount++
console.log(error)
}
// Respect rate limits
await rateLimitSleep()
}
/**
* Main script function
*/
async function main(trackProgress) {
const parseResult = paramsSchema.safeParse(
minimist(process.argv.slice(2), {
boolean: ['commit', 'verbose'],
string: ['region'],
number: ['limit'],
})
)
if (!parseResult.success) {
throw new Error(`Invalid parameters: ${parseResult.error.message}`)
}
const { region, commit, verbose, limit } = parseResult.data
const mode = commit ? 'COMMIT MODE' : 'DRY RUN MODE'
await trackProgress(
`Starting script in ${mode} for Stripe ${region.toUpperCase()} region`
)
if (limit) {
await trackProgress(`Processing limited to ${limit} subscriptions`)
}
// Get Stripe client for the specified region
const stripeClient = getRegionClient(region)
// Reset counters
processedCount = 0
updatedCount = 0
errorCount = 0
await trackProgress('Starting to iterate through Stripe subscriptions...')
const listParams = {
limit: 100, // Stripe's maximum limit per request
}
let hasMore = true
let startingAfter = null
let totalProcessed = 0
while (hasMore) {
const params = { ...listParams }
if (startingAfter) {
params.starting_after = startingAfter
}
// Get batch of subscriptions
const subscriptions = await stripeClient.stripe.subscriptions.list(params)
await trackProgress(
`Retrieved ${subscriptions.data.length} subscriptions (total processed so far: ${totalProcessed})`
)
// Process each subscription in the batch
for (const subscription of subscriptions.data) {
await processSubscription(subscription, stripeClient, commit, verbose)
totalProcessed++
// Check if we've hit the limit
if (limit && totalProcessed >= limit) {
await trackProgress(`Reached limit of ${limit} subscriptions, stopping`)
hasMore = false
break
}
// Progress update every 50 subscriptions
if (totalProcessed % 50 === 0) {
await trackProgress(
`Progress: ${totalProcessed} processed, ${updatedCount} subscriptions updated, ${errorCount} errors`
)
}
}
// Check if there are more subscriptions to process
hasMore = hasMore && subscriptions.has_more
if (hasMore && subscriptions.data.length > 0) {
startingAfter = subscriptions.data[subscriptions.data.length - 1].id
}
// Rate limit between batch requests
await rateLimitSleep()
}
// Final summary
await trackProgress('FINAL SUMMARY:')
await trackProgress(` Total subscriptions processed: ${processedCount}`)
await trackProgress(
` Subscriptions ${commit ? 'updated' : 'would be updated'}: ${updatedCount}`
)
await trackProgress(` Errors encountered: ${errorCount}`)
if (!commit && updatedCount > 0) {
await trackProgress('')
await trackProgress(
'To actually perform the updates, run the script with --commit flag'
)
}
if (errorCount > 0) {
await trackProgress(
'Some errors were encountered. Check the logs above for details.'
)
}
await trackProgress(`Script completed successfully in ${mode}`)
}
// Execute the script using the runner
try {
await scriptRunner(main)
process.exit(0)
} catch (error) {
console.error('Script failed:', error.message)
process.exit(1)
}