/* eslint-disable camelcase, node/handle-callback-err, max-len, no-return-assign, no-unused-vars, */ // TODO: This file was created by bulk-decaffeinate. // Fix any style issues and re-enable lint. /* * decaffeinate suggestions: * DS102: Remove unnecessary code created because of implicit returns * DS103: Rewrite code to no longer use __guard__ * DS207: Consider shorter variations of null checks * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md */ let SubscriptionController const AuthenticationController = require('../Authentication/AuthenticationController') const SubscriptionHandler = require('./SubscriptionHandler') const PlansLocator = require('./PlansLocator') const SubscriptionViewModelBuilder = require('./SubscriptionViewModelBuilder') const LimitationsManager = require('./LimitationsManager') const RecurlyWrapper = require('./RecurlyWrapper') const Settings = require('settings-sharelatex') const logger = require('logger-sharelatex') const GeoIpLookup = require('../../infrastructure/GeoIpLookup') const UserGetter = require('../User/UserGetter') const FeaturesUpdater = require('./FeaturesUpdater') const planFeatures = require('./planFeatures') const GroupPlansData = require('./GroupPlansData') const V1SubscriptionManager = require('./V1SubscriptionManager') const Errors = require('../Errors/Errors') const HttpErrorHandler = require('../Errors/HttpErrorHandler') const SubscriptionErrors = require('./Errors') const OError = require('@overleaf/o-error') module.exports = SubscriptionController = { plansPage(req, res, next) { const plans = SubscriptionViewModelBuilder.buildPlansList() let currentUser = null return GeoIpLookup.getCurrencyCode( (req.query != null ? req.query.ip : undefined) || req.ip, function (err, recomendedCurrency) { if (err != null) { return next(err) } const render = () => res.render('subscriptions/plans', { title: 'plans_and_pricing', plans, gaExperiments: Settings.gaExperiments.plansPage, gaOptimize: true, recomendedCurrency, planFeatures, groupPlans: GroupPlansData, }) const user_id = AuthenticationController.getLoggedInUserId(req) if (user_id != null) { return UserGetter.getUser( user_id, { signUpDate: 1 }, function (err, user) { if (err != null) { return next(err) } currentUser = user return render() } ) } else { return render() } } ) }, // get to show the recurly.js page paymentPage(req, res, next) { const user = AuthenticationController.getSessionUser(req) const plan = PlansLocator.findLocalPlanInSettings(req.query.planCode) if (!plan) { return HttpErrorHandler.unprocessableEntity(req, res, 'Plan not found') } return LimitationsManager.userHasV1OrV2Subscription( user, function (err, hasSubscription) { if (err != null) { return next(err) } if (hasSubscription) { return res.redirect('/user/subscription?hasSubscription=true') } else { // LimitationsManager.userHasV2Subscription only checks Mongo. Double check with // Recurly as well at this point (we don't do this most places for speed). return SubscriptionHandler.validateNoSubscriptionInRecurly( user._id, function (error, valid) { if (error != null) { return next(error) } if (!valid) { res.redirect('/user/subscription?hasSubscription=true') } else { let currency = req.query.currency != null ? req.query.currency.toUpperCase() : undefined return GeoIpLookup.getCurrencyCode( (req.query != null ? req.query.ip : undefined) || req.ip, function (err, recomendedCurrency, countryCode) { if (err != null) { return next(err) } if (recomendedCurrency != null && currency == null) { currency = recomendedCurrency } return res.render('subscriptions/new', { title: 'subscribe', currency, countryCode, plan, showStudentPlan: req.query.ssp === 'true', recurlyConfig: JSON.stringify({ currency, subdomain: Settings.apis.recurly.subdomain, }), showCouponField: !!req.query.scf, showVatField: !!req.query.svf, gaOptimize: true, }) } ) } } ) } } ) }, userSubscriptionPage(req, res, next) { const user = AuthenticationController.getSessionUser(req) return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel( user, function (error, results) { if (error != null) { return next(error) } const { personalSubscription, memberGroupSubscriptions, managedGroupSubscriptions, confirmedMemberAffiliations, managedInstitutions, managedPublishers, v1SubscriptionStatus, } = results return LimitationsManager.userHasV1OrV2Subscription( user, function (err, hasSubscription) { if (error != null) { return next(error) } const fromPlansPage = req.query.hasSubscription const plans = SubscriptionViewModelBuilder.buildPlansList( personalSubscription ? personalSubscription.plan : undefined ) const data = { title: 'your_subscription', plans, user, hasSubscription, fromPlansPage, personalSubscription, memberGroupSubscriptions, managedGroupSubscriptions, confirmedMemberAffiliations, managedInstitutions, managedPublishers, v1SubscriptionStatus, } return res.render('subscriptions/dashboard', data) } ) } ) }, createSubscription(req, res, next) { const user = AuthenticationController.getSessionUser(req) const recurlyTokenIds = { billing: req.body.recurly_token_id, threeDSecureActionResult: req.body.recurly_three_d_secure_action_result_token_id, } const { subscriptionDetails } = req.body return LimitationsManager.userHasV1OrV2Subscription( user, function (err, hasSubscription) { if (err != null) { return next(err) } if (hasSubscription) { logger.warn({ user_id: user._id }, 'user already has subscription') return res.sendStatus(409) // conflict } return SubscriptionHandler.createSubscription( user, subscriptionDetails, recurlyTokenIds, function (err) { if (!err) { return res.sendStatus(201) } if ( err instanceof SubscriptionErrors.RecurlyTransactionError || err instanceof Errors.InvalidError ) { logger.error({ err }, 'recurly transaction error, potential 422') return HttpErrorHandler.unprocessableEntity( req, res, err.message, OError.getFullInfo(err).public ) } logger.warn( { err, user_id: user._id }, 'something went wrong creating subscription' ) next(err) } ) } ) }, successful_subscription(req, res, next) { const user = AuthenticationController.getSessionUser(req) return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel( user, function (error, { personalSubscription }) { if (error != null) { return next(error) } if (personalSubscription == null) { return res.redirect('/user/subscription/plans') } return res.render('subscriptions/successful_subscription', { title: 'thank_you', personalSubscription, }) } ) }, cancelSubscription(req, res, next) { const user = AuthenticationController.getSessionUser(req) logger.log({ user_id: user._id }, 'canceling subscription') return SubscriptionHandler.cancelSubscription(user, function (err) { if (err != null) { OError.tag(err, 'something went wrong canceling subscription', { user_id: user._id, }) return next(err) } // Note: this redirect isn't used in the main flow as the redirection is // handled by Angular return res.redirect('/user/subscription/canceled') }) }, canceledSubscription(req, res, next) { const user = AuthenticationController.getSessionUser(req) return res.render('subscriptions/canceled_subscription', { title: 'subscription_canceled', }) }, cancelV1Subscription(req, res, next) { const user_id = AuthenticationController.getLoggedInUserId(req) logger.log({ user_id }, 'canceling v1 subscription') return V1SubscriptionManager.cancelV1Subscription(user_id, function (err) { if (err != null) { OError.tag(err, 'something went wrong canceling v1 subscription', { user_id, }) return next(err) } return res.redirect('/user/subscription') }) }, updateSubscription(req, res, next) { const _origin = __guard__(req != null ? req.query : undefined, x => x.origin) || null const user = AuthenticationController.getSessionUser(req) const planCode = req.body.plan_code if (planCode == null) { const err = new Error('plan_code is not defined') logger.warn( { user_id: user._id, err, planCode, origin: _origin, body: req.body }, '[Subscription] error in updateSubscription form' ) return next(err) } logger.log({ planCode, user_id: user._id }, 'updating subscription') return SubscriptionHandler.updateSubscription( user, planCode, null, function (err) { if (err != null) { OError.tag(err, 'something went wrong updating subscription', { user_id: user._id, }) return next(err) } return res.redirect('/user/subscription') } ) }, cancelPendingSubscriptionChange(req, res, next) { const user = AuthenticationController.getSessionUser(req) logger.log({ user_id: user._id }, 'canceling pending subscription change') SubscriptionHandler.cancelPendingSubscriptionChange(user, function (err) { if (err != null) { OError.tag( err, 'something went wrong canceling pending subscription change', { user_id: user._id, } ) return next(err) } res.redirect('/user/subscription') }) }, updateAccountEmailAddress(req, res, next) { const user = AuthenticationController.getSessionUser(req) RecurlyWrapper.updateAccountEmailAddress( user._id, user.email, function (error) { if (error) { return next(error) } res.sendStatus(200) } ) }, reactivateSubscription(req, res, next) { const user = AuthenticationController.getSessionUser(req) logger.log({ user_id: user._id }, 'reactivating subscription') SubscriptionHandler.reactivateSubscription(user, function (err) { if (err != null) { OError.tag(err, 'something went wrong reactivating subscription', { user_id: user._id, }) return next(err) } res.redirect('/user/subscription') }) }, recurlyCallback(req, res, next) { logger.log({ data: req.body }, 'received recurly callback') const event = Object.keys(req.body)[0] const eventData = req.body[event] if ( [ 'new_subscription_notification', 'updated_subscription_notification', 'expired_subscription_notification', ].includes(event) ) { const recurlySubscription = eventData.subscription return SubscriptionHandler.syncSubscription( recurlySubscription, { ip: req.ip }, function (err) { if (err != null) { return next(err) } return res.sendStatus(200) } ) } else if (event === 'billing_info_updated_notification') { const recurlyAccountCode = eventData.account.account_code return SubscriptionHandler.attemptPaypalInvoiceCollection( recurlyAccountCode, function (err) { if (err) { return next(err) } return res.sendStatus(200) } ) } else { return res.sendStatus(200) } }, renderUpgradeToAnnualPlanPage(req, res, next) { const user = AuthenticationController.getSessionUser(req) return LimitationsManager.userHasV2Subscription( user, function (err, hasSubscription, subscription) { let planName if (err != null) { return next(err) } const planCode = subscription != null ? subscription.planCode.toLowerCase() : undefined if ( (planCode != null ? planCode.indexOf('annual') : undefined) !== -1 ) { planName = 'annual' } else if ( (planCode != null ? planCode.indexOf('student') : undefined) !== -1 ) { planName = 'student' } else if ( (planCode != null ? planCode.indexOf('collaborator') : undefined) !== -1 ) { planName = 'collaborator' } if (!hasSubscription) { return res.redirect('/user/subscription/plans') } return res.render('subscriptions/upgradeToAnnual', { title: 'Upgrade to annual', planName, }) } ) }, processUpgradeToAnnualPlan(req, res, next) { const user = AuthenticationController.getSessionUser(req) const { planName } = req.body const coupon_code = Settings.coupon_codes.upgradeToAnnualPromo[planName] const annualPlanName = `${planName}-annual` logger.log( { user_id: user._id, planName: annualPlanName }, 'user is upgrading to annual billing with discount' ) return SubscriptionHandler.updateSubscription( user, annualPlanName, coupon_code, function (err) { if (err != null) { OError.tag(err, 'error updating subscription', { user_id: user._id, }) return next(err) } return res.sendStatus(200) } ) }, extendTrial(req, res, next) { const user = AuthenticationController.getSessionUser(req) return LimitationsManager.userHasV2Subscription( user, function (err, hasSubscription, subscription) { if (err != null) { return next(err) } return SubscriptionHandler.extendTrial( subscription, 14, function (err) { if (err != null) { return res.sendStatus(500) } else { return res.sendStatus(200) } } ) } ) }, recurlyNotificationParser(req, res, next) { let xml = '' req.on('data', chunk => (xml += chunk)) return req.on('end', () => RecurlyWrapper._parseXml(xml, function (error, body) { if (error != null) { return next(error) } req.body = body return next() }) ) }, refreshUserFeatures(req, res, next) { const { user_id } = req.params return FeaturesUpdater.refreshFeatures( user_id, 'subscription-controller', function (error) { if (error != null) { return next(error) } return res.sendStatus(200) } ) }, } function __guard__(value, transform) { return typeof value !== 'undefined' && value !== null ? transform(value) : undefined }