From 4488478a9174d68d85f74d55ff4aa7e8de2dae3a Mon Sep 17 00:00:00 2001 From: John Shields Date: Tue, 20 Feb 2024 11:16:52 +0000 Subject: [PATCH] =?UTF-8?q?PP-11681=20Update=20=E2=80=98ledger.client?= =?UTF-8?q?=E2=80=99=20with=20=E2=80=98axios-base-client=E2=80=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Change ‘ledger.client’ methods to use ‘axios-base-client’. - Refactor call parameters initialisation. - Use ‘async/await’ syntax. - Use baseUrl from options parameter if present otherwise use default envvar. --- app/services/clients/ledger.client.js | 192 ++++++-------- app/services/clients/ledger.client.test.js | 281 +++++++++++++++++++++ 2 files changed, 365 insertions(+), 108 deletions(-) create mode 100644 app/services/clients/ledger.client.test.js diff --git a/app/services/clients/ledger.client.js b/app/services/clients/ledger.client.js index 4b2137244d..a5605d556d 100644 --- a/app/services/clients/ledger.client.js +++ b/app/services/clients/ledger.client.js @@ -1,6 +1,8 @@ 'use strict' -const baseClient = require('./base-client/base.client') +const { Client } = require('@govuk-pay/pay-js-commons/lib/utils/axios-base-client/axios-base-client') +const { configureClient } = require('./base/config') +const urlJoin = require('url-join') const { legacyConnectorTransactionParity, legacyConnectorEventsParity, @@ -18,53 +20,49 @@ const defaultOptions = { limit_total: true } -const transaction = function transaction (id, gatewayAccountId, options = {}) { - const configuration = Object.assign({ - url: `/v1/transaction/${id}`, - qs: { - account_id: gatewayAccountId, - ...options.transaction_type && { transaction_type: options.transaction_type } - }, - description: 'Get individual transaction details', - transform: legacyConnectorTransactionParity - }, defaultOptions, options) - return baseClient.get(configuration) +const client = new Client(defaultOptions.service) + +const transaction = async function transaction (id, gatewayAccountId, options = {}) { + const baseUrl = options.baseUrl ? options.baseUrl : defaultOptions.baseUrl + let url = `${baseUrl}/v1/transaction/${id}?account_id=${gatewayAccountId}` + if ( options.transaction_type ) { + url = `${url}&transaction_type=${options.transaction_type}` + } + configureClient(client, url) + const response = await client.get(url, 'Get individual transaction details') + const body = legacyConnectorTransactionParity(response.data) + return body } -const transactionWithAccountOverride = function transactionWithAccountOverride (id, options = {}) { - const configuration = Object.assign({ - url: `/v1/transaction/${id}`, - qs: { - override_account_id_restriction: true - }, - description: 'Get individual transaction details with no accountId restriction' - }, defaultOptions, options) - return baseClient.get(configuration) +const transactionWithAccountOverride = async function transactionWithAccountOverride (id, options = {}) { + const baseUrl = options.baseUrl ? options.baseUrl : defaultOptions.baseUrl + const url = urlJoin(baseUrl,'/v1/transaction', id) + const fullUrl = `${url}?override_account_id_restriction=true` + configureClient(client, fullUrl) + const response = await client.get(fullUrl, 'Get individual transaction details with no accountId restriction') + return response.data } -function getDisputesForTransaction (id, gatewayAccountId, options = {}) { - const configuration = Object.assign({ - url: `/v1/transaction/${id}/transaction`, - qs: { - gateway_account_id: gatewayAccountId, - transaction_type: 'DISPUTE' - }, - description: 'Get disputes for payment' - }, defaultOptions, options) - return baseClient.get(configuration) +async function getDisputesForTransaction (id, gatewayAccountId, options = {}) { + const baseUrl = options.baseUrl ? options.baseUrl : defaultOptions.baseUrl + const url = urlJoin(baseUrl,'/v1/transaction', id, 'transaction') + const fullUrl = `${url}?gateway_account_id=${gatewayAccountId}&transaction_type=DISPUTE` + configureClient(client, fullUrl) + const response = await client.get(fullUrl, 'Get disputes for payment') + return response.data } -const events = function events (transactionId, gatewayAccountId, options = {}) { - const configuration = Object.assign({ - url: `/v1/transaction/${transactionId}/event`, - qs: { gateway_account_id: gatewayAccountId }, - description: 'List events for a given transaction', - transform: legacyConnectorEventsParity - }, defaultOptions, options) - return baseClient.get(configuration) +const events = async function events (transactionId, gatewayAccountId, options = {}) { + const baseUrl = options.baseUrl ? options.baseUrl : defaultOptions.baseUrl + const url = urlJoin(baseUrl,'/v1/transaction', transactionId, 'event') + const fullUrl = `${url}?gateway_account_id=${gatewayAccountId}` + configureClient(client, fullUrl) + const response = await client.get(fullUrl, 'List events for a given transaction') + const body = legacyConnectorEventsParity(response.data) + return body } -const transactions = function transactions (gatewayAccountIds = [], filters = {}, options = {}) { +const transactions = async function transactions (gatewayAccountIds = [], filters = {}, options = {}) { const formatOptions = { arrayFormat: 'comma' } const path = '/v1/transaction' const params = { @@ -75,50 +73,43 @@ const transactions = function transactions (gatewayAccountIds = [], filters = {} const formattedParams = qs.stringify(params, formatOptions) const formattedFilterParams = getQueryStringForParams(filters, true, true) - const configuration = Object.assign({ - url: `${path}?${formattedParams}&${formattedFilterParams}`, - description: 'List transactions for a given gateway account ID', - transform: legacyConnectorTransactionsParity, - additionalLoggingFields: { - gateway_account_ids: gatewayAccountIds, - multiple_accounts: gatewayAccountIds.length > 1, - filters: Object.keys(filters).sort().join(', ') + const baseUrl = options.baseUrl ? options.baseUrl : defaultOptions.baseUrl + const url = `${baseUrl}${path}?${formattedParams}&${formattedFilterParams}` + configureClient(client, url) + const response = await client.get( + url, + 'List transactions for a given gateway account ID', + { + data : { + gateway_account_ids: gatewayAccountIds, + multiple_accounts: gatewayAccountIds.length > 1, + filters: Object.keys(filters).sort().join(', ') + } } - }, defaultOptions, options) - - return baseClient.get(configuration) + ) + const body = legacyConnectorTransactionsParity(response.data) + return body } -const transactionSummary = function transactionSummary (gatewayAccountId, fromDate, toDate, options = {}) { +const transactionSummary = async function transactionSummary (gatewayAccountId, fromDate, toDate, options = {}) { const path = '/v1/report/transactions-summary' - const configuration = Object.assign({ - url: path, - qs: { - account_id: gatewayAccountId, - from_date: fromDate, - to_date: toDate - }, - description: 'Transaction summary statistics for a given gateway account ID', - transform: legacyConnectorTransactionSummaryParity - }, defaultOptions, options) - - return baseClient.get(configuration) + const baseUrl = options.baseUrl ? options.baseUrl : defaultOptions.baseUrl + const url = `${baseUrl}${path}?account_id=${gatewayAccountId}&from_date=${fromDate}&to_date=${toDate}` + configureClient(client, url) + const response = await client.get(url,'Transaction summary statistics for a given gateway account ID') + const body = legacyConnectorTransactionSummaryParity(response.data) + return body } -const payouts = function payouts (gatewayAccountIds = [], page = 1, displaySize, options = {}) { - const configuration = Object.assign({ - url: '/v1/payout', - qs: { - // qsStringifyOptions doesn't seem to be accepted here and the request library is deprecated for upstream changes - gateway_account_id: gatewayAccountIds.join(','), - state: 'paidout', - ...displaySize && { display_size: displaySize }, - page - }, - description: 'List payouts for a given gateway account ID' - }, defaultOptions, options) - - return baseClient.get(configuration) +const payouts = async function payouts (gatewayAccountIds = [], page = 1, displaySize, options = {}) { + const baseUrl = options.baseUrl ? options.baseUrl : defaultOptions.baseUrl + let url = `${baseUrl}/v1/payout?gateway_account_id=${gatewayAccountIds.join(',')}&state=paidout&page=${page}` + if ( displaySize ) { + url = `${url}&display_size=${displaySize}` + } + configureClient(client, url) + const response = await client.get(url,'List payouts for a given gateway account ID') + return response.data } /** @@ -127,39 +118,24 @@ const payouts = function payouts (gatewayAccountIds = [], page = 1, displaySize, * we haven't realised yet. For now, we additionally send the gateway account ID but the intention is to remove the need * to send this. */ -const agreements = function agreements (serviceId, live, accountId, page = 1, options = {}) { - const config = { - url: '/v1/agreement', - qs: { - service_id: serviceId, - live, - account_id: accountId, - page, - ...options.filters - }, - description: 'List agreements for a given service and environment', - baseUrl: process.env.LEDGER_URL, - json: true, - service: 'ledger', - ...options +const agreements = async function agreements (serviceId, live, accountId, page = 1, options = {}) { + const baseUrl = options.baseUrl ? options.baseUrl : defaultOptions.baseUrl + let url = `${baseUrl}/v1/agreement?service_id=${serviceId}&account_id=${accountId}&live=${live}&page=${page}` + if ( options.filters ) { + const filterParams = new URLSearchParams(options.filters).toString() + url = `${url}&${filterParams}` } - - return baseClient.get(config) + configureClient(client, url) + const response = await client.get(url,'List agreements for a given service and environment') + return response.data } -const agreement = function agreement (id, serviceId, options = {}) { - const config = { - url: `/v1/agreement/${id}`, - qs: { - service_id: serviceId - }, - description: 'Get agreement by ID', - baseUrl: process.env.LEDGER_URL, - json: true, - service: 'ledger', - ...options - } - return baseClient.get(config) +const agreement = async function agreement (id, serviceId, options = {}) { + const baseUrl = options.baseUrl ? options.baseUrl : defaultOptions.baseUrl + let url = `${baseUrl}/v1/agreement/${id}?service_id=${serviceId}` + configureClient(client, url) + const response = await client.get(url,'Get agreement by ID') + return response.data } module.exports = { diff --git a/app/services/clients/ledger.client.test.js b/app/services/clients/ledger.client.test.js new file mode 100644 index 0000000000..5d4d4a2d56 --- /dev/null +++ b/app/services/clients/ledger.client.test.js @@ -0,0 +1,281 @@ +'use strict' + +const sinon = require('sinon') +const proxyquire = require('proxyquire') +const { expect } = require('chai') +const transactionDetailsFixtures = require('../../../test/fixtures/ledger-transaction.fixtures') + +const configureSpy = sinon.spy() + +const validCreatedTransactionDetailsResponse = transactionDetailsFixtures.validTransactionCreatedDetailsResponse({ + transaction_id: 'ch_123abc456xyz', + type: 'payment', + amount: 100, + fee: 5, + net_amount: 95, + refund_summary_available: 100 +}) + +const validTransactionSearchResponse = transactionDetailsFixtures.validTransactionSearchResponse({ + gateway_account_id: '123456', + transactions: [ + { + amount: 2000, + state: { + status: 'success', + finished: true + }, + transaction_id: '222222', + created_date: '2018-09-22T10:14:15.067Z', + refund_summary_status: 'available', + refund_summary_available: 1850, + amount_submitted: 150, + type: 'payment', + card_brand: 'visa' + } + ] +}) + +const ledgerTransactionEventsFixture = { + events: [{ + timestamp: 'some-iso-timestamp', + resource_type: 'PAYMENT', + data: {} + }] +} + +const validTransactionSummaryResponse = transactionDetailsFixtures.validTransactionSummaryDetails({ + account_id: '123456', + from_date: '2019-09-19T13:00:00.000Z', + to_date: '2019-09-22T00:00:00.000Z', + paymentCount: 2, + paymentTotal: 3500, + refundCount: 1, + refundTotal: 1000 +}) + +class MockClient { + configure (baseUrl, options) { + configureSpy(baseUrl, options) + } + + async get (url, description) { + let dataResponse + + if (url.match(/\.*\/event/)) { dataResponse = ledgerTransactionEventsFixture } + else if (url.match(/\/transaction\//)) dataResponse = { validCreatedTransactionDetailsResponse } + else if (url.match(/\/report\/transactions-summary/)) { dataResponse = validTransactionSummaryResponse } + else if (url.match(/\/agreement/)) dataResponse = { validTransactionSummaryResponse } + else if (url.match(/\/payout/)) dataResponse = {} + else if (url.match(/\/transaction\?account_id.*limit_total/)) { dataResponse = validTransactionSearchResponse } + else dataResponse = {} + + return Promise.resolve({ data: dataResponse }) + } +} + +function getLedgerClient () { + return proxyquire('./ledger.client', { + '@govuk-pay/pay-js-commons/lib/utils/axios-base-client/axios-base-client': { Client: MockClient } + }) +} + +describe('Ledger client', () => { + describe('transaction function', () => { + beforeEach(() => { + configureSpy.resetHistory() + }) + + it('should use default base URL when base URL has not been set', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.transaction('id', 'a-gateway-account-id', {}) + + expect(configureSpy.getCall(0).args[0]).to.equal('http://127.0.0.1:8006/v1/transaction/id?account_id=a-gateway-account-id') + }) + + it('should use configured base url', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.transaction('id', 'a-gateway-account-id', { baseUrl: 'https://example.com' }) + + expect(configureSpy.getCall(0).args[0]).to.equal('https://example.com/v1/transaction/id?account_id=a-gateway-account-id') + }) + }) + + describe('transactionWithAccountOverride function', () => { + beforeEach(() => { + configureSpy.resetHistory() + }) + + it('should use default base URL when base URL has not been set', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.transactionWithAccountOverride('id', {}) + + expect(configureSpy.getCall(0).args[0]).to.equal('http://127.0.0.1:8006/v1/transaction/id?override_account_id_restriction=true') + }) + + it('should use configured base url', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.transactionWithAccountOverride('id', { baseUrl: 'https://example.com' }) + + expect(configureSpy.getCall(0).args[0]).to.equal('https://example.com/v1/transaction/id?override_account_id_restriction=true') + }) + }) + + describe('getDisputesForTransaction function', () => { + beforeEach(() => { + configureSpy.resetHistory() + }) + + it('should use default base URL when base URL has not been set', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.getDisputesForTransaction('id', 'a-gateway-account-id', {}) + + expect(configureSpy.getCall(0).args[0]).to.equal('http://127.0.0.1:8006/v1/transaction/id/transaction?gateway_account_id=a-gateway-account-id&transaction_type=DISPUTE') + }) + + it('should use configured base url', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.getDisputesForTransaction('id', 'a-gateway-account-id', { baseUrl: 'https://example.com' }) + + expect(configureSpy.getCall(0).args[0]).to.equal('https://example.com/v1/transaction/id/transaction?gateway_account_id=a-gateway-account-id&transaction_type=DISPUTE') + }) + }) + + describe('events function', () => { + beforeEach(() => { + configureSpy.resetHistory() + }) + + it('should use default base URL when base URL has not been set', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.events('transaction_id', 'a-gateway-account-id', {}) + + expect(configureSpy.getCall(0).args[0]).to.equal('http://127.0.0.1:8006/v1/transaction/transaction_id/event?gateway_account_id=a-gateway-account-id') + }) + + it('should use configured base url', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.events('transaction_id', 'a-gateway-account-id', { baseUrl: 'https://example.com' }) + + expect(configureSpy.getCall(0).args[0]).to.equal('https://example.com/v1/transaction/transaction_id/event?gateway_account_id=a-gateway-account-id') + }) + }) + + describe('transactions function', () => { + beforeEach(() => { + configureSpy.resetHistory() + }) + + it('should use default base URL when base URL has not been set', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.transactions(['a-gateway-account-id'], {}, {}) + + expect(configureSpy.getCall(0).args[0]).to.equal('http://127.0.0.1:8006/v1/transaction?account_id=a-gateway-account-id&limit_total=true&limit_total_size=5001&page=1&display_size=100') + }) + + it('should use configured base url', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.transactions(['a-gateway-account-id'], {}, { baseUrl: 'https://example.com' }) + + expect(configureSpy.getCall(0).args[0]).to.equal('https://example.com/v1/transaction?account_id=a-gateway-account-id&limit_total=true&limit_total_size=5001&page=1&display_size=100') + }) + }) + + describe('transactionSummary function', () => { + beforeEach(() => { + configureSpy.resetHistory() + }) + + it('should use default base URL when base URL has not been set', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.transactionSummary('a-gateway-account-id', '', '', {}) + + expect(configureSpy.getCall(0).args[0]).to.equal('http://127.0.0.1:8006/v1/report/transactions-summary?account_id=a-gateway-account-id&from_date=&to_date=') + }) + + it('should use configured base url', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.transactionSummary('a-gateway-account-id', '', '', { baseUrl: 'https://example.com' }) + + expect(configureSpy.getCall(0).args[0]).to.equal('https://example.com/v1/report/transactions-summary?account_id=a-gateway-account-id&from_date=&to_date=') + }) + }) + + describe('payouts function', () => { + beforeEach(() => { + configureSpy.resetHistory() + }) + + it('should use default base URL when base URL has not been set', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.payouts([], 1, 100, {}) + + expect(configureSpy.getCall(0).args[0]).to.equal('http://127.0.0.1:8006/v1/payout?gateway_account_id=&state=paidout&page=1&display_size=100') + }) + + it('should use configured base url', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.payouts([], 1, 100, { baseUrl: 'https://example.com' }) + + expect(configureSpy.getCall(0).args[0]).to.equal('https://example.com/v1/payout?gateway_account_id=&state=paidout&page=1&display_size=100') + }) + }) + + describe('agreements function', () => { + beforeEach(() => { + configureSpy.resetHistory() + }) + + it('should use default base URL when base URL has not been set', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.agreements('serviceId', 'live', 'accountId', 1, {}) + + expect(configureSpy.getCall(0).args[0]).to.equal('http://127.0.0.1:8006/v1/agreement?service_id=serviceId&account_id=accountId&live=live&page=1') + }) + + it('should use configured base url', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.agreements('serviceId', 'live', 'accountId', 1, { baseUrl: 'https://example.com' }) + + expect(configureSpy.getCall(0).args[0]).to.equal('https://example.com/v1/agreement?service_id=serviceId&account_id=accountId&live=live&page=1') + }) + }) + + describe('agreement function', () => { + beforeEach(() => { + configureSpy.resetHistory() + }) + + it('should use default base URL when base URL has not been set', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.agreement('id', 'service_id', {}) + + expect(configureSpy.getCall(0).args[0]).to.equal('http://127.0.0.1:8006/v1/agreement/id?service_id=service_id') + }) + + it('should use configured base url', async () => { + const ledgerClient = getLedgerClient() + + await ledgerClient.agreement('id', 'service_id', { baseUrl: 'https://example.com' }) + + expect(configureSpy.getCall(0).args[0]).to.equal('https://example.com/v1/agreement/id?service_id=service_id') + }) + }) +})