handle-stripe-payment-action.test.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131
  1. import sinon from 'sinon'
  2. import { expect } from 'chai'
  3. import fetchMock from 'fetch-mock'
  4. import * as stripeModule from '@stripe/stripe-js/pure'
  5. import { FetchError } from '@/infrastructure/fetch-json'
  6. import handleStripePaymentAction from '../../../../../frontend/js/features/subscription/util/handle-stripe-payment-action'
  7. describe('handleStripePaymentAction', function () {
  8. let sandbox: sinon.SinonSandbox
  9. let confirmPaymentStub: sinon.SinonStub
  10. beforeEach(function () {
  11. sandbox = sinon.createSandbox()
  12. confirmPaymentStub = sandbox.stub()
  13. sandbox.stub(stripeModule, 'loadStripe').resolves({
  14. confirmPayment: confirmPaymentStub,
  15. } as any)
  16. window.history.pushState(
  17. {},
  18. '',
  19. '/user/subscription/preview?planCode=professional-annual'
  20. )
  21. window.metaAttributesCache.set('ol-ExposedSettings', {
  22. siteUrl: 'https://www.overleaf.com',
  23. })
  24. })
  25. afterEach(function () {
  26. sandbox.restore()
  27. fetchMock.removeRoutes().clearHistory()
  28. window.history.pushState({}, '', '/')
  29. })
  30. describe('when error has no clientSecret or publicKey', function () {
  31. it('returns { handled: false } without calling Stripe', async function () {
  32. const error = new FetchError('error', 'url', undefined, undefined, {})
  33. const result = await handleStripePaymentAction(error)
  34. expect(result).to.deep.equal({ handled: false })
  35. sinon.assert.notCalled(
  36. stripeModule.loadStripe as unknown as sinon.SinonStub
  37. )
  38. })
  39. })
  40. describe('when error has clientSecret and publicKey', function () {
  41. let error: FetchError
  42. beforeEach(function () {
  43. error = new FetchError(
  44. 'Payment action required',
  45. 'url',
  46. undefined,
  47. undefined,
  48. {
  49. clientSecret: 'cs_test_123',
  50. publicKey: 'pk_test_abc',
  51. }
  52. )
  53. })
  54. it('passes return_url with full path including query string', async function () {
  55. confirmPaymentStub.resolves({})
  56. fetchMock.post('/user/subscription/sync', 200)
  57. await handleStripePaymentAction(error)
  58. sinon.assert.calledOnce(confirmPaymentStub)
  59. const { confirmParams } = confirmPaymentStub.firstCall.args[0]
  60. const returnUrl = new URL(confirmParams.return_url)
  61. expect(returnUrl.searchParams.get('path')).to.equal(
  62. '/user/subscription/preview?planCode=professional-annual'
  63. )
  64. })
  65. it('includes successPath in return_url when provided', async function () {
  66. confirmPaymentStub.resolves({})
  67. fetchMock.post('/user/subscription/sync', 200)
  68. await handleStripePaymentAction(error, {
  69. successPath: '/user/subscription/thank-you?upgrade=true',
  70. })
  71. const { confirmParams } = confirmPaymentStub.firstCall.args[0]
  72. const returnUrl = new URL(confirmParams.return_url)
  73. expect(returnUrl.searchParams.get('successPath')).to.equal(
  74. '/user/subscription/thank-you?upgrade=true'
  75. )
  76. })
  77. it('uses redirect: if_required', async function () {
  78. confirmPaymentStub.resolves({})
  79. fetchMock.post('/user/subscription/sync', 200)
  80. await handleStripePaymentAction(error)
  81. expect(confirmPaymentStub.firstCall.args[0].redirect).to.equal(
  82. 'if_required'
  83. )
  84. })
  85. describe('when confirmPayment succeeds', function () {
  86. it('returns { handled: true }', async function () {
  87. confirmPaymentStub.resolves({})
  88. fetchMock.post('/user/subscription/sync', 200)
  89. const result = await handleStripePaymentAction(error)
  90. expect(result).to.deep.equal({ handled: true })
  91. })
  92. })
  93. describe('when confirmPayment returns an error', function () {
  94. it('returns { handled: false }', async function () {
  95. confirmPaymentStub.resolves({
  96. error: { payment_intent: { id: 'pi_test_456' } },
  97. })
  98. fetchMock.post(
  99. '/user/subscription/void-change?payment_intent_id=pi_test_456',
  100. 200
  101. )
  102. const result = await handleStripePaymentAction(error)
  103. expect(result).to.deep.equal({ handled: false })
  104. })
  105. })
  106. })
  107. })