@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.
Files changed (25) hide show
  1. package/.server/server/plugins/engine/components/PaymentField.js +2 -2
  2. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  3. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js +14 -1
  4. package/.server/server/plugins/engine/pageControllers/FileUploadPageController.js.map +1 -1
  5. package/.server/server/plugins/engine/routes/payment-helper.d.ts +22 -0
  6. package/.server/server/plugins/engine/routes/payment-helper.js +29 -1
  7. package/.server/server/plugins/engine/routes/payment-helper.js.map +1 -1
  8. package/.server/server/plugins/engine/routes/payment-helper.test.js +27 -1
  9. package/.server/server/plugins/engine/routes/payment-helper.test.js.map +1 -1
  10. package/.server/server/plugins/engine/routes/payment.js +23 -1
  11. package/.server/server/plugins/engine/routes/payment.js.map +1 -1
  12. package/.server/server/plugins/payment/service.d.ts +4 -2
  13. package/.server/server/plugins/payment/service.js +8 -21
  14. package/.server/server/plugins/payment/service.js.map +1 -1
  15. package/.server/server/plugins/payment/service.test.js +5 -5
  16. package/.server/server/plugins/payment/service.test.js.map +1 -1
  17. package/package.json +1 -1
  18. package/src/server/plugins/engine/components/PaymentField.ts +5 -1
  19. package/src/server/plugins/engine/pageControllers/FileUploadPageController.test.ts +84 -0
  20. package/src/server/plugins/engine/pageControllers/FileUploadPageController.ts +20 -1
  21. package/src/server/plugins/engine/routes/payment-helper.js +38 -1
  22. package/src/server/plugins/engine/routes/payment-helper.test.js +44 -1
  23. package/src/server/plugins/engine/routes/payment.js +46 -1
  24. package/src/server/plugins/payment/service.js +32 -24
  25. 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra/forms-engine-plugin",
3
- "version": "4.0.52",
3
+ "version": "4.0.54",
4
4
  "description": "Defra forms engine",
5
5
  "type": "module",
6
6
  "files": [
@@ -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(paymentId)
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
- const statusResponse = await getUploadStatus(uploadId)
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(paymentId)
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 { getPaymentContext } from '~/src/server/plugins/engine/routes/payment-helper.js'
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 { getPaymentContext } from '~/src/server/plugins/engine/routes/payment-helper.js'
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':