SubscriptionHandlerTests.js 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790
  1. const SandboxedModule = require('sandboxed-module')
  2. const sinon = require('sinon')
  3. const chai = require('chai')
  4. const { expect } = chai
  5. const {
  6. RecurlySubscription,
  7. RecurlySubscriptionChangeRequest,
  8. } = require('../../../../app/src/Features/Subscription/RecurlyEntities')
  9. const MODULE_PATH =
  10. '../../../../app/src/Features/Subscription/SubscriptionHandler'
  11. const mockRecurlySubscriptions = {
  12. 'subscription-123-active': {
  13. uuid: 'subscription-123-active',
  14. plan: {
  15. name: 'Gold',
  16. plan_code: 'gold',
  17. },
  18. current_period_ends_at: new Date(),
  19. state: 'active',
  20. unit_amount_in_cents: 999,
  21. account: {
  22. account_code: 'user-123',
  23. },
  24. },
  25. }
  26. const mockRecurlyClientSubscriptions = {
  27. 'subscription-123-active': new RecurlySubscription({
  28. id: 'subscription-123-active',
  29. userId: 'user-id',
  30. planCode: 'collaborator',
  31. planName: 'Collaborator',
  32. planPrice: 10,
  33. subtotal: 10,
  34. currency: 'USD',
  35. total: 10,
  36. }),
  37. }
  38. const mockSubscriptionChanges = {
  39. 'subscription-123-active': {
  40. id: 'subscription-change-id',
  41. subscriptionId: 'subscription-123-recurly-id', // not the UUID
  42. },
  43. }
  44. describe('SubscriptionHandler', function () {
  45. beforeEach(function () {
  46. this.Settings = {
  47. plans: [
  48. {
  49. planCode: 'collaborator',
  50. name: 'Collaborator',
  51. price_in_cents: 1000,
  52. features: {
  53. collaborators: -1,
  54. versioning: true,
  55. },
  56. },
  57. {
  58. planCode: 'professional',
  59. price_in_cents: 1500,
  60. },
  61. ],
  62. defaultPlanCode: {
  63. collaborators: 0,
  64. versioning: false,
  65. },
  66. }
  67. this.activeRecurlySubscription =
  68. mockRecurlySubscriptions['subscription-123-active']
  69. this.activeRecurlyClientSubscription =
  70. mockRecurlyClientSubscriptions['subscription-123-active']
  71. this.activeRecurlySubscriptionChange =
  72. mockSubscriptionChanges['subscription-123-active']
  73. this.User = {}
  74. this.user = { _id: (this.user_id = 'user_id_here_') }
  75. this.subscription = {
  76. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  77. }
  78. this.RecurlyWrapper = {
  79. promises: {
  80. getSubscription: sinon.stub().resolves(this.activeRecurlySubscription),
  81. redeemCoupon: sinon.stub().resolves(),
  82. createSubscription: sinon
  83. .stub()
  84. .resolves(this.activeRecurlySubscription),
  85. getBillingInfo: sinon.stub().resolves(),
  86. getAccountPastDueInvoices: sinon.stub().resolves(),
  87. attemptInvoiceCollection: sinon.stub().resolves(),
  88. listAccountActiveSubscriptions: sinon.stub().resolves([]),
  89. },
  90. }
  91. this.RecurlyClient = {
  92. promises: {
  93. reactivateSubscriptionByUuid: sinon
  94. .stub()
  95. .resolves(this.activeRecurlyClientSubscription),
  96. cancelSubscriptionByUuid: sinon.stub().resolves(),
  97. applySubscriptionChangeRequest: sinon
  98. .stub()
  99. .resolves(this.activeRecurlySubscriptionChange),
  100. getSubscription: sinon
  101. .stub()
  102. .resolves(this.activeRecurlyClientSubscription),
  103. pauseSubscriptionByUuid: sinon.stub().resolves(),
  104. resumeSubscriptionByUuid: sinon.stub().resolves(),
  105. },
  106. }
  107. this.SubscriptionUpdater = {
  108. promises: {
  109. updateSubscriptionFromRecurly: sinon.stub().resolves(),
  110. syncSubscription: sinon.stub().resolves(),
  111. startFreeTrial: sinon.stub().resolves(),
  112. },
  113. }
  114. this.LimitationsManager = {
  115. promises: {
  116. userHasSubscription: sinon.stub().resolves(),
  117. },
  118. }
  119. this.SubscriptionLocator = {
  120. promises: {
  121. getUsersSubscription: sinon.stub().resolves(this.subscription),
  122. },
  123. }
  124. this.EmailHandler = {
  125. sendEmail: sinon.stub(),
  126. sendDeferredEmail: sinon.stub(),
  127. }
  128. this.UserUpdater = {
  129. promises: {
  130. updateUser: sinon.stub().resolves(),
  131. },
  132. }
  133. this.SubscriptionHandler = SandboxedModule.require(MODULE_PATH, {
  134. requires: {
  135. './RecurlyWrapper': this.RecurlyWrapper,
  136. './RecurlyClient': this.RecurlyClient,
  137. '@overleaf/settings': this.Settings,
  138. '../../models/User': {
  139. User: this.User,
  140. },
  141. './SubscriptionUpdater': this.SubscriptionUpdater,
  142. './SubscriptionLocator': this.SubscriptionLocator,
  143. './LimitationsManager': this.LimitationsManager,
  144. '../Email/EmailHandler': this.EmailHandler,
  145. '../Analytics/AnalyticsManager': this.AnalyticsManager,
  146. '../User/UserUpdater': this.UserUpdater,
  147. },
  148. })
  149. })
  150. describe('createSubscription', function () {
  151. beforeEach(function () {
  152. this.subscriptionDetails = {
  153. cvv: '123',
  154. number: '12345',
  155. }
  156. this.recurlyTokenIds = { billing: '45555666' }
  157. })
  158. describe('successfully', function () {
  159. beforeEach(async function () {
  160. await this.SubscriptionHandler.promises.createSubscription(
  161. this.user,
  162. this.subscriptionDetails,
  163. this.recurlyTokenIds
  164. )
  165. })
  166. it('should create the subscription with the wrapper', function () {
  167. this.RecurlyWrapper.promises.createSubscription
  168. .calledWith(this.user, this.subscriptionDetails, this.recurlyTokenIds)
  169. .should.equal(true)
  170. })
  171. it('should sync the subscription to the user', function () {
  172. this.SubscriptionUpdater.promises.syncSubscription.calledOnce.should.equal(
  173. true
  174. )
  175. this.SubscriptionUpdater.promises.syncSubscription.args[0][0].should.deep.equal(
  176. this.activeRecurlySubscription
  177. )
  178. this.SubscriptionUpdater.promises.syncSubscription.args[0][1].should.deep.equal(
  179. this.user._id
  180. )
  181. })
  182. it('should not set last trial date if not a trial/the trial_started_at is not set', function () {
  183. this.UserUpdater.promises.updateUser.should.not.have.been.called
  184. })
  185. })
  186. describe('when the subscription is a trial and has a trial_started_at date', function () {
  187. beforeEach(async function () {
  188. this.activeRecurlySubscription.trial_started_at =
  189. '2024-01-01T09:58:35.531+00:00'
  190. await this.SubscriptionHandler.promises.createSubscription(
  191. this.user,
  192. this.subscriptionDetails,
  193. this.recurlyTokenIds
  194. )
  195. })
  196. it('should set the users lastTrial date', function () {
  197. this.UserUpdater.promises.updateUser.should.have.been.calledOnce
  198. expect(this.UserUpdater.promises.updateUser.args[0][0]).to.deep.equal({
  199. _id: this.user_id,
  200. lastTrial: {
  201. $not: {
  202. $gt: new Date(this.activeRecurlySubscription.trial_started_at),
  203. },
  204. },
  205. })
  206. expect(this.UserUpdater.promises.updateUser.args[0][1]).to.deep.equal({
  207. $set: {
  208. lastTrial: new Date(
  209. this.activeRecurlySubscription.trial_started_at
  210. ),
  211. },
  212. })
  213. })
  214. })
  215. describe('when there is already a subscription in Recurly', function () {
  216. beforeEach(function () {
  217. this.RecurlyWrapper.promises.listAccountActiveSubscriptions.resolves([
  218. this.subscription,
  219. ])
  220. })
  221. it('should an error', function () {
  222. expect(
  223. this.SubscriptionHandler.promises.createSubscription(
  224. this.user,
  225. this.subscriptionDetails,
  226. this.recurlyTokenIds
  227. )
  228. ).to.be.rejectedWith('user already has subscription in recurly')
  229. })
  230. })
  231. })
  232. describe('updateSubscription', function () {
  233. describe('with a user with a subscription', function () {
  234. beforeEach(async function () {
  235. this.user.id = this.activeRecurlySubscription.account.account_code
  236. this.User.findById = (userId, projection) => ({
  237. exec: () => {
  238. userId.should.equal(this.user.id)
  239. return Promise.resolve(this.user)
  240. },
  241. })
  242. this.plan_code = 'professional'
  243. this.LimitationsManager.promises.userHasSubscription.resolves({
  244. hasSubscription: true,
  245. subscription: this.subscription,
  246. })
  247. await this.SubscriptionHandler.promises.updateSubscription(
  248. this.user,
  249. this.plan_code,
  250. null
  251. )
  252. })
  253. it('should update the subscription', function () {
  254. expect(
  255. this.RecurlyClient.promises.applySubscriptionChangeRequest
  256. ).to.have.been.calledWith(
  257. new RecurlySubscriptionChangeRequest({
  258. subscription: this.activeRecurlyClientSubscription,
  259. timeframe: 'now',
  260. planCode: this.plan_code,
  261. })
  262. )
  263. })
  264. it('should sync the new subscription to the user', function () {
  265. expect(this.SubscriptionUpdater.promises.syncSubscription).to.have.been
  266. .called
  267. this.SubscriptionUpdater.promises.syncSubscription.args[0][0].should.deep.equal(
  268. this.activeRecurlySubscription
  269. )
  270. this.SubscriptionUpdater.promises.syncSubscription.args[0][1].should.deep.equal(
  271. this.user._id
  272. )
  273. })
  274. })
  275. describe('when plan(s) could not be located in settings', function () {
  276. beforeEach(async function () {
  277. this.user.id = this.activeRecurlySubscription.account.account_code
  278. this.User.findById = (userId, projection) => ({
  279. exec: () => {
  280. userId.should.equal(this.user.id)
  281. return Promise.resolve(this.user)
  282. },
  283. })
  284. this.LimitationsManager.promises.userHasSubscription.resolves({
  285. hasSubscription: true,
  286. subscription: this.subscription,
  287. })
  288. })
  289. it('should be rejected and should not update the subscription', function () {
  290. expect(
  291. this.SubscriptionHandler.promises.updateSubscription(
  292. this.user,
  293. 'unknown-plan',
  294. null
  295. )
  296. ).to.be.rejected
  297. this.RecurlyClient.promises.applySubscriptionChangeRequest.called.should.equal(
  298. false
  299. )
  300. })
  301. })
  302. describe('with a user without a subscription', function () {
  303. beforeEach(async function () {
  304. this.LimitationsManager.promises.userHasSubscription.resolves(false)
  305. await this.SubscriptionHandler.promises.updateSubscription(
  306. this.user,
  307. this.plan_code,
  308. null
  309. )
  310. })
  311. it('should redirect to the subscription dashboard', function () {
  312. this.RecurlyClient.promises.applySubscriptionChangeRequest.called.should.equal(
  313. false
  314. )
  315. this.SubscriptionUpdater.promises.syncSubscription.called.should.equal(
  316. false
  317. )
  318. })
  319. })
  320. describe('with a coupon code', function () {
  321. beforeEach(async function () {
  322. this.user.id = this.activeRecurlySubscription.account.account_code
  323. this.User.findById = (userId, projection) => ({
  324. exec: () => {
  325. userId.should.equal(this.user.id)
  326. return Promise.resolve(this.user)
  327. },
  328. })
  329. this.plan_code = 'collaborator'
  330. this.coupon_code = '1231312'
  331. this.LimitationsManager.promises.userHasSubscription.resolves({
  332. hasSubscription: true,
  333. subscription: this.subscription,
  334. })
  335. await this.SubscriptionHandler.promises.updateSubscription(
  336. this.user,
  337. this.plan_code,
  338. this.coupon_code
  339. )
  340. })
  341. it('should get the users account', function () {
  342. this.RecurlyWrapper.promises.getSubscription
  343. .calledWith(this.activeRecurlySubscription.uuid)
  344. .should.equal(true)
  345. })
  346. it('should redeem the coupon', function () {
  347. this.RecurlyWrapper.promises.redeemCoupon
  348. .calledWith(
  349. this.activeRecurlySubscription.account.account_code,
  350. this.coupon_code
  351. )
  352. .should.equal(true)
  353. })
  354. it('should update the subscription', function () {
  355. expect(
  356. this.RecurlyClient.promises.applySubscriptionChangeRequest
  357. ).to.be.calledWith(
  358. new RecurlySubscriptionChangeRequest({
  359. subscription: this.activeRecurlyClientSubscription,
  360. timeframe: 'now',
  361. planCode: this.plan_code,
  362. })
  363. )
  364. })
  365. })
  366. })
  367. describe('cancelSubscription', function () {
  368. describe('with a user without a subscription', function () {
  369. beforeEach(async function () {
  370. this.LimitationsManager.promises.userHasSubscription.resolves({
  371. hasSubscription: false,
  372. subscription: this.subscription,
  373. })
  374. await this.SubscriptionHandler.promises.cancelSubscription(this.user)
  375. })
  376. it('should redirect to the subscription dashboard', function () {
  377. this.RecurlyClient.promises.cancelSubscriptionByUuid.called.should.equal(
  378. false
  379. )
  380. })
  381. })
  382. describe('with a user with a subscription', function () {
  383. beforeEach(async function () {
  384. this.LimitationsManager.promises.userHasSubscription.resolves({
  385. hasSubscription: true,
  386. subscription: this.subscription,
  387. })
  388. await this.SubscriptionHandler.promises.cancelSubscription(this.user)
  389. })
  390. it('should cancel the subscription', function () {
  391. this.RecurlyClient.promises.cancelSubscriptionByUuid.called.should.equal(
  392. true
  393. )
  394. this.RecurlyClient.promises.cancelSubscriptionByUuid
  395. .calledWith(this.subscription.recurlySubscription_id)
  396. .should.equal(true)
  397. })
  398. it('should send the email after 1 hour', function () {
  399. const ONE_HOUR_IN_MS = 1000 * 60 * 60
  400. expect(this.EmailHandler.sendDeferredEmail).to.have.been.calledWith(
  401. 'canceledSubscription',
  402. { to: this.user.email, first_name: this.user.first_name },
  403. ONE_HOUR_IN_MS
  404. )
  405. })
  406. })
  407. })
  408. describe('resumeSubscription', function () {
  409. describe('for a user without a subscription', function () {
  410. beforeEach(async function () {
  411. this.LimitationsManager.promises.userHasSubscription.resolves({
  412. hasSubscription: false,
  413. subscription: this.subscription,
  414. })
  415. })
  416. it('should not make a resume call to recurly', async function () {
  417. expect(
  418. this.SubscriptionHandler.promises.resumeSubscription(this.user)
  419. ).to.be.rejectedWith('No active subscription to resume')
  420. this.RecurlyClient.promises.resumeSubscriptionByUuid.called.should.equal(
  421. false
  422. )
  423. })
  424. })
  425. describe('for a user with a subscription', function () {
  426. beforeEach(async function () {
  427. this.LimitationsManager.promises.userHasSubscription.resolves({
  428. hasSubscription: true,
  429. subscription: {
  430. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  431. recurlyStatus: { state: 'non-trial' },
  432. planCode: 'collaborator',
  433. },
  434. })
  435. })
  436. it('should make a resume call to recurly', async function () {
  437. await this.SubscriptionHandler.promises.resumeSubscription(this.user)
  438. this.RecurlyClient.promises.resumeSubscriptionByUuid.called.should.equal(
  439. true
  440. )
  441. })
  442. })
  443. })
  444. describe('pauseSubscription', function () {
  445. describe('for a user without a subscription', function () {
  446. beforeEach(async function () {
  447. this.LimitationsManager.promises.userHasSubscription.resolves({
  448. hasSubscription: false,
  449. subscription: this.subscription,
  450. })
  451. })
  452. it('should not make a pause call to recurly', async function () {
  453. expect(
  454. this.SubscriptionHandler.promises.pauseSubscription(this.user, 3)
  455. ).to.be.rejectedWith('No active subscription to pause')
  456. this.RecurlyClient.promises.pauseSubscriptionByUuid.called.should.equal(
  457. false
  458. )
  459. })
  460. })
  461. describe('for a user with an annual subscription', function () {
  462. beforeEach(async function () {
  463. this.LimitationsManager.promises.userHasSubscription.resolves({
  464. hasSubscription: false,
  465. subscription: {
  466. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  467. recurlyStatus: { state: 'non-trial' },
  468. planCode: 'collaborator-annual',
  469. },
  470. })
  471. })
  472. it('should not make a pause call to recurly', async function () {
  473. expect(
  474. this.SubscriptionHandler.promises.pauseSubscription(this.user, 3)
  475. ).to.be.rejectedWith('Can only pause monthly individual plans')
  476. this.RecurlyClient.promises.pauseSubscriptionByUuid.called.should.equal(
  477. false
  478. )
  479. })
  480. })
  481. describe('for a user with a subscription', function () {
  482. beforeEach(async function () {
  483. this.LimitationsManager.promises.userHasSubscription.resolves({
  484. hasSubscription: true,
  485. subscription: {
  486. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  487. recurlyStatus: { state: 'non-trial' },
  488. planCode: 'collaborator',
  489. addOns: [],
  490. },
  491. })
  492. })
  493. it('should make a pause call to recurly', async function () {
  494. await this.SubscriptionHandler.promises.pauseSubscription(this.user, 3)
  495. this.RecurlyClient.promises.pauseSubscriptionByUuid.called.should.equal(
  496. true
  497. )
  498. })
  499. })
  500. describe('for a user in a trial', function () {
  501. beforeEach(async function () {
  502. this.LimitationsManager.promises.userHasSubscription.resolves({
  503. hasSubscription: true,
  504. subscription: {
  505. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  506. recurlyStatus: {
  507. state: 'trial',
  508. trialEndsAt: Date.now() + 1000000,
  509. },
  510. planCode: 'collaborator',
  511. },
  512. })
  513. })
  514. it('should not make a pause call to recurly', async function () {
  515. expect(
  516. this.SubscriptionHandler.promises.pauseSubscription(this.user, 3)
  517. ).to.be.rejectedWith('Cannot pause a subscription in a trial')
  518. this.RecurlyClient.promises.pauseSubscriptionByUuid.called.should.equal(
  519. false
  520. )
  521. })
  522. })
  523. describe('for a user with addons', function () {
  524. beforeEach(async function () {
  525. this.LimitationsManager.promises.userHasSubscription.resolves({
  526. hasSubscription: true,
  527. subscription: {
  528. recurlySubscription_id: this.activeRecurlySubscription.uuid,
  529. recurlyStatus: { state: 'non-trial' },
  530. planCode: 'collaborator',
  531. addOns: ['mock-addon'],
  532. },
  533. })
  534. })
  535. it('should not make a pause call to recurly', async function () {
  536. expect(
  537. this.SubscriptionHandler.promises.pauseSubscription(this.user, 3)
  538. ).to.be.rejectedWith('Cannot pause a subscription with addons')
  539. this.RecurlyClient.promises.pauseSubscriptionByUuid.called.should.equal(
  540. false
  541. )
  542. })
  543. })
  544. })
  545. describe('reactivateSubscription', function () {
  546. describe('with a user without a subscription', function () {
  547. beforeEach(async function () {
  548. this.LimitationsManager.promises.userHasSubscription.resolves({
  549. hasSubscription: false,
  550. subscription: this.subscription,
  551. })
  552. await this.SubscriptionHandler.promises.reactivateSubscription(
  553. this.user
  554. )
  555. })
  556. it('should redirect to the subscription dashboard', function () {
  557. this.RecurlyClient.promises.reactivateSubscriptionByUuid.called.should.equal(
  558. false
  559. )
  560. })
  561. it('should not send a notification email', function () {
  562. sinon.assert.notCalled(this.EmailHandler.sendEmail)
  563. })
  564. })
  565. describe('with a user with a subscription', function () {
  566. beforeEach(async function () {
  567. this.LimitationsManager.promises.userHasSubscription.resolves({
  568. hasSubscription: true,
  569. subscription: this.subscription,
  570. })
  571. await this.SubscriptionHandler.promises.reactivateSubscription(
  572. this.user
  573. )
  574. })
  575. it('should reactivate the subscription', function () {
  576. this.RecurlyClient.promises.reactivateSubscriptionByUuid.called.should.equal(
  577. true
  578. )
  579. this.RecurlyClient.promises.reactivateSubscriptionByUuid
  580. .calledWith(this.subscription.recurlySubscription_id)
  581. .should.equal(true)
  582. })
  583. it('should send a notification email', function () {
  584. sinon.assert.calledWith(
  585. this.EmailHandler.sendEmail,
  586. 'reactivatedSubscription'
  587. )
  588. })
  589. })
  590. })
  591. describe('syncSubscription', function () {
  592. describe('with an actionable request', function () {
  593. beforeEach(async function () {
  594. this.user.id = this.activeRecurlySubscription.account.account_code
  595. this.User.findById = (userId, projection) => ({
  596. exec: () => {
  597. userId.should.equal(this.user.id)
  598. return Promise.resolve(this.user)
  599. },
  600. })
  601. await this.SubscriptionHandler.promises.syncSubscription(
  602. this.activeRecurlySubscription,
  603. {}
  604. )
  605. })
  606. it('should request the affected subscription from the API', function () {
  607. this.RecurlyWrapper.promises.getSubscription
  608. .calledWith(this.activeRecurlySubscription.uuid)
  609. .should.equal(true)
  610. })
  611. it('should request the account details of the subscription', function () {
  612. const options = this.RecurlyWrapper.promises.getSubscription.args[0][1]
  613. options.includeAccount.should.equal(true)
  614. })
  615. it('should sync the subscription to the user', function () {
  616. this.SubscriptionUpdater.promises.syncSubscription.calledOnce.should.equal(
  617. true
  618. )
  619. this.SubscriptionUpdater.promises.syncSubscription.args[0][0].should.deep.equal(
  620. this.activeRecurlySubscription
  621. )
  622. this.SubscriptionUpdater.promises.syncSubscription.args[0][1].should.deep.equal(
  623. this.user._id
  624. )
  625. })
  626. })
  627. })
  628. describe('attemptPaypalInvoiceCollection', function () {
  629. describe('for credit card users', function () {
  630. beforeEach(async function () {
  631. this.RecurlyWrapper.promises.getBillingInfo.resolves({
  632. paypal_billing_agreement_id: null,
  633. })
  634. await this.SubscriptionHandler.promises.attemptPaypalInvoiceCollection(
  635. this.activeRecurlySubscription.account.account_code
  636. )
  637. })
  638. it('gets billing infos', function () {
  639. sinon.assert.calledWith(
  640. this.RecurlyWrapper.promises.getBillingInfo,
  641. this.activeRecurlySubscription.account.account_code
  642. )
  643. })
  644. it('skips user', function () {
  645. sinon.assert.notCalled(
  646. this.RecurlyWrapper.promises.getAccountPastDueInvoices
  647. )
  648. })
  649. })
  650. describe('for paypal users', function () {
  651. beforeEach(async function () {
  652. this.RecurlyWrapper.promises.getBillingInfo.resolves({
  653. paypal_billing_agreement_id: 'mock-billing-agreement',
  654. })
  655. this.RecurlyWrapper.promises.getAccountPastDueInvoices.resolves([
  656. { invoice_number: 'mock-invoice-number' },
  657. ])
  658. await this.SubscriptionHandler.promises.attemptPaypalInvoiceCollection(
  659. this.activeRecurlySubscription.account.account_code
  660. )
  661. })
  662. it('gets past due invoices', function () {
  663. sinon.assert.calledWith(
  664. this.RecurlyWrapper.promises.getAccountPastDueInvoices,
  665. this.activeRecurlySubscription.account.account_code
  666. )
  667. })
  668. it('calls attemptInvoiceCollection', function () {
  669. sinon.assert.calledWith(
  670. this.RecurlyWrapper.promises.attemptInvoiceCollection,
  671. 'mock-invoice-number'
  672. )
  673. })
  674. })
  675. })
  676. describe('validateNoSubscriptionInRecurly', function () {
  677. describe('with a subscription in recurly', function () {
  678. beforeEach(async function () {
  679. this.RecurlyWrapper.promises.listAccountActiveSubscriptions.resolves([
  680. this.subscription,
  681. ])
  682. this.isValid =
  683. await this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly(
  684. this.user_id
  685. )
  686. })
  687. it('should call RecurlyWrapper.promises.listAccountActiveSubscriptions with the user id', function () {
  688. this.RecurlyWrapper.promises.listAccountActiveSubscriptions
  689. .calledWith(this.user_id)
  690. .should.equal(true)
  691. })
  692. it('should sync the subscription', function () {
  693. this.SubscriptionUpdater.promises.syncSubscription
  694. .calledWith(this.subscription, this.user_id)
  695. .should.equal(true)
  696. })
  697. it('should return false', function () {
  698. expect(this.isValid).to.equal(false)
  699. })
  700. })
  701. describe('with no subscription in recurly', function () {
  702. beforeEach(async function () {
  703. this.isValid =
  704. await this.SubscriptionHandler.promises.validateNoSubscriptionInRecurly(
  705. this.user_id
  706. )
  707. })
  708. it('should be rejected and not sync the subscription', function () {
  709. this.SubscriptionUpdater.promises.syncSubscription.called.should.equal(
  710. false
  711. )
  712. })
  713. it('should return true', function () {
  714. expect(this.isValid).to.equal(true)
  715. })
  716. })
  717. })
  718. })