overleaf/services/web/scripts/recurly/setup_assistant_addon.js

220 lines
5.2 KiB
JavaScript
Raw Normal View History

// @ts-check
const _ = require('lodash')
const recurly = require('recurly')
const minimist = require('minimist')
const Settings = require('@overleaf/settings')
const ADD_ON_CODE = 'assistant'
const ADD_ON_NAME = 'Error Assist'
const INDIVIDUAL_PLANS = [
'student',
'collaborator',
'professional',
'paid-personal',
]
const INDIVIDUAL_VARIANTS = ['', '_free_trial_7_days']
const GROUP_PLANS = ['collaborator', 'professional']
const GROUP_SIZES = [2, 3, 4, 5, 10, 20, 50, 100, 200, 500]
const GROUP_SEGMENTS = ['educational', 'enterprise']
const ARGS = parseArgs()
const recurlyClient = new recurly.Client(Settings.apis.recurly.apiKey)
function usage() {
console.log(`Usage: setup_assistant_addon.js [--commit]
This script will copy prices from the ${ADD_ON_CODE} and ${ADD_ON_CODE}-annual
plans into the ${ADD_ON_CODE} add-on for every other plan
Options:
--commit Make actual changes to Recurly
`)
}
function parseArgs() {
const args = minimist(process.argv.slice(2), {
boolean: ['commit', 'help'],
})
if (args.help) {
usage()
process.exit(0)
}
return { commit: args.commit }
}
async function main() {
const monthlyPlan = await getPlan(ADD_ON_CODE)
if (monthlyPlan == null) {
console.error(`Monthly plan missing in Recurly: ${ADD_ON_CODE}`)
process.exit(1)
}
console.log('\nMonthly prices:')
for (const { currency, unitAmount } of monthlyPlan.currencies ?? []) {
console.log(`- ${unitAmount} ${currency}`)
}
const annualPlan = await getPlan(`${ADD_ON_CODE}-annual`)
if (annualPlan == null) {
console.error(`Annual plan missing in Recurly: ${ADD_ON_CODE}-annual`)
process.exit(1)
}
console.log('\nAnnual prices:')
for (const { currency, unitAmount } of annualPlan.currencies ?? []) {
console.log(`- ${unitAmount} ${currency}`)
}
console.log()
for (const { code, annual } of getPlanSpecs()) {
const prices = annual ? annualPlan.currencies : monthlyPlan.currencies
await setupAddOn(code, prices ?? [])
}
if (ARGS.commit) {
console.log('Done')
} else {
console.log('This was a dry run. Re-run with --commit to apply changes.')
}
}
function* getPlanSpecs() {
for (const plan of INDIVIDUAL_PLANS) {
for (const variant of INDIVIDUAL_VARIANTS) {
yield { code: `${plan}${variant}`, annual: false }
yield { code: `${plan}-annual${variant}`, annual: true }
}
}
for (const plan of GROUP_PLANS) {
for (const size of GROUP_SIZES) {
for (const segment of GROUP_SEGMENTS) {
yield { code: `group_${plan}_${size}_${segment}`, annual: true }
}
}
}
}
/**
* Create or update the assistant add-on for a plan
*
* @param {string} planCode
* @param {recurly.AddOnPricing[]} prices
*/
async function setupAddOn(planCode, prices) {
const currentAddOn = await getAddOn(planCode, ADD_ON_CODE)
const newAddOnConfig = getAddOnConfig(prices)
if (currentAddOn == null || currentAddOn.deletedAt != null) {
await createAddOn(planCode, newAddOnConfig)
} else if (_.isMatch(currentAddOn, newAddOnConfig)) {
console.log(`No changes for plan ${planCode}`)
} else {
await updateAddOn(planCode, newAddOnConfig)
}
}
/**
* Get a plan configuration from Recurly
*
* @param {string} planCode
*/
async function getPlan(planCode) {
try {
return await recurlyClient.getPlan(`code-${planCode}`)
} catch (err) {
if (err instanceof recurly.errors.NotFoundError) {
return null
} else {
throw err
}
}
}
/**
* Get an add-on configuration from Recurly
*
* @param {string} planCode
* @param {string} addOnCode
*/
async function getAddOn(planCode, addOnCode) {
try {
return await recurlyClient.getPlanAddOn(
`code-${planCode}`,
`code-${addOnCode}`
)
} catch (err) {
if (err instanceof recurly.errors.NotFoundError) {
return null
} else {
throw err
}
}
}
/**
* Create the add-on described by the given config on the given plan
*
* @param {string} planCode
* @param {recurly.AddOnCreate} config
*/
async function createAddOn(planCode, config) {
if (ARGS.commit) {
console.log(`Creating ${ADD_ON_CODE} add-on for plan ${planCode}...`)
await recurlyClient.createPlanAddOn(`code-${planCode}`, config)
} else {
console.log(`Would create ${ADD_ON_CODE} add-on for plan ${planCode}`)
}
}
/**
* Update the add-on described by the given config on the given plan
*
* @param {string} planCode
* @param {recurly.AddOnUpdate} config
*/
async function updateAddOn(planCode, config) {
if (ARGS.commit) {
console.log(`Updating ${ADD_ON_CODE} add-on for plan ${planCode}...`)
await recurlyClient.updatePlanAddOn(
`code-${planCode}`,
`code-${ADD_ON_CODE}`,
config
)
} else {
console.log(`Would update ${ADD_ON_CODE} add-on for plan ${planCode}`)
}
}
/**
* Get an assistant add-on config
*
* @param {recurly.AddOnPricing[]} prices
*/
function getAddOnConfig(prices) {
return {
code: ADD_ON_CODE,
name: ADD_ON_NAME,
optional: true,
currencies: prices.map(price =>
_.pick(
price,
'currency',
'unitAmount',
'unitAmountDecimal',
'taxInclusive'
)
),
}
}
main()
.then(() => {
process.exit(0)
})
.catch(err => {
console.error(err)
process.exit(1)
})