overleaf-cep/services/web/scripts/recurly/resync_recurly_state_single_subscription.mjs
Lucie Germain f53a13ae1e Merge pull request #26604 from overleaf/mf-resync-recurly-state-single-subscription
Add script to sync recurlyStatus.state for a single subscriptionId

GitOrigin-RevId: 69af89a44b4043d92853862baee65d8b7f84b88f
2025-06-25 08:05:29 +00:00

137 lines
3.8 KiB
JavaScript

import { Subscription } from '../../app/src/models/Subscription.js'
import RecurlyWrapper from '../../app/src/Features/Subscription/RecurlyWrapper.js'
import SubscriptionUpdater from '../../app/src/Features/Subscription/SubscriptionUpdater.js'
import minimist from 'minimist'
import { setTimeout } from 'node:timers/promises'
import util from 'node:util'
util.inspect.defaultOptions.maxArrayLength = null
const handleSyncSubscriptionError = async (subscription, error) => {
console.warn(`Errors with subscription id=${subscription._id}:`, error)
if (typeof error === 'string' && error.match(/429$/)) {
console.warn('Recurly rate limit hit (429). Waiting for 5 minutes...')
await setTimeout(1000 * 60 * 5)
return
}
if (typeof error === 'string' && error.match(/5\d\d$/)) {
console.warn('Recurly server error (5xx). Retrying in 1 minute...')
await setTimeout(1000 * 60)
await syncRecurlyStateInSubscription(subscription)
return
}
await setTimeout(80)
}
const syncRecurlyStateInSubscription = async subscription => {
let recurlySubscription
try {
recurlySubscription = await RecurlyWrapper.promises.getSubscription(
subscription.recurlySubscription_id
)
} catch (error) {
await handleSyncSubscriptionError(subscription, error)
return
}
if (!subscription.recurlyStatus) {
subscription.recurlyStatus = {}
}
if (subscription.recurlyStatus.state !== recurlySubscription.state) {
console.log(
`Mismatched recurlyStatus.state for subscription ID ${subscription._id}. ` +
`Our database: '${subscription.recurlyStatus.state || 'undefined/null'}', recurly: '${recurlySubscription.state}'.`
)
subscription.recurlyStatus.state = recurlySubscription.state
if (COMMIT) {
try {
console.log(
`Committing update for subscription ID: ${subscription._id}`
)
await SubscriptionUpdater.promises.updateSubscriptionFromRecurly(
recurlySubscription,
subscription,
{}
)
} catch (error) {
await handleSyncSubscriptionError(subscription, error)
}
console.log(
`Successfully updated subscription ID ${subscription._id} with new recurlyStatus.state: ${subscription.recurlyStatus.state}`
)
}
} else {
console.log(
`Subscription ID ${subscription._id}: recurlyStatus.state is already in sync.`
)
}
await setTimeout(80)
}
let COMMIT, SUBSCRIPTION_ID
const setup = () => {
const argv = minimist(process.argv.slice(2))
SUBSCRIPTION_ID = argv.subscriptionId
if (!SUBSCRIPTION_ID) {
console.error(
'Error: Please provide a subscription ID using --subscriptionId=<id>'
)
process.exit(1)
}
console.log(
`Attempting to sync subscription.recurlyStatus with ID: ${SUBSCRIPTION_ID}`
)
COMMIT = argv.commit !== undefined
if (!COMMIT) {
console.warn(
'Doing dry run without --commit. No database changes will be made.'
)
}
}
const run = async () => {
try {
const subscription = await Subscription.findById(SUBSCRIPTION_ID).exec()
if (!subscription) {
console.error(
`Error: Subscription with ID ${SUBSCRIPTION_ID} not found in the database.`
)
process.exit(1)
}
if (!subscription.recurlySubscription_id) {
console.error(
`Error: Subscription ID ${SUBSCRIPTION_ID} does not have a Recurly subscription ID.`
)
process.exit(1)
}
console.log(
`Found subscription: ${subscription._id}, Recurly ID: ${subscription.recurlySubscription_id}`
)
await syncRecurlyStateInSubscription(subscription)
console.log('DONE')
} catch (error) {
console.error('An unhandled error occurred during script execution:', error)
process.exit(1)
}
}
setup()
await run()
process.exit(0)