@defra-fish/gafl-webapp-service 1.64.0-rc.8 → 1.65.0-rc.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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@defra-fish/gafl-webapp-service",
3
- "version": "1.64.0-rc.8",
3
+ "version": "1.65.0-rc.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.64.0-rc.8",
40
- "@defra-fish/connectors-lib": "1.64.0-rc.8",
39
+ "@defra-fish/business-rules-lib": "1.65.0-rc.0",
40
+ "@defra-fish/connectors-lib": "1.65.0-rc.0",
41
41
  "@defra/hapi-gapi": "2.0.0",
42
42
  "@hapi/boom": "9.1.2",
43
43
  "@hapi/catbox-redis": "6.0.2",
@@ -79,5 +79,5 @@
79
79
  "./gafl-jest-matchers.js"
80
80
  ]
81
81
  },
82
- "gitHead": "88a60cab95e119b46cec17336d784aec1aeabae8"
82
+ "gitHead": "cdf9253964871c4afe0ff85d085364ffe4e890cf"
83
83
  }
@@ -0,0 +1,214 @@
1
+ import handler from '../cancel-recurring-payment-authentication-handler'
2
+ import { CANCEL_RP_IDENTIFY, CANCEL_RP_DETAILS } from '../../uri.js'
3
+ import { addLanguageCodeToUri } from '../../processors/uri-helper.js'
4
+ import { salesApi } from '@defra-fish/connectors-lib'
5
+
6
+ jest.mock('../../processors/uri-helper.js')
7
+ jest.mock('@defra-fish/connectors-lib')
8
+ jest.mock('../../processors/renewals-write-cache.js', () => ({
9
+ setUpCacheFromAuthenticationResult: jest.fn().mockResolvedValue(undefined),
10
+ setUpPayloads: jest.fn().mockResolvedValue(undefined)
11
+ }))
12
+ jest.mock('@defra-fish/business-rules-lib', () => ({
13
+ validation: {
14
+ contact: {
15
+ createBirthDateValidator: () => ({ validateAsync: async () => '1970-01-01' }),
16
+ createOverseasPostcodeValidator: () => ({ validateAsync: async () => 'AA1 1AA' })
17
+ }
18
+ }
19
+ }))
20
+ jest.mock('../../uri.js', () => ({
21
+ CANCEL_RP_IDENTIFY: { page: 'cancel-rp-identify page', uri: Symbol('cancel-rp-identify-uri') },
22
+ CANCEL_RP_DETAILS: { uri: Symbol('cancel-rp-details-uri') }
23
+ }))
24
+ jest.mock('../../processors/recurring-payments-write-cache.js')
25
+
26
+ const getSampleRequest = (payloadOverride = {}) => {
27
+ const getCurrentPermission = jest.fn(async () => ({}))
28
+ const setCurrentPermission = jest.fn()
29
+
30
+ return {
31
+ cache: () => ({
32
+ helpers: {
33
+ page: {
34
+ getCurrentPermission: jest.fn(async () => ({
35
+ payload: {
36
+ referenceNumber: 'ABC123',
37
+ 'date-of-birth-day': '01',
38
+ 'date-of-birth-month': '01',
39
+ 'date-of-birth-year': '1970',
40
+ postcode: 'AA1 1AA',
41
+ ...payloadOverride
42
+ }
43
+ })),
44
+ setCurrentPermission
45
+ },
46
+ status: {
47
+ getCurrentPermission,
48
+ setCurrentPermission
49
+ }
50
+ }
51
+ })
52
+ }
53
+ }
54
+
55
+ const getSampleResponseTooklkit = () => ({
56
+ redirectWithLanguageCode: jest.fn().mockReturnValue('redirected')
57
+ })
58
+
59
+ const invokeHandlerWithMocks = async ({ salesApiResponse, decoratedIdentifyUri } = {}) => {
60
+ if (typeof salesApiResponse !== 'undefined') {
61
+ salesApi.authenticateRecurringPayment.mockResolvedValueOnce(salesApiResponse)
62
+ }
63
+ if (decoratedIdentifyUri) {
64
+ addLanguageCodeToUri.mockReturnValueOnce(decoratedIdentifyUri)
65
+ }
66
+ const request = getSampleRequest()
67
+ const h = getSampleResponseTooklkit()
68
+ if (decoratedIdentifyUri) {
69
+ h.redirect = jest.fn().mockReturnValue('redirect-response')
70
+ }
71
+ const result = await handler(request, h)
72
+ return { request, h, result }
73
+ }
74
+
75
+ const mockSuccessResponse = () => ({
76
+ permission: { id: 'perm-id' },
77
+ recurringPayment: { id: 'rcp-id', status: 0, cancelledDate: null }
78
+ })
79
+
80
+ describe('Cancel RP Authentication Handler', () => {
81
+ beforeEach(() => {
82
+ jest.resetAllMocks()
83
+ })
84
+
85
+ describe('Successful authentication', () => {
86
+ it('returns the redirect result', async () => {
87
+ const { result } = await invokeHandlerWithMocks({
88
+ salesApiResponse: mockSuccessResponse()
89
+ })
90
+ expect(result).toBe('redirected')
91
+ })
92
+
93
+ it('redirects to details', async () => {
94
+ const { h } = await invokeHandlerWithMocks({
95
+ salesApiResponse: mockSuccessResponse()
96
+ })
97
+ expect(h.redirectWithLanguageCode).toHaveBeenCalledWith(CANCEL_RP_DETAILS.uri)
98
+ })
99
+
100
+ it('marks status as authorised', async () => {
101
+ const { request } = await invokeHandlerWithMocks({
102
+ salesApiResponse: mockSuccessResponse()
103
+ })
104
+ expect(request.cache().helpers.status.setCurrentPermission).toHaveBeenCalledWith({ authentication: { authorised: true } })
105
+ })
106
+ })
107
+
108
+ describe('Unsuccessful authentication - no match', () => {
109
+ it('redirects to the decorated identify URI', async () => {
110
+ const { h } = await invokeHandlerWithMocks({ salesApiResponse: null, decoratedIdentifyUri: 'decorated-identify-uri' })
111
+ expect(h.redirect).toHaveBeenCalledWith('decorated-identify-uri')
112
+ })
113
+
114
+ it('sets page cache error and preserves payload', async () => {
115
+ const { request } = await invokeHandlerWithMocks({ salesApiResponse: null, decoratedIdentifyUri: 'decorated-identify-uri' })
116
+ expect(request.cache().helpers.page.setCurrentPermission).toHaveBeenCalledWith(
117
+ CANCEL_RP_IDENTIFY.page,
118
+ expect.objectContaining({
119
+ payload: expect.any(Object),
120
+ error: { referenceNumber: 'not-found' }
121
+ })
122
+ )
123
+ })
124
+
125
+ it('marks status as unauthorised', async () => {
126
+ const { request } = await invokeHandlerWithMocks({ salesApiResponse: null, decoratedIdentifyUri: 'decorated-identify-uri' })
127
+ expect(request.cache().helpers.status.setCurrentPermission).toHaveBeenCalledWith(
128
+ expect.objectContaining({ authentication: { authorised: false } })
129
+ )
130
+ })
131
+ })
132
+
133
+ describe('Unsuccessful authentication - no recurring payment agreement', () => {
134
+ it('redirects to the decorated identify URI', async () => {
135
+ const { h } = await invokeHandlerWithMocks({
136
+ salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: null },
137
+ decoratedIdentifyUri: 'decorated-identify-uri'
138
+ })
139
+ expect(h.redirect).toHaveBeenCalledWith('decorated-identify-uri')
140
+ })
141
+
142
+ it('sets page cache error for no RCP setup', async () => {
143
+ const { request } = await invokeHandlerWithMocks({
144
+ salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: null },
145
+ decoratedIdentifyUri: 'decorated-identify-uri'
146
+ })
147
+ expect(request.cache().helpers.page.setCurrentPermission).toHaveBeenCalledWith(
148
+ CANCEL_RP_IDENTIFY.page,
149
+ expect.objectContaining({
150
+ payload: expect.any(Object),
151
+ error: { recurringPayment: 'not-set-up' }
152
+ })
153
+ )
154
+ })
155
+
156
+ it('marks status as unauthorised', async () => {
157
+ const { request } = await invokeHandlerWithMocks({
158
+ salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: null },
159
+ decoratedIdentifyUri: 'decorated-identify-uri'
160
+ })
161
+ expect(request.cache().helpers.status.setCurrentPermission).toHaveBeenCalledWith(
162
+ expect.objectContaining({ authentication: { authorised: false } })
163
+ )
164
+ })
165
+ })
166
+
167
+ describe('Unsuccessful authentication - RCP cancelled', () => {
168
+ it('redirects to the decorated identify URI', async () => {
169
+ const { h } = await invokeHandlerWithMocks({
170
+ salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: { id: 'rcp-id', status: 1, cancelledDate: '2024-01-01' } },
171
+ decoratedIdentifyUri: 'decorated-identify-uri'
172
+ })
173
+ expect(h.redirect).toHaveBeenCalledWith('decorated-identify-uri')
174
+ })
175
+
176
+ it('sets page cache error for RCP cancelled', async () => {
177
+ const { request } = await invokeHandlerWithMocks({
178
+ salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: { id: 'rcp-id', status: 1, cancelledDate: '2024-01-01' } },
179
+ decoratedIdentifyUri: 'decorated-identify-uri'
180
+ })
181
+ expect(request.cache().helpers.page.setCurrentPermission).toHaveBeenCalledWith(
182
+ CANCEL_RP_IDENTIFY.page,
183
+ expect.objectContaining({
184
+ payload: expect.any(Object),
185
+ error: { recurringPayment: 'rcp-cancelled' }
186
+ })
187
+ )
188
+ })
189
+
190
+ it('marks status as unauthorised', async () => {
191
+ const { request } = await invokeHandlerWithMocks({
192
+ salesApiResponse: { permission: { id: 'perm-id' }, recurringPayment: { id: 'rcp-id', status: 1, cancelledDate: '2024-01-01' } },
193
+ decoratedIdentifyUri: 'decorated-identify-uri'
194
+ })
195
+ expect(request.cache().helpers.status.setCurrentPermission).toHaveBeenCalledWith(
196
+ expect.objectContaining({ authentication: { authorised: false } })
197
+ )
198
+ })
199
+ })
200
+
201
+ it('uses referenceNumber from status when payload is missing', async () => {
202
+ salesApi.authenticateRecurringPayment.mockResolvedValueOnce({
203
+ permission: { id: 'perm-id' },
204
+ recurringPayment: { id: 'rcp-id', status: 0, cancelledDate: null }
205
+ })
206
+ const request = getSampleRequest({ referenceNumber: undefined })
207
+ request.cache().helpers.status.getCurrentPermission.mockReturnValueOnce({
208
+ referenceNumber: 'A1B2C3'
209
+ })
210
+ const h = getSampleResponseTooklkit()
211
+ await handler(request, h)
212
+ expect(salesApi.authenticateRecurringPayment).toHaveBeenCalledWith('A1B2C3', expect.anything(), expect.anything())
213
+ })
214
+ })
@@ -0,0 +1,60 @@
1
+ import { CANCEL_RP_IDENTIFY, CANCEL_RP_DETAILS } from '../../src/uri.js'
2
+ import { addLanguageCodeToUri } from '../processors/uri-helper.js'
3
+ import { salesApi } from '@defra-fish/connectors-lib'
4
+ import { validation } from '@defra-fish/business-rules-lib'
5
+ import { setupCancelRecurringPaymentCacheFromAuthResult } from '../processors/recurring-payments-write-cache.js'
6
+ import Joi from 'joi'
7
+
8
+ const buildAuthFailure = (referenceNumber, payload, error) => ({
9
+ page: {
10
+ page: CANCEL_RP_IDENTIFY.page,
11
+ data: { payload, error }
12
+ },
13
+ status: {
14
+ referenceNumber,
15
+ authentication: { authorised: false }
16
+ },
17
+ redirectPath: CANCEL_RP_IDENTIFY.uri
18
+ })
19
+
20
+ const applyAuthFailure = async (request, h, failure) => {
21
+ await request.cache().helpers.page.setCurrentPermission(failure.page.page, failure.page.data)
22
+ await request.cache().helpers.status.setCurrentPermission(failure.status)
23
+ return h.redirect(addLanguageCodeToUri(request, failure.redirectPath))
24
+ }
25
+
26
+ const cancelRecurringPaymentAuthenticationHandler = async (request, h) => {
27
+ const { payload } = await request.cache().helpers.page.getCurrentPermission(CANCEL_RP_IDENTIFY.page)
28
+ const permission = await request.cache().helpers.status.getCurrentPermission()
29
+
30
+ const referenceNumber = payload.referenceNumber || permission.referenceNumber
31
+
32
+ const dateOfBirth = await validation.contact
33
+ .createBirthDateValidator(Joi)
34
+ .validateAsync(`${payload['date-of-birth-year']}-${payload['date-of-birth-month']}-${payload['date-of-birth-day']}`)
35
+ const postcode = await validation.contact.createOverseasPostcodeValidator(Joi).validateAsync(payload.postcode)
36
+
37
+ const authenticationResult = await salesApi.authenticateRecurringPayment(referenceNumber, dateOfBirth, postcode)
38
+
39
+ const failures = error => applyAuthFailure(request, h, buildAuthFailure(referenceNumber, payload, error))
40
+
41
+ if (!authenticationResult) {
42
+ return failures({ referenceNumber: 'not-found' })
43
+ }
44
+ if (!authenticationResult.recurringPayment) {
45
+ return failures({ recurringPayment: 'not-set-up' })
46
+ }
47
+ if (authenticationResult.recurringPayment.cancelledDate) {
48
+ return failures({ recurringPayment: 'rcp-cancelled' })
49
+ }
50
+
51
+ await setupCancelRecurringPaymentCacheFromAuthResult(request, authenticationResult)
52
+
53
+ await request.cache().helpers.status.setCurrentPermission({
54
+ authentication: { authorised: true }
55
+ })
56
+
57
+ return h.redirectWithLanguageCode(CANCEL_RP_DETAILS.uri)
58
+ }
59
+
60
+ export default cancelRecurringPaymentAuthenticationHandler
@@ -140,6 +140,19 @@
140
140
  "back": "Yn ôl",
141
141
  "buy_another_licence": "Prynu trwydded arall",
142
142
  "buy_different_licence": "Prynu trwydded wahanol",
143
+ "cancel_rp_identify_caption": "Cancel your recurring card payment agreement",
144
+ "cancel_rp_identify_title": "Enter your details",
145
+ "cancel_rp_identify_body": "Cancelling your recurring card payment agreement will not cancel or refund your current licence. You can still fish until your current licence expires but your licence will not renew automatically.",
146
+ "cancel_rp_identify_ref_number": "Last 6 characters of your licence number",
147
+ "cancel_rp_identify_ref_number_hint": "For example, F4A315",
148
+ "cancel_rp_identify_ref_number_error": "Enter the last 6 characters of your licence number",
149
+ "cancel_rp_identify_ref_pattern": "The last six characters of your licence number don’t look right. Check and enter again",
150
+ "cancel_rp_identify_dob": "Date of birth",
151
+ "cancel_rp_identify_dob_hint": "For example, 31 3 1980",
152
+ "cancel_rp_identify_postcode": "Postcode",
153
+ "cancel_rp_identify_postcode_hint": "For example, WA4 1AB",
154
+ "cancel_rp_identify_postcode_error": "Enter a postcode",
155
+ "cancel_rp_identify_postcode_pattern": "Your postcode doesn’t look right. Check and enter again",
143
156
  "change_licence_details_you": "Adolygu neu newid manylion eich trwydded",
144
157
  "change_licence_details_other": "Adolygu neu newid manylion y drwydded",
145
158
  "change_licence_number": "Newid rhif y drwydded",
@@ -169,6 +182,7 @@
169
182
  "client_error_payment_start_again_pre": "Neu, os byddai’n well gennych, gallwch waredu’r drwydded hon a ",
170
183
  "client_error_payment_start_again": "dechrau eto",
171
184
  "concession_applied": "Consesiwn wedi’i gynnwys",
185
+ "confirm_continue": "Confirm and continue",
172
186
  "contact_summary_change": "Newid",
173
187
  "contact_summary_email": "e-bost i ",
174
188
  "contact_summary_hidden_address": "cyfeiriad",
@@ -774,8 +788,13 @@
774
788
  "role_required_title": "Role required",
775
789
  "rp_cancel_complete_title": "Cancel your recurring card payment agreement - complete",
776
790
  "rp_cancel_confirm_title": "Cancel your recurring card payment agreement - confirm",
777
- "rp_cancel_details_title": "Cancel your recurring card payment agreement - details",
778
- "rp_cancel_identify_title": "Cancel your recurring card payment agreement - identify",
791
+ "rp_cancel_details_last_purchased": "Last licence purchased",
792
+ "rp_cancel_details_licence_holder": "Licence holder",
793
+ "rp_cancel_details_licence_type": "Licence type",
794
+ "rp_cancel_details_licence_valid_until": "Licence valid until",
795
+ "rp_cancel_details_payment_card": "Payment card (last 4 digits)",
796
+ "rp_cancel_details_title": "Check your details",
797
+ "rp_cancel_details_summary_title": "If you need help checking your details",
779
798
  "save_changes": "Cadw newidiadau",
780
799
  "server_error_title_suffix": " - GOV.UK",
781
800
  "server_error_bulletpoint_1": "Os ydych chi wedi gofyn am hysbysiad dros e-bost neu neges destun, dylai’r hysbysiad gyrraedd o fewn 3 awr os bydd y taliad wedi cael ei gymryd.",
@@ -140,6 +140,19 @@
140
140
  "back": "Back",
141
141
  "buy_another_licence": "Buy another licence",
142
142
  "buy_different_licence": "Buy a different licence",
143
+ "cancel_rp_identify_caption": "Cancel your recurring card payment agreement",
144
+ "cancel_rp_identify_title": "Enter your details",
145
+ "cancel_rp_identify_body": "Cancelling your recurring card payment agreement will not cancel or refund your current licence. You can still fish until your current licence expires but your licence will not renew automatically.",
146
+ "cancel_rp_identify_ref_number": "Last 6 characters of your licence number",
147
+ "cancel_rp_identify_ref_number_hint": "For example, F4A315",
148
+ "cancel_rp_identify_ref_number_error": "Enter the last 6 characters of your licence number",
149
+ "cancel_rp_identify_ref_pattern": "The last six characters of your licence number don’t look right. Check and enter again",
150
+ "cancel_rp_identify_dob": "Date of birth",
151
+ "cancel_rp_identify_dob_hint": "For example, 31 3 1980",
152
+ "cancel_rp_identify_postcode": "Postcode",
153
+ "cancel_rp_identify_postcode_hint": "For example, WA4 1AB",
154
+ "cancel_rp_identify_postcode_error": "Enter a postcode",
155
+ "cancel_rp_identify_postcode_pattern": "Your postcode doesn’t look right. Check and enter again",
143
156
  "change_licence_details_you": "Review or change the licence details",
144
157
  "change_licence_details_other": "Review or change the licence details",
145
158
  "change_licence_number": "Change licence number",
@@ -169,6 +182,7 @@
169
182
  "client_error_payment_start_again_pre": "Or, if you wish, you can discard this licence and ",
170
183
  "client_error_payment_start_again": "start again",
171
184
  "concession_applied": "Concession applied",
185
+ "confirm_continue": "Confirm and continue",
172
186
  "contact_summary_change": "Change",
173
187
  "contact_summary_email": "Email to ",
174
188
  "contact_summary_hidden_address": "address",
@@ -774,8 +788,13 @@
774
788
  "role_required_title": "Role required",
775
789
  "rp_cancel_complete_title": "Cancel your recurring card payment agreement - complete",
776
790
  "rp_cancel_confirm_title": "Cancel your recurring card payment agreement - confirm",
777
- "rp_cancel_details_title": "Cancel your recurring card payment agreement - details",
778
- "rp_cancel_identify_title": "Cancel your recurring card payment agreement - identify",
791
+ "rp_cancel_details_last_purchased": "Last licence purchased",
792
+ "rp_cancel_details_licence_holder": "Licence holder",
793
+ "rp_cancel_details_licence_type": "Licence type",
794
+ "rp_cancel_details_licence_valid_until": "Licence valid until",
795
+ "rp_cancel_details_payment_card": "Payment card (last 4 digits)",
796
+ "rp_cancel_details_title": "Check your details",
797
+ "rp_cancel_details_summary_title": "If you need help checking your details",
779
798
  "save_changes": "Save changes",
780
799
  "server_error_title_suffix": " - GOV.UK",
781
800
  "server_error_bulletpoint_1": "If you have requested an email or text notification, this should be delivered in 3 hours if a payment has been taken.",
@@ -59,17 +59,17 @@
59
59
  <p class="govuk-body">{{ mssgs.access_statement_tech_body }}</p>
60
60
 
61
61
  <h2 class="govuk-heading-m">{{ mssgs.access_statement_compliance_heading }}</h2>
62
- <p class="govuk-body">{{ mssgs.access_statement_compliance_body_1 }}<a href="https://www.w3.org/TR/WCAG21/" class="govuk-link" target="_blank">{{ mssgs.access_statement_compliance_body_link_1 }}</a>{{ mssgs.full_stop }}</p>
63
- <p class="govuk-body">{{ mssgs.access_statement_compliance_body_2 }}<a href="https://www.w3.org/TR/WCAG21/" class="govuk-link" target="_blank">{{ mssgs.access_statement_compliance_body_link_2 }}</a>{{ mssgs.full_stop }}</p>
62
+ <p class="govuk-body">{{ mssgs.access_statement_compliance_body_1 }}<a href="https://www.w3.org/TR/WCAG22/" class="govuk-link" target="_blank">{{ mssgs.access_statement_compliance_body_link_1 }}</a>{{ mssgs.full_stop }}</p>
63
+ <p class="govuk-body">{{ mssgs.access_statement_compliance_body_2 }}<a href="https://www.w3.org/TR/WCAG22/" class="govuk-link" target="_blank">{{ mssgs.access_statement_compliance_body_link_2 }}</a>{{ mssgs.full_stop }}</p>
64
64
 
65
65
  <h2 class="govuk-heading-m">{{ mssgs.access_statement_nonaccess_heading }}</h2>
66
66
  <p class="govuk-body">{{ mssgs.access_statement_nonaccess_body }}</p>
67
67
 
68
68
  <h2 class="govuk-heading-m">{{ mssgs.access_statement_noncompliance_heading }}</h2>
69
69
  <ul class="govuk-list govuk-list--bullet">
70
- <li> {{ mssgs.access_statement_noncompliance_body_1 }}<a href="https://www.w3.org/WAI/WCAG21/Understanding/keyboard-no-exception.html" class="govuk-link" target="_blank">{{ mssgs.access_statement_noncompliance_body_link_1 }}</a>{{ mssgs.access_statement_noncompliance_body_2 }}</li>
71
- <li> {{ mssgs.access_statement_noncompliance_body_3 }}<a href="https://www.w3.org/WAI/WCAG21/Understanding/contrast-enhanced.html" class="govuk-link" target="_blank">{{ mssgs.access_statement_noncompliance_body_link_2 }}</a>{{ mssgs.access_statement_noncompliance_body_4 }} </li>
72
- <li> {{ mssgs.access_statement_noncompliance_body_5 }}<a href="https://www.w3.org/WAI/WCAG21/Understanding/link-purpose-link-only.html" class="govuk-link" target="_blank">{{ mssgs.access_statement_noncompliance_body_link_3 }}</a>{{ mssgs.access_statement_noncompliance_body_6 }} </li>
70
+ <li> {{ mssgs.access_statement_noncompliance_body_1 }}<a href="https://www.w3.org/WAI/WCAG22/Understanding/keyboard-no-exception.html" class="govuk-link" target="_blank">{{ mssgs.access_statement_noncompliance_body_link_1 }}</a>{{ mssgs.access_statement_noncompliance_body_2 }}</li>
71
+ <li> {{ mssgs.access_statement_noncompliance_body_3 }}<a href="https://www.w3.org/WAI/WCAG22/Understanding/contrast-enhanced.html" class="govuk-link" target="_blank">{{ mssgs.access_statement_noncompliance_body_link_2 }}</a>{{ mssgs.access_statement_noncompliance_body_4 }} </li>
72
+ <li> {{ mssgs.access_statement_noncompliance_body_5 }}<a href="https://www.w3.org/WAI/WCAG22/Understanding/link-purpose-link-only.html" class="govuk-link" target="_blank">{{ mssgs.access_statement_noncompliance_body_link_3 }}</a>{{ mssgs.access_statement_noncompliance_body_6 }} </li>
73
73
  </ul>
74
74
 
75
75
  <h2 class="govuk-heading-m">{{ mssgs.access_statement_burden_title }}</h2>
@@ -0,0 +1,131 @@
1
+ import pageRoute from '../../../../../routes/page-route.js'
2
+ import { CANCEL_RP_DETAILS, CANCEL_RP_CONFIRM } from '../../../../../uri.js'
3
+ import { addLanguageCodeToUri } from '../../../../../processors/uri-helper.js'
4
+ import { getData } from '../route.js'
5
+
6
+ jest.mock('../../../../../routes/page-route.js')
7
+ jest.mock('../../../../../uri.js', () => ({
8
+ ...jest.requireActual('../../../../../uri.js'),
9
+ CANCEL_RP_DETAILS: { page: Symbol('cancel-rp-details-page'), uri: Symbol('cancel-rp-details-uri') },
10
+ CANCEL_RP_CONFIRM: { uri: Symbol('cancel-rp-confirm-uri') }
11
+ }))
12
+ jest.mock('../../../../../processors/uri-helper.js')
13
+
14
+ describe('route', () => {
15
+ beforeEach(jest.clearAllMocks)
16
+
17
+ describe('pageRoute receives expected arguments', () => {
18
+ it('should call the pageRoute with cancel-rp-details, /buy/cancel-recurring-payment/details, dateOfBirthValidator, nextPage and getData', async () => {
19
+ jest.isolateModules(() => {
20
+ require('../route.js')
21
+ expect(pageRoute).toHaveBeenCalledWith(
22
+ CANCEL_RP_DETAILS.page,
23
+ CANCEL_RP_DETAILS.uri,
24
+ expect.any(Function),
25
+ expect.any(Function),
26
+ expect.any(Function)
27
+ )
28
+ })
29
+ })
30
+ })
31
+
32
+ it('calls addLanguageCodeToUri with request object', async () => {
33
+ jest.isolateModules(() => {
34
+ require('../route.js')
35
+ })
36
+ const [[, , , completion]] = pageRoute.mock.calls
37
+ const sampleRequest = Symbol('sample request')
38
+ completion(sampleRequest)
39
+ expect(addLanguageCodeToUri).toHaveBeenCalledWith(sampleRequest, expect.anything())
40
+ })
41
+
42
+ it('calls addLanguageCodeToUri with CANCEL_RP_AUTHENTICATE uri', () => {
43
+ jest.isolateModules(() => {
44
+ require('../route.js')
45
+ })
46
+ const [[, , , completion]] = pageRoute.mock.calls
47
+ completion({})
48
+ expect(addLanguageCodeToUri).toHaveBeenCalledWith(expect.anything(), CANCEL_RP_CONFIRM.uri)
49
+ })
50
+
51
+ it('returns the value of addLanguageCodeToUri', () => {
52
+ jest.isolateModules(() => {
53
+ require('../route.js')
54
+ })
55
+ const [[, , , completion]] = pageRoute.mock.calls
56
+ const expectedCompletionRedirect = Symbol('expected-completion-redirect')
57
+ addLanguageCodeToUri.mockReturnValueOnce(expectedCompletionRedirect)
58
+
59
+ const completionRedirect = completion({})
60
+
61
+ expect(completionRedirect).toBe(expectedCompletionRedirect)
62
+ })
63
+
64
+ const getSampleCatalog = () => ({
65
+ rp_cancel_details_licence_holder: 'Licence holder',
66
+ rp_cancel_details_licence_type: 'Licence type',
67
+ rp_cancel_details_payment_card: 'Payment card',
68
+ rp_cancel_details_last_purchased: 'Last purchased',
69
+ rp_cancel_details_licence_valid_until: 'Valid until'
70
+ })
71
+
72
+ const getSamplePermission = () => ({
73
+ permission: {
74
+ licensee: {
75
+ firstName: 'John',
76
+ lastName: 'Smith'
77
+ },
78
+ permit: {
79
+ description: 'Salmon and sea trout'
80
+ },
81
+ endDate: '01-01-2026',
82
+ referenceNumber: 'abc123'
83
+ },
84
+ recurringPayment: {
85
+ lastDigitsCardNumbers: 1234
86
+ }
87
+ })
88
+
89
+ const createMockRequest = ({ currentPermission = getSamplePermission(), catalog = getSampleCatalog() } = {}) => ({
90
+ cache: () => ({
91
+ helpers: {
92
+ transaction: {
93
+ getCurrentPermission: jest.fn().mockResolvedValue(currentPermission)
94
+ }
95
+ }
96
+ }),
97
+ i18n: {
98
+ getCatalog: () => catalog
99
+ }
100
+ })
101
+
102
+ describe('getData', () => {
103
+ beforeEach(() => {
104
+ jest.clearAllMocks()
105
+ })
106
+
107
+ it('returns request.i18n.getCatalog()', async () => {
108
+ const mssgs = getSampleCatalog()
109
+ const mockRequest = createMockRequest({ catalog: mssgs })
110
+
111
+ const result = await getData(mockRequest)
112
+
113
+ expect(result.mssgs).toEqual(mssgs)
114
+ })
115
+
116
+ it('returns summaryTable with expected data', async () => {
117
+ const mssgs = getSampleCatalog()
118
+ const mockRequest = createMockRequest({ catalog: mssgs })
119
+
120
+ const result = await getData(mockRequest)
121
+
122
+ expect(result.summaryTable).toEqual([
123
+ { key: { text: mssgs.rp_cancel_details_licence_holder }, value: { text: 'John Smith' } },
124
+ { key: { text: mssgs.rp_cancel_details_licence_type }, value: { text: 'Salmon and sea trout' } },
125
+ { key: { text: mssgs.rp_cancel_details_payment_card }, value: { text: 1234 } },
126
+ { key: { text: mssgs.rp_cancel_details_last_purchased }, value: { text: 'abc123' } },
127
+ { key: { text: mssgs.rp_cancel_details_licence_valid_until }, value: { text: '01-01-2026' } }
128
+ ])
129
+ })
130
+ })
131
+ })
@@ -2,22 +2,70 @@
2
2
 
3
3
  {% from "button/macro.njk" import govukButton %}
4
4
  {% from "page-title.njk" import pageTitle %}
5
+ {% from "fieldset/macro.njk" import govukFieldset %}
6
+ {% from "summary-list/macro.njk" import govukSummaryList %}
7
+ {% from "details/macro.njk" import govukDetails %}
5
8
 
6
9
  {% set title = mssgs.rp_cancel_details_title %}
7
10
  {% block pageTitle %}{{ pageTitle(title, error, mssgs) }}{% endblock %}
8
11
 
12
+ {% set licenseDetailsSummary %}
13
+ {% set legendHtml %}
14
+ <span class="govuk-caption-l">{{ mssgs.cancel_rp_identify_caption }}</span>
15
+ {{ title }}
16
+ {% endset %}
17
+
18
+ {% call govukFieldset({
19
+ legend: {
20
+ html: legendHtml,
21
+ classes: "govuk-fieldset__legend--l govuk-!-margin-bottom-3",
22
+ isPageHeading: true
23
+ }
24
+ }) %}
25
+ {{ govukSummaryList({
26
+ classes: 'licence-summary-list',
27
+ rows: data.summaryTable
28
+ }) }}
29
+ {% endcall %}
30
+ {% endset -%}
31
+
32
+ {% set detailsHtml %}
33
+ <p class="govuk-body">
34
+ {{ mssgs.refund_bulletpoint_1_2 }}
35
+ </p>
36
+ <p class="govuk-body">
37
+ {{ mssgs.refund_bulletpoint_1_3 }}
38
+ </p>
39
+ <p class="govuk-body">
40
+ {{ mssgs.refund_bulletpoint_1_4 }}
41
+ </p>
42
+ <p class="govuk-body govuk-!-margin-bottom-0">
43
+ <a class="govuk-link" href="https://www.gov.uk/call-charges" target="_blank" rel="noreferrer noopener">
44
+ {{ mssgs.refund_bulletpoint_1_5 }}
45
+ </a>
46
+ </p>
47
+ {% endset %}
48
+
9
49
  {% block content %}
10
50
  <div class="govuk-grid-row">
11
51
  <div class="govuk-grid-column-two-thirds">
12
- <form method="post" class="govuk-!-margin-bottom-6">
13
- {{ govukButton({
14
- attributes: { id: 'continue' },
15
- preventDoubleClick: true,
16
- name: "continue",
17
- text: mssgs.continue,
18
- classes: "govuk-!-margin-top-1"
19
- }) }}
20
- {{ csrf() }}
52
+ <form method="post">
53
+ {{ licenseDetailsSummary | trim | safe }}
54
+
55
+ {{ govukButton({
56
+ attributes: { id: 'continue' },
57
+ preventDoubleClick: true,
58
+ name: "continue",
59
+ text: mssgs.confirm_continue,
60
+ classes: "govuk-!-margin-top-5"
61
+ }) }}
62
+
63
+ {{ govukDetails({
64
+ summaryText: mssgs.rp_cancel_details_summary_title,
65
+ html: detailsHtml
66
+ }) }}
67
+
68
+ {{ csrf() }}
21
69
  </form>
22
70
  </div>
23
71
  </div>
@@ -2,10 +2,31 @@ import pageRoute from '../../../../routes/page-route.js'
2
2
  import { CANCEL_RP_DETAILS, CANCEL_RP_CONFIRM } from '../../../../uri.js'
3
3
  import { addLanguageCodeToUri } from '../../../../processors/uri-helper.js'
4
4
 
5
+ const getLicenseeDetailsSummaryRows = (currentPermission, mssgs) => [
6
+ {
7
+ key: { text: mssgs.rp_cancel_details_licence_holder },
8
+ value: { text: `${currentPermission.permission.licensee.firstName} ${currentPermission.permission.licensee.lastName}` }
9
+ },
10
+ { key: { text: mssgs.rp_cancel_details_licence_type }, value: { text: currentPermission.permission.permit.description } },
11
+ { key: { text: mssgs.rp_cancel_details_payment_card }, value: { text: currentPermission.recurringPayment.lastDigitsCardNumbers } },
12
+ { key: { text: mssgs.rp_cancel_details_last_purchased }, value: { text: currentPermission.permission.referenceNumber } },
13
+ { key: { text: mssgs.rp_cancel_details_licence_valid_until }, value: { text: currentPermission.permission.endDate } }
14
+ ]
15
+
16
+ export const getData = async request => {
17
+ const currentPermission = await request.cache().helpers.transaction.getCurrentPermission()
18
+ const mssgs = request.i18n.getCatalog()
19
+
20
+ return {
21
+ mssgs,
22
+ summaryTable: getLicenseeDetailsSummaryRows(currentPermission, mssgs)
23
+ }
24
+ }
25
+
5
26
  export default pageRoute(
6
27
  CANCEL_RP_DETAILS.page,
7
28
  CANCEL_RP_DETAILS.uri,
8
29
  () => {},
9
30
  request => addLanguageCodeToUri(request, CANCEL_RP_CONFIRM.uri),
10
- () => {}
31
+ getData
11
32
  )