فهرست منبع

Merge pull request #31218 from overleaf/kh-update-assistant-prices

* update Stripe AI assist prices
* add soft archive option to the archiving prices script

GitOrigin-RevId: 3f0b66cf227e31e03fb3337b3cb4c1b6a82bd1db
Kristina 6 ماه پیش
والد
کامیت
0cabc81d46

+ 1 - 1
services/web/app/src/Features/Subscription/PlansLocator.mjs

@@ -56,7 +56,7 @@ const recurlyCodeToStripeBaseLookupKey = {
 }
 
 // Keep in sync with StripeLookupKeyVersion in types/subscription/plan.ts
-const LATEST_STRIPE_LOOKUP_KEY_VERSION = 'nov2025'
+const LATEST_STRIPE_LOOKUP_KEY_VERSION = 'feb2026'
 
 /**
  * Build the Stripe lookup key, will be in this format:

+ 29 - 15
services/web/scripts/stripe/archive_prices_by_version_key.mjs

@@ -10,7 +10,10 @@
  * Options:
  *   --region           Required. Stripe region to process (us or uk)
  *   --version          Required. Version key to match in lookup keys (e.g., 'jul2025')
- *   --action           Required. Action to perform: 'archive' or 'unarchive'
+ *   --action           Required. Action to perform: 'archive', 'soft-archive', or 'unarchive'
+ *                      - archive: Set prices as inactive (if not associated with any active subscriptions) and add [ARCHIVED] to nickname
+ *                      - soft-archive: Only add [ARCHIVED] to nickname, keep prices active
+ *                      - unarchive: Reactivate prices and remove [ARCHIVED] from nickname
  *   --commit           Actually perform the updates (default: dry-run mode)
  *
  * Examples:
@@ -20,6 +23,9 @@
  *   # Commit archive prices with version 'jul2025' in UK region
  *   node scripts/stripe/archive_prices_by_version_key.mjs --region uk --version jul2025 --action archive --commit
  *
+ *   # Soft archive: only mark in nickname, keep prices active
+ *   node scripts/stripe/archive_prices_by_version_key.mjs --region us --version jul2025 --action soft-archive --commit
+ *
  *   # Unarchive prices with version 'jul2025'
  *   node scripts/stripe/archive_prices_by_version_key.mjs --region us --version jul2025 --action unarchive --commit
  */
@@ -36,7 +42,7 @@ import { getRegionClient } from '../../modules/subscriptions/app/src/StripeClien
 const paramsSchema = z.object({
   region: z.enum(['us', 'uk']),
   version: z.string(),
-  action: z.enum(['archive', 'unarchive']),
+  action: z.enum(['archive', 'soft-archive', 'unarchive']),
   commit: z.boolean().default(false),
 })
 
@@ -149,6 +155,8 @@ async function fetchPricesByVersion(stripe, version, trackProgress) {
  */
 async function processPrices(prices, stripe, action, commit, trackProgress) {
   const targetActiveStatus = action === 'unarchive'
+  const isSoftArchive = action === 'soft-archive'
+  const isArchiving = action === 'archive' || action === 'soft-archive'
   const results = {
     processed: 0,
     skipped: 0,
@@ -160,10 +168,9 @@ async function processPrices(prices, stripe, action, commit, trackProgress) {
   const pricesToProcess = []
   for (const price of prices) {
     const hasArchivedNickname = price.nickname?.includes('[ARCHIVED]')
-    const alreadyInDesiredState =
-      action === 'archive'
-        ? hasArchivedNickname
-        : price.active && !hasArchivedNickname
+    const alreadyInDesiredState = isArchiving
+      ? hasArchivedNickname
+      : price.active && !hasArchivedNickname
 
     if (alreadyInDesiredState) {
       await trackProgress(
@@ -194,11 +201,12 @@ async function processPrices(prices, stripe, action, commit, trackProgress) {
       if (commit) {
         const updateParams = {}
 
-        if (!hasActiveSubscriptions) {
+        // only update active status if not soft-archiving and price doesn't have active subscriptions
+        if (!isSoftArchive && !hasActiveSubscriptions) {
           updateParams.active = targetActiveStatus
         }
 
-        if (action === 'archive' && !price.nickname?.includes('[ARCHIVED]')) {
+        if (isArchiving && !price.nickname?.includes('[ARCHIVED]')) {
           updateParams.nickname = price.nickname
             ? `[ARCHIVED] ${price.nickname}`
             : '[ARCHIVED]'
@@ -209,18 +217,24 @@ async function processPrices(prices, stripe, action, commit, trackProgress) {
 
         if (Object.keys(updateParams).length > 0) {
           await stripe.prices.update(price.id, updateParams)
-          const statusNote = hasActiveSubscriptions
-            ? '(nickname only - has active subscriptions)'
-            : ''
+          let statusNote = ''
+          if (hasActiveSubscriptions) {
+            statusNote = '(soft archived - has active subscriptions)'
+          } else if (isSoftArchive) {
+            statusNote = '(soft archived)'
+          }
           await trackProgress(
-            `${action === 'archive' ? 'Archived' : 'Unarchived'} price: ${price.id} (${price.lookup_key}) ${statusNote}`
+            `${isArchiving ? 'Archived' : 'Unarchived'} price: ${price.id} (${price.lookup_key}) ${statusNote}`
           )
           await rateLimitSleep()
         }
       } else {
-        const statusNote = hasActiveSubscriptions
-          ? '(nickname only - has active subscriptions)'
-          : ''
+        let statusNote = ''
+        if (hasActiveSubscriptions) {
+          statusNote = '(soft archived - has active subscriptions)'
+        } else if (isSoftArchive) {
+          statusNote = '(soft archived)'
+        }
         await trackProgress(
           `[DRY RUN] Would ${action} price: ${price.id} (${price.lookup_key}) ${statusNote}`
         )

+ 11 - 11
services/web/test/unit/src/Subscription/PlansLocator.test.mjs

@@ -57,7 +57,7 @@ describe('PlansLocator', function () {
         planCode,
         currency
       )
-      expect(lookupKey).to.equal('standard_monthly_nov2025_eur')
+      expect(lookupKey).to.equal('standard_monthly_feb2026_eur')
     })
 
     it('should map "collaborator_free_trial_7_days" plan code to stripe lookup keys', function (ctx) {
@@ -67,7 +67,7 @@ describe('PlansLocator', function () {
         planCode,
         currency
       )
-      expect(lookupKey).to.equal('standard_monthly_nov2025_eur')
+      expect(lookupKey).to.equal('standard_monthly_feb2026_eur')
     })
 
     it('should map "collaborator-annual" plan code to stripe lookup keys', function (ctx) {
@@ -77,7 +77,7 @@ describe('PlansLocator', function () {
         planCode,
         currency
       )
-      expect(lookupKey).to.equal('standard_annual_nov2025_eur')
+      expect(lookupKey).to.equal('standard_annual_feb2026_eur')
     })
 
     it('should map "professional" plan code to stripe lookup keys', function (ctx) {
@@ -87,7 +87,7 @@ describe('PlansLocator', function () {
         planCode,
         currency
       )
-      expect(lookupKey).to.equal('professional_monthly_nov2025_eur')
+      expect(lookupKey).to.equal('professional_monthly_feb2026_eur')
     })
 
     it('should map "professional_free_trial_7_days" plan code to stripe lookup keys', function (ctx) {
@@ -97,7 +97,7 @@ describe('PlansLocator', function () {
         planCode,
         currency
       )
-      expect(lookupKey).to.equal('professional_monthly_nov2025_eur')
+      expect(lookupKey).to.equal('professional_monthly_feb2026_eur')
     })
 
     it('should map "professional-annual" plan code to stripe lookup keys', function (ctx) {
@@ -107,7 +107,7 @@ describe('PlansLocator', function () {
         planCode,
         currency
       )
-      expect(lookupKey).to.equal('professional_annual_nov2025_eur')
+      expect(lookupKey).to.equal('professional_annual_feb2026_eur')
     })
 
     it('should map "student" plan code to stripe lookup keys', function (ctx) {
@@ -117,7 +117,7 @@ describe('PlansLocator', function () {
         planCode,
         currency
       )
-      expect(lookupKey).to.equal('student_monthly_nov2025_eur')
+      expect(lookupKey).to.equal('student_monthly_feb2026_eur')
     })
 
     it('shoult map "student_free_trial_7_days" plan code to stripe lookup keys', function (ctx) {
@@ -127,7 +127,7 @@ describe('PlansLocator', function () {
         planCode,
         currency
       )
-      expect(lookupKey).to.equal('student_monthly_nov2025_eur')
+      expect(lookupKey).to.equal('student_monthly_feb2026_eur')
     })
 
     it('should map "student-annual" plan code to stripe lookup keys', function (ctx) {
@@ -137,7 +137,7 @@ describe('PlansLocator', function () {
         planCode,
         currency
       )
-      expect(lookupKey).to.equal('student_annual_nov2025_eur')
+      expect(lookupKey).to.equal('student_annual_feb2026_eur')
     })
 
     it('should return null for unknown add-on codes', function (ctx) {
@@ -169,7 +169,7 @@ describe('PlansLocator', function () {
         currency,
         billingCycleInterval
       )
-      expect(lookupKey).to.equal('assistant_monthly_nov2025_gbp')
+      expect(lookupKey).to.equal('assistant_monthly_feb2026_gbp')
     })
 
     it('returns the key for an annual AI assist add-on', function (ctx) {
@@ -181,7 +181,7 @@ describe('PlansLocator', function () {
         currency,
         billingCycleInterval
       )
-      expect(lookupKey).to.equal('assistant_annual_nov2025_gbp')
+      expect(lookupKey).to.equal('assistant_annual_feb2026_gbp')
     })
   })
 

+ 1 - 1
services/web/types/subscription/plan.ts

@@ -109,7 +109,7 @@ export type StripeBaseLookupKey =
   | 'group_professional_educational'
 
 // Keep in sync with LATEST_STRIPE_LOOKUP_KEY_VERSION in PlansLocator.mjs
-export type StripeLookupKeyVersion = 'nov2025'
+export type StripeLookupKeyVersion = 'feb2026'
 
 export type StripeLookupKey =
   `${StripeBaseLookupKey}_${StripeLookupKeyVersion}_${StripeCurrencyCode}`