RecurlyWrapperTests.js 47 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570
  1. /* eslint-disable
  2. camelcase,
  3. handle-callback-err,
  4. max-len,
  5. no-return-assign,
  6. no-unused-vars,
  7. standard/no-callback-literal,
  8. */
  9. // TODO: This file was created by bulk-decaffeinate.
  10. // Fix any style issues and re-enable lint.
  11. /*
  12. * decaffeinate suggestions:
  13. * DS102: Remove unnecessary code created because of implicit returns
  14. * Full docs: https://github.com/decaffeinate/decaffeinate/blob/master/docs/suggestions.md
  15. */
  16. const should = require('chai').should()
  17. const { expect } = require('chai')
  18. const sinon = require('sinon')
  19. const crypto = require('crypto')
  20. const querystring = require('querystring')
  21. const modulePath = '../../../../app/src/Features/Subscription/RecurlyWrapper'
  22. const SandboxedModule = require('sandboxed-module')
  23. const tk = require('timekeeper')
  24. const fixtures = {
  25. 'subscriptions/44f83d7cba354d5b84812419f923ea96':
  26. '<?xml version="1.0" encoding="UTF-8"?>' +
  27. '<subscription href="https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96">' +
  28. ' <account href="https://api.recurly.com/v2/accounts/104"/>' +
  29. ' <plan href="https://api.recurly.com/v2/plans/gold">' +
  30. ' <plan_code>gold</plan_code>' +
  31. ' <name>Gold plan</name>' +
  32. ' </plan>' +
  33. ' <uuid>44f83d7cba354d5b84812419f923ea96</uuid>' +
  34. ' <state>active</state>' +
  35. ' <unit_amount_in_cents type="integer">800</unit_amount_in_cents>' +
  36. ' <currency>EUR</currency>' +
  37. ' <quantity type="integer">1</quantity>' +
  38. ' <activated_at type="datetime">2011-05-27T07:00:00Z</activated_at>' +
  39. ' <canceled_at nil="nil"></canceled_at>' +
  40. ' <expires_at nil="nil"></expires_at>' +
  41. ' <current_period_started_at type="datetime">2011-06-27T07:00:00Z</current_period_started_at>' +
  42. ' <current_period_ends_at type="datetime">2011-07-27T07:00:00Z</current_period_ends_at>' +
  43. ' <trial_started_at nil="nil"></trial_started_at>' +
  44. ' <trial_ends_at nil="nil"></trial_ends_at>' +
  45. ' <subscription_add_ons type="array">' +
  46. ' <subscription_add_on>' +
  47. ' <add_on_code>ipaddresses</add_on_code>' +
  48. ' <quantity>10</quantity>' +
  49. ' <unit_amount_in_cents>150</unit_amount_in_cents>' +
  50. ' </subscription_add_on>' +
  51. ' </subscription_add_ons>' +
  52. ' <a name="cancel" href="https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/cancel" method="put"/>' +
  53. ' <a name="terminate" href="https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/terminate" method="put"/>' +
  54. ' <a name="postpone" href="https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/postpone" method="put"/>' +
  55. '</subscription>',
  56. 'recurly_js/result/70db44b10f5f4b238669480c9903f6f5':
  57. '<?xml version="1.0" encoding="UTF-8"?>' +
  58. '<subscription href="https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96">' +
  59. ' <account href="https://api.recurly.com/v2/accounts/104"/>' +
  60. ' <plan href="https://api.recurly.com/v2/plans/gold">' +
  61. ' <plan_code>gold</plan_code>' +
  62. ' <name>Gold plan</name>' +
  63. ' </plan>' +
  64. ' <uuid>44f83d7cba354d5b84812419f923ea96</uuid>' +
  65. ' <state>active</state>' +
  66. ' <unit_amount_in_cents type="integer">800</unit_amount_in_cents>' +
  67. ' <currency>EUR</currency>' +
  68. ' <quantity type="integer">1</quantity>' +
  69. ' <activated_at type="datetime">2011-05-27T07:00:00Z</activated_at>' +
  70. ' <canceled_at nil="nil"></canceled_at>' +
  71. ' <expires_at nil="nil"></expires_at>' +
  72. ' <current_period_started_at type="datetime">2011-06-27T07:00:00Z</current_period_started_at>' +
  73. ' <current_period_ends_at type="datetime">2011-07-27T07:00:00Z</current_period_ends_at>' +
  74. ' <trial_started_at nil="nil"></trial_started_at>' +
  75. ' <trial_ends_at nil="nil"></trial_ends_at>' +
  76. ' <subscription_add_ons type="array">' +
  77. ' <subscription_add_on>' +
  78. ' <add_on_code>ipaddresses</add_on_code>' +
  79. ' <quantity>10</quantity>' +
  80. ' <unit_amount_in_cents>150</unit_amount_in_cents>' +
  81. ' </subscription_add_on>' +
  82. ' </subscription_add_ons>' +
  83. ' <a name="cancel" href="https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/cancel" method="put"/>' +
  84. ' <a name="terminate" href="https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/terminate" method="put"/>' +
  85. ' <a name="postpone" href="https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/postpone" method="put"/>' +
  86. '</subscription>',
  87. 'accounts/104':
  88. '<?xml version="1.0" encoding="UTF-8"?>' +
  89. '<account href="https://api.recurly.com/v2/accounts/104">' +
  90. ' <adjustments href="https://api.recurly.com/v2/accounts/1/adjustments"/>' +
  91. ' <billing_info href="https://api.recurly.com/v2/accounts/1/billing_info"/>' +
  92. ' <invoices href="https://api.recurly.com/v2/accounts/1/invoices"/>' +
  93. ' <redemption href="https://api.recurly.com/v2/accounts/1/redemption"/>' +
  94. ' <subscriptions href="https://api.recurly.com/v2/accounts/1/subscriptions"/>' +
  95. ' <transactions href="https://api.recurly.com/v2/accounts/1/transactions"/>' +
  96. ' <account_code>104</account_code>' +
  97. ' <state>active</state>' +
  98. ' <username nil="nil"></username>' +
  99. ' <email>verena@example.com</email>' +
  100. ' <first_name>Verena</first_name>' +
  101. ' <last_name>Example</last_name>' +
  102. ' <accept_language nil="nil"></accept_language>' +
  103. ' <hosted_login_token>a92468579e9c4231a6c0031c4716c01d</hosted_login_token>' +
  104. ' <created_at type="datetime">2011-10-25T12:00:00</created_at>' +
  105. '</account>'
  106. }
  107. const mockApiRequest = function(options, callback) {
  108. if (fixtures[options.url]) {
  109. return callback(null, { statusCode: 200 }, fixtures[options.url])
  110. } else {
  111. return callback(new Error('Not found'), { statusCode: 404 })
  112. }
  113. }
  114. describe('RecurlyWrapper', function() {
  115. before(function() {
  116. let RecurlyWrapper
  117. this.settings = {
  118. plans: [
  119. {
  120. planCode: 'collaborator',
  121. name: 'Collaborator',
  122. features: {
  123. collaborators: -1,
  124. versioning: true
  125. }
  126. }
  127. ],
  128. defaultPlanCode: {
  129. collaborators: 0,
  130. versioning: false
  131. },
  132. apis: {
  133. recurly: {
  134. apiKey: 'nonsense',
  135. privateKey: 'private_nonsense'
  136. }
  137. }
  138. }
  139. tk.freeze(Date.now()) // freeze the time for these tests
  140. return (this.RecurlyWrapper = RecurlyWrapper = SandboxedModule.require(
  141. modulePath,
  142. {
  143. globals: {
  144. console: console
  145. },
  146. requires: {
  147. 'settings-sharelatex': this.settings,
  148. 'logger-sharelatex': {
  149. err: sinon.stub(),
  150. error: sinon.stub(),
  151. warn: sinon.stub(),
  152. log: sinon.stub()
  153. },
  154. request: sinon.stub(),
  155. xml2js: require('xml2js')
  156. }
  157. }
  158. ))
  159. })
  160. after(function() {
  161. return tk.reset()
  162. })
  163. describe('getSubscription', function() {
  164. describe('with proper subscription id', function() {
  165. before(function() {
  166. this.apiRequest = sinon.stub(
  167. this.RecurlyWrapper,
  168. 'apiRequest',
  169. mockApiRequest
  170. )
  171. return this.RecurlyWrapper.getSubscription(
  172. '44f83d7cba354d5b84812419f923ea96',
  173. (error, recurlySubscription) => {
  174. return (this.recurlySubscription = recurlySubscription)
  175. }
  176. )
  177. })
  178. after(function() {
  179. return this.RecurlyWrapper.apiRequest.restore()
  180. })
  181. it('should look up the subscription at the normal API end point', function() {
  182. return this.apiRequest.args[0][0].url.should.equal(
  183. 'subscriptions/44f83d7cba354d5b84812419f923ea96'
  184. )
  185. })
  186. it('should return the subscription', function() {
  187. return this.recurlySubscription.uuid.should.equal(
  188. '44f83d7cba354d5b84812419f923ea96'
  189. )
  190. })
  191. })
  192. describe('with ReculyJS token', function() {
  193. before(function() {
  194. this.apiRequest = sinon.stub(
  195. this.RecurlyWrapper,
  196. 'apiRequest',
  197. mockApiRequest
  198. )
  199. return this.RecurlyWrapper.getSubscription(
  200. '70db44b10f5f4b238669480c9903f6f5',
  201. { recurlyJsResult: true },
  202. (error, recurlySubscription) => {
  203. return (this.recurlySubscription = recurlySubscription)
  204. }
  205. )
  206. })
  207. after(function() {
  208. return this.RecurlyWrapper.apiRequest.restore()
  209. })
  210. it('should return the subscription', function() {
  211. return this.recurlySubscription.uuid.should.equal(
  212. '44f83d7cba354d5b84812419f923ea96'
  213. )
  214. })
  215. it('should look up the subscription at the RecurlyJS API end point', function() {
  216. return this.apiRequest.args[0][0].url.should.equal(
  217. 'recurly_js/result/70db44b10f5f4b238669480c9903f6f5'
  218. )
  219. })
  220. })
  221. describe('with includeAccount', function() {
  222. beforeEach(function() {
  223. this.apiRequest = sinon.stub(
  224. this.RecurlyWrapper,
  225. 'apiRequest',
  226. mockApiRequest
  227. )
  228. return this.RecurlyWrapper.getSubscription(
  229. '44f83d7cba354d5b84812419f923ea96',
  230. { includeAccount: true },
  231. (error, recurlySubscription) => {
  232. return (this.recurlySubscription = recurlySubscription)
  233. }
  234. )
  235. })
  236. afterEach(function() {
  237. return this.RecurlyWrapper.apiRequest.restore()
  238. })
  239. it('should request the account from the API', function() {
  240. return this.apiRequest.args[1][0].url.should.equal('accounts/104')
  241. })
  242. it('should populate the account attribute', function() {
  243. return this.recurlySubscription.account.account_code.should.equal('104')
  244. })
  245. })
  246. })
  247. describe('updateSubscription', function() {
  248. beforeEach(function(done) {
  249. this.recurlySubscriptionId = 'subscription-id-123'
  250. this.apiRequest = sinon.stub(
  251. this.RecurlyWrapper,
  252. 'apiRequest',
  253. (options, callback) => {
  254. this.requestOptions = options
  255. return callback(
  256. null,
  257. {},
  258. fixtures['subscriptions/44f83d7cba354d5b84812419f923ea96']
  259. )
  260. }
  261. )
  262. return this.RecurlyWrapper.updateSubscription(
  263. this.recurlySubscriptionId,
  264. { plan_code: 'silver', timeframe: 'now' },
  265. (error, recurlySubscription) => {
  266. this.recurlySubscription = recurlySubscription
  267. return done()
  268. }
  269. )
  270. })
  271. afterEach(function() {
  272. return this.RecurlyWrapper.apiRequest.restore()
  273. })
  274. it('sends correct XML', function() {
  275. this.apiRequest.called.should.equal(true)
  276. const { body } = this.apiRequest.lastCall.args[0]
  277. expect(body).to.equal(`\
  278. <subscription>
  279. <plan_code>silver</plan_code>
  280. <timeframe>now</timeframe>
  281. </subscription>\
  282. `)
  283. this.requestOptions.url.should.equal(
  284. `subscriptions/${this.recurlySubscriptionId}`
  285. )
  286. return this.requestOptions.method.should.equal('put')
  287. })
  288. it('should return the updated subscription', function() {
  289. should.exist(this.recurlySubscription)
  290. return this.recurlySubscription.plan.plan_code.should.equal('gold')
  291. })
  292. })
  293. describe('cancelSubscription', function() {
  294. beforeEach(function(done) {
  295. this.recurlySubscriptionId = 'subscription-id-123'
  296. this.apiRequest = sinon.stub(
  297. this.RecurlyWrapper,
  298. 'apiRequest',
  299. (options, callback) => {
  300. options.url.should.equal(
  301. `subscriptions/${this.recurlySubscriptionId}/cancel`
  302. )
  303. options.method.should.equal('put')
  304. return callback()
  305. }
  306. )
  307. return this.RecurlyWrapper.cancelSubscription(
  308. this.recurlySubscriptionId,
  309. done
  310. )
  311. })
  312. afterEach(function() {
  313. return this.RecurlyWrapper.apiRequest.restore()
  314. })
  315. it('should send a cancel request to the API', function() {
  316. return this.apiRequest.called.should.equal(true)
  317. })
  318. describe('when the subscription is already cancelled', function() {
  319. beforeEach(function() {
  320. this.RecurlyWrapper.apiRequest.restore()
  321. this.recurlySubscriptionId = 'subscription-id-123'
  322. return (this.apiRequest = sinon.stub(
  323. this.RecurlyWrapper,
  324. 'apiRequest',
  325. (options, callback) => {
  326. return callback(
  327. new Error('woops'),
  328. {},
  329. "<error><description>A canceled subscription can't transition to canceled</description></error>"
  330. )
  331. }
  332. ))
  333. })
  334. it('should not produce an error', function(done) {
  335. return this.RecurlyWrapper.cancelSubscription(
  336. this.recurlySubscriptionId,
  337. err => {
  338. expect(err).to.equal(null)
  339. return done()
  340. }
  341. )
  342. })
  343. })
  344. })
  345. describe('reactivateSubscription', function() {
  346. beforeEach(function(done) {
  347. this.recurlySubscriptionId = 'subscription-id-123'
  348. this.apiRequest = sinon.stub(
  349. this.RecurlyWrapper,
  350. 'apiRequest',
  351. (options, callback) => {
  352. options.url.should.equal(
  353. `subscriptions/${this.recurlySubscriptionId}/reactivate`
  354. )
  355. options.method.should.equal('put')
  356. return callback()
  357. }
  358. )
  359. return this.RecurlyWrapper.reactivateSubscription(
  360. this.recurlySubscriptionId,
  361. done
  362. )
  363. })
  364. afterEach(function() {
  365. return this.RecurlyWrapper.apiRequest.restore()
  366. })
  367. it('should send a cancel request to the API', function() {
  368. return this.apiRequest.called.should.equal(true)
  369. })
  370. })
  371. describe('redeemCoupon', function() {
  372. beforeEach(function(done) {
  373. this.recurlyAccountId = 'account-id-123'
  374. this.coupon_code = '312321312'
  375. this.apiRequest = sinon.stub(
  376. this.RecurlyWrapper,
  377. 'apiRequest',
  378. (options, callback) => {
  379. options.url.should.equal(`coupons/${this.coupon_code}/redeem`)
  380. options.method.should.equal('post')
  381. return callback()
  382. }
  383. )
  384. return this.RecurlyWrapper.redeemCoupon(
  385. this.recurlyAccountId,
  386. this.coupon_code,
  387. done
  388. )
  389. })
  390. afterEach(function() {
  391. return this.RecurlyWrapper.apiRequest.restore()
  392. })
  393. it('sends correct XML', function() {
  394. this.apiRequest.called.should.equal(true)
  395. const { body } = this.apiRequest.lastCall.args[0]
  396. return expect(body).to.equal(`\
  397. <redemption>
  398. <account_code>account-id-123</account_code>
  399. <currency>USD</currency>
  400. </redemption>\
  401. `)
  402. })
  403. })
  404. describe('createFixedAmmountCoupon', function() {
  405. beforeEach(function(done) {
  406. this.couponCode = 'a-coupon-code'
  407. this.couponName = 'a-coupon-name'
  408. this.currencyCode = 'EUR'
  409. this.discount = 1337
  410. this.planCode = 'a-plan-code'
  411. this.apiRequest = sinon.stub(
  412. this.RecurlyWrapper,
  413. 'apiRequest',
  414. (options, callback) => {
  415. return callback()
  416. }
  417. )
  418. return this.RecurlyWrapper.createFixedAmmountCoupon(
  419. this.couponCode,
  420. this.couponName,
  421. this.currencyCode,
  422. this.discount,
  423. this.planCode,
  424. done
  425. )
  426. })
  427. afterEach(function() {
  428. return this.RecurlyWrapper.apiRequest.restore()
  429. })
  430. it('sends correct XML', function() {
  431. this.apiRequest.called.should.equal(true)
  432. const { body } = this.apiRequest.lastCall.args[0]
  433. return expect(body).to.equal(`\
  434. <coupon>
  435. <coupon_code>a-coupon-code</coupon_code>
  436. <name>a-coupon-name</name>
  437. <discount_type>dollars</discount_type>
  438. <discount_in_cents>
  439. <EUR>1337</EUR>
  440. </discount_in_cents>
  441. <plan_codes>
  442. <plan_code>a-plan-code</plan_code>
  443. </plan_codes>
  444. <applies_to_all_plans>false</applies_to_all_plans>
  445. </coupon>\
  446. `)
  447. })
  448. })
  449. describe('createSubscription', function() {
  450. beforeEach(function() {
  451. this.user = {
  452. _id: 'some_id',
  453. email: 'user@example.com'
  454. }
  455. this.subscriptionDetails = {
  456. currencyCode: 'EUR',
  457. plan_code: 'some_plan_code',
  458. coupon_code: '',
  459. isPaypal: true,
  460. address: {
  461. address1: 'addr_one',
  462. address2: 'addr_two',
  463. country: 'some_country',
  464. state: 'some_state',
  465. zip: 'some_zip'
  466. }
  467. }
  468. this.subscription = {}
  469. this.recurly_token_id = 'a-token-id'
  470. return (this.call = callback => {
  471. return this.RecurlyWrapper.createSubscription(
  472. this.user,
  473. this.subscriptionDetails,
  474. this.recurly_token_id,
  475. callback
  476. )
  477. })
  478. })
  479. describe('when paypal', function() {
  480. beforeEach(function() {
  481. this.subscriptionDetails.isPaypal = true
  482. this._createPaypalSubscription = sinon.stub(
  483. this.RecurlyWrapper,
  484. '_createPaypalSubscription'
  485. )
  486. return this._createPaypalSubscription.callsArgWith(
  487. 3,
  488. null,
  489. this.subscription
  490. )
  491. })
  492. afterEach(function() {
  493. return this._createPaypalSubscription.restore()
  494. })
  495. it('should not produce an error', function(done) {
  496. return this.call((err, sub) => {
  497. expect(err).to.equal(null)
  498. expect(err).to.not.be.instanceof(Error)
  499. return done()
  500. })
  501. })
  502. it('should produce a subscription object', function(done) {
  503. return this.call((err, sub) => {
  504. expect(sub).to.deep.equal(this.subscription)
  505. return done()
  506. })
  507. })
  508. it('should call _createPaypalSubscription', function(done) {
  509. return this.call((err, sub) => {
  510. this._createPaypalSubscription.callCount.should.equal(1)
  511. return done()
  512. })
  513. })
  514. describe('when _createPaypalSubscription produces an error', function() {
  515. beforeEach(function() {
  516. return this._createPaypalSubscription.callsArgWith(
  517. 3,
  518. new Error('woops')
  519. )
  520. })
  521. it('should produce an error', function(done) {
  522. return this.call((err, sub) => {
  523. expect(err).to.be.instanceof(Error)
  524. return done()
  525. })
  526. })
  527. })
  528. })
  529. describe('when not paypal', function() {
  530. beforeEach(function() {
  531. this.subscriptionDetails.isPaypal = false
  532. this._createCreditCardSubscription = sinon.stub(
  533. this.RecurlyWrapper,
  534. '_createCreditCardSubscription'
  535. )
  536. return this._createCreditCardSubscription.callsArgWith(
  537. 3,
  538. null,
  539. this.subscription
  540. )
  541. })
  542. afterEach(function() {
  543. return this._createCreditCardSubscription.restore()
  544. })
  545. it('should not produce an error', function(done) {
  546. return this.call((err, sub) => {
  547. expect(err).to.equal(null)
  548. expect(err).to.not.be.instanceof(Error)
  549. return done()
  550. })
  551. })
  552. it('should produce a subscription object', function(done) {
  553. return this.call((err, sub) => {
  554. expect(sub).to.deep.equal(this.subscription)
  555. return done()
  556. })
  557. })
  558. it('should call _createCreditCardSubscription', function(done) {
  559. return this.call((err, sub) => {
  560. this._createCreditCardSubscription.callCount.should.equal(1)
  561. return done()
  562. })
  563. })
  564. describe('when _createCreditCardSubscription produces an error', function() {
  565. beforeEach(function() {
  566. return this._createCreditCardSubscription.callsArgWith(
  567. 3,
  568. new Error('woops')
  569. )
  570. })
  571. it('should produce an error', function(done) {
  572. return this.call((err, sub) => {
  573. expect(err).to.be.instanceof(Error)
  574. return done()
  575. })
  576. })
  577. })
  578. })
  579. })
  580. describe('_createCreditCardSubscription', function() {
  581. beforeEach(function() {
  582. this.user = {
  583. _id: 'some_id',
  584. email: 'user@example.com',
  585. first_name: 'Foo',
  586. last_name: 'Johnson'
  587. }
  588. this.subscriptionDetails = {
  589. currencyCode: 'EUR',
  590. plan_code: 'some_plan_code',
  591. coupon_code: '',
  592. isPaypal: true,
  593. first_name: 'Prairie',
  594. address: {
  595. address1: 'addr_one',
  596. address2: 'addr_two',
  597. country: 'some_country',
  598. state: 'some_state',
  599. zip: 'some_zip'
  600. }
  601. }
  602. this.subscription = {}
  603. this.recurly_token_id = 'a-token-id'
  604. this.apiRequest = sinon.stub(this.RecurlyWrapper, 'apiRequest')
  605. this.response = { statusCode: 200 }
  606. this.body = '<xml>is_bad</xml>'
  607. this.apiRequest.callsArgWith(1, null, this.response, this.body)
  608. this._parseSubscriptionXml = sinon.stub(
  609. this.RecurlyWrapper,
  610. '_parseSubscriptionXml'
  611. )
  612. this._parseSubscriptionXml.callsArgWith(1, null, this.subscription)
  613. return (this.call = callback => {
  614. return this.RecurlyWrapper._createCreditCardSubscription(
  615. this.user,
  616. this.subscriptionDetails,
  617. this.recurly_token_id,
  618. callback
  619. )
  620. })
  621. })
  622. afterEach(function() {
  623. this.apiRequest.restore()
  624. return this._parseSubscriptionXml.restore()
  625. })
  626. it('sends correct XML', function(done) {
  627. return this.call((err, result) => {
  628. const { body } = this.apiRequest.lastCall.args[0]
  629. expect(body).to.equal(`\
  630. <subscription>
  631. <plan_code>some_plan_code</plan_code>
  632. <currency>EUR</currency>
  633. <coupon_code/>
  634. <account>
  635. <account_code>some_id</account_code>
  636. <email>user@example.com</email>
  637. <first_name>Prairie</first_name>
  638. <last_name>Johnson</last_name>
  639. <billing_info>
  640. <token_id>a-token-id</token_id>
  641. </billing_info>
  642. </account>
  643. </subscription>\
  644. `)
  645. return done()
  646. })
  647. })
  648. it('should not produce an error', function(done) {
  649. return this.call((err, sub) => {
  650. expect(err).to.not.be.instanceof(Error)
  651. expect(err).to.equal(null)
  652. return done()
  653. })
  654. })
  655. it('should produce a subscription', function(done) {
  656. return this.call((err, sub) => {
  657. expect(sub).to.equal(this.subscription)
  658. return done()
  659. })
  660. })
  661. it('should call apiRequest', function(done) {
  662. return this.call((err, sub) => {
  663. this.apiRequest.callCount.should.equal(1)
  664. return done()
  665. })
  666. })
  667. it('should call _parseSubscriptionXml', function(done) {
  668. return this.call((err, sub) => {
  669. this._parseSubscriptionXml.callCount.should.equal(1)
  670. return done()
  671. })
  672. })
  673. describe('when api request produces an error', function() {
  674. beforeEach(function() {
  675. return this.apiRequest.callsArgWith(1, new Error('woops'))
  676. })
  677. it('should produce an error', function(done) {
  678. return this.call((err, sub) => {
  679. expect(err).to.be.instanceof(Error)
  680. return done()
  681. })
  682. })
  683. it('should call apiRequest', function(done) {
  684. return this.call((err, sub) => {
  685. this.apiRequest.callCount.should.equal(1)
  686. return done()
  687. })
  688. })
  689. it('should not _parseSubscriptionXml', function(done) {
  690. return this.call((err, sub) => {
  691. this._parseSubscriptionXml.callCount.should.equal(0)
  692. return done()
  693. })
  694. })
  695. })
  696. describe('when parse xml produces an error', function() {
  697. beforeEach(function() {
  698. return this._parseSubscriptionXml.callsArgWith(1, new Error('woops'))
  699. })
  700. it('should produce an error', function(done) {
  701. return this.call((err, sub) => {
  702. expect(err).to.be.instanceof(Error)
  703. return done()
  704. })
  705. })
  706. })
  707. })
  708. describe('_createPaypalSubscription', function() {
  709. beforeEach(function() {
  710. this.checkAccountExists = sinon.stub(
  711. this.RecurlyWrapper._paypal,
  712. 'checkAccountExists'
  713. )
  714. this.createAccount = sinon.stub(
  715. this.RecurlyWrapper._paypal,
  716. 'createAccount'
  717. )
  718. this.createBillingInfo = sinon.stub(
  719. this.RecurlyWrapper._paypal,
  720. 'createBillingInfo'
  721. )
  722. this.setAddress = sinon.stub(this.RecurlyWrapper._paypal, 'setAddress')
  723. this.createSubscription = sinon.stub(
  724. this.RecurlyWrapper._paypal,
  725. 'createSubscription'
  726. )
  727. this.user = {
  728. _id: 'some_id',
  729. email: 'user@example.com'
  730. }
  731. this.subscriptionDetails = {
  732. currencyCode: 'EUR',
  733. plan_code: 'some_plan_code',
  734. coupon_code: '',
  735. isPaypal: true,
  736. address: {
  737. address1: 'addr_one',
  738. address2: 'addr_two',
  739. country: 'some_country',
  740. state: 'some_state',
  741. zip: 'some_zip'
  742. }
  743. }
  744. this.subscription = {}
  745. this.recurly_token_id = 'a-token-id'
  746. // set up data callbacks
  747. const { user } = this
  748. const { subscriptionDetails } = this
  749. const { recurly_token_id } = this
  750. this.checkAccountExists.callsArgWith(1, null, {
  751. user,
  752. subscriptionDetails,
  753. recurly_token_id,
  754. userExists: false,
  755. account: { accountCode: 'xx' }
  756. })
  757. this.createAccount.callsArgWith(1, null, {
  758. user,
  759. subscriptionDetails,
  760. recurly_token_id,
  761. userExists: false,
  762. account: { accountCode: 'xx' }
  763. })
  764. this.createBillingInfo.callsArgWith(1, null, {
  765. user,
  766. subscriptionDetails,
  767. recurly_token_id,
  768. userExists: false,
  769. account: { accountCode: 'xx' },
  770. billingInfo: { token_id: 'abc' }
  771. })
  772. this.setAddress.callsArgWith(1, null, {
  773. user,
  774. subscriptionDetails,
  775. recurly_token_id,
  776. userExists: false,
  777. account: { accountCode: 'xx' },
  778. billingInfo: { token_id: 'abc' }
  779. })
  780. this.createSubscription.callsArgWith(1, null, {
  781. user,
  782. subscriptionDetails,
  783. recurly_token_id,
  784. userExists: false,
  785. account: { accountCode: 'xx' },
  786. billingInfo: { token_id: 'abc' },
  787. subscription: this.subscription
  788. })
  789. return (this.call = callback => {
  790. return this.RecurlyWrapper._createPaypalSubscription(
  791. this.user,
  792. this.subscriptionDetails,
  793. this.recurly_token_id,
  794. callback
  795. )
  796. })
  797. })
  798. afterEach(function() {
  799. this.checkAccountExists.restore()
  800. this.createAccount.restore()
  801. this.createBillingInfo.restore()
  802. this.setAddress.restore()
  803. return this.createSubscription.restore()
  804. })
  805. it('should not produce an error', function(done) {
  806. return this.call((err, sub) => {
  807. expect(err).to.not.be.instanceof(Error)
  808. return done()
  809. })
  810. })
  811. it('should produce a subscription object', function(done) {
  812. return this.call((err, sub) => {
  813. expect(sub).to.not.equal(null)
  814. expect(sub).to.equal(this.subscription)
  815. return done()
  816. })
  817. })
  818. it('should call each of the paypal stages', function(done) {
  819. return this.call((err, sub) => {
  820. this.checkAccountExists.callCount.should.equal(1)
  821. this.createAccount.callCount.should.equal(1)
  822. this.createBillingInfo.callCount.should.equal(1)
  823. this.setAddress.callCount.should.equal(1)
  824. this.createSubscription.callCount.should.equal(1)
  825. return done()
  826. })
  827. })
  828. describe('when one of the paypal stages produces an error', function() {
  829. beforeEach(function() {
  830. return this.createAccount.callsArgWith(1, new Error('woops'))
  831. })
  832. it('should produce an error', function(done) {
  833. return this.call((err, sub) => {
  834. expect(err).to.be.instanceof(Error)
  835. return done()
  836. })
  837. })
  838. it('should stop calling the paypal stages after the error', function(done) {
  839. return this.call((err, sub) => {
  840. this.checkAccountExists.callCount.should.equal(1)
  841. this.createAccount.callCount.should.equal(1)
  842. this.createBillingInfo.callCount.should.equal(0)
  843. this.setAddress.callCount.should.equal(0)
  844. this.createSubscription.callCount.should.equal(0)
  845. return done()
  846. })
  847. })
  848. })
  849. })
  850. describe('paypal actions', function() {
  851. beforeEach(function() {
  852. this.apiRequest = sinon.stub(this.RecurlyWrapper, 'apiRequest')
  853. this._parseAccountXml = sinon.spy(this.RecurlyWrapper, '_parseAccountXml')
  854. this._parseBillingInfoXml = sinon.spy(
  855. this.RecurlyWrapper,
  856. '_parseBillingInfoXml'
  857. )
  858. this._parseSubscriptionXml = sinon.spy(
  859. this.RecurlyWrapper,
  860. '_parseSubscriptionXml'
  861. )
  862. return (this.cache = {
  863. user: (this.user = {
  864. _id: 'some_id',
  865. email: 'foo@bar.com',
  866. first_name: 'Foo',
  867. last_name: 'Bar'
  868. }),
  869. recurly_token_id: (this.recurly_token_id = 'some_token'),
  870. subscriptionDetails: (this.subscriptionDetails = {
  871. currencyCode: 'EUR',
  872. plan_code: 'some_plan_code',
  873. coupon_code: '',
  874. isPaypal: true,
  875. address: {
  876. address1: 'addr_one',
  877. address2: 'addr_two',
  878. city: 'some_city',
  879. country: 'some_country',
  880. state: 'some_state',
  881. zip: 'some_zip'
  882. }
  883. })
  884. })
  885. })
  886. afterEach(function() {
  887. this.apiRequest.restore()
  888. this._parseAccountXml.restore()
  889. this._parseBillingInfoXml.restore()
  890. return this._parseSubscriptionXml.restore()
  891. })
  892. describe('_paypal.checkAccountExists', function() {
  893. beforeEach(function() {
  894. return (this.call = callback => {
  895. return this.RecurlyWrapper._paypal.checkAccountExists(
  896. this.cache,
  897. callback
  898. )
  899. })
  900. })
  901. describe('when the account exists', function() {
  902. beforeEach(function() {
  903. const resultXml =
  904. '<account><account_code>abc</account_code></account>'
  905. return this.apiRequest.callsArgWith(
  906. 1,
  907. null,
  908. { statusCode: 200 },
  909. resultXml
  910. )
  911. })
  912. it('should not produce an error', function(done) {
  913. return this.call((err, result) => {
  914. expect(err).to.not.be.instanceof(Error)
  915. return done()
  916. })
  917. })
  918. it('should call apiRequest', function(done) {
  919. return this.call((err, result) => {
  920. this.apiRequest.callCount.should.equal(1)
  921. return done()
  922. })
  923. })
  924. it('should call _parseAccountXml', function(done) {
  925. return this.call((err, result) => {
  926. this.RecurlyWrapper._parseAccountXml.callCount.should.equal(1)
  927. return done()
  928. })
  929. })
  930. it('should add the account to the cumulative result', function(done) {
  931. return this.call((err, result) => {
  932. expect(result.account).to.not.equal(null)
  933. expect(result.account).to.not.equal(undefined)
  934. expect(result.account).to.deep.equal({
  935. account_code: 'abc'
  936. })
  937. return done()
  938. })
  939. })
  940. it('should set userExists to true', function(done) {
  941. return this.call((err, result) => {
  942. expect(result.userExists).to.equal(true)
  943. return done()
  944. })
  945. })
  946. })
  947. describe('when the account does not exist', function() {
  948. beforeEach(function() {
  949. return this.apiRequest.callsArgWith(1, null, { statusCode: 404 }, '')
  950. })
  951. it('should not produce an error', function(done) {
  952. return this.call((err, result) => {
  953. expect(err).to.not.be.instanceof(Error)
  954. return done()
  955. })
  956. })
  957. it('should call apiRequest', function(done) {
  958. return this.call((err, result) => {
  959. this.apiRequest.callCount.should.equal(1)
  960. this.apiRequest.firstCall.args[0].method.should.equal('GET')
  961. return done()
  962. })
  963. })
  964. it('should not call _parseAccountXml', function(done) {
  965. return this.call((err, result) => {
  966. this.RecurlyWrapper._parseAccountXml.callCount.should.equal(0)
  967. return done()
  968. })
  969. })
  970. it('should not add the account to result', function(done) {
  971. return this.call((err, result) => {
  972. expect(result.account).to.equal(undefined)
  973. return done()
  974. })
  975. })
  976. it('should set userExists to false', function(done) {
  977. return this.call((err, result) => {
  978. expect(result.userExists).to.equal(false)
  979. return done()
  980. })
  981. })
  982. })
  983. describe('when apiRequest produces an error', function() {
  984. beforeEach(function() {
  985. return this.apiRequest.callsArgWith(1, new Error('woops'), {
  986. statusCode: 500
  987. })
  988. })
  989. it('should produce an error', function(done) {
  990. return this.call((err, result) => {
  991. expect(err).to.be.instanceof(Error)
  992. return done()
  993. })
  994. })
  995. })
  996. })
  997. describe('_paypal.createAccount', function() {
  998. beforeEach(function() {
  999. return (this.call = callback => {
  1000. return this.RecurlyWrapper._paypal.createAccount(this.cache, callback)
  1001. })
  1002. })
  1003. describe('when address is missing from subscriptionDetails', function() {
  1004. beforeEach(function() {
  1005. return (this.cache.subscriptionDetails.address = null)
  1006. })
  1007. it('should produce an error', function(done) {
  1008. return this.call((err, result) => {
  1009. expect(err).to.be.instanceof(Error)
  1010. return done()
  1011. })
  1012. })
  1013. })
  1014. describe('when account already exists', function() {
  1015. beforeEach(function() {
  1016. this.cache.userExists = true
  1017. return (this.cache.account = { account_code: 'abc' })
  1018. })
  1019. it('should not produce an error', function(done) {
  1020. return this.call((err, result) => {
  1021. expect(err).to.not.be.instanceof(Error)
  1022. return done()
  1023. })
  1024. })
  1025. it('should produce cache object', function(done) {
  1026. return this.call((err, result) => {
  1027. expect(result).to.deep.equal(this.cache)
  1028. expect(result.account).to.deep.equal({
  1029. account_code: 'abc'
  1030. })
  1031. return done()
  1032. })
  1033. })
  1034. it('should not call apiRequest', function(done) {
  1035. return this.call((err, result) => {
  1036. this.apiRequest.callCount.should.equal(0)
  1037. return done()
  1038. })
  1039. })
  1040. it('should not call _parseAccountXml', function(done) {
  1041. return this.call((err, result) => {
  1042. this.RecurlyWrapper._parseAccountXml.callCount.should.equal(0)
  1043. return done()
  1044. })
  1045. })
  1046. })
  1047. describe('when account does not exist', function() {
  1048. beforeEach(function() {
  1049. this.cache.userExists = false
  1050. const resultXml =
  1051. '<account><account_code>abc</account_code></account>'
  1052. return this.apiRequest.callsArgWith(
  1053. 1,
  1054. null,
  1055. { statusCode: 200 },
  1056. resultXml
  1057. )
  1058. })
  1059. it('sends correct XML', function(done) {
  1060. return this.call((err, result) => {
  1061. const { body } = this.apiRequest.lastCall.args[0]
  1062. expect(body).to.equal(`\
  1063. <account>
  1064. <account_code>some_id</account_code>
  1065. <email>foo@bar.com</email>
  1066. <first_name>Foo</first_name>
  1067. <last_name>Bar</last_name>
  1068. <address>
  1069. <address1>addr_one</address1>
  1070. <address2>addr_two</address2>
  1071. <city>some_city</city>
  1072. <state>some_state</state>
  1073. <zip>some_zip</zip>
  1074. <country>some_country</country>
  1075. </address>
  1076. </account>\
  1077. `)
  1078. return done()
  1079. })
  1080. })
  1081. it('should not produce an error', function(done) {
  1082. return this.call((err, result) => {
  1083. expect(err).to.not.be.instanceof(Error)
  1084. return done()
  1085. })
  1086. })
  1087. it('should call apiRequest', function(done) {
  1088. return this.call((err, result) => {
  1089. this.apiRequest.callCount.should.equal(1)
  1090. this.apiRequest.firstCall.args[0].method.should.equal('POST')
  1091. return done()
  1092. })
  1093. })
  1094. it('should call _parseAccountXml', function(done) {
  1095. return this.call((err, result) => {
  1096. this.RecurlyWrapper._parseAccountXml.callCount.should.equal(1)
  1097. return done()
  1098. })
  1099. })
  1100. describe('when apiRequest produces an error', function() {
  1101. beforeEach(function() {
  1102. return this.apiRequest.callsArgWith(1, new Error('woops'), {
  1103. statusCode: 500
  1104. })
  1105. })
  1106. it('should produce an error', function(done) {
  1107. return this.call((err, result) => {
  1108. expect(err).to.be.instanceof(Error)
  1109. return done()
  1110. })
  1111. })
  1112. })
  1113. })
  1114. })
  1115. describe('_paypal.createBillingInfo', function() {
  1116. beforeEach(function() {
  1117. this.cache.account = { account_code: 'abc' }
  1118. return (this.call = callback => {
  1119. return this.RecurlyWrapper._paypal.createBillingInfo(
  1120. this.cache,
  1121. callback
  1122. )
  1123. })
  1124. })
  1125. describe('when account_code is missing from cache', function() {
  1126. beforeEach(function() {
  1127. return (this.cache.account.account_code = null)
  1128. })
  1129. it('should produce an error', function(done) {
  1130. return this.call((err, result) => {
  1131. expect(err).to.be.instanceof(Error)
  1132. return done()
  1133. })
  1134. })
  1135. })
  1136. describe('when all goes well', function() {
  1137. beforeEach(function() {
  1138. const resultXml = '<billing_info><a>1</a></billing_info>'
  1139. return this.apiRequest.callsArgWith(
  1140. 1,
  1141. null,
  1142. { statusCode: 200 },
  1143. resultXml
  1144. )
  1145. })
  1146. it('sends correct XML', function(done) {
  1147. return this.call((err, result) => {
  1148. const { body } = this.apiRequest.lastCall.args[0]
  1149. expect(body).to.equal(`\
  1150. <billing_info>
  1151. <token_id>some_token</token_id>
  1152. </billing_info>\
  1153. `)
  1154. return done()
  1155. })
  1156. })
  1157. it('should not produce an error', function(done) {
  1158. return this.call((err, result) => {
  1159. expect(err).to.not.be.instanceof(Error)
  1160. return done()
  1161. })
  1162. })
  1163. it('should call apiRequest', function(done) {
  1164. return this.call((err, result) => {
  1165. this.apiRequest.callCount.should.equal(1)
  1166. this.apiRequest.firstCall.args[0].method.should.equal('POST')
  1167. return done()
  1168. })
  1169. })
  1170. it('should call _parseBillingInfoXml', function(done) {
  1171. return this.call((err, result) => {
  1172. this.RecurlyWrapper._parseBillingInfoXml.callCount.should.equal(1)
  1173. return done()
  1174. })
  1175. })
  1176. it('should set billingInfo on cache', function(done) {
  1177. return this.call((err, result) => {
  1178. expect(result.billingInfo).to.deep.equal({
  1179. a: '1'
  1180. })
  1181. return done()
  1182. })
  1183. })
  1184. })
  1185. describe('when apiRequest produces an error', function() {
  1186. beforeEach(function() {
  1187. return this.apiRequest.callsArgWith(1, new Error('woops'), {
  1188. statusCode: 500
  1189. })
  1190. })
  1191. it('should produce an error', function(done) {
  1192. return this.call((err, result) => {
  1193. expect(err).to.be.instanceof(Error)
  1194. return done()
  1195. })
  1196. })
  1197. })
  1198. })
  1199. describe('_paypal.setAddress', function() {
  1200. beforeEach(function() {
  1201. this.cache.account = { account_code: 'abc' }
  1202. this.cache.billingInfo = {}
  1203. return (this.call = callback => {
  1204. return this.RecurlyWrapper._paypal.setAddress(this.cache, callback)
  1205. })
  1206. })
  1207. describe('when account_code is missing from cache', function() {
  1208. beforeEach(function() {
  1209. return (this.cache.account.account_code = null)
  1210. })
  1211. it('should produce an error', function(done) {
  1212. return this.call((err, result) => {
  1213. expect(err).to.be.instanceof(Error)
  1214. return done()
  1215. })
  1216. })
  1217. })
  1218. describe('when address is missing from subscriptionDetails', function() {
  1219. beforeEach(function() {
  1220. return (this.cache.subscriptionDetails.address = null)
  1221. })
  1222. it('should produce an error', function(done) {
  1223. return this.call((err, result) => {
  1224. expect(err).to.be.instanceof(Error)
  1225. return done()
  1226. })
  1227. })
  1228. })
  1229. describe('when all goes well', function() {
  1230. beforeEach(function() {
  1231. const resultXml = '<billing_info><city>London</city></billing_info>'
  1232. return this.apiRequest.callsArgWith(
  1233. 1,
  1234. null,
  1235. { statusCode: 200 },
  1236. resultXml
  1237. )
  1238. })
  1239. it('sends correct XML', function(done) {
  1240. return this.call((err, result) => {
  1241. const { body } = this.apiRequest.lastCall.args[0]
  1242. expect(body).to.equal(`\
  1243. <billing_info>
  1244. <address1>addr_one</address1>
  1245. <address2>addr_two</address2>
  1246. <city>some_city</city>
  1247. <state>some_state</state>
  1248. <zip>some_zip</zip>
  1249. <country>some_country</country>
  1250. </billing_info>\
  1251. `)
  1252. return done()
  1253. })
  1254. })
  1255. it('should not produce an error', function(done) {
  1256. return this.call((err, result) => {
  1257. expect(err).to.not.be.instanceof(Error)
  1258. return done()
  1259. })
  1260. })
  1261. it('should call apiRequest', function(done) {
  1262. return this.call((err, result) => {
  1263. this.apiRequest.callCount.should.equal(1)
  1264. this.apiRequest.firstCall.args[0].method.should.equal('PUT')
  1265. return done()
  1266. })
  1267. })
  1268. it('should call _parseBillingInfoXml', function(done) {
  1269. return this.call((err, result) => {
  1270. this.RecurlyWrapper._parseBillingInfoXml.callCount.should.equal(1)
  1271. return done()
  1272. })
  1273. })
  1274. it('should set billingInfo on cache', function(done) {
  1275. return this.call((err, result) => {
  1276. expect(result.billingInfo).to.deep.equal({
  1277. city: 'London'
  1278. })
  1279. return done()
  1280. })
  1281. })
  1282. })
  1283. describe('when apiRequest produces an error', function() {
  1284. beforeEach(function() {
  1285. return this.apiRequest.callsArgWith(1, new Error('woops'), {
  1286. statusCode: 500
  1287. })
  1288. })
  1289. it('should produce an error', function(done) {
  1290. return this.call((err, result) => {
  1291. expect(err).to.be.instanceof(Error)
  1292. return done()
  1293. })
  1294. })
  1295. })
  1296. })
  1297. describe('_paypal.createSubscription', function() {
  1298. beforeEach(function() {
  1299. this.cache.account = { account_code: 'abc' }
  1300. this.cache.billingInfo = {}
  1301. return (this.call = callback => {
  1302. return this.RecurlyWrapper._paypal.createSubscription(
  1303. this.cache,
  1304. callback
  1305. )
  1306. })
  1307. })
  1308. describe('when all goes well', function() {
  1309. beforeEach(function() {
  1310. const resultXml = '<subscription><a>1</a></subscription>'
  1311. return this.apiRequest.callsArgWith(
  1312. 1,
  1313. null,
  1314. { statusCode: 200 },
  1315. resultXml
  1316. )
  1317. })
  1318. it('sends correct XML', function(done) {
  1319. return this.call((err, result) => {
  1320. const { body } = this.apiRequest.lastCall.args[0]
  1321. expect(body).to.equal(`\
  1322. <subscription>
  1323. <plan_code>some_plan_code</plan_code>
  1324. <currency>EUR</currency>
  1325. <coupon_code/>
  1326. <account>
  1327. <account_code>some_id</account_code>
  1328. </account>
  1329. </subscription>\
  1330. `)
  1331. return done()
  1332. })
  1333. })
  1334. it('should not produce an error', function(done) {
  1335. return this.call((err, result) => {
  1336. expect(err).to.not.be.instanceof(Error)
  1337. return done()
  1338. })
  1339. })
  1340. it('should call apiRequest', function(done) {
  1341. return this.call((err, result) => {
  1342. this.apiRequest.callCount.should.equal(1)
  1343. this.apiRequest.firstCall.args[0].method.should.equal('POST')
  1344. return done()
  1345. })
  1346. })
  1347. it('should call _parseSubscriptionXml', function(done) {
  1348. return this.call((err, result) => {
  1349. this.RecurlyWrapper._parseSubscriptionXml.callCount.should.equal(1)
  1350. return done()
  1351. })
  1352. })
  1353. it('should set subscription on cache', function(done) {
  1354. return this.call((err, result) => {
  1355. expect(result.subscription).to.deep.equal({
  1356. a: '1'
  1357. })
  1358. return done()
  1359. })
  1360. })
  1361. })
  1362. describe('when apiRequest produces an error', function() {
  1363. beforeEach(function() {
  1364. return this.apiRequest.callsArgWith(1, new Error('woops'), {
  1365. statusCode: 500
  1366. })
  1367. })
  1368. it('should produce an error', function(done) {
  1369. return this.call((err, result) => {
  1370. expect(err).to.be.instanceof(Error)
  1371. return done()
  1372. })
  1373. })
  1374. })
  1375. })
  1376. })
  1377. describe('listAccountActiveSubscriptions', function() {
  1378. beforeEach(function() {
  1379. this.user_id = 'mock-user-id'
  1380. this.callback = sinon.stub()
  1381. this.RecurlyWrapper.apiRequest = sinon
  1382. .stub()
  1383. .yields(
  1384. null,
  1385. (this.response = { mock: 'response' }),
  1386. (this.body = '<mock body/>')
  1387. )
  1388. return (this.RecurlyWrapper._parseSubscriptionsXml = sinon
  1389. .stub()
  1390. .yields(null, (this.subscriptions = ['mock', 'subscriptions'])))
  1391. })
  1392. describe('with an account', function() {
  1393. beforeEach(function() {
  1394. return this.RecurlyWrapper.listAccountActiveSubscriptions(
  1395. this.user_id,
  1396. this.callback
  1397. )
  1398. })
  1399. it('should send a request to Recurly', function() {
  1400. return this.RecurlyWrapper.apiRequest
  1401. .calledWith({
  1402. url: `accounts/${this.user_id}/subscriptions`,
  1403. qs: {
  1404. state: 'active'
  1405. },
  1406. expect404: true
  1407. })
  1408. .should.equal(true)
  1409. })
  1410. it('should return the subscriptions', function() {
  1411. return this.callback
  1412. .calledWith(null, this.subscriptions)
  1413. .should.equal(true)
  1414. })
  1415. })
  1416. describe('without an account', function() {
  1417. beforeEach(function() {
  1418. this.response.statusCode = 404
  1419. return this.RecurlyWrapper.listAccountActiveSubscriptions(
  1420. this.user_id,
  1421. this.callback
  1422. )
  1423. })
  1424. it('should return an empty array of subscriptions', function() {
  1425. return this.callback.calledWith(null, []).should.equal(true)
  1426. })
  1427. })
  1428. })
  1429. })