2019-05-29 05:21:06 -04:00
|
|
|
/* eslint-disable
|
|
|
|
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:
|
|
|
|
* DS101: Remove unnecessary use of Array.from
|
|
|
|
* 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
|
|
|
|
*/
|
|
|
|
const Settings = require('settings-sharelatex')
|
|
|
|
const RecurlyWrapper = require('./RecurlyWrapper')
|
|
|
|
const PlansLocator = require('./PlansLocator')
|
|
|
|
const SubscriptionFormatters = require('./SubscriptionFormatters')
|
|
|
|
const LimitationsManager = require('./LimitationsManager')
|
|
|
|
const SubscriptionLocator = require('./SubscriptionLocator')
|
|
|
|
const V1SubscriptionManager = require('./V1SubscriptionManager')
|
|
|
|
const InstitutionsGetter = require('../Institutions/InstitutionsGetter')
|
|
|
|
const PublishersGetter = require('../Publishers/PublishersGetter')
|
|
|
|
const sanitizeHtml = require('sanitize-html')
|
|
|
|
const logger = require('logger-sharelatex')
|
|
|
|
const _ = require('underscore')
|
|
|
|
const async = require('async')
|
|
|
|
|
2019-08-20 08:44:59 -04:00
|
|
|
const buildHostedLink = function(recurlySubscription, type) {
|
2019-07-15 09:09:04 -04:00
|
|
|
const recurlySubdomain = Settings.apis.recurly.subdomain
|
2019-08-20 08:44:59 -04:00
|
|
|
const hostedLoginToken = recurlySubscription.account.hosted_login_token
|
|
|
|
let path = ''
|
|
|
|
if (type === 'billingDetails') {
|
|
|
|
path = 'billing_info/edit?ht='
|
|
|
|
}
|
|
|
|
if (hostedLoginToken && recurlySubdomain) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return [
|
|
|
|
'https://',
|
|
|
|
recurlySubdomain,
|
2019-08-20 08:44:59 -04:00
|
|
|
'.recurly.com/account/',
|
|
|
|
path,
|
2019-05-29 05:21:06 -04:00
|
|
|
hostedLoginToken
|
|
|
|
].join('')
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
module.exports = {
|
|
|
|
buildUsersSubscriptionViewModel(user, callback) {
|
|
|
|
if (callback == null) {
|
|
|
|
callback = function(error, data) {}
|
|
|
|
}
|
|
|
|
return async.auto(
|
|
|
|
{
|
|
|
|
personalSubscription(cb) {
|
|
|
|
return SubscriptionLocator.getUsersSubscription(user, cb)
|
|
|
|
},
|
|
|
|
recurlySubscription: [
|
|
|
|
'personalSubscription',
|
|
|
|
function(cb, { personalSubscription }) {
|
|
|
|
if (
|
|
|
|
(personalSubscription != null
|
|
|
|
? personalSubscription.recurlySubscription_id
|
|
|
|
: undefined) == null ||
|
|
|
|
(personalSubscription != null
|
|
|
|
? personalSubscription.recurlySubscription_id
|
|
|
|
: undefined) === ''
|
|
|
|
) {
|
|
|
|
return cb(null, null)
|
|
|
|
}
|
|
|
|
return RecurlyWrapper.getSubscription(
|
|
|
|
personalSubscription.recurlySubscription_id,
|
|
|
|
{ includeAccount: true },
|
|
|
|
cb
|
|
|
|
)
|
|
|
|
}
|
|
|
|
],
|
|
|
|
recurlyCoupons: [
|
|
|
|
'recurlySubscription',
|
|
|
|
function(cb, { recurlySubscription }) {
|
|
|
|
if (!recurlySubscription) {
|
|
|
|
return cb(null, null)
|
|
|
|
}
|
|
|
|
const accountId = recurlySubscription.account.account_code
|
|
|
|
return RecurlyWrapper.getAccountActiveCoupons(accountId, cb)
|
|
|
|
}
|
|
|
|
],
|
|
|
|
plan: [
|
|
|
|
'personalSubscription',
|
|
|
|
function(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
|
|
|
|
}'`
|
|
|
|
)
|
|
|
|
)
|
|
|
|
}
|
|
|
|
return cb(null, plan)
|
|
|
|
}
|
|
|
|
],
|
|
|
|
memberGroupSubscriptions(cb) {
|
|
|
|
return SubscriptionLocator.getMemberSubscriptions(user, cb)
|
|
|
|
},
|
|
|
|
managedGroupSubscriptions(cb) {
|
|
|
|
return SubscriptionLocator.getManagedGroupSubscriptions(user, cb)
|
|
|
|
},
|
|
|
|
confirmedMemberInstitutions(cb) {
|
|
|
|
return InstitutionsGetter.getConfirmedInstitutions(user._id, cb)
|
|
|
|
},
|
|
|
|
managedInstitutions(cb) {
|
|
|
|
return InstitutionsGetter.getManagedInstitutions(user._id, cb)
|
|
|
|
},
|
|
|
|
managedPublishers(cb) {
|
|
|
|
return PublishersGetter.getManagedPublishers(user._id, cb)
|
|
|
|
},
|
|
|
|
v1SubscriptionStatus(cb) {
|
|
|
|
return V1SubscriptionManager.getSubscriptionStatusFromV1(
|
|
|
|
user._id,
|
|
|
|
function(error, status, v1Id) {
|
|
|
|
if (error != null) {
|
|
|
|
return cb(error)
|
|
|
|
}
|
|
|
|
return cb(null, status)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
},
|
|
|
|
function(err, results) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
let {
|
|
|
|
personalSubscription,
|
|
|
|
memberGroupSubscriptions,
|
|
|
|
managedGroupSubscriptions,
|
|
|
|
confirmedMemberInstitutions,
|
|
|
|
managedInstitutions,
|
|
|
|
managedPublishers,
|
|
|
|
v1SubscriptionStatus,
|
|
|
|
recurlySubscription,
|
|
|
|
recurlyCoupons,
|
|
|
|
plan
|
|
|
|
} = results
|
|
|
|
if (memberGroupSubscriptions == null) {
|
|
|
|
memberGroupSubscriptions = []
|
|
|
|
}
|
|
|
|
if (managedGroupSubscriptions == null) {
|
|
|
|
managedGroupSubscriptions = []
|
|
|
|
}
|
|
|
|
if (confirmedMemberInstitutions == null) {
|
|
|
|
confirmedMemberInstitutions = []
|
|
|
|
}
|
|
|
|
if (managedInstitutions == null) {
|
|
|
|
managedInstitutions = []
|
|
|
|
}
|
|
|
|
if (v1SubscriptionStatus == null) {
|
|
|
|
v1SubscriptionStatus = {}
|
|
|
|
}
|
|
|
|
if (recurlyCoupons == null) {
|
|
|
|
recurlyCoupons = []
|
|
|
|
}
|
|
|
|
|
|
|
|
if (
|
|
|
|
(personalSubscription != null
|
|
|
|
? personalSubscription.toObject
|
|
|
|
: undefined) != null
|
|
|
|
) {
|
|
|
|
// Downgrade from Mongoose object, so we can add a recurly and plan attribute
|
|
|
|
personalSubscription = personalSubscription.toObject()
|
|
|
|
}
|
|
|
|
|
|
|
|
if (plan != null) {
|
|
|
|
personalSubscription.plan = plan
|
|
|
|
}
|
|
|
|
|
|
|
|
if (personalSubscription != null && recurlySubscription != null) {
|
|
|
|
const tax =
|
|
|
|
(recurlySubscription != null
|
|
|
|
? recurlySubscription.tax_in_cents
|
|
|
|
: undefined) || 0
|
|
|
|
personalSubscription.recurly = {
|
|
|
|
tax,
|
|
|
|
taxRate: parseFloat(
|
|
|
|
__guard__(
|
|
|
|
recurlySubscription != null
|
|
|
|
? recurlySubscription.tax_rate
|
|
|
|
: undefined,
|
|
|
|
x => x._
|
|
|
|
)
|
|
|
|
),
|
2019-08-20 08:44:59 -04:00
|
|
|
billingDetailsLink: buildHostedLink(
|
|
|
|
recurlySubscription,
|
|
|
|
'billingDetails'
|
|
|
|
),
|
|
|
|
accountManagementLink: buildHostedLink(recurlySubscription),
|
2019-05-29 05:21:06 -04:00
|
|
|
price: SubscriptionFormatters.formatPrice(
|
|
|
|
(recurlySubscription != null
|
|
|
|
? recurlySubscription.unit_amount_in_cents
|
|
|
|
: undefined) + tax,
|
|
|
|
recurlySubscription != null
|
|
|
|
? recurlySubscription.currency
|
|
|
|
: undefined
|
|
|
|
),
|
|
|
|
nextPaymentDueAt: SubscriptionFormatters.formatDate(
|
|
|
|
recurlySubscription != null
|
|
|
|
? recurlySubscription.current_period_ends_at
|
|
|
|
: undefined
|
|
|
|
),
|
|
|
|
currency: recurlySubscription.currency,
|
|
|
|
state: recurlySubscription.state,
|
|
|
|
trialEndsAtFormatted: SubscriptionFormatters.formatDate(
|
|
|
|
recurlySubscription != null
|
|
|
|
? recurlySubscription.trial_ends_at
|
|
|
|
: undefined
|
|
|
|
),
|
|
|
|
trial_ends_at: recurlySubscription.trial_ends_at,
|
|
|
|
activeCoupons: recurlyCoupons
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
for (let memberGroupSubscription of Array.from(
|
|
|
|
memberGroupSubscriptions
|
|
|
|
)) {
|
|
|
|
if (memberGroupSubscription.teamNotice) {
|
|
|
|
memberGroupSubscription.teamNotice = sanitizeHtml(
|
|
|
|
memberGroupSubscription.teamNotice
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return callback(null, {
|
|
|
|
personalSubscription,
|
|
|
|
managedGroupSubscriptions,
|
|
|
|
memberGroupSubscriptions,
|
|
|
|
confirmedMemberInstitutions,
|
|
|
|
managedInstitutions,
|
|
|
|
managedPublishers,
|
|
|
|
v1SubscriptionStatus
|
|
|
|
})
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2020-02-05 11:34:29 -05:00
|
|
|
buildPlansList() {
|
2019-05-29 05:21:06 -04:00
|
|
|
const { plans } = Settings
|
|
|
|
|
|
|
|
const allPlans = {}
|
|
|
|
plans.forEach(plan => (allPlans[plan.planCode] = plan))
|
|
|
|
|
|
|
|
const result = { allPlans }
|
|
|
|
|
|
|
|
result.studentAccounts = _.filter(
|
|
|
|
plans,
|
|
|
|
plan => plan.planCode.indexOf('student') !== -1
|
|
|
|
)
|
|
|
|
|
|
|
|
result.groupMonthlyPlans = _.filter(
|
|
|
|
plans,
|
|
|
|
plan => plan.groupPlan && !plan.annual
|
|
|
|
)
|
|
|
|
|
|
|
|
result.groupAnnualPlans = _.filter(
|
|
|
|
plans,
|
|
|
|
plan => plan.groupPlan && plan.annual
|
|
|
|
)
|
|
|
|
|
|
|
|
result.individualMonthlyPlans = _.filter(
|
|
|
|
plans,
|
|
|
|
plan =>
|
|
|
|
!plan.groupPlan &&
|
|
|
|
!plan.annual &&
|
2020-01-27 08:54:07 -05:00
|
|
|
plan.planCode !== 'personal' && // Prevent the personal plan from appearing on the change-plans page
|
2019-05-29 05:21:06 -04:00
|
|
|
plan.planCode.indexOf('student') === -1
|
|
|
|
)
|
|
|
|
|
|
|
|
result.individualAnnualPlans = _.filter(
|
|
|
|
plans,
|
|
|
|
plan =>
|
|
|
|
!plan.groupPlan &&
|
|
|
|
plan.annual &&
|
|
|
|
plan.planCode.indexOf('student') === -1
|
|
|
|
)
|
|
|
|
|
|
|
|
return result
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
function __guard__(value, transform) {
|
|
|
|
return typeof value !== 'undefined' && value !== null
|
|
|
|
? transform(value)
|
|
|
|
: undefined
|
|
|
|
}
|