Просмотр исходного кода

Merge pull request #24417 from overleaf/mf-implement-stripe-hosted-checkout-split-test

[web] Implement stripe hosted checkout with split test

GitOrigin-RevId: 25e5ff2a46135f402cdf479623ab38c858c5640c
M Fahru 1 год назад
Родитель
Сommit
9eb84d6ad5

+ 53 - 0
services/web/app/src/Features/Subscription/PlansLocator.js

@@ -1,6 +1,12 @@
+// TODO: This file may be deleted when Stripe is fully implemented to all users, so, consider deleting it
 const Settings = require('@overleaf/settings')
 const Settings = require('@overleaf/settings')
 const logger = require('@overleaf/logger')
 const logger = require('@overleaf/logger')
 
 
+/**
+ * @typedef {import('../../../../types/subscription/plan').RecurlyPlanCode} RecurlyPlanCode
+ * @typedef {import('../../../../types/subscription/plan').StripeLookupKey} StripeLookupKey
+ */
+
 function ensurePlansAreSetupCorrectly() {
 function ensurePlansAreSetupCorrectly() {
   Settings.plans.forEach(plan => {
   Settings.plans.forEach(plan => {
     if (typeof plan.price_in_cents !== 'number') {
     if (typeof plan.price_in_cents !== 'number') {
@@ -18,6 +24,51 @@ function ensurePlansAreSetupCorrectly() {
   })
   })
 }
 }
 
 
+const recurlyPlanCodeToStripeLookupKey = {
+  'professional-annual': 'professional_annual',
+  professional: 'professional_monthly',
+  professional_free_trial_7_days: 'professional_monthly',
+  'collaborator-annual': 'standard_annual',
+  collaborator: 'standard_monthly',
+  collaborator_free_trial_7_days: 'standard_monthly',
+  'student-annual': 'student_annual',
+  student: 'student_monthly',
+  student_free_trial_7_days: 'student_monthly',
+}
+
+/**
+ *
+ * @param {RecurlyPlanCode} recurlyPlanCode
+ * @returns {StripeLookupKey}
+ */
+function mapRecurlyPlanCodeToStripeLookupKey(recurlyPlanCode) {
+  return recurlyPlanCodeToStripeLookupKey[recurlyPlanCode]
+}
+
+const recurlyPlanCodeToPlanTypeAndPeriod = {
+  collaborator: { planType: 'standard', period: 'monthly' },
+  collaborator_free_trial_7_days: { planType: 'standard', period: 'monthly' },
+  'collaborator-annual': { planType: 'standard', period: 'annual' },
+  professional: { planType: 'professional', period: 'monthly' },
+  professional_free_trial_7_days: {
+    planType: 'professional',
+    period: 'monthly',
+  },
+  'professional-annual': { planType: 'professional', period: 'annual' },
+  student: { planType: 'student', period: 'monthly' },
+  student_free_trial_7_days: { planType: 'student', period: 'monthly' },
+  'student-annual': { planType: 'student', period: 'annual' },
+}
+
+/**
+ *
+ * @param {RecurlyPlanCode} recurlyPlanCode
+ * @returns {{ planType: 'standard' | 'professional' | 'student', period: 'annual' | 'monthly'}}
+ */
+function getPlanTypeAndPeriodFromRecurlyPlanCode(recurlyPlanCode) {
+  return recurlyPlanCodeToPlanTypeAndPeriod[recurlyPlanCode]
+}
+
 function findLocalPlanInSettings(planCode) {
 function findLocalPlanInSettings(planCode) {
   for (const plan of Settings.plans) {
   for (const plan of Settings.plans) {
     if (plan.planCode === planCode) {
     if (plan.planCode === planCode) {
@@ -30,4 +81,6 @@ function findLocalPlanInSettings(planCode) {
 module.exports = {
 module.exports = {
   ensurePlansAreSetupCorrectly,
   ensurePlansAreSetupCorrectly,
   findLocalPlanInSettings,
   findLocalPlanInSettings,
+  mapRecurlyPlanCodeToStripeLookupKey,
+  getPlanTypeAndPeriodFromRecurlyPlanCode,
 }
 }

+ 8 - 1
services/web/app/src/Features/Subscription/SubscriptionController.js

@@ -629,7 +629,7 @@ async function getRecommendedCurrency(req, res) {
     ip = req.query.ip
     ip = req.query.ip
   }
   }
   const currencyLookup = await GeoIpLookup.promises.getCurrencyCode(ip)
   const currencyLookup = await GeoIpLookup.promises.getCurrencyCode(ip)
-  const countryCode = currencyLookup.countryCode
+  let countryCode = currencyLookup.countryCode
   const recommendedCurrency = currencyLookup.currencyCode
   const recommendedCurrency = currencyLookup.currencyCode
 
 
   let currency = null
   let currency = null
@@ -640,6 +640,13 @@ async function getRecommendedCurrency(req, res) {
     currency = recommendedCurrency
     currency = recommendedCurrency
   }
   }
 
 
+  const queryCountryCode = req.query.countryCode?.toUpperCase()
+
+  // only enable countryCode testing flag on staging or dev environments
+  if (queryCountryCode && process.env.NODE_ENV !== 'production') {
+    countryCode = queryCountryCode
+  }
+
   return {
   return {
     currency,
     currency,
     recommendedCurrency,
     recommendedCurrency,

+ 146 - 0
services/web/test/unit/src/Subscription/PlansLocatorTests.js

@@ -48,4 +48,150 @@ describe('PlansLocator', function () {
       expect(plan).to.be.a('null')
       expect(plan).to.be.a('null')
     })
     })
   })
   })
+
+  describe('mapRecurlyPlanCodeToStripeLookupKey', 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('standard_monthly')
+    })
+
+    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('standard_monthly')
+    })
+
+    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('standard_annual')
+    })
+
+    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_monthly')
+    })
+
+    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_monthly')
+    })
+
+    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')
+    })
+
+    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_monthly')
+    })
+
+    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_monthly')
+    })
+
+    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')
+    })
+  })
+
+  describe('getPlanTypeAndPeriodFromRecurlyPlanCode', function () {
+    it('should return the plan type and period for "collaborator"', function () {
+      const { planType, period } =
+        this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
+          'collaborator'
+        )
+      expect(planType).to.equal('standard')
+      expect(period).to.equal('monthly')
+    })
+
+    it('should return the plan type and period for "collaborator_free_trial_7_days"', function () {
+      const { planType, period } =
+        this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
+          'collaborator_free_trial_7_days'
+        )
+      expect(planType).to.equal('standard')
+      expect(period).to.equal('monthly')
+    })
+
+    it('should return the plan type and period for "collaborator-annual"', function () {
+      const { planType, period } =
+        this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
+          'collaborator-annual'
+        )
+      expect(planType).to.equal('standard')
+      expect(period).to.equal('annual')
+    })
+
+    it('should return the plan type and period for "professional"', function () {
+      const { planType, period } =
+        this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
+          'professional'
+        )
+      expect(planType).to.equal('professional')
+      expect(period).to.equal('monthly')
+    })
+
+    it('should return the plan type and period for "professional_free_trial_7_days"', function () {
+      const { planType, period } =
+        this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
+          'professional_free_trial_7_days'
+        )
+      expect(planType).to.equal('professional')
+      expect(period).to.equal('monthly')
+    })
+
+    it('should return the plan type and period for "professional-annual"', function () {
+      const { planType, period } =
+        this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
+          'professional-annual'
+        )
+      expect(planType).to.equal('professional')
+      expect(period).to.equal('annual')
+    })
+
+    it('should return the plan type and period for "student"', function () {
+      const { planType, period } =
+        this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode('student')
+      expect(planType).to.equal('student')
+      expect(period).to.equal('monthly')
+    })
+
+    it('should return the plan type and period for "student_free_trial_7_days"', function () {
+      const { planType, period } =
+        this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
+          'student_free_trial_7_days'
+        )
+      expect(planType).to.equal('student')
+      expect(period).to.equal('monthly')
+    })
+
+    it('should return the plan type and period for "student-annual"', function () {
+      const { planType, period } =
+        this.PlansLocator.getPlanTypeAndPeriodFromRecurlyPlanCode(
+          'student-annual'
+        )
+      expect(planType).to.equal('student')
+      expect(period).to.equal('annual')
+    })
+  })
 })
 })

+ 19 - 0
services/web/types/subscription/plan.ts

@@ -68,3 +68,22 @@ export type PriceForDisplayData = {
   includesTax: boolean
   includesTax: boolean
   perUserDisplayPrice?: string
   perUserDisplayPrice?: string
 }
 }
+
+export type RecurlyPlanCode =
+  | 'collaborator'
+  | 'collaborator-annual'
+  | 'collaborator_free_trial_7_days'
+  | 'professional'
+  | 'professional-annual'
+  | 'professional_free_trial_7_days'
+  | 'student'
+  | 'student-annual'
+  | 'student_free_trial_7_days'
+
+export type StripeLookupKey =
+  | 'collaborator_monthly'
+  | 'collaborator_annual'
+  | 'professional_monthly'
+  | 'professional_annual'
+  | 'student_monthly'
+  | 'student_annual'