RecurlyWrapperTests.js 47 KB

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