2019-05-29 05:21:06 -04:00
|
|
|
/* eslint-disable
|
|
|
|
camelcase,
|
|
|
|
handle-callback-err,
|
|
|
|
max-len,
|
|
|
|
no-unused-vars,
|
|
|
|
standard/no-callback-literal,
|
|
|
|
*/
|
|
|
|
// 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
|
|
|
|
* DS207: Consider shorter variations of null checks
|
|
|
|
* Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
|
|
|
|
*/
|
|
|
|
const async = require('async')
|
|
|
|
const RecurlyWrapper = require('./RecurlyWrapper')
|
|
|
|
const Settings = require('settings-sharelatex')
|
|
|
|
const { User } = require('../../models/User')
|
2019-08-28 08:59:41 -04:00
|
|
|
const { promisifyAll } = require('../../util/promises')
|
2019-05-29 05:21:06 -04:00
|
|
|
const logger = require('logger-sharelatex')
|
|
|
|
const SubscriptionUpdater = require('./SubscriptionUpdater')
|
|
|
|
const LimitationsManager = require('./LimitationsManager')
|
|
|
|
const EmailHandler = require('../Email/EmailHandler')
|
|
|
|
const Events = require('../../infrastructure/Events')
|
|
|
|
const Analytics = require('../Analytics/AnalyticsManager')
|
|
|
|
|
2019-08-28 08:59:41 -04:00
|
|
|
const SubscriptionHandler = {
|
2019-05-29 05:21:06 -04:00
|
|
|
validateNoSubscriptionInRecurly(user_id, callback) {
|
|
|
|
if (callback == null) {
|
|
|
|
callback = function(error, valid) {}
|
|
|
|
}
|
|
|
|
return RecurlyWrapper.listAccountActiveSubscriptions(user_id, function(
|
|
|
|
error,
|
|
|
|
subscriptions
|
|
|
|
) {
|
|
|
|
if (subscriptions == null) {
|
|
|
|
subscriptions = []
|
|
|
|
}
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (subscriptions.length > 0) {
|
|
|
|
return SubscriptionUpdater.syncSubscription(
|
|
|
|
subscriptions[0],
|
|
|
|
user_id,
|
|
|
|
function(error) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
return callback(null, false)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
return callback(null, true)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2019-08-22 07:57:50 -04:00
|
|
|
createSubscription(user, subscriptionDetails, recurlyTokenIds, callback) {
|
2019-05-29 05:21:06 -04:00
|
|
|
const clientTokenId = ''
|
2019-08-28 08:59:41 -04:00
|
|
|
return SubscriptionHandler.validateNoSubscriptionInRecurly(
|
|
|
|
user._id,
|
|
|
|
function(error, valid) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2019-08-28 08:59:41 -04:00
|
|
|
if (!valid) {
|
|
|
|
return callback(new Error('user already has subscription in recurly'))
|
|
|
|
}
|
|
|
|
return RecurlyWrapper.createSubscription(
|
|
|
|
user,
|
|
|
|
subscriptionDetails,
|
|
|
|
recurlyTokenIds,
|
|
|
|
function(error, recurlySubscription) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
return SubscriptionUpdater.syncSubscription(
|
|
|
|
recurlySubscription,
|
|
|
|
user._id,
|
|
|
|
function(error) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
},
|
|
|
|
|
|
|
|
updateSubscription(user, plan_code, coupon_code, callback) {
|
|
|
|
return LimitationsManager.userHasV2Subscription(user, function(
|
|
|
|
err,
|
|
|
|
hasSubscription,
|
|
|
|
subscription
|
|
|
|
) {
|
|
|
|
if (!hasSubscription) {
|
|
|
|
return callback()
|
|
|
|
} else {
|
|
|
|
return async.series(
|
|
|
|
[
|
|
|
|
function(cb) {
|
|
|
|
if (coupon_code == null) {
|
|
|
|
return cb()
|
|
|
|
}
|
|
|
|
return RecurlyWrapper.getSubscription(
|
|
|
|
subscription.recurlySubscription_id,
|
|
|
|
{ includeAccount: true },
|
|
|
|
function(err, usersSubscription) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
|
|
|
const { account_code } = usersSubscription.account
|
|
|
|
return RecurlyWrapper.redeemCoupon(
|
|
|
|
account_code,
|
|
|
|
coupon_code,
|
|
|
|
cb
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
cb =>
|
|
|
|
RecurlyWrapper.updateSubscription(
|
|
|
|
subscription.recurlySubscription_id,
|
|
|
|
{ plan_code, timeframe: 'now' },
|
|
|
|
function(error, recurlySubscription) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
return SubscriptionUpdater.syncSubscription(
|
|
|
|
recurlySubscription,
|
|
|
|
user._id,
|
|
|
|
cb
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
],
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
cancelSubscription(user, callback) {
|
|
|
|
return LimitationsManager.userHasV2Subscription(user, function(
|
|
|
|
err,
|
|
|
|
hasSubscription,
|
|
|
|
subscription
|
|
|
|
) {
|
|
|
|
if (hasSubscription) {
|
|
|
|
return RecurlyWrapper.cancelSubscription(
|
|
|
|
subscription.recurlySubscription_id,
|
|
|
|
function(error) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
const emailOpts = {
|
|
|
|
to: user.email,
|
|
|
|
first_name: user.first_name
|
|
|
|
}
|
|
|
|
const ONE_HOUR_IN_MS = 1000 * 60 * 60
|
|
|
|
setTimeout(
|
2019-10-15 09:12:11 -04:00
|
|
|
() =>
|
|
|
|
EmailHandler.sendEmail(
|
|
|
|
'canceledSubscription',
|
|
|
|
emailOpts,
|
|
|
|
err => {
|
|
|
|
if (err != null) {
|
|
|
|
logger.warn(
|
|
|
|
{ err },
|
|
|
|
'failed to send confirmation email for subscription cancellation'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
),
|
2019-05-29 05:21:06 -04:00
|
|
|
ONE_HOUR_IN_MS
|
|
|
|
)
|
|
|
|
Events.emit('cancelSubscription', user._id)
|
|
|
|
Analytics.recordEvent(user._id, 'subscription-canceled')
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
reactivateSubscription(user, callback) {
|
|
|
|
return LimitationsManager.userHasV2Subscription(user, function(
|
|
|
|
err,
|
|
|
|
hasSubscription,
|
|
|
|
subscription
|
|
|
|
) {
|
|
|
|
if (hasSubscription) {
|
|
|
|
return RecurlyWrapper.reactivateSubscription(
|
|
|
|
subscription.recurlySubscription_id,
|
|
|
|
function(error) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
2019-10-15 09:12:11 -04:00
|
|
|
EmailHandler.sendEmail(
|
|
|
|
'reactivatedSubscription',
|
|
|
|
{ to: user.email },
|
|
|
|
err => {
|
|
|
|
if (err != null) {
|
|
|
|
logger.warn(
|
|
|
|
{ err },
|
|
|
|
'failed to send reactivation confirmation email'
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
Analytics.recordEvent(user._id, 'subscription-reactivated')
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2019-11-12 03:56:08 -05:00
|
|
|
syncSubscription(recurlySubscription, requesterData, callback) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return RecurlyWrapper.getSubscription(
|
|
|
|
recurlySubscription.uuid,
|
|
|
|
{ includeAccount: true },
|
|
|
|
function(error, recurlySubscription) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
return User.findById(recurlySubscription.account.account_code, function(
|
|
|
|
error,
|
|
|
|
user
|
|
|
|
) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (user == null) {
|
2019-07-01 09:54:23 -04:00
|
|
|
return callback(new Error('no user found'))
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
return SubscriptionUpdater.syncSubscription(
|
|
|
|
recurlySubscription,
|
|
|
|
user != null ? user._id : undefined,
|
2019-09-09 07:51:34 -04:00
|
|
|
requesterData,
|
2019-05-29 05:21:06 -04:00
|
|
|
callback
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
2019-11-12 03:56:08 -05:00
|
|
|
// attempt to collect past due invoice for customer. Only do that when a) the
|
|
|
|
// customer is using Paypal and b) there is only one past due invoice.
|
|
|
|
// This is used because Recurly doesn't always attempt collection of paast due
|
|
|
|
// invoices after Paypal billing info were updated.
|
|
|
|
attemptPaypalInvoiceCollection(recurlyAccountCode, callback) {
|
|
|
|
RecurlyWrapper.getBillingInfo(recurlyAccountCode, (error, billingInfo) => {
|
|
|
|
if (error) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (!billingInfo.paypal_billing_agreement_id) {
|
|
|
|
// this is not a Paypal user
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
RecurlyWrapper.getAccountPastDueInvoices(
|
|
|
|
recurlyAccountCode,
|
|
|
|
(error, pastDueInvoices) => {
|
|
|
|
if (error) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (pastDueInvoices.length !== 1) {
|
|
|
|
// no past due invoices, or more than one. Ignore.
|
|
|
|
return callback()
|
|
|
|
}
|
|
|
|
RecurlyWrapper.attemptInvoiceCollection(
|
|
|
|
pastDueInvoices[0].invoice_number,
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2019-05-29 05:21:06 -04:00
|
|
|
extendTrial(subscription, daysToExend, callback) {
|
|
|
|
return RecurlyWrapper.extendTrial(
|
|
|
|
subscription.recurlySubscription_id,
|
|
|
|
daysToExend,
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
}
|
|
|
|
}
|
2019-08-28 08:59:41 -04:00
|
|
|
|
|
|
|
SubscriptionHandler.promises = promisifyAll(SubscriptionHandler)
|
|
|
|
module.exports = SubscriptionHandler
|