@defra-fish/recurring-payments-job 1.62.0-rc.6 → 1.62.0-rc.7
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.
|
|
3
|
+
"version": "1.62.0-rc.7",
|
|
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.
|
|
40
|
-
"@defra-fish/connectors-lib": "1.62.0-rc.
|
|
39
|
+
"@defra-fish/business-rules-lib": "1.62.0-rc.7",
|
|
40
|
+
"@defra-fish/connectors-lib": "1.62.0-rc.7",
|
|
41
41
|
"commander": "^7.2.0",
|
|
42
42
|
"debug": "^4.3.3",
|
|
43
43
|
"moment-timezone": "^0.5.34"
|
|
44
44
|
},
|
|
45
|
-
"gitHead": "
|
|
45
|
+
"gitHead": "769c73ef2423de6a672ce76dd831fe5927393373"
|
|
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(
|
|
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.
|
|
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
|
-
|
|
111
|
-
|
|
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 => {
|