Browse Source

[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
Antoine Clausse 5 months ago
parent
commit
b6c38ef5d0

+ 2 - 1
services/web/app/src/Features/Project/ProjectListController.mjs

@@ -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

+ 7 - 0
services/web/app/src/Features/Subscription/SubscriptionController.mjs

@@ -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

+ 12 - 2
services/web/app/src/infrastructure/GeoIpLookup.mjs

@@ -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

+ 1 - 0
services/web/frontend/extracted-translations.json

@@ -1812,6 +1812,7 @@
   "strongly_disagree": "",
   "student": "",
   "student_disclaimer": "",
+  "student_discount": "",
   "subject": "",
   "subject_area": "",
   "subject_to_additional_vat": "",

+ 2 - 1
services/web/locales/en.json

@@ -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 you’re eligible for the discount.",
+  "student_discount": "Student discount",
   "student_verification_required": "Student verification required",
   "students": "Students",
   "subject": "Subject",