add_user_id_to_stripe_customer.mjs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248
  1. #!/usr/bin/env node
  2. /**
  3. * This script iterates through all Stripe subscriptions, checks if they have metadata adminUserId,
  4. * and sets it to customer metadata "userId" if present.
  5. *
  6. * Usage:
  7. * node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=us [options]
  8. * node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=uk [options]
  9. *
  10. * Options:
  11. * --region=us|uk Required. Stripe region to process (us or uk)
  12. * --commit Actually perform the updates (default: dry-run mode)
  13. * --verbose Enable verbose logging
  14. * --limit=N Limit processing to N subscriptions (for testing)
  15. *
  16. * Examples:
  17. * # Dry run for US region with verbose output
  18. * node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=us --verbose
  19. *
  20. * # Commit changes for UK region
  21. * node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=uk --commit
  22. *
  23. * # Test with limited subscriptions
  24. * node scripts/stripe/add_user_id_to_stripe_customer.mjs --region=us --limit=10 --verbose
  25. */
  26. import minimist from 'minimist'
  27. import { z } from '../../app/src/infrastructure/Validation.js'
  28. import { scriptRunner } from '../lib/ScriptRunner.mjs'
  29. import {
  30. getRegionClient,
  31. CUSTOMER_SEGMENT_MAPPING,
  32. } from '../../modules/subscriptions/app/src/StripeClient.mjs'
  33. const paramsSchema = z.object({
  34. region: z.enum(['us', 'uk']),
  35. commit: z.boolean().default(false),
  36. verbose: z.boolean().default(false),
  37. limit: z.number().int().min(1).optional(),
  38. })
  39. let processedCount = 0
  40. let updatedCount = 0
  41. let errorCount = 0
  42. /**
  43. * Sleep function to respect Stripe rate limits (100 requests per second)
  44. * We'll be conservative and sleep for 50ms between requests to stay well under the limit
  45. */
  46. async function rateLimitSleep() {
  47. return new Promise(resolve => setTimeout(resolve, 50))
  48. }
  49. /**
  50. * Process a single subscription and update customer metadata if needed
  51. */
  52. async function processSubscription(
  53. subscription,
  54. stripeClient,
  55. commit,
  56. verbose
  57. ) {
  58. try {
  59. processedCount++
  60. // Check if subscription has adminUserId metadata
  61. const adminUserId = subscription.metadata?.adminUserId
  62. if (verbose) {
  63. console.info(
  64. `Processing subscription ${subscription.id} (customer: ${subscription.customer.id}) - adminUserId: ${adminUserId || 'none'}`
  65. )
  66. }
  67. if (!adminUserId) {
  68. // No adminUserId to migrate
  69. return
  70. }
  71. // Get customer details to check current metadata
  72. const customer = subscription.customer
  73. if (customer.metadata?.userId === adminUserId) {
  74. if (verbose) {
  75. console.info(
  76. `Customer ${customer.id} already has userId=${adminUserId}, skipping`
  77. )
  78. }
  79. return
  80. }
  81. if (customer.metadata?.userId && customer.metadata.userId !== adminUserId) {
  82. console.warn(
  83. `Customer ${customer.id} has existing userId=${customer.metadata.userId} which differs from adminUserId=${adminUserId}, skipping to avoid overwrite`
  84. )
  85. return
  86. }
  87. if (commit) {
  88. // Update customer metadata using the StripeClient method
  89. await stripeClient.updateCustomerMetadata(customer.id, {
  90. ...customer.metadata,
  91. userId: adminUserId,
  92. segment: CUSTOMER_SEGMENT_MAPPING.B2C,
  93. })
  94. console.info(
  95. `Updated customer ${customer.id} metadata: userId=${adminUserId}`
  96. )
  97. } else {
  98. console.info(
  99. `DRY RUN: Would update customer ${customer.id} metadata: userId=${adminUserId}`
  100. )
  101. }
  102. updatedCount++
  103. } catch (error) {
  104. errorCount++
  105. console.log(error)
  106. }
  107. // Respect rate limits
  108. await rateLimitSleep()
  109. }
  110. /**
  111. * Main script function
  112. */
  113. async function main(trackProgress) {
  114. const parseResult = paramsSchema.safeParse(
  115. minimist(process.argv.slice(2), {
  116. boolean: ['commit', 'verbose'],
  117. string: ['region'],
  118. number: ['limit'],
  119. })
  120. )
  121. if (!parseResult.success) {
  122. throw new Error(`Invalid parameters: ${parseResult.error.message}`)
  123. }
  124. const { region, commit, verbose, limit } = parseResult.data
  125. const mode = commit ? 'COMMIT MODE' : 'DRY RUN MODE'
  126. await trackProgress(
  127. `Starting script in ${mode} for Stripe ${region.toUpperCase()} region`
  128. )
  129. if (limit) {
  130. await trackProgress(`Processing limited to ${limit} subscriptions`)
  131. }
  132. // Get Stripe client for the specified region
  133. const stripeClient = getRegionClient(region)
  134. // Reset counters
  135. processedCount = 0
  136. updatedCount = 0
  137. errorCount = 0
  138. await trackProgress('Starting to iterate through Stripe subscriptions...')
  139. const listParams = {
  140. limit: 100, // Stripe's maximum limit per request
  141. expand: ['data.customer'], // Expand customer data to reduce additional API calls
  142. }
  143. let hasMore = true
  144. let startingAfter = null
  145. let totalProcessed = 0
  146. while (hasMore) {
  147. const params = { ...listParams }
  148. if (startingAfter) {
  149. params.starting_after = startingAfter
  150. }
  151. // Get batch of subscriptions
  152. const subscriptions = await stripeClient.stripe.subscriptions.list(params)
  153. await trackProgress(
  154. `Retrieved ${subscriptions.data.length} subscriptions (total processed so far: ${totalProcessed})`
  155. )
  156. // Process each subscription in the batch
  157. for (const subscription of subscriptions.data) {
  158. await processSubscription(subscription, stripeClient, commit, verbose)
  159. totalProcessed++
  160. // Check if we've hit the limit
  161. if (limit && totalProcessed >= limit) {
  162. await trackProgress(`Reached limit of ${limit} subscriptions, stopping`)
  163. hasMore = false
  164. break
  165. }
  166. // Progress update every 50 subscriptions
  167. if (totalProcessed % 50 === 0) {
  168. await trackProgress(
  169. `Progress: ${totalProcessed} processed, ${updatedCount} customers updated, ${errorCount} errors`
  170. )
  171. }
  172. }
  173. // Check if there are more subscriptions to process
  174. hasMore = hasMore && subscriptions.has_more
  175. if (hasMore && subscriptions.data.length > 0) {
  176. startingAfter = subscriptions.data[subscriptions.data.length - 1].id
  177. }
  178. // Rate limit between batch requests
  179. await rateLimitSleep()
  180. }
  181. // Final summary
  182. await trackProgress('FINAL SUMMARY:')
  183. await trackProgress(` Total subscriptions processed: ${processedCount}`)
  184. await trackProgress(
  185. ` Customers ${commit ? 'updated' : 'would be updated'}: ${updatedCount}`
  186. )
  187. await trackProgress(` Errors encountered: ${errorCount}`)
  188. if (!commit && updatedCount > 0) {
  189. await trackProgress('')
  190. await trackProgress(
  191. 'To actually perform the updates, run the script with --commit flag'
  192. )
  193. }
  194. if (errorCount > 0) {
  195. await trackProgress(
  196. 'Some errors were encountered. Check the logs above for details.'
  197. )
  198. }
  199. await trackProgress(`Script completed successfully in ${mode}`)
  200. }
  201. // Execute the script using the runner
  202. try {
  203. await scriptRunner(main)
  204. process.exit(0)
  205. } catch (error) {
  206. console.error('Script failed:', error.message)
  207. process.exit(1)
  208. }