diff --git a/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee b/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee index 41435eabe4..841ee0a77e 100644 --- a/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee +++ b/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee @@ -18,160 +18,178 @@ module.exports = RecurlyWrapper = resultString += "\n" return resultString + _paypal: + checkAccountExists: (cache, next) -> + user = cache.user + recurly_token_id = cache.recurly_token_id + subscriptionDetails = cache.subscriptionDetails + logger.log {user_id: user._id, recurly_token_id}, "checking if recurly account exists for user" + RecurlyWrapper.apiRequest({ + url: "accounts/#{user._id}" + method: "GET" + }, (error, response, responseBody) -> + if error + if response.statusCode == 404 # actually not an error in this case, just no existing account + cache.userExists = false + return next(null, cache) + logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while checking account" + return next(error) + logger.log {user_id: user._id, recurly_token_id}, "user appears to exist in recurly" + RecurlyWrapper._parseAccountXml responseBody, (err, account) -> + if err + logger.error {err, user_id: user._id, recurly_token_id}, "error parsing account" + return next(err) + cache.account = account + return next(null, cache) + ) + createAccount: (cache, next) -> + user = cache.user + recurly_token_id = cache.recurly_token_id + subscriptionDetails = cache.subscriptionDetails + if cache.userExists + logger.log {user_id: user._id, recurly_token_id}, "user already exists in recurly" + return next(null, cache) + logger.log {user_id: user._id, recurly_token_id}, "creating user in recurly" + address = subscriptionDetails.address + if !address + return next(new Error('no address in subscriptionDetails at createAccount stage')) + requestBody = """ + + #{user._id} + #{user.email} + #{user.first_name} + #{user.last_name} +
+ #{address.address1} + #{address.address2} + #{address.city || ''} + #{address.state || ''} + #{address.zip || ''} + #{address.country} +
+
+ """ + RecurlyWrapper.apiRequest({ + url : "accounts" + method : "POST" + body : requestBody + }, (error, response, responseBody) => + if error + logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while creating account" + return next(error) + RecurlyWrapper._parseAccountXml responseBody, (err, account) -> + if err + logger.error {err, user_id: user._id, recurly_token_id}, "error creating account" + return next(err) + cache.account = account + return next(null, cache) + ) + createBillingInfo: (cache, next) -> + user = cache.user + recurly_token_id = cache.recurly_token_id + subscriptionDetails = cache.subscriptionDetails + logger.log {user_id: user._id, recurly_token_id}, "creating billing info in recurly" + accountCode = cache?.account?.account_code + if !accountCode + return next(new Error('no account code at createBillingInfo stage')) + requestBody = """ + + #{recurly_token_id} + + """ + RecurlyWrapper.apiRequest({ + url: "accounts/#{accountCode}/billing_info" + method: "POST" + body: requestBody + }, (error, response, responseBody) => + if error + logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while creating billing info" + return next(error) + RecurlyWrapper._parseBillingInfoXml responseBody, (err, billingInfo) -> + if err + logger.error {err, user_id: user._id, accountCode, recurly_token_id}, "error creating billing info" + return next(err) + cache.billingInfo = billingInfo + return next(null, cache) + ) + + setAddress: (cache, next) -> + user = cache.user + recurly_token_id = cache.recurly_token_id + subscriptionDetails = cache.subscriptionDetails + logger.log {user_id: user._id, recurly_token_id}, "setting billing address in recurly" + accountCode = cache?.account?.account_code + if !accountCode + return next(new Error('no account code at setAddress stage')) + address = subscriptionDetails.address + if !address + return next(new Error('no address in subscriptionDetails at setAddress stage')) + requestBody = RecurlyWrapper._addressToXml(address) + RecurlyWrapper.apiRequest({ + url: "accounts/#{accountCode}/billing_info" + method: "PUT" + body: requestBody + }, (error, response, responseBody) => + if error + logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while setting address" + return next(error) + RecurlyWrapper._parseBillingInfoXml responseBody, (err, billingInfo) -> + if err + logger.error {err, user_id: user._id, recurly_token_id}, "error updating billing info" + return next(err) + cache.billingInfo = billingInfo + return next(null, cache) + ) + createSubscription: (cache, next) -> + user = cache.user + recurly_token_id = cache.recurly_token_id + subscriptionDetails = cache.subscriptionDetails + logger.log {user_id: user._id, recurly_token_id}, "creating subscription in recurly" + requestBody = """ + + #{subscriptionDetails.plan_code} + #{subscriptionDetails.currencyCode} + #{subscriptionDetails.coupon_code} + + #{user._id} + + + """ # TODO: check account details and billing + RecurlyWrapper.apiRequest({ + url : "subscriptions" + method : "POST" + body : requestBody + }, (error, response, responseBody) => + if error + logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while creating subscription" + return next(error) + RecurlyWrapper._parseSubscriptionXml responseBody, (err, subscription) -> + if err + logger.error {err, user_id: user._id, recurly_token_id}, "error creating subscription" + return next(err) + cache.subscription = subscription + return next(null, cache) + ) + _createPaypalSubscription: (user, subscriptionDetails, recurly_token_id, callback) -> logger.log {user_id: user._id, recurly_token_id}, "starting process of creating paypal subscription" + cache = {user, recurly_token_id, subscriptionDetails} Async.waterfall([ - (next) -> # check if account exists - logger.log {user_id: user._id, recurly_token_id}, "checking if recurly account exists for user" - RecurlyWrapper.apiRequest({ - url: "accounts/#{user._id}" - method: "GET" - }, (error, response, responseBody) -> - result = {userExists: true} - if error - if response.statusCode == 404 # actually not an error in this case, just no existing account - result.userExists = false - return next(null, result) - logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while checking account" - return next(error) - logger.log {user_id: user._id, recurly_token_id}, "user appears to exist in recurly" - RecurlyWrapper._parseAccountXml responseBody, (err, account) -> - if err - logger.error {err, user_id: user._id, recurly_token_id}, "error parsing account" - return next(err) - result.account = account - return next(null, result) - ) - - , (result, next) -> # create account - if result.userExists - logger.log {user_id: user._id, recurly_token_id}, "user already exists in recurly" - return next(null, result) - logger.log {user_id: user._id, recurly_token_id}, "creating user in recurly" - address = subscriptionDetails.address - if !address - return next(new Error('no address in subscriptionDetails at createAccount stage')) - requestBody = """ - - #{user._id} - #{user.email} - #{user.first_name} - #{user.last_name} -
- #{address.address1} - #{address.address2} - #{address.city || ''} - #{address.state || ''} - #{address.zip || ''} - #{address.country} -
-
- """ - RecurlyWrapper.apiRequest({ - url : "accounts" - method : "POST" - body : requestBody - }, (error, response, responseBody) => - if error - logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while creating account" - return next(error) - RecurlyWrapper._parseAccountXml responseBody, (err, account) -> - if err - logger.error {err, user_id: user._id, recurly_token_id}, "error creating account" - return next(err) - result.account = account - return next(null, result) - ) - - , (result, next) -> # create billing info - logger.log {user_id: user._id, recurly_token_id}, "creating billing info in recurly" - accountCode = result?.account?.account_code - if !accountCode - return next(new Error('no account code at createBillingInfo stage')) - requestBody = """ - - #{recurly_token_id} - - """ - RecurlyWrapper.apiRequest({ - url: "accounts/#{accountCode}/billing_info" - method: "POST" - body: requestBody - }, (error, response, responseBody) => - if error - logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while creating billing info" - return next(error) - RecurlyWrapper._parseBillingInfoXml responseBody, (err, billingInfo) -> - if err - logger.error {err, user_id: user._id, accountCode, recurly_token_id}, "error creating billing info" - return next(err) - result.billingInfo = billingInfo - return next(null, result) - ) - - , (result, next) -> # set address - logger.log {user_id: user._id, recurly_token_id}, "setting billing address in recurly" - accountCode = result?.account?.account_code - if !accountCode - return next(new Error('no account code at setAddress stage')) - address = subscriptionDetails.address - if !address - return next(new Error('no address in subscriptionDetails at setAddress stage')) - requestBody = RecurlyWrapper._addressToXml(address) - RecurlyWrapper.apiRequest({ - url: "accounts/#{accountCode}/billing_info" - method: "PUT" - body: requestBody - }, (error, response, responseBody) => - if error - logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while setting address" - return next(error) - RecurlyWrapper._parseBillingInfoXml responseBody, (err, billingInfo) -> - if err - logger.error {err, user_id: user._id, recurly_token_id}, "error updating billing info" - return next(err) - result.billingInfo = billingInfo - return next(null, result) - ) - - , (result, next) -> # create subscription - logger.log {user_id: user._id, recurly_token_id}, "creating subscription in recurly" - requestBody = """ - - #{subscriptionDetails.plan_code} - #{subscriptionDetails.currencyCode} - #{subscriptionDetails.coupon_code} - - #{user._id} - - - """ # TODO: check account details and billing - RecurlyWrapper.apiRequest({ - url : "subscriptions" - method : "POST" - body : requestBody - }, (error, response, responseBody) => - if error - logger.error {error, user_id: user._id, recurly_token_id}, "error response from recurly while creating subscription" - return next(error) - RecurlyWrapper._parseSubscriptionXml responseBody, (err, subscription) -> - if err - logger.error {err, user_id: user._id, recurly_token_id}, "error creating subscription" - return next(err) - result.subscription = subscription - return next(null, result) - ) - - ], (err, result) -> - if err - logger.error {err, user_id: user._id, recurly_token_id}, "error in paypal subscription creation process" - return callback(err) - if !result.subscription - err = new Error('no subscription object in result') - logger.error {err, user_id: user._id, recurly_token_id}, "error in paypal subscription creation process" - return callback(err) - logger.log {user_id: user._id, recurly_token_id}, "done creating paypal subscription for user" - callback(null, result.subscription) + Async.apply(RecurlyWrapper._paypal.checkAccountExists, cache), + RecurlyWrapper._paypal.createAccount, + RecurlyWrapper._paypal.createBillingInfo, + RecurlyWrapper._paypal.setAddress, + RecurlyWrapper._paypal.createSubscription, + ], (err, result) -> + if err + logger.error {err, user_id: user._id, recurly_token_id}, "error in paypal subscription creation process" + return callback(err) + if !result.subscription + err = new Error('no subscription object in result') + logger.error {err, user_id: user._id, recurly_token_id}, "error in paypal subscription creation process" + return callback(err) + logger.log {user_id: user._id, recurly_token_id}, "done creating paypal subscription for user" + callback(null, result.subscription) ) _createCreditCardSubscription: (user, subscriptionDetails, recurly_token_id, callback) -> diff --git a/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee b/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee index fd8bbe25ed..e9dae2cd66 100644 --- a/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee +++ b/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee @@ -516,6 +516,16 @@ describe "RecurlyWrapper", -> expect(sub).to.equal @subscription done() + it 'should call apiRequest', (done) -> + @call (err, sub) => + @apiRequest.callCount.should.equal 1 + done() + + it 'should call _parseSubscriptionXml', (done) -> + @call (err, sub) => + @_parseSubscriptionXml.callCount.should.equal 1 + done() + describe 'when api request produces an error', -> beforeEach -> @@ -526,6 +536,16 @@ describe "RecurlyWrapper", -> expect(err).to.be.instanceof Error done() + it 'should call apiRequest', (done) -> + @call (err, sub) => + @apiRequest.callCount.should.equal 1 + done() + + it 'should not _parseSubscriptionXml', (done) -> + @call (err, sub) => + @_parseSubscriptionXml.callCount.should.equal 0 + done() + describe 'when parse xml produces an error', -> beforeEach -> @@ -539,7 +559,69 @@ describe "RecurlyWrapper", -> describe '_createPaypalSubscription', -> beforeEach -> + @checkAccountExists = sinon.stub(@RecurlyWrapper._paypal, 'checkAccountExists') + @createAccount = sinon.stub(@RecurlyWrapper._paypal, 'createAccount') + @createBillingInfo = sinon.stub(@RecurlyWrapper._paypal, 'createBillingInfo') + @setAddress = sinon.stub(@RecurlyWrapper._paypal, 'setAddress') + @createSubscription = sinon.stub(@RecurlyWrapper._paypal, 'createSubscription') + @user = + _id: 'some_id' + email: 'user@example.com' + @subscriptionDetails = + currencyCode: "EUR" + plan_code: "some_plan_code" + coupon_code: "" + isPaypal: true + address: + address1: "addr_one" + address2: "addr_two" + country: "some_country" + state: "some_state" + zip: "some_zip" + @subscription = {} + @recurly_token_id = "a-token-id" - describe 'when all goes well', -> + # set up data callbacks + user = @user + subscriptionDetails = @subscriptionDetails + recurly_token_id = @recurly_token_id - beforeEach -> + @checkAccountExists.callsArgWith(1, null, + {user, subscriptionDetails, recurly_token_id, + userExists: false, account: {accountCode: 'xx'}} + ) + + @createAccount.callsArgWith(1, null, + {user, subscriptionDetails, recurly_token_id, + userExists: false, account: {accountCode: 'xx'}} + ) + + @createBillingInfo.callsArgWith(1, null, + {user, subscriptionDetails, recurly_token_id, + userExists: false, account: {accountCode: 'xx'}, billingInfo: {token_id: 'abc'}} + ) + + @setAddress.callsArgWith(1, null, + {user, subscriptionDetails, recurly_token_id, + userExists: false, account: {accountCode: 'xx'}, billingInfo: {token_id: 'abc'}} + ) + + @createSubscription.callsArgWith(1, null, + {user, subscriptionDetails, recurly_token_id, + userExists: false, account: {accountCode: 'xx'}, billingInfo: {token_id: 'abc'}, subscription: {}} + ) + + @call = (callback) => + @RecurlyWrapper._createPaypalSubscription @user, @subscriptionDetails, @recurly_token_id, callback + + afterEach -> + @checkAccountExists.restore() + @createAccount.restore() + @createBillingInfo.restore() + @setAddress.restore() + @createSubscription.restore() + + it 'should not produce an error', (done) -> + @call (err, sub) => + expect(err).to.not.be.instanceof Error + done()