mirror of
https://github.com/overleaf/overleaf.git
synced 2024-11-21 20:47:08 -05:00
Merge pull request #4071 from overleaf/ab-subscription-decaf-cleanup
Subscription controller decaf cleanup GitOrigin-RevId: 79b8adfabe30e4557a95b1aad71a5162e6f42cce
This commit is contained in:
parent
b93761f275
commit
18d62dcee9
7 changed files with 826 additions and 1130 deletions
|
@ -1,4 +1,3 @@
|
||||||
let LimitationsManager
|
|
||||||
const OError = require('@overleaf/o-error')
|
const OError = require('@overleaf/o-error')
|
||||||
const logger = require('logger-sharelatex')
|
const logger = require('logger-sharelatex')
|
||||||
const ProjectGetter = require('../Project/ProjectGetter')
|
const ProjectGetter = require('../Project/ProjectGetter')
|
||||||
|
@ -9,8 +8,9 @@ const CollaboratorsGetter = require('../Collaborators/CollaboratorsGetter')
|
||||||
const CollaboratorsInvitesHandler = require('../Collaborators/CollaboratorsInviteHandler')
|
const CollaboratorsInvitesHandler = require('../Collaborators/CollaboratorsInviteHandler')
|
||||||
const V1SubscriptionManager = require('./V1SubscriptionManager')
|
const V1SubscriptionManager = require('./V1SubscriptionManager')
|
||||||
const { V1ConnectionError } = require('../Errors/Errors')
|
const { V1ConnectionError } = require('../Errors/Errors')
|
||||||
|
const { promisifyAll } = require('../../util/promises')
|
||||||
|
|
||||||
module.exports = LimitationsManager = {
|
const LimitationsManager = {
|
||||||
allowedNumberOfCollaboratorsInProject(projectId, callback) {
|
allowedNumberOfCollaboratorsInProject(projectId, callback) {
|
||||||
ProjectGetter.getProject(
|
ProjectGetter.getProject(
|
||||||
projectId,
|
projectId,
|
||||||
|
@ -226,3 +226,12 @@ module.exports = LimitationsManager = {
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LimitationsManager.promises = promisifyAll(LimitationsManager, {
|
||||||
|
multiResult: {
|
||||||
|
userHasV2Subscription: ['hasSubscription', 'subscription'],
|
||||||
|
userIsMemberOfGroupSubscription: ['isMember', 'subscriptions'],
|
||||||
|
hasGroupMembersLimitReached: ['limitReached', 'subscription'],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
module.exports = LimitationsManager
|
||||||
|
|
|
@ -1,20 +1,3 @@
|
||||||
/* 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 AuthenticationController = require('../Authentication/AuthenticationController')
|
||||||
const SubscriptionHandler = require('./SubscriptionHandler')
|
const SubscriptionHandler = require('./SubscriptionHandler')
|
||||||
const PlansLocator = require('./PlansLocator')
|
const PlansLocator = require('./PlansLocator')
|
||||||
|
@ -24,7 +7,6 @@ const RecurlyWrapper = require('./RecurlyWrapper')
|
||||||
const Settings = require('settings-sharelatex')
|
const Settings = require('settings-sharelatex')
|
||||||
const logger = require('logger-sharelatex')
|
const logger = require('logger-sharelatex')
|
||||||
const GeoIpLookup = require('../../infrastructure/GeoIpLookup')
|
const GeoIpLookup = require('../../infrastructure/GeoIpLookup')
|
||||||
const UserGetter = require('../User/UserGetter')
|
|
||||||
const FeaturesUpdater = require('./FeaturesUpdater')
|
const FeaturesUpdater = require('./FeaturesUpdater')
|
||||||
const planFeatures = require('./planFeatures')
|
const planFeatures = require('./planFeatures')
|
||||||
const GroupPlansData = require('./GroupPlansData')
|
const GroupPlansData = require('./GroupPlansData')
|
||||||
|
@ -34,537 +16,485 @@ const HttpErrorHandler = require('../Errors/HttpErrorHandler')
|
||||||
const SubscriptionErrors = require('./Errors')
|
const SubscriptionErrors = require('./Errors')
|
||||||
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
const SplitTestHandler = require('../SplitTests/SplitTestHandler')
|
||||||
const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
const AnalyticsManager = require('../Analytics/AnalyticsManager')
|
||||||
|
const { expressify } = require('../../util/promises')
|
||||||
const OError = require('@overleaf/o-error')
|
const OError = require('@overleaf/o-error')
|
||||||
const _ = require('lodash')
|
const _ = require('lodash')
|
||||||
|
|
||||||
const SUBSCRIPTION_PAGE_SPLIT_TEST = 'subscription-page'
|
const SUBSCRIPTION_PAGE_SPLIT_TEST = 'subscription-page'
|
||||||
|
|
||||||
module.exports = SubscriptionController = {
|
async function plansPage(req, res) {
|
||||||
plansPage(req, res, next) {
|
const plans = SubscriptionViewModelBuilder.buildPlansList()
|
||||||
const plans = SubscriptionViewModelBuilder.buildPlansList()
|
|
||||||
let currentUser = null
|
|
||||||
|
|
||||||
return GeoIpLookup.getCurrencyCode(
|
const recommendedCurrency = await GeoIpLookup.promises.getCurrencyCode(
|
||||||
(req.query != null ? req.query.ip : undefined) || req.ip,
|
(req.query ? req.query.ip : undefined) || req.ip
|
||||||
function (err, recomendedCurrency) {
|
)
|
||||||
if (err != null) {
|
|
||||||
return next(err)
|
res.render('subscriptions/plans', {
|
||||||
}
|
title: 'plans_and_pricing',
|
||||||
const render = () =>
|
plans,
|
||||||
res.render('subscriptions/plans', {
|
gaExperiments: Settings.gaExperiments.plansPage,
|
||||||
title: 'plans_and_pricing',
|
gaOptimize: true,
|
||||||
plans,
|
recomendedCurrency: recommendedCurrency,
|
||||||
gaExperiments: Settings.gaExperiments.plansPage,
|
planFeatures,
|
||||||
gaOptimize: true,
|
groupPlans: GroupPlansData,
|
||||||
recomendedCurrency,
|
})
|
||||||
planFeatures,
|
}
|
||||||
groupPlans: GroupPlansData,
|
|
||||||
})
|
// get to show the recurly.js page
|
||||||
const user_id = AuthenticationController.getLoggedInUserId(req)
|
async function paymentPage(req, res) {
|
||||||
if (user_id != null) {
|
const user = AuthenticationController.getSessionUser(req)
|
||||||
return UserGetter.getUser(
|
const plan = PlansLocator.findLocalPlanInSettings(req.query.planCode)
|
||||||
user_id,
|
if (!plan) {
|
||||||
{ signUpDate: 1 },
|
return HttpErrorHandler.unprocessableEntity(req, res, 'Plan not found')
|
||||||
function (err, user) {
|
}
|
||||||
if (err != null) {
|
const hasSubscription = await LimitationsManager.promises.userHasV1OrV2Subscription(
|
||||||
return next(err)
|
user
|
||||||
}
|
)
|
||||||
currentUser = user
|
if (hasSubscription) {
|
||||||
return render()
|
res.redirect('/user/subscription?hasSubscription=true')
|
||||||
}
|
} else {
|
||||||
)
|
// LimitationsManager.userHasV2Subscription only checks Mongo. Double check with
|
||||||
} else {
|
// Recurly as well at this point (we don't do this most places for speed).
|
||||||
return render()
|
const valid = await SubscriptionHandler.promises.validateNoSubscriptionInRecurly(
|
||||||
}
|
user._id
|
||||||
}
|
|
||||||
)
|
)
|
||||||
},
|
if (!valid) {
|
||||||
|
res.redirect('/user/subscription?hasSubscription=true')
|
||||||
// get to show the recurly.js page
|
} else {
|
||||||
paymentPage(req, res, next) {
|
let currency = req.query.currency
|
||||||
const user = AuthenticationController.getSessionUser(req)
|
? req.query.currency.toUpperCase()
|
||||||
const plan = PlansLocator.findLocalPlanInSettings(req.query.planCode)
|
: undefined
|
||||||
if (!plan) {
|
const {
|
||||||
return HttpErrorHandler.unprocessableEntity(req, res, 'Plan not found')
|
recomendedCurrency: recommendedCurrency,
|
||||||
|
countryCode,
|
||||||
|
} = await GeoIpLookup.promises.getCurrencyCode(
|
||||||
|
(req.query ? req.query.ip : undefined) || req.ip
|
||||||
|
)
|
||||||
|
if (recommendedCurrency && currency == null) {
|
||||||
|
currency = recommendedCurrency
|
||||||
|
}
|
||||||
|
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,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
return LimitationsManager.userHasV1OrV2Subscription(
|
}
|
||||||
user,
|
}
|
||||||
function (err, hasSubscription) {
|
|
||||||
if (err != null) {
|
function userSubscriptionPage(req, res, next) {
|
||||||
return next(err)
|
const user = AuthenticationController.getSessionUser(req)
|
||||||
}
|
SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
|
||||||
if (hasSubscription) {
|
user,
|
||||||
return res.redirect('/user/subscription?hasSubscription=true')
|
function (error, results) {
|
||||||
} else {
|
if (error) {
|
||||||
// LimitationsManager.userHasV2Subscription only checks Mongo. Double check with
|
return next(error)
|
||||||
// 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,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
)
|
const {
|
||||||
},
|
personalSubscription,
|
||||||
|
memberGroupSubscriptions,
|
||||||
userSubscriptionPage(req, res, next) {
|
managedGroupSubscriptions,
|
||||||
const user = AuthenticationController.getSessionUser(req)
|
confirmedMemberAffiliations,
|
||||||
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
|
managedInstitutions,
|
||||||
user,
|
managedPublishers,
|
||||||
function (error, results) {
|
v1SubscriptionStatus,
|
||||||
if (error != null) {
|
} = results
|
||||||
return next(error)
|
LimitationsManager.userHasV1OrV2Subscription(
|
||||||
}
|
user,
|
||||||
const {
|
function (err, hasSubscription) {
|
||||||
personalSubscription,
|
if (error) {
|
||||||
memberGroupSubscriptions,
|
return next(error)
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
let subscriptionCopy = 'default'
|
|
||||||
if (
|
|
||||||
personalSubscription ||
|
|
||||||
hasSubscription ||
|
|
||||||
(memberGroupSubscriptions &&
|
|
||||||
memberGroupSubscriptions.length > 0) ||
|
|
||||||
(confirmedMemberAffiliations &&
|
|
||||||
confirmedMemberAffiliations.length > 0 &&
|
|
||||||
_.find(confirmedMemberAffiliations, affiliation => {
|
|
||||||
return affiliation.licence && affiliation.licence !== 'free'
|
|
||||||
}))
|
|
||||||
) {
|
|
||||||
AnalyticsManager.recordEvent(user._id, 'subscription-page-view')
|
|
||||||
} else {
|
|
||||||
const testSegmentation = SplitTestHandler.getTestSegmentation(
|
|
||||||
user._id,
|
|
||||||
SUBSCRIPTION_PAGE_SPLIT_TEST
|
|
||||||
)
|
|
||||||
if (testSegmentation.enabled) {
|
|
||||||
subscriptionCopy = testSegmentation.variant
|
|
||||||
|
|
||||||
AnalyticsManager.recordEvent(
|
|
||||||
user._id,
|
|
||||||
'subscription-page-view',
|
|
||||||
{
|
|
||||||
splitTestId: SUBSCRIPTION_PAGE_SPLIT_TEST,
|
|
||||||
splitTestVariantId: testSegmentation.variant,
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
AnalyticsManager.recordEvent(user._id, 'subscription-page-view')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const data = {
|
|
||||||
title: 'your_subscription',
|
|
||||||
plans,
|
|
||||||
user,
|
|
||||||
hasSubscription,
|
|
||||||
subscriptionCopy,
|
|
||||||
fromPlansPage,
|
|
||||||
personalSubscription,
|
|
||||||
memberGroupSubscriptions,
|
|
||||||
managedGroupSubscriptions,
|
|
||||||
confirmedMemberAffiliations,
|
|
||||||
managedInstitutions,
|
|
||||||
managedPublishers,
|
|
||||||
v1SubscriptionStatus,
|
|
||||||
}
|
|
||||||
return res.render('subscriptions/dashboard', data)
|
|
||||||
}
|
}
|
||||||
)
|
const fromPlansPage = req.query.hasSubscription
|
||||||
}
|
const plans = SubscriptionViewModelBuilder.buildPlansList(
|
||||||
)
|
personalSubscription ? personalSubscription.plan : undefined
|
||||||
},
|
)
|
||||||
|
|
||||||
createSubscription(req, res, next) {
|
let subscriptionCopy = 'default'
|
||||||
const user = AuthenticationController.getSessionUser(req)
|
if (
|
||||||
const recurlyTokenIds = {
|
personalSubscription ||
|
||||||
billing: req.body.recurly_token_id,
|
hasSubscription ||
|
||||||
threeDSecureActionResult:
|
(memberGroupSubscriptions && memberGroupSubscriptions.length > 0) ||
|
||||||
req.body.recurly_three_d_secure_action_result_token_id,
|
(confirmedMemberAffiliations &&
|
||||||
|
confirmedMemberAffiliations.length > 0 &&
|
||||||
|
_.find(confirmedMemberAffiliations, affiliation => {
|
||||||
|
return affiliation.licence && affiliation.licence !== 'free'
|
||||||
|
}))
|
||||||
|
) {
|
||||||
|
AnalyticsManager.recordEvent(user._id, 'subscription-page-view')
|
||||||
|
} else {
|
||||||
|
const testSegmentation = SplitTestHandler.getTestSegmentation(
|
||||||
|
user._id,
|
||||||
|
SUBSCRIPTION_PAGE_SPLIT_TEST
|
||||||
|
)
|
||||||
|
if (testSegmentation.enabled) {
|
||||||
|
subscriptionCopy = testSegmentation.variant
|
||||||
|
|
||||||
|
AnalyticsManager.recordEvent(user._id, 'subscription-page-view', {
|
||||||
|
splitTestId: SUBSCRIPTION_PAGE_SPLIT_TEST,
|
||||||
|
splitTestVariantId: testSegmentation.variant,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
AnalyticsManager.recordEvent(user._id, 'subscription-page-view')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = {
|
||||||
|
title: 'your_subscription',
|
||||||
|
plans,
|
||||||
|
user,
|
||||||
|
hasSubscription,
|
||||||
|
subscriptionCopy,
|
||||||
|
fromPlansPage,
|
||||||
|
personalSubscription,
|
||||||
|
memberGroupSubscriptions,
|
||||||
|
managedGroupSubscriptions,
|
||||||
|
confirmedMemberAffiliations,
|
||||||
|
managedInstitutions,
|
||||||
|
managedPublishers,
|
||||||
|
v1SubscriptionStatus,
|
||||||
|
}
|
||||||
|
res.render('subscriptions/dashboard', data)
|
||||||
|
}
|
||||||
|
)
|
||||||
}
|
}
|
||||||
const { subscriptionDetails } = req.body
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return LimitationsManager.userHasV1OrV2Subscription(
|
function createSubscription(req, res, next) {
|
||||||
user,
|
const user = AuthenticationController.getSessionUser(req)
|
||||||
function (err, hasSubscription) {
|
const recurlyTokenIds = {
|
||||||
if (err != null) {
|
billing: req.body.recurly_token_id,
|
||||||
return next(err)
|
threeDSecureActionResult:
|
||||||
}
|
req.body.recurly_three_d_secure_action_result_token_id,
|
||||||
if (hasSubscription) {
|
}
|
||||||
logger.warn({ user_id: user._id }, 'user already has subscription')
|
const { subscriptionDetails } = req.body
|
||||||
return res.sendStatus(409) // conflict
|
|
||||||
}
|
|
||||||
return SubscriptionHandler.createSubscription(
|
|
||||||
user,
|
|
||||||
subscriptionDetails,
|
|
||||||
recurlyTokenIds,
|
|
||||||
function (err) {
|
|
||||||
if (!err) {
|
|
||||||
return res.sendStatus(201)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
LimitationsManager.userHasV1OrV2Subscription(
|
||||||
err instanceof SubscriptionErrors.RecurlyTransactionError ||
|
user,
|
||||||
err instanceof Errors.InvalidError
|
function (err, hasSubscription) {
|
||||||
) {
|
if (err) {
|
||||||
logger.error({ err }, 'recurly transaction error, potential 422')
|
return next(err)
|
||||||
return HttpErrorHandler.unprocessableEntity(
|
}
|
||||||
req,
|
if (hasSubscription) {
|
||||||
res,
|
logger.warn({ user_id: user._id }, 'user already has subscription')
|
||||||
err.message,
|
return res.sendStatus(409) // conflict
|
||||||
OError.getFullInfo(err).public
|
}
|
||||||
)
|
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')
|
||||||
|
HttpErrorHandler.unprocessableEntity(
|
||||||
|
req,
|
||||||
|
res,
|
||||||
|
err.message,
|
||||||
|
OError.getFullInfo(err).public
|
||||||
|
)
|
||||||
|
} else {
|
||||||
logger.warn(
|
logger.warn(
|
||||||
{ err, user_id: user._id },
|
{ err, user_id: user._id },
|
||||||
'something went wrong creating subscription'
|
'something went wrong creating subscription'
|
||||||
)
|
)
|
||||||
next(err)
|
next(err)
|
||||||
}
|
}
|
||||||
)
|
}
|
||||||
}
|
)
|
||||||
)
|
}
|
||||||
},
|
)
|
||||||
|
}
|
||||||
|
|
||||||
successful_subscription(req, res, next) {
|
function successfulSubscription(req, res, next) {
|
||||||
const user = AuthenticationController.getSessionUser(req)
|
const user = AuthenticationController.getSessionUser(req)
|
||||||
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
|
return SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel(
|
||||||
user,
|
user,
|
||||||
function (error, { personalSubscription }) {
|
function (error, { personalSubscription }) {
|
||||||
if (error != null) {
|
if (error) {
|
||||||
return next(error)
|
return next(error)
|
||||||
}
|
}
|
||||||
if (personalSubscription == null) {
|
if (personalSubscription == null) {
|
||||||
return res.redirect('/user/subscription/plans')
|
res.redirect('/user/subscription/plans')
|
||||||
}
|
} else {
|
||||||
return res.render('subscriptions/successful_subscription', {
|
res.render('subscriptions/successful_subscription', {
|
||||||
title: 'thank_you',
|
title: 'thank_you',
|
||||||
personalSubscription,
|
personalSubscription,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelSubscription(req, res, next) {
|
||||||
|
const user = AuthenticationController.getSessionUser(req)
|
||||||
|
logger.log({ user_id: user._id }, 'canceling subscription')
|
||||||
|
SubscriptionHandler.cancelSubscription(user, function (err) {
|
||||||
|
if (err) {
|
||||||
|
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
|
||||||
|
res.redirect('/user/subscription/canceled')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function canceledSubscription(req, res, next) {
|
||||||
|
return res.render('subscriptions/canceled_subscription', {
|
||||||
|
title: 'subscription_canceled',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function cancelV1Subscription(req, res, next) {
|
||||||
|
const userId = AuthenticationController.getLoggedInUserId(req)
|
||||||
|
logger.log({ userId }, 'canceling v1 subscription')
|
||||||
|
V1SubscriptionManager.cancelV1Subscription(userId, function (err) {
|
||||||
|
if (err) {
|
||||||
|
OError.tag(err, 'something went wrong canceling v1 subscription', {
|
||||||
|
userId,
|
||||||
|
})
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
res.redirect('/user/subscription')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateSubscription(req, res, next) {
|
||||||
|
const origin = req && req.query ? req.query.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, body: req.body },
|
||||||
|
'[Subscription] error in updateSubscription form'
|
||||||
)
|
)
|
||||||
},
|
return next(err)
|
||||||
|
}
|
||||||
|
logger.log({ planCode, user_id: user._id }, 'updating subscription')
|
||||||
|
SubscriptionHandler.updateSubscription(user, planCode, null, function (err) {
|
||||||
|
if (err) {
|
||||||
|
OError.tag(err, 'something went wrong updating subscription', {
|
||||||
|
user_id: user._id,
|
||||||
|
})
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
res.redirect('/user/subscription')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
cancelSubscription(req, res, next) {
|
function cancelPendingSubscriptionChange(req, res, next) {
|
||||||
const user = AuthenticationController.getSessionUser(req)
|
const user = AuthenticationController.getSessionUser(req)
|
||||||
logger.log({ user_id: user._id }, 'canceling subscription')
|
logger.log({ user_id: user._id }, 'canceling pending subscription change')
|
||||||
return SubscriptionHandler.cancelSubscription(user, function (err) {
|
SubscriptionHandler.cancelPendingSubscriptionChange(user, function (err) {
|
||||||
if (err != null) {
|
if (err) {
|
||||||
OError.tag(err, 'something went wrong canceling subscription', {
|
OError.tag(
|
||||||
|
err,
|
||||||
|
'something went wrong canceling pending subscription change',
|
||||||
|
{
|
||||||
user_id: user._id,
|
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)
|
return next(err)
|
||||||
}
|
}
|
||||||
logger.log({ planCode, user_id: user._id }, 'updating subscription')
|
res.redirect('/user/subscription')
|
||||||
return SubscriptionHandler.updateSubscription(
|
})
|
||||||
user,
|
}
|
||||||
planCode,
|
|
||||||
null,
|
function 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)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function reactivateSubscription(req, res, next) {
|
||||||
|
const user = AuthenticationController.getSessionUser(req)
|
||||||
|
logger.log({ user_id: user._id }, 'reactivating subscription')
|
||||||
|
SubscriptionHandler.reactivateSubscription(user, function (err) {
|
||||||
|
if (err) {
|
||||||
|
OError.tag(err, 'something went wrong reactivating subscription', {
|
||||||
|
user_id: user._id,
|
||||||
|
})
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
res.redirect('/user/subscription')
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function 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
|
||||||
|
SubscriptionHandler.syncSubscription(
|
||||||
|
recurlySubscription,
|
||||||
|
{ ip: req.ip },
|
||||||
function (err) {
|
function (err) {
|
||||||
if (err != null) {
|
if (err) {
|
||||||
OError.tag(err, 'something went wrong updating subscription', {
|
|
||||||
user_id: user._id,
|
|
||||||
})
|
|
||||||
return next(err)
|
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)
|
res.sendStatus(200)
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
},
|
} else if (event === 'billing_info_updated_notification') {
|
||||||
|
const recurlyAccountCode = eventData.account.account_code
|
||||||
|
SubscriptionHandler.attemptPaypalInvoiceCollection(
|
||||||
|
recurlyAccountCode,
|
||||||
|
function (err) {
|
||||||
|
if (err) {
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
res.sendStatus(200)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
res.sendStatus(200)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
reactivateSubscription(req, res, next) {
|
function renderUpgradeToAnnualPlanPage(req, res, next) {
|
||||||
const user = AuthenticationController.getSessionUser(req)
|
const user = AuthenticationController.getSessionUser(req)
|
||||||
logger.log({ user_id: user._id }, 'reactivating subscription')
|
LimitationsManager.userHasV2Subscription(
|
||||||
SubscriptionHandler.reactivateSubscription(user, function (err) {
|
user,
|
||||||
if (err != null) {
|
function (err, hasSubscription, subscription) {
|
||||||
OError.tag(err, 'something went wrong reactivating subscription', {
|
let planName
|
||||||
|
if (err) {
|
||||||
|
return next(err)
|
||||||
|
}
|
||||||
|
const planCode = subscription
|
||||||
|
? subscription.planCode.toLowerCase()
|
||||||
|
: undefined
|
||||||
|
if ((planCode ? planCode.indexOf('annual') : undefined) !== -1) {
|
||||||
|
planName = 'annual'
|
||||||
|
} else if ((planCode ? planCode.indexOf('student') : undefined) !== -1) {
|
||||||
|
planName = 'student'
|
||||||
|
} else if (
|
||||||
|
(planCode ? planCode.indexOf('collaborator') : undefined) !== -1
|
||||||
|
) {
|
||||||
|
planName = 'collaborator'
|
||||||
|
}
|
||||||
|
if (hasSubscription) {
|
||||||
|
res.render('subscriptions/upgradeToAnnual', {
|
||||||
|
title: 'Upgrade to annual',
|
||||||
|
planName,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
res.redirect('/user/subscription/plans')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function processUpgradeToAnnualPlan(req, res, next) {
|
||||||
|
const user = AuthenticationController.getSessionUser(req)
|
||||||
|
const { planName } = req.body
|
||||||
|
const couponCode = 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,
|
||||||
|
couponCode,
|
||||||
|
function (err) {
|
||||||
|
if (err) {
|
||||||
|
OError.tag(err, 'error updating subscription', {
|
||||||
user_id: user._id,
|
user_id: user._id,
|
||||||
})
|
})
|
||||||
return next(err)
|
return next(err)
|
||||||
}
|
}
|
||||||
res.redirect('/user/subscription')
|
res.sendStatus(200)
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
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) {
|
async function extendTrial(req, res) {
|
||||||
return typeof value !== 'undefined' && value !== null
|
const user = AuthenticationController.getSessionUser(req)
|
||||||
? transform(value)
|
const {
|
||||||
: undefined
|
subscription,
|
||||||
|
} = await LimitationsManager.promises.userHasV2Subscription(user)
|
||||||
|
|
||||||
|
try {
|
||||||
|
await SubscriptionHandler.promises.extendTrial(subscription, 14)
|
||||||
|
} catch (error) {
|
||||||
|
return res.sendStatus(500)
|
||||||
|
}
|
||||||
|
res.sendStatus(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
function recurlyNotificationParser(req, res, next) {
|
||||||
|
let xml = ''
|
||||||
|
req.on('data', chunk => (xml += chunk))
|
||||||
|
req.on('end', () =>
|
||||||
|
RecurlyWrapper._parseXml(xml, function (error, body) {
|
||||||
|
if (error) {
|
||||||
|
return next(error)
|
||||||
|
}
|
||||||
|
req.body = body
|
||||||
|
next()
|
||||||
|
})
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function refreshUserFeatures(req, res) {
|
||||||
|
const { user_id: userId } = req.params
|
||||||
|
await FeaturesUpdater.promises.refreshFeatures(userId)
|
||||||
|
res.sendStatus(200)
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
plansPage: expressify(plansPage),
|
||||||
|
paymentPage: expressify(paymentPage),
|
||||||
|
userSubscriptionPage,
|
||||||
|
createSubscription,
|
||||||
|
successfulSubscription,
|
||||||
|
cancelSubscription,
|
||||||
|
canceledSubscription,
|
||||||
|
cancelV1Subscription,
|
||||||
|
updateSubscription,
|
||||||
|
cancelPendingSubscriptionChange,
|
||||||
|
updateAccountEmailAddress,
|
||||||
|
reactivateSubscription,
|
||||||
|
recurlyCallback,
|
||||||
|
renderUpgradeToAnnualPlanPage,
|
||||||
|
processUpgradeToAnnualPlan,
|
||||||
|
extendTrial: expressify(extendTrial),
|
||||||
|
recurlyNotificationParser,
|
||||||
|
refreshUserFeatures: expressify(refreshUserFeatures),
|
||||||
}
|
}
|
||||||
|
|
|
@ -38,7 +38,7 @@ module.exports = {
|
||||||
webRouter.get(
|
webRouter.get(
|
||||||
'/user/subscription/thank-you',
|
'/user/subscription/thank-you',
|
||||||
AuthenticationController.requireLogin(),
|
AuthenticationController.requireLogin(),
|
||||||
SubscriptionController.successful_subscription
|
SubscriptionController.successfulSubscription
|
||||||
)
|
)
|
||||||
|
|
||||||
webRouter.get(
|
webRouter.get(
|
||||||
|
|
|
@ -10,6 +10,7 @@ const sanitizeHtml = require('sanitize-html')
|
||||||
const _ = require('underscore')
|
const _ = require('underscore')
|
||||||
const async = require('async')
|
const async = require('async')
|
||||||
const SubscriptionHelper = require('./SubscriptionHelper')
|
const SubscriptionHelper = require('./SubscriptionHelper')
|
||||||
|
const { promisify } = require('../../util/promises')
|
||||||
|
|
||||||
function buildHostedLink(recurlySubscription, type) {
|
function buildHostedLink(recurlySubscription, type) {
|
||||||
const recurlySubdomain = Settings.apis.recurly.subdomain
|
const recurlySubdomain = Settings.apis.recurly.subdomain
|
||||||
|
@ -29,311 +30,312 @@ function buildHostedLink(recurlySubscription, type) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
module.exports = {
|
function buildUsersSubscriptionViewModel(user, callback) {
|
||||||
buildUsersSubscriptionViewModel(user, callback) {
|
async.auto(
|
||||||
async.auto(
|
{
|
||||||
{
|
personalSubscription(cb) {
|
||||||
personalSubscription(cb) {
|
SubscriptionLocator.getUsersSubscription(user, cb)
|
||||||
SubscriptionLocator.getUsersSubscription(user, cb)
|
},
|
||||||
},
|
recurlySubscription: [
|
||||||
recurlySubscription: [
|
'personalSubscription',
|
||||||
'personalSubscription',
|
(cb, { personalSubscription }) => {
|
||||||
(cb, { personalSubscription }) => {
|
if (
|
||||||
if (
|
personalSubscription == null ||
|
||||||
personalSubscription == null ||
|
personalSubscription.recurlySubscription_id == null ||
|
||||||
personalSubscription.recurlySubscription_id == null ||
|
personalSubscription.recurlySubscription_id === ''
|
||||||
personalSubscription.recurlySubscription_id === ''
|
) {
|
||||||
) {
|
return cb(null, null)
|
||||||
return cb(null, null)
|
}
|
||||||
}
|
RecurlyWrapper.getSubscription(
|
||||||
RecurlyWrapper.getSubscription(
|
personalSubscription.recurlySubscription_id,
|
||||||
personalSubscription.recurlySubscription_id,
|
{ includeAccount: true },
|
||||||
{ includeAccount: true },
|
cb
|
||||||
cb
|
|
||||||
)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
recurlyCoupons: [
|
|
||||||
'recurlySubscription',
|
|
||||||
(cb, { recurlySubscription }) => {
|
|
||||||
if (!recurlySubscription) {
|
|
||||||
return cb(null, null)
|
|
||||||
}
|
|
||||||
const accountId = recurlySubscription.account.account_code
|
|
||||||
RecurlyWrapper.getAccountActiveCoupons(accountId, cb)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
plan: [
|
|
||||||
'personalSubscription',
|
|
||||||
(cb, { personalSubscription }) => {
|
|
||||||
if (personalSubscription == null) {
|
|
||||||
return cb()
|
|
||||||
}
|
|
||||||
const plan = PlansLocator.findLocalPlanInSettings(
|
|
||||||
personalSubscription.planCode
|
|
||||||
)
|
|
||||||
if (plan == null) {
|
|
||||||
return cb(
|
|
||||||
new Error(
|
|
||||||
`No plan found for planCode '${personalSubscription.planCode}'`
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
cb(null, plan)
|
|
||||||
},
|
|
||||||
],
|
|
||||||
memberGroupSubscriptions(cb) {
|
|
||||||
SubscriptionLocator.getMemberSubscriptions(user, cb)
|
|
||||||
},
|
|
||||||
managedGroupSubscriptions(cb) {
|
|
||||||
SubscriptionLocator.getManagedGroupSubscriptions(user, cb)
|
|
||||||
},
|
|
||||||
confirmedMemberAffiliations(cb) {
|
|
||||||
InstitutionsGetter.getConfirmedAffiliations(user._id, cb)
|
|
||||||
},
|
|
||||||
managedInstitutions(cb) {
|
|
||||||
InstitutionsGetter.getManagedInstitutions(user._id, cb)
|
|
||||||
},
|
|
||||||
managedPublishers(cb) {
|
|
||||||
PublishersGetter.getManagedPublishers(user._id, cb)
|
|
||||||
},
|
|
||||||
v1SubscriptionStatus(cb) {
|
|
||||||
V1SubscriptionManager.getSubscriptionStatusFromV1(
|
|
||||||
user._id,
|
|
||||||
(error, status, v1Id) => {
|
|
||||||
if (error) {
|
|
||||||
return cb(error)
|
|
||||||
}
|
|
||||||
cb(null, status)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
},
|
],
|
||||||
(err, results) => {
|
recurlyCoupons: [
|
||||||
if (err) {
|
'recurlySubscription',
|
||||||
return callback(err)
|
(cb, { recurlySubscription }) => {
|
||||||
}
|
if (!recurlySubscription) {
|
||||||
let {
|
return cb(null, null)
|
||||||
personalSubscription,
|
|
||||||
memberGroupSubscriptions,
|
|
||||||
managedGroupSubscriptions,
|
|
||||||
confirmedMemberAffiliations,
|
|
||||||
managedInstitutions,
|
|
||||||
managedPublishers,
|
|
||||||
v1SubscriptionStatus,
|
|
||||||
recurlySubscription,
|
|
||||||
recurlyCoupons,
|
|
||||||
plan,
|
|
||||||
} = results
|
|
||||||
if (memberGroupSubscriptions == null) {
|
|
||||||
memberGroupSubscriptions = []
|
|
||||||
}
|
|
||||||
if (managedGroupSubscriptions == null) {
|
|
||||||
managedGroupSubscriptions = []
|
|
||||||
}
|
|
||||||
if (confirmedMemberAffiliations == null) {
|
|
||||||
confirmedMemberAffiliations = []
|
|
||||||
}
|
|
||||||
if (managedInstitutions == null) {
|
|
||||||
managedInstitutions = []
|
|
||||||
}
|
|
||||||
if (v1SubscriptionStatus == null) {
|
|
||||||
v1SubscriptionStatus = {}
|
|
||||||
}
|
|
||||||
if (recurlyCoupons == null) {
|
|
||||||
recurlyCoupons = []
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
personalSubscription &&
|
|
||||||
typeof personalSubscription.toObject === 'function'
|
|
||||||
) {
|
|
||||||
// Downgrade from Mongoose object, so we can add a recurly and plan attribute
|
|
||||||
personalSubscription = personalSubscription.toObject()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (plan != null) {
|
|
||||||
personalSubscription.plan = plan
|
|
||||||
}
|
|
||||||
|
|
||||||
if (personalSubscription && recurlySubscription) {
|
|
||||||
const tax = recurlySubscription.tax_in_cents || 0
|
|
||||||
// Some plans allow adding more seats than the base plan provides.
|
|
||||||
// This is recorded as a subscription add on.
|
|
||||||
// Note: tax_in_cents already includes the tax for any addon.
|
|
||||||
let addOnPrice = 0
|
|
||||||
let additionalLicenses = 0
|
|
||||||
if (
|
|
||||||
plan.membersLimitAddOn &&
|
|
||||||
Array.isArray(recurlySubscription.subscription_add_ons)
|
|
||||||
) {
|
|
||||||
recurlySubscription.subscription_add_ons.forEach(addOn => {
|
|
||||||
if (addOn.add_on_code === plan.membersLimitAddOn) {
|
|
||||||
addOnPrice += addOn.quantity * addOn.unit_amount_in_cents
|
|
||||||
additionalLicenses += addOn.quantity
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
const totalLicenses = (plan.membersLimit || 0) + additionalLicenses
|
const accountId = recurlySubscription.account.account_code
|
||||||
personalSubscription.recurly = {
|
RecurlyWrapper.getAccountActiveCoupons(accountId, cb)
|
||||||
tax,
|
},
|
||||||
taxRate: recurlySubscription.tax_rate
|
],
|
||||||
? parseFloat(recurlySubscription.tax_rate._)
|
plan: [
|
||||||
: 0,
|
'personalSubscription',
|
||||||
billingDetailsLink: buildHostedLink(
|
(cb, { personalSubscription }) => {
|
||||||
recurlySubscription,
|
if (personalSubscription == null) {
|
||||||
'billingDetails'
|
return cb()
|
||||||
),
|
|
||||||
accountManagementLink: buildHostedLink(recurlySubscription),
|
|
||||||
additionalLicenses,
|
|
||||||
totalLicenses,
|
|
||||||
nextPaymentDueAt: SubscriptionFormatters.formatDate(
|
|
||||||
recurlySubscription.current_period_ends_at
|
|
||||||
),
|
|
||||||
currency: recurlySubscription.currency,
|
|
||||||
state: recurlySubscription.state,
|
|
||||||
trialEndsAtFormatted: SubscriptionFormatters.formatDate(
|
|
||||||
recurlySubscription.trial_ends_at
|
|
||||||
),
|
|
||||||
trial_ends_at: recurlySubscription.trial_ends_at,
|
|
||||||
activeCoupons: recurlyCoupons,
|
|
||||||
account: recurlySubscription.account,
|
|
||||||
}
|
}
|
||||||
if (recurlySubscription.pending_subscription) {
|
const plan = PlansLocator.findLocalPlanInSettings(
|
||||||
const pendingPlan = PlansLocator.findLocalPlanInSettings(
|
personalSubscription.planCode
|
||||||
recurlySubscription.pending_subscription.plan.plan_code
|
)
|
||||||
|
if (plan == null) {
|
||||||
|
return cb(
|
||||||
|
new Error(
|
||||||
|
`No plan found for planCode '${personalSubscription.planCode}'`
|
||||||
|
)
|
||||||
)
|
)
|
||||||
if (pendingPlan == null) {
|
}
|
||||||
return callback(
|
cb(null, plan)
|
||||||
new Error(
|
},
|
||||||
`No plan found for planCode '${personalSubscription.planCode}'`
|
],
|
||||||
)
|
memberGroupSubscriptions(cb) {
|
||||||
|
SubscriptionLocator.getMemberSubscriptions(user, cb)
|
||||||
|
},
|
||||||
|
managedGroupSubscriptions(cb) {
|
||||||
|
SubscriptionLocator.getManagedGroupSubscriptions(user, cb)
|
||||||
|
},
|
||||||
|
confirmedMemberAffiliations(cb) {
|
||||||
|
InstitutionsGetter.getConfirmedAffiliations(user._id, cb)
|
||||||
|
},
|
||||||
|
managedInstitutions(cb) {
|
||||||
|
InstitutionsGetter.getManagedInstitutions(user._id, cb)
|
||||||
|
},
|
||||||
|
managedPublishers(cb) {
|
||||||
|
PublishersGetter.getManagedPublishers(user._id, cb)
|
||||||
|
},
|
||||||
|
v1SubscriptionStatus(cb) {
|
||||||
|
V1SubscriptionManager.getSubscriptionStatusFromV1(
|
||||||
|
user._id,
|
||||||
|
(error, status, v1Id) => {
|
||||||
|
if (error) {
|
||||||
|
return cb(error)
|
||||||
|
}
|
||||||
|
cb(null, status)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
},
|
||||||
|
},
|
||||||
|
(err, results) => {
|
||||||
|
if (err) {
|
||||||
|
return callback(err)
|
||||||
|
}
|
||||||
|
let {
|
||||||
|
personalSubscription,
|
||||||
|
memberGroupSubscriptions,
|
||||||
|
managedGroupSubscriptions,
|
||||||
|
confirmedMemberAffiliations,
|
||||||
|
managedInstitutions,
|
||||||
|
managedPublishers,
|
||||||
|
v1SubscriptionStatus,
|
||||||
|
recurlySubscription,
|
||||||
|
recurlyCoupons,
|
||||||
|
plan,
|
||||||
|
} = results
|
||||||
|
if (memberGroupSubscriptions == null) {
|
||||||
|
memberGroupSubscriptions = []
|
||||||
|
}
|
||||||
|
if (managedGroupSubscriptions == null) {
|
||||||
|
managedGroupSubscriptions = []
|
||||||
|
}
|
||||||
|
if (confirmedMemberAffiliations == null) {
|
||||||
|
confirmedMemberAffiliations = []
|
||||||
|
}
|
||||||
|
if (managedInstitutions == null) {
|
||||||
|
managedInstitutions = []
|
||||||
|
}
|
||||||
|
if (v1SubscriptionStatus == null) {
|
||||||
|
v1SubscriptionStatus = {}
|
||||||
|
}
|
||||||
|
if (recurlyCoupons == null) {
|
||||||
|
recurlyCoupons = []
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
personalSubscription &&
|
||||||
|
typeof personalSubscription.toObject === 'function'
|
||||||
|
) {
|
||||||
|
// Downgrade from Mongoose object, so we can add a recurly and plan attribute
|
||||||
|
personalSubscription = personalSubscription.toObject()
|
||||||
|
}
|
||||||
|
|
||||||
|
if (plan != null) {
|
||||||
|
personalSubscription.plan = plan
|
||||||
|
}
|
||||||
|
|
||||||
|
if (personalSubscription && recurlySubscription) {
|
||||||
|
const tax = recurlySubscription.tax_in_cents || 0
|
||||||
|
// Some plans allow adding more seats than the base plan provides.
|
||||||
|
// This is recorded as a subscription add on.
|
||||||
|
// Note: tax_in_cents already includes the tax for any addon.
|
||||||
|
let addOnPrice = 0
|
||||||
|
let additionalLicenses = 0
|
||||||
|
if (
|
||||||
|
plan.membersLimitAddOn &&
|
||||||
|
Array.isArray(recurlySubscription.subscription_add_ons)
|
||||||
|
) {
|
||||||
|
recurlySubscription.subscription_add_ons.forEach(addOn => {
|
||||||
|
if (addOn.add_on_code === plan.membersLimitAddOn) {
|
||||||
|
addOnPrice += addOn.quantity * addOn.unit_amount_in_cents
|
||||||
|
additionalLicenses += addOn.quantity
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const totalLicenses = (plan.membersLimit || 0) + additionalLicenses
|
||||||
|
personalSubscription.recurly = {
|
||||||
|
tax,
|
||||||
|
taxRate: recurlySubscription.tax_rate
|
||||||
|
? parseFloat(recurlySubscription.tax_rate._)
|
||||||
|
: 0,
|
||||||
|
billingDetailsLink: buildHostedLink(
|
||||||
|
recurlySubscription,
|
||||||
|
'billingDetails'
|
||||||
|
),
|
||||||
|
accountManagementLink: buildHostedLink(recurlySubscription),
|
||||||
|
additionalLicenses,
|
||||||
|
totalLicenses,
|
||||||
|
nextPaymentDueAt: SubscriptionFormatters.formatDate(
|
||||||
|
recurlySubscription.current_period_ends_at
|
||||||
|
),
|
||||||
|
currency: recurlySubscription.currency,
|
||||||
|
state: recurlySubscription.state,
|
||||||
|
trialEndsAtFormatted: SubscriptionFormatters.formatDate(
|
||||||
|
recurlySubscription.trial_ends_at
|
||||||
|
),
|
||||||
|
trial_ends_at: recurlySubscription.trial_ends_at,
|
||||||
|
activeCoupons: recurlyCoupons,
|
||||||
|
account: recurlySubscription.account,
|
||||||
|
}
|
||||||
|
if (recurlySubscription.pending_subscription) {
|
||||||
|
const pendingPlan = PlansLocator.findLocalPlanInSettings(
|
||||||
|
recurlySubscription.pending_subscription.plan.plan_code
|
||||||
|
)
|
||||||
|
if (pendingPlan == null) {
|
||||||
|
return callback(
|
||||||
|
new Error(
|
||||||
|
`No plan found for planCode '${personalSubscription.planCode}'`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
let pendingAdditionalLicenses = 0
|
||||||
|
let pendingAddOnTax = 0
|
||||||
|
let pendingAddOnPrice = 0
|
||||||
|
if (recurlySubscription.pending_subscription.subscription_add_ons) {
|
||||||
|
if (
|
||||||
|
pendingPlan.membersLimitAddOn &&
|
||||||
|
Array.isArray(
|
||||||
|
recurlySubscription.pending_subscription.subscription_add_ons
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
recurlySubscription.pending_subscription.subscription_add_ons.forEach(
|
||||||
|
addOn => {
|
||||||
|
if (addOn.add_on_code === pendingPlan.membersLimitAddOn) {
|
||||||
|
pendingAddOnPrice +=
|
||||||
|
addOn.quantity * addOn.unit_amount_in_cents
|
||||||
|
pendingAdditionalLicenses += addOn.quantity
|
||||||
|
}
|
||||||
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
let pendingAdditionalLicenses = 0
|
// Need to calculate tax ourselves as we don't get tax amounts for pending subs
|
||||||
let pendingAddOnTax = 0
|
pendingAddOnTax =
|
||||||
let pendingAddOnPrice = 0
|
personalSubscription.recurly.taxRate * pendingAddOnPrice
|
||||||
if (recurlySubscription.pending_subscription.subscription_add_ons) {
|
|
||||||
if (
|
|
||||||
pendingPlan.membersLimitAddOn &&
|
|
||||||
Array.isArray(
|
|
||||||
recurlySubscription.pending_subscription.subscription_add_ons
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
recurlySubscription.pending_subscription.subscription_add_ons.forEach(
|
|
||||||
addOn => {
|
|
||||||
if (addOn.add_on_code === pendingPlan.membersLimitAddOn) {
|
|
||||||
pendingAddOnPrice +=
|
|
||||||
addOn.quantity * addOn.unit_amount_in_cents
|
|
||||||
pendingAdditionalLicenses += addOn.quantity
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
}
|
|
||||||
// Need to calculate tax ourselves as we don't get tax amounts for pending subs
|
|
||||||
pendingAddOnTax =
|
|
||||||
personalSubscription.recurly.taxRate * pendingAddOnPrice
|
|
||||||
}
|
|
||||||
const pendingSubscriptionTax =
|
|
||||||
personalSubscription.recurly.taxRate *
|
|
||||||
recurlySubscription.pending_subscription.unit_amount_in_cents
|
|
||||||
personalSubscription.recurly.price = SubscriptionFormatters.formatPrice(
|
|
||||||
recurlySubscription.pending_subscription.unit_amount_in_cents +
|
|
||||||
pendingAddOnPrice +
|
|
||||||
pendingAddOnTax +
|
|
||||||
pendingSubscriptionTax,
|
|
||||||
recurlySubscription.currency
|
|
||||||
)
|
|
||||||
const pendingTotalLicenses =
|
|
||||||
(pendingPlan.membersLimit || 0) + pendingAdditionalLicenses
|
|
||||||
personalSubscription.recurly.pendingAdditionalLicenses = pendingAdditionalLicenses
|
|
||||||
personalSubscription.recurly.pendingTotalLicenses = pendingTotalLicenses
|
|
||||||
personalSubscription.pendingPlan = pendingPlan
|
|
||||||
} else {
|
|
||||||
personalSubscription.recurly.price = SubscriptionFormatters.formatPrice(
|
|
||||||
recurlySubscription.unit_amount_in_cents + addOnPrice + tax,
|
|
||||||
recurlySubscription.currency
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
const pendingSubscriptionTax =
|
||||||
|
personalSubscription.recurly.taxRate *
|
||||||
|
recurlySubscription.pending_subscription.unit_amount_in_cents
|
||||||
|
personalSubscription.recurly.price = SubscriptionFormatters.formatPrice(
|
||||||
|
recurlySubscription.pending_subscription.unit_amount_in_cents +
|
||||||
|
pendingAddOnPrice +
|
||||||
|
pendingAddOnTax +
|
||||||
|
pendingSubscriptionTax,
|
||||||
|
recurlySubscription.currency
|
||||||
|
)
|
||||||
|
const pendingTotalLicenses =
|
||||||
|
(pendingPlan.membersLimit || 0) + pendingAdditionalLicenses
|
||||||
|
personalSubscription.recurly.pendingAdditionalLicenses = pendingAdditionalLicenses
|
||||||
|
personalSubscription.recurly.pendingTotalLicenses = pendingTotalLicenses
|
||||||
|
personalSubscription.pendingPlan = pendingPlan
|
||||||
|
} else {
|
||||||
|
personalSubscription.recurly.price = SubscriptionFormatters.formatPrice(
|
||||||
|
recurlySubscription.unit_amount_in_cents + addOnPrice + tax,
|
||||||
|
recurlySubscription.currency
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const memberGroupSubscription of memberGroupSubscriptions) {
|
|
||||||
if (memberGroupSubscription.teamNotice) {
|
|
||||||
memberGroupSubscription.teamNotice = sanitizeHtml(
|
|
||||||
memberGroupSubscription.teamNotice
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
callback(null, {
|
|
||||||
personalSubscription,
|
|
||||||
managedGroupSubscriptions,
|
|
||||||
memberGroupSubscriptions,
|
|
||||||
confirmedMemberAffiliations,
|
|
||||||
managedInstitutions,
|
|
||||||
managedPublishers,
|
|
||||||
v1SubscriptionStatus,
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
)
|
|
||||||
},
|
|
||||||
|
|
||||||
buildPlansList(currentPlan) {
|
for (const memberGroupSubscription of memberGroupSubscriptions) {
|
||||||
const { plans } = Settings
|
if (memberGroupSubscription.teamNotice) {
|
||||||
|
memberGroupSubscription.teamNotice = sanitizeHtml(
|
||||||
|
memberGroupSubscription.teamNotice
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const allPlans = {}
|
callback(null, {
|
||||||
plans.forEach(plan => {
|
personalSubscription,
|
||||||
allPlans[plan.planCode] = plan
|
managedGroupSubscriptions,
|
||||||
})
|
memberGroupSubscriptions,
|
||||||
|
confirmedMemberAffiliations,
|
||||||
const result = { allPlans }
|
managedInstitutions,
|
||||||
|
managedPublishers,
|
||||||
if (currentPlan) {
|
v1SubscriptionStatus,
|
||||||
result.planCodesChangingAtTermEnd = _.pluck(
|
})
|
||||||
_.filter(plans, plan => {
|
|
||||||
if (!plan.hideFromUsers) {
|
|
||||||
return SubscriptionHelper.shouldPlanChangeAtTermEnd(
|
|
||||||
currentPlan,
|
|
||||||
plan
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
'planCode'
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
result.studentAccounts = _.filter(
|
function buildPlansList(currentPlan) {
|
||||||
plans,
|
const { plans } = Settings
|
||||||
plan => plan.planCode.indexOf('student') !== -1
|
|
||||||
|
const allPlans = {}
|
||||||
|
plans.forEach(plan => {
|
||||||
|
allPlans[plan.planCode] = plan
|
||||||
|
})
|
||||||
|
|
||||||
|
const result = { allPlans }
|
||||||
|
|
||||||
|
if (currentPlan) {
|
||||||
|
result.planCodesChangingAtTermEnd = _.pluck(
|
||||||
|
_.filter(plans, plan => {
|
||||||
|
if (!plan.hideFromUsers) {
|
||||||
|
return SubscriptionHelper.shouldPlanChangeAtTermEnd(currentPlan, plan)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'planCode'
|
||||||
)
|
)
|
||||||
|
}
|
||||||
|
|
||||||
result.groupMonthlyPlans = _.filter(
|
result.studentAccounts = _.filter(
|
||||||
plans,
|
plans,
|
||||||
plan => plan.groupPlan && !plan.annual
|
plan => plan.planCode.indexOf('student') !== -1
|
||||||
)
|
)
|
||||||
|
|
||||||
result.groupAnnualPlans = _.filter(
|
result.groupMonthlyPlans = _.filter(
|
||||||
plans,
|
plans,
|
||||||
plan => plan.groupPlan && plan.annual
|
plan => plan.groupPlan && !plan.annual
|
||||||
)
|
)
|
||||||
|
|
||||||
result.individualMonthlyPlans = _.filter(
|
result.groupAnnualPlans = _.filter(
|
||||||
plans,
|
plans,
|
||||||
plan =>
|
plan => plan.groupPlan && plan.annual
|
||||||
!plan.groupPlan &&
|
)
|
||||||
!plan.annual &&
|
|
||||||
plan.planCode !== 'personal' && // Prevent the personal plan from appearing on the change-plans page
|
|
||||||
plan.planCode.indexOf('student') === -1
|
|
||||||
)
|
|
||||||
|
|
||||||
result.individualAnnualPlans = _.filter(
|
result.individualMonthlyPlans = _.filter(
|
||||||
plans,
|
plans,
|
||||||
plan =>
|
plan =>
|
||||||
!plan.groupPlan &&
|
!plan.groupPlan &&
|
||||||
plan.annual &&
|
!plan.annual &&
|
||||||
plan.planCode.indexOf('student') === -1
|
plan.planCode !== 'personal' && // Prevent the personal plan from appearing on the change-plans page
|
||||||
)
|
plan.planCode.indexOf('student') === -1
|
||||||
|
)
|
||||||
|
|
||||||
return result
|
result.individualAnnualPlans = _.filter(
|
||||||
|
plans,
|
||||||
|
plan =>
|
||||||
|
!plan.groupPlan && plan.annual && plan.planCode.indexOf('student') === -1
|
||||||
|
)
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
buildUsersSubscriptionViewModel,
|
||||||
|
buildPlansList,
|
||||||
|
promises: {
|
||||||
|
buildUsersSubscriptionViewModel: promisify(buildUsersSubscriptionViewModel),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,22 +1,9 @@
|
||||||
/* eslint-disable
|
|
||||||
max-len,
|
|
||||||
no-return-assign,
|
|
||||||
*/
|
|
||||||
// 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 GeoIpLookup
|
|
||||||
const request = require('request')
|
const request = require('request')
|
||||||
const settings = require('settings-sharelatex')
|
const settings = require('settings-sharelatex')
|
||||||
const _ = require('underscore')
|
const _ = require('underscore')
|
||||||
const logger = require('logger-sharelatex')
|
const logger = require('logger-sharelatex')
|
||||||
const URL = require('url')
|
const URL = require('url')
|
||||||
|
const { promisify } = require('../util/promises')
|
||||||
|
|
||||||
const currencyMappings = {
|
const currencyMappings = {
|
||||||
GB: 'GBP',
|
GB: 'GBP',
|
||||||
|
@ -61,49 +48,50 @@ const EuroCountries = [
|
||||||
|
|
||||||
_.each(EuroCountries, country => (currencyMappings[country] = 'EUR'))
|
_.each(EuroCountries, country => (currencyMappings[country] = 'EUR'))
|
||||||
|
|
||||||
module.exports = GeoIpLookup = {
|
function getDetails(ip, callback) {
|
||||||
getDetails(ip, callback) {
|
if (ip == null) {
|
||||||
if (ip == null) {
|
const e = new Error('no ip passed')
|
||||||
const e = new Error('no ip passed')
|
return callback(e)
|
||||||
return callback(e)
|
}
|
||||||
|
ip = ip.trim().split(' ')[0]
|
||||||
|
const opts = {
|
||||||
|
url: URL.resolve(settings.apis.geoIpLookup.url, ip),
|
||||||
|
timeout: 1000,
|
||||||
|
json: true,
|
||||||
|
}
|
||||||
|
logger.log({ ip, opts }, 'getting geo ip details')
|
||||||
|
request.get(opts, function (err, res, ipDetails) {
|
||||||
|
if (err) {
|
||||||
|
logger.warn({ err, ip }, 'error getting ip details')
|
||||||
}
|
}
|
||||||
ip = ip.trim().split(' ')[0]
|
callback(err, ipDetails)
|
||||||
const opts = {
|
})
|
||||||
url: URL.resolve(settings.apis.geoIpLookup.url, ip),
|
}
|
||||||
timeout: 1000,
|
|
||||||
json: true,
|
|
||||||
}
|
|
||||||
logger.log({ ip, opts }, 'getting geo ip details')
|
|
||||||
return request.get(opts, function (err, res, ipDetails) {
|
|
||||||
if (err != null) {
|
|
||||||
logger.warn({ err, ip }, 'error getting ip details')
|
|
||||||
}
|
|
||||||
return callback(err, ipDetails)
|
|
||||||
})
|
|
||||||
},
|
|
||||||
|
|
||||||
getCurrencyCode(ip, callback) {
|
function getCurrencyCode(ip, callback) {
|
||||||
return GeoIpLookup.getDetails(ip, function (err, ipDetails) {
|
getDetails(ip, function (err, ipDetails) {
|
||||||
if (err != null || ipDetails == null) {
|
if (err || !ipDetails) {
|
||||||
logger.err(
|
logger.err(
|
||||||
{ err, ip },
|
{ err, ip },
|
||||||
'problem getting currencyCode for ip, defaulting to USD'
|
'problem getting currencyCode for ip, defaulting to USD'
|
||||||
)
|
|
||||||
return callback(null, 'USD')
|
|
||||||
}
|
|
||||||
const countryCode = __guard__(
|
|
||||||
ipDetails != null ? ipDetails.country_code : undefined,
|
|
||||||
x => x.toUpperCase()
|
|
||||||
)
|
)
|
||||||
const currencyCode = currencyMappings[countryCode] || 'USD'
|
return callback(null, 'USD')
|
||||||
logger.log({ ip, currencyCode, ipDetails }, 'got currencyCode for ip')
|
}
|
||||||
return callback(err, currencyCode, countryCode)
|
const countryCode =
|
||||||
})
|
ipDetails && ipDetails.countryCode
|
||||||
},
|
? ipDetails.countryCode.toUpperCase()
|
||||||
|
: undefined
|
||||||
|
const currencyCode = currencyMappings[countryCode] || 'USD'
|
||||||
|
logger.log({ ip, currencyCode, ipDetails }, 'got currencyCode for ip')
|
||||||
|
return callback(err, currencyCode, countryCode)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
function __guard__(value, transform) {
|
module.exports = {
|
||||||
return typeof value !== 'undefined' && value !== null
|
getDetails,
|
||||||
? transform(value)
|
getCurrencyCode,
|
||||||
: undefined
|
promises: {
|
||||||
|
getDetails: promisify(getDetails),
|
||||||
|
getCurrencyCode: promisify(getCurrencyCode),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,6 +61,15 @@ describe('SubscriptionController', function () {
|
||||||
syncSubscription: sinon.stub().yields(),
|
syncSubscription: sinon.stub().yields(),
|
||||||
attemptPaypalInvoiceCollection: sinon.stub().yields(),
|
attemptPaypalInvoiceCollection: sinon.stub().yields(),
|
||||||
startFreeTrial: sinon.stub(),
|
startFreeTrial: sinon.stub(),
|
||||||
|
promises: {
|
||||||
|
createSubscription: sinon.stub().resolves(),
|
||||||
|
updateSubscription: sinon.stub().resolves(),
|
||||||
|
reactivateSubscription: sinon.stub().resolves(),
|
||||||
|
cancelSubscription: sinon.stub().resolves(),
|
||||||
|
syncSubscription: sinon.stub().resolves(),
|
||||||
|
attemptPaypalInvoiceCollection: sinon.stub().resolves(),
|
||||||
|
startFreeTrial: sinon.stub().resolves(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.PlansLocator = { findLocalPlanInSettings: sinon.stub() }
|
this.PlansLocator = { findLocalPlanInSettings: sinon.stub() }
|
||||||
|
@ -69,11 +78,19 @@ describe('SubscriptionController', function () {
|
||||||
hasPaidSubscription: sinon.stub(),
|
hasPaidSubscription: sinon.stub(),
|
||||||
userHasV1OrV2Subscription: sinon.stub(),
|
userHasV1OrV2Subscription: sinon.stub(),
|
||||||
userHasV2Subscription: sinon.stub(),
|
userHasV2Subscription: sinon.stub(),
|
||||||
|
promises: {
|
||||||
|
hasPaidSubscription: sinon.stub().resolves(),
|
||||||
|
userHasV1OrV2Subscription: sinon.stub().resolves(),
|
||||||
|
userHasV2Subscription: sinon.stub().resolves(),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
this.SubscriptionViewModelBuilder = {
|
this.SubscriptionViewModelBuilder = {
|
||||||
buildUsersSubscriptionViewModel: sinon.stub().callsArgWith(1, null, {}),
|
buildUsersSubscriptionViewModel: sinon.stub().callsArgWith(1, null, {}),
|
||||||
buildPlansList: sinon.stub(),
|
buildPlansList: sinon.stub(),
|
||||||
|
promises: {
|
||||||
|
buildUsersSubscriptionViewModel: sinon.stub().resolves({}),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
this.settings = {
|
this.settings = {
|
||||||
coupon_codes: {
|
coupon_codes: {
|
||||||
|
@ -90,9 +107,17 @@ describe('SubscriptionController', function () {
|
||||||
siteUrl: 'http://de.sharelatex.dev:3000',
|
siteUrl: 'http://de.sharelatex.dev:3000',
|
||||||
gaExperiments: {},
|
gaExperiments: {},
|
||||||
}
|
}
|
||||||
this.GeoIpLookup = { getCurrencyCode: sinon.stub() }
|
this.GeoIpLookup = {
|
||||||
|
getCurrencyCode: sinon.stub(),
|
||||||
|
promises: {
|
||||||
|
getCurrencyCode: sinon.stub(),
|
||||||
|
},
|
||||||
|
}
|
||||||
this.UserGetter = {
|
this.UserGetter = {
|
||||||
getUser: sinon.stub().callsArgWith(2, null, this.user),
|
getUser: sinon.stub().callsArgWith(2, null, this.user),
|
||||||
|
promises: {
|
||||||
|
getUser: sinon.stub().resolves(this.user),
|
||||||
|
},
|
||||||
}
|
}
|
||||||
this.SubscriptionController = SandboxedModule.require(modulePath, {
|
this.SubscriptionController = SandboxedModule.require(modulePath, {
|
||||||
requires: {
|
requires: {
|
||||||
|
@ -135,75 +160,26 @@ describe('SubscriptionController', function () {
|
||||||
describe('plansPage', function () {
|
describe('plansPage', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.req.ip = '1234.3123.3131.333 313.133.445.666 653.5345.5345.534'
|
this.req.ip = '1234.3123.3131.333 313.133.445.666 653.5345.5345.534'
|
||||||
return this.GeoIpLookup.getCurrencyCode.callsArgWith(
|
return this.GeoIpLookup.promises.getCurrencyCode.resolves(
|
||||||
1,
|
|
||||||
null,
|
|
||||||
this.stubbedCurrencyCode
|
this.stubbedCurrencyCode
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('when user is logged in', function (done) {
|
|
||||||
beforeEach(function (done) {
|
|
||||||
this.res.callback = done
|
|
||||||
return this.SubscriptionController.plansPage(this.req, this.res)
|
|
||||||
})
|
|
||||||
it('should fetch the current user', function (done) {
|
|
||||||
this.UserGetter.getUser.callCount.should.equal(1)
|
|
||||||
return done()
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('not dependant on logged in state', function (done) {
|
|
||||||
// these could have been put in 'when user is not logged in' too
|
|
||||||
it('should set the recommended currency from the geoiplookup', function (done) {
|
|
||||||
this.res.renderedVariables.recomendedCurrency.should.equal(
|
|
||||||
this.stubbedCurrencyCode
|
|
||||||
)
|
|
||||||
this.GeoIpLookup.getCurrencyCode
|
|
||||||
.calledWith(this.req.ip)
|
|
||||||
.should.equal(true)
|
|
||||||
return done()
|
|
||||||
})
|
|
||||||
it('should include data for features table', function (done) {
|
|
||||||
this.res.renderedVariables.planFeatures.length.should.not.equal(0)
|
|
||||||
return done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('when user is not logged in', function (done) {
|
|
||||||
beforeEach(function (done) {
|
|
||||||
this.res.callback = done
|
|
||||||
this.AuthenticationController.getLoggedInUserId = sinon
|
|
||||||
.stub()
|
|
||||||
.returns(null)
|
|
||||||
return this.SubscriptionController.plansPage(this.req, this.res)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should not fetch the current user', function (done) {
|
|
||||||
this.UserGetter.getUser.callCount.should.equal(0)
|
|
||||||
return done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('paymentPage', function () {
|
describe('paymentPage', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.req.headers = {}
|
this.req.headers = {}
|
||||||
this.SubscriptionHandler.validateNoSubscriptionInRecurly = sinon
|
this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly = sinon
|
||||||
.stub()
|
.stub()
|
||||||
.yields(null, true)
|
.resolves(true)
|
||||||
return this.GeoIpLookup.getCurrencyCode.callsArgWith(
|
return this.GeoIpLookup.promises.getCurrencyCode.resolves({
|
||||||
1,
|
recomendedCurrency: this.stubbedCurrencyCode,
|
||||||
null,
|
})
|
||||||
this.stubbedCurrencyCode
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with a user without a subscription', function () {
|
describe('with a user without a subscription', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.LimitationsManager.userHasV1OrV2Subscription.callsArgWith(
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
|
||||||
1,
|
|
||||||
null,
|
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
return this.PlansLocator.findLocalPlanInSettings.returns({})
|
return this.PlansLocator.findLocalPlanInSettings.returns({})
|
||||||
|
@ -213,9 +189,9 @@ describe('SubscriptionController', function () {
|
||||||
it('should render the new subscription page', function (done) {
|
it('should render the new subscription page', function (done) {
|
||||||
this.res.render = (page, opts) => {
|
this.res.render = (page, opts) => {
|
||||||
page.should.equal('subscriptions/new')
|
page.should.equal('subscriptions/new')
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
return this.SubscriptionController.paymentPage(this.req, this.res)
|
this.SubscriptionController.paymentPage(this.req, this.res)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -223,9 +199,7 @@ describe('SubscriptionController', function () {
|
||||||
describe('with a user with subscription', function () {
|
describe('with a user with subscription', function () {
|
||||||
it('should redirect to the subscription dashboard', function (done) {
|
it('should redirect to the subscription dashboard', function (done) {
|
||||||
this.PlansLocator.findLocalPlanInSettings.returns({})
|
this.PlansLocator.findLocalPlanInSettings.returns({})
|
||||||
this.LimitationsManager.userHasV1OrV2Subscription.callsArgWith(
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
|
||||||
1,
|
|
||||||
null,
|
|
||||||
true
|
true
|
||||||
)
|
)
|
||||||
this.res.redirect = url => {
|
this.res.redirect = url => {
|
||||||
|
@ -238,9 +212,7 @@ describe('SubscriptionController', function () {
|
||||||
|
|
||||||
describe('with an invalid plan code', function () {
|
describe('with an invalid plan code', function () {
|
||||||
it('should return 422 error - Unprocessable Entity', function (done) {
|
it('should return 422 error - Unprocessable Entity', function (done) {
|
||||||
this.LimitationsManager.userHasV1OrV2Subscription.callsArgWith(
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
|
||||||
1,
|
|
||||||
null,
|
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
this.PlansLocator.findLocalPlanInSettings.returns(null)
|
this.PlansLocator.findLocalPlanInSettings.returns(null)
|
||||||
|
@ -252,19 +224,13 @@ describe('SubscriptionController', function () {
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
return this.SubscriptionController.paymentPage(
|
return this.SubscriptionController.paymentPage(this.req, this.res)
|
||||||
this.req,
|
|
||||||
this.res,
|
|
||||||
this.next
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('which currency to use', function () {
|
describe('which currency to use', function () {
|
||||||
beforeEach(function () {
|
beforeEach(function () {
|
||||||
this.LimitationsManager.userHasV1OrV2Subscription.callsArgWith(
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
|
||||||
1,
|
|
||||||
null,
|
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
return this.PlansLocator.findLocalPlanInSettings.returns({})
|
return this.PlansLocator.findLocalPlanInSettings.returns({})
|
||||||
|
@ -293,23 +259,21 @@ describe('SubscriptionController', function () {
|
||||||
this.req.query.currency = null
|
this.req.query.currency = null
|
||||||
this.res.render = (page, opts) => {
|
this.res.render = (page, opts) => {
|
||||||
opts.currency.should.equal(this.stubbedCurrencyCode)
|
opts.currency.should.equal(this.stubbedCurrencyCode)
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
return this.SubscriptionController.paymentPage(this.req, this.res)
|
this.SubscriptionController.paymentPage(this.req, this.res)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('with a recurly subscription already', function () {
|
describe('with a recurly subscription already', function () {
|
||||||
it('should redirect to the subscription dashboard', function (done) {
|
it('should redirect to the subscription dashboard', function (done) {
|
||||||
this.PlansLocator.findLocalPlanInSettings.returns({})
|
this.PlansLocator.findLocalPlanInSettings.returns({})
|
||||||
this.LimitationsManager.userHasV1OrV2Subscription.callsArgWith(
|
this.LimitationsManager.promises.userHasV1OrV2Subscription.resolves(
|
||||||
1,
|
false
|
||||||
null,
|
)
|
||||||
|
this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly.resolves(
|
||||||
false
|
false
|
||||||
)
|
)
|
||||||
this.SubscriptionHandler.validateNoSubscriptionInRecurly = sinon
|
|
||||||
.stub()
|
|
||||||
.yields(null, false)
|
|
||||||
this.res.redirect = url => {
|
this.res.redirect = url => {
|
||||||
url.should.equal('/user/subscription?hasSubscription=true')
|
url.should.equal('/user/subscription?hasSubscription=true')
|
||||||
return done()
|
return done()
|
||||||
|
@ -319,7 +283,7 @@ describe('SubscriptionController', function () {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('successful_subscription', function () {
|
describe('successfulSubscription', function () {
|
||||||
beforeEach(function (done) {
|
beforeEach(function (done) {
|
||||||
this.SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel.callsArgWith(
|
this.SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel.callsArgWith(
|
||||||
1,
|
1,
|
||||||
|
@ -327,7 +291,7 @@ describe('SubscriptionController', function () {
|
||||||
{}
|
{}
|
||||||
)
|
)
|
||||||
this.res.callback = done
|
this.res.callback = done
|
||||||
return this.SubscriptionController.successful_subscription(
|
return this.SubscriptionController.successfulSubscription(
|
||||||
this.req,
|
this.req,
|
||||||
this.res
|
this.res
|
||||||
)
|
)
|
||||||
|
@ -359,12 +323,9 @@ describe('SubscriptionController', function () {
|
||||||
this.res.render = (view, data) => {
|
this.res.render = (view, data) => {
|
||||||
this.data = data
|
this.data = data
|
||||||
expect(view).to.equal('subscriptions/dashboard')
|
expect(view).to.equal('subscriptions/dashboard')
|
||||||
return done()
|
done()
|
||||||
}
|
}
|
||||||
return this.SubscriptionController.userSubscriptionPage(
|
this.SubscriptionController.userSubscriptionPage(this.req, this.res)
|
||||||
this.req,
|
|
||||||
this.res
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
it('should load the personal, groups and v1 subscriptions', function () {
|
it('should load the personal, groups and v1 subscriptions', function () {
|
||||||
|
|
|
@ -1,194 +0,0 @@
|
||||||
/* eslint-disable
|
|
||||||
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
|
|
||||||
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
||||||
*/
|
|
||||||
const SandboxedModule = require('sandboxed-module')
|
|
||||||
const assert = require('assert')
|
|
||||||
const path = require('path')
|
|
||||||
const sinon = require('sinon')
|
|
||||||
const modulePath = path.join(
|
|
||||||
__dirname,
|
|
||||||
'../../../../app/src/infrastructure/GeoIpLookup'
|
|
||||||
)
|
|
||||||
const { expect } = require('chai')
|
|
||||||
|
|
||||||
describe('GeoIpLookup', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
this.settings = {
|
|
||||||
apis: {
|
|
||||||
geoIpLookup: {
|
|
||||||
url: 'http://lookup.com',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
this.request = { get: sinon.stub() }
|
|
||||||
this.GeoIpLookup = SandboxedModule.require(modulePath, {
|
|
||||||
requires: {
|
|
||||||
request: this.request,
|
|
||||||
'settings-sharelatex': this.settings,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
this.ipAddress = '123.456.789.123'
|
|
||||||
|
|
||||||
return (this.stubbedResponse = {
|
|
||||||
ip: this.ipAddress,
|
|
||||||
country_code: 'GB',
|
|
||||||
country_name: 'United Kingdom',
|
|
||||||
region_code: 'H9',
|
|
||||||
region_name: 'London, City of',
|
|
||||||
city: 'London',
|
|
||||||
zipcode: 'SE16',
|
|
||||||
latitude: 51.0,
|
|
||||||
longitude: -0.0493,
|
|
||||||
metro_code: '',
|
|
||||||
area_code: '',
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getDetails', function () {
|
|
||||||
beforeEach(function () {
|
|
||||||
return this.request.get.callsArgWith(1, null, null, this.stubbedResponse)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should request the details using the ip', function (done) {
|
|
||||||
return this.GeoIpLookup.getDetails(this.ipAddress, err => {
|
|
||||||
this.request.get
|
|
||||||
.calledWith({
|
|
||||||
url: this.settings.apis.geoIpLookup.url + '/' + this.ipAddress,
|
|
||||||
timeout: 1000,
|
|
||||||
json: true,
|
|
||||||
})
|
|
||||||
.should.equal(true)
|
|
||||||
return done()
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return the ip details', function (done) {
|
|
||||||
return this.GeoIpLookup.getDetails(
|
|
||||||
this.ipAddress,
|
|
||||||
(err, returnedDetails) => {
|
|
||||||
assert.deepEqual(returnedDetails, this.stubbedResponse)
|
|
||||||
return done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should take the first ip in the string', function (done) {
|
|
||||||
return this.GeoIpLookup.getDetails(
|
|
||||||
` ${this.ipAddress} 456.312.452.102 432.433.888.234`,
|
|
||||||
err => {
|
|
||||||
this.request.get
|
|
||||||
.calledWith({
|
|
||||||
url: this.settings.apis.geoIpLookup.url + '/' + this.ipAddress,
|
|
||||||
timeout: 1000,
|
|
||||||
json: true,
|
|
||||||
})
|
|
||||||
.should.equal(true)
|
|
||||||
return done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('getCurrencyCode', function () {
|
|
||||||
it('should return GBP for GB country', function (done) {
|
|
||||||
this.GeoIpLookup.getDetails = sinon
|
|
||||||
.stub()
|
|
||||||
.callsArgWith(1, null, this.stubbedResponse)
|
|
||||||
return this.GeoIpLookup.getCurrencyCode(
|
|
||||||
this.ipAddress,
|
|
||||||
(err, currencyCode) => {
|
|
||||||
currencyCode.should.equal('GBP')
|
|
||||||
return done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return GBP for gb country', function (done) {
|
|
||||||
this.stubbedResponse.country_code = 'gb'
|
|
||||||
this.GeoIpLookup.getDetails = sinon
|
|
||||||
.stub()
|
|
||||||
.callsArgWith(1, null, this.stubbedResponse)
|
|
||||||
return this.GeoIpLookup.getCurrencyCode(
|
|
||||||
this.ipAddress,
|
|
||||||
(err, currencyCode) => {
|
|
||||||
currencyCode.should.equal('GBP')
|
|
||||||
return done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return USD for US', function (done) {
|
|
||||||
this.stubbedResponse.country_code = 'US'
|
|
||||||
this.GeoIpLookup.getDetails = sinon
|
|
||||||
.stub()
|
|
||||||
.callsArgWith(1, null, this.stubbedResponse)
|
|
||||||
return this.GeoIpLookup.getCurrencyCode(
|
|
||||||
this.ipAddress,
|
|
||||||
(err, currencyCode) => {
|
|
||||||
currencyCode.should.equal('USD')
|
|
||||||
return done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should return EUR for DE', function (done) {
|
|
||||||
this.stubbedResponse.country_code = 'DE'
|
|
||||||
this.GeoIpLookup.getDetails = sinon
|
|
||||||
.stub()
|
|
||||||
.callsArgWith(1, null, this.stubbedResponse)
|
|
||||||
return this.GeoIpLookup.getCurrencyCode(
|
|
||||||
this.ipAddress,
|
|
||||||
(err, currencyCode) => {
|
|
||||||
currencyCode.should.equal('EUR')
|
|
||||||
return done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should default to USD if there is an error', function (done) {
|
|
||||||
this.GeoIpLookup.getDetails = sinon.stub().callsArgWith(1, 'problem')
|
|
||||||
return this.GeoIpLookup.getCurrencyCode(
|
|
||||||
this.ipAddress,
|
|
||||||
(err, currencyCode) => {
|
|
||||||
currencyCode.should.equal('USD')
|
|
||||||
return done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should default to USD if there are no details', function (done) {
|
|
||||||
this.GeoIpLookup.getDetails = sinon.stub().callsArgWith(1)
|
|
||||||
return this.GeoIpLookup.getCurrencyCode(
|
|
||||||
this.ipAddress,
|
|
||||||
(err, currencyCode) => {
|
|
||||||
currencyCode.should.equal('USD')
|
|
||||||
return done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
|
|
||||||
it('should default to USD if there is no match for their country', function (done) {
|
|
||||||
this.stubbedResponse.country_code = 'Non existant'
|
|
||||||
this.GeoIpLookup.getDetails = sinon
|
|
||||||
.stub()
|
|
||||||
.callsArgWith(1, null, this.stubbedResponse)
|
|
||||||
return this.GeoIpLookup.getCurrencyCode(
|
|
||||||
this.ipAddress,
|
|
||||||
(err, currencyCode) => {
|
|
||||||
currencyCode.should.equal('USD')
|
|
||||||
return done()
|
|
||||||
}
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
})
|
|
Loading…
Reference in a new issue