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
This commit is contained in:
Kristina
2025-07-03 09:21:42 +02:00
committed by Copybot
parent 638d0fb156
commit a15de4e18c
2 changed files with 110 additions and 0 deletions

View File

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

View File

@@ -7,6 +7,9 @@ const modulePath =
'../../../../app/src/Features/Subscription/SubscriptionController'
const SubscriptionErrors = require('../../../../app/src/Features/Subscription/Errors')
const SubscriptionHelper = require('../../../../app/src/Features/Subscription/SubscriptionHelper')
const {
AI_ADD_ON_CODE,
} = require('../../../../app/src/Features/Subscription/PaymentProviderEntities')
const mockSubscriptions = {
'subscription-123-active': {
@@ -59,6 +62,7 @@ describe('SubscriptionController', function () {
syncSubscription: sinon.stub().resolves(),
attemptPaypalInvoiceCollection: 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
})
})
})