@defra-fish/recurring-payments-job 1.62.0-rc.6 → 1.62.0-rc.8

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/README.md CHANGED
@@ -9,7 +9,7 @@ When the RP job runs, all RP entries with an nextDueDate of the current date, a
9
9
  # Environment variables
10
10
 
11
11
  | name | description | required | default | valid | notes |
12
- | ------------------------------ | --------------------------------------------------------------- | :------: | ------------------- | ----------------------------- | --------------------------------------------------------------------------------- |
12
+ | ------------------------------ | --------------------------------------------------------------- | :------: | ------------------- | ----------------------------- | --------------------------------------------------------------------------------- | --- |
13
13
  | NODE_ENV | Node environment | no | | development, test, production | |
14
14
  | RUN_RECURRING_PAYMENTS | Determine whether to run recurring payments job or not | yes | | | |
15
15
  | SALES_API_URL | URL for the sales API | no | http://0.0.0.0:4000 | | |
@@ -22,7 +22,7 @@ When the RP job runs, all RP entries with an nextDueDate of the current date, a
22
22
  | DYNAMICS_API_PATH | Full URL to the Dynamics API | yes | | | The full URL to the dynamics web api. e.g. https://dynamics-server/api/data/v9.1/ |
23
23
  | DYNAMICS_API_VERSION | The version of the Dynamics API | yes | | | The version of the dynamics web api. e.g. 9.1 |
24
24
  | 1 |
25
- | RECURRING_PAYMENTS_LOCAL_DELAY | Delay for running recurring payments until sales api is running | no | | |
25
+ | RECURRING_PAYMENTS_LOCAL_DELAY | Delay for running recurring payments until sales api is running | no | | | | |
26
26
 
27
27
  ### See also:
28
28
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra-fish/recurring-payments-job",
3
- "version": "1.62.0-rc.6",
3
+ "version": "1.62.0-rc.8",
4
4
  "description": "Rod Licensing Recurring Payments Job",
5
5
  "type": "module",
6
6
  "engines": {
@@ -36,11 +36,11 @@
36
36
  "test": "echo \"Error: run tests from root\" && exit 1"
37
37
  },
38
38
  "dependencies": {
39
- "@defra-fish/business-rules-lib": "1.62.0-rc.6",
40
- "@defra-fish/connectors-lib": "1.62.0-rc.6",
39
+ "@defra-fish/business-rules-lib": "1.62.0-rc.8",
40
+ "@defra-fish/connectors-lib": "1.62.0-rc.8",
41
41
  "commander": "^7.2.0",
42
42
  "debug": "^4.3.3",
43
43
  "moment-timezone": "^0.5.34"
44
44
  },
45
- "gitHead": "99bf15ee471b05eb6f99d1051ddf955c1cbed9bf"
45
+ "gitHead": "5d9e947d9ce87f6f43aa6ca7ac8d78f797ecebdf"
46
46
  }
@@ -1,9 +1,23 @@
1
1
  import { salesApi } from '@defra-fish/connectors-lib'
2
+ import { PAYMENT_JOURNAL_STATUS_CODES } from '@defra-fish/business-rules-lib'
2
3
  import { processRecurringPayments } from '../recurring-payments-processor.js'
3
4
  import { getPaymentStatus, sendPayment } from '../services/govuk-pay-service.js'
4
5
  import db from 'debug'
5
6
 
6
- jest.mock('@defra-fish/business-rules-lib')
7
+ jest.mock('@defra-fish/business-rules-lib', () => ({
8
+ PAYMENT_STATUS: {
9
+ Success: 'payment status success',
10
+ Failure: 'payment status failure',
11
+ Error: 'payment status error'
12
+ },
13
+ PAYMENT_JOURNAL_STATUS_CODES: {
14
+ InProgress: 'in progress payment',
15
+ Cancelled: 'cancelled payment',
16
+ Failed: 'failed payment',
17
+ Expired: 'expired payment',
18
+ Completed: 'completed payment'
19
+ }
20
+ }))
7
21
  jest.mock('@defra-fish/connectors-lib', () => ({
8
22
  salesApi: {
9
23
  getDueRecurringPayments: jest.fn(() => []),
@@ -14,7 +28,9 @@ jest.mock('@defra-fish/connectors-lib', () => ({
14
28
  id: 'test-transaction-id',
15
29
  cost: 30
16
30
  })),
17
- processRPResult: jest.fn()
31
+ processRPResult: jest.fn(),
32
+ updatePaymentJournal: jest.fn(),
33
+ getPaymentJournal: jest.fn()
18
34
  }
19
35
  }))
20
36
  jest.mock('../services/govuk-pay-service.js', () => ({
@@ -25,7 +41,9 @@ jest.mock('debug', () => jest.fn(() => jest.fn()))
25
41
 
26
42
  const debugMock = db.mock.results[0].value
27
43
  const PAYMENT_STATUS_DELAY = 60000
28
- const getPaymentStatusSuccess = () => ({ state: { status: 'success' } })
44
+ const getPaymentStatusSuccess = () => ({ state: { status: 'payment status success' } })
45
+ const getPaymentStatusFailure = () => ({ state: { status: 'payment status failure' } })
46
+ const getPaymentStatusError = () => ({ state: { status: 'payment status error' } })
29
47
  const getMockPaymentRequestResponse = () => [
30
48
  {
31
49
  entity: { agreementId: 'agreement-1' },
@@ -387,11 +405,69 @@ describe('recurring-payments-processor', () => {
387
405
  salesApi.getDueRecurringPayments.mockResolvedValueOnce(getMockPaymentRequestResponse())
388
406
  salesApi.createTransaction.mockResolvedValueOnce({ id: mockPaymentId, cost: 30 })
389
407
  sendPayment.mockResolvedValueOnce({ payment_id: mockPaymentId, agreementId: 'agreement-1' })
390
- getPaymentStatus.mockResolvedValueOnce({ state: { status: 'Pending' } })
408
+ getPaymentStatus.mockResolvedValueOnce(getPaymentStatusFailure())
409
+
410
+ await processRecurringPayments()
411
+
412
+ expect(salesApi.processRPResult).not.toHaveBeenCalled()
413
+ })
414
+
415
+ it.each([
416
+ ['agreement-id', getPaymentStatusFailure(), 'failure'],
417
+ ['test-agreement-id', getPaymentStatusFailure(), 'failure'],
418
+ ['another-agreement-id', getPaymentStatusFailure(), 'failure'],
419
+ ['agreement-id', getPaymentStatusError(), 'error'],
420
+ ['test-agreement-id', getPaymentStatusError(), 'error'],
421
+ ['another-agreement-id', getPaymentStatusError(), 'error']
422
+ ])(
423
+ 'console error displays "Payment failed. Recurring payment agreement for: %s set to be cancelled" when payment is a %status',
424
+ async (agreementId, mockStatus, status) => {
425
+ const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
426
+ salesApi.getDueRecurringPayments.mockReturnValueOnce([getMockDueRecurringPayment('reference', agreementId)])
427
+ const mockPaymentResponse = { payment_id: 'test-payment-id', created_date: '2025-01-01T00:00:00.000Z' }
428
+ sendPayment.mockResolvedValueOnce(mockPaymentResponse)
429
+ getPaymentStatus.mockResolvedValueOnce(mockStatus)
430
+
431
+ await processRecurringPayments()
432
+
433
+ expect(consoleErrorSpy).toHaveBeenCalledWith(
434
+ `Payment failed. Recurring payment agreement for: ${agreementId} set to be cancelled. Updating payment journal.`
435
+ )
436
+ }
437
+ )
438
+
439
+ it('updatePaymentJournal is called with transaction id and failed status code payment is not succesful and payment journal exists', async () => {
440
+ salesApi.getDueRecurringPayments.mockReturnValueOnce([getMockDueRecurringPayment()])
441
+ const transactionId = 'test-transaction-id'
442
+ salesApi.createTransaction.mockReturnValueOnce({
443
+ cost: 50,
444
+ id: transactionId
445
+ })
446
+ const mockPaymentResponse = { payment_id: 'test-payment-id', created_date: '2025-01-01T00:00:00.000Z' }
447
+ sendPayment.mockResolvedValueOnce(mockPaymentResponse)
448
+ getPaymentStatus.mockResolvedValueOnce(getPaymentStatusFailure())
449
+ salesApi.getPaymentJournal.mockResolvedValueOnce(true)
450
+
451
+ await processRecurringPayments()
452
+
453
+ expect(salesApi.updatePaymentJournal).toHaveBeenCalledWith(transactionId, { paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.Failed })
454
+ })
455
+
456
+ it('updatePaymentJournal is not called when failed status code payment is not succesful but payment journal does not exist', async () => {
457
+ salesApi.getDueRecurringPayments.mockReturnValueOnce([getMockDueRecurringPayment()])
458
+ const transactionId = 'test-transaction-id'
459
+ salesApi.createTransaction.mockReturnValueOnce({
460
+ cost: 50,
461
+ id: transactionId
462
+ })
463
+ const mockPaymentResponse = { payment_id: 'test-payment-id', created_date: '2025-01-01T00:00:00.000Z' }
464
+ sendPayment.mockResolvedValueOnce(mockPaymentResponse)
465
+ getPaymentStatus.mockResolvedValueOnce(getPaymentStatusFailure())
466
+ salesApi.getPaymentJournal.mockResolvedValueOnce(undefined)
391
467
 
392
468
  await processRecurringPayments()
393
469
 
394
- expect(salesApi.processRPResult).not.toHaveBeenCalledWith()
470
+ expect(salesApi.updatePaymentJournal).not.toHaveBeenCalled()
395
471
  })
396
472
 
397
473
  describe.each([2, 3, 10])('if there are %d recurring payments', count => {
@@ -400,7 +476,6 @@ describe('recurring-payments-processor', () => {
400
476
  for (let i = 0; i < count; i++) {
401
477
  references.push(Symbol('reference' + i))
402
478
  }
403
-
404
479
  const mockGetDueRecurringPayments = []
405
480
  references.forEach(reference => {
406
481
  mockGetDueRecurringPayments.push(getMockDueRecurringPayment(reference))
@@ -1,5 +1,5 @@
1
1
  import moment from 'moment-timezone'
2
- import { SERVICE_LOCAL_TIME } from '@defra-fish/business-rules-lib'
2
+ import { PAYMENT_STATUS, SERVICE_LOCAL_TIME, PAYMENT_JOURNAL_STATUS_CODES } from '@defra-fish/business-rules-lib'
3
3
  import { salesApi } from '@defra-fish/connectors-lib'
4
4
  import { getPaymentStatus, sendPayment } from './services/govuk-pay-service.js'
5
5
  import db from 'debug'
@@ -7,7 +7,6 @@ const debug = db('recurring-payments:processor')
7
7
 
8
8
  const PAYMENT_STATUS_DELAY = 60000
9
9
  const payments = []
10
- const PAYMENT_STATUS_SUCCESS = 'success'
11
10
 
12
11
  export const processRecurringPayments = async () => {
13
12
  if (process.env.RUN_RECURRING_PAYMENTS?.toLowerCase() === 'true') {
@@ -107,11 +106,19 @@ const processRecurringPaymentStatus = async record => {
107
106
  state: { status }
108
107
  } = await getPaymentStatus(paymentId)
109
108
  debug(`Payment status for ${paymentId}: ${status}`)
110
- if (status === PAYMENT_STATUS_SUCCESS) {
111
- const payment = payments.find(p => p.paymentId === paymentId)
109
+ const payment = payments.find(p => p.paymentId === paymentId)
110
+ if (status === PAYMENT_STATUS.Success) {
112
111
  await salesApi.processRPResult(payment.transaction.id, paymentId, payment.created_date)
113
112
  debug(`Processed Recurring Payment for ${payment.transaction.id}`)
114
113
  }
114
+ if (status === PAYMENT_STATUS.Failure || status === PAYMENT_STATUS.Error) {
115
+ console.error(`Payment failed. Recurring payment agreement for: ${agreementId} set to be cancelled. Updating payment journal.`)
116
+ if (await salesApi.getPaymentJournal(payment.transaction.id)) {
117
+ await salesApi.updatePaymentJournal(payment.transaction.id, {
118
+ paymentStatus: PAYMENT_JOURNAL_STATUS_CODES.Failed
119
+ })
120
+ }
121
+ }
115
122
  }
116
123
 
117
124
  const getPaymentId = agreementId => {