RecurlyWrapperTests.coffee 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310
  1. should = require('chai').should()
  2. sinon = require 'sinon'
  3. crypto = require 'crypto'
  4. querystring = require 'querystring'
  5. RecurlyWrapper = require "../../../../app/js/Features/Subscription/RecurlyWrapper"
  6. Settings = require "settings-sharelatex"
  7. fixtures =
  8. "subscriptions/44f83d7cba354d5b84812419f923ea96":
  9. "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
  10. "<subscription href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96\">" +
  11. " <account href=\"https://api.recurly.com/v2/accounts/104\"/>" +
  12. " <plan href=\"https://api.recurly.com/v2/plans/gold\">" +
  13. " <plan_code>gold</plan_code>" +
  14. " <name>Gold plan</name>" +
  15. " </plan>" +
  16. " <uuid>44f83d7cba354d5b84812419f923ea96</uuid>" +
  17. " <state>active</state>" +
  18. " <unit_amount_in_cents type=\"integer\">800</unit_amount_in_cents>" +
  19. " <currency>EUR</currency>" +
  20. " <quantity type=\"integer\">1</quantity>" +
  21. " <activated_at type=\"datetime\">2011-05-27T07:00:00Z</activated_at>" +
  22. " <canceled_at nil=\"nil\"></canceled_at>" +
  23. " <expires_at nil=\"nil\"></expires_at>" +
  24. " <current_period_started_at type=\"datetime\">2011-06-27T07:00:00Z</current_period_started_at>" +
  25. " <current_period_ends_at type=\"datetime\">2011-07-27T07:00:00Z</current_period_ends_at>" +
  26. " <trial_started_at nil=\"nil\"></trial_started_at>" +
  27. " <trial_ends_at nil=\"nil\"></trial_ends_at>" +
  28. " <subscription_add_ons type=\"array\">" +
  29. " <subscription_add_on>" +
  30. " <add_on_code>ipaddresses</add_on_code>" +
  31. " <quantity>10</quantity>" +
  32. " <unit_amount_in_cents>150</unit_amount_in_cents>" +
  33. " </subscription_add_on>" +
  34. " </subscription_add_ons>" +
  35. " <a name=\"cancel\" href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/cancel\" method=\"put\"/>" +
  36. " <a name=\"terminate\" href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/terminate\" method=\"put\"/>" +
  37. " <a name=\"postpone\" href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/postpone\" method=\"put\"/>" +
  38. "</subscription>"
  39. "recurly_js/result/70db44b10f5f4b238669480c9903f6f5":
  40. "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
  41. "<subscription href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96\">" +
  42. " <account href=\"https://api.recurly.com/v2/accounts/104\"/>" +
  43. " <plan href=\"https://api.recurly.com/v2/plans/gold\">" +
  44. " <plan_code>gold</plan_code>" +
  45. " <name>Gold plan</name>" +
  46. " </plan>" +
  47. " <uuid>44f83d7cba354d5b84812419f923ea96</uuid>" +
  48. " <state>active</state>" +
  49. " <unit_amount_in_cents type=\"integer\">800</unit_amount_in_cents>" +
  50. " <currency>EUR</currency>" +
  51. " <quantity type=\"integer\">1</quantity>" +
  52. " <activated_at type=\"datetime\">2011-05-27T07:00:00Z</activated_at>" +
  53. " <canceled_at nil=\"nil\"></canceled_at>" +
  54. " <expires_at nil=\"nil\"></expires_at>" +
  55. " <current_period_started_at type=\"datetime\">2011-06-27T07:00:00Z</current_period_started_at>" +
  56. " <current_period_ends_at type=\"datetime\">2011-07-27T07:00:00Z</current_period_ends_at>" +
  57. " <trial_started_at nil=\"nil\"></trial_started_at>" +
  58. " <trial_ends_at nil=\"nil\"></trial_ends_at>" +
  59. " <subscription_add_ons type=\"array\">" +
  60. " <subscription_add_on>" +
  61. " <add_on_code>ipaddresses</add_on_code>" +
  62. " <quantity>10</quantity>" +
  63. " <unit_amount_in_cents>150</unit_amount_in_cents>" +
  64. " </subscription_add_on>" +
  65. " </subscription_add_ons>" +
  66. " <a name=\"cancel\" href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/cancel\" method=\"put\"/>" +
  67. " <a name=\"terminate\" href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/terminate\" method=\"put\"/>" +
  68. " <a name=\"postpone\" href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/postpone\" method=\"put\"/>" +
  69. "</subscription>"
  70. "accounts/104":
  71. "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
  72. "<account href=\"https://api.recurly.com/v2/accounts/104\">" +
  73. " <adjustments href=\"https://api.recurly.com/v2/accounts/1/adjustments\"/>" +
  74. " <billing_info href=\"https://api.recurly.com/v2/accounts/1/billing_info\"/>" +
  75. " <invoices href=\"https://api.recurly.com/v2/accounts/1/invoices\"/>" +
  76. " <redemption href=\"https://api.recurly.com/v2/accounts/1/redemption\"/>" +
  77. " <subscriptions href=\"https://api.recurly.com/v2/accounts/1/subscriptions\"/>" +
  78. " <transactions href=\"https://api.recurly.com/v2/accounts/1/transactions\"/>" +
  79. " <account_code>104</account_code>" +
  80. " <state>active</state>" +
  81. " <username nil=\"nil\"></username>" +
  82. " <email>verena@example.com</email>" +
  83. " <first_name>Verena</first_name>" +
  84. " <last_name>Example</last_name>" +
  85. " <accept_language nil=\"nil\"></accept_language>" +
  86. " <hosted_login_token>a92468579e9c4231a6c0031c4716c01d</hosted_login_token>" +
  87. " <created_at type=\"datetime\">2011-10-25T12:00:00</created_at>" +
  88. "</account>"
  89. mockApiRequest = (options, callback) ->
  90. if fixtures[options.url]
  91. callback(null, {statusCode : 200}, fixtures[options.url])
  92. else
  93. callback("Not found", {statusCode : 404})
  94. describe "RecurlyWrapper", ->
  95. beforeEach ->
  96. Settings.plans = [{
  97. planCode: "collaborator"
  98. name: "Collaborator"
  99. features:
  100. collaborators: -1
  101. versioning: true
  102. }]
  103. Settings.defaultPlanCode =
  104. collaborators: 0
  105. versioning: false
  106. describe "sign", ->
  107. before (done) ->
  108. RecurlyWrapper.sign({
  109. subscription :
  110. plan_code : "gold"
  111. name : "$$$"
  112. }, (error, signature) =>
  113. @signature = signature
  114. done()
  115. )
  116. it "should be signed correctly", ->
  117. signed = @signature.split("|")[0]
  118. query = @signature.split("|")[1]
  119. crypto.createHmac("sha1", Settings.apis.recurly.privateKey).update(query).digest("hex").should.equal signed
  120. it "should be url escaped", ->
  121. query = @signature.split("|")[1]
  122. should.equal query.match(/\[/), null
  123. query.match(/\%5B/).should.not.equal null
  124. it "should contain the passed data", ->
  125. query = querystring.parse @signature.split("|")[1]
  126. query["subscription[plan_code]"].should.equal "gold"
  127. query["subscription[name]"].should.equal "$$$"
  128. it "should contain a nonce", ->
  129. query = querystring.parse @signature.split("|")[1]
  130. should.exist query["nonce"]
  131. it "should contain a timestamp", ->
  132. query = querystring.parse @signature.split("|")[1]
  133. query["timestamp"].should.equal Math.round(Date.now() / 1000) + ""
  134. describe "_parseXml", ->
  135. it "should convert different data types into correct representations", (done) ->
  136. xml =
  137. "<?xml version=\"1.0\" encoding=\"UTF-8\"?>" +
  138. "<subscription href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96\">" +
  139. " <account href=\"https://api.recurly.com/v2/accounts/1\"/>" +
  140. " <plan href=\"https://api.recurly.com/v2/plans/gold\">" +
  141. " <plan_code>gold</plan_code>" +
  142. " <name>Gold plan</name>" +
  143. " </plan>" +
  144. " <uuid>44f83d7cba354d5b84812419f923ea96</uuid>" +
  145. " <state>active</state>" +
  146. " <unit_amount_in_cents type=\"integer\">800</unit_amount_in_cents>" +
  147. " <currency>EUR</currency>" +
  148. " <quantity type=\"integer\">1</quantity>" +
  149. " <activated_at type=\"datetime\">2011-05-27T07:00:00Z</activated_at>" +
  150. " <canceled_at nil=\"nil\"></canceled_at>" +
  151. " <expires_at nil=\"nil\"></expires_at>" +
  152. " <current_period_started_at type=\"datetime\">2011-06-27T07:00:00Z</current_period_started_at>" +
  153. " <current_period_ends_at type=\"datetime\">2011-07-27T07:00:00Z</current_period_ends_at>" +
  154. " <trial_started_at nil=\"nil\"></trial_started_at>" +
  155. " <trial_ends_at nil=\"nil\"></trial_ends_at>" +
  156. " <subscription_add_ons type=\"array\">" +
  157. " <subscription_add_on>" +
  158. " <add_on_code>ipaddresses</add_on_code>" +
  159. " <quantity>10</quantity>" +
  160. " <unit_amount_in_cents>150</unit_amount_in_cents>" +
  161. " </subscription_add_on>" +
  162. " </subscription_add_ons>" +
  163. " <a name=\"cancel\" href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/cancel\" method=\"put\"/>" +
  164. " <a name=\"terminate\" href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/terminate\" method=\"put\"/>" +
  165. " <a name=\"postpone\" href=\"https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96/postpone\" method=\"put\"/>" +
  166. "</subscription>"
  167. RecurlyWrapper._parseXml xml, (error, data) ->
  168. data.subscription.plan.plan_code.should.equal "gold"
  169. data.subscription.plan.name.should.equal "Gold plan"
  170. data.subscription.uuid.should.equal "44f83d7cba354d5b84812419f923ea96"
  171. data.subscription.state.should.equal "active"
  172. data.subscription.unit_amount_in_cents.should.equal 800
  173. data.subscription.currency.should.equal "EUR"
  174. data.subscription.quantity.should.equal 1
  175. data.subscription.activated_at.should.deep.equal new Date("2011-05-27T07:00:00Z")
  176. should.equal data.subscription.canceled_at, null
  177. should.equal data.subscription.expires_at, null
  178. data.subscription.current_period_started_at.should.deep.equal new Date("2011-06-27T07:00:00Z")
  179. data.subscription.current_period_ends_at.should.deep.equal new Date("2011-07-27T07:00:00Z")
  180. should.equal data.subscription.trial_started_at, null
  181. should.equal data.subscription.trial_ends_at, null
  182. data.subscription.subscription_add_ons.should.deep.equal [{
  183. add_on_code: "ipaddresses"
  184. quantity: "10"
  185. unit_amount_in_cents: "150"
  186. }]
  187. data.subscription.account.url.should.equal "https://api.recurly.com/v2/accounts/1"
  188. data.subscription.url.should.equal "https://api.recurly.com/v2/subscriptions/44f83d7cba354d5b84812419f923ea96"
  189. data.subscription.plan.url.should.equal "https://api.recurly.com/v2/plans/gold"
  190. done()
  191. describe "getSubscription", ->
  192. describe "with proper subscription id", ->
  193. before ->
  194. @apiRequest = sinon.stub(RecurlyWrapper, "apiRequest", mockApiRequest)
  195. RecurlyWrapper.getSubscription "44f83d7cba354d5b84812419f923ea96", (error, recurlySubscription) =>
  196. @recurlySubscription = recurlySubscription
  197. after ->
  198. RecurlyWrapper.apiRequest.restore()
  199. it "should look up the subscription at the normal API end point", ->
  200. @apiRequest.args[0][0].url.should.equal "subscriptions/44f83d7cba354d5b84812419f923ea96"
  201. it "should return the subscription", ->
  202. @recurlySubscription.uuid.should.equal "44f83d7cba354d5b84812419f923ea96"
  203. describe "with ReculyJS token", ->
  204. before ->
  205. @apiRequest = sinon.stub(RecurlyWrapper, "apiRequest", mockApiRequest)
  206. RecurlyWrapper.getSubscription "70db44b10f5f4b238669480c9903f6f5", {recurlyJsResult: true}, (error, recurlySubscription) =>
  207. @recurlySubscription = recurlySubscription
  208. after ->
  209. RecurlyWrapper.apiRequest.restore()
  210. it "should return the subscription", ->
  211. @recurlySubscription.uuid.should.equal "44f83d7cba354d5b84812419f923ea96"
  212. it "should look up the subscription at the RecurlyJS API end point", ->
  213. @apiRequest.args[0][0].url.should.equal "recurly_js/result/70db44b10f5f4b238669480c9903f6f5"
  214. describe "with includeAccount", ->
  215. beforeEach ->
  216. @apiRequest = sinon.stub(RecurlyWrapper, "apiRequest", mockApiRequest)
  217. RecurlyWrapper.getSubscription "44f83d7cba354d5b84812419f923ea96", {includeAccount: true}, (error, recurlySubscription) =>
  218. @recurlySubscription = recurlySubscription
  219. afterEach ->
  220. RecurlyWrapper.apiRequest.restore()
  221. it "should request the account from the API", ->
  222. @apiRequest.args[1][0].url.should.equal "accounts/104"
  223. it "should populate the account attribute", ->
  224. @recurlySubscription.account.account_code.should.equal "104"
  225. describe "updateSubscription", ->
  226. beforeEach (done) ->
  227. @recurlySubscriptionId = "subscription-id-123"
  228. @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) =>
  229. @requestOptions = options
  230. callback null, {}, fixtures["subscriptions/44f83d7cba354d5b84812419f923ea96"]
  231. RecurlyWrapper.updateSubscription @recurlySubscriptionId, { plan_code : "silver", timeframe: "now" }, (error, recurlySubscription) =>
  232. @recurlySubscription = recurlySubscription
  233. done()
  234. afterEach ->
  235. RecurlyWrapper.apiRequest.restore()
  236. it "should send an update request to the API", ->
  237. @apiRequest.called.should.equal true
  238. @requestOptions.body.should.equal """
  239. <subscription>
  240. <plan_code>silver</plan_code>
  241. <timeframe>now</timeframe>
  242. </subscription>
  243. """
  244. @requestOptions.url.should.equal "subscriptions/#{@recurlySubscriptionId}"
  245. @requestOptions.method.should.equal "put"
  246. it "should return the updated subscription", ->
  247. should.exist @recurlySubscription
  248. @recurlySubscription.plan.plan_code.should.equal "gold"
  249. describe "cancelSubscription", ->
  250. beforeEach (done) ->
  251. @recurlySubscriptionId = "subscription-id-123"
  252. @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) =>
  253. options.url.should.equal "subscriptions/#{@recurlySubscriptionId}/cancel"
  254. options.method.should.equal "put"
  255. callback()
  256. RecurlyWrapper.cancelSubscription(@recurlySubscriptionId, done)
  257. afterEach ->
  258. RecurlyWrapper.apiRequest.restore()
  259. it "should send a cancel request to the API", ->
  260. @apiRequest.called.should.equal true
  261. describe "reactivateSubscription", ->
  262. beforeEach (done) ->
  263. @recurlySubscriptionId = "subscription-id-123"
  264. @apiRequest = sinon.stub RecurlyWrapper, "apiRequest", (options, callback) =>
  265. options.url.should.equal "subscriptions/#{@recurlySubscriptionId}/reactivate"
  266. options.method.should.equal "put"
  267. callback()
  268. RecurlyWrapper.reactivateSubscription(@recurlySubscriptionId, done)
  269. afterEach ->
  270. RecurlyWrapper.apiRequest.restore()
  271. it "should send a cancel request to the API", ->
  272. @apiRequest.called.should.equal true