diff --git a/services/web/app/src/Features/Subscription/RecurlyWrapper.js b/services/web/app/src/Features/Subscription/RecurlyWrapper.js index c464a05e2c..6714da86f1 100644 --- a/services/web/app/src/Features/Subscription/RecurlyWrapper.js +++ b/services/web/app/src/Features/Subscription/RecurlyWrapper.js @@ -1041,7 +1041,18 @@ const RecurlyWrapper = { } RecurlyWrapper.promises = { + attemptInvoiceCollection: promisify(RecurlyWrapper.attemptInvoiceCollection), + createSubscription: promisify(RecurlyWrapper.createSubscription), + extendTrial: promisify(RecurlyWrapper.extendTrial), + getBillingInfo: promisify(RecurlyWrapper.getBillingInfo), + getAccountPastDueInvoices: promisify( + RecurlyWrapper.getAccountPastDueInvoices + ), getSubscription: promisify(RecurlyWrapper.getSubscription), + listAccountActiveSubscriptions: promisify( + RecurlyWrapper.listAccountActiveSubscriptions + ), + redeemCoupon: promisify(RecurlyWrapper.redeemCoupon), updateAccountEmailAddress: promisify(updateAccountEmailAddress), } diff --git a/services/web/app/src/Features/Subscription/SubscriptionHandler.js b/services/web/app/src/Features/Subscription/SubscriptionHandler.js index 674441f25b..2849f49500 100644 --- a/services/web/app/src/Features/Subscription/SubscriptionHandler.js +++ b/services/web/app/src/Features/Subscription/SubscriptionHandler.js @@ -1,5 +1,3 @@ -const async = require('async') -const { promisify } = require('util') const RecurlyWrapper = require('./RecurlyWrapper') const RecurlyClient = require('./RecurlyClient') const { User } = require('../../models/User') @@ -11,171 +9,118 @@ const PlansLocator = require('./PlansLocator') const SubscriptionHelper = require('./SubscriptionHelper') const { callbackify } = require('@overleaf/promise-utils') -function validateNoSubscriptionInRecurly(userId, callback) { - RecurlyWrapper.listAccountActiveSubscriptions( - userId, - function (error, subscriptions) { - if (!subscriptions) { - subscriptions = [] - } - if (error) { - return callback(error) - } - if (subscriptions.length > 0) { - SubscriptionUpdater.syncSubscription( - subscriptions[0], - userId, - function (error) { - if (error) { - return callback(error) - } - callback(null, false) - } - ) - } else { - callback(null, true) - } - } - ) -} +async function validateNoSubscriptionInRecurly(userId) { + let subscriptions = + await RecurlyWrapper.promises.listAccountActiveSubscriptions(userId) -function createSubscription( - user, - subscriptionDetails, - recurlyTokenIds, - callback -) { - validateNoSubscriptionInRecurly(user._id, function (error, valid) { - if (error) { - 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) { - return callback(error) - } - SubscriptionUpdater.syncSubscription( - recurlySubscription, - user._id, - function (error) { - if (error) { - return callback(error) - } - callback() - } - ) - } + if (!subscriptions) { + subscriptions = [] + } + + if (subscriptions.length > 0) { + await SubscriptionUpdater.promises.syncSubscription( + subscriptions[0], + userId ) - }) + + return false + } + + return true } -function updateSubscription(user, planCode, couponCode, callback) { - LimitationsManager.userHasV2Subscription( +async function createSubscription(user, subscriptionDetails, recurlyTokenIds) { + const valid = await validateNoSubscriptionInRecurly(user._id) + + if (!valid) { + throw new Error('user already has subscription in recurly') + } + + const recurlySubscription = await RecurlyWrapper.promises.createSubscription( user, - function (err, hasSubscription, subscription) { - if (err) { - logger.warn( - { err, userId: user._id, hasSubscription }, - 'there was an error checking user v2 subscription' - ) - } - if (!hasSubscription) { - callback() - } else { - async.series( - [ - function (cb) { - if (!couponCode) { - return cb() - } - RecurlyWrapper.getSubscription( - subscription.recurlySubscription_id, - { includeAccount: true }, - function (err, usersSubscription) { - if (err) { - 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, timeframe }, - function (error, subscriptionChange) { - if (error) { - 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 - ) - } - } + subscriptionDetails, + recurlyTokenIds + ) + + await SubscriptionUpdater.promises.syncSubscription( + recurlySubscription, + user._id ) } -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) { - return callback(error) - } - callback() - } - ) - } else { - callback() - } - } +async function updateSubscription(user, planCode, couponCode) { + let hasSubscription = false + let subscription + + try { + ;({ hasSubscription, subscription } = + await LimitationsManager.promises.userHasV2Subscription(user)) + } catch (err) { + logger.warn( + { err, userId: user._id }, + 'there was an error checking user v2 subscription' + ) + } + + if (!hasSubscription) { + return + } + + if (couponCode) { + const usersSubscription = await RecurlyWrapper.promises.getSubscription( + subscription.recurlySubscription_id, + { includeAccount: true } + ) + + await RecurlyWrapper.promises.redeemCoupon( + usersSubscription.account.account_code, + couponCode + ) + } + + 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' + ) + throw new Error('unable to locate both plans in settings') + } + + const timeframe = changeAtTermEnd ? 'term_end' : 'now' + + await RecurlyClient.promises.changeSubscriptionByUuid( + subscription.recurlySubscription_id, + { planCode, timeframe } + ) + + // 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() + await syncSubscription( + { uuid: subscription.recurlySubscription_id }, + user._id + ) +} + +async function cancelPendingSubscriptionChange(user) { + const { hasSubscription, subscription } = + await LimitationsManager.promises.userHasV2Subscription(user) + + if (hasSubscription) { + await RecurlyClient.promises.removeSubscriptionChangeByUuid( + subscription.recurlySubscription_id + ) + } } async function cancelSubscription(user) { @@ -236,33 +181,24 @@ async function reactivateSubscription(user) { } } -function syncSubscription(recurlySubscription, requesterData, callback) { - RecurlyWrapper.getSubscription( +async function syncSubscription(recurlySubscription, requesterData) { + const storedSubscription = await RecurlyWrapper.promises.getSubscription( recurlySubscription.uuid, - { includeAccount: true }, - function (error, recurlySubscription) { - if (error) { - return callback(error) - } - User.findById( - recurlySubscription.account.account_code, - { _id: 1 }, - function (error, user) { - if (error) { - return callback(error) - } - if (!user) { - return callback(new Error('no user found')) - } - SubscriptionUpdater.syncSubscription( - recurlySubscription, - user._id, - requesterData, - callback - ) - } - ) - } + { includeAccount: true } + ) + + const user = await User.findById(storedSubscription.account.account_code, { + _id: 1, + }).exec() + + if (!user) { + throw new Error('no user found') + } + + await SubscriptionUpdater.promises.syncSubscription( + storedSubscription, + user._id, + requesterData ) } @@ -270,39 +206,33 @@ function syncSubscription(recurlySubscription, requesterData, callback) { // 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 - ) - } - ) - }) +async function attemptPaypalInvoiceCollection(recurlyAccountCode) { + const billingInfo = await RecurlyWrapper.promises.getBillingInfo( + recurlyAccountCode + ) + + if (!billingInfo.paypal_billing_agreement_id) { + // this is not a Paypal user + return + } + + const pastDueInvoices = + await RecurlyWrapper.promises.getAccountPastDueInvoices(recurlyAccountCode) + + if (pastDueInvoices.length !== 1) { + // no past due invoices, or more than one. Ignore. + return + } + + return await RecurlyWrapper.promises.attemptInvoiceCollection( + pastDueInvoices[0].invoice_number + ) } -function extendTrial(subscription, daysToExend, callback) { - RecurlyWrapper.extendTrial( +async function extendTrial(subscription, daysToExend) { + await RecurlyWrapper.promises.extendTrial( subscription.recurlySubscription_id, - daysToExend, - callback + daysToExend ) } @@ -318,24 +248,24 @@ async function _updateSubscriptionFromRecurly(subscription) { } module.exports = { - validateNoSubscriptionInRecurly, - createSubscription, - updateSubscription, - cancelPendingSubscriptionChange, + validateNoSubscriptionInRecurly: callbackify(validateNoSubscriptionInRecurly), + createSubscription: callbackify(createSubscription), + updateSubscription: callbackify(updateSubscription), + cancelPendingSubscriptionChange: callbackify(cancelPendingSubscriptionChange), cancelSubscription: callbackify(cancelSubscription), reactivateSubscription: callbackify(reactivateSubscription), - syncSubscription, - attemptPaypalInvoiceCollection, - extendTrial, + syncSubscription: callbackify(syncSubscription), + attemptPaypalInvoiceCollection: callbackify(attemptPaypalInvoiceCollection), + extendTrial: callbackify(extendTrial), promises: { - validateNoSubscriptionInRecurly: promisify(validateNoSubscriptionInRecurly), - createSubscription: promisify(createSubscription), - updateSubscription: promisify(updateSubscription), - cancelPendingSubscriptionChange: promisify(cancelPendingSubscriptionChange), + validateNoSubscriptionInRecurly, + createSubscription, + updateSubscription, + cancelPendingSubscriptionChange, cancelSubscription, reactivateSubscription, - syncSubscription: promisify(syncSubscription), - attemptPaypalInvoiceCollection: promisify(attemptPaypalInvoiceCollection), - extendTrial: promisify(extendTrial), + syncSubscription, + attemptPaypalInvoiceCollection, + extendTrial, }, } diff --git a/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js b/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js index 7510d1c644..bc84d2815f 100644 --- a/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js +++ b/services/web/test/unit/src/Subscription/SubscriptionHandlerTests.js @@ -48,8 +48,6 @@ const mockSubscriptionChanges = { describe('SubscriptionHandler', function () { beforeEach(function () { - this.callback = sinon.stub() - this.Settings = { plans: [ { @@ -78,50 +76,42 @@ describe('SubscriptionHandler', function () { recurlySubscription_id: this.activeRecurlySubscription.uuid, } this.RecurlyWrapper = { - getSubscription: sinon - .stub() - .callsArgWith(2, null, this.activeRecurlySubscription), - redeemCoupon: sinon.stub().callsArgWith(2), - createSubscription: sinon - .stub() - .callsArgWith(3, null, this.activeRecurlySubscription), - getBillingInfo: sinon.stub().yields(), - getAccountPastDueInvoices: sinon.stub().yields(), - attemptInvoiceCollection: sinon.stub().yields(), - listAccountActiveSubscriptions: sinon.stub().yields(null, []), promises: { getSubscription: sinon.stub().resolves(this.activeRecurlySubscription), + redeemCoupon: sinon.stub().resolves(), + createSubscription: sinon + .stub() + .resolves(this.activeRecurlySubscription), + getBillingInfo: sinon.stub().resolves(), + getAccountPastDueInvoices: sinon.stub().resolves(), + attemptInvoiceCollection: sinon.stub().resolves(), + listAccountActiveSubscriptions: sinon.stub().resolves([]), }, } this.RecurlyClient = { - changeSubscriptionByUuid: sinon - .stub() - .yields(null, this.activeRecurlySubscriptionChange), - getSubscription: sinon - .stub() - .yields(null, this.activeRecurlyClientSubscription), - reactivateSubscriptionByUuid: sinon - .stub() - .yields(null, this.activeRecurlyClientSubscription), - cancelSubscriptionByUuid: sinon.stub().yields(), promises: { reactivateSubscriptionByUuid: sinon .stub() .resolves(this.activeRecurlyClientSubscription), cancelSubscriptionByUuid: sinon.stub().resolves(), + changeSubscriptionByUuid: sinon + .stub() + .resolves(this.activeRecurlySubscriptionChange), + getSubscription: sinon + .stub() + .resolves(this.activeRecurlyClientSubscription), }, } this.SubscriptionUpdater = { - syncSubscription: sinon.stub().yields(), - startFreeTrial: sinon.stub().callsArgWith(1), promises: { updateSubscriptionFromRecurly: sinon.stub().resolves(), + syncSubscription: sinon.stub().resolves(), + startFreeTrial: sinon.stub().resolves(), }, } this.LimitationsManager = { - userHasV2Subscription: sinon.stub(), promises: { userHasV2Subscription: sinon.stub().resolves(), }, @@ -132,8 +122,6 @@ describe('SubscriptionHandler', function () { sendDeferredEmail: sinon.stub(), } - this.AnalyticsManager = { recordEventForUser: sinon.stub() } - this.PlansLocator = { findLocalPlanInSettings: sinon.stub().returns({ planCode: 'plan' }), } @@ -170,27 +158,28 @@ describe('SubscriptionHandler', function () { }) describe('successfully', function () { - beforeEach(function () { - this.SubscriptionHandler.createSubscription( + beforeEach(async function () { + await this.SubscriptionHandler.promises.createSubscription( this.user, this.subscriptionDetails, - this.recurlyTokenIds, - this.callback + this.recurlyTokenIds ) }) it('should create the subscription with the wrapper', function () { - this.RecurlyWrapper.createSubscription + this.RecurlyWrapper.promises.createSubscription .calledWith(this.user, this.subscriptionDetails, this.recurlyTokenIds) .should.equal(true) }) it('should sync the subscription to the user', function () { - this.SubscriptionUpdater.syncSubscription.calledOnce.should.equal(true) - this.SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal( + this.SubscriptionUpdater.promises.syncSubscription.calledOnce.should.equal( + true + ) + this.SubscriptionUpdater.promises.syncSubscription.args[0][0].should.deep.equal( this.activeRecurlySubscription ) - this.SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal( + this.SubscriptionUpdater.promises.syncSubscription.args[0][1].should.deep.equal( this.user._id ) }) @@ -198,21 +187,19 @@ describe('SubscriptionHandler', function () { describe('when there is already a subscription in Recurly', function () { beforeEach(function () { - this.RecurlyWrapper.listAccountActiveSubscriptions.yields(null, [ + this.RecurlyWrapper.promises.listAccountActiveSubscriptions.resolves([ this.subscription, ]) - this.SubscriptionHandler.createSubscription( - this.user, - this.subscriptionDetails, - this.recurlyTokenIds, - this.callback - ) }) - it('should an error', function () { - this.callback.calledWith( - new Error('user already has subscription in recurly') - ) + it('should an error', function () { + expect( + this.SubscriptionHandler.promises.createSubscription( + this.user, + this.subscriptionDetails, + this.recurlyTokenIds + ) + ).to.be.rejectedWith('user already has subscription in recurly') }) }) }) @@ -220,21 +207,23 @@ describe('SubscriptionHandler', function () { function shouldUpdateSubscription() { it('should update the subscription', function () { expect( - this.RecurlyClient.changeSubscriptionByUuid + this.RecurlyClient.promises.changeSubscriptionByUuid ).to.have.been.calledWith(this.subscription.recurlySubscription_id) const updateOptions = - this.RecurlyClient.changeSubscriptionByUuid.args[0][1] + this.RecurlyClient.promises.changeSubscriptionByUuid.args[0][1] updateOptions.planCode.should.equal(this.plan_code) }) } function shouldSyncSubscription() { it('should sync the new subscription to the user', function () { - expect(this.SubscriptionUpdater.syncSubscription).to.have.been.called - this.SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal( + expect(this.SubscriptionUpdater.promises.syncSubscription).to.have.been + .called + + this.SubscriptionUpdater.promises.syncSubscription.args[0][0].should.deep.equal( this.activeRecurlySubscription ) - this.SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal( + this.SubscriptionUpdater.promises.syncSubscription.args[0][1].should.deep.equal( this.user._id ) }) @@ -244,27 +233,26 @@ describe('SubscriptionHandler', function () { describe( 'when change should happen with timeframe ' + timeframe, function () { - beforeEach(function (done) { + beforeEach(async function () { this.user.id = this.activeRecurlySubscription.account.account_code - this.User.findById = (userId, projection, callback) => { - userId.should.equal(this.user.id) - callback(null, this.user) - } + this.User.findById = (userId, projection) => ({ + exec: () => { + userId.should.equal(this.user.id) + return Promise.resolve(this.user) + }, + }) this.plan_code = 'collaborator' this.SubscriptionHelper.shouldPlanChangeAtTermEnd.returns( shouldPlanChangeAtTermEnd ) - this.LimitationsManager.userHasV2Subscription.callsArgWith( - 1, - null, - true, - this.subscription - ) - this.SubscriptionHandler.updateSubscription( + this.LimitationsManager.promises.userHasV2Subscription.resolves({ + hasSubscription: true, + subscription: this.subscription, + }) + await this.SubscriptionHandler.promises.updateSubscription( this.user, this.plan_code, - null, - done + null ) }) @@ -273,7 +261,7 @@ describe('SubscriptionHandler', function () { it('should update with timeframe ' + timeframe, function () { const updateOptions = - this.RecurlyClient.changeSubscriptionByUuid.args[0][1] + this.RecurlyClient.promises.changeSubscriptionByUuid.args[0][1] updateOptions.timeframe.should.equal(timeframe) }) } @@ -286,106 +274,102 @@ describe('SubscriptionHandler', function () { testUserWithASubscription(true, 'term_end') describe('when plan(s) could not be located in settings', function () { - beforeEach(function () { + beforeEach(async function () { this.user.id = this.activeRecurlySubscription.account.account_code - this.User.findById = (userId, projection, callback) => { - userId.should.equal(this.user.id) - callback(null, this.user) - } + this.User.findById = (userId, projection) => ({ + exec: () => { + userId.should.equal(this.user.id) + return Promise.resolve(this.user) + }, + }) + this.plan_code = 'collaborator' this.PlansLocator.findLocalPlanInSettings.returns(null) - this.LimitationsManager.userHasV2Subscription.callsArgWith( - 1, - null, - true, - this.subscription - ) - this.SubscriptionHandler.updateSubscription( - this.user, - this.plan_code, - null, - this.callback - ) + this.LimitationsManager.promises.userHasV2Subscription.resolves({ + hasSubscription: true, + subscription: this.subscription, + }) }) - it('should not update the subscription', function () { - this.RecurlyClient.changeSubscriptionByUuid.called.should.equal(false) - }) - - it('should return an error to the callback', function () { - this.callback - .calledWith(sinon.match.instanceOf(Error)) - .should.equal(true) + it('should be rejected and should not update the subscription', function () { + expect( + this.SubscriptionHandler.promises.updateSubscription( + this.user, + this.plan_code, + null + ) + ).to.be.rejected + this.RecurlyClient.promises.changeSubscriptionByUuid.called.should.equal( + false + ) }) }) }) describe('with a user without a subscription', function () { - beforeEach(function (done) { - this.LimitationsManager.userHasV2Subscription.callsArgWith( - 1, - null, - false - ) - this.SubscriptionHandler.updateSubscription( + beforeEach(async function () { + this.LimitationsManager.promises.userHasV2Subscription.resolves(false) + await this.SubscriptionHandler.promises.updateSubscription( this.user, this.plan_code, - null, - done + null ) }) it('should redirect to the subscription dashboard', function () { - this.RecurlyClient.changeSubscriptionByUuid.called.should.equal(false) - this.SubscriptionUpdater.syncSubscription.called.should.equal(false) + this.RecurlyClient.promises.changeSubscriptionByUuid.called.should.equal( + false + ) + this.SubscriptionUpdater.promises.syncSubscription.called.should.equal( + false + ) }) }) describe('with a coupon code', function () { - beforeEach(function (done) { + beforeEach(async function () { this.user.id = this.activeRecurlySubscription.account.account_code - this.User.findById = (userId, projection, callback) => { - userId.should.equal(this.user.id) - callback(null, this.user) - } + + this.User.findById = (userId, projection) => ({ + exec: () => { + userId.should.equal(this.user.id) + return Promise.resolve(this.user) + }, + }) this.plan_code = 'collaborator' this.coupon_code = '1231312' - this.LimitationsManager.userHasV2Subscription.callsArgWith( - 1, - null, - true, - this.subscription - ) - this.SubscriptionHandler.updateSubscription( + this.LimitationsManager.promises.userHasV2Subscription.resolves({ + hasSubscription: true, + subscription: this.subscription, + }) + await this.SubscriptionHandler.promises.updateSubscription( this.user, this.plan_code, - this.coupon_code, - done + this.coupon_code ) }) it('should get the users account', function () { - this.RecurlyWrapper.getSubscription + this.RecurlyWrapper.promises.getSubscription .calledWith(this.activeRecurlySubscription.uuid) .should.equal(true) }) - it('should redeem the coupon', function (done) { - this.RecurlyWrapper.redeemCoupon + it('should redeem the coupon', function () { + this.RecurlyWrapper.promises.redeemCoupon .calledWith( this.activeRecurlySubscription.account.account_code, this.coupon_code ) .should.equal(true) - done() }) it('should update the subscription', function () { - expect(this.RecurlyClient.changeSubscriptionByUuid).to.be.calledWith( - this.subscription.recurlySubscription_id - ) + expect( + this.RecurlyClient.promises.changeSubscriptionByUuid + ).to.be.calledWith(this.subscription.recurlySubscription_id) const updateOptions = - this.RecurlyClient.changeSubscriptionByUuid.args[0][1] + this.RecurlyClient.promises.changeSubscriptionByUuid.args[0][1] updateOptions.planCode.should.equal(this.plan_code) }) }) @@ -393,28 +377,28 @@ describe('SubscriptionHandler', function () { describe('cancelSubscription', function () { describe('with a user without a subscription', function () { - beforeEach(function (done) { - this.LimitationsManager.promises.userHasV2Subscription.callsArgWith( - 1, - null, - false, - this.subscription - ) - this.SubscriptionHandler.cancelSubscription(this.user, done) + beforeEach(async function () { + this.LimitationsManager.promises.userHasV2Subscription.resolves({ + hasSubscription: false, + subscription: this.subscription, + }) + await this.SubscriptionHandler.promises.cancelSubscription(this.user) }) it('should redirect to the subscription dashboard', function () { - this.RecurlyClient.cancelSubscriptionByUuid.called.should.equal(false) + this.RecurlyClient.promises.cancelSubscriptionByUuid.called.should.equal( + false + ) }) }) describe('with a user with a subscription', function () { - beforeEach(function (done) { + beforeEach(async function () { this.LimitationsManager.promises.userHasV2Subscription.resolves({ hasSubscription: true, subscription: this.subscription, }) - this.SubscriptionHandler.cancelSubscription(this.user, done) + await this.SubscriptionHandler.promises.cancelSubscription(this.user) }) it('should cancel the subscription', function () { @@ -439,16 +423,18 @@ describe('SubscriptionHandler', function () { describe('reactivateSubscription', function () { describe('with a user without a subscription', function () { - beforeEach(function (done) { + beforeEach(async function () { this.LimitationsManager.promises.userHasV2Subscription.resolves({ hasSubscription: false, subscription: this.subscription, }) - this.SubscriptionHandler.reactivateSubscription(this.user, done) + await this.SubscriptionHandler.promises.reactivateSubscription( + this.user + ) }) it('should redirect to the subscription dashboard', function () { - this.RecurlyClient.reactivateSubscriptionByUuid.called.should.equal( + this.RecurlyClient.promises.reactivateSubscriptionByUuid.called.should.equal( false ) }) @@ -459,12 +445,14 @@ describe('SubscriptionHandler', function () { }) describe('with a user with a subscription', function () { - beforeEach(function (done) { + beforeEach(async function () { this.LimitationsManager.promises.userHasV2Subscription.resolves({ hasSubscription: true, subscription: this.subscription, }) - this.SubscriptionHandler.reactivateSubscription(this.user, done) + await this.SubscriptionHandler.promises.reactivateSubscription( + this.user + ) }) it('should reactivate the subscription', function () { @@ -487,37 +475,41 @@ describe('SubscriptionHandler', function () { describe('syncSubscription', function () { describe('with an actionable request', function () { - beforeEach(function (done) { + beforeEach(async function () { this.user.id = this.activeRecurlySubscription.account.account_code - this.User.findById = (userId, projection, callback) => { - userId.should.equal(this.user.id) - callback(null, this.user) - } - this.SubscriptionHandler.syncSubscription( + this.User.findById = (userId, projection) => ({ + exec: () => { + userId.should.equal(this.user.id) + return Promise.resolve(this.user) + }, + }) + + await this.SubscriptionHandler.promises.syncSubscription( this.activeRecurlySubscription, - {}, - done + {} ) }) it('should request the affected subscription from the API', function () { - this.RecurlyWrapper.getSubscription + this.RecurlyWrapper.promises.getSubscription .calledWith(this.activeRecurlySubscription.uuid) .should.equal(true) }) it('should request the account details of the subscription', function () { - const options = this.RecurlyWrapper.getSubscription.args[0][1] + const options = this.RecurlyWrapper.promises.getSubscription.args[0][1] options.includeAccount.should.equal(true) }) it('should sync the subscription to the user', function () { - this.SubscriptionUpdater.syncSubscription.calledOnce.should.equal(true) - this.SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal( + this.SubscriptionUpdater.promises.syncSubscription.calledOnce.should.equal( + true + ) + this.SubscriptionUpdater.promises.syncSubscription.args[0][0].should.deep.equal( this.activeRecurlySubscription ) - this.SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal( + this.SubscriptionUpdater.promises.syncSubscription.args[0][1].should.deep.equal( this.user._id ) }) @@ -526,52 +518,52 @@ describe('SubscriptionHandler', function () { describe('attemptPaypalInvoiceCollection', function () { describe('for credit card users', function () { - beforeEach(function (done) { - this.RecurlyWrapper.getBillingInfo.yields(null, { + beforeEach(async function () { + this.RecurlyWrapper.promises.getBillingInfo.resolves({ paypal_billing_agreement_id: null, }) - this.SubscriptionHandler.attemptPaypalInvoiceCollection( - this.activeRecurlySubscription.account.account_code, - done + await this.SubscriptionHandler.promises.attemptPaypalInvoiceCollection( + this.activeRecurlySubscription.account.account_code ) }) it('gets billing infos', function () { sinon.assert.calledWith( - this.RecurlyWrapper.getBillingInfo, + this.RecurlyWrapper.promises.getBillingInfo, this.activeRecurlySubscription.account.account_code ) }) it('skips user', function () { - sinon.assert.notCalled(this.RecurlyWrapper.getAccountPastDueInvoices) + sinon.assert.notCalled( + this.RecurlyWrapper.promises.getAccountPastDueInvoices + ) }) }) describe('for paypal users', function () { - beforeEach(function (done) { - this.RecurlyWrapper.getBillingInfo.yields(null, { + beforeEach(async function () { + this.RecurlyWrapper.promises.getBillingInfo.resolves({ paypal_billing_agreement_id: 'mock-billing-agreement', }) - this.RecurlyWrapper.getAccountPastDueInvoices.yields(null, [ + this.RecurlyWrapper.promises.getAccountPastDueInvoices.resolves([ { invoice_number: 'mock-invoice-number' }, ]) - this.SubscriptionHandler.attemptPaypalInvoiceCollection( - this.activeRecurlySubscription.account.account_code, - done + await this.SubscriptionHandler.promises.attemptPaypalInvoiceCollection( + this.activeRecurlySubscription.account.account_code ) }) it('gets past due invoices', function () { sinon.assert.calledWith( - this.RecurlyWrapper.getAccountPastDueInvoices, + this.RecurlyWrapper.promises.getAccountPastDueInvoices, this.activeRecurlySubscription.account.account_code ) }) it('calls attemptInvoiceCollection', function () { sinon.assert.calledWith( - this.RecurlyWrapper.attemptInvoiceCollection, + this.RecurlyWrapper.promises.attemptInvoiceCollection, 'mock-invoice-number' ) }) @@ -580,47 +572,49 @@ describe('SubscriptionHandler', function () { describe('validateNoSubscriptionInRecurly', function () { describe('with a subscription in recurly', function () { - beforeEach(function () { - this.RecurlyWrapper.listAccountActiveSubscriptions.yields(null, [ + beforeEach(async function () { + this.RecurlyWrapper.promises.listAccountActiveSubscriptions.resolves([ this.subscription, ]) - this.SubscriptionHandler.validateNoSubscriptionInRecurly( - this.user_id, - this.callback - ) + this.isValid = + await this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly( + this.user_id + ) }) it('should call RecurlyWrapper.listAccountActiveSubscriptions with the user id', function () { - this.RecurlyWrapper.listAccountActiveSubscriptions + this.RecurlyWrapper.promises.listAccountActiveSubscriptions .calledWith(this.user_id) .should.equal(true) }) it('should sync the subscription', function () { - this.SubscriptionUpdater.syncSubscription + this.SubscriptionUpdater.promises.syncSubscription .calledWith(this.subscription, this.user_id) .should.equal(true) }) - it('should call the callback with valid == false', function () { - this.callback.calledWith(null, false).should.equal(true) + it('should return false', function () { + expect(this.isValid).to.equal(false) }) }) describe('with no subscription in recurly', function () { - beforeEach(function () { - this.SubscriptionHandler.validateNoSubscriptionInRecurly( - this.user_id, - this.callback + beforeEach(async function () { + this.isValid = + await this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly( + this.user_id + ) + }) + + it('should be rejected and not sync the subscription', function () { + this.SubscriptionUpdater.promises.syncSubscription.called.should.equal( + false ) }) - it('should not sync the subscription', function () { - this.SubscriptionUpdater.syncSubscription.called.should.equal(false) - }) - - it('should call the callback with valid == true', function () { - this.callback.calledWith(null, true).should.equal(true) + it('should return true', function () { + expect(this.isValid).to.equal(true) }) }) })