@defra-fish/gafl-webapp-service 1.55.0 → 1.56.0
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/package.json +4 -4
- package/src/handlers/__tests__/agreed-handler-recurring-payments.spec.js +94 -40
- package/src/handlers/agreed-handler.js +13 -5
- package/src/locales/cy.json +3 -3
- package/src/locales/en.json +3 -3
- package/src/pages/summary/licence-summary/__tests__/__snapshots__/route.spec.js.snap +121 -0
- package/src/pages/summary/licence-summary/__tests__/route.spec.js +12 -11
- package/src/pages/summary/licence-summary/route.js +7 -6
- package/src/processors/__tests__/api-transaction.spec.js +9 -1
- package/src/processors/__tests__/payment.spec.js +33 -8
- package/src/processors/api-transaction.js +3 -2
- package/src/processors/payment.js +7 -3
- package/src/services/payment/__test__/govuk-pay-service.spec.js +253 -1
- package/src/services/payment/govuk-pay-service.js +4 -4
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@defra-fish/gafl-webapp-service",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.56.0",
|
|
4
4
|
"description": "The websales frontend for the GAFL service",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"engines": {
|
|
@@ -36,8 +36,8 @@
|
|
|
36
36
|
"prepare": "gulp --gulpfile build/gulpfile.cjs"
|
|
37
37
|
},
|
|
38
38
|
"dependencies": {
|
|
39
|
-
"@defra-fish/business-rules-lib": "1.
|
|
40
|
-
"@defra-fish/connectors-lib": "1.
|
|
39
|
+
"@defra-fish/business-rules-lib": "1.56.0",
|
|
40
|
+
"@defra-fish/connectors-lib": "1.56.0",
|
|
41
41
|
"@defra/hapi-gapi": "^2.0.0",
|
|
42
42
|
"@hapi/boom": "^9.1.2",
|
|
43
43
|
"@hapi/catbox-redis": "^6.0.2",
|
|
@@ -80,5 +80,5 @@
|
|
|
80
80
|
"./gafl-jest-matchers.js"
|
|
81
81
|
]
|
|
82
82
|
},
|
|
83
|
-
"gitHead": "
|
|
83
|
+
"gitHead": "4edcdd349b077ad0bdb3e7b4029df3aba7c04b49"
|
|
84
84
|
}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { salesApi } from '@defra-fish/connectors-lib'
|
|
2
2
|
import { COMPLETION_STATUS, RECURRING_PAYMENT } from '../../constants.js'
|
|
3
3
|
import agreedHandler from '../agreed-handler.js'
|
|
4
|
-
import {
|
|
5
|
-
import { sendRecurringPayment } from '../../services/payment/govuk-pay-service.js'
|
|
4
|
+
import { preparePayment, prepareRecurringPaymentAgreement } from '../../processors/payment.js'
|
|
5
|
+
import { sendPayment, sendRecurringPayment, getPaymentStatus } from '../../services/payment/govuk-pay-service.js'
|
|
6
6
|
import { prepareApiTransactionPayload } from '../../processors/api-transaction.js'
|
|
7
7
|
import { v4 as uuidv4 } from 'uuid'
|
|
8
8
|
import db from 'debug'
|
|
@@ -10,7 +10,13 @@ import db from 'debug'
|
|
|
10
10
|
jest.mock('@defra-fish/connectors-lib')
|
|
11
11
|
jest.mock('../../processors/payment.js')
|
|
12
12
|
jest.mock('../../services/payment/govuk-pay-service.js', () => ({
|
|
13
|
-
sendPayment: jest.fn()
|
|
13
|
+
sendPayment: jest.fn(() => ({
|
|
14
|
+
payment_id: 'payment-id-1',
|
|
15
|
+
_links: {
|
|
16
|
+
next_url: { href: 'next-url' },
|
|
17
|
+
self: { href: 'self-url' }
|
|
18
|
+
}
|
|
19
|
+
})),
|
|
14
20
|
getPaymentStatus: jest.fn(),
|
|
15
21
|
sendRecurringPayment: jest.fn(() => ({ agreementId: 'agr-eem-ent-id1' }))
|
|
16
22
|
}))
|
|
@@ -32,12 +38,12 @@ describe('The agreed handler', () => {
|
|
|
32
38
|
})
|
|
33
39
|
beforeEach(jest.clearAllMocks)
|
|
34
40
|
|
|
35
|
-
const getMockRequest = (overrides = {}) => ({
|
|
41
|
+
const getMockRequest = ({ overrides = {}, transactionSet = () => {} } = {}) => ({
|
|
36
42
|
cache: () => ({
|
|
37
43
|
helpers: {
|
|
38
44
|
transaction: {
|
|
39
45
|
get: async () => ({ cost: 0 }),
|
|
40
|
-
set:
|
|
46
|
+
set: transactionSet
|
|
41
47
|
},
|
|
42
48
|
status: {
|
|
43
49
|
get: async () => ({
|
|
@@ -54,6 +60,7 @@ describe('The agreed handler', () => {
|
|
|
54
60
|
})
|
|
55
61
|
|
|
56
62
|
const getRequestToolkit = () => ({
|
|
63
|
+
redirect: jest.fn(),
|
|
57
64
|
redirectWithLanguageCode: jest.fn()
|
|
58
65
|
})
|
|
59
66
|
|
|
@@ -61,18 +68,20 @@ describe('The agreed handler', () => {
|
|
|
61
68
|
it('sends the request and transaction to prepare the recurring payment', async () => {
|
|
62
69
|
const transaction = { cost: 0 }
|
|
63
70
|
const mockRequest = getMockRequest({
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
71
|
+
overrides: {
|
|
72
|
+
transaction: {
|
|
73
|
+
get: async () => transaction,
|
|
74
|
+
set: () => {}
|
|
75
|
+
}
|
|
67
76
|
}
|
|
68
77
|
})
|
|
69
78
|
await agreedHandler(mockRequest, getRequestToolkit())
|
|
70
|
-
expect(
|
|
79
|
+
expect(prepareRecurringPaymentAgreement).toHaveBeenCalledWith(mockRequest, transaction)
|
|
71
80
|
})
|
|
72
81
|
|
|
73
82
|
it('adds a v4 guid to the transaction as an id', async () => {
|
|
74
83
|
let transactionPayload = null
|
|
75
|
-
|
|
84
|
+
prepareRecurringPaymentAgreement.mockImplementationOnce((_p1, tp) => {
|
|
76
85
|
transactionPayload = { ...tp }
|
|
77
86
|
})
|
|
78
87
|
const v4guid = Symbol('v4guid')
|
|
@@ -90,46 +99,73 @@ describe('The agreed handler', () => {
|
|
|
90
99
|
expect(transactionPayload.id).toBe(v4guid)
|
|
91
100
|
})
|
|
92
101
|
|
|
93
|
-
it(
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
102
|
+
it('sends a recurring payment agreement creation request to Gov.UK Pay', async () => {
|
|
103
|
+
const preparedPayment = Symbol('preparedPayment')
|
|
104
|
+
prepareRecurringPaymentAgreement.mockResolvedValueOnce(preparedPayment)
|
|
105
|
+
await agreedHandler(getMockRequest(), getRequestToolkit())
|
|
106
|
+
expect(sendRecurringPayment).toHaveBeenCalledWith(preparedPayment)
|
|
107
|
+
})
|
|
108
|
+
|
|
109
|
+
describe('when there is a cost and recurringAgreement status is set to true', () => {
|
|
110
|
+
beforeEach(() => {
|
|
111
|
+
salesApi.createTransaction.mockResolvedValueOnce({
|
|
112
|
+
id: 'transaction-id-1',
|
|
113
|
+
cost: 100
|
|
114
|
+
})
|
|
115
|
+
})
|
|
116
|
+
|
|
117
|
+
it('calls preparePayment', async () => {
|
|
118
|
+
const transaction = { id: Symbol('transaction') }
|
|
119
|
+
const request = getMockRequest({
|
|
120
|
+
overrides: {
|
|
121
|
+
transaction: {
|
|
122
|
+
get: async () => transaction,
|
|
123
|
+
set: () => {}
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
})
|
|
127
|
+
const toolkit = getRequestToolkit()
|
|
128
|
+
|
|
129
|
+
await agreedHandler(request, toolkit)
|
|
130
|
+
expect(preparePayment).toHaveBeenCalledWith(request, transaction)
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
it('calls sendPayment with recurring as true', async () => {
|
|
134
|
+
const preparedPayment = Symbol('preparedPayment')
|
|
135
|
+
preparePayment.mockReturnValueOnce(preparedPayment)
|
|
136
|
+
|
|
137
|
+
await agreedHandler(getMockRequest(), getRequestToolkit())
|
|
138
|
+
expect(sendPayment).toHaveBeenCalledWith(preparedPayment, true)
|
|
99
139
|
})
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
140
|
+
|
|
141
|
+
it('calls getPaymentStatus with recurring as true', async () => {
|
|
142
|
+
const id = Symbol('paymentId')
|
|
143
|
+
const transaction = { id: '123', payment: { payment_id: id } }
|
|
144
|
+
const request = getMockRequest({
|
|
145
|
+
overrides: {
|
|
146
|
+
transaction: {
|
|
147
|
+
get: async () => transaction,
|
|
148
|
+
set: () => {}
|
|
149
|
+
},
|
|
103
150
|
status: {
|
|
104
151
|
get: async () => ({
|
|
105
152
|
[COMPLETION_STATUS.agreed]: true,
|
|
106
|
-
[COMPLETION_STATUS.posted]:
|
|
107
|
-
[COMPLETION_STATUS.finalised]:
|
|
108
|
-
[RECURRING_PAYMENT]:
|
|
153
|
+
[COMPLETION_STATUS.posted]: false,
|
|
154
|
+
[COMPLETION_STATUS.finalised]: true,
|
|
155
|
+
[RECURRING_PAYMENT]: true,
|
|
156
|
+
[COMPLETION_STATUS.paymentCreated]: true
|
|
109
157
|
}),
|
|
110
158
|
set: () => {}
|
|
111
|
-
},
|
|
112
|
-
transaction: {
|
|
113
|
-
get: async () => ({ cost: 0, id: transactionId }),
|
|
114
|
-
set: setTransaction
|
|
115
159
|
}
|
|
116
160
|
}
|
|
117
161
|
})
|
|
118
|
-
|
|
162
|
+
const toolkit = getRequestToolkit()
|
|
119
163
|
|
|
120
|
-
|
|
164
|
+
getPaymentStatus.mockReturnValueOnce({ state: { finished: true, status: 'success' } })
|
|
121
165
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
)
|
|
126
|
-
})
|
|
127
|
-
|
|
128
|
-
it('sends a recurring payment creation request to Gov.UK Pay', async () => {
|
|
129
|
-
const preparedPayment = Symbol('preparedPayment')
|
|
130
|
-
prepareRecurringPayment.mockResolvedValueOnce(preparedPayment)
|
|
131
|
-
await agreedHandler(getMockRequest(), getRequestToolkit())
|
|
132
|
-
expect(sendRecurringPayment).toHaveBeenCalledWith(preparedPayment)
|
|
166
|
+
await agreedHandler(request, toolkit)
|
|
167
|
+
expect(getPaymentStatus).toHaveBeenCalledWith(id, true)
|
|
168
|
+
})
|
|
133
169
|
})
|
|
134
170
|
|
|
135
171
|
// this doesn't really belong here, but until the other agreed handler tests are refactored to
|
|
@@ -140,7 +176,7 @@ describe('The agreed handler', () => {
|
|
|
140
176
|
|
|
141
177
|
await agreedHandler(getMockRequest(), getRequestToolkit())
|
|
142
178
|
|
|
143
|
-
expect(prepareApiTransactionPayload).toHaveBeenCalledWith(expect.any(Object), v4guid)
|
|
179
|
+
expect(prepareApiTransactionPayload).toHaveBeenCalledWith(expect.any(Object), v4guid, undefined)
|
|
144
180
|
})
|
|
145
181
|
|
|
146
182
|
it.each(['zxy-098-wvu-765', '467482f1-099d-403d-b6b3-8db7e70d19e3'])(
|
|
@@ -158,5 +194,23 @@ describe('The agreed handler', () => {
|
|
|
158
194
|
expect(debugMock).toHaveBeenCalledWith(`Created agreement with id ${agreement_id}`)
|
|
159
195
|
}
|
|
160
196
|
)
|
|
197
|
+
|
|
198
|
+
it.each(['zxy-098-wvu-765', '467482f1-099d-403d-b6b3-8db7e70d19e3'])(
|
|
199
|
+
"assigns agreement id '%s' to the transaction when recurring payment agreement created",
|
|
200
|
+
async agreementId => {
|
|
201
|
+
const mockTransactionCacheSet = jest.fn()
|
|
202
|
+
sendRecurringPayment.mockResolvedValueOnce({
|
|
203
|
+
agreement_id: agreementId
|
|
204
|
+
})
|
|
205
|
+
|
|
206
|
+
await agreedHandler(getMockRequest({ transactionSet: mockTransactionCacheSet }), getRequestToolkit())
|
|
207
|
+
|
|
208
|
+
expect(mockTransactionCacheSet).toHaveBeenCalledWith(
|
|
209
|
+
expect.objectContaining({
|
|
210
|
+
agreementId
|
|
211
|
+
})
|
|
212
|
+
)
|
|
213
|
+
}
|
|
214
|
+
)
|
|
161
215
|
})
|
|
162
216
|
})
|
|
@@ -14,7 +14,7 @@ import db from 'debug'
|
|
|
14
14
|
import { salesApi } from '@defra-fish/connectors-lib'
|
|
15
15
|
import { prepareApiTransactionPayload, prepareApiFinalisationPayload } from '../processors/api-transaction.js'
|
|
16
16
|
import { sendPayment, getPaymentStatus, sendRecurringPayment } from '../services/payment/govuk-pay-service.js'
|
|
17
|
-
import { preparePayment,
|
|
17
|
+
import { preparePayment, prepareRecurringPaymentAgreement } from '../processors/payment.js'
|
|
18
18
|
import { COMPLETION_STATUS, RECURRING_PAYMENT } from '../constants.js'
|
|
19
19
|
import { ORDER_COMPLETE, PAYMENT_CANCELLED, PAYMENT_FAILED } from '../uri.js'
|
|
20
20
|
import { PAYMENT_JOURNAL_STATUS_CODES, GOVUK_PAY_ERROR_STATUS_CODES } from '@defra-fish/business-rules-lib'
|
|
@@ -29,7 +29,7 @@ const debug = db('webapp:agreed-handler')
|
|
|
29
29
|
* @returns {Promise<*>}
|
|
30
30
|
*/
|
|
31
31
|
const sendToSalesApi = async (request, transaction, status) => {
|
|
32
|
-
const apiTransactionPayload = await prepareApiTransactionPayload(request, transaction.id)
|
|
32
|
+
const apiTransactionPayload = await prepareApiTransactionPayload(request, transaction.id, transaction.agreementId)
|
|
33
33
|
let response
|
|
34
34
|
try {
|
|
35
35
|
response = await salesApi.createTransaction(apiTransactionPayload)
|
|
@@ -63,7 +63,7 @@ const createRecurringPayment = async (request, transaction, status) => {
|
|
|
63
63
|
/*
|
|
64
64
|
* Prepare the payment payload
|
|
65
65
|
*/
|
|
66
|
-
const preparedPayment = await
|
|
66
|
+
const preparedPayment = await prepareRecurringPaymentAgreement(request, transaction)
|
|
67
67
|
|
|
68
68
|
/*
|
|
69
69
|
* Send the prepared payment to the GOV.UK pay API using the connector
|
|
@@ -72,7 +72,11 @@ const createRecurringPayment = async (request, transaction, status) => {
|
|
|
72
72
|
|
|
73
73
|
debug(`Created agreement with id ${paymentResponse.agreement_id}`)
|
|
74
74
|
status[COMPLETION_STATUS.recurringAgreement] = true
|
|
75
|
+
|
|
76
|
+
transaction.agreementId = paymentResponse.agreement_id
|
|
77
|
+
|
|
75
78
|
await request.cache().helpers.status.set(status)
|
|
79
|
+
await request.cache().helpers.transaction.set(transaction)
|
|
76
80
|
}
|
|
77
81
|
|
|
78
82
|
/**
|
|
@@ -88,6 +92,8 @@ const createRecurringPayment = async (request, transaction, status) => {
|
|
|
88
92
|
* @returns {Promise<void>}
|
|
89
93
|
*/
|
|
90
94
|
const createPayment = async (request, transaction, status) => {
|
|
95
|
+
const recurring = status && status[COMPLETION_STATUS.recurringAgreement] === true
|
|
96
|
+
|
|
91
97
|
/*
|
|
92
98
|
* Prepare the payment payload
|
|
93
99
|
*/
|
|
@@ -96,7 +102,7 @@ const createPayment = async (request, transaction, status) => {
|
|
|
96
102
|
/*
|
|
97
103
|
* Send the prepared payment to the GOV.UK pay API using the connector
|
|
98
104
|
*/
|
|
99
|
-
const paymentResponse = await sendPayment(preparedPayment)
|
|
105
|
+
const paymentResponse = await sendPayment(preparedPayment, recurring)
|
|
100
106
|
|
|
101
107
|
/*
|
|
102
108
|
* Used by the payment mop up job, create the payment journal entry which is removed when the user completes the journey
|
|
@@ -143,10 +149,12 @@ const createPayment = async (request, transaction, status) => {
|
|
|
143
149
|
* @returns {Promise<void>}
|
|
144
150
|
*/
|
|
145
151
|
const processPayment = async (request, transaction, status) => {
|
|
152
|
+
const recurring = status && status[COMPLETION_STATUS.recurringAgreement] === true
|
|
153
|
+
|
|
146
154
|
/*
|
|
147
155
|
* Get the payment status
|
|
148
156
|
*/
|
|
149
|
-
const { state } = await getPaymentStatus(transaction.payment.payment_id)
|
|
157
|
+
const { state } = await getPaymentStatus(transaction.payment.payment_id, recurring)
|
|
150
158
|
|
|
151
159
|
if (!state.finished) {
|
|
152
160
|
throw Boom.forbidden('Attempt to access the agreed handler during payment journey')
|
package/src/locales/cy.json
CHANGED
|
@@ -466,11 +466,11 @@
|
|
|
466
466
|
"licence_type_payment_edge_case": "Mae’n rhaid i chi gwblhau eich taliad cyn 11:30pm ar 31 Mawrth 2024 i dalu’r pris a ddangosir",
|
|
467
467
|
"licence_type_radio_salmon_hint": "Mae'n cynnwys brithyllod a physgod bras (hyd at 3 gwialen)",
|
|
468
468
|
"licence_type_radio_salmon": "Eogiaid a brithyllod y môr",
|
|
469
|
-
"licence_type_radio_salmon_payment_summary": "
|
|
469
|
+
"licence_type_radio_salmon_payment_summary": "Eogiaid a brithyllod y môr",
|
|
470
470
|
"licence_type_radio_trout_three_rod": "Brithyllod a physgod bras (hyd at 3 gwialen)",
|
|
471
|
-
"licence_type_radio_trout_three_rod_payment_summary": "
|
|
471
|
+
"licence_type_radio_trout_three_rod_payment_summary": "Brithyllod a physgod bras (hyd at 3 gwialen)",
|
|
472
472
|
"licence_type_radio_trout_two_rod": "Brithyllod a physgod bras (hyd at 2 wialen)",
|
|
473
|
-
"licence_type_radio_trout_two_rod_payment_summary": "
|
|
473
|
+
"licence_type_radio_trout_two_rod_payment_summary": "Brithyllod a physgod bras (hyd at 2 wialen)",
|
|
474
474
|
"licence_type_rules": "rheolau pysgota â gwialen (yn agor ar dudalen newydd)",
|
|
475
475
|
"licence_type_salmon_acr_note_1": "Yn ôl y gyfraith, mae'n rhaid i chi roi gwybod am ",
|
|
476
476
|
"licence_type_salmon_acr_note_2": " ffurflen daliadau (yn agor ar dudalen newydd)",
|
package/src/locales/en.json
CHANGED
|
@@ -466,11 +466,11 @@
|
|
|
466
466
|
"licence_type_payment_edge_case": "You must complete payment before 11:30pm on 31 March 2024 to get the price shown",
|
|
467
467
|
"licence_type_radio_salmon_hint": "Includes trout and coarse (up to 3 rods)",
|
|
468
468
|
"licence_type_radio_salmon": "Salmon and sea trout",
|
|
469
|
-
"licence_type_radio_salmon_payment_summary": "
|
|
469
|
+
"licence_type_radio_salmon_payment_summary": "Salmon and sea trout",
|
|
470
470
|
"licence_type_radio_trout_three_rod": "Trout and coarse (up to 3 rods)",
|
|
471
|
-
"licence_type_radio_trout_three_rod_payment_summary": "
|
|
471
|
+
"licence_type_radio_trout_three_rod_payment_summary": "Trout and coarse (up to 3 rods)",
|
|
472
472
|
"licence_type_radio_trout_two_rod": "Trout and coarse (up to 2 rods)",
|
|
473
|
-
"licence_type_radio_trout_two_rod_payment_summary": "
|
|
473
|
+
"licence_type_radio_trout_two_rod_payment_summary": "Trout and coarse (up to 2 rods)",
|
|
474
474
|
"licence_type_rules": "rod fishing rules (opens in new tab)",
|
|
475
475
|
"licence_type_salmon_acr_note_1": "Licence holders must by law ",
|
|
476
476
|
"licence_type_salmon_acr_note_2": " report a catch return (opens in new tab)",
|
|
@@ -246,6 +246,127 @@ Array [
|
|
|
246
246
|
]
|
|
247
247
|
`;
|
|
248
248
|
|
|
249
|
+
exports[`licence-summary > route licence summary rows creates licence summary name rows for 1 year new three rod licence 1`] = `
|
|
250
|
+
Array [
|
|
251
|
+
Object {
|
|
252
|
+
"actions": Object {
|
|
253
|
+
"items": Array [
|
|
254
|
+
Object {
|
|
255
|
+
"attributes": Object {
|
|
256
|
+
"id": "change-name",
|
|
257
|
+
},
|
|
258
|
+
"href": "/buy/name",
|
|
259
|
+
"text": "contact_summary_change",
|
|
260
|
+
"visuallyHiddenText": "licence_summary_name",
|
|
261
|
+
},
|
|
262
|
+
],
|
|
263
|
+
},
|
|
264
|
+
"key": Object {
|
|
265
|
+
"text": "licence_summary_name",
|
|
266
|
+
},
|
|
267
|
+
"value": Object {
|
|
268
|
+
"html": "Brenin Pysgotwr",
|
|
269
|
+
},
|
|
270
|
+
},
|
|
271
|
+
Object {
|
|
272
|
+
"actions": Object {
|
|
273
|
+
"items": Array [
|
|
274
|
+
Object {
|
|
275
|
+
"attributes": Object {
|
|
276
|
+
"id": "change-birth-date",
|
|
277
|
+
},
|
|
278
|
+
"href": "/buy/date-of-birth",
|
|
279
|
+
"text": "contact_summary_change",
|
|
280
|
+
"visuallyHiddenText": "licence_summary_dob",
|
|
281
|
+
},
|
|
282
|
+
],
|
|
283
|
+
},
|
|
284
|
+
"key": Object {
|
|
285
|
+
"text": "licence_summary_dob",
|
|
286
|
+
},
|
|
287
|
+
"value": Object {
|
|
288
|
+
"html": "1st January 1946",
|
|
289
|
+
},
|
|
290
|
+
},
|
|
291
|
+
Object {
|
|
292
|
+
"actions": Object {
|
|
293
|
+
"items": Array [
|
|
294
|
+
Object {
|
|
295
|
+
"attributes": Object {
|
|
296
|
+
"id": "change-licence-type",
|
|
297
|
+
},
|
|
298
|
+
"href": "/buy/licence-type",
|
|
299
|
+
"text": "contact_summary_change",
|
|
300
|
+
"visuallyHiddenText": "licence_summary_type",
|
|
301
|
+
},
|
|
302
|
+
],
|
|
303
|
+
},
|
|
304
|
+
"key": Object {
|
|
305
|
+
"text": "licence_summary_type",
|
|
306
|
+
},
|
|
307
|
+
"value": Object {
|
|
308
|
+
"html": "Special Canal Licence, Shopping Trollies and Old Wellies",
|
|
309
|
+
},
|
|
310
|
+
},
|
|
311
|
+
Object {
|
|
312
|
+
"key": Object {
|
|
313
|
+
"text": "licence_summary_length",
|
|
314
|
+
},
|
|
315
|
+
"value": Object {
|
|
316
|
+
"html": "licence_type_12m",
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
Object {
|
|
320
|
+
"actions": Object {
|
|
321
|
+
"items": Array [
|
|
322
|
+
Object {
|
|
323
|
+
"attributes": Object {
|
|
324
|
+
"id": "change-licence-to-start",
|
|
325
|
+
},
|
|
326
|
+
"href": "/buy/start-kind",
|
|
327
|
+
"text": "contact_summary_change",
|
|
328
|
+
"visuallyHiddenText": "licence_summary_start_date",
|
|
329
|
+
},
|
|
330
|
+
],
|
|
331
|
+
},
|
|
332
|
+
"key": Object {
|
|
333
|
+
"text": "licence_summary_start_date",
|
|
334
|
+
},
|
|
335
|
+
"value": Object {
|
|
336
|
+
"html": "30licence_summary_minutes_after_payment",
|
|
337
|
+
},
|
|
338
|
+
},
|
|
339
|
+
Object {
|
|
340
|
+
"actions": Object {
|
|
341
|
+
"items": Array [
|
|
342
|
+
Object {
|
|
343
|
+
"attributes": Object {
|
|
344
|
+
"id": "change-benefit-check",
|
|
345
|
+
},
|
|
346
|
+
"href": "/buy/disability-concession",
|
|
347
|
+
"text": "contact_summary_change",
|
|
348
|
+
"visuallyHiddenText": "licence_summary_ni_num",
|
|
349
|
+
},
|
|
350
|
+
],
|
|
351
|
+
},
|
|
352
|
+
"key": Object {
|
|
353
|
+
"text": "licence_summary_ni_num",
|
|
354
|
+
},
|
|
355
|
+
"value": Object {
|
|
356
|
+
"html": "AB 12 34 56 A",
|
|
357
|
+
},
|
|
358
|
+
},
|
|
359
|
+
Object {
|
|
360
|
+
"key": Object {
|
|
361
|
+
"text": "damage",
|
|
362
|
+
},
|
|
363
|
+
"value": Object {
|
|
364
|
+
"html": "#6",
|
|
365
|
+
},
|
|
366
|
+
},
|
|
367
|
+
]
|
|
368
|
+
`;
|
|
369
|
+
|
|
249
370
|
exports[`licence-summary > route licence summary rows creates licence summary name rows for 1 year renewal 1`] = `
|
|
250
371
|
Array [
|
|
251
372
|
Object {
|
|
@@ -116,7 +116,7 @@ const getMockPermission = (licenseeOverrides = {}) => ({
|
|
|
116
116
|
licenceToStart: 'after-payment',
|
|
117
117
|
licenceStartDate: '2022-11-10',
|
|
118
118
|
licenceType: 'Trout and coarse',
|
|
119
|
-
numberOfRods: '
|
|
119
|
+
numberOfRods: '2',
|
|
120
120
|
permit: { cost: 6 }
|
|
121
121
|
})
|
|
122
122
|
|
|
@@ -372,16 +372,17 @@ describe('licence-summary > route', () => {
|
|
|
372
372
|
|
|
373
373
|
describe('licence summary rows', () => {
|
|
374
374
|
it.each`
|
|
375
|
-
desc
|
|
376
|
-
${'1 year renewal'}
|
|
377
|
-
${'1 year new licence'}
|
|
378
|
-
${'1 year senior renewal'}
|
|
379
|
-
${'8 day licence'}
|
|
380
|
-
${'1 day licence'}
|
|
381
|
-
${'Junior licence'}
|
|
382
|
-
${'Blue badge concession'}
|
|
383
|
-
${'Continuing permission'}
|
|
384
|
-
${'Another date permission'}
|
|
375
|
+
desc | currentPermission
|
|
376
|
+
${'1 year renewal'} | ${getMockPermission()}
|
|
377
|
+
${'1 year new licence'} | ${getMockNewPermission()}
|
|
378
|
+
${'1 year senior renewal'} | ${getMockSeniorPermission()}
|
|
379
|
+
${'8 day licence'} | ${{ ...getMockNewPermission(), licenceLength: '8D' }}
|
|
380
|
+
${'1 day licence'} | ${{ ...getMockNewPermission(), licenceLength: '1D' }}
|
|
381
|
+
${'Junior licence'} | ${getMockJuniorPermission()}
|
|
382
|
+
${'Blue badge concession'} | ${getMockBlueBadgePermission()}
|
|
383
|
+
${'Continuing permission'} | ${getMockContinuingPermission()}
|
|
384
|
+
${'Another date permission'} | ${{ ...getMockPermission(), licenceToStart: 'another-date' }}
|
|
385
|
+
${'1 year new three rod licence '} | ${{ ...getMockNewPermission(), numberOfRods: '3' }}
|
|
385
386
|
`('creates licence summary name rows for $desc', async ({ currentPermission }) => {
|
|
386
387
|
const mockRequest = getMockRequest({ currentPermission })
|
|
387
388
|
const data = await getData(mockRequest)
|
|
@@ -113,12 +113,13 @@ class RowGenerator {
|
|
|
113
113
|
}
|
|
114
114
|
|
|
115
115
|
generateLicenceLengthRow () {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
LICENCE_LENGTH.uri,
|
|
120
|
-
|
|
121
|
-
|
|
116
|
+
const args = ['licence_summary_length', this.labels[`licence_type_${this.permission.licenceLength.toLowerCase()}`]]
|
|
117
|
+
|
|
118
|
+
if (this.permission.numberOfRods !== '3') {
|
|
119
|
+
args.push(LICENCE_LENGTH.uri, 'change-licence-length')
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
return this.generateStandardRow(...args)
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
|
|
@@ -63,7 +63,7 @@ describe('prepareApiTransactionPayload', () => {
|
|
|
63
63
|
})
|
|
64
64
|
})
|
|
65
65
|
|
|
66
|
-
it('adds
|
|
66
|
+
it('adds transactionId to payload', async () => {
|
|
67
67
|
const transactionId = Symbol('transactionId')
|
|
68
68
|
|
|
69
69
|
const payload = await prepareApiTransactionPayload(getMockRequest(), transactionId)
|
|
@@ -71,6 +71,14 @@ describe('prepareApiTransactionPayload', () => {
|
|
|
71
71
|
expect(payload.transactionId).toBe(transactionId)
|
|
72
72
|
})
|
|
73
73
|
|
|
74
|
+
it('adds agreementId to payload', async () => {
|
|
75
|
+
const agreementId = Symbol('agreementId')
|
|
76
|
+
|
|
77
|
+
const payload = await prepareApiTransactionPayload(getMockRequest(), 'transaction_id', agreementId)
|
|
78
|
+
|
|
79
|
+
expect(payload.agreementId).toBe(agreementId)
|
|
80
|
+
})
|
|
81
|
+
|
|
74
82
|
const getMockRequest = (overrides = {}, state = {}) => ({
|
|
75
83
|
cache: () => ({
|
|
76
84
|
helpers: {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { preparePayment,
|
|
1
|
+
import { preparePayment, prepareRecurringPaymentAgreement } from '../payment.js'
|
|
2
2
|
import { licenceTypeAndLengthDisplay } from '../licence-type-display.js'
|
|
3
3
|
import { addLanguageCodeToUri } from '../uri-helper.js'
|
|
4
4
|
import { AGREED } from '../../uri.js'
|
|
@@ -20,9 +20,16 @@ const createRequest = (opts = {}, catalog = {}) => ({
|
|
|
20
20
|
server: { info: { protocol: opts.protocol || '' } }
|
|
21
21
|
})
|
|
22
22
|
|
|
23
|
-
const createTransaction = ({
|
|
23
|
+
const createTransaction = ({
|
|
24
|
+
isLicenceForYou = true,
|
|
25
|
+
additionalPermissions = [],
|
|
26
|
+
cost = 12,
|
|
27
|
+
licenseeOverrides = {},
|
|
28
|
+
agreementId
|
|
29
|
+
} = {}) => ({
|
|
24
30
|
id: 'transaction-id',
|
|
25
31
|
cost,
|
|
32
|
+
agreementId,
|
|
26
33
|
permissions: [
|
|
27
34
|
{
|
|
28
35
|
id: 'permission-id',
|
|
@@ -49,7 +56,7 @@ describe('preparePayment', () => {
|
|
|
49
56
|
it.each(['http', 'https'])('uses SSL when "x-forwarded-proto" header is present, proto "%s"', proto => {
|
|
50
57
|
addLanguageCodeToUri.mockReturnValue(proto + '://localhost:1234/buy/agreed')
|
|
51
58
|
const request = createRequest({ headers: { 'x-forwarded-proto': proto } })
|
|
52
|
-
const result = preparePayment(request, createTransaction())
|
|
59
|
+
const result = preparePayment(request, createTransaction(), false)
|
|
53
60
|
|
|
54
61
|
expect(result.return_url).toBe(`${proto}://localhost:1234/buy/agreed`)
|
|
55
62
|
})
|
|
@@ -211,12 +218,30 @@ describe('preparePayment', () => {
|
|
|
211
218
|
expect(result.email).toBe(undefined)
|
|
212
219
|
})
|
|
213
220
|
})
|
|
221
|
+
|
|
222
|
+
describe('if agreementId is not present', () => {
|
|
223
|
+
it('does not include set_up_agreement', () => {
|
|
224
|
+
const result = preparePayment(createRequest(), createTransaction())
|
|
225
|
+
expect(result.set_up_agreement).toBe(undefined)
|
|
226
|
+
})
|
|
227
|
+
})
|
|
228
|
+
|
|
229
|
+
describe('if agreementId is present', () => {
|
|
230
|
+
it('set_up_agreement is set to agreementId', () => {
|
|
231
|
+
const agreementId = 'foo'
|
|
232
|
+
const recurringPaymentTransaction = createTransaction({ agreementId })
|
|
233
|
+
|
|
234
|
+
const result = preparePayment(createRequest(), recurringPaymentTransaction)
|
|
235
|
+
|
|
236
|
+
expect(result.set_up_agreement).toBe(agreementId)
|
|
237
|
+
})
|
|
238
|
+
})
|
|
214
239
|
})
|
|
215
240
|
|
|
216
|
-
describe('
|
|
241
|
+
describe('prepareRecurringPaymentAgreement', () => {
|
|
217
242
|
it('reference equals transaction.id', async () => {
|
|
218
243
|
const transaction = createTransaction()
|
|
219
|
-
const result = await
|
|
244
|
+
const result = await prepareRecurringPaymentAgreement(createRequest(), transaction)
|
|
220
245
|
expect(result.reference).toBe(transaction.id)
|
|
221
246
|
})
|
|
222
247
|
|
|
@@ -227,7 +252,7 @@ describe('prepareRecurringPayment', () => {
|
|
|
227
252
|
const request = createRequest({}, mockCatalog)
|
|
228
253
|
const transaction = createTransaction()
|
|
229
254
|
|
|
230
|
-
const result = await
|
|
255
|
+
const result = await prepareRecurringPaymentAgreement(request, transaction)
|
|
231
256
|
expect(result.description).toBe(mockCatalog.recurring_payment_description)
|
|
232
257
|
})
|
|
233
258
|
|
|
@@ -235,7 +260,7 @@ describe('prepareRecurringPayment', () => {
|
|
|
235
260
|
const transaction = createTransaction()
|
|
236
261
|
const request = createRequest()
|
|
237
262
|
|
|
238
|
-
const result = await
|
|
239
|
-
expect(debug).toHaveBeenCalledWith('Creating prepared recurring payment %o', result)
|
|
263
|
+
const result = await prepareRecurringPaymentAgreement(request, transaction)
|
|
264
|
+
expect(debug).toHaveBeenCalledWith('Creating prepared recurring payment agreement %o', result)
|
|
240
265
|
})
|
|
241
266
|
})
|
|
@@ -7,7 +7,7 @@ import { countries } from './refdata-helper.js'
|
|
|
7
7
|
import { salesApi } from '@defra-fish/connectors-lib'
|
|
8
8
|
import { licenceToStart } from '../pages/licence-details/licence-to-start/update-transaction.js'
|
|
9
9
|
|
|
10
|
-
export const prepareApiTransactionPayload = async (request, transactionId) => {
|
|
10
|
+
export const prepareApiTransactionPayload = async (request, transactionId, agreementId) => {
|
|
11
11
|
const transactionCache = await request.cache().helpers.transaction.get()
|
|
12
12
|
const concessions = await salesApi.concessions.getAll()
|
|
13
13
|
const countryList = await countries.getAll()
|
|
@@ -63,7 +63,8 @@ export const prepareApiTransactionPayload = async (request, transactionId) => {
|
|
|
63
63
|
request.state && request.state[process.env.OIDC_SESSION_COOKIE_NAME]
|
|
64
64
|
? request.state[process.env.OIDC_SESSION_COOKIE_NAME].oid
|
|
65
65
|
: undefined,
|
|
66
|
-
transactionId
|
|
66
|
+
transactionId,
|
|
67
|
+
agreementId
|
|
67
68
|
}
|
|
68
69
|
}
|
|
69
70
|
|
|
@@ -50,17 +50,21 @@ export const preparePayment = (request, transaction) => {
|
|
|
50
50
|
}
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
if (transaction.agreementId) {
|
|
54
|
+
result.set_up_agreement = transaction.agreementId
|
|
55
|
+
}
|
|
56
|
+
|
|
53
57
|
debug('Creating prepared payment %o', result)
|
|
54
58
|
return result
|
|
55
59
|
}
|
|
56
60
|
|
|
57
|
-
export const
|
|
61
|
+
export const prepareRecurringPaymentAgreement = async (request, transaction) => {
|
|
58
62
|
debug('Preparing recurring payment %s', JSON.stringify(transaction, undefined, '\t'))
|
|
59
|
-
// The recurring card payment for your rod fishing licence
|
|
63
|
+
// The recurring card payment agreement for your rod fishing licence
|
|
60
64
|
const result = {
|
|
61
65
|
reference: transaction.id,
|
|
62
66
|
description: request.i18n.getCatalog().recurring_payment_description
|
|
63
67
|
}
|
|
64
|
-
debug('Creating prepared recurring payment %o', result)
|
|
68
|
+
debug('Creating prepared recurring payment agreement %o', result)
|
|
65
69
|
return result
|
|
66
70
|
}
|
|
@@ -2,7 +2,7 @@ import mockTransaction from './data/mock-transaction.js'
|
|
|
2
2
|
import { preparePayment } from '../../../processors/payment.js'
|
|
3
3
|
import { AGREED } from '../../../uri.js'
|
|
4
4
|
import { addLanguageCodeToUri } from '../../../processors/uri-helper.js'
|
|
5
|
-
import { sendRecurringPayment } from '../govuk-pay-service.js'
|
|
5
|
+
import { sendPayment, sendRecurringPayment, getPaymentStatus } from '../govuk-pay-service.js'
|
|
6
6
|
import { govUkPayApi } from '@defra-fish/connectors-lib'
|
|
7
7
|
import db from 'debug'
|
|
8
8
|
const { value: debug } = db.mock.results[db.mock.calls.findIndex(c => c[0] === 'webapp:govuk-pay-service')]
|
|
@@ -158,6 +158,142 @@ describe('The govuk-pay-service', () => {
|
|
|
158
158
|
console.log(preparedPayment)
|
|
159
159
|
})
|
|
160
160
|
|
|
161
|
+
describe('sendPayment', () => {
|
|
162
|
+
const preparedPayment = {
|
|
163
|
+
id: '1234',
|
|
164
|
+
user_identifier: 'test-user'
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
beforeEach(() => {
|
|
168
|
+
jest.clearAllMocks()
|
|
169
|
+
})
|
|
170
|
+
|
|
171
|
+
it.each([
|
|
172
|
+
[true, true],
|
|
173
|
+
[false, false],
|
|
174
|
+
[false, undefined]
|
|
175
|
+
])('should call the govUkPayApi with recurring as %s if the argument is %s', async (expected, value) => {
|
|
176
|
+
const mockResponse = {
|
|
177
|
+
ok: true,
|
|
178
|
+
json: jest.fn().mockResolvedValue({ success: true, paymentId: 'abc123' })
|
|
179
|
+
}
|
|
180
|
+
govUkPayApi.createPayment.mockResolvedValue(mockResponse)
|
|
181
|
+
const unique = Symbol('payload')
|
|
182
|
+
const payload = { unique }
|
|
183
|
+
await sendPayment(payload, value)
|
|
184
|
+
expect(govUkPayApi.createPayment).toHaveBeenCalledWith(payload, expected)
|
|
185
|
+
})
|
|
186
|
+
|
|
187
|
+
it('should send provided payload data to Gov.UK Pay', async () => {
|
|
188
|
+
const mockResponse = {
|
|
189
|
+
ok: true,
|
|
190
|
+
json: jest.fn().mockResolvedValue({ success: true, paymentId: 'abc123' })
|
|
191
|
+
}
|
|
192
|
+
govUkPayApi.createPayment.mockResolvedValue(mockResponse)
|
|
193
|
+
const unique = Symbol('payload')
|
|
194
|
+
const payload = {
|
|
195
|
+
reference: 'd81f1a2b-6508-468f-8342-b6770f60f7cd',
|
|
196
|
+
description: 'Fishing permission',
|
|
197
|
+
user_identifier: '1218c1c5-38e4-4bf3-81ea-9cbce3994d30',
|
|
198
|
+
unique
|
|
199
|
+
}
|
|
200
|
+
await sendPayment(payload)
|
|
201
|
+
expect(govUkPayApi.createPayment).toHaveBeenCalledWith(payload, false)
|
|
202
|
+
})
|
|
203
|
+
|
|
204
|
+
it('should return response body when payment creation is successful', async () => {
|
|
205
|
+
const mockResponse = {
|
|
206
|
+
ok: true,
|
|
207
|
+
json: jest.fn().mockResolvedValue({ success: true, paymentId: 'abc123' })
|
|
208
|
+
}
|
|
209
|
+
govUkPayApi.createPayment.mockResolvedValue(mockResponse)
|
|
210
|
+
|
|
211
|
+
const result = await sendPayment(preparedPayment)
|
|
212
|
+
|
|
213
|
+
expect(result).toEqual({ success: true, paymentId: 'abc123' })
|
|
214
|
+
})
|
|
215
|
+
|
|
216
|
+
it('should log debug message when response.ok is true', async () => {
|
|
217
|
+
const mockResponse = {
|
|
218
|
+
ok: true,
|
|
219
|
+
json: jest.fn().mockResolvedValue({ success: true, paymentId: 'abc123' })
|
|
220
|
+
}
|
|
221
|
+
govUkPayApi.createPayment.mockResolvedValue(mockResponse)
|
|
222
|
+
|
|
223
|
+
await sendPayment(preparedPayment)
|
|
224
|
+
|
|
225
|
+
expect(debug).toHaveBeenCalledWith('Successful payment creation response: %o', { success: true, paymentId: 'abc123' })
|
|
226
|
+
})
|
|
227
|
+
|
|
228
|
+
it('should log error message when response.ok is false', async () => {
|
|
229
|
+
const mockResponse = {
|
|
230
|
+
ok: false,
|
|
231
|
+
status: 500,
|
|
232
|
+
json: jest.fn().mockResolvedValue({ message: 'Server error' })
|
|
233
|
+
}
|
|
234
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
235
|
+
govUkPayApi.createPayment.mockResolvedValue(mockResponse)
|
|
236
|
+
|
|
237
|
+
try {
|
|
238
|
+
await sendPayment(preparedPayment)
|
|
239
|
+
} catch (error) {
|
|
240
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith('Failure creating payment in the GOV.UK API service', {
|
|
241
|
+
transactionId: preparedPayment.id,
|
|
242
|
+
method: 'POST',
|
|
243
|
+
payload: preparedPayment,
|
|
244
|
+
status: mockResponse.status,
|
|
245
|
+
response: { message: 'Server error' }
|
|
246
|
+
})
|
|
247
|
+
}
|
|
248
|
+
})
|
|
249
|
+
|
|
250
|
+
it('should throw error when API call fails with network issue', async () => {
|
|
251
|
+
const mockError = new Error('Network error')
|
|
252
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
|
|
253
|
+
govUkPayApi.createPayment.mockRejectedValue(mockError)
|
|
254
|
+
|
|
255
|
+
try {
|
|
256
|
+
await sendPayment(preparedPayment)
|
|
257
|
+
} catch (error) {
|
|
258
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
259
|
+
`Error creating payment in the GOV.UK API service - tid: ${preparedPayment.id}`,
|
|
260
|
+
mockError
|
|
261
|
+
)
|
|
262
|
+
}
|
|
263
|
+
})
|
|
264
|
+
|
|
265
|
+
it('should throw error for when rate limit is breached', async () => {
|
|
266
|
+
const mockResponse = {
|
|
267
|
+
ok: false,
|
|
268
|
+
status: 429,
|
|
269
|
+
json: jest.fn().mockResolvedValue({ message: 'Rate limit exceeded' })
|
|
270
|
+
}
|
|
271
|
+
const consoleErrorSpy = jest.spyOn(console, 'info').mockImplementation(jest.fn())
|
|
272
|
+
govUkPayApi.createPayment.mockResolvedValue(mockResponse)
|
|
273
|
+
|
|
274
|
+
try {
|
|
275
|
+
await sendPayment(preparedPayment)
|
|
276
|
+
} catch (error) {
|
|
277
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(`GOV.UK Pay API rate limit breach - tid: ${preparedPayment.id}`)
|
|
278
|
+
}
|
|
279
|
+
})
|
|
280
|
+
|
|
281
|
+
it('should throw error for unexpected response status', async () => {
|
|
282
|
+
const mockResponse = {
|
|
283
|
+
ok: false,
|
|
284
|
+
status: 500,
|
|
285
|
+
json: jest.fn().mockResolvedValue({ message: 'Server error' })
|
|
286
|
+
}
|
|
287
|
+
govUkPayApi.createPayment.mockResolvedValue(mockResponse)
|
|
288
|
+
|
|
289
|
+
try {
|
|
290
|
+
await sendPayment(preparedPayment)
|
|
291
|
+
} catch (error) {
|
|
292
|
+
expect(error.message).toBe('Unexpected response from GOV.UK pay API')
|
|
293
|
+
}
|
|
294
|
+
})
|
|
295
|
+
})
|
|
296
|
+
|
|
161
297
|
describe('sendRecurringPayment', () => {
|
|
162
298
|
const preparedPayment = {
|
|
163
299
|
id: '1234',
|
|
@@ -277,4 +413,120 @@ describe('The govuk-pay-service', () => {
|
|
|
277
413
|
}
|
|
278
414
|
})
|
|
279
415
|
})
|
|
416
|
+
|
|
417
|
+
describe('getPaymentStatus', () => {
|
|
418
|
+
const paymentId = '1234'
|
|
419
|
+
|
|
420
|
+
beforeEach(() => {
|
|
421
|
+
jest.clearAllMocks()
|
|
422
|
+
})
|
|
423
|
+
|
|
424
|
+
it.each([
|
|
425
|
+
[true, true],
|
|
426
|
+
[false, false],
|
|
427
|
+
[false, undefined]
|
|
428
|
+
])('should call the govUkPayApi with recurring as %s if the argument is %s', async (expected, value) => {
|
|
429
|
+
const mockResponse = { ok: true, status: 200, json: () => {} }
|
|
430
|
+
govUkPayApi.fetchPaymentStatus.mockResolvedValue(mockResponse)
|
|
431
|
+
await getPaymentStatus(paymentId, value)
|
|
432
|
+
expect(govUkPayApi.fetchPaymentStatus).toHaveBeenCalledWith(paymentId, expected)
|
|
433
|
+
})
|
|
434
|
+
|
|
435
|
+
it('should send provided paymentId to Gov.UK Pay', async () => {
|
|
436
|
+
const mockResponse = { ok: true, status: 200, json: () => {} }
|
|
437
|
+
govUkPayApi.fetchPaymentStatus.mockResolvedValue(mockResponse)
|
|
438
|
+
await getPaymentStatus(paymentId)
|
|
439
|
+
expect(govUkPayApi.fetchPaymentStatus).toHaveBeenCalledWith(paymentId, false)
|
|
440
|
+
})
|
|
441
|
+
|
|
442
|
+
it('should return response body when payment status check is successful', async () => {
|
|
443
|
+
const resBody = Symbol('body')
|
|
444
|
+
const mockResponse = { ok: true, status: 200, json: jest.fn().mockResolvedValue(resBody) }
|
|
445
|
+
govUkPayApi.fetchPaymentStatus.mockResolvedValue(mockResponse)
|
|
446
|
+
|
|
447
|
+
const result = await getPaymentStatus(paymentId)
|
|
448
|
+
|
|
449
|
+
expect(result).toEqual(resBody)
|
|
450
|
+
})
|
|
451
|
+
|
|
452
|
+
it('should log debug message when response.ok is true', async () => {
|
|
453
|
+
const resBody = Symbol('body')
|
|
454
|
+
const mockResponse = { ok: true, status: 200, json: jest.fn().mockResolvedValue(resBody) }
|
|
455
|
+
govUkPayApi.fetchPaymentStatus.mockResolvedValue(mockResponse)
|
|
456
|
+
|
|
457
|
+
await getPaymentStatus(paymentId)
|
|
458
|
+
|
|
459
|
+
expect(debug).toHaveBeenCalledWith('Payment status response: %o', resBody)
|
|
460
|
+
})
|
|
461
|
+
|
|
462
|
+
it('should log error message when response.ok is false', async () => {
|
|
463
|
+
const mockResponse = {
|
|
464
|
+
ok: false,
|
|
465
|
+
status: 500,
|
|
466
|
+
json: jest.fn().mockResolvedValue({ message: 'Server error' })
|
|
467
|
+
}
|
|
468
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(() => {})
|
|
469
|
+
govUkPayApi.fetchPaymentStatus.mockResolvedValue(mockResponse)
|
|
470
|
+
|
|
471
|
+
try {
|
|
472
|
+
await getPaymentStatus(paymentId)
|
|
473
|
+
} catch (error) {
|
|
474
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
475
|
+
`Error retrieving the payment status from the GOV.UK API service - tid: ${paymentId}`,
|
|
476
|
+
{
|
|
477
|
+
method: 'GET',
|
|
478
|
+
paymentId: paymentId,
|
|
479
|
+
status: mockResponse.status,
|
|
480
|
+
response: { message: 'Server error' }
|
|
481
|
+
}
|
|
482
|
+
)
|
|
483
|
+
}
|
|
484
|
+
})
|
|
485
|
+
|
|
486
|
+
it('should throw error when API call fails with network issue', async () => {
|
|
487
|
+
const mockError = new Error('Network error')
|
|
488
|
+
const consoleErrorSpy = jest.spyOn(console, 'error').mockImplementation(jest.fn())
|
|
489
|
+
govUkPayApi.fetchPaymentStatus.mockRejectedValue(mockError)
|
|
490
|
+
|
|
491
|
+
try {
|
|
492
|
+
await getPaymentStatus(paymentId)
|
|
493
|
+
} catch (error) {
|
|
494
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(
|
|
495
|
+
`Error retrieving the payment status from the GOV.UK API service - paymentId: ${paymentId}`,
|
|
496
|
+
mockError
|
|
497
|
+
)
|
|
498
|
+
}
|
|
499
|
+
})
|
|
500
|
+
|
|
501
|
+
it('should throw error for when rate limit is breached', async () => {
|
|
502
|
+
const mockResponse = {
|
|
503
|
+
ok: false,
|
|
504
|
+
status: 429,
|
|
505
|
+
json: jest.fn().mockResolvedValue({ message: 'Rate limit exceeded' })
|
|
506
|
+
}
|
|
507
|
+
const consoleErrorSpy = jest.spyOn(console, 'info').mockImplementation(jest.fn())
|
|
508
|
+
govUkPayApi.fetchPaymentStatus.mockResolvedValue(mockResponse)
|
|
509
|
+
|
|
510
|
+
try {
|
|
511
|
+
await getPaymentStatus(paymentId)
|
|
512
|
+
} catch (error) {
|
|
513
|
+
expect(consoleErrorSpy).toHaveBeenCalledWith(`GOV.UK Pay API rate limit breach - paymentId: ${paymentId}`)
|
|
514
|
+
}
|
|
515
|
+
})
|
|
516
|
+
|
|
517
|
+
it('should throw error for unexpected response status', async () => {
|
|
518
|
+
const mockResponse = {
|
|
519
|
+
ok: false,
|
|
520
|
+
status: 500,
|
|
521
|
+
json: jest.fn().mockResolvedValue({ message: 'Server error' })
|
|
522
|
+
}
|
|
523
|
+
govUkPayApi.fetchPaymentStatus.mockResolvedValue(mockResponse)
|
|
524
|
+
|
|
525
|
+
try {
|
|
526
|
+
await getPaymentStatus(paymentId)
|
|
527
|
+
} catch (error) {
|
|
528
|
+
expect(error.message).toBe('Unexpected response from GOV.UK pay API')
|
|
529
|
+
}
|
|
530
|
+
})
|
|
531
|
+
})
|
|
280
532
|
})
|
|
@@ -46,10 +46,10 @@ const getTransactionErrorMessage = async (transactionId, payload, response) => (
|
|
|
46
46
|
* @param preparedPayment - the prepared payload for the payment. See in processors/payment.js
|
|
47
47
|
* @returns {Promise<*>}
|
|
48
48
|
*/
|
|
49
|
-
export const sendPayment = async preparedPayment => {
|
|
49
|
+
export const sendPayment = async (preparedPayment, recurring = false) => {
|
|
50
50
|
let response
|
|
51
51
|
try {
|
|
52
|
-
response = await govUkPayApi.createPayment(preparedPayment)
|
|
52
|
+
response = await govUkPayApi.createPayment(preparedPayment, recurring)
|
|
53
53
|
} catch (err) {
|
|
54
54
|
/*
|
|
55
55
|
* Potentially errors caught here (unreachable, timeouts) may be retried - set origin on the error to indicate
|
|
@@ -78,11 +78,11 @@ export const sendPayment = async preparedPayment => {
|
|
|
78
78
|
* @param paymentId - the paymentId
|
|
79
79
|
* @returns {Promise<any>}
|
|
80
80
|
*/
|
|
81
|
-
export const getPaymentStatus = async paymentId => {
|
|
81
|
+
export const getPaymentStatus = async (paymentId, recurring = false) => {
|
|
82
82
|
debug(`Get payment status for paymentId: ${paymentId}`)
|
|
83
83
|
let response
|
|
84
84
|
try {
|
|
85
|
-
response = await govUkPayApi.fetchPaymentStatus(paymentId)
|
|
85
|
+
response = await govUkPayApi.fetchPaymentStatus(paymentId, recurring)
|
|
86
86
|
} catch (err) {
|
|
87
87
|
/*
|
|
88
88
|
* Errors caught here (unreachable, timeouts) may be retried - set origin on the error to indicate
|