From 34a34698f686b2af951c10903e22140046b62040 Mon Sep 17 00:00:00 2001 From: Brian Gough Date: Wed, 20 Sep 2023 15:09:57 +0100 Subject: [PATCH] Merge pull request #14861 from overleaf/jpa-web-restrict-new-subscription [web] block web sales to restricted countries GitOrigin-RevId: 21029cf016eaa0c63ce6939ab8681979118a9dc4 --- .../Subscription/SubscriptionController.js | 15 +++++++++- services/web/config/settings.defaults.js | 1 + .../SubscriptionControllerTests.js | 30 +++++++++++++++++-- 3 files changed, 42 insertions(+), 4 deletions(-) diff --git a/services/web/app/src/Features/Subscription/SubscriptionController.js b/services/web/app/src/Features/Subscription/SubscriptionController.js index 5f5eb821b7..1f80f1431f 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionController.js +++ b/services/web/app/src/Features/Subscription/SubscriptionController.js @@ -227,7 +227,7 @@ async function paymentPage(req, res) { } // Block web sales to restricted countries - if (['CU', 'IR', 'KP', 'RU', 'SY', 'VE'].includes(countryCode)) { + if (Settings.restrictedCountries.includes(countryCode)) { return res.render('subscriptions/restricted-country', { title: 'restricted', }) @@ -510,6 +510,19 @@ async function createSubscription(req, res) { return res.sendStatus(409) // conflict } + const { countryCode } = await _getRecommendedCurrency(req, res) + + // Block web sales to restricted countries + if (Settings.restrictedCountries.includes(countryCode)) { + return HttpErrorHandler.unprocessableEntity( + req, + res, + req.i18n.translate('sorry_detected_sales_restricted_region', { + link: '/contact', + }) + ) + } + const result = {} await Modules.promises.hooks.fire( 'createSubscription', diff --git a/services/web/config/settings.defaults.js b/services/web/config/settings.defaults.js index d45bc439a4..5dbb753931 100644 --- a/services/web/config/settings.defaults.js +++ b/services/web/config/settings.defaults.js @@ -347,6 +347,7 @@ module.exports = { ], enableSubscriptions: false, + restrictedCountries: [], enabledLinkedFileTypes: (process.env.ENABLED_LINKED_FILE_TYPES || '').split( ',' diff --git a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js index a89bc5616b..1aae4e42b0 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionControllerTests.js @@ -85,6 +85,7 @@ describe('SubscriptionController', function () { .returns({ plans: [], planCodesChangingAtTermEnd: [] }), } this.settings = { + restrictedCountries: ['KP'], coupon_codes: { upgradeToAnnualPromo: { student: 'STUDENTCODEHERE', @@ -116,9 +117,12 @@ describe('SubscriptionController', function () { } this.GeoIpLookup = { isValidCurrencyParam: sinon.stub().returns(true), - getCurrencyCode: sinon.stub(), + getCurrencyCode: sinon.stub().yields('USD', 'US'), promises: { - getCurrencyCode: sinon.stub(), + getCurrencyCode: sinon.stub().resolves({ + countryCode: 'US', + currencyCode: 'USD', + }), }, } this.UserGetter = { @@ -161,7 +165,10 @@ describe('SubscriptionController', function () { './GroupPlansData': (this.GroupPlansData = {}), './V1SubscriptionManager': (this.V1SubscriptionManager = {}), '../Errors/HttpErrorHandler': (this.HttpErrorHandler = { - unprocessableEntity: sinon.stub(), + unprocessableEntity: sinon.stub().callsFake((req, res, message) => { + res.status(422) + res.json({ message }) + }), }), './Errors': SubscriptionErrors, '../Analytics/AnalyticsManager': (this.AnalyticsManager = { @@ -599,6 +606,23 @@ describe('SubscriptionController', function () { }) }) + it('should handle restricted country', function (done) { + this.GeoIpLookup.promises.getCurrencyCode.resolves({ + countryCode: 'KP', + }) + this.res.callback = () => { + expect(this.res.statusCode).to.equal(422) + expect(this.res.body).to.include( + 'sorry_detected_sales_restricted_region' + ) + this.SubscriptionHandler.promises.createSubscription.called.should.equal( + false + ) + done() + } + this.SubscriptionController.createSubscription(this.req, this.res) + }) + it('should handle 3DSecure errors/recurly transaction errors', function (done) { this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(false) this.SubscriptionHandler.promises.createSubscription.rejects(