Merge pull request #26397 from overleaf/kh-use-new-price-lookups

[web] use new price lookup keys

GitOrigin-RevId: f4c077d946100862aaea0288d5035a34d6188e83
This commit is contained in:
Kristina
2025-06-16 14:36:33 +02:00
committed by Copybot
parent 9aa261eaf6
commit 6b38336c7b
5 changed files with 135 additions and 82 deletions

View File

@@ -1,11 +1,14 @@
// TODO: This file may be deleted when Stripe is fully implemented to all users, so, consider deleting it
// @ts-check
const Settings = require('@overleaf/settings')
const logger = require('@overleaf/logger')
/**
* @typedef {import('../../../../types/subscription/plan').RecurlyPlanCode} RecurlyPlanCode
* @typedef {import('../../../../types/subscription/plan').RecurlyAddOnCode} RecurlyAddOnCode
* @typedef {import('../../../../types/subscription/plan').StripeLookupKey} StripeLookupKey
* @typedef {import('../../../../types/subscription/plan').StripeBaseLookupKey} StripeBaseLookupKey
* @typedef {import('../../../../types/subscription/plan').Plan} Plan
* @typedef {import('../../../../types/subscription/currency').StripeCurrencyCode} StripeCurrencyCode
* @typedef {import('stripe').Stripe.Price.Recurring.Interval} BillingCycleInterval
*/
@@ -26,18 +29,21 @@ function ensurePlansAreSetupCorrectly() {
})
}
const recurlyPlanCodeToStripeLookupKey = {
collaborator: 'collaborator_may2025',
'collaborator-annual': 'collaborator_annual_may2025',
collaborator_free_trial_7_days: 'collaborator_may2025',
/**
* @type {Record<RecurlyPlanCode, StripeBaseLookupKey>}
*/
const recurlyCodeToStripeBaseLookupKey = {
collaborator: 'standard_monthly',
'collaborator-annual': 'standard_annual',
collaborator_free_trial_7_days: 'standard_monthly',
professional: 'professional_may2025',
'professional-annual': 'professional_annual_may2025',
professional_free_trial_7_days: 'professional_may2025',
professional: 'professional_monthly',
'professional-annual': 'professional_annual',
professional_free_trial_7_days: 'professional_monthly',
student: 'student_may2025',
'student-annual': 'student_annual_may2025',
student_free_trial_7_days: 'student_may2025',
student: 'student_monthly',
'student-annual': 'student_annual',
student_free_trial_7_days: 'student_monthly',
// TODO: change all group plans' lookup_keys to match the UK account after they have been added
group_collaborator: 'group_standard_enterprise',
@@ -45,41 +51,46 @@ const recurlyPlanCodeToStripeLookupKey = {
group_professional: 'group_professional_enterprise',
group_professional_educational: 'group_professional_educational',
assistant: 'assistant_may2025',
'assistant-annual': 'assistant_annual_may2025',
assistant: 'assistant_monthly',
'assistant-annual': 'assistant_annual',
}
/**
*
* @param {RecurlyPlanCode} recurlyPlanCode
* @returns {StripeLookupKey}
*/
function mapRecurlyPlanCodeToStripeLookupKey(recurlyPlanCode) {
return recurlyPlanCodeToStripeLookupKey[recurlyPlanCode]
}
const LATEST_STRIPE_LOOKUP_KEY_VERSION = 'jun2025'
/**
* Build the Stripe lookup key, will be in this format:
* `${productCode}_${billingInterval}_${latestVersion}_${currency}`
* (for example: 'assistant_annual_jun2025_clp')
*
* @param {RecurlyAddOnCode} recurlyAddOnCode
* @param {BillingCycleInterval} billingCycleInterval
* @param {RecurlyPlanCode} recurlyCode
* @param {StripeCurrencyCode} currency
* @param {BillingCycleInterval} [billingCycleInterval] -- needed for handling 'assistant' add-on
* @returns {StripeLookupKey|null}
*/
function mapRecurlyAddOnCodeToStripeLookupKey(
recurlyAddOnCode,
billingCycleInterval
) {
function buildStripeLookupKey(recurlyCode, currency, billingCycleInterval) {
let stripeBaseLookupKey = recurlyCodeToStripeBaseLookupKey[recurlyCode]
// Recurly always uses 'assistant' as the code regardless of the subscription duration
if (recurlyAddOnCode === 'assistant') {
if (recurlyCode === 'assistant' && billingCycleInterval) {
if (billingCycleInterval === 'month') {
return 'assistant_may2025'
stripeBaseLookupKey = 'assistant_monthly'
}
if (billingCycleInterval === 'year') {
return 'assistant_annual_may2025'
stripeBaseLookupKey = 'assistant_annual'
}
}
return null
if (stripeBaseLookupKey == null) {
return null
}
return `${stripeBaseLookupKey}_${LATEST_STRIPE_LOOKUP_KEY_VERSION}_${currency}`
}
/**
* @typedef {{ planType: 'individual' | 'group' | 'student' | null, period: 'annual' | 'monthly' }} PlanTypeAndPeriod
* @type {Record<RecurlyPlanCode, PlanTypeAndPeriod>}
*/
const recurlyPlanCodeToPlanTypeAndPeriod = {
collaborator: { planType: 'individual', period: 'monthly' },
'collaborator-annual': { planType: 'individual', period: 'annual' },
@@ -106,14 +117,17 @@ const recurlyPlanCodeToPlanTypeAndPeriod = {
}
/**
*
* @param {RecurlyPlanCode} recurlyPlanCode
* @returns {{ planType: 'individual' | 'group' | 'student' | null, period: 'annual' | 'monthly'}}
* @returns {PlanTypeAndPeriod}
*/
function getPlanTypeAndPeriodFromRecurlyPlanCode(recurlyPlanCode) {
return recurlyPlanCodeToPlanTypeAndPeriod[recurlyPlanCode]
}
/**
* @param {string|null} [planCode]
* @returns {Plan|null}
*/
function findLocalPlanInSettings(planCode) {
for (const plan of Settings.plans) {
if (plan.planCode === planCode) {
@@ -126,7 +140,6 @@ function findLocalPlanInSettings(planCode) {
module.exports = {
ensurePlansAreSetupCorrectly,
findLocalPlanInSettings,
mapRecurlyPlanCodeToStripeLookupKey,
mapRecurlyAddOnCodeToStripeLookupKey,
buildStripeLookupKey,
getPlanTypeAndPeriodFromRecurlyPlanCode,
}

View File

@@ -839,7 +839,7 @@ function makeChangePreview(
paymentMethod: paymentMethod?.toString(),
netTerms: subscription.netTerms,
nextPlan: {
annual: nextPlan.annual ?? false,
annual: nextPlan?.annual ?? false,
},
nextInvoice: {
date: subscription.periodEnd.toISOString(),

View File

@@ -66,6 +66,7 @@ async function _getGroupSubscriptionPlanCode(userId) {
const plan = PlansLocator.findLocalPlanInSettings(subscription.planCode)
if (
plan &&
plan.features &&
FeaturesHelper.isFeatureSetBetter(plan.features, bestFeatures)
) {
bestPlanCode = plan.planCode

View File

@@ -50,84 +50,111 @@ describe('PlansLocator', function () {
})
})
describe('mapRecurlyPlanCodeToStripeLookupKey', function () {
describe('buildStripeLookupKey', function () {
it('should map "collaborator" plan code to stripe lookup keys', function () {
const planCode = 'collaborator'
const lookupKey =
this.PlansLocator.mapRecurlyPlanCodeToStripeLookupKey(planCode)
expect(lookupKey).to.equal('collaborator_may2025')
const currency = 'eur'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
planCode,
currency
)
expect(lookupKey).to.equal('standard_monthly_jun2025_eur')
})
it('should map "collaborator_free_trial_7_days" plan code to stripe lookup keys', function () {
const planCode = 'collaborator_free_trial_7_days'
const lookupKey =
this.PlansLocator.mapRecurlyPlanCodeToStripeLookupKey(planCode)
expect(lookupKey).to.equal('collaborator_may2025')
const currency = 'eur'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
planCode,
currency
)
expect(lookupKey).to.equal('standard_monthly_jun2025_eur')
})
it('should map "collaborator-annual" plan code to stripe lookup keys', function () {
const planCode = 'collaborator-annual'
const lookupKey =
this.PlansLocator.mapRecurlyPlanCodeToStripeLookupKey(planCode)
expect(lookupKey).to.equal('collaborator_annual_may2025')
const currency = 'eur'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
planCode,
currency
)
expect(lookupKey).to.equal('standard_annual_jun2025_eur')
})
it('should map "professional" plan code to stripe lookup keys', function () {
const planCode = 'professional'
const lookupKey =
this.PlansLocator.mapRecurlyPlanCodeToStripeLookupKey(planCode)
expect(lookupKey).to.equal('professional_may2025')
const currency = 'eur'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
planCode,
currency
)
expect(lookupKey).to.equal('professional_monthly_jun2025_eur')
})
it('should map "professional_free_trial_7_days" plan code to stripe lookup keys', function () {
const planCode = 'professional_free_trial_7_days'
const lookupKey =
this.PlansLocator.mapRecurlyPlanCodeToStripeLookupKey(planCode)
expect(lookupKey).to.equal('professional_may2025')
const currency = 'eur'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
planCode,
currency
)
expect(lookupKey).to.equal('professional_monthly_jun2025_eur')
})
it('should map "professional-annual" plan code to stripe lookup keys', function () {
const planCode = 'professional-annual'
const lookupKey =
this.PlansLocator.mapRecurlyPlanCodeToStripeLookupKey(planCode)
expect(lookupKey).to.equal('professional_annual_may2025')
const currency = 'eur'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
planCode,
currency
)
expect(lookupKey).to.equal('professional_annual_jun2025_eur')
})
it('should map "student" plan code to stripe lookup keys', function () {
const planCode = 'student'
const lookupKey =
this.PlansLocator.mapRecurlyPlanCodeToStripeLookupKey(planCode)
expect(lookupKey).to.equal('student_may2025')
const currency = 'eur'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
planCode,
currency
)
expect(lookupKey).to.equal('student_monthly_jun2025_eur')
})
it('shoult map "student_free_trial_7_days" plan code to stripe lookup keys', function () {
const planCode = 'student_free_trial_7_days'
const lookupKey =
this.PlansLocator.mapRecurlyPlanCodeToStripeLookupKey(planCode)
expect(lookupKey).to.equal('student_may2025')
const currency = 'eur'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
planCode,
currency
)
expect(lookupKey).to.equal('student_monthly_jun2025_eur')
})
it('should map "student-annual" plan code to stripe lookup keys', function () {
const planCode = 'student-annual'
const lookupKey =
this.PlansLocator.mapRecurlyPlanCodeToStripeLookupKey(planCode)
expect(lookupKey).to.equal('student_annual_may2025')
const currency = 'eur'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
planCode,
currency
)
expect(lookupKey).to.equal('student_annual_jun2025_eur')
})
})
describe('mapRecurlyAddOnCodeToStripeLookupKey', function () {
it('should return null for unknown add-on codes', function () {
const billingCycleInterval = 'month'
const addOnCode = 'unknown_addon'
const lookupKey = this.PlansLocator.mapRecurlyAddOnCodeToStripeLookupKey(
const currency = 'gbp'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
addOnCode,
currency,
billingCycleInterval
)
expect(lookupKey).to.equal(null)
})
it('should handle missing input', function () {
const lookupKey = this.PlansLocator.mapRecurlyAddOnCodeToStripeLookupKey(
const lookupKey = this.PlansLocator.buildStripeLookupKey(
undefined,
undefined
)
@@ -137,21 +164,25 @@ describe('PlansLocator', function () {
it('returns the key for a monthly AI assist add-on', function () {
const billingCycleInterval = 'month'
const addOnCode = this.AI_ADD_ON_CODE
const lookupKey = this.PlansLocator.mapRecurlyAddOnCodeToStripeLookupKey(
const currency = 'gbp'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
addOnCode,
currency,
billingCycleInterval
)
expect(lookupKey).to.equal('assistant_may2025')
expect(lookupKey).to.equal('assistant_monthly_jun2025_gbp')
})
it('returns the key for an annual AI assist add-on', function () {
const billingCycleInterval = 'year'
const addOnCode = this.AI_ADD_ON_CODE
const lookupKey = this.PlansLocator.mapRecurlyAddOnCodeToStripeLookupKey(
const currency = 'gbp'
const lookupKey = this.PlansLocator.buildStripeLookupKey(
addOnCode,
currency,
billingCycleInterval
)
expect(lookupKey).to.equal('assistant_annual_may2025')
expect(lookupKey).to.equal('assistant_annual_jun2025_gbp')
})
})

View File

@@ -1,3 +1,5 @@
import { StripeCurrencyCode } from './currency'
type Features = {
collaborators: number
compileGroup: string
@@ -60,6 +62,7 @@ export type Plan = {
name: string
planCode: string
price_in_cents: number
canUseFlexibleLicensing?: boolean
}
export type PriceForDisplayData = {
@@ -90,17 +93,22 @@ export type RecurlyPlanCode =
export type RecurlyAddOnCode = 'assistant'
export type StripeLookupKey =
| 'collaborator_may2025'
| 'collaborator_annual_may2025'
| 'professional_may2025'
| 'professional_annual_may2025'
| 'student_may2025'
| 'student_annual_may2025'
export type StripeBaseLookupKey =
| 'standard_monthly'
| 'standard_annual'
| 'professional_monthly'
| 'professional_annual'
| 'student_monthly'
| 'student_annual'
| 'assistant_annual'
| 'assistant_monthly'
// TODO: change all group plans' lookup_keys to match the UK account after they have been added
| 'group_standard_enterprise'
| 'group_professional_enterprise'
| 'group_standard_educational'
| 'group_professional_educational'
| 'assistant_annual_may2025'
| 'assistant_may2025'
export type StripeLookupKeyVersion = 'jun2025'
export type StripeLookupKey =
`${StripeBaseLookupKey}_${StripeLookupKeyVersion}_${StripeCurrencyCode}`