@defra/forms-engine-plugin 4.0.52 → 4.0.54
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.server/server/plugins/engine/components/PaymentField.js +2 -2
- package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +14 -1
- package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
- package/.server/server/plugins/engine/routes/payment-helper.d.ts +22 -0
- package/.server/server/plugins/engine/routes/payment-helper.js +29 -1
- package/.server/server/plugins/engine/routes/payment-helper.js.map +1 -1
- package/.server/server/plugins/engine/routes/payment-helper.test.js +27 -1
- package/.server/server/plugins/engine/routes/payment-helper.test.js.map +1 -1
- package/.server/server/plugins/engine/routes/payment.js +23 -1
- package/.server/server/plugins/engine/routes/payment.js.map +1 -1
- package/.server/server/plugins/payment/service.d.ts +4 -2
- package/.server/server/plugins/payment/service.js +8 -21
- package/.server/server/plugins/payment/service.js.map +1 -1
- package/.server/server/plugins/payment/service.test.js +5 -5
- package/.server/server/plugins/payment/service.test.js.map +1 -1
- package/package.json +1 -1
- package/src/server/plugins/engine/components/PaymentField.ts +5 -1
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +84 -0
- package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +20 -1
- package/src/server/plugins/engine/routes/payment-helper.js +38 -1
- package/src/server/plugins/engine/routes/payment-helper.test.js +44 -1
- package/src/server/plugins/engine/routes/payment.js +46 -1
- package/src/server/plugins/payment/service.js +32 -24
- package/src/server/plugins/payment/service.test.js +8 -2
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { StatusCodes } from 'http-status-codes';
|
|
2
2
|
import { createLogger } from "../../common/helpers/logging/logger.js";
|
|
3
|
+
import { buildPaymentInfo, convertPenceToPounds } from "../engine/routes/payment-helper.js";
|
|
3
4
|
import { get, post, postJson } from "../../services/httpService.js";
|
|
4
5
|
const PAYMENT_BASE_URL = 'https://publicapi.payments.service.gov.uk';
|
|
5
6
|
const PAYMENT_ENDPOINT = '/v1/payments';
|
|
@@ -31,9 +32,10 @@ export class PaymentService {
|
|
|
31
32
|
* @param {string} description
|
|
32
33
|
* @param {string} returnUrl
|
|
33
34
|
* @param {string} reference
|
|
35
|
+
* @param {boolean} isLivePayment
|
|
34
36
|
* @param {{ formId: string, slug: string }} metadata
|
|
35
37
|
*/
|
|
36
|
-
async createPayment(amount, description, returnUrl, reference, metadata) {
|
|
38
|
+
async createPayment(amount, description, returnUrl, reference, isLivePayment, metadata) {
|
|
37
39
|
const response = await this.postToPayProvider({
|
|
38
40
|
amount,
|
|
39
41
|
description,
|
|
@@ -42,15 +44,7 @@ export class PaymentService {
|
|
|
42
44
|
return_url: returnUrl,
|
|
43
45
|
delayed_capture: true
|
|
44
46
|
});
|
|
45
|
-
logger.info({
|
|
46
|
-
event: {
|
|
47
|
-
category: 'payment',
|
|
48
|
-
action: 'create-payment',
|
|
49
|
-
outcome: 'success',
|
|
50
|
-
reason: `amount=${amount}`,
|
|
51
|
-
reference: response.payment_id
|
|
52
|
-
}
|
|
53
|
-
}, `[payment] Created payment and user taken to enter pre-auth details for paymentId=${response.payment_id}`);
|
|
47
|
+
logger.info(buildPaymentInfo('create-payment', 'success', `amount=${convertPenceToPounds(amount)}`, isLivePayment, response.payment_id), `[payment] Created payment and user taken to enter pre-auth details for paymentId=${response.payment_id}`);
|
|
54
48
|
return {
|
|
55
49
|
paymentId: response.payment_id,
|
|
56
50
|
paymentUrl: response._links.next_url.href
|
|
@@ -59,9 +53,10 @@ export class PaymentService {
|
|
|
59
53
|
|
|
60
54
|
/**
|
|
61
55
|
* @param {string} paymentId
|
|
56
|
+
* @param {boolean} isLivePayment
|
|
62
57
|
* @returns {Promise<GetPaymentResponse>}
|
|
63
58
|
*/
|
|
64
|
-
async getPaymentStatus(paymentId) {
|
|
59
|
+
async getPaymentStatus(paymentId, isLivePayment) {
|
|
65
60
|
const getByType = /** @type {typeof get<GetPaymentApiResponse>} */get;
|
|
66
61
|
try {
|
|
67
62
|
const response = await getByType(`${PAYMENT_BASE_URL}${PAYMENT_ENDPOINT}/${paymentId}`, {
|
|
@@ -73,15 +68,7 @@ export class PaymentService {
|
|
|
73
68
|
throw new Error(`Failed to get payment status: ${errorMessage}`);
|
|
74
69
|
}
|
|
75
70
|
const state = response.payload.state;
|
|
76
|
-
logger.info({
|
|
77
|
-
event: {
|
|
78
|
-
category: 'payment',
|
|
79
|
-
action: 'get-payment-status',
|
|
80
|
-
outcome: state.status === 'capturable' || state.status === 'success' ? 'success' : 'failure',
|
|
81
|
-
reason: `status:${state.status} code:${state.code ?? 'N/A'} message:${state.message ?? 'N/A'}`,
|
|
82
|
-
reference: paymentId
|
|
83
|
-
}
|
|
84
|
-
}, `[payment] Got payment status for paymentId=${paymentId}: status=${state.status}`);
|
|
71
|
+
logger.info(buildPaymentInfo('get-payment-status', state.status === 'capturable' || state.status === 'success' ? 'success' : 'failure', `status:${state.status} code:${state.code ?? 'N/A'} message:${state.message ?? 'N/A'}`, isLivePayment, paymentId), `[payment] Got payment status for paymentId=${paymentId}: status=${state.status}`);
|
|
85
72
|
return {
|
|
86
73
|
state,
|
|
87
74
|
_links: response.payload._links,
|
|
@@ -114,7 +101,7 @@ export class PaymentService {
|
|
|
114
101
|
category: 'payment',
|
|
115
102
|
action: 'capture-payment',
|
|
116
103
|
outcome: 'success',
|
|
117
|
-
reason: `amount=${amount}`,
|
|
104
|
+
reason: `amount=${convertPenceToPounds(amount)}`,
|
|
118
105
|
reference: paymentId
|
|
119
106
|
}
|
|
120
107
|
}, `[payment] Successfully captured payment for paymentId=${paymentId}`);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.js","names":["StatusCodes","createLogger","get","post","postJson","PAYMENT_BASE_URL","PAYMENT_ENDPOINT","logger","getAuthHeaders","apiKey","Authorization","PaymentService","constructor","createPayment","amount","description","returnUrl","reference","metadata","response","postToPayProvider","return_url","delayed_capture","info","event","category","action","outcome","reason","payment_id","paymentId","paymentUrl","_links","next_url","href","getPaymentStatus","getByType","headers","json","error","errorMessage","Error","message","JSON","stringify","state","payload","status","code","email","err","capturePayment","statusCode","res","OK","NO_CONTENT","postJsonByType"],"sources":["../../../../src/server/plugins/payment/service.js"],"sourcesContent":["import { StatusCodes } from 'http-status-codes'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { get, post, postJson } from '~/src/server/services/httpService.js'\n\nconst PAYMENT_BASE_URL = 'https://publicapi.payments.service.gov.uk'\nconst PAYMENT_ENDPOINT = '/v1/payments'\n\nconst logger = createLogger()\n\n/**\n * @param {string} apiKey\n * @returns {{ Authorization: string }}\n */\nfunction getAuthHeaders(apiKey) {\n return {\n Authorization: `Bearer ${apiKey}`\n }\n}\n\nexport class PaymentService {\n /** @type {string} */\n #apiKey\n\n /**\n * @param {string} apiKey - API key to use (global config for test value, per-form config for live value)\n */\n constructor(apiKey) {\n this.#apiKey = apiKey\n }\n\n /**\n * Creates a payment with delayed capture (pre-authorisation)\n * @param {number} amount - in pence\n * @param {string} description\n * @param {string} returnUrl\n * @param {string} reference\n * @param {{ formId: string, slug: string }} metadata\n */\n async createPayment(amount, description, returnUrl, reference, metadata) {\n const response = await this.postToPayProvider({\n amount,\n description,\n reference,\n metadata,\n return_url: returnUrl,\n delayed_capture: true\n })\n\n logger.info(\n {\n event: {\n category: 'payment',\n action: 'create-payment',\n outcome: 'success',\n reason: `amount=${amount}`,\n reference: response.payment_id\n }\n },\n `[payment] Created payment and user taken to enter pre-auth details for paymentId=${response.payment_id}`\n )\n\n return {\n paymentId: response.payment_id,\n paymentUrl: response._links.next_url.href\n }\n }\n\n /**\n * @param {string} paymentId\n * @returns {Promise<GetPaymentResponse>}\n */\n async getPaymentStatus(paymentId) {\n const getByType = /** @type {typeof get<GetPaymentApiResponse>} */ (get)\n\n try {\n const response = await getByType(\n `${PAYMENT_BASE_URL}${PAYMENT_ENDPOINT}/${paymentId}`,\n {\n headers: getAuthHeaders(this.#apiKey),\n json: true\n }\n )\n\n if (response.error) {\n const errorMessage =\n response.error instanceof Error\n ? response.error.message\n : JSON.stringify(response.error)\n throw new Error(`Failed to get payment status: ${errorMessage}`)\n }\n\n const state = response.payload.state\n logger.info(\n {\n event: {\n category: 'payment',\n action: 'get-payment-status',\n outcome:\n state.status === 'capturable' || state.status === 'success'\n ? 'success'\n : 'failure',\n reason: `status:${state.status} code:${state.code ?? 'N/A'} message:${state.message ?? 'N/A'}`,\n reference: paymentId\n }\n },\n `[payment] Got payment status for paymentId=${paymentId}: status=${state.status}`\n )\n\n return {\n state,\n _links: response.payload._links,\n email: response.payload.email,\n paymentId: response.payload.payment_id,\n amount: response.payload.amount\n }\n } catch (err) {\n const error = /** @type {Error} */ (err)\n logger.error(\n error,\n `[payment] Error getting payment status for paymentId=${paymentId}: ${error.message}`\n )\n throw err\n }\n }\n\n /**\n * Captures a payment that is in 'capturable' status\n * @param {string} paymentId\n * @param {number} amount\n * @returns {Promise<boolean>}\n */\n async capturePayment(paymentId, amount) {\n try {\n const response = await post(\n `${PAYMENT_BASE_URL}${PAYMENT_ENDPOINT}/${paymentId}/capture`,\n {\n headers: getAuthHeaders(this.#apiKey)\n }\n )\n\n const statusCode = response.res.statusCode\n\n if (\n statusCode === StatusCodes.OK ||\n statusCode === StatusCodes.NO_CONTENT\n ) {\n logger.info(\n {\n event: {\n category: 'payment',\n action: 'capture-payment',\n outcome: 'success',\n reason: `amount=${amount}`,\n reference: paymentId\n }\n },\n `[payment] Successfully captured payment for paymentId=${paymentId}`\n )\n return true\n }\n\n logger.error(\n `[payment] Capture failed for paymentId=${paymentId}: HTTP ${statusCode}`\n )\n return false\n } catch (err) {\n const error = /** @type {Error} */ (err)\n logger.error(\n error,\n `[payment] Error capturing payment for paymentId=${paymentId}: ${error.message}`\n )\n throw err\n }\n }\n\n /**\n * @param {CreatePaymentRequest} payload\n */\n async postToPayProvider(payload) {\n const postJsonByType =\n /** @type {typeof postJson<CreatePaymentResponse>} */ (postJson)\n\n try {\n const response = await postJsonByType(\n `${PAYMENT_BASE_URL}${PAYMENT_ENDPOINT}`,\n {\n payload,\n headers: getAuthHeaders(this.#apiKey)\n }\n )\n\n if (response.payload?.state.status !== 'created') {\n throw new Error(\n `Failed to create payment for reference=${payload.reference}`\n )\n }\n\n return response.payload\n } catch (err) {\n const error = /** @type {Error} */ (err)\n logger.error(\n error,\n `[payment] Error creating payment for reference=${payload.reference}: ${error.message}`\n )\n throw err\n }\n }\n}\n\n/**\n * @import { CreatePaymentRequest, CreatePaymentResponse, GetPaymentApiResponse, GetPaymentResponse } from '~/src/server/plugins/payment/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"service.js","names":["StatusCodes","createLogger","buildPaymentInfo","convertPenceToPounds","get","post","postJson","PAYMENT_BASE_URL","PAYMENT_ENDPOINT","logger","getAuthHeaders","apiKey","Authorization","PaymentService","constructor","createPayment","amount","description","returnUrl","reference","isLivePayment","metadata","response","postToPayProvider","return_url","delayed_capture","info","payment_id","paymentId","paymentUrl","_links","next_url","href","getPaymentStatus","getByType","headers","json","error","errorMessage","Error","message","JSON","stringify","state","payload","status","code","email","err","capturePayment","statusCode","res","OK","NO_CONTENT","event","category","action","outcome","reason","postJsonByType"],"sources":["../../../../src/server/plugins/payment/service.js"],"sourcesContent":["import { StatusCodes } from 'http-status-codes'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport {\n buildPaymentInfo,\n convertPenceToPounds\n} from '~/src/server/plugins/engine/routes/payment-helper.js'\nimport { get, post, postJson } from '~/src/server/services/httpService.js'\n\nconst PAYMENT_BASE_URL = 'https://publicapi.payments.service.gov.uk'\nconst PAYMENT_ENDPOINT = '/v1/payments'\n\nconst logger = createLogger()\n\n/**\n * @param {string} apiKey\n * @returns {{ Authorization: string }}\n */\nfunction getAuthHeaders(apiKey) {\n return {\n Authorization: `Bearer ${apiKey}`\n }\n}\n\nexport class PaymentService {\n /** @type {string} */\n #apiKey\n\n /**\n * @param {string} apiKey - API key to use (global config for test value, per-form config for live value)\n */\n constructor(apiKey) {\n this.#apiKey = apiKey\n }\n\n /**\n * Creates a payment with delayed capture (pre-authorisation)\n * @param {number} amount - in pence\n * @param {string} description\n * @param {string} returnUrl\n * @param {string} reference\n * @param {boolean} isLivePayment\n * @param {{ formId: string, slug: string }} metadata\n */\n async createPayment(\n amount,\n description,\n returnUrl,\n reference,\n isLivePayment,\n metadata\n ) {\n const response = await this.postToPayProvider({\n amount,\n description,\n reference,\n metadata,\n return_url: returnUrl,\n delayed_capture: true\n })\n\n logger.info(\n buildPaymentInfo(\n 'create-payment',\n 'success',\n `amount=${convertPenceToPounds(amount)}`,\n isLivePayment,\n response.payment_id\n ),\n `[payment] Created payment and user taken to enter pre-auth details for paymentId=${response.payment_id}`\n )\n\n return {\n paymentId: response.payment_id,\n paymentUrl: response._links.next_url.href\n }\n }\n\n /**\n * @param {string} paymentId\n * @param {boolean} isLivePayment\n * @returns {Promise<GetPaymentResponse>}\n */\n async getPaymentStatus(paymentId, isLivePayment) {\n const getByType = /** @type {typeof get<GetPaymentApiResponse>} */ (get)\n\n try {\n const response = await getByType(\n `${PAYMENT_BASE_URL}${PAYMENT_ENDPOINT}/${paymentId}`,\n {\n headers: getAuthHeaders(this.#apiKey),\n json: true\n }\n )\n\n if (response.error) {\n const errorMessage =\n response.error instanceof Error\n ? response.error.message\n : JSON.stringify(response.error)\n throw new Error(`Failed to get payment status: ${errorMessage}`)\n }\n\n const state = response.payload.state\n logger.info(\n buildPaymentInfo(\n 'get-payment-status',\n state.status === 'capturable' || state.status === 'success'\n ? 'success'\n : 'failure',\n `status:${state.status} code:${state.code ?? 'N/A'} message:${state.message ?? 'N/A'}`,\n isLivePayment,\n paymentId\n ),\n `[payment] Got payment status for paymentId=${paymentId}: status=${state.status}`\n )\n\n return {\n state,\n _links: response.payload._links,\n email: response.payload.email,\n paymentId: response.payload.payment_id,\n amount: response.payload.amount\n }\n } catch (err) {\n const error = /** @type {Error} */ (err)\n logger.error(\n error,\n `[payment] Error getting payment status for paymentId=${paymentId}: ${error.message}`\n )\n throw err\n }\n }\n\n /**\n * Captures a payment that is in 'capturable' status\n * @param {string} paymentId\n * @param {number} amount\n * @returns {Promise<boolean>}\n */\n async capturePayment(paymentId, amount) {\n try {\n const response = await post(\n `${PAYMENT_BASE_URL}${PAYMENT_ENDPOINT}/${paymentId}/capture`,\n {\n headers: getAuthHeaders(this.#apiKey)\n }\n )\n\n const statusCode = response.res.statusCode\n\n if (\n statusCode === StatusCodes.OK ||\n statusCode === StatusCodes.NO_CONTENT\n ) {\n logger.info(\n {\n event: {\n category: 'payment',\n action: 'capture-payment',\n outcome: 'success',\n reason: `amount=${convertPenceToPounds(amount)}`,\n reference: paymentId\n }\n },\n `[payment] Successfully captured payment for paymentId=${paymentId}`\n )\n return true\n }\n\n logger.error(\n `[payment] Capture failed for paymentId=${paymentId}: HTTP ${statusCode}`\n )\n return false\n } catch (err) {\n const error = /** @type {Error} */ (err)\n logger.error(\n error,\n `[payment] Error capturing payment for paymentId=${paymentId}: ${error.message}`\n )\n throw err\n }\n }\n\n /**\n * @param {CreatePaymentRequest} payload\n */\n async postToPayProvider(payload) {\n const postJsonByType =\n /** @type {typeof postJson<CreatePaymentResponse>} */ (postJson)\n\n try {\n const response = await postJsonByType(\n `${PAYMENT_BASE_URL}${PAYMENT_ENDPOINT}`,\n {\n payload,\n headers: getAuthHeaders(this.#apiKey)\n }\n )\n\n if (response.payload?.state.status !== 'created') {\n throw new Error(\n `Failed to create payment for reference=${payload.reference}`\n )\n }\n\n return response.payload\n } catch (err) {\n const error = /** @type {Error} */ (err)\n logger.error(\n error,\n `[payment] Error creating payment for reference=${payload.reference}: ${error.message}`\n )\n throw err\n }\n }\n}\n\n/**\n * @import { CreatePaymentRequest, CreatePaymentResponse, GetPaymentApiResponse, GetPaymentResponse } from '~/src/server/plugins/payment/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -35,7 +35,7 @@ describe('payment service', () => {
|
|
|
35
35
|
formId: 'form-id',
|
|
36
36
|
slug: 'my-form-slug'
|
|
37
37
|
};
|
|
38
|
-
const payment = await service.createPayment(100, 'Payment description', returnUrl, referenceNumber, metadata);
|
|
38
|
+
const payment = await service.createPayment(100, 'Payment description', returnUrl, referenceNumber, false, metadata);
|
|
39
39
|
expect(payment.paymentId).toBe('payment-id-12345');
|
|
40
40
|
expect(payment.paymentUrl).toBe('http://next-url-href/payment');
|
|
41
41
|
});
|
|
@@ -47,7 +47,7 @@ describe('payment service', () => {
|
|
|
47
47
|
formId: 'form-id',
|
|
48
48
|
slug: 'my-form-slug'
|
|
49
49
|
};
|
|
50
|
-
await expect(() => service.createPayment(100, 'Payment description', returnUrl, referenceNumber, metadata)).rejects.toThrow('internal creation error');
|
|
50
|
+
await expect(() => service.createPayment(100, 'Payment description', returnUrl, referenceNumber, false, metadata)).rejects.toThrow('internal creation error');
|
|
51
51
|
});
|
|
52
52
|
it('should throw if fails to create a payment - bad result from API call', async () => {
|
|
53
53
|
const createPaymentResult = {
|
|
@@ -69,7 +69,7 @@ describe('payment service', () => {
|
|
|
69
69
|
formId: 'form-id',
|
|
70
70
|
slug: 'my-form-slug'
|
|
71
71
|
};
|
|
72
|
-
await expect(() => service.createPayment(100, 'Payment description', returnUrl, referenceNumber, metadata)).rejects.toThrow('Failed to create payment');
|
|
72
|
+
await expect(() => service.createPayment(100, 'Payment description', returnUrl, referenceNumber, false, metadata)).rejects.toThrow('Failed to create payment');
|
|
73
73
|
});
|
|
74
74
|
});
|
|
75
75
|
describe('getPaymentStatus', () => {
|
|
@@ -93,7 +93,7 @@ describe('payment service', () => {
|
|
|
93
93
|
payload: getPaymentStatusResult,
|
|
94
94
|
error: undefined
|
|
95
95
|
});
|
|
96
|
-
const paymentStatus = await service.getPaymentStatus('payment-id-12345');
|
|
96
|
+
const paymentStatus = await service.getPaymentStatus('payment-id-12345', false);
|
|
97
97
|
expect(paymentStatus.paymentId).toBe('payment-id-12345');
|
|
98
98
|
expect(paymentStatus._links.next_url?.href).toBe('http://next-url-href/payment');
|
|
99
99
|
});
|
|
@@ -106,7 +106,7 @@ describe('payment service', () => {
|
|
|
106
106
|
payload: undefined,
|
|
107
107
|
error: new Error('some-error')
|
|
108
108
|
});
|
|
109
|
-
await expect(() => service.getPaymentStatus('payment-id-12345')).rejects.toThrow('Failed to get payment status: some-error');
|
|
109
|
+
await expect(() => service.getPaymentStatus('payment-id-12345', false)).rejects.toThrow('Failed to get payment status: some-error');
|
|
110
110
|
});
|
|
111
111
|
});
|
|
112
112
|
describe('capturePayment', () => {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"service.test.js","names":["PaymentService","get","post","postJson","jest","mock","describe","service","it","expect","toBeDefined","createPaymentResult","payment_id","_links","next_url","href","state","status","mocked","mockResolvedValueOnce","res","statusCode","headers","payload","error","undefined","referenceNumber","returnUrl","metadata","formId","slug","payment","createPayment","paymentId","toBe","paymentUrl","mockRejectedValueOnce","Error","rejects","toThrow","getPaymentStatusResult","paymentStatus","getPaymentStatus","capturePaymentResult","captureResult","capturePayment"],"sources":["../../../../src/server/plugins/payment/service.test.js"],"sourcesContent":["import { PaymentService } from '~/src/server/plugins/payment/service.js'\nimport { get, post, postJson } from '~/src/server/services/httpService.js'\n\njest.mock('~/src/server/services/httpService.ts')\n\ndescribe('payment service', () => {\n const service = new PaymentService('my-api-key')\n describe('constructor', () => {\n it('should create instance', () => {\n expect(service).toBeDefined()\n })\n })\n\n describe('createPayment', () => {\n it('should create a payment', async () => {\n const createPaymentResult = {\n payment_id: 'payment-id-12345',\n _links: {\n next_url: {\n href: 'http://next-url-href/payment'\n }\n },\n state: {\n status: 'created'\n }\n }\n jest.mocked(postJson).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: createPaymentResult,\n error: undefined\n })\n\n const referenceNumber = 'ABC-DEF-123'\n const returnUrl = 'http://localhost:3009/payment-callback-handler'\n const metadata = { formId: 'form-id', slug: 'my-form-slug' }\n const payment = await service.createPayment(\n 100,\n 'Payment description',\n returnUrl,\n referenceNumber,\n metadata\n )\n expect(payment.paymentId).toBe('payment-id-12345')\n expect(payment.paymentUrl).toBe('http://next-url-href/payment')\n })\n\n it('should throw if fails to create a payment - failed API call', async () => {\n jest\n .mocked(postJson)\n .mockRejectedValueOnce(new Error('internal creation error'))\n\n const referenceNumber = 'ABC-DEF-123'\n const returnUrl = 'http://localhost:3009/payment-callback-handler'\n const metadata = { formId: 'form-id', slug: 'my-form-slug' }\n await expect(() =>\n service.createPayment(\n 100,\n 'Payment description',\n returnUrl,\n referenceNumber,\n metadata\n )\n ).rejects.toThrow('internal creation error')\n })\n\n it('should throw if fails to create a payment - bad result from API call', async () => {\n const createPaymentResult = {\n state: {\n status: 'failed'\n }\n }\n jest.mocked(postJson).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: createPaymentResult,\n error: undefined\n })\n\n const referenceNumber = 'ABC-DEF-123'\n const returnUrl = 'http://localhost:3009/payment-callback-handler'\n const metadata = { formId: 'form-id', slug: 'my-form-slug' }\n await expect(() =>\n service.createPayment(\n 100,\n 'Payment description',\n returnUrl,\n referenceNumber,\n metadata\n )\n ).rejects.toThrow('Failed to create payment')\n })\n })\n\n describe('getPaymentStatus', () => {\n it('should get payment status if exists', async () => {\n const getPaymentStatusResult = {\n payment_id: 'payment-id-12345',\n _links: {\n next_url: {\n href: 'http://next-url-href/payment'\n }\n },\n state: {\n status: 'created'\n }\n }\n\n jest.mocked(get).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: getPaymentStatusResult,\n error: undefined\n })\n\n const paymentStatus = await service.getPaymentStatus('payment-id-12345')\n expect(paymentStatus.paymentId).toBe('payment-id-12345')\n expect(paymentStatus._links.next_url?.href).toBe(\n 'http://next-url-href/payment'\n )\n })\n\n it('should handle payment status error', async () => {\n jest.mocked(get).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: undefined,\n error: new Error('some-error')\n })\n\n await expect(() =>\n service.getPaymentStatus('payment-id-12345')\n ).rejects.toThrow('Failed to get payment status: some-error')\n })\n })\n\n describe('capturePayment', () => {\n it('should return true when successful capture with statusCode 200', async () => {\n const capturePaymentResult = {}\n jest.mocked(post).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: capturePaymentResult,\n error: undefined\n })\n\n const captureResult = await service.capturePayment(\n 'payment-id-12345',\n 100\n )\n expect(captureResult).toBe(true)\n })\n\n it('should return true when successful capture with statusCode 204', async () => {\n const capturePaymentResult = {}\n jest.mocked(post).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 204,\n headers: {}\n }),\n payload: capturePaymentResult,\n error: undefined\n })\n\n const captureResult = await service.capturePayment(\n 'payment-id-12345',\n 100\n )\n expect(captureResult).toBe(true)\n })\n\n it('should return false when status code not 200 or 204', async () => {\n const capturePaymentResult = {}\n jest.mocked(post).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 500,\n headers: {}\n }),\n payload: capturePaymentResult,\n error: undefined\n })\n\n const captureResult = await service.capturePayment(\n 'payment-id-12345',\n 100\n )\n expect(captureResult).toBe(false)\n })\n\n it('should throw when internal error', async () => {\n jest\n .mocked(post)\n .mockRejectedValueOnce(new Error('internal capture error'))\n\n await expect(() =>\n service.capturePayment('payment-id-12345', 100)\n ).rejects.toThrow('internal capture error')\n })\n })\n})\n\n/**\n * @import { IncomingMessage } from 'node:http'\n */\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"service.test.js","names":["PaymentService","get","post","postJson","jest","mock","describe","service","it","expect","toBeDefined","createPaymentResult","payment_id","_links","next_url","href","state","status","mocked","mockResolvedValueOnce","res","statusCode","headers","payload","error","undefined","referenceNumber","returnUrl","metadata","formId","slug","payment","createPayment","paymentId","toBe","paymentUrl","mockRejectedValueOnce","Error","rejects","toThrow","getPaymentStatusResult","paymentStatus","getPaymentStatus","capturePaymentResult","captureResult","capturePayment"],"sources":["../../../../src/server/plugins/payment/service.test.js"],"sourcesContent":["import { PaymentService } from '~/src/server/plugins/payment/service.js'\nimport { get, post, postJson } from '~/src/server/services/httpService.js'\n\njest.mock('~/src/server/services/httpService.ts')\n\ndescribe('payment service', () => {\n const service = new PaymentService('my-api-key')\n describe('constructor', () => {\n it('should create instance', () => {\n expect(service).toBeDefined()\n })\n })\n\n describe('createPayment', () => {\n it('should create a payment', async () => {\n const createPaymentResult = {\n payment_id: 'payment-id-12345',\n _links: {\n next_url: {\n href: 'http://next-url-href/payment'\n }\n },\n state: {\n status: 'created'\n }\n }\n jest.mocked(postJson).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: createPaymentResult,\n error: undefined\n })\n\n const referenceNumber = 'ABC-DEF-123'\n const returnUrl = 'http://localhost:3009/payment-callback-handler'\n const metadata = { formId: 'form-id', slug: 'my-form-slug' }\n const payment = await service.createPayment(\n 100,\n 'Payment description',\n returnUrl,\n referenceNumber,\n false,\n metadata\n )\n expect(payment.paymentId).toBe('payment-id-12345')\n expect(payment.paymentUrl).toBe('http://next-url-href/payment')\n })\n\n it('should throw if fails to create a payment - failed API call', async () => {\n jest\n .mocked(postJson)\n .mockRejectedValueOnce(new Error('internal creation error'))\n\n const referenceNumber = 'ABC-DEF-123'\n const returnUrl = 'http://localhost:3009/payment-callback-handler'\n const metadata = { formId: 'form-id', slug: 'my-form-slug' }\n await expect(() =>\n service.createPayment(\n 100,\n 'Payment description',\n returnUrl,\n referenceNumber,\n false,\n metadata\n )\n ).rejects.toThrow('internal creation error')\n })\n\n it('should throw if fails to create a payment - bad result from API call', async () => {\n const createPaymentResult = {\n state: {\n status: 'failed'\n }\n }\n jest.mocked(postJson).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: createPaymentResult,\n error: undefined\n })\n\n const referenceNumber = 'ABC-DEF-123'\n const returnUrl = 'http://localhost:3009/payment-callback-handler'\n const metadata = { formId: 'form-id', slug: 'my-form-slug' }\n await expect(() =>\n service.createPayment(\n 100,\n 'Payment description',\n returnUrl,\n referenceNumber,\n false,\n metadata\n )\n ).rejects.toThrow('Failed to create payment')\n })\n })\n\n describe('getPaymentStatus', () => {\n it('should get payment status if exists', async () => {\n const getPaymentStatusResult = {\n payment_id: 'payment-id-12345',\n _links: {\n next_url: {\n href: 'http://next-url-href/payment'\n }\n },\n state: {\n status: 'created'\n }\n }\n\n jest.mocked(get).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: getPaymentStatusResult,\n error: undefined\n })\n\n const paymentStatus = await service.getPaymentStatus(\n 'payment-id-12345',\n false\n )\n expect(paymentStatus.paymentId).toBe('payment-id-12345')\n expect(paymentStatus._links.next_url?.href).toBe(\n 'http://next-url-href/payment'\n )\n })\n\n it('should handle payment status error', async () => {\n jest.mocked(get).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: undefined,\n error: new Error('some-error')\n })\n\n await expect(() =>\n service.getPaymentStatus('payment-id-12345', false)\n ).rejects.toThrow('Failed to get payment status: some-error')\n })\n })\n\n describe('capturePayment', () => {\n it('should return true when successful capture with statusCode 200', async () => {\n const capturePaymentResult = {}\n jest.mocked(post).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: capturePaymentResult,\n error: undefined\n })\n\n const captureResult = await service.capturePayment(\n 'payment-id-12345',\n 100\n )\n expect(captureResult).toBe(true)\n })\n\n it('should return true when successful capture with statusCode 204', async () => {\n const capturePaymentResult = {}\n jest.mocked(post).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 204,\n headers: {}\n }),\n payload: capturePaymentResult,\n error: undefined\n })\n\n const captureResult = await service.capturePayment(\n 'payment-id-12345',\n 100\n )\n expect(captureResult).toBe(true)\n })\n\n it('should return false when status code not 200 or 204', async () => {\n const capturePaymentResult = {}\n jest.mocked(post).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 500,\n headers: {}\n }),\n payload: capturePaymentResult,\n error: undefined\n })\n\n const captureResult = await service.capturePayment(\n 'payment-id-12345',\n 100\n )\n expect(captureResult).toBe(false)\n })\n\n it('should throw when internal error', async () => {\n jest\n .mocked(post)\n .mockRejectedValueOnce(new Error('internal capture error'))\n\n await expect(() =>\n service.capturePayment('payment-id-12345', 100)\n ).rejects.toThrow('internal capture error')\n })\n })\n})\n\n/**\n * @import { IncomingMessage } from 'node:http'\n */\n"],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -225,6 +225,7 @@ export class PaymentField extends FormComponent {
|
|
|
225
225
|
description,
|
|
226
226
|
payCallbackUrl,
|
|
227
227
|
reference,
|
|
228
|
+
isLivePayment,
|
|
228
229
|
{ formId, slug }
|
|
229
230
|
)
|
|
230
231
|
|
|
@@ -276,7 +277,10 @@ export class PaymentField extends FormComponent {
|
|
|
276
277
|
/**
|
|
277
278
|
* @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle
|
|
278
279
|
*/
|
|
279
|
-
const status = await paymentService.getPaymentStatus(
|
|
280
|
+
const status = await paymentService.getPaymentStatus(
|
|
281
|
+
paymentId,
|
|
282
|
+
isLivePayment
|
|
283
|
+
)
|
|
280
284
|
|
|
281
285
|
PaymentSubmissionError.checkPaymentAmount(
|
|
282
286
|
status.amount,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/dot-notation */
|
|
2
2
|
import { ComponentType, type ComponentDef } from '@defra/forms-model'
|
|
3
|
+
import Boom from '@hapi/boom'
|
|
3
4
|
import { type ValidationErrorItem, type ValidationResult } from 'joi'
|
|
4
5
|
|
|
5
6
|
import {
|
|
@@ -195,6 +196,89 @@ describe('FileUploadPageController', () => {
|
|
|
195
196
|
)
|
|
196
197
|
})
|
|
197
198
|
|
|
199
|
+
it('initiates new upload when getUploadStatus throws a 404 error', async () => {
|
|
200
|
+
const state = {
|
|
201
|
+
upload: {
|
|
202
|
+
[controller.path]: {
|
|
203
|
+
upload: {
|
|
204
|
+
uploadId: 'some-id',
|
|
205
|
+
uploadUrl: 'some-url',
|
|
206
|
+
statusUrl: 'some-status-url'
|
|
207
|
+
},
|
|
208
|
+
files: []
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
} as unknown as FormSubmissionState
|
|
212
|
+
|
|
213
|
+
const notFoundError = Boom.notFound('Upload not found')
|
|
214
|
+
|
|
215
|
+
jest
|
|
216
|
+
.spyOn(uploadService, 'getUploadStatus')
|
|
217
|
+
.mockRejectedValue(notFoundError)
|
|
218
|
+
|
|
219
|
+
const testController = controller as TestableFileUploadPageController
|
|
220
|
+
const initiateSpy = jest.spyOn(
|
|
221
|
+
testController,
|
|
222
|
+
'initiateAndStoreNewUpload'
|
|
223
|
+
)
|
|
224
|
+
initiateSpy.mockResolvedValue(state as never)
|
|
225
|
+
|
|
226
|
+
const result = await controller['checkUploadStatus'](request, state, 1)
|
|
227
|
+
|
|
228
|
+
expect(initiateSpy).toHaveBeenCalledWith(request, state)
|
|
229
|
+
expect(result).toBe(state)
|
|
230
|
+
})
|
|
231
|
+
|
|
232
|
+
it('re-throws non-404 Boom errors from getUploadStatus', async () => {
|
|
233
|
+
const state = {
|
|
234
|
+
upload: {
|
|
235
|
+
[controller.path]: {
|
|
236
|
+
upload: {
|
|
237
|
+
uploadId: 'some-id',
|
|
238
|
+
uploadUrl: 'some-url',
|
|
239
|
+
statusUrl: 'some-status-url'
|
|
240
|
+
},
|
|
241
|
+
files: []
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
} as unknown as FormSubmissionState
|
|
245
|
+
|
|
246
|
+
const serverError = Boom.internal('Server error')
|
|
247
|
+
|
|
248
|
+
jest
|
|
249
|
+
.spyOn(uploadService, 'getUploadStatus')
|
|
250
|
+
.mockRejectedValue(serverError)
|
|
251
|
+
|
|
252
|
+
await expect(
|
|
253
|
+
controller['checkUploadStatus'](request, state, 1)
|
|
254
|
+
).rejects.toThrow('Server error')
|
|
255
|
+
})
|
|
256
|
+
|
|
257
|
+
it('re-throws non-Boom errors from getUploadStatus', async () => {
|
|
258
|
+
const state = {
|
|
259
|
+
upload: {
|
|
260
|
+
[controller.path]: {
|
|
261
|
+
upload: {
|
|
262
|
+
uploadId: 'some-id',
|
|
263
|
+
uploadUrl: 'some-url',
|
|
264
|
+
statusUrl: 'some-status-url'
|
|
265
|
+
},
|
|
266
|
+
files: []
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
} as unknown as FormSubmissionState
|
|
270
|
+
|
|
271
|
+
const networkError = new Error('Network failure')
|
|
272
|
+
|
|
273
|
+
jest
|
|
274
|
+
.spyOn(uploadService, 'getUploadStatus')
|
|
275
|
+
.mockRejectedValue(networkError)
|
|
276
|
+
|
|
277
|
+
await expect(
|
|
278
|
+
controller['checkUploadStatus'](request, state, 1)
|
|
279
|
+
).rejects.toThrow('Network failure')
|
|
280
|
+
})
|
|
281
|
+
|
|
198
282
|
it('handles pending upload with backoff and retries', async () => {
|
|
199
283
|
const state = {
|
|
200
284
|
upload: {
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ComponentType, type PageFileUpload } from '@defra/forms-model'
|
|
2
2
|
import Boom from '@hapi/boom'
|
|
3
3
|
import { wait } from '@hapi/hoek'
|
|
4
|
+
import { StatusCodes } from 'http-status-codes'
|
|
4
5
|
import { type ValidationErrorItem } from 'joi'
|
|
5
6
|
|
|
6
7
|
import {
|
|
@@ -327,7 +328,25 @@ export class FileUploadPageController extends QuestionPageController {
|
|
|
327
328
|
}
|
|
328
329
|
|
|
329
330
|
const uploadId = upload.uploadId
|
|
330
|
-
|
|
331
|
+
|
|
332
|
+
let statusResponse
|
|
333
|
+
|
|
334
|
+
try {
|
|
335
|
+
statusResponse = await getUploadStatus(uploadId)
|
|
336
|
+
} catch (err) {
|
|
337
|
+
// if the user loads a file upload page and queries the cached upload, after the upload has
|
|
338
|
+
// expired in CDP, we will get a 404 from the getUploadStatus endpoint.
|
|
339
|
+
// In this case we want to initiate a new upload and return that state, so the form
|
|
340
|
+
// doesn't blow up for the end user.
|
|
341
|
+
if (
|
|
342
|
+
Boom.isBoom(err) &&
|
|
343
|
+
err.output.statusCode === StatusCodes.NOT_FOUND.valueOf()
|
|
344
|
+
) {
|
|
345
|
+
return this.initiateAndStoreNewUpload(request, state)
|
|
346
|
+
}
|
|
347
|
+
throw err
|
|
348
|
+
}
|
|
349
|
+
|
|
331
350
|
if (!statusResponse) {
|
|
332
351
|
throw Boom.badRequest(
|
|
333
352
|
`Unexpected empty response from getUploadStatus for ${uploadId}`
|
|
@@ -28,11 +28,48 @@ export async function getPaymentContext(request, uuid) {
|
|
|
28
28
|
|
|
29
29
|
const apiKey = getPaymentApiKey(isLivePayment, formId)
|
|
30
30
|
const paymentService = new PaymentService(apiKey)
|
|
31
|
-
const paymentStatus = await paymentService.getPaymentStatus(
|
|
31
|
+
const paymentStatus = await paymentService.getPaymentStatus(
|
|
32
|
+
paymentId,
|
|
33
|
+
isLivePayment
|
|
34
|
+
)
|
|
32
35
|
|
|
33
36
|
return { session, sessionKey, paymentStatus }
|
|
34
37
|
}
|
|
35
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Builds an object for logging payment information
|
|
41
|
+
* @param {string} action
|
|
42
|
+
* @param {string} outcome
|
|
43
|
+
* @param {string} reason
|
|
44
|
+
* @param {boolean} isLivePayment
|
|
45
|
+
* @param {string} paymentId
|
|
46
|
+
*/
|
|
47
|
+
export function buildPaymentInfo(
|
|
48
|
+
action,
|
|
49
|
+
outcome,
|
|
50
|
+
reason,
|
|
51
|
+
isLivePayment,
|
|
52
|
+
paymentId
|
|
53
|
+
) {
|
|
54
|
+
return {
|
|
55
|
+
event: {
|
|
56
|
+
category: 'payment',
|
|
57
|
+
action,
|
|
58
|
+
outcome,
|
|
59
|
+
reason,
|
|
60
|
+
type: isLivePayment ? 'live' : 'test',
|
|
61
|
+
reference: paymentId
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* @param {number} amount
|
|
68
|
+
*/
|
|
69
|
+
export function convertPenceToPounds(amount) {
|
|
70
|
+
return `${amount / 100}`
|
|
71
|
+
}
|
|
72
|
+
|
|
36
73
|
/**
|
|
37
74
|
* @import { Request } from '@hapi/hapi'
|
|
38
75
|
* @import { GetPaymentResponse, PaymentSessionData } from '~/src/server/plugins/payment/types.js'
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
buildPaymentInfo,
|
|
3
|
+
getPaymentContext
|
|
4
|
+
} from '~/src/server/plugins/engine/routes/payment-helper.js'
|
|
2
5
|
import { get } from '~/src/server/services/httpService.js'
|
|
3
6
|
|
|
4
7
|
jest.mock('~/src/server/services/httpService.ts')
|
|
@@ -83,6 +86,46 @@ describe('payment helper', () => {
|
|
|
83
86
|
sessionKey: 'payment-5a54c2fe-da49-4202-8cd3-2121eaca03c3'
|
|
84
87
|
})
|
|
85
88
|
})
|
|
89
|
+
|
|
90
|
+
it('should create logging info for a test payment', () => {
|
|
91
|
+
const res = buildPaymentInfo(
|
|
92
|
+
'action1',
|
|
93
|
+
'outcome1',
|
|
94
|
+
'reason1',
|
|
95
|
+
false,
|
|
96
|
+
'pay-123'
|
|
97
|
+
)
|
|
98
|
+
expect(res).toEqual({
|
|
99
|
+
event: {
|
|
100
|
+
category: 'payment',
|
|
101
|
+
action: 'action1',
|
|
102
|
+
outcome: 'outcome1',
|
|
103
|
+
reason: 'reason1',
|
|
104
|
+
type: 'test',
|
|
105
|
+
reference: 'pay-123'
|
|
106
|
+
}
|
|
107
|
+
})
|
|
108
|
+
})
|
|
109
|
+
|
|
110
|
+
it('should create logging info for a live payment', () => {
|
|
111
|
+
const res = buildPaymentInfo(
|
|
112
|
+
'action2',
|
|
113
|
+
'outcome2',
|
|
114
|
+
'reason2',
|
|
115
|
+
true,
|
|
116
|
+
'pay-123'
|
|
117
|
+
)
|
|
118
|
+
expect(res).toEqual({
|
|
119
|
+
event: {
|
|
120
|
+
category: 'payment',
|
|
121
|
+
action: 'action2',
|
|
122
|
+
outcome: 'outcome2',
|
|
123
|
+
reason: 'reason2',
|
|
124
|
+
type: 'live',
|
|
125
|
+
reference: 'pay-123'
|
|
126
|
+
}
|
|
127
|
+
})
|
|
128
|
+
})
|
|
86
129
|
})
|
|
87
130
|
|
|
88
131
|
/**
|
|
@@ -2,12 +2,19 @@ import Boom from '@hapi/boom'
|
|
|
2
2
|
import { StatusCodes } from 'http-status-codes'
|
|
3
3
|
import Joi from 'joi'
|
|
4
4
|
|
|
5
|
+
import { createLogger } from '~/src/server/common/helpers/logging/logger.js'
|
|
5
6
|
import { EXTERNAL_STATE_APPENDAGE } from '~/src/server/constants.js'
|
|
6
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
buildPaymentInfo,
|
|
9
|
+
convertPenceToPounds,
|
|
10
|
+
getPaymentContext
|
|
11
|
+
} from '~/src/server/plugins/engine/routes/payment-helper.js'
|
|
7
12
|
|
|
8
13
|
export const PAYMENT_RETURN_PATH = '/payment-callback'
|
|
9
14
|
export const PAYMENT_SESSION_PREFIX = 'payment-'
|
|
10
15
|
|
|
16
|
+
const logger = createLogger()
|
|
17
|
+
|
|
11
18
|
/**
|
|
12
19
|
* Flash form component state after successful payment
|
|
13
20
|
* @param {Request} request - the request
|
|
@@ -48,6 +55,42 @@ export function getRoutes() {
|
|
|
48
55
|
return [getReturnRoute()]
|
|
49
56
|
}
|
|
50
57
|
|
|
58
|
+
/**
|
|
59
|
+
* Logs successful payment
|
|
60
|
+
* @param {PaymentSessionData} session - the session data
|
|
61
|
+
* @param {GetPaymentResponse} paymentStatus - the payment status from GOV.UK Pay
|
|
62
|
+
*/
|
|
63
|
+
function logPaymentSuccess(session, paymentStatus) {
|
|
64
|
+
logger.info(
|
|
65
|
+
buildPaymentInfo(
|
|
66
|
+
'pre-auth',
|
|
67
|
+
'success',
|
|
68
|
+
`${paymentStatus.state.status} amount=${convertPenceToPounds(paymentStatus.amount)}`,
|
|
69
|
+
session.isLivePayment,
|
|
70
|
+
paymentStatus.paymentId
|
|
71
|
+
),
|
|
72
|
+
`[payment] Successful pre-auth for paymentId=${paymentStatus.paymentId}`
|
|
73
|
+
)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Logs failed/cancelled payment
|
|
78
|
+
* @param {PaymentSessionData} session - the session data
|
|
79
|
+
* @param {GetPaymentResponse} paymentStatus - the payment status from GOV.UK Pay
|
|
80
|
+
*/
|
|
81
|
+
function logPaymentFailure(session, paymentStatus) {
|
|
82
|
+
logger.info(
|
|
83
|
+
buildPaymentInfo(
|
|
84
|
+
'pre-auth',
|
|
85
|
+
'failed/cancelled',
|
|
86
|
+
`${paymentStatus.state.status} amount=${convertPenceToPounds(paymentStatus.amount)}`,
|
|
87
|
+
session.isLivePayment,
|
|
88
|
+
paymentStatus.paymentId
|
|
89
|
+
),
|
|
90
|
+
`[payment] Failed/cancelled pre-auth for paymentId=${paymentStatus.paymentId}`
|
|
91
|
+
)
|
|
92
|
+
}
|
|
93
|
+
|
|
51
94
|
/**
|
|
52
95
|
* Handles successful payment states (capturable/success)
|
|
53
96
|
* @param {Request} request - the request
|
|
@@ -98,6 +141,7 @@ function getReturnRoute() {
|
|
|
98
141
|
switch (status) {
|
|
99
142
|
case 'capturable':
|
|
100
143
|
case 'success':
|
|
144
|
+
logPaymentSuccess(session, paymentStatus)
|
|
101
145
|
return handlePaymentSuccess(
|
|
102
146
|
request,
|
|
103
147
|
h,
|
|
@@ -109,6 +153,7 @@ function getReturnRoute() {
|
|
|
109
153
|
case 'cancelled':
|
|
110
154
|
case 'failed':
|
|
111
155
|
case 'error':
|
|
156
|
+
logPaymentFailure(session, paymentStatus)
|
|
112
157
|
return handlePaymentFailure(request, h, session, sessionKey)
|
|
113
158
|
|
|
114
159
|
case 'created':
|