Skip to content

Commit

Permalink
PP-11288-Improve Validation on Prefilled Payment Links
Browse files Browse the repository at this point in the history
- Updated the session data handling logic which determined a prefilled journey and used the paymentLinkSession attribute
- Added test for the amount page for non-prefilled payment link scenario.
- Added test for the confirmation page prefilled payment link scenario.
  • Loading branch information
olatomgds committed Nov 14, 2023
1 parent 4642e35 commit 552bdef
Show file tree
Hide file tree
Showing 6 changed files with 95 additions and 46 deletions.
2 changes: 0 additions & 2 deletions app/payment-links/amount/amount.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -73,8 +73,6 @@ function postPage (req, res, next) {
const paymentAmountInPence = convertPoundsAndPenceToPence(paymentAmount)

paymentLinkSession.setAmount(req, product.externalId, paymentAmountInPence)
paymentLinkSession.setPrefilledFlag(req, product.externalId, 'false')

return res.redirect(replaceParamsInPath(paths.paymentLinks.confirm, product.externalId))
}

Expand Down
26 changes: 26 additions & 0 deletions app/payment-links/amount/amount.controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,32 @@ describe('Amount Page Controller', () => {
sinon.assert.calledWith(res.locals.__p, 'paymentLinks.fieldValidation.enterAnAmountInPounds')
})

it('when a zero value amount is entered, it should display an error message and the back link correctly', () => {
req = {
correlationId: '123',
product,
body: {
'payment-amount': '0.00'
}
}

res = {
redirect: sinon.spy(),
locals: {
__p: sinon.spy()
}
}

controller.postPage(req, res)

sinon.assert.calledWith(responseSpy, req, res, 'amount/amount')

const pageData = mockResponses.response.args[0][3]
expect(pageData.backLinkHref).to.equal('/pay/an-external-id/reference')

sinon.assert.calledWith(res.locals.__p, 'paymentLinks.fieldValidation.enterANonZeroAmountInPounds')
})

it('when an invalid amount is entered and the change query parameter is present, it should display an error' +
'message and set the back link to the CONFIRM page', () => {
req = {
Expand Down
14 changes: 7 additions & 7 deletions app/payment-links/confirm/confirm.controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ const captcha = require('../../utils/captcha')
const logger = require('../../utils/logger')(__filename)
const productsClient = require('../../clients/products/products.client')
const paymentLinkSession = require('../utils/payment-link-session')
const { validateAmount } = require('../../utils/validation/form-validations')
const formValidation = require('../../utils/validation/form-validations')
const { convertPenceToPoundsAndPence } = require('../../utils/currency')
const { AccountCannotTakePaymentsError } = require('../../errors')

const HIDDEN_FORM_FIELD_ID_REFERENCE_VALUE = 'reference-value'
const HIDDEN_FORM_FIELD_ID_AMOUNT = 'amount'
const GOOGLE_RECAPTCHA_FORM_NAME = 'g-recaptcha-response'
const ERROR_KEY_RECAPTCHA = 'recaptcha'
const CONTACT_SERVICE_ZERO_VALUE_PAYMENT_ERROR = 'error.contactServiceForZeroValuePayment'

function getBackLinkUrl (product, referenceProvidedByQueryParams, amountProvidedByQueryParams) {
if (!product.price && !amountProvidedByQueryParams) {
Expand Down Expand Up @@ -106,13 +107,12 @@ async function postPage (req, res, next) {
const sessionAmount = paymentLinkSession.getAmount(req, product.externalId)
const referenceProvidedByQueryParams = paymentLinkSession.getReferenceProvidedByQueryParams(req, product.externalId)
const amountProvidedByQueryParams = paymentLinkSession.getAmountProvidedByQueryParams(req, product.externalId)
const isPrefilledPayment = paymentLinkSession.getPrefilledFlag(req, product.externalId)

if (isPrefilledPayment !== 'false' &&
validateAmount(amountProvidedByQueryParams) &&
!validateAmount(amountProvidedByQueryParams).valid) {
const zeroAmountErrorMessagePath = 'error.contactServiceForZeroValuePayment'
return renderErrorView(req, res, zeroAmountErrorMessagePath, 400)
if (amountProvidedByQueryParams &&
formValidation.validateAmount(sessionAmount.toString()) &&
!formValidation.validateAmount(sessionAmount.toString()).valid
) {
return renderErrorView(req, res, CONTACT_SERVICE_ZERO_VALUE_PAYMENT_ERROR, 400)
}

if (product.requireCaptcha) {
Expand Down
83 changes: 59 additions & 24 deletions app/payment-links/confirm/confirm.controller.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -521,37 +521,72 @@ describe('Confirm Page Controller', () => {
sinon.assert.calledWith(next, expectedError)
})

it('when creating a payment returns a 403, should call next() with an AccountCannotTakePaymentsError', async () => {
req = {
correlationId: '123',
product,
body: {
amount: '1000'
it('when creating a prefilled payment link with an amount equals to £0.00, should render an error view with the correct message',
async () => {
req = {
correlationId: '123',
product,
body: {
amount: '0'
}
}
}

res = {
redirect: sinon.stub()
}
res = {
setHeader: sinon.stub(),
status: sinon.stub(),
render: sinon.stub(),
locals: {
__p: sinon.stub()
}
}

const next = sinon.stub()
const next = sinon.stub()

const error = new Error('Failed to create payment.')
error.errorCode = 403
mockProductsClient.payment.create.rejects(error)
mockPaymentLinkSession.getReference.withArgs(req, product.externalId).returns('test invoice number')
mockPaymentLinkSession.getAmount.withArgs(req, product.externalId).returns(0)
mockPaymentLinkSession.getReferenceProvidedByQueryParams.withArgs(req, product.externalId).returns(true)
mockPaymentLinkSession.getAmountProvidedByQueryParams.withArgs(req, product.externalId).returns(true)
res.locals.__p.withArgs('paymentLinks.confirm.totalToPay').returns('Total to pay')

await controller.postPage(req, res, next)
await controller.postPage(req, res, next)

sinon.assert.calledWith(
mockProductsClient.payment.create,
'an-external-id',
1000
)
sinon.assert.calledWith(res.setHeader, 'Content-Type', 'text/html')
sinon.assert.calledWith(res.status, 400)
sinon.assert.calledWith(res.render, 'error', { message: 'error.contactServiceForZeroValuePayment' })
})

const expectedError = sinon.match.instanceOf(AccountCannotTakePaymentsError)
.and(sinon.match.has('message', 'Forbidden response returned by Public API when creating payment'))
sinon.assert.calledWith(next, expectedError)
})
it('when creating a payment returns a 403, should call next() with an AccountCannotTakePaymentsError',
async () => {
req = {
correlationId: '123',
product,
body: {
amount: '1000'
}
}

res = {
redirect: sinon.stub()
}

const next = sinon.stub()

const error = new Error('Failed to create payment.')
error.errorCode = 403
mockProductsClient.payment.create.rejects(error)

await controller.postPage(req, res, next)

sinon.assert.calledWith(
mockProductsClient.payment.create,
'an-external-id',
1000
)

const expectedError = sinon.match.instanceOf(AccountCannotTakePaymentsError)
.and(sinon.match.has('message', 'Forbidden response returned by Public API when creating payment'))
sinon.assert.calledWith(next, expectedError)
})

it('should return 400 and redirect to reference page for CARD_NUMBER_IN_PAYMENT_LINK_REFERENCE_REJECTED error', async () => {
req = {
Expand Down
12 changes: 1 addition & 11 deletions app/payment-links/utils/payment-link-session.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ const AMOUNT_KEY = 'amount'
const REFERENCE_PROVIDED_BY_QUERY_PARAMS_KEY = 'referenceProvidedByQueryParams'
const AMOUNT_PROVIDED_BY_QUERY_PARAMS_KEY = 'amountProvidedByQueryParams'
const ERROR_KEY = 'error'
const PRE_FILLED_KEY = 'prefilled'

function cookieIndex (key, productExternalId) {
return `${getSessionCookieName()}.${productExternalId}.${key}`
Expand Down Expand Up @@ -56,13 +55,6 @@ function deletePaymentLinkSession (req, productExternalId) {
lodash.unset(req, `${getSessionCookieName()}.${productExternalId}`)
}

function getPrefilledFlag (req, productExternalId) {
return lodash.get(req, cookieIndex(PRE_FILLED_KEY, productExternalId))
}
function setPrefilledFlag (req, productExternalId, prefilled) {
lodash.set(req, cookieIndex(PRE_FILLED_KEY, productExternalId), prefilled)
}

module.exports = {
getReference,
setReference,
Expand All @@ -72,7 +64,5 @@ module.exports = {
getAmountProvidedByQueryParams,
deletePaymentLinkSession,
getError,
setError,
setPrefilledFlag,
getPrefilledFlag
setError
}
4 changes: 2 additions & 2 deletions locales/en.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
"default": "There’s a problem with the payments platform. Please try again later",
"internal": "Sorry, we’re unable to process your request. Try again later.",
"contactService": "Please contact the service you are trying to make a payment to.",
"contactServiceForZeroValuePayment": "Please contact the service regarding the zero amount payment error."
"contactServiceForZeroValuePayment": "There is a problem with the link you have been sent to use to pay. The payment amount cannot be £0.00. Contact the service you’re trying to make a payment to."
},
"404Page": {
"title": "Page not found",
Expand Down Expand Up @@ -82,7 +82,7 @@
},
"fieldValidation": {
"enterAnAmountInPounds": "Enter an amount of money to pay",
"enterANonZeroAmountInPounds": "Enter an amount that is £0.01 or more",
"enterANonZeroAmountInPounds": "Amount must be £0.01 or more",
"enterAnAmountInTheCorrectFormat": "Enter an amount in pounds and pence using digits and a decimal point, like 123.45 or 156.00",
"enterAnAmountUnderMaxAmount": "Enter an amount that is £100,000 or less",
"enterAReference": "You must enter your %s",
Expand Down

0 comments on commit 552bdef

Please sign in to comment.