Преглед изворни кода

Merge pull request #20898 from overleaf/em-ai-add-on-setup-recurly

Script for setting up the Assistant add-on in Recurly

GitOrigin-RevId: 25a94961e4068456795b6be6b5e047efc65363fa
Eric Mc Sween пре 1 година
родитељ
комит
13ecddaef1
1 измењених фајлова са 219 додато и 0 уклоњено
  1. 219 0
      services/web/scripts/recurly/setup_assistant_addon.js

+ 219 - 0
services/web/scripts/recurly/setup_assistant_addon.js

@@ -0,0 +1,219 @@
+// @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)
+  })