mirror of
https://github.com/overleaf/overleaf.git
synced 2025-04-15 22:27:23 +00:00
Merge pull request #17005 from overleaf/dp-mongoose-callback-subscription-handler
Promisify SubscriptionHandler and SubscriptionHandlerTests GitOrigin-RevId: b34328ee2cca4449a02723a587a1bfb887ed847a
This commit is contained in:
parent
bdc6b417e2
commit
6551aba1a5
3 changed files with 352 additions and 417 deletions
|
@ -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),
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
},
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Loading…
Add table
Reference in a new issue