Check Recurly for subscription as well before creating subscription

This commit is contained in:
James Allen
2017-03-27 12:07:43 +01:00
parent 1d927e5eb1
commit 7aa4c0c030
5 changed files with 137 additions and 62 deletions
@@ -470,32 +470,34 @@ module.exports = RecurlyWrapper =
callback(error) callback(error)
) )
_parseSubscriptionXml: (xml, callback) -> listAccountActiveSubscriptions: (account_id, callback = (error, subscriptions) ->) ->
RecurlyWrapper._parseXml xml, (error, data) -> RecurlyWrapper.apiRequest {
url: "accounts/#{account_id}/subscriptions"
qs:
state: "active"
}, (error, response, body) ->
return callback(error) if error? return callback(error) if error?
if data? and data.subscription? RecurlyWrapper._parseSubscriptionsXml body, callback
recurlySubscription = data.subscription
else _parseSubscriptionsXml: (xml, callback) ->
return callback "I don't understand the response from Recurly" RecurlyWrapper._parseXmlAndGetAttribute xml, "subscriptions", callback
callback null, recurlySubscription
_parseSubscriptionXml: (xml, callback) ->
RecurlyWrapper._parseXmlAndGetAttribute xml, "subscription", callback
_parseAccountXml: (xml, callback) -> _parseAccountXml: (xml, callback) ->
RecurlyWrapper._parseXml xml, (error, data) -> RecurlyWrapper._parseXmlAndGetAttribute xml, "account", callback
return callback(error) if error?
if data? and data.account?
account = data.account
else
return callback "I don't understand the response from Recurly"
callback null, account
_parseBillingInfoXml: (xml, callback) -> _parseBillingInfoXml: (xml, callback) ->
RecurlyWrapper._parseXmlAndGetAttribute xml, "billing_info", callback
_parseXmlAndGetAttribute: (xml, attribute, callback) ->
RecurlyWrapper._parseXml xml, (error, data) -> RecurlyWrapper._parseXml xml, (error, data) ->
return callback(error) if error? return callback(error) if error?
if data? and data.billing_info? if data? and data[attribute]?
billingInfo = data.billing_info return callback null, data[attribute]
else else
return callback "I don't understand the response from Recurly" return callback(new Error("I don't understand the response from Recurly"))
callback null, billingInfo
_parseXml: (xml, callback) -> _parseXml: (xml, callback) ->
convertDataTypes = (data) -> convertDataTypes = (data) ->
@@ -46,31 +46,39 @@ module.exports = SubscriptionController =
if hasSubscription or !plan? if hasSubscription or !plan?
res.redirect "/user/subscription" res.redirect "/user/subscription"
else else
currency = req.query.currency?.toUpperCase() # LimitationsManager.userHasSubscription only checks Mongo. Double check with
GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency, countryCode)-> # Recurly as well at this point (we don't do this most places for speed).
return next(err) if err? SubscriptionHandler.validateNoSubscriptionInRecurly user._id, (error, valid) ->
if recomendedCurrency? and !currency? return next(error) if error?
currency = recomendedCurrency if !valid
RecurlyWrapper.sign { res.redirect "/user/subscription"
subscription: return
plan_code : req.query.planCode else
currency: currency currency = req.query.currency?.toUpperCase()
account_code: user._id GeoIpLookup.getCurrencyCode req.query?.ip || req.ip, (err, recomendedCurrency, countryCode)->
}, (error, signature) -> return next(err) if err?
return next(error) if error? if recomendedCurrency? and !currency?
res.render "subscriptions/new", currency = recomendedCurrency
title : "subscribe" RecurlyWrapper.sign {
plan_code: req.query.planCode subscription:
currency: currency plan_code : req.query.planCode
countryCode:countryCode currency: currency
plan:plan account_code: user._id
showStudentPlan: req.query.ssp }, (error, signature) ->
recurlyConfig: JSON.stringify return next(error) if error?
currency: currency res.render "subscriptions/new",
subdomain: Settings.apis.recurly.subdomain title : "subscribe"
showCouponField: req.query.scf plan_code: req.query.planCode
showVatField: req.query.svf currency: currency
couponCode: req.query.cc or "" countryCode:countryCode
plan:plan
showStudentPlan: req.query.ssp
recurlyConfig: JSON.stringify
currency: currency
subdomain: Settings.apis.recurly.subdomain
showCouponField: req.query.scf
showVatField: req.query.svf
couponCode: req.query.cc or ""
@@ -11,15 +11,28 @@ Analytics = require("../Analytics/AnalyticsManager")
module.exports = module.exports =
validateNoSubscriptionInRecurly: (user_id, callback = (error, valid) ->) ->
RecurlyWrapper.listAccountActiveSubscriptions user_id, (error, subscriptions = []) ->
return callback(error) if error?
if subscriptions.length > 0
SubscriptionUpdater.syncSubscription subscriptions[0], user_id, (error) ->
return callback(error) if error?
return callback(null, false)
else
return callback(null, true)
createSubscription: (user, subscriptionDetails, recurly_token_id, callback)-> createSubscription: (user, subscriptionDetails, recurly_token_id, callback)->
self = @ self = @
clientTokenId = "" clientTokenId = ""
RecurlyWrapper.createSubscription user, subscriptionDetails, recurly_token_id, (error, recurlySubscription)-> @validateNoSubscriptionInRecurly user._id, (error, valid) ->
return callback(error) if error? return callback(error) if error?
SubscriptionUpdater.syncSubscription recurlySubscription, user._id, (error) -> if !valid
return callback(new Error("user already has subscription in recurly"))
RecurlyWrapper.createSubscription user, subscriptionDetails, recurly_token_id, (error, recurlySubscription)->
return callback(error) if error? return callback(error) if error?
callback() SubscriptionUpdater.syncSubscription recurlySubscription, user._id, (error) ->
return callback(error) if error?
callback()
updateSubscription: (user, plan_code, coupon_code, callback)-> updateSubscription: (user, plan_code, coupon_code, callback)->
logger.log user:user, plan_code:plan_code, coupon_code:coupon_code, "updating subscription" logger.log user:user, plan_code:plan_code, coupon_code:coupon_code, "updating subscription"
@@ -17,8 +17,7 @@ mockSubscriptions =
account: account:
account_code: "user-123" account_code: "user-123"
describe "SubscriptionController sanboxed", -> describe "SubscriptionController", ->
beforeEach -> beforeEach ->
@user = {email:"tom@yahoo.com", _id: 'one', signUpDate: new Date('2000-10-01')} @user = {email:"tom@yahoo.com", _id: 'one', signUpDate: new Date('2000-10-01')}
@activeRecurlySubscription = mockSubscriptions["subscription-123-active"] @activeRecurlySubscription = mockSubscriptions["subscription-123-active"]
@@ -150,6 +149,7 @@ describe "SubscriptionController sanboxed", ->
describe "paymentPage", -> describe "paymentPage", ->
beforeEach -> beforeEach ->
@req.headers = {} @req.headers = {}
@SubscriptionHandler.validateNoSubscriptionInRecurly = sinon.stub().yields(null, true)
@GeoIpLookup.getCurrencyCode.callsArgWith(1, null, @stubbedCurrencyCode) @GeoIpLookup.getCurrencyCode.callsArgWith(1, null, @stubbedCurrencyCode)
describe "with a user without a subscription", -> describe "with a user without a subscription", ->
@@ -210,6 +210,16 @@ describe "SubscriptionController sanboxed", ->
done() done()
@SubscriptionController.paymentPage @req, @res @SubscriptionController.paymentPage @req, @res
describe "with a recurly subscription already", ->
it "should redirect to the subscription dashboard", (done)->
@LimitationsManager.userHasSubscription.callsArgWith(1, null, false)
@SubscriptionHandler.validateNoSubscriptionInRecurly = sinon.stub().yields(null, false)
@res.redirect = (url)=>
url.should.equal "/user/subscription"
done()
@SubscriptionController.paymentPage(@req, @res)
describe "successful_subscription", -> describe "successful_subscription", ->
beforeEach (done) -> beforeEach (done) ->
@SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel.callsArgWith(1, null, {}) @SubscriptionViewModelBuilder.buildUsersSubscriptionViewModel.callsArgWith(1, null, {})
@@ -16,7 +16,7 @@ mockRecurlySubscriptions =
account: account:
account_code: "user-123" account_code: "user-123"
describe "Subscription Handler sanboxed", -> describe "SubscriptionHandler", ->
beforeEach -> beforeEach ->
@Settings = @Settings =
@@ -33,7 +33,7 @@ describe "Subscription Handler sanboxed", ->
@activeRecurlySubscription = mockRecurlySubscriptions["subscription-123-active"] @activeRecurlySubscription = mockRecurlySubscriptions["subscription-123-active"]
@User = {} @User = {}
@user = @user =
_id: "user_id_here_" _id: @user_id = "user_id_here_"
@subscription = @subscription =
recurlySubscription_id: @activeRecurlySubscription.uuid recurlySubscription_id: @activeRecurlySubscription.uuid
@RecurlyWrapper = @RecurlyWrapper =
@@ -77,23 +77,33 @@ describe "Subscription Handler sanboxed", ->
describe "createSubscription", -> describe "createSubscription", ->
beforeEach (done) -> beforeEach ->
@callback = sinon.stub()
@subscriptionDetails = @subscriptionDetails =
cvv:"123" cvv:"123"
number:"12345" number:"12345"
@recurly_token_id = "45555666" @recurly_token_id = "45555666"
@SubscriptionHandler.createSubscription(@user, @subscriptionDetails, @recurly_token_id, done) @SubscriptionHandler.validateNoSubscriptionInRecurly = sinon.stub().yields(null, true)
it "should create the subscription with the wrapper", (done)-> describe "successfully", ->
@RecurlyWrapper.createSubscription.calledWith(@user, @subscriptionDetails, @recurly_token_id).should.equal true beforeEach ->
done() @SubscriptionHandler.createSubscription(@user, @subscriptionDetails, @recurly_token_id, @callback)
it "should sync the subscription to the user", (done)-> it "should create the subscription with the wrapper", ->
@SubscriptionUpdater.syncSubscription.calledOnce.should.equal true @RecurlyWrapper.createSubscription.calledWith(@user, @subscriptionDetails, @recurly_token_id).should.equal true
@SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal @activeRecurlySubscription
@SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal @user._id
done()
it "should sync the subscription to the user", ->
@SubscriptionUpdater.syncSubscription.calledOnce.should.equal true
@SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal @activeRecurlySubscription
@SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal @user._id
describe "when there is already a subscription in Recurly", ->
beforeEach ->
@SubscriptionHandler.validateNoSubscriptionInRecurly = sinon.stub().yields(null, false)
@SubscriptionHandler.createSubscription(@user, @subscriptionDetails, @recurly_token_id, @callback)
it "should return an error", ->
@callback.calledWith(new Error("user already has subscription in recurly"))
describe "updateSubscription", -> describe "updateSubscription", ->
describe "with a user with a subscription", -> describe "with a user with a subscription", ->
@@ -145,8 +155,6 @@ describe "Subscription Handler sanboxed", ->
updateOptions = @RecurlyWrapper.updateSubscription.args[0][1] updateOptions = @RecurlyWrapper.updateSubscription.args[0][1]
updateOptions.plan_code.should.equal @plan_code updateOptions.plan_code.should.equal @plan_code
describe "cancelSubscription", -> describe "cancelSubscription", ->
describe "with a user without a subscription", -> describe "with a user without a subscription", ->
beforeEach (done) -> beforeEach (done) ->
@@ -210,5 +218,39 @@ describe "Subscription Handler sanboxed", ->
@SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal @activeRecurlySubscription @SubscriptionUpdater.syncSubscription.args[0][0].should.deep.equal @activeRecurlySubscription
@SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal @user._id @SubscriptionUpdater.syncSubscription.args[0][1].should.deep.equal @user._id
describe "validateNoSubscriptionInRecurly", ->
beforeEach ->
@subscriptions = []
@RecurlyWrapper.listAccountActiveSubscriptions = sinon.stub().yields(null, @subscriptions)
@SubscriptionUpdater.syncSubscription = sinon.stub().yields()
@callback = sinon.stub()
describe "with no subscription in recurly", ->
beforeEach ->
@subscriptions.push @subscription = { "mock": "subscription" }
@SubscriptionHandler.validateNoSubscriptionInRecurly @user_id, @callback
it "should call RecurlyWrapper.listAccountActiveSubscriptions with the user id", ->
@RecurlyWrapper.listAccountActiveSubscriptions
.calledWith(@user_id)
.should.equal true
it "should sync the subscription", ->
@SubscriptionUpdater.syncSubscription
.calledWith(@subscription, @user_id)
.should.equal true
it "should call the callback with valid == false", ->
@callback.calledWith(null, false).should.equal true
describe "with a subscription in recurly", ->
beforeEach ->
@SubscriptionHandler.validateNoSubscriptionInRecurly @user_id, @callback
it "should not sync the subscription", ->
@SubscriptionUpdater.syncSubscription
.called
.should.equal false
it "should call the callback with valid == true", ->
@callback.calledWith(null, true).should.equal true