SubscriptionHandlerTests.js 25 KB

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