Merge pull request #19822 from overleaf/jel-plans-notifications

[web] Show geo banners on light touch redesign of plans page

GitOrigin-RevId: 08ee6827cc16ec9326e5dbb1a165602be9cfe86d
This commit is contained in:
Jessica Lawshe 2024-08-12 09:01:58 -05:00 committed by Copybot
parent 47ad169781
commit c636089939
4 changed files with 542 additions and 26 deletions

View file

@ -53,6 +53,15 @@ function _getGroupPlanModalDefaults(req, currency) {
}
}
function _plansBanners({ geoPricingLATAMTestVariant, countryCode }) {
const showLATAMBanner =
geoPricingLATAMTestVariant === 'latam' &&
['MX', 'CO', 'CL', 'PE'].includes(countryCode)
const showInrGeoBanner = countryCode === 'IN'
const showBrlGeoBanner = countryCode === 'BR'
return { showLATAMBanner, showInrGeoBanner, showBrlGeoBanner }
}
async function plansPage(req, res) {
const websiteRedesignPlansAssignment =
await SplitTestHandler.promises.getAssignment(
@ -95,9 +104,12 @@ async function plansPage(req, res) {
plansPageViewSegmentation
)
const showLATAMBanner =
geoPricingLATAMTestVariant === 'latam' &&
['MX', 'CO', 'CL', 'PE'].includes(countryCode)
const { showLATAMBanner, showInrGeoBanner, showBrlGeoBanner } = _plansBanners(
{
geoPricingLATAMTestVariant,
countryCode,
}
)
const localCcyAssignment = await SplitTestHandler.promises.getAssignment(
req,
@ -129,8 +141,8 @@ async function plansPage(req, res) {
language,
formatCurrency
),
showInrGeoBanner: countryCode === 'IN',
showBrlGeoBanner: countryCode === 'BR',
showInrGeoBanner,
showBrlGeoBanner,
showLATAMBanner,
latamCountryBannerDetails,
})
@ -144,8 +156,12 @@ async function plansPageLightDesign(req, res) {
if (!splitTestActive && req.query.preview !== 'true') {
return res.redirect(302, '/user/subscription/plans')
}
const { currency } = await _getRecommendedCurrency(req, res)
const {
currency,
countryCode,
geoPricingLATAMTestVariant,
recommendedCurrency,
} = await _getRecommendedCurrency(req, res)
const language = req.i18n.language || 'en'
const currentView = 'annual'
@ -153,7 +169,27 @@ async function plansPageLightDesign(req, res) {
const groupPlanModalDefaults = _getGroupPlanModalDefaults(req, currency)
const formatCurrency = SubscriptionHelper.formatCurrencyDefault
// TODO: add page view analytics?
const { showLATAMBanner, showInrGeoBanner, showBrlGeoBanner } = _plansBanners(
{
geoPricingLATAMTestVariant,
countryCode,
}
)
const latamCountryBannerDetails = await getLatamCountryBannerDetails(req, res)
const plansPageViewSegmentation = {
currency: recommendedCurrency,
countryCode,
'geo-pricing-latam-v2': geoPricingLATAMTestVariant,
}
AnalyticsManager.recordEventForSession(
req.session,
'plans-page-view',
plansPageViewSegmentation
)
res.render('subscriptions/plans-light-design', {
title: 'plans_and_pricing',
currentView,
@ -174,6 +210,10 @@ async function plansPageLightDesign(req, res) {
language,
formatCurrency
),
showLATAMBanner,
showInrGeoBanner,
showBrlGeoBanner,
latamCountryBannerDetails,
})
}
@ -332,9 +372,11 @@ async function interstitialPaymentPage(req, res) {
paywallPlansPageViewSegmentation
)
const showLATAMBanner =
geoPricingLATAMTestVariant === 'latam' &&
['MX', 'CO', 'CL', 'PE'].includes(countryCode)
const { showLATAMBanner, showInrGeoBanner, showBrlGeoBanner } =
_plansBanners({
geoPricingLATAMTestVariant,
countryCode,
})
const localCcyAssignment = await SplitTestHandler.promises.getAssignment(
req,
@ -355,8 +397,8 @@ async function interstitialPaymentPage(req, res) {
? formatCurrencyLocalized
: SubscriptionHelper.formatCurrencyDefault,
showCurrencyAndPaymentMethods: localCcyAssignment.variant === 'enabled',
showInrGeoBanner: countryCode === 'IN',
showBrlGeoBanner: countryCode === 'BR',
showInrGeoBanner,
showBrlGeoBanner,
showLATAMBanner,
latamCountryBannerDetails,
skipLinkTarget: req.session?.postCheckoutRedirect || '/project',

View file

@ -21,13 +21,13 @@ block content
.plans-page.plans-page-interstitial
.container
if showInrGeoBanner
div.notification.notification-type-success.text-center
.mb-5.notification.notification-type-success.text-center
div.notification-content !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})}
if showBrlGeoBanner
div.notification.notification-type-success.text-center
.mb-5.notification.notification-type-success.text-center
div.notification-content !{translate("brl_discount_offer_plans_page_banner", {flag: '🇧🇷'})}
if showLATAMBanner
div.notification.notification-type-success.text-center
.mb-5.notification.notification-type-success.text-center
div.notification-content !{translate("latam_discount_offer_plans_page_banner", {flag: latamCountryBannerDetails.latamCountryFlag, country: latamCountryBannerDetails.country, currency: latamCountryBannerDetails.currency, discount: latamCountryBannerDetails.discount })}
.row

View file

@ -16,19 +16,19 @@ block content
main.website-redesign#main-content
.plans-page
.container
//- if showInrGeoBanner
//- div.notification.notification-type-success.text-centered
//- div.notification-content !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})}
//- if showBrlGeoBanner
//- div.notification.notification-type-success.text-centered
//- div.notification-content !{translate("brl_discount_offer_plans_page_banner", {flag: '🇧🇷'})}
//- if showLATAMBanner
//- div.notification.notification-type-success.text-centered
//- div.notification-content !{translate("latam_discount_offer_plans_page_banner", {flag: latamCountryBannerDetails.latamCountryFlag, country: latamCountryBannerDetails.country, currency: latamCountryBannerDetails.currency, discount: latamCountryBannerDetails.discount })}
if showInrGeoBanner
.mb-5.notification.notification-type-success.text-center
div.notification-content !{translate("inr_discount_offer_plans_page_banner", {flag: '🇮🇳'})}
if showBrlGeoBanner
.mb-5.notification.notification-type-success.text-center
div.notification-content !{translate("brl_discount_offer_plans_page_banner", {flag: '🇧🇷'})}
if showLATAMBanner
.mb-5.notification.notification-type-success.text-center
div.notification-content !{translate("latam_discount_offer_plans_page_banner", {flag: latamCountryBannerDetails.latamCountryFlag, country: latamCountryBannerDetails.country, currency: latamCountryBannerDetails.currency, discount: latamCountryBannerDetails.discount })}
.row
.col-md-12
h1.text-centered
h1.text-center
+eyebrow(translate('plans_and_pricing_lowercase'))
| #{translate('choose_your_plan')}

View file

@ -132,6 +132,7 @@ describe('SubscriptionController', function () {
this.SplitTestV2Hander = {
promises: {
getAssignment: sinon.stub().resolves({ variant: 'default' }),
isSplitTestActive: sinon.stub().resolves(true),
},
}
this.SubscriptionHelper = {
@ -281,6 +282,342 @@ describe('SubscriptionController', function () {
})
this.SubscriptionController.plansPage(this.req, this.res)
})
it('should return false for US Users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans')
opts.showInrGeoBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'US',
})
this.SubscriptionController.plansPage(this.req, this.res)
})
})
describe('showBrlGeoBanner data', function () {
it('should return true for Brazilian users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans')
opts.showBrlGeoBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'BR',
})
this.SubscriptionController.plansPage(this.req, this.res)
})
it('should return false for US users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans')
opts.showBrlGeoBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'US',
})
this.SubscriptionController.plansPage(this.req, this.res)
})
})
describe('showLATAMBanner', function () {
describe('latam variant', function () {
beforeEach(function () {
this.SplitTestV2Hander.promises.getAssignment.resolves({
variant: 'latam',
})
})
it('should return true for Mexican users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'MX',
})
this.SubscriptionController.plansPage(this.req, this.res)
})
it('should return true for Colombian users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'CO',
})
this.SubscriptionController.plansPage(this.req, this.res)
})
it('should return true for Chilean users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'CL',
})
this.SubscriptionController.plansPage(this.req, this.res)
})
it('should return true for Peruvian users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'PE',
})
this.SubscriptionController.plansPage(this.req, this.res)
})
it('should return true for US users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans')
opts.showLATAMBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'US',
})
this.SubscriptionController.plansPage(this.req, this.res)
})
})
describe('default variant', function () {
beforeEach(function () {
this.SplitTestV2Hander.promises.getAssignment.resolves({
variant: 'default',
})
})
it('should return false', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans')
opts.showLATAMBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'MX',
})
this.SubscriptionController.plansPage(this.req, this.res)
})
})
})
})
describe('plansPage light touch redesign', function () {
beforeEach(function () {
this.req.ip = '1234.3123.3131.333 313.133.445.666 653.5345.5345.534'
this.GeoIpLookup.promises.getCurrencyCode.resolves({
currencyCode: this.stubbedCurrencyCode,
})
})
describe('ip override', function () {
beforeEach(function () {
this.req.ip = '1.2.3.4'
this.req.query = { ip: '5.6.7.8' }
this.GeoIpLookup.promises.getCurrencyCode.withArgs('1.2.3.4').resolves({
currencyCode: 'GBP',
})
this.GeoIpLookup.promises.getCurrencyCode.withArgs('5.6.7.8').resolves({
currencyCode: 'USD',
})
})
it('should ignore override for non admin', function (done) {
this.res.render = (page, opts) => {
opts.recommendedCurrency.should.equal('GBP')
done()
}
this.AuthorizationManager.promises.isUserSiteAdmin.resolves(false)
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
it('should accept override for admin', function (done) {
this.res.render = (page, opts) => {
opts.recommendedCurrency.should.equal('USD')
done()
}
this.AuthorizationManager.promises.isUserSiteAdmin.resolves(true)
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
})
describe('groupPlanModal data', function () {
it('should pass local currency if valid', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.groupPlanModalDefaults.currency.should.equal('GBP')
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
currencyCode: 'GBP',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
it('should fallback to USD when valid', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.groupPlanModalDefaults.currency.should.equal('USD')
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
currencyCode: 'FOO',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
it('should pass valid options for group plan modal and discard invalid', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.groupPlanModalDefaults.size.should.equal('42')
opts.groupPlanModalDefaults.plan_code.should.equal('collaborator')
opts.groupPlanModalDefaults.currency.should.equal('GBP')
opts.groupPlanModalDefaults.usage.should.equal('foo')
done()
}
this.GeoIpLookup.isValidCurrencyParam.returns(false)
this.req.query = {
number: '42',
currency: 'ABC',
plan: 'does-not-exist',
usage: 'foo',
}
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
})
describe('showInrGeoBanner data', function () {
it('should return true for Indian Users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.showInrGeoBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'IN',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
it('should return false for US Users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.showInrGeoBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'US',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
})
describe('showBrlGeoBanner data', function () {
it('should return true for Brazilian users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.showBrlGeoBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'BR',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
it('should return false for US users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.showBrlGeoBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'US',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
})
describe('showLATAMBanner', function () {
describe('latam variant', function () {
beforeEach(function () {
this.SplitTestV2Hander.promises.getAssignment.resolves({
variant: 'latam',
})
})
it('should return true for Mexican users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'MX',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
it('should return true for Colombian users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'CO',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
it('should return true for Chilean users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'CL',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
it('should return true for Peruvian users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'PE',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
it('should return true for US users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.showLATAMBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'US',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
})
describe('default variant', function () {
beforeEach(function () {
this.SplitTestV2Hander.promises.getAssignment.resolves({
variant: 'default',
})
})
it('should return false', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/plans-light-design')
opts.showLATAMBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'MX',
})
this.SubscriptionController.plansPageLightDesign(this.req, this.res)
})
})
})
})
@ -328,6 +665,143 @@ describe('SubscriptionController', function () {
})
this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
})
it('should be false for US users', function (done) {
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'US',
})
this.res.render = (page, opts) => {
page.should.equal('subscriptions/interstitial-payment')
opts.showInrGeoBanner.should.equal(false)
done()
}
this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
})
})
describe('showBrlGeoBanner data', function () {
it('should return true for Brazilian users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/interstitial-payment')
opts.showBrlGeoBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'BR',
})
this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
})
it('should return false for US users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/interstitial-payment')
opts.showBrlGeoBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'US',
})
this.SubscriptionController.interstitialPaymentPage(this.req, this.res)
})
})
describe('showLATAMBanner', function () {
describe('latam variant', function () {
beforeEach(function () {
this.SplitTestV2Hander.promises.getAssignment.resolves({
variant: 'latam',
})
})
it('should return true for Mexican users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/interstitial-payment')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'MX',
})
this.SubscriptionController.interstitialPaymentPage(
this.req,
this.res
)
})
it('should return true for Colombian users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/interstitial-payment')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'CO',
})
this.SubscriptionController.interstitialPaymentPage(
this.req,
this.res
)
})
it('should return true for Chilean users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/interstitial-payment')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'CL',
})
this.SubscriptionController.interstitialPaymentPage(
this.req,
this.res
)
})
it('should return true for Peruvian users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/interstitial-payment')
opts.showLATAMBanner.should.equal(true)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'PE',
})
this.SubscriptionController.interstitialPaymentPage(
this.req,
this.res
)
})
it('should return true for US users', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/interstitial-payment')
opts.showLATAMBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'US',
})
this.SubscriptionController.interstitialPaymentPage(
this.req,
this.res
)
})
})
describe('default variant', function () {
beforeEach(function () {
this.SplitTestV2Hander.promises.getAssignment.resolves({
variant: 'default',
})
})
it('should return false', function (done) {
this.res.render = (page, opts) => {
page.should.equal('subscriptions/interstitial-payment')
opts.showLATAMBanner.should.equal(false)
done()
}
this.GeoIpLookup.promises.getCurrencyCode.resolves({
countryCode: 'MX',
})
this.SubscriptionController.interstitialPaymentPage(
this.req,
this.res
)
})
})
})
})