| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361 |
- const async = require('async')
- const { promisify } = require('util')
- const RecurlyWrapper = require('./RecurlyWrapper')
- const RecurlyClient = require('./RecurlyClient')
- const { User } = require('../../models/User')
- const logger = require('@overleaf/logger')
- const SubscriptionUpdater = require('./SubscriptionUpdater')
- const LimitationsManager = require('./LimitationsManager')
- const EmailHandler = require('../Email/EmailHandler')
- const PlansLocator = require('./PlansLocator')
- const SubscriptionHelper = require('./SubscriptionHelper')
- function validateNoSubscriptionInRecurly(userId, callback) {
- if (callback == null) {
- callback = function () {}
- }
- RecurlyWrapper.listAccountActiveSubscriptions(
- userId,
- function (error, subscriptions) {
- if (subscriptions == null) {
- subscriptions = []
- }
- if (error != null) {
- return callback(error)
- }
- if (subscriptions.length > 0) {
- SubscriptionUpdater.syncSubscription(
- subscriptions[0],
- userId,
- function (error) {
- if (error != null) {
- return callback(error)
- }
- callback(null, false)
- }
- )
- } else {
- callback(null, true)
- }
- }
- )
- }
- function createSubscription(
- user,
- subscriptionDetails,
- recurlyTokenIds,
- callback
- ) {
- validateNoSubscriptionInRecurly(user._id, function (error, valid) {
- if (error != null) {
- return callback(error)
- }
- 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()
- }
- )
- }
- )
- })
- }
- function updateSubscription(user, planCode, couponCode, callback) {
- LimitationsManager.userHasV2Subscription(
- user,
- function (err, hasSubscription, subscription) {
- if (err) {
- logger.warn(
- { err, user_id: user._id, hasSubscription },
- 'there was an error checking user v2 subscription'
- )
- }
- if (!hasSubscription) {
- return callback()
- } else {
- return async.series(
- [
- function (cb) {
- if (couponCode == null) {
- return cb()
- }
- RecurlyWrapper.getSubscription(
- subscription.recurlySubscription_id,
- { includeAccount: true },
- function (err, usersSubscription) {
- if (err != null) {
- return cb(err)
- }
- RecurlyWrapper.redeemCoupon(
- usersSubscription.account.account_code,
- couponCode,
- cb
- )
- }
- )
- },
- function (cb) {
- let changeAtTermEnd
- const currentPlan = PlansLocator.findLocalPlanInSettings(
- subscription.planCode
- )
- const newPlan = PlansLocator.findLocalPlanInSettings(planCode)
- if (currentPlan && newPlan) {
- changeAtTermEnd = SubscriptionHelper.shouldPlanChangeAtTermEnd(
- currentPlan,
- newPlan
- )
- } else {
- logger.error(
- { currentPlan: subscription.planCode, newPlan: planCode },
- 'unable to locate both plans in settings'
- )
- return cb(new Error('unable to locate both plans in settings'))
- }
- const timeframe = changeAtTermEnd ? 'term_end' : 'now'
- RecurlyClient.changeSubscriptionByUuid(
- subscription.recurlySubscription_id,
- { planCode: planCode, timeframe: timeframe },
- function (error, subscriptionChange) {
- if (error != null) {
- return cb(error)
- }
- // v2 recurly API wants a UUID, but UUID isn't included in the subscription change response
- // we got the UUID from the DB using userHasV2Subscription() - it is the only property
- // we need to be able to build a 'recurlySubscription' object for syncSubscription()
- syncSubscription(
- { uuid: subscription.recurlySubscription_id },
- user._id,
- cb
- )
- }
- )
- },
- ],
- callback
- )
- }
- }
- )
- }
- function cancelPendingSubscriptionChange(user, callback) {
- LimitationsManager.userHasV2Subscription(
- user,
- function (err, hasSubscription, subscription) {
- if (err) {
- return callback(err)
- }
- if (hasSubscription) {
- RecurlyClient.removeSubscriptionChangeByUuid(
- subscription.recurlySubscription_id,
- function (error) {
- if (error != null) {
- return callback(error)
- }
- callback()
- }
- )
- } else {
- callback()
- }
- }
- )
- }
- function cancelSubscription(user, callback) {
- LimitationsManager.userHasV2Subscription(
- user,
- function (err, hasSubscription, subscription) {
- if (err) {
- logger.warn(
- { err, user_id: user._id, hasSubscription },
- 'there was an error checking user v2 subscription'
- )
- }
- if (hasSubscription) {
- RecurlyClient.cancelSubscriptionByUuid(
- 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(
- () =>
- EmailHandler.sendEmail(
- 'canceledSubscription',
- emailOpts,
- err => {
- if (err != null) {
- logger.warn(
- { err },
- 'failed to send confirmation email for subscription cancellation'
- )
- }
- }
- ),
- ONE_HOUR_IN_MS
- )
- callback()
- }
- )
- } else {
- callback()
- }
- }
- )
- }
- function reactivateSubscription(user, callback) {
- LimitationsManager.userHasV2Subscription(
- user,
- function (err, hasSubscription, subscription) {
- if (err) {
- logger.warn(
- { err, user_id: user._id, hasSubscription },
- 'there was an error checking user v2 subscription'
- )
- }
- if (hasSubscription) {
- RecurlyClient.reactivateSubscriptionByUuid(
- subscription.recurlySubscription_id,
- function (error) {
- if (error != null) {
- return callback(error)
- }
- EmailHandler.sendEmail(
- 'reactivatedSubscription',
- { to: user.email },
- err => {
- if (err != null) {
- logger.warn(
- { err },
- 'failed to send reactivation confirmation email'
- )
- }
- }
- )
- callback()
- }
- )
- } else {
- callback()
- }
- }
- )
- }
- function syncSubscription(recurlySubscription, requesterData, callback) {
- RecurlyWrapper.getSubscription(
- recurlySubscription.uuid,
- { includeAccount: true },
- function (error, recurlySubscription) {
- if (error != null) {
- return callback(error)
- }
- 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._id,
- requesterData,
- callback
- )
- }
- )
- }
- )
- }
- // 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.
- function 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
- )
- }
- )
- })
- }
- function extendTrial(subscription, daysToExend, callback) {
- return RecurlyWrapper.extendTrial(
- subscription.recurlySubscription_id,
- daysToExend,
- callback
- )
- }
- module.exports = {
- validateNoSubscriptionInRecurly,
- createSubscription,
- updateSubscription,
- cancelPendingSubscriptionChange,
- cancelSubscription,
- reactivateSubscription,
- syncSubscription,
- attemptPaypalInvoiceCollection,
- extendTrial,
- promises: {
- validateNoSubscriptionInRecurly: promisify(validateNoSubscriptionInRecurly),
- createSubscription: promisify(createSubscription),
- updateSubscription: promisify(updateSubscription),
- cancelPendingSubscriptionChange: promisify(cancelPendingSubscriptionChange),
- cancelSubscription: promisify(cancelSubscription),
- reactivateSubscription: promisify(reactivateSubscription),
- syncSubscription: promisify(syncSubscription),
- attemptPaypalInvoiceCollection: promisify(attemptPaypalInvoiceCollection),
- extendTrial: promisify(extendTrial),
- },
- }
|