2019-05-29 05:21:06 -04:00
|
|
|
const async = require('async')
|
|
|
|
const RecurlyWrapper = require('./RecurlyWrapper')
|
|
|
|
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 = {
|
2020-07-16 02:48:20 -04:00
|
|
|
validateNoSubscriptionInRecurly(userId, callback) {
|
2019-05-29 05:21:06 -04:00
|
|
|
if (callback == null) {
|
2020-07-16 02:48:20 -04:00
|
|
|
callback = function() {}
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2020-07-16 02:48:20 -04:00
|
|
|
RecurlyWrapper.listAccountActiveSubscriptions(userId, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
error,
|
|
|
|
subscriptions
|
|
|
|
) {
|
|
|
|
if (subscriptions == null) {
|
|
|
|
subscriptions = []
|
|
|
|
}
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (subscriptions.length > 0) {
|
2020-07-16 02:48:20 -04:00
|
|
|
SubscriptionUpdater.syncSubscription(subscriptions[0], userId, function(
|
|
|
|
error
|
|
|
|
) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2020-07-16 02:48:20 -04:00
|
|
|
callback(null, false)
|
|
|
|
})
|
2019-05-29 05:21:06 -04:00
|
|
|
} else {
|
2020-07-16 02:48:20 -04:00
|
|
|
callback(null, true)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2019-08-22 07:57:50 -04:00
|
|
|
createSubscription(user, subscriptionDetails, recurlyTokenIds, callback) {
|
2020-07-16 02:48:20 -04:00
|
|
|
SubscriptionHandler.validateNoSubscriptionInRecurly(user._id, function(
|
|
|
|
error,
|
|
|
|
valid
|
|
|
|
) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
2019-08-28 08:59:41 -04:00
|
|
|
}
|
2020-07-16 02:48:20 -04:00
|
|
|
if (!valid) {
|
|
|
|
return callback(new Error('user already has subscription in recurly'))
|
|
|
|
}
|
|
|
|
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
|
|
|
},
|
|
|
|
|
2020-07-16 02:48:20 -04:00
|
|
|
updateSubscription(user, planCode, couponCode, callback) {
|
|
|
|
LimitationsManager.userHasV2Subscription(user, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
err,
|
|
|
|
hasSubscription,
|
|
|
|
subscription
|
|
|
|
) {
|
2020-07-16 02:48:20 -04:00
|
|
|
if (err) {
|
|
|
|
logger.warn(
|
|
|
|
{ err, user_id: user._id, hasSubscription },
|
|
|
|
'there was an error checking user v2 subscription'
|
|
|
|
)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
if (!hasSubscription) {
|
|
|
|
return callback()
|
|
|
|
} else {
|
|
|
|
return async.series(
|
|
|
|
[
|
|
|
|
function(cb) {
|
2020-07-16 02:48:20 -04:00
|
|
|
if (couponCode == null) {
|
2019-05-29 05:21:06 -04:00
|
|
|
return cb()
|
|
|
|
}
|
2020-07-16 02:48:20 -04:00
|
|
|
RecurlyWrapper.getSubscription(
|
2019-05-29 05:21:06 -04:00
|
|
|
subscription.recurlySubscription_id,
|
|
|
|
{ includeAccount: true },
|
|
|
|
function(err, usersSubscription) {
|
|
|
|
if (err != null) {
|
|
|
|
return callback(err)
|
|
|
|
}
|
2020-07-16 02:48:20 -04:00
|
|
|
RecurlyWrapper.redeemCoupon(
|
|
|
|
usersSubscription.account.account_code,
|
|
|
|
couponCode,
|
2019-05-29 05:21:06 -04:00
|
|
|
cb
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
cb =>
|
|
|
|
RecurlyWrapper.updateSubscription(
|
|
|
|
subscription.recurlySubscription_id,
|
2020-07-16 02:48:20 -04:00
|
|
|
{ plan_code: planCode, timeframe: 'now' },
|
2019-05-29 05:21:06 -04:00
|
|
|
function(error, recurlySubscription) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
2020-07-16 02:48:20 -04:00
|
|
|
SubscriptionUpdater.syncSubscription(
|
2019-05-29 05:21:06 -04:00
|
|
|
recurlySubscription,
|
|
|
|
user._id,
|
|
|
|
cb
|
|
|
|
)
|
|
|
|
}
|
|
|
|
)
|
|
|
|
],
|
|
|
|
callback
|
|
|
|
)
|
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
cancelSubscription(user, callback) {
|
2020-07-16 02:48:20 -04:00
|
|
|
LimitationsManager.userHasV2Subscription(user, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
err,
|
|
|
|
hasSubscription,
|
|
|
|
subscription
|
|
|
|
) {
|
2020-07-16 02:48:20 -04:00
|
|
|
if (err) {
|
|
|
|
logger.warn(
|
|
|
|
{ err, user_id: user._id, hasSubscription },
|
|
|
|
'there was an error checking user v2 subscription'
|
|
|
|
)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
if (hasSubscription) {
|
2020-07-16 02:48:20 -04:00
|
|
|
RecurlyWrapper.cancelSubscription(
|
2019-05-29 05:21:06 -04:00
|
|
|
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')
|
2020-07-16 02:48:20 -04:00
|
|
|
callback()
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
} else {
|
2020-07-16 02:48:20 -04:00
|
|
|
callback()
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
|
|
|
reactivateSubscription(user, callback) {
|
2020-07-16 02:48:20 -04:00
|
|
|
LimitationsManager.userHasV2Subscription(user, function(
|
2019-05-29 05:21:06 -04:00
|
|
|
err,
|
|
|
|
hasSubscription,
|
|
|
|
subscription
|
|
|
|
) {
|
2020-07-16 02:48:20 -04:00
|
|
|
if (err) {
|
|
|
|
logger.warn(
|
|
|
|
{ err, user_id: user._id, hasSubscription },
|
|
|
|
'there was an error checking user v2 subscription'
|
|
|
|
)
|
|
|
|
}
|
2019-05-29 05:21:06 -04:00
|
|
|
if (hasSubscription) {
|
2020-07-16 02:48:20 -04:00
|
|
|
RecurlyWrapper.reactivateSubscription(
|
2019-05-29 05:21:06 -04:00
|
|
|
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')
|
2020-07-16 02:48:20 -04:00
|
|
|
callback()
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
} else {
|
2020-07-16 02:48:20 -04:00
|
|
|
callback()
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
})
|
|
|
|
},
|
|
|
|
|
2019-11-12 03:56:08 -05:00
|
|
|
syncSubscription(recurlySubscription, requesterData, callback) {
|
2020-07-16 02:48:20 -04:00
|
|
|
RecurlyWrapper.getSubscription(
|
2019-05-29 05:21:06 -04:00
|
|
|
recurlySubscription.uuid,
|
|
|
|
{ includeAccount: true },
|
|
|
|
function(error, recurlySubscription) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
2020-07-16 02:48:20 -04:00
|
|
|
User.findById(
|
|
|
|
recurlySubscription.account.account_code,
|
|
|
|
{ _id: 1 },
|
|
|
|
function(error, user) {
|
|
|
|
if (error != null) {
|
|
|
|
return callback(error)
|
|
|
|
}
|
|
|
|
if (user == null) {
|
|
|
|
return callback(new Error('no user found'))
|
|
|
|
}
|
|
|
|
SubscriptionUpdater.syncSubscription(
|
|
|
|
recurlySubscription,
|
|
|
|
user != null ? user._id : undefined,
|
|
|
|
requesterData,
|
|
|
|
callback
|
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
2020-07-16 02:48:20 -04:00
|
|
|
)
|
2019-05-29 05:21:06 -04:00
|
|
|
}
|
|
|
|
)
|
|
|
|
},
|
|
|
|
|
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
|