[web] fix source on analytics mapping in finalization script (#31143)

* fix source on analytics mapping in finalization script
* add script to redo mappings for finalized subscriptions

GitOrigin-RevId: a62af6c6c7ae11f9bea62ed643c8dfa7595ba99b
This commit is contained in:
Kristina
2026-01-30 09:06:17 +00:00
committed by Copybot
parent d6fbed2a74
commit 3000a9108d
2 changed files with 245 additions and 1 deletions
@@ -468,7 +468,7 @@ async function performCutover(
AccountMappingHelper.generateSubscriptionToStripeMapping(
mongoSubscription._id,
stripeSubscription.id,
stripeSubscription.service
stripeClient.serviceName
)
)
} catch (err) {
@@ -0,0 +1,244 @@
#!/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)
}