Skip to content

Commit

Permalink
PP-13313 Implement save credentials functionality in POST
Browse files Browse the repository at this point in the history
  • Loading branch information
DomBelcher committed Dec 18, 2024
1 parent bb0f7fb commit e9b64d6
Show file tree
Hide file tree
Showing 14 changed files with 146 additions and 60 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,14 @@ async function post (req, res) {
})
}

await worldpayDetailsService.updateCredentials(
req.service.externalId,
req.account.type,
req.account.getCurrentCredential().externalId,
req.user.externalId,
credential
)

return res.redirect(formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.worldpayDetails.index,
req.service.externalId, req.account.type))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe('Controller: settings/worldpay-details/credentials', () => {

it('should pass context data to the response method', () => {
expect(mockResponse.args[0][3]).to.have.property('backLink').to.equal(
formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.worldpayDetails.index, SERVICE_ID, ACCOUNT_TYPE)
formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.worldpayDetails.index, SERVICE_ID, ACCOUNT_TYPE, CREDENTIAL_EXTERNAL_ID)
)
})
})
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ describe('Controller: settings/worldpay-details', () => {

it('should pass context data to the response method', () => {
const tasks = [{
href: formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.worldpayDetails.credentials,
href: formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.worldpayDetails.oneOffCustomerInitiated,
SERVICE_ID, ACCOUNT_TYPE),
id: 'worldpay-credentials',
linkText: 'Link your Worldpay account with GOV.UK Pay',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,12 +28,7 @@ async function getGatewayAccount (serviceExternalId, accountType) {
serviceExternalId,
accountType
}
let gatewayAccount = await connectorClient.getAccountByServiceExternalIdAndAccountType(params)

gatewayAccount = _.extend({}, gatewayAccount, {
supports3ds: ['worldpay', 'stripe'].includes(gatewayAccount.paymentProvider),
disableToggle3ds: gatewayAccount.paymentProvider === 'stripe'
})
const gatewayAccount = await connectorClient.getAccountByServiceExternalIdAndAccountType(params)

const switchingCredential = getSwitchingCredentialIfExists(gatewayAccount)
const isSwitchingToStripe = switchingCredential && switchingCredential.payment_provider === 'stripe'
Expand Down
27 changes: 23 additions & 4 deletions app/models/GatewayAccount.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,16 +44,35 @@ class GatewayAccount {
this.recurringEnabled = gatewayAccountData.recurring_enabled
if (gatewayAccountData?.gateway_account_credentials) {
this.gatewayAccountCredentials = gatewayAccountData?.gateway_account_credentials
.map(credentialData => new GatewayAccountCredential(credentialData))

this.activeCredential = this.gatewayAccountCredentials.filter((credential) =>
credential.state === CREDENTIAL_STATE.ACTIVE)[0] || null
.map(credentialData => GatewayAccountCredential.fromJson(credentialData))
}
this.supports3ds = ['worldpay', 'stripe'].includes(gatewayAccountData.payment_provider)
this.disableToggle3ds = gatewayAccountData.payment_provider === 'stripe'
/** @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 = gatewayAccountData
}

/**
*
* @returns {GatewayAccountCredential}
*/
getCurrentCredential () {
if (this.gatewayAccountCredentials.length === 1) {
return this.gatewayAccountCredentials[0]
}
return this.getActiveCredential()
}

/**
*
* @returns {GatewayAccountCredential}
*/
getActiveCredential () {
return this.gatewayAccountCredentials
.filter((credential) => credential.state === CREDENTIAL_STATE.ACTIVE)[0] || null
}

/**
* @method toJson
* @returns {Object} A minimal representation of the gateway account
Expand Down
4 changes: 2 additions & 2 deletions app/models/WorldpayTasks.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,11 @@ class WorldpayTasks {
this.tasks = []
this.incompleteTasks = true

const credential = gatewayAccount.activeCredential
const credential = gatewayAccount.getCurrentCredential()

if (gatewayAccount.allowMoto) {
const worldpayCredentials = {
href: formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.worldpayDetails.credentials,
href: formatSimplifiedAccountPathsFor(paths.simplifiedAccount.settings.worldpayDetails.oneOffCustomerInitiated,
service.externalId, gatewayAccount.type),
id: 'worldpay-credentials',
linkText: 'Link your Worldpay account with GOV.UK Pay',
Expand Down
23 changes: 12 additions & 11 deletions app/models/gateway-account-credential/Credential.class.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ class Credential {
* @returns {Credential}
*/
withStripeAccountId (stripeAccountId) {
if (stripeAccountId) {
this.stripeAccountId = stripeAccountId
}
this.stripeAccountId = stripeAccountId
return this
}

Expand All @@ -19,9 +17,7 @@ class Credential {
* @returns {Credential}
*/
withOneOffCustomerInitiated (oneOffCustomerInitiated) {
if (oneOffCustomerInitiated) {
this.oneOffCustomerInitiated = oneOffCustomerInitiated
}
this.oneOffCustomerInitiated = oneOffCustomerInitiated
return this
}

Expand All @@ -36,16 +32,21 @@ class Credential {

toJson () {
return {
...this.stripeAccountId && { stripe_account_id: this.stripeAccountId },
...this.oneOffCustomerInitiated && { one_off_customer_initiated: this.oneOffCustomerInitiated.toJson() }
...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))
const credential = new Credential()
.withRawResponse(data)
if (data?.stripe_account_id) {
credential.withStripeAccountId(data.stripe_account_id)
}
if (data?.one_off_customer_initiated) {
credential.withOneOffCustomerInitiated(WorldpayCredential.fromJson(data.one_off_customer_initiated))
}
return credential
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,63 @@ const CREDENTIAL_STATE = {
}

class GatewayAccountCredential {
constructor (data) {
this.externalId = data.external_id
this.paymentProvider = data.payment_provider
this.credentials = Credential.fromJson(data.credentials)
this.state = data.state
this.createdDate = data.created_date
this.activeStartDate = data.active_start_date
this.activeEndDate = data.active_end_date
this.gatewayAccountId = data.gateway_account_id
withExternalId (externalId) {
this.externalId = externalId
return this
}

withPaymentProvider (paymentProvider) {
this.paymentProvider = paymentProvider
return this
}

withCredentials (credentials) {
this.credentials = credentials
return this
}

withState (state) {
if (state) {
this.state = state
}
return this
}

withCreatedDate (createdDate) {
this.createdDate = createdDate
return this
}

withActiveStartDate (activeStartDate) {
this.activeStartDate = activeStartDate
return this
}

withActiveEndDate (activeEndDate) {
this.activeEndDate = activeEndDate
return this
}

withGatewayAccountId (gatewayAccountId) {
this.gatewayAccountId = gatewayAccountId
return this
}

/**
*
* @param data
* @returns {GatewayAccountCredential}
*/
static fromJson (data) {
return new GatewayAccountCredential()
.withExternalId(data?.external_id)
.withPaymentProvider(data?.payment_provider)
.withCredentials(Credential.fromJson(data?.credentials))
.withState(data?.state)
.withCreatedDate(data?.created_date)
.withActiveStartDate(data?.active_start_date)
.withActiveEndDate(data?.active_end_date)
.withGatewayAccountId(data?.gateway_account_id)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,15 @@ class GatewayAccountCredentialUpdateRequest {

const safeOperation = (op, request) => {
return {
credentials: (value) => {
request.updates.push({ op, path: 'credentials', value })
return request
credentials: () => {
return {
oneOffCustomerInitiated: (value) => {
request.updates.push({ op, path: 'credentials/worldpay/one_off_customer_initiated', value })
return request
}
}
}
}
}
module.exports = { GatewayAccountCredentialUpdateRequest }

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

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

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

Expand All @@ -29,9 +23,6 @@ class WorldpayCredential {
}

static fromJson (data) {
if (!data) {
return undefined
}
return new WorldpayCredential()
.withMerchantCode(data?.merchant_code)
.withUsername(data?.username)
Expand Down
5 changes: 3 additions & 2 deletions app/paths.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ const keys = {
ENVIRONMENT_ID: 'environmentId',
SERVICE_EXTERNAL_ID: 'serviceExternalId',
GATEWAY_ACCOUNT_EXTERNAL_ID: 'gatewayAccountExternalId',
ACCOUNT_TYPE: 'accountType'
ACCOUNT_TYPE: 'accountType',
CREDENTIAL_EXTERNAL_ID: 'credentialExternalId'
}

module.exports = {
Expand Down Expand Up @@ -215,7 +216,7 @@ module.exports = {
},
worldpayDetails: {
index: '/settings/worldpay-details',
credentials: '/settings/worldpay-details/credentials'
oneOffCustomerInitiated: '/settings/worldpay-details/one-off-customer-initiated'
},
cardPayments: {
index: '/settings/card-payments'
Expand Down
9 changes: 5 additions & 4 deletions app/services/clients/connector.client.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ 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')
const { GatewayAccountCredential } = require('@models/gateway-account-credential/GatewayAccountCredential.class')

// Constants
const SERVICE_NAME = 'connector'
Expand Down Expand Up @@ -138,18 +139,18 @@ ConnectorClient.prototype = {
* @param {String} serviceExternalId
* @param {String} accountType
* @param {String} credentialsId
* @param {Object} payload
* @param {GatewayAccountCredentialUpdateRequest} patchRequest
* @returns {Promise<GatewayAccountCredential>}
*/
patchGatewayAccountCredentialsByServiceIdAndAccountType: async function (serviceExternalId, accountType, credentialsId, payload) {
patchGatewayAccountCredentialsByServiceIdAndAccountType: async function (serviceExternalId, accountType, credentialsId, patchRequest) {
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
const response = await client.patch(url, patchRequest.formatPayload(), 'patch gateway account credentials')
return GatewayAccountCredential.fromJson(response.data)
},

patchGooglePayGatewayMerchantId: async function (gatewayAccountId, gatewayAccountCredentialsId, googlePayGatewayMerchantId, userExternalId) {
Expand Down
21 changes: 19 additions & 2 deletions app/services/worldpay-details.service.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
const { ConnectorClient } = require('./clients/connector.client')
const GatewayAccountCredentialUpdateRequest = require('@models/gateway-account-credential/GatewayAccountCredentialUpdateRequest.class')
const logger = require('../utils/logger')(__filename)

const connectorClient = new ConnectorClient(process.env.CONNECTOR_URL)
Expand All @@ -8,7 +9,7 @@ const connectorClient = new ConnectorClient(process.env.CONNECTOR_URL)
* @param {String} serviceExternalId
* @param {String} accountType
* @param {WorldpayCredential} credential
* @returns {Promise<Object>}
* @returns {Promise<boolean>}
*/
async function checkCredential (serviceExternalId, accountType, credential) {
const credentialCheck = await connectorClient.postCheckWorldpayCredentialByServiceExternalIdAndAccountType(
Expand All @@ -25,6 +26,22 @@ async function checkCredential (serviceExternalId, accountType, credential) {
return true
}

/**
*
* @param {String} serviceExternalId
* @param {String} accountType
* @param {String} credentialId
* @param {String} userExternalId
* @param {WorldpayCredential} credential
* @returns {Promise<GatewayAccountCredential>}
*/
async function updateCredentials (serviceExternalId, accountType, credentialId, userExternalId, credential) {
const patchRequest = new GatewayAccountCredentialUpdateRequest(userExternalId)
.replace().credentials().oneOffCustomerInitiated(credential.toJson())
return connectorClient.patchGatewayAccountCredentialsByServiceIdAndAccountType(serviceExternalId, accountType, credentialId, patchRequest)
}

module.exports = {
checkCredential
checkCredential,
updateCredentials
}
4 changes: 2 additions & 2 deletions app/simplified-account-routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,8 +66,8 @@ simplifiedAccount.get(paths.simplifiedAccount.settings.worldpayDetails.index, pe

// worldpay details
simplifiedAccount.get(paths.simplifiedAccount.settings.worldpayDetails.index, permission('gateway-credentials:read'), serviceSettingsController.worldpayDetails.get)
simplifiedAccount.get(paths.simplifiedAccount.settings.worldpayDetails.credentials, permission('gateway-credentials:update'), serviceSettingsController.worldpayDetails.worldpayCredentials.get)
simplifiedAccount.post(paths.simplifiedAccount.settings.worldpayDetails.credentials, permission('gateway-credentials:update'), serviceSettingsController.worldpayDetails.worldpayCredentials.post)
simplifiedAccount.get(paths.simplifiedAccount.settings.worldpayDetails.oneOffCustomerInitiated, permission('gateway-credentials:update'), serviceSettingsController.worldpayDetails.worldpayCredentials.get)
simplifiedAccount.post(paths.simplifiedAccount.settings.worldpayDetails.oneOffCustomerInitiated, permission('gateway-credentials:update'), serviceSettingsController.worldpayDetails.worldpayCredentials.post)

// card types
simplifiedAccount.get(paths.simplifiedAccount.settings.cardTypes.index, permission('transactions:read'), serviceSettingsController.cardTypes.get)
Expand Down

0 comments on commit e9b64d6

Please sign in to comment.