mirror of
https://github.com/yu-i-i/overleaf-cep.git
synced 2026-05-23 17:19:37 +02:00
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
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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}`
|
||||
)
|
||||
|
||||
@@ -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')
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@@ -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}`
|
||||
|
||||
Reference in New Issue
Block a user