[web] Show student discount pre checkout (#31820)

* Compute student discount from prices

* Add presentational discount in the checkout page

* Put student discount row behind feature flag

* Update code and tests to clarify that `currency` is always defined

* Introduce `usePlanPriceItems` to normalize the list

* Simplify `usePlanPriceItems`

Co-authored-by: Olzhas Askar <olzhas.askar@overleaf.com>

* Remove student discount percent

* Update Standard Monthly/Annual names in the checkout page

* Simplify `getRecommendedCurrency` mock

* Fix testid: price-summary-plan

* Add test on stripe-price-summary

* Add `Math.abs` on accessibility discounted info (!)

---------

Co-authored-by: Olzhas Askar <olzhas.askar@overleaf.com>
GitOrigin-RevId: f297eab4b6abd6a84842054667a3734cb33866fe
This commit is contained in:
Antoine Clausse
2026-02-26 16:24:53 +01:00
committed by Copybot
parent 65180f3fa0
commit b6c38ef5d0
5 changed files with 24 additions and 4 deletions
@@ -496,7 +496,8 @@ async function projectListPage(req, res, next) {
showInrGeoBanner = true
}
showLATAMBanner = ['MX', 'CO', 'CL', 'PE'].includes(countryCode)
showLATAMBanner =
!!countryCode && ['MX', 'CO', 'CL', 'PE'].includes(countryCode)
// LATAM Banner needs to know which currency to display
if (showLATAMBanner) {
recommendedCurrency = currencyCode
@@ -48,6 +48,10 @@ const {
const SUBSCRIPTION_PAUSED_REDIRECT_PATH =
'/user/subscription?redirect-reason=subscription-paused'
/**
* @typedef {import('../../../../types/subscription/currency').CurrencyCode} CurrencyCode
*/
/**
* Check if a Stripe subscription is currently paused
* @param {Object} subscription - The subscription object
@@ -972,6 +976,9 @@ async function refreshUserFeatures(req, res) {
res.sendStatus(200)
}
/**
* @returns {Promise<{currency: CurrencyCode, recommendedCurrency: CurrencyCode, countryCode: string|undefined}>}
*/
async function getRecommendedCurrency(req, res) {
const userId = SessionManager.getLoggedInUserId(req.session)
let ip = req.ip
@@ -1,9 +1,16 @@
// @ts-check
import settings from '@overleaf/settings'
import logger from '@overleaf/logger'
import { fetchJson } from '@overleaf/fetch-utils'
const DEFAULT_CURRENCY_CODE = 'USD'
/**
* @typedef {import('../../../types/subscription/currency').CurrencyCode} CurrencyCode
*/
const DEFAULT_CURRENCY_CODE = /** @type {const} */ 'USD'
/** @type {Record<string, CurrencyCode>} */
const currencyMappings = {
GB: 'GBP',
US: 'USD',
@@ -84,6 +91,9 @@ async function getDetails(ip, callback) {
return await fetchJson(url, { signal: AbortSignal.timeout(1_000) })
}
/**
* @returns {Promise<{currencyCode: CurrencyCode, countryCode: string|undefined}>}
*/
async function getCurrencyCode(ip) {
let ipDetails
try {
@@ -93,7 +103,7 @@ async function getCurrencyCode(ip) {
{ err, ip },
`problem getting currencyCode for ip, defaulting to ${DEFAULT_CURRENCY_CODE}`
)
return { currencyCode: DEFAULT_CURRENCY_CODE }
return { currencyCode: DEFAULT_CURRENCY_CODE, countryCode: undefined }
}
const countryCode =
ipDetails && ipDetails.country_code
@@ -1812,6 +1812,7 @@
"strongly_disagree": "",
"student": "",
"student_disclaimer": "",
"student_discount": "",
"subject": "",
"subject_area": "",
"subject_to_additional_vat": "",
+2 -1
View File
@@ -191,7 +191,7 @@
"apply_educational_discount": "Apply educational discount",
"apply_educational_discount_description": "40% discount for groups using __appName__ for teaching",
"apply_educational_discount_description_with_group_discount": "Get a total of 40% off for groups using __appName__ for teaching",
"apply_educational_discount_individual": "Apply 50% student discount",
"apply_student_discount": "Apply student discount",
"apply_suggestion": "Apply suggestion",
"april": "April",
"archive": "Archive",
@@ -2305,6 +2305,7 @@
"strongly_disagree": "Strongly disagree",
"student": "Student",
"student_disclaimer": "The educational discount applies to all students at secondary and postsecondary institutions (schools and universities). We may contact you to confirm that youre eligible for the discount.",
"student_discount": "Student discount",
"student_verification_required": "Student verification required",
"students": "Students",
"subject": "Subject",