diff --git a/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee b/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee
index 680905ca6c..059c5fb02c 100644
--- a/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee
+++ b/services/web/app/coffee/Features/Subscription/RecurlyWrapper.coffee
@@ -4,11 +4,201 @@ request = require 'request'
Settings = require "settings-sharelatex"
xml2js = require "xml2js"
logger = require("logger-sharelatex")
+Async = require('async')
module.exports = RecurlyWrapper =
apiUrl : "https://api.recurly.com/v2"
- createSubscription: (user, subscriptionDetails, recurly_token_id, callback)->
+ _addressToXml: (address) ->
+ allowedKeys = ['address1', 'address2', 'city', 'country', 'state', 'zip', 'postal_code']
+ resultString = "\n"
+ for k, v of address
+ if k == 'postal_code'
+ k = 'zip'
+ if v and (k in allowedKeys)
+ resultString += "<#{k}#{if k == 'address2' then ' nil="nil"' else ''}>#{v || ''}#{k}>\n"
+ 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.userExists = true
+ cache.account = account
+ return next(null, cache)
+ )
+ createAccount: (cache, next) ->
+ user = cache.user
+ recurly_token_id = cache.recurly_token_id
+ subscriptionDetails = cache.subscriptionDetails
+ address = subscriptionDetails.address
+ if !address
+ return next(new Error('no address in subscriptionDetails at createAccount stage'))
+ 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"
+ 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"
+ # We use `async.waterfall` to run each of these actions in sequence
+ # passing a `cache` object along the way. The cache is initialized
+ # with required data, and `async.apply` to pass the cache to the first function
+ cache = {user, recurly_token_id, subscriptionDetails}
+ Async.waterfall([
+ 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) ->
requestBody = """
#{subscriptionDetails.plan_code}
@@ -25,17 +215,23 @@ module.exports = RecurlyWrapper =
"""
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url : "subscriptions"
method : "POST"
body : requestBody
}, (error, response, responseBody) =>
return callback(error) if error?
- @_parseSubscriptionXml responseBody, callback
- )
+ RecurlyWrapper._parseSubscriptionXml responseBody, callback
+ )
+
+ createSubscription: (user, subscriptionDetails, recurly_token_id, callback)->
+ isPaypal = subscriptionDetails.isPaypal
+ logger.log {user_id: user._id, isPaypal, recurly_token_id}, "setting up subscription in recurly"
+ fn = if isPaypal then RecurlyWrapper._createPaypalSubscription else RecurlyWrapper._createCreditCardSubscription
+ fn user, subscriptionDetails, recurly_token_id, callback
apiRequest : (options, callback) ->
- options.url = @apiUrl + "/" + options.url
+ options.url = RecurlyWrapper.apiUrl + "/" + options.url
options.headers =
"Authorization" : "Basic " + new Buffer(Settings.apis.recurly.apiKey).toString("base64")
"Accept" : "application/xml"
@@ -60,7 +256,7 @@ module.exports = RecurlyWrapper =
newAttributes[key] = value
else
newAttributes[newKey] = value
-
+
return newAttributes
crypto.randomBytes 32, (error, buffer) ->
@@ -74,14 +270,14 @@ module.exports = RecurlyWrapper =
signature = "#{signed}|#{unsignedQuery}"
callback null, signature
-
+
getSubscriptions: (accountId, callback)->
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url: "accounts/#{accountId}/subscriptions"
}, (error, response, body) =>
return callback(error) if error?
- @_parseXml body, callback
+ RecurlyWrapper._parseXml body, callback
)
@@ -94,11 +290,11 @@ module.exports = RecurlyWrapper =
else
url = "subscriptions/#{subscriptionId}"
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url: url
}, (error, response, body) =>
return callback(error) if error?
- @_parseSubscriptionXml body, (error, recurlySubscription) =>
+ RecurlyWrapper._parseSubscriptionXml body, (error, recurlySubscription) =>
return callback(error) if error?
if options.includeAccount
if recurlySubscription.account? and recurlySubscription.account.url?
@@ -106,7 +302,7 @@ module.exports = RecurlyWrapper =
else
return callback "I don't understand the response from Recurly"
- @getAccount accountId, (error, account) ->
+ RecurlyWrapper.getAccount accountId, (error, account) ->
return callback(error) if error?
recurlySubscription.account = account
callback null, recurlySubscription
@@ -124,9 +320,9 @@ module.exports = RecurlyWrapper =
per_page:200
if cursor?
opts.qs.cursor = cursor
- @apiRequest opts, (error, response, body) =>
+ RecurlyWrapper.apiRequest opts, (error, response, body) =>
return callback(error) if error?
- @_parseXml body, (err, data)->
+ RecurlyWrapper._parseXml body, (err, data)->
if err?
logger.err err:err, "could not get accoutns"
callback(err)
@@ -142,19 +338,19 @@ module.exports = RecurlyWrapper =
getAccount: (accountId, callback) ->
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url: "accounts/#{accountId}"
}, (error, response, body) =>
return callback(error) if error?
- @_parseAccountXml body, callback
+ RecurlyWrapper._parseAccountXml body, callback
)
getBillingInfo: (accountId, callback)->
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url: "accounts/#{accountId}/billing_info"
}, (error, response, body) =>
return callback(error) if error?
- @_parseXml body, callback
+ RecurlyWrapper._parseXml body, callback
)
@@ -166,13 +362,13 @@ module.exports = RecurlyWrapper =
#{options.timeframe}
"""
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url : "subscriptions/#{subscriptionId}"
method : "put"
body : requestBody
}, (error, response, responseBody) =>
return callback(error) if error?
- @_parseSubscriptionXml responseBody, callback
+ RecurlyWrapper._parseSubscriptionXml responseBody, callback
)
createFixedAmmountCoupon: (coupon_code, name, currencyCode, discount_in_cents, plan_code, callback)->
@@ -191,7 +387,7 @@ module.exports = RecurlyWrapper =
"""
logger.log coupon_code:coupon_code, requestBody:requestBody, "creating coupon"
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url : "coupons"
method : "post"
body : requestBody
@@ -203,16 +399,16 @@ module.exports = RecurlyWrapper =
lookupCoupon: (coupon_code, callback)->
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url: "coupons/#{coupon_code}"
}, (error, response, body) =>
return callback(error) if error?
- @_parseXml body, callback
+ RecurlyWrapper._parseXml body, callback
)
cancelSubscription: (subscriptionId, callback) ->
logger.log subscriptionId:subscriptionId, "telling recurly to cancel subscription"
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url: "subscriptions/#{subscriptionId}/cancel",
method: "put"
}, (error, response, body) ->
@@ -221,13 +417,13 @@ module.exports = RecurlyWrapper =
reactivateSubscription: (subscriptionId, callback) ->
logger.log subscriptionId:subscriptionId, "telling recurly to reactivating subscription"
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url: "subscriptions/#{subscriptionId}/reactivate",
method: "put"
}, (error, response, body) ->
callback(error)
)
-
+
redeemCoupon: (account_code, coupon_code, callback)->
requestBody = """
@@ -237,7 +433,7 @@ module.exports = RecurlyWrapper =
"""
logger.log account_code:account_code, coupon_code:coupon_code, requestBody:requestBody, "redeeming coupon for user"
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url : "coupons/#{coupon_code}/redeem"
method : "post"
body : requestBody
@@ -251,7 +447,7 @@ module.exports = RecurlyWrapper =
next_renewal_date = new Date()
next_renewal_date.setDate(next_renewal_date.getDate() + daysUntilExpire)
logger.log subscriptionId:subscriptionId, daysUntilExpire:daysUntilExpire, "Exending Free trial for user"
- @apiRequest({
+ RecurlyWrapper.apiRequest({
url : "/subscriptions/#{subscriptionId}/postpone?next_renewal_date=#{next_renewal_date}&bulk=false"
method : "put"
}, (error, response, responseBody) =>
@@ -261,7 +457,7 @@ module.exports = RecurlyWrapper =
)
_parseSubscriptionXml: (xml, callback) ->
- @_parseXml xml, (error, data) ->
+ RecurlyWrapper._parseXml xml, (error, data) ->
return callback(error) if error?
if data? and data.subscription?
recurlySubscription = data.subscription
@@ -270,7 +466,7 @@ module.exports = RecurlyWrapper =
callback null, recurlySubscription
_parseAccountXml: (xml, callback) ->
- @_parseXml xml, (error, data) ->
+ RecurlyWrapper._parseXml xml, (error, data) ->
return callback(error) if error?
if data? and data.account?
account = data.account
@@ -278,6 +474,15 @@ module.exports = RecurlyWrapper =
return callback "I don't understand the response from Recurly"
callback null, account
+ _parseBillingInfoXml: (xml, callback) ->
+ RecurlyWrapper._parseXml xml, (error, data) ->
+ return callback(error) if error?
+ if data? and data.billing_info?
+ billingInfo = data.billing_info
+ else
+ return callback "I don't understand the response from Recurly"
+ callback null, billingInfo
+
_parseXml: (xml, callback) ->
convertDataTypes = (data) ->
if data? and data["$"]?
@@ -299,7 +504,7 @@ module.exports = RecurlyWrapper =
else
array.push(convertDataTypes(value))
data = array
-
+
if data instanceof Array
data = (convertDataTypes(entry) for entry in data)
else if typeof data == "object"
@@ -315,6 +520,3 @@ module.exports = RecurlyWrapper =
return callback(error) if error?
result = convertDataTypes(data)
callback null, result
-
-
-
diff --git a/services/web/public/coffee/main/new-subscription.coffee b/services/web/public/coffee/main/new-subscription.coffee
index 404b512e7b..93c2c0a7cc 100644
--- a/services/web/public/coffee/main/new-subscription.coffee
+++ b/services/web/public/coffee/main/new-subscription.coffee
@@ -115,6 +115,13 @@ define [
currencyCode:pricing.items.currency
plan_code:pricing.items.plan.code
coupon_code:pricing.items?.coupon?.code || ""
+ isPaypal: $scope.paymentMethod == 'paypal'
+ address:
+ address1: $scope.data.address1
+ address2: $scope.data.address2
+ country: $scope.data.country
+ state: $scope.data.state
+ postal_code: $scope.data.postal_code
$http.post("/user/subscription/create", postData)
.success (data, status, headers)->
sixpack.convert "in-editor-free-trial-plan", pricing.items.plan.code, (err)->
diff --git a/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee b/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee
index e05040772d..5a5e9fc40c 100644
--- a/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee
+++ b/services/web/test/UnitTests/coffee/Subscription/RecurlyWrapperTests.coffee
@@ -1,9 +1,10 @@
should = require('chai').should()
+expect = require('chai').expect
sinon = require 'sinon'
crypto = require 'crypto'
querystring = require 'querystring'
-RecurlyWrapper = require "../../../../app/js/Features/Subscription/RecurlyWrapper"
-Settings = require "settings-sharelatex"
+modulePath = "../../../../app/js/Features/Subscription/RecurlyWrapper"
+SandboxedModule = require('sandboxed-module')
tk = require("timekeeper")
fixtures =
@@ -97,22 +98,37 @@ mockApiRequest = (options, callback) ->
describe "RecurlyWrapper", ->
- beforeEach ->
- Settings.plans = [{
- planCode: "collaborator"
- name: "Collaborator"
- features:
- collaborators: -1
- versioning: true
- }]
- Settings.defaultPlanCode =
- collaborators: 0
- versioning: false
+
+ before ->
+ @settings =
+ plans: [{
+ planCode: "collaborator"
+ name: "Collaborator"
+ features:
+ collaborators: -1
+ versioning: true
+ }]
+ defaultPlanCode:
+ collaborators: 0
+ versioning: false
+ apis:
+ recurly:
+ apiKey: 'nonsense'
+ privateKey: 'private_nonsense'
+
+ @RecurlyWrapper = RecurlyWrapper = SandboxedModule.require modulePath, requires:
+ "settings-sharelatex": @settings
+ "logger-sharelatex":
+ err: sinon.stub()
+ error: sinon.stub()
+ log: sinon.stub()
+ "request": sinon.stub()
describe "sign", ->
+
before (done) ->
tk.freeze Date.now() # freeze the time for these tests
- RecurlyWrapper.sign({
+ @RecurlyWrapper.sign({
subscription :
plan_code : "gold"
name : "$$$"
@@ -127,7 +143,7 @@ describe "RecurlyWrapper", ->
it "should be signed correctly", ->
signed = @signature.split("|")[0]
query = @signature.split("|")[1]
- crypto.createHmac("sha1", Settings.apis.recurly.privateKey).update(query).digest("hex").should.equal signed
+ crypto.createHmac("sha1", @settings.apis.recurly.privateKey).update(query).digest("hex").should.equal signed
it "should be url escaped", ->
query = @signature.split("|")[1]
@@ -149,38 +165,39 @@ describe "RecurlyWrapper", ->
describe "_parseXml", ->
it "should convert different data types into correct representations", (done) ->
- xml =
- "" +
- "" +
- " " +
- " " +
- " gold" +
- " Gold plan" +
- " " +
- " 44f83d7cba354d5b84812419f923ea96" +
- " active" +
- " 800" +
- " EUR" +
- " 1" +
- " 2011-05-27T07:00:00Z" +
- " " +
- " " +
- " 2011-06-27T07:00:00Z" +
- " 2011-07-27T07:00:00Z" +
- " " +
- " " +
- " " +
- " " +
- " ipaddresses" +
- " 10" +
- " 150" +
- " " +
- " " +
- " " +
- " " +
- " " +
- ""
- RecurlyWrapper._parseXml xml, (error, data) ->
+ xml = """
+
+
+
+
+ gold
+ Gold plan
+
+ 44f83d7cba354d5b84812419f923ea96
+ active
+ 800
+ EUR
+ 1
+ 2011-05-27T07:00:00Z
+
+
+ 2011-06-27T07:00:00Z
+ 2011-07-27T07:00:00Z
+
+
+
+
+ ipaddresses
+ 10
+ 150
+
+
+
+
+
+
+ """
+ @RecurlyWrapper._parseXml xml, (error, data) ->
data.subscription.plan.plan_code.should.equal "gold"
data.subscription.plan.name.should.equal "Gold plan"
data.subscription.uuid.should.equal "44f83d7cba354d5b84812419f923ea96"
@@ -188,32 +205,37 @@ describe "RecurlyWrapper", ->
data.subscription.unit_amount_in_cents.should.equal 800
data.subscription.currency.should.equal "EUR"
data.subscription.quantity.should.equal 1
+
data.subscription.activated_at.should.deep.equal new Date("2011-05-27T07:00:00Z")
should.equal data.subscription.canceled_at, null
should.equal data.subscription.expires_at, null
+
data.subscription.current_period_started_at.should.deep.equal new Date("2011-06-27T07:00:00Z")
+
data.subscription.current_period_ends_at.should.deep.equal new Date("2011-07-27T07:00:00Z")
should.equal data.subscription.trial_started_at, null
should.equal data.subscription.trial_ends_at, null
- data.subscription.subscription_add_ons.should.deep.equal [{
+
+ data.subscription.subscription_add_ons[0].should.deep.equal {
add_on_code: "ipaddresses"
quantity: "10"
unit_amount_in_cents: "150"
- }]
+ }
data.subscription.account.url.should.equal "https://api.recurly.com/v2/accounts/1"
data.subscription.url.should.equal "https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96"
data.subscription.plan.url.should.equal "https://api.recurly.com/v2/plans/gold"
done()
-
+
describe "getSubscription", ->
+
describe "with proper subscription id", ->
before ->
- @apiRequest = sinon.stub(RecurlyWrapper, "apiRequest", mockApiRequest)
- RecurlyWrapper.getSubscription "44f83d7cba354d5b84812419f923ea96", (error, recurlySubscription) =>
+ @apiRequest = sinon.stub(@RecurlyWrapper, "apiRequest", mockApiRequest)
+ @RecurlyWrapper.getSubscription "44f83d7cba354d5b84812419f923ea96", (error, recurlySubscription) =>
@recurlySubscription = recurlySubscription
after ->
- RecurlyWrapper.apiRequest.restore()
-
+ @RecurlyWrapper.apiRequest.restore()
+
it "should look up the subscription at the normal API end point", ->
@apiRequest.args[0][0].url.should.equal "subscriptions/44f83d7cba354d5b84812419f923ea96"
@@ -222,12 +244,12 @@ describe "RecurlyWrapper", ->
describe "with ReculyJS token", ->
before ->
- @apiRequest = sinon.stub(RecurlyWrapper, "apiRequest", mockApiRequest)
- RecurlyWrapper.getSubscription "70db44b10f5f4b238669480c9903f6f5", {recurlyJsResult: true}, (error, recurlySubscription) =>
+ @apiRequest = sinon.stub(@RecurlyWrapper, "apiRequest", mockApiRequest)
+ @RecurlyWrapper.getSubscription "70db44b10f5f4b238669480c9903f6f5", {recurlyJsResult: true}, (error, recurlySubscription) =>
@recurlySubscription = recurlySubscription
after ->
- RecurlyWrapper.apiRequest.restore()
-
+ @RecurlyWrapper.apiRequest.restore()
+
it "should return the subscription", ->
@recurlySubscription.uuid.should.equal "44f83d7cba354d5b84812419f923ea96"
@@ -236,30 +258,30 @@ describe "RecurlyWrapper", ->
describe "with includeAccount", ->
beforeEach ->
- @apiRequest = sinon.stub(RecurlyWrapper, "apiRequest", mockApiRequest)
- RecurlyWrapper.getSubscription "44f83d7cba354d5b84812419f923ea96", {includeAccount: true}, (error, recurlySubscription) =>
+ @apiRequest = sinon.stub(@RecurlyWrapper, "apiRequest", mockApiRequest)
+ @RecurlyWrapper.getSubscription "44f83d7cba354d5b84812419f923ea96", {includeAccount: true}, (error, recurlySubscription) =>
@recurlySubscription = recurlySubscription
afterEach ->
- RecurlyWrapper.apiRequest.restore()
+ @RecurlyWrapper.apiRequest.restore()
it "should request the account from the API", ->
@apiRequest.args[1][0].url.should.equal "accounts/104"
-
+
it "should populate the account attribute", ->
@recurlySubscription.account.account_code.should.equal "104"
-
+
describe "updateSubscription", ->
beforeEach (done) ->
@recurlySubscriptionId = "subscription-id-123"
- @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) =>
+ @apiRequest = sinon.stub @RecurlyWrapper, "apiRequest", (options, callback) =>
@requestOptions = options
callback null, {}, fixtures["subscriptions/44f83d7cba354d5b84812419f923ea96"]
- RecurlyWrapper.updateSubscription @recurlySubscriptionId, { plan_code : "silver", timeframe: "now" }, (error, recurlySubscription) =>
+ @RecurlyWrapper.updateSubscription @recurlySubscriptionId, { plan_code : "silver", timeframe: "now" }, (error, recurlySubscription) =>
@recurlySubscription = recurlySubscription
done()
afterEach ->
- RecurlyWrapper.apiRequest.restore()
+ @RecurlyWrapper.apiRequest.restore()
it "should send an update request to the API", ->
@apiRequest.called.should.equal true
@@ -275,59 +297,723 @@ describe "RecurlyWrapper", ->
it "should return the updated subscription", ->
should.exist @recurlySubscription
@recurlySubscription.plan.plan_code.should.equal "gold"
-
+
describe "cancelSubscription", ->
beforeEach (done) ->
@recurlySubscriptionId = "subscription-id-123"
- @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) =>
+ @apiRequest = sinon.stub @RecurlyWrapper, "apiRequest", (options, callback) =>
options.url.should.equal "subscriptions/#{@recurlySubscriptionId}/cancel"
options.method.should.equal "put"
callback()
- RecurlyWrapper.cancelSubscription(@recurlySubscriptionId, done)
+ @RecurlyWrapper.cancelSubscription(@recurlySubscriptionId, done)
afterEach ->
- RecurlyWrapper.apiRequest.restore()
+ @RecurlyWrapper.apiRequest.restore()
it "should send a cancel request to the API", ->
@apiRequest.called.should.equal true
-
+
describe "reactivateSubscription", ->
beforeEach (done) ->
@recurlySubscriptionId = "subscription-id-123"
- @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) =>
+ @apiRequest = sinon.stub @RecurlyWrapper, "apiRequest", (options, callback) =>
options.url.should.equal "subscriptions/#{@recurlySubscriptionId}/reactivate"
options.method.should.equal "put"
callback()
- RecurlyWrapper.reactivateSubscription(@recurlySubscriptionId, done)
+ @RecurlyWrapper.reactivateSubscription(@recurlySubscriptionId, done)
afterEach ->
- RecurlyWrapper.apiRequest.restore()
+ @RecurlyWrapper.apiRequest.restore()
it "should send a cancel request to the API", ->
@apiRequest.called.should.equal true
-
-
+
+
describe "redeemCoupon", ->
beforeEach (done) ->
@recurlyAccountId = "account-id-123"
@coupon_code = "312321312"
- @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) =>
+ @apiRequest = sinon.stub @RecurlyWrapper, "apiRequest", (options, callback) =>
options.url.should.equal "coupons/#{@coupon_code}/redeem"
options.body.indexOf("#{@recurlyAccountId}").should.not.equal -1
options.body.indexOf("USD").should.not.equal -1
options.method.should.equal "post"
callback()
- RecurlyWrapper.redeemCoupon(@recurlyAccountId, @coupon_code, done)
+ @RecurlyWrapper.redeemCoupon(@recurlyAccountId, @coupon_code, done)
afterEach ->
- RecurlyWrapper.apiRequest.restore()
+ @RecurlyWrapper.apiRequest.restore()
it "should send the request to redem the coupon", ->
@apiRequest.called.should.equal true
-
+
+ describe "_addressToXml", ->
+
+ beforeEach ->
+ @address =
+ address1: "addr_one"
+ address2: "addr_two"
+ country: "some_country"
+ state: "some_state"
+ postal_code: "some_zip"
+ nonsenseKey: "rubbish"
+
+ it 'should generate the correct xml', () ->
+ result = @RecurlyWrapper._addressToXml @address
+ should.equal(
+ result,
+ """
+
+ addr_one
+ addr_two
+ some_country
+ some_state
+ some_zip
+ \n
+ """
+ )
+
+ describe 'createSubscription', ->
+
+ beforeEach ->
+ @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"
+ @call = (callback) =>
+ @RecurlyWrapper.createSubscription(@user, @subscriptionDetails, @recurly_token_id, callback)
+ describe 'when paypal', ->
+ beforeEach ->
+ @subscriptionDetails.isPaypal = true
+ @_createPaypalSubscription = sinon.stub(@RecurlyWrapper, '_createPaypalSubscription')
+ @_createPaypalSubscription.callsArgWith(3, null, @subscription)
+
+ afterEach ->
+ @_createPaypalSubscription.restore()
+
+ it 'should not produce an error', (done) ->
+ @call (err, sub) =>
+ expect(err).to.equal null
+ expect(err).to.not.be.instanceof Error
+ done()
+
+ it 'should produce a subscription object', (done) ->
+ @call (err, sub) =>
+ expect(sub).to.deep.equal @subscription
+ done()
+
+ it 'should call _createPaypalSubscription', (done) ->
+ @call (err, sub) =>
+ @_createPaypalSubscription.callCount.should.equal 1
+ done()
+
+ describe "when _createPaypalSubscription produces an error", ->
+
+ beforeEach ->
+ @_createPaypalSubscription.callsArgWith(3, new Error('woops'))
+
+ it 'should produce an error', (done) ->
+ @call (err, sub) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ describe 'when not paypal', ->
+
+ beforeEach ->
+ @subscriptionDetails.isPaypal = false
+ @_createCreditCardSubscription = sinon.stub(@RecurlyWrapper, '_createCreditCardSubscription')
+ @_createCreditCardSubscription.callsArgWith(3, null, @subscription)
+
+ afterEach ->
+ @_createCreditCardSubscription.restore()
+
+ it 'should not produce an error', (done) ->
+ @call (err, sub) =>
+ expect(err).to.equal null
+ expect(err).to.not.be.instanceof Error
+ done()
+
+ it 'should produce a subscription object', (done) ->
+ @call (err, sub) =>
+ expect(sub).to.deep.equal @subscription
+ done()
+
+ it 'should call _createCreditCardSubscription', (done) ->
+ @call (err, sub) =>
+ @_createCreditCardSubscription.callCount.should.equal 1
+ done()
+
+ describe "when _createCreditCardSubscription produces an error", ->
+
+ beforeEach ->
+ @_createCreditCardSubscription.callsArgWith(3, new Error('woops'))
+
+ it 'should produce an error', (done) ->
+ @call (err, sub) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+
+ describe '_createCreditCardSubscription', ->
+
+ beforeEach ->
+ @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"
+ @apiRequest = sinon.stub(@RecurlyWrapper, 'apiRequest')
+ @response =
+ statusCode: 200
+ @body = "is_bad"
+ @apiRequest.callsArgWith(1, null, @response, @body)
+ @_parseSubscriptionXml = sinon.stub(@RecurlyWrapper, '_parseSubscriptionXml')
+ @_parseSubscriptionXml.callsArgWith(1, null, @subscription)
+ @call = (callback) =>
+ @RecurlyWrapper._createCreditCardSubscription(@user, @subscriptionDetails, @recurly_token_id, callback)
+
+ afterEach ->
+ @apiRequest.restore()
+ @_parseSubscriptionXml.restore()
+
+ it 'should not produce an error', (done) ->
+ @call (err, sub) =>
+ expect(err).to.not.be.instanceof Error
+ expect(err).to.equal null
+ done()
+
+ it 'should produce a subscription', (done) ->
+ @call (err, sub) =>
+ 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 ->
+ @apiRequest.callsArgWith(1, new Error('woops'))
+
+ it 'should produce an error', (done) ->
+ @call (err, sub) =>
+ 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 ->
+ @_parseSubscriptionXml.callsArgWith(1, new Error('woops'))
+
+ it 'should produce an error', (done) ->
+ @call (err, sub) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ 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"
+
+ # set up data callbacks
+ user = @user
+ subscriptionDetails = @subscriptionDetails
+ recurly_token_id = @recurly_token_id
+
+ @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: @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()
+
+ it 'should produce a subscription object', (done) ->
+ @call (err, sub) =>
+ expect(sub).to.not.equal null
+ expect(sub).to.equal @subscription
+ done()
+
+ it 'should call each of the paypal stages', (done) ->
+ @call (err, sub) =>
+ @checkAccountExists.callCount.should.equal 1
+ @createAccount.callCount.should.equal 1
+ @createBillingInfo.callCount.should.equal 1
+ @setAddress.callCount.should.equal 1
+ @createSubscription.callCount.should.equal 1
+ done()
+
+ describe 'when one of the paypal stages produces an error', ->
+
+ beforeEach ->
+ @createAccount.callsArgWith(1, new Error('woops'))
+
+ it 'should produce an error', (done) ->
+ @call (err, sub) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ it 'should stop calling the paypal stages after the error', (done) ->
+ @call (err, sub) =>
+ @checkAccountExists.callCount.should.equal 1
+ @createAccount.callCount.should.equal 1
+ @createBillingInfo.callCount.should.equal 0
+ @setAddress.callCount.should.equal 0
+ @createSubscription.callCount.should.equal 0
+ done()
+
+ describe 'paypal actions', ->
+
+ beforeEach ->
+ @apiRequest = sinon.stub(@RecurlyWrapper, 'apiRequest')
+ @_parseAccountXml = sinon.spy(@RecurlyWrapper, '_parseAccountXml')
+ @_parseBillingInfoXml = sinon.spy(@RecurlyWrapper, '_parseBillingInfoXml')
+ @_parseSubscriptionXml = sinon.spy(@RecurlyWrapper, '_parseSubscriptionXml')
+ @cache =
+ user: @user = {_id: 'some_id'}
+ recurly_token_id: @recurly_token_id = "some_token"
+ subscriptionDetails: @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"
+
+ afterEach ->
+ @apiRequest.restore()
+ @_parseAccountXml.restore()
+ @_parseBillingInfoXml.restore()
+ @_parseSubscriptionXml.restore()
+
+ describe '_paypal.checkAccountExists', ->
+
+ beforeEach ->
+ @call = (callback) =>
+ @RecurlyWrapper._paypal.checkAccountExists @cache, callback
+
+ describe 'when the account exists', ->
+
+ beforeEach ->
+ resultXml = 'abc'
+ @apiRequest.callsArgWith(1, null, {statusCode: 200}, resultXml)
+
+ it 'should not produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.not.be.instanceof Error
+ done()
+
+ it 'should call apiRequest', (done) ->
+ @call (err, result) =>
+ @apiRequest.callCount.should.equal 1
+ done()
+
+ it 'should call _parseAccountXml', (done) ->
+ @call (err, result) =>
+ @RecurlyWrapper._parseAccountXml.callCount.should.equal 1
+ done()
+
+ it 'should add the account to the cumulative result', (done) ->
+ @call (err, result) =>
+ expect(result.account).to.not.equal null
+ expect(result.account).to.not.equal undefined
+ expect(result.account).to.deep.equal {
+ account_code: 'abc'
+ }
+ done()
+
+ it 'should set userExists to true', (done) ->
+ @call (err, result) =>
+ expect(result.userExists).to.equal true
+ done()
+
+ describe 'when the account does not exist', ->
+
+ beforeEach ->
+ @apiRequest.callsArgWith(1, new Error('not found'), {statusCode: 404}, '')
+
+ it 'should not produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.not.be.instanceof Error
+ done()
+
+ it 'should call apiRequest', (done) ->
+ @call (err, result) =>
+ @apiRequest.callCount.should.equal 1
+ @apiRequest.firstCall.args[0].method.should.equal 'GET'
+ done()
+
+ it 'should not call _parseAccountXml', (done) ->
+ @call (err, result) =>
+ @RecurlyWrapper._parseAccountXml.callCount.should.equal 0
+ done()
+
+ it 'should not add the account to result', (done) ->
+ @call (err, result) =>
+ expect(result.account).to.equal undefined
+ done()
+
+ it 'should set userExists to false', (done) ->
+ @call (err, result) =>
+ expect(result.userExists).to.equal false
+ done()
+
+ describe 'when apiRequest produces an error', ->
+
+ beforeEach ->
+ @apiRequest.callsArgWith(1, new Error('woops'), {statusCode: 500})
+
+ it 'should produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ describe '_paypal.createAccount', ->
+
+ beforeEach ->
+ @call = (callback) =>
+ @RecurlyWrapper._paypal.createAccount @cache, callback
+
+ describe 'when address is missing from subscriptionDetails', ->
+
+ beforeEach ->
+ @cache.subscriptionDetails.address = null
+
+ it 'should produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ describe 'when account already exists', ->
+
+ beforeEach ->
+ @cache.userExists = true
+ @cache.account =
+ account_code: 'abc'
+
+ it 'should not produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.not.be.instanceof Error
+ done()
+
+ it 'should produce cache object', (done) ->
+ @call (err, result) =>
+ expect(result).to.deep.equal @cache
+ expect(result.account).to.deep.equal {
+ account_code: 'abc'
+ }
+ done()
+
+ it 'should not call apiRequest', (done) ->
+ @call (err, result) =>
+ @apiRequest.callCount.should.equal 0
+ done()
+
+ it 'should not call _parseAccountXml', (done) ->
+ @call (err, result) =>
+ @RecurlyWrapper._parseAccountXml.callCount.should.equal 0
+ done()
+
+ describe 'when account does not exist', ->
+
+ beforeEach ->
+ @cache.userExists = false
+ resultXml = 'abc'
+ @apiRequest.callsArgWith(1, null, {statusCode: 200}, resultXml)
+
+ it 'should not produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.not.be.instanceof Error
+ done()
+
+ it 'should call apiRequest', (done) ->
+ @call (err, result) =>
+ @apiRequest.callCount.should.equal 1
+ @apiRequest.firstCall.args[0].method.should.equal 'POST'
+ done()
+
+ it 'should call _parseAccountXml', (done) ->
+ @call (err, result) =>
+ @RecurlyWrapper._parseAccountXml.callCount.should.equal 1
+ done()
+
+ describe 'when apiRequest produces an error', ->
+
+ beforeEach ->
+ @apiRequest.callsArgWith(1, new Error('woops'), {statusCode: 500})
+
+ it 'should produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ describe '_paypal.createBillingInfo', ->
+
+ beforeEach ->
+ @cache.account =
+ account_code: 'abc'
+ @call = (callback) =>
+ @RecurlyWrapper._paypal.createBillingInfo @cache, callback
+
+ describe 'when account_code is missing from cache', ->
+
+ beforeEach ->
+ @cache.account.account_code = null
+
+ it 'should produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ describe 'when all goes well', ->
+
+ beforeEach ->
+ resultXml = '1'
+ @apiRequest.callsArgWith(1, null, {statusCode: 200}, resultXml)
+
+ it 'should not produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.not.be.instanceof Error
+ done()
+
+ it 'should call apiRequest', (done) ->
+ @call (err, result) =>
+ @apiRequest.callCount.should.equal 1
+ @apiRequest.firstCall.args[0].method.should.equal 'POST'
+ done()
+
+ it 'should call _parseBillingInfoXml', (done) ->
+ @call (err, result) =>
+ @RecurlyWrapper._parseBillingInfoXml.callCount.should.equal 1
+ done()
+
+ it 'should set billingInfo on cache', (done) ->
+ @call (err, result) =>
+ expect(result.billingInfo).to.deep.equal {
+ a: "1"
+ }
+ done()
+
+ describe 'when apiRequest produces an error', ->
+
+ beforeEach ->
+ @apiRequest.callsArgWith(1, new Error('woops'), {statusCode: 500})
+
+ it 'should produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ describe '_paypal.setAddress', ->
+
+ beforeEach ->
+ @cache.account =
+ account_code: 'abc'
+ @cache.billingInfo = {}
+ @call = (callback) =>
+ @RecurlyWrapper._paypal.setAddress @cache, callback
+
+ describe 'when account_code is missing from cache', ->
+
+ beforeEach ->
+ @cache.account.account_code = null
+
+ it 'should produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ describe 'when address is missing from subscriptionDetails', ->
+
+ beforeEach ->
+ @cache.subscriptionDetails.address = null
+
+ it 'should produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ describe 'when all goes well', ->
+
+ beforeEach ->
+ resultXml = 'London'
+ @apiRequest.callsArgWith(1, null, {statusCode: 200}, resultXml)
+
+ it 'should not produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.not.be.instanceof Error
+ done()
+
+ it 'should call apiRequest', (done) ->
+ @call (err, result) =>
+ @apiRequest.callCount.should.equal 1
+ @apiRequest.firstCall.args[0].method.should.equal 'PUT'
+ done()
+
+ it 'should call _parseBillingInfoXml', (done) ->
+ @call (err, result) =>
+ @RecurlyWrapper._parseBillingInfoXml.callCount.should.equal 1
+ done()
+
+ it 'should set billingInfo on cache', (done) ->
+ @call (err, result) =>
+ expect(result.billingInfo).to.deep.equal {
+ city: 'London'
+ }
+ done()
+
+ describe 'when apiRequest produces an error', ->
+
+ beforeEach ->
+ @apiRequest.callsArgWith(1, new Error('woops'), {statusCode: 500})
+
+ it 'should produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.be.instanceof Error
+ done()
+
+ describe '_paypal.createSubscription', ->
+
+ beforeEach ->
+ @cache.account =
+ account_code: 'abc'
+ @cache.billingInfo = {}
+ @call = (callback) =>
+ @RecurlyWrapper._paypal.createSubscription @cache, callback
+
+ describe 'when all goes well', ->
+
+ beforeEach ->
+ resultXml = '1'
+ @apiRequest.callsArgWith(1, null, {statusCode: 200}, resultXml)
+
+ it 'should not produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.not.be.instanceof Error
+ done()
+
+ it 'should call apiRequest', (done) ->
+ @call (err, result) =>
+ @apiRequest.callCount.should.equal 1
+ @apiRequest.firstCall.args[0].method.should.equal 'POST'
+ done()
+
+ it 'should call _parseSubscriptionXml', (done) ->
+ @call (err, result) =>
+ @RecurlyWrapper._parseSubscriptionXml.callCount.should.equal 1
+ done()
+
+ it 'should set subscription on cache', (done) ->
+ @call (err, result) =>
+ expect(result.subscription).to.deep.equal {
+ a: "1"
+ }
+ done()
+
+ describe 'when apiRequest produces an error', ->
+
+ beforeEach ->
+ @apiRequest.callsArgWith(1, new Error('woops'), {statusCode: 500})
+
+ it 'should produce an error', (done) ->
+ @call (err, result) =>
+ expect(err).to.be.instanceof Error
+ done()