Skip to content

Commit

Permalink
PP-13313 Validate Credentials with Worldpay (#4386)
Browse files Browse the repository at this point in the history
* PP-13313 Validate Credentials with Worldpay
  • Loading branch information
DomBelcher authored Dec 17, 2024
1 parent cf0767a commit bb0f7fb
Show file tree
Hide file tree
Showing 10 changed files with 245 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ const formatSimplifiedAccountPathsFor = require('@utils/simplified-account/forma
const paths = require('@root/paths')
const { body, validationResult } = require('express-validator')
const formatValidationErrors = require('@utils/simplified-account/format/format-validation-errors')
const worldpayDetailsService = require('@services/worldpay-details.service')
const WorldpayCredential = require('@models/gateway-account-credential/WorldpayCredential.class')

function get (req, res) {
return response(req, res, 'simplified-account/settings/worldpay-details/credentials', {
Expand Down Expand Up @@ -33,6 +35,26 @@ async function post (req, res) {
formErrors: formattedErrors.formErrors
})
}

const credential = new WorldpayCredential()
.withMerchantCode(req.body.merchantCode)
.withUsername(req.body.username)
.withPassword(req.body.password)

const isValid = await worldpayDetailsService.checkCredential(req.service.externalId, req.account.type, credential)
if (!isValid) {
return errorResponse(req, res, {
summary: [
{
text: 'Check your Worldpay credentials, failed to link your account to Worldpay with credentials provided',
href: '#merchantCode'
}
]
})
}

return res.redirect(formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.worldpayDetails.index,
req.service.externalId, req.account.type))
}

const errorResponse = (req, res, errors) => {
Expand Down
2 changes: 1 addition & 1 deletion app/models/GatewayAccount.class.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const { GatewayAccountCredential, CREDENTIAL_STATE } = require('@models/GatewayAccountCredential.class')
const { GatewayAccountCredential, CREDENTIAL_STATE } = require('@models/gateway-account-credential/GatewayAccountCredential.class')

/**
* @class GatewayAccount
Expand Down
3 changes: 2 additions & 1 deletion app/models/WorldpayTasks.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const paths = require('@root/paths')
class WorldpayTasks {
/**
* @param {GatewayAccount} gatewayAccount
* @param {Service} service
*/
constructor (gatewayAccount, service) {
this.tasks = []
Expand All @@ -21,7 +22,7 @@ class WorldpayTasks {
linkText: 'Link your Worldpay account with GOV.UK Pay',
complete: true
}
if (credential === null || credential.credentials.one_off_customer_initiated === null) {
if (!credential || !credential.credentials.oneOffCustomerInitiated) {
worldpayCredentials.complete = false
}
this.tasks.push(worldpayCredentials)
Expand Down
52 changes: 52 additions & 0 deletions app/models/gateway-account-credential/Credential.class.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
const WorldpayCredential = require('./WorldpayCredential.class')

class Credential {
/**
*
* @param {String} stripeAccountId
* @returns {Credential}
*/
withStripeAccountId (stripeAccountId) {
if (stripeAccountId) {
this.stripeAccountId = stripeAccountId
}
return this
}

/**
*
* @param {WorldpayCredential} oneOffCustomerInitiated
* @returns {Credential}
*/
withOneOffCustomerInitiated (oneOffCustomerInitiated) {
if (oneOffCustomerInitiated) {
this.oneOffCustomerInitiated = oneOffCustomerInitiated
}
return this
}

/** @deprecated this is a temporary compatability fix! If you find yourself using this for new code
* you should instead add any rawResponse data as part of the constructor */
withRawResponse (data) {
/** @deprecated this is a temporary compatability fix! If you find yourself using this for new code
* you should instead add any rawResponse data as part of the constructor */
this.rawResponse = data
return this
}

toJson () {
return {
...this.stripeAccountId && { stripe_account_id: this.stripeAccountId },
...this.oneOffCustomerInitiated && { one_off_customer_initiated: this.oneOffCustomerInitiated.toJson() }
}
}

static fromJson (data) {
return new Credential()
.withStripeAccountId(data?.stripe_account_id)
.withOneOffCustomerInitiated(WorldpayCredential.fromJson(data?.one_off_customer_initiated))
.withRawResponse(data)
}
}

module.exports = Credential
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
const Credential = require('./Credential.class')

const CREDENTIAL_STATE = {
CREATED: 'CREATED',
ENTERED: 'ENTERED',
Expand All @@ -10,7 +12,7 @@ class GatewayAccountCredential {
constructor (data) {
this.externalId = data.external_id
this.paymentProvider = data.payment_provider
this.credentials = new Credential(data.credentials)
this.credentials = Credential.fromJson(data.credentials)
this.state = data.state
this.createdDate = data.created_date
this.activeStartDate = data.active_start_date
Expand All @@ -19,14 +21,6 @@ class GatewayAccountCredential {
}
}

class Credential {
constructor (data) {
this.stripeAccountId = data.stripe_account_id
/** @deprecated this is a temporary compatability fix! If you find yourself using this for new code
* you should instead add any rawResponse data as part of the constructor */
this.rawResponse = data
}
}

module.exports.GatewayAccountCredential = GatewayAccountCredential
module.exports.Credential = Credential
module.exports.CREDENTIAL_STATE = CREDENTIAL_STATE
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
'use strict'

class GatewayAccountCredentialUpdateRequest {
/**
* @param {String} userExternalId
*/
constructor (userExternalId) {
this.updates = [{
op: 'replace',
path: 'last_updated_by_user_external_id',
value: userExternalId
}]
}

replace () {
return safeOperation('replace', this)
}

formatPayload () {
return this.updates
}
}

const safeOperation = (op, request) => {
return {
credentials: (value) => {
request.updates.push({ op, path: 'credentials', value })
return request
}
}
}
module.exports = { GatewayAccountCredentialUpdateRequest }
24 changes: 24 additions & 0 deletions app/models/gateway-account-credential/ValidationResult.class.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
class ValidationResult {
/**
*
* @param {String} result
*/
withResult (result) {
if (result) {
this.result = result
}
return this
}

/**
*
* @param data
* @returns {ValidationResult}
*/
static fromJson (data) {
return new ValidationResult()
.withResult(data?.result)
}
}

module.exports = ValidationResult
42 changes: 42 additions & 0 deletions app/models/gateway-account-credential/WorldpayCredential.class.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
class WorldpayCredential {
withMerchantCode (merchantCode) {
if (merchantCode) {
this.merchantCode = merchantCode
}
return this
}

withUsername (username) {
if (username) {
this.username = username
}
return this
}

withPassword (password) {
if (password) {
this.password = password
}
return this
}

toJson () {
return {
...this.merchantCode && { merchant_code: this.merchantCode },
...this.username && { username: this.username },
...this.password && { password: this.password }
}
}

static fromJson (data) {
if (!data) {
return undefined
}
return new WorldpayCredential()
.withMerchantCode(data?.merchant_code)
.withUsername(data?.username)
.withPassword(data?.password)
}
}

module.exports = WorldpayCredential
36 changes: 36 additions & 0 deletions app/services/clients/connector.client.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ const { configureClient } = require('./base/config')
const StripeAccountSetup = require('../../models/StripeAccountSetup.class')
const StripeAccount = require('../../models/StripeAccount.class')
const GatewayAccount = require('@models/GatewayAccount.class')
const ValidationResult = require('@models/gateway-account-credential/ValidationResult.class')

// Constants
const SERVICE_NAME = 'connector'
Expand Down Expand Up @@ -132,6 +133,25 @@ ConnectorClient.prototype = {
return response.data
},

/**
*
* @param {String} serviceExternalId
* @param {String} accountType
* @param {String} credentialsId
* @param {Object} payload
* @returns {Promise<GatewayAccountCredential>}
*/
patchGatewayAccountCredentialsByServiceIdAndAccountType: async function (serviceExternalId, accountType, credentialsId, payload) {
const url = `${this.connectorUrl}/v1/api/service/{serviceExternalId}/account/{accountType}/credentials/{credentialsId}`
.replace('{serviceExternalId}', encodeURIComponent(serviceExternalId))
.replace('{accountType}', encodeURIComponent(accountType))
.replace('{credentialsId}', encodeURIComponent(credentialsId))

configureClient(client, url)
const response = await client.patch(url, payload, 'patch gateway account credentials')
return response.data
},

patchGooglePayGatewayMerchantId: async function (gatewayAccountId, gatewayAccountCredentialsId, googlePayGatewayMerchantId, userExternalId) {
const url = `${this.connectorUrl}/v1/api/accounts/{accountId}/credentials/{credentialsId}`
.replace('{accountId}', encodeURIComponent(gatewayAccountId))
Expand Down Expand Up @@ -217,6 +237,22 @@ ConnectorClient.prototype = {
return response.data
},

/**
*
* @param {String} serviceExternalId
* @param {String} accountType
* @param {WorldpayCredential} credentials
* @returns {Promise<ValidationResult>}
*/
postCheckWorldpayCredentialByServiceExternalIdAndAccountType: async function (serviceExternalId, accountType, credentials) {
const url = `${this.connectorUrl}/v1/api/service/{serviceExternalId}/account/{accountType}/worldpay/check-credentials`
.replace('{serviceExternalId}', encodeURIComponent(serviceExternalId))
.replace('{accountType}', encodeURIComponent(accountType))
configureClient(client, url)
const response = await client.post(url, credentials.toJson(), 'Check Worldpay credentials')
return ValidationResult.fromJson(response.data)
},

/**
*
* @param {Object} params
Expand Down
30 changes: 30 additions & 0 deletions app/services/worldpay-details.service.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
const { ConnectorClient } = require('./clients/connector.client')
const logger = require('../utils/logger')(__filename)

const connectorClient = new ConnectorClient(process.env.CONNECTOR_URL)

/**
*
* @param {String} serviceExternalId
* @param {String} accountType
* @param {WorldpayCredential} credential
* @returns {Promise<Object>}
*/
async function checkCredential (serviceExternalId, accountType, credential) {
const credentialCheck = await connectorClient.postCheckWorldpayCredentialByServiceExternalIdAndAccountType(
serviceExternalId,
accountType,
credential
)
if (credentialCheck.result !== 'valid') {
logger.warn(`Credentials provided for service external ID [${serviceExternalId}], account type [${accountType}] failed validation with Worldpay`)
return false
}

logger.info(`Successfully validated credentials for service external ID [${serviceExternalId}], account type [${accountType}] with Worldpay`)
return true
}

module.exports = {
checkCredential
}

0 comments on commit bb0f7fb

Please sign in to comment.