Просмотр исходного кода

Merge pull request #24271 from overleaf/ls-script-to-update-manually-billed-users

Scripts to update terms and conditions for manually billed users

GitOrigin-RevId: 5efe43a42c3ed21779c9de698268817e2cbb5249
Liangjun Song 1 год назад
Родитель
Сommit
e44f892cb0

+ 103 - 0
services/web/scripts/recurly/get_manually_billed_users_details.mjs

@@ -0,0 +1,103 @@
+import Settings from '@overleaf/settings'
+import recurly from 'recurly'
+import fs from 'node:fs'
+import { setTimeout } from 'node:timers/promises'
+import minimist from 'minimist'
+import * as csv from 'csv'
+import Stream from 'node:stream/promises'
+
+const recurlyApiKey = Settings.apis.recurly.apiKey
+if (!recurlyApiKey) {
+  throw new Error('Recurly API key is not set in the settings')
+}
+const client = new recurly.Client(recurlyApiKey)
+
+function usage() {
+  console.error(
+    'Script to retrieve details of manually billed users from Recurly'
+  )
+  console.error('')
+  console.error('Usage:')
+  console.error(
+    '  node scripts/recurly/get_manually_billed_users_details.mjs [options]'
+  )
+  console.error('')
+  console.error('Options:')
+  console.error(
+    '  --input, -i <file>   Path to CSV file containing subscription_id, period_end, currency (can be exported from Recurly)'
+  )
+  console.error('  --output, -o <file>  Path to output CSV file')
+  console.error('')
+  console.error('Input format:')
+  console.error(
+    '  CSV file with the following columns: subscription_id, period_end, currency (header row is skipped)'
+  )
+}
+
+function parseArgs() {
+  return minimist(process.argv.slice(2), {
+    alias: { i: 'input', o: 'output' },
+    string: ['input', 'output'],
+  })
+}
+
+async function enrichRow(row) {
+  const account = await client.getAccount(`code-${row.account_code}`)
+  return {
+    ...row,
+    email: account.email,
+    first_name: account.firstName,
+    last_name: account.lastName,
+    cc_emails: account.ccEmails,
+  }
+}
+
+async function main() {
+  const { input: inputPath, output: outputPath, h, help } = parseArgs()
+  if (help || h || !inputPath || !outputPath) {
+    usage()
+    process.exit(0)
+  }
+
+  let processedCount = 0
+  await Stream.pipeline([
+    fs.createReadStream(inputPath),
+    csv.parse({ columns: true }),
+    async function* (rows) {
+      for await (const row of rows) {
+        try {
+          yield await enrichRow(row)
+        } catch (error) {
+          console.error(`Error processing subscription ${row.subscription_id}`)
+        }
+        processedCount++
+        if (processedCount % 1 === 0) {
+          console.log(`Processed ${processedCount} subscriptions`)
+        }
+        await setTimeout(1000)
+      }
+    },
+    csv.stringify({
+      header: true,
+      columns: {
+        subscription_id: 'subscription_id',
+        current_period_ends_at: 'period_end',
+        currency: 'currency',
+        email: 'email',
+        first_name: 'first_name',
+        last_name: 'last_name',
+        cc_emails: 'cc_emails',
+      },
+    }),
+    fs.createWriteStream(outputPath),
+  ])
+  console.log(`Processed ${processedCount} subscriptions in total`)
+}
+
+try {
+  await main()
+  process.exit(0)
+} catch (error) {
+  console.error(error)
+  process.exit(1)
+}

+ 103 - 0
services/web/scripts/recurly/update_terms_and_conditions_for_manually_billed_users.mjs

@@ -0,0 +1,103 @@
+import recurly from 'recurly'
+import Settings from '@overleaf/settings'
+import fs from 'node:fs'
+import minimist from 'minimist'
+import * as csv from 'csv'
+import { setTimeout } from 'node:timers/promises'
+
+const recurlyApiKey = Settings.apis.recurly.apiKey
+if (!recurlyApiKey) {
+  throw new Error('Recurly API key is not set in the settings')
+}
+const client = new recurly.Client(recurlyApiKey)
+
+function usage() {
+  console.error(
+    'Script to update terms and conditions for manually billed Recurly subscriptions'
+  )
+  console.error('')
+  console.error('Usage:')
+  console.error(
+    '  node scripts/recurly/update_terms_and_conditions_for_manually_billed_users.mjs [options]'
+  )
+  console.error('')
+  console.error('Options:')
+  console.error(
+    '  --input, -i <file>              Path to CSV file containing subscription IDs (can be exported from Recurly)'
+  )
+  console.error(
+    '  --termsAndConditions, -t <file>  Path to text file containing terms and conditions'
+  )
+  console.error('')
+  console.error('Input format:')
+  console.error(
+    '  - Subscription IDs CSV: First column contains subscription IDs (header row is skipped)'
+  )
+  console.error(
+    '  - Terms and conditions: Plain text file with the terms and conditions content'
+  )
+}
+
+function parseArgs() {
+  return minimist(process.argv.slice(2), {
+    string: ['input', 'termsAndConditions'],
+    alias: {
+      i: 'input',
+      t: 'termsAndConditions',
+    },
+  })
+}
+
+async function updateTermsAndConditionsForSubscription(
+  subscriptionId,
+  termsAndConditions
+) {
+  try {
+    await client.updateSubscription(`uuid-${subscriptionId}`, {
+      terms_and_conditions: termsAndConditions,
+    })
+  } catch (error) {
+    console.error(
+      `Error updating subscription ${subscriptionId}: ${error.message}`
+    )
+  }
+}
+
+async function main() {
+  const {
+    termsAndConditions: termsAndConditionsPath,
+    input: inputPath,
+    h,
+    help,
+  } = parseArgs()
+  if (help || h || !termsAndConditionsPath || !inputPath) {
+    usage()
+    process.exit(0)
+  }
+  const termsAndConditions = fs.readFileSync(termsAndConditionsPath, 'utf8')
+
+  const parser = csv.parse({ columns: true })
+  fs.createReadStream(inputPath).pipe(parser)
+  let processedCount = 0
+  for await (const row of parser) {
+    const subscriptionId = row.subscription_id
+    await updateTermsAndConditionsForSubscription(
+      subscriptionId,
+      termsAndConditions
+    )
+    processedCount++
+    if (processedCount % 10 === 0) {
+      console.log(`Processed ${processedCount} subscriptions`)
+    }
+    await setTimeout(1000)
+  }
+  console.log(`Processed ${processedCount} subscriptions in total`)
+}
+
+try {
+  await main()
+  process.exit(0)
+} catch (error) {
+  console.error(error)
+  process.exit(1)
+}