Skip to content

Commit

Permalink
PP-12853 Update apple pay merchant validation to use Axios
Browse files Browse the repository at this point in the history
With this change, we are removing the use of `requestretry` from the Merchant
Validation Controller which currently uses this module.

We are replacing it by using Axios directly.

Further information in Jira.

https://payments-platform.atlassian.net/browse/PP-12853
  • Loading branch information
marcotranchino committed Jul 12, 2024
1 parent 6f35fae commit f2c3a12
Show file tree
Hide file tree
Showing 3 changed files with 86 additions and 80 deletions.
13 changes: 2 additions & 11 deletions .secrets.baseline
Original file line number Diff line number Diff line change
Expand Up @@ -118,16 +118,7 @@
"filename": "app/controllers/web-payments/apple-pay/merchant-validation.controller.js",
"hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9",
"is_verified": false,
"line_number": 14
}
],
"test/controllers/web-payments/apple-pay/merchant-validation.controller.test.js": [
{
"type": "Private Key",
"filename": "test/controllers/web-payments/apple-pay/merchant-validation.controller.test.js",
"hashed_secret": "1348b145fa1a555461c1b790a2f66614781091e9",
"is_verified": false,
"line_number": 68
"line_number": 15
}
],
"test/controllers/web-payments/apple-pay/normalise-apple-pay-payload.test.js": [
Expand Down Expand Up @@ -389,5 +380,5 @@
}
]
},
"generated_at": "2023-11-09T18:54:00Z"
"generated_at": "2024-07-09T14:54:04Z"
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
'use strict'

const request = require('requestretry')
const logger = require('../../../utils/logger')(__filename)
const { getLoggingFields } = require('../../../utils/logging-fields-helper')
const axios = require('axios')
const https = require('https')

function getCertificateMultiline (cert) {
return `-----BEGIN CERTIFICATE-----
Expand Down Expand Up @@ -38,7 +39,7 @@ function getApplePayMerchantIdentityVariables (paymentProvider) {
// When an Apple payment is initiated in Safari, it must check that the request
// is coming from a registered and authorised Apple Merchant Account. The
// browser will produce a URL which we should dial with our certificates server side.
module.exports = (req, res) => {
module.exports = async (req, res) => {
if (!req.body.url) {
return res.sendStatus(400)
}
Expand All @@ -48,30 +49,34 @@ module.exports = (req, res) => {
return res.sendStatus(400)
}

const httpsAgent = new https.Agent({
cert: merchantIdentityVars.cert,
key: merchantIdentityVars.key
})

const options = {
url: url,
cert: merchantIdentityVars.cert,
key: merchantIdentityVars.key,
method: 'post',
body: {
headers: { 'Content-Type': 'application/json' },
data: {
merchantIdentifier: merchantIdentityVars.merchantIdentifier,
displayName: 'GOV.UK Pay',
initiative: 'web',
initiativeContext: process.env.APPLE_PAY_MERCHANT_DOMAIN
},
json: true
httpsAgent
}

request(options, (err, response, body) => {
if (err) {
logger.info('Error generating Apple Pay session', {
...getLoggingFields(req),
error: err,
response: response,
body: body
})
return res.status(500).send(body)
}
res.status(200).send(body)
})
try {
const response = await axios(options)
res.status(200).send(response.data)
} catch (error) {
logger.info('Error generating Apple Pay session', {
...getLoggingFields(req),
error: error,
response: error.response,
data: error.response ? error.response.data : null
})
res.status(500).send(error.response ? error.response.data : 'Apple Pay Error')
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

const sinon = require('sinon')
const proxyquire = require('proxyquire')
const https = require('https')

const merchantDomain = 'www.pymnt.uk'
const worldpayMerchantId = 'worldpay.merchant.id'
Expand All @@ -12,17 +13,16 @@ const stripeCertificate = 'A-STRIPE-CERTIFICATE'
const stripeKey = 'A-STRIPE-KEY'
const url = 'https://fakeapple.url'

const appleResponse = { status: 200 }
const appleResponseBody = { foo: 'bar' }
const appleResponse = { status: 200, data: { foo: 'bar' } }

function getControllerWithMocks (requestMock) {
function getControllerWithMocks (axiosMock) {
return proxyquire('../../../../app/controllers/web-payments/apple-pay/merchant-validation.controller', {
requestretry: requestMock
axios: axiosMock
})
}

describe('Validate with Apple the merchant is legitimate', () => {
let res, sendSpy
let res, sendSpy, axiosStub

beforeEach(() => {
process.env.APPLE_PAY_MERCHANT_DOMAIN = merchantDomain
Expand All @@ -38,11 +38,12 @@ describe('Validate with Apple the merchant is legitimate', () => {
status: sinon.spy(() => ({ send: sendSpy })),
sendStatus: sinon.spy()
}

axiosStub = sinon.stub().resolves(appleResponse)
})

it('should return a payload for a Worldpay payment if Merchant is valid', async () => {
const mockRequest = sinon.stub().yields(null, appleResponse, appleResponseBody)
const controller = getControllerWithMocks(mockRequest)
const controller = getControllerWithMocks(axiosStub)

const req = {
body: {
Expand All @@ -52,30 +53,32 @@ describe('Validate with Apple the merchant is legitimate', () => {
}
await controller(req, res)

sinon.assert.calledWith(mockRequest, {
sinon.assert.calledWith(axiosStub, sinon.match({
url,
body: {
method: 'post',
headers: { 'Content-Type': 'application/json' },
data: {
merchantIdentifier: worldpayMerchantId,
displayName: 'GOV.UK Pay',
initiative: 'web',
initiativeContext: merchantDomain
},
method: 'post',
json: true,
cert: `-----BEGIN CERTIFICATE-----
${worldpayCertificate}
-----END CERTIFICATE-----`,
key: `-----BEGIN PRIVATE KEY-----
${worldpayKey}
-----END PRIVATE KEY-----`
httpsAgent: sinon.match.instanceOf(https.Agent)
}))

const httpsAgentArg = axiosStub.getCall(0).args[0].httpsAgent
sinon.assert.match(httpsAgentArg.options, {
cert: sinon.match(cert => cert.includes(worldpayCertificate)),
key: sinon.match(key => key.includes(worldpayKey))
})

sinon.assert.calledWith(res.status, 200)
sinon.assert.calledWith(sendSpy, appleResponseBody)
sinon.assert.calledWith(sendSpy, appleResponse.data)
})

it('should return a payload for a Stripe payment if Merchant is valid', async () => {
const mockRequest = sinon.stub().yields(null, appleResponse, appleResponseBody)
const controller = getControllerWithMocks(mockRequest)
const axiosStub = sinon.stub().resolves({ data: appleResponse.data, status: 200 })
const controller = getControllerWithMocks(axiosStub)

const req = {
body: {
Expand All @@ -85,30 +88,33 @@ ${worldpayKey}
}
await controller(req, res)

sinon.assert.calledWith(mockRequest, {
sinon.assert.calledWith(axiosStub, sinon.match({
url,
body: {
method: 'post',
headers: { 'Content-Type': 'application/json' },
data: {
merchantIdentifier: stripeMerchantId,
displayName: 'GOV.UK Pay',
initiative: 'web',
initiativeContext: merchantDomain
},
method: 'post',
json: true,
cert: `-----BEGIN CERTIFICATE-----
${stripeCertificate}
-----END CERTIFICATE-----`,
key: `-----BEGIN PRIVATE KEY-----
${stripeKey}
-----END PRIVATE KEY-----`
httpsAgent: sinon.match.instanceOf(https.Agent)
}))

const httpsAgentArg = axiosStub.getCall(0).args[0].httpsAgent
sinon.assert.match(httpsAgentArg.options, {
cert: sinon.match(cert => cert.includes(stripeCertificate)),
key: sinon.match(key => key.includes(stripeKey))
})

sinon.assert.calledWith(res.status, 200)
sinon.assert.calledWith(sendSpy, appleResponseBody)
sinon.assert.calledWith(sendSpy, appleResponse.data)
})


it('should return 400 if no url is provided', async () => {
const mockRequest = sinon.stub().yields(null, appleResponse, appleResponseBody)
const controller = getControllerWithMocks(mockRequest)
const axiosStub = sinon.stub().resolves({ data: appleResponse.data, status: 200 })
const controller = getControllerWithMocks(axiosStub)

const req = {
body: {
Expand All @@ -117,11 +123,12 @@ ${stripeKey}
}
await controller(req, res)
sinon.assert.calledWith(res.sendStatus, 400)
sinon.assert.notCalled(axiosStub)
})

it('should return a payload for a Sandbox payment if Merchant is valid', async () => {
const mockRequest = sinon.stub().yields(null, appleResponse, appleResponseBody)
const controller = getControllerWithMocks(mockRequest)
const axiosStub = sinon.stub().resolves({ data: appleResponse.data, status: 200 })
const controller = getControllerWithMocks(axiosStub)

const req = {
body: {
Expand All @@ -131,30 +138,32 @@ ${stripeKey}
}
await controller(req, res)

sinon.assert.calledWith(mockRequest, {
sinon.assert.calledWith(axiosStub, sinon.match({
url,
body: {
method: 'post',
headers: { 'Content-Type': 'application/json' },
data: {
merchantIdentifier: worldpayMerchantId,
displayName: 'GOV.UK Pay',
initiative: 'web',
initiativeContext: merchantDomain
},
method: 'post',
json: true,
cert: `-----BEGIN CERTIFICATE-----
${worldpayCertificate}
-----END CERTIFICATE-----`,
key: `-----BEGIN PRIVATE KEY-----
${worldpayKey}
-----END PRIVATE KEY-----`
httpsAgent: sinon.match.instanceOf(https.Agent)
}))

const httpsAgentArg = axiosStub.getCall(0).args[0].httpsAgent
sinon.assert.match(httpsAgentArg.options, {
cert: sinon.match(cert => cert.includes(worldpayCertificate)),
key: sinon.match(key => key.includes(worldpayKey))
})

sinon.assert.calledWith(res.status, 200)
sinon.assert.calledWith(sendSpy, appleResponseBody)
sinon.assert.calledWith(sendSpy, appleResponse.data)
})

it('should return 400 for unexpected payment provider', async () => {
const mockRequest = sinon.stub().yields(null, appleResponse, appleResponseBody)
const controller = getControllerWithMocks(mockRequest)
const axiosStub = sinon.stub().resolves({ data: appleResponse.data, status: 200 })
const controller = getControllerWithMocks(axiosStub)

const req = {
body: {
Expand All @@ -164,11 +173,12 @@ ${worldpayKey}
}
await controller(req, res)
sinon.assert.calledWith(res.sendStatus, 400)
sinon.assert.notCalled(axiosStub)
})

it('should return an error if Apple Pay returns an error', async () => {
const mockRequest = sinon.stub().yields(new Error(), appleResponse, appleResponseBody)
const controller = getControllerWithMocks(mockRequest)
const axiosErrorStub = sinon.stub().rejects(new Error('Whatever error from Apple Pay'))
const controller = getControllerWithMocks(axiosErrorStub)

const req = {
body: {
Expand All @@ -178,6 +188,6 @@ ${worldpayKey}
}
await controller(req, res)
sinon.assert.calledWith(res.status, 500)
sinon.assert.calledWith(sendSpy, appleResponseBody)
sinon.assert.calledWith(sendSpy, 'Apple Pay Error')
})
})

0 comments on commit f2c3a12

Please sign in to comment.