ソースを参照

Merge pull request #26793 from overleaf/mf-add-missing-public-key-on-purchase-addon

[web] Add missing publicKey to purchase add-on flow when user need to authenticate their payment via 3ds secure flow

GitOrigin-RevId: cc330cb8dad501479bbb3c5c5b4fc32ef9d36921
Kristina 1 年間 前
親
コミット
a74c0abdf5

+ 1 - 0
services/web/app/src/Features/Subscription/SubscriptionController.js

@@ -453,6 +453,7 @@ async function purchaseAddon(req, res, next) {
       return res.status(402).json({
       return res.status(402).json({
         message: 'Payment action required',
         message: 'Payment action required',
         clientSecret: err.info.clientSecret,
         clientSecret: err.info.clientSecret,
+        publicKey: err.info.publicKey,
       })
       })
     } else {
     } else {
       if (err instanceof Error) {
       if (err instanceof Error) {

+ 109 - 0
services/web/test/unit/src/Subscription/SubscriptionControllerTests.js

@@ -7,6 +7,9 @@ const modulePath =
   '../../../../app/src/Features/Subscription/SubscriptionController'
   '../../../../app/src/Features/Subscription/SubscriptionController'
 const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Errors')
 const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Errors')
 const SubscriptionHelper = require('../../../../app/src/Features/Subscription/SubscriptionHelper')
 const SubscriptionHelper = require('../../../../app/src/Features/Subscription/SubscriptionHelper')
+const {
+  AI_ADD_ON_CODE,
+} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
 
 
 const mockSubscriptions = {
 const mockSubscriptions = {
   'subscription-123-active': {
   'subscription-123-active': {
@@ -59,6 +62,7 @@ describe('SubscriptionController', function () {
         syncSubscription: sinon.stub().resolves(),
         syncSubscription: sinon.stub().resolves(),
         attemptPaypalInvoiceCollection: sinon.stub().resolves(),
         attemptPaypalInvoiceCollection: sinon.stub().resolves(),
         startFreeTrial: sinon.stub().resolves(),
         startFreeTrial: sinon.stub().resolves(),
+        purchaseAddon: sinon.stub().resolves(),
       },
       },
     }
     }
 
 
@@ -635,4 +639,109 @@ describe('SubscriptionController', function () {
       })
       })
     })
     })
   })
   })
+
+  describe('purchaseAddon', function () {
+    beforeEach(function () {
+      this.SessionManager.getSessionUser.returns(this.user) // Make sure getSessionUser returns the user
+      this.next = sinon.stub()
+      this.req.params = { addOnCode: AI_ADD_ON_CODE } // Mock add-on code
+    })
+
+    it('should return 200 on successful purchase of AI add-on', async function () {
+      await this.SubscriptionController.purchaseAddon(
+        this.req,
+        this.res,
+        this.next
+      )
+      this.res.sendStatus = sinon.spy()
+
+      await this.SubscriptionController.purchaseAddon(
+        this.req,
+        this.res,
+        this.next
+      )
+
+      expect(this.SubscriptionHandler.promises.purchaseAddon).to.have.been
+        .called
+      expect(
+        this.SubscriptionHandler.promises.purchaseAddon
+      ).to.have.been.calledWith(this.user._id, AI_ADD_ON_CODE, 1)
+      expect(
+        this.FeaturesUpdater.promises.refreshFeatures
+      ).to.have.been.calledWith(this.user._id, 'add-on-purchase')
+      expect(this.res.sendStatus).to.have.been.calledWith(200)
+      expect(this.logger.debug).to.have.been.calledWith(
+        { userId: this.user._id, addOnCode: AI_ADD_ON_CODE },
+        'purchasing add-ons'
+      )
+    })
+
+    it('should return 404 if the add-on code is not AI_ADD_ON_CODE', async function () {
+      this.req.params = { addOnCode: 'some-other-addon' }
+      this.res.sendStatus = sinon.spy()
+
+      await this.SubscriptionController.purchaseAddon(
+        this.req,
+        this.res,
+        this.next
+      )
+
+      expect(this.SubscriptionHandler.promises.purchaseAddon).to.not.have.been
+        .called
+      expect(this.FeaturesUpdater.promises.refreshFeatures).to.not.have.been
+        .called
+      expect(this.res.sendStatus).to.have.been.calledWith(404)
+    })
+
+    it('should handle DuplicateAddOnError and send badRequest while sending 200', async function () {
+      this.req.params.addOnCode = AI_ADD_ON_CODE
+      this.SubscriptionHandler.promises.purchaseAddon.rejects(
+        new SubscriptionErrors.DuplicateAddOnError()
+      )
+
+      await this.SubscriptionController.purchaseAddon(
+        this.req,
+        this.res,
+        this.next
+      )
+
+      expect(this.HttpErrorHandler.badRequest).to.have.been.calledWith(
+        this.req,
+        this.res,
+        'Your subscription already includes this add-on',
+        { addon: AI_ADD_ON_CODE }
+      )
+      expect(
+        this.FeaturesUpdater.promises.refreshFeatures
+      ).to.have.been.calledWith(this.user._id, 'add-on-purchase')
+      expect(this.res.sendStatus).to.have.been.calledWith(200)
+    })
+
+    it('should handle PaymentActionRequiredError and return 402 with details', async function () {
+      this.req.params.addOnCode = AI_ADD_ON_CODE
+      const paymentError = new SubscriptionErrors.PaymentActionRequiredError({
+        clientSecret: 'secret123',
+        publicKey: 'pubkey456',
+      })
+      this.SubscriptionHandler.promises.purchaseAddon.rejects(paymentError)
+
+      await this.SubscriptionController.purchaseAddon(
+        this.req,
+        this.res,
+        this.next
+      )
+
+      this.res.status.calledWith(402).should.equal(true)
+      this.res.json
+        .calledWith({
+          message: 'Payment action required',
+          clientSecret: 'secret123',
+          publicKey: 'pubkey456',
+        })
+        .should.equal(true)
+
+      expect(this.FeaturesUpdater.promises.refreshFeatures).to.not.have.been
+        .called
+    })
+  })
 })
 })