@defra/forms-engine-plugin 4.8.0 → 4.9.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.
Files changed (43) hide show
  1. package/.server/server/forms/payment-v2-test.yaml +341 -0
  2. package/.server/server/plugins/engine/components/PaymentField.d.ts +7 -0
  3. package/.server/server/plugins/engine/components/PaymentField.js +58 -6
  4. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  5. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +2 -0
  6. package/.server/server/plugins/engine/models/SummaryViewModel.js +2 -0
  7. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  8. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +24 -2
  9. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  10. package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +10 -0
  11. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +57 -13
  12. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  13. package/.server/server/plugins/engine/pageControllers/errors.d.ts +1 -1
  14. package/.server/server/plugins/engine/pageControllers/errors.js +2 -2
  15. package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -1
  16. package/.server/server/plugins/engine/routes/index.js +5 -2
  17. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  18. package/.server/server/plugins/engine/routes/payment.js +6 -1
  19. package/.server/server/plugins/engine/routes/payment.js.map +1 -1
  20. package/.server/server/plugins/engine/routes/payment.test.js +3 -3
  21. package/.server/server/plugins/engine/routes/payment.test.js.map +1 -1
  22. package/.server/server/plugins/engine/services/localFormsService.js +6 -0
  23. package/.server/server/plugins/engine/services/localFormsService.js.map +1 -1
  24. package/.server/server/plugins/engine/views/summary.html +2 -1
  25. package/.server/server/plugins/payment/service.d.ts +2 -1
  26. package/.server/server/plugins/payment/service.js +11 -3
  27. package/.server/server/plugins/payment/service.js.map +1 -1
  28. package/.server/server/plugins/payment/types.d.ts +4 -0
  29. package/.server/server/plugins/payment/types.js +1 -0
  30. package/.server/server/plugins/payment/types.js.map +1 -1
  31. package/package.json +2 -2
  32. package/src/server/forms/payment-v2-test.yaml +341 -0
  33. package/src/server/plugins/engine/components/PaymentField.ts +70 -6
  34. package/src/server/plugins/engine/models/SummaryViewModel.ts +2 -0
  35. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +32 -1
  36. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +99 -17
  37. package/src/server/plugins/engine/pageControllers/errors.ts +2 -2
  38. package/src/server/plugins/engine/routes/index.ts +9 -2
  39. package/src/server/plugins/engine/routes/payment.js +7 -1
  40. package/src/server/plugins/engine/services/localFormsService.js +7 -0
  41. package/src/server/plugins/engine/views/summary.html +2 -1
  42. package/src/server/plugins/payment/service.js +13 -3
  43. package/src/server/plugins/payment/types.js +1 -0
@@ -0,0 +1,341 @@
1
+ ---
2
+ # Based on "Apply for a lock and weir fishing permit" production form.
3
+ # Extended with duration and site access to demonstrate complex compound conditions.
4
+ # Pricing matrix: duration x permit type x site access
5
+ schema: 2
6
+ name: Apply for a lock and weir fishing permit (v2 payment test)
7
+ engine: V2
8
+ declaration: I apply for permission to fish at the sites listed on this application for the duration of the permit, subject to the normal closed seasons.
9
+ startPage: '/fishing-sites'
10
+ options:
11
+ showReferenceNumber: true
12
+ pages:
13
+ - title: Fishing sites
14
+ path: '/fishing-sites'
15
+ components:
16
+ - id: '1fb84634-86f2-477b-affa-c7ace61aec26'
17
+ type: Markdown
18
+ content: "The fishing sites include:\n\n* Buscot\n* Grafton\n* Rushey\n* Sandford\n* Abingdon\n* Benson\n* Goring\n* Hurley\n* Bell Weir\n* Molesey"
19
+ options: {}
20
+ schema: {}
21
+ name: fishingSites
22
+ next: []
23
+ - title: Contact details
24
+ path: '/contact-details'
25
+ components:
26
+ - id: '3598ed25-1b9a-4ce6-8432-5676063b96ec'
27
+ type: TextField
28
+ title: What is your full name?
29
+ name: fullName
30
+ shortDescription: Full name
31
+ options:
32
+ required: true
33
+ schema: {}
34
+ - id: '9c4f0158-8f87-4cbd-a3a1-960166f015e4'
35
+ type: TelephoneNumberField
36
+ title: What is your phone number?
37
+ name: phoneNumber
38
+ shortDescription: Phone number
39
+ options:
40
+ required: true
41
+ schema: {}
42
+ - id: '9b83dc1e-e385-4cd3-b642-dcc247f3fc89'
43
+ type: EmailAddressField
44
+ title: What is your email address?
45
+ name: emailAddress
46
+ shortDescription: Email address
47
+ options:
48
+ required: true
49
+ next: []
50
+ - title: ''
51
+ path: '/rod-licence-number'
52
+ components:
53
+ - id: '22405838-becd-48ab-b984-c3c886822412'
54
+ type: TextField
55
+ title: What is your rod licence number (current or previous)?
56
+ name: rodLicenceNumber
57
+ shortDescription: Rod licence number
58
+ hint: The permit must be used in conjunction with a valid rod licence.
59
+ options:
60
+ required: true
61
+ schema: {}
62
+ next: []
63
+ - title: ''
64
+ path: '/permit-duration'
65
+ components:
66
+ - id: 'a1a1a1a1-1111-4aaa-aaaa-000000000001'
67
+ type: RadiosField
68
+ title: What duration of permit do you need?
69
+ name: permitDuration
70
+ shortDescription: Permit duration
71
+ options:
72
+ required: true
73
+ list: 'b1b1b1b1-1111-4bbb-bbbb-000000000001'
74
+ next: []
75
+ - title: ''
76
+ path: '/what-kind-of-permit-do-you-require'
77
+ components:
78
+ - id: 'f7663ac4-61e7-4b64-a157-70f002818493'
79
+ type: RadiosField
80
+ title: What kind of permit do you require?
81
+ name: permitType
82
+ shortDescription: Permit type
83
+ options:
84
+ required: true
85
+ list: 'aac4ee00-fb82-4a37-88ad-b9e10ded92e9'
86
+ next: []
87
+ - title: ''
88
+ path: '/site-access'
89
+ condition: 'c1c1c1c1-6666-4ccc-cccc-000000000005'
90
+ components:
91
+ - id: 'a1a1a1a1-1111-4aaa-aaaa-000000000003'
92
+ type: RadiosField
93
+ title: Which site access do you need?
94
+ name: siteAccess
95
+ shortDescription: Site access
96
+ hint: Single site permits are valid for one named site only.
97
+ options:
98
+ required: true
99
+ list: 'b1b1b1b1-1111-4bbb-bbbb-000000000002'
100
+ next: []
101
+ - title: ''
102
+ path: '/payment-required'
103
+ components:
104
+ - id: '6522e3f7-f414-42e5-9dbf-84e5868fbbd3'
105
+ type: PaymentField
106
+ title: Payment required
107
+ name: fishingPermitPayment
108
+ options:
109
+ required: true
110
+ amount: 0
111
+ description: Lock and weir fishing permit
112
+ emailField: emailAddress
113
+ conditionalAmounts:
114
+ # === 3-way: 12-month + type + site (most specific first) ===
115
+ - condition: 'd1d1d1d1-8888-4ddd-dddd-000000000001'
116
+ amount: 38
117
+ - condition: 'd1d1d1d1-8888-4ddd-dddd-000000000002'
118
+ amount: 25
119
+ - condition: 'd1d1d1d1-8888-4ddd-dddd-000000000003'
120
+ amount: 24
121
+ - condition: 'd1d1d1d1-8888-4ddd-dddd-000000000004'
122
+ amount: 16
123
+ # === 2-way: 8-day + type (site doesn't matter) ===
124
+ - condition: 'd1d1d1d1-8888-4ddd-dddd-000000000005'
125
+ amount: 12
126
+ - condition: 'd1d1d1d1-8888-4ddd-dddd-000000000006'
127
+ amount: 8
128
+ # === Simple: 1-day flat rate ===
129
+ - condition: 'c1c1c1c1-6666-4ccc-cccc-000000000003'
130
+ amount: 5
131
+ # === Simple: Junior free ===
132
+ - condition: 'c1c1c1c1-6666-4ccc-cccc-000000000004'
133
+ amount: 0
134
+ next: []
135
+ - title: ''
136
+ path: '/summary'
137
+ controller: SummaryPageController
138
+ conditions:
139
+ # =================================================================
140
+ # Simple atomic conditions
141
+ # =================================================================
142
+ - id: 'c1c1c1c1-6666-4ccc-cccc-000000000001'
143
+ displayName: Is 12-month permit
144
+ items:
145
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000001'
146
+ componentId: 'a1a1a1a1-1111-4aaa-aaaa-000000000001'
147
+ operator: is
148
+ type: ListItemRef
149
+ value:
150
+ itemId: 'f1f1f1f1-1111-4fff-ffff-000000000001'
151
+ listId: 'b1b1b1b1-1111-4bbb-bbbb-000000000001'
152
+ - id: 'c1c1c1c1-6666-4ccc-cccc-000000000002'
153
+ displayName: Is 8-day permit
154
+ items:
155
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000002'
156
+ componentId: 'a1a1a1a1-1111-4aaa-aaaa-000000000001'
157
+ operator: is
158
+ type: ListItemRef
159
+ value:
160
+ itemId: 'f1f1f1f1-1111-4fff-ffff-000000000002'
161
+ listId: 'b1b1b1b1-1111-4bbb-bbbb-000000000001'
162
+ - id: 'c1c1c1c1-6666-4ccc-cccc-000000000003'
163
+ displayName: Is 1-day permit
164
+ items:
165
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000003'
166
+ componentId: 'a1a1a1a1-1111-4aaa-aaaa-000000000001'
167
+ operator: is
168
+ type: ListItemRef
169
+ value:
170
+ itemId: 'f1f1f1f1-1111-4fff-ffff-000000000003'
171
+ listId: 'b1b1b1b1-1111-4bbb-bbbb-000000000001'
172
+ - id: 'c1c1c1c1-6666-4ccc-cccc-000000000004'
173
+ displayName: Is Junior permit type
174
+ items:
175
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000004'
176
+ componentId: 'f7663ac4-61e7-4b64-a157-70f002818493'
177
+ operator: is
178
+ type: ListItemRef
179
+ value:
180
+ itemId: 'f1f1f1f1-2222-4fff-ffff-000000000003'
181
+ listId: 'aac4ee00-fb82-4a37-88ad-b9e10ded92e9'
182
+ - id: 'dcaa3b3d-5cbc-4be9-b5ce-b2be5c72ccd1'
183
+ displayName: Is Adult permit type
184
+ items:
185
+ - id: '13b5e86b-2eb5-4a6c-8ab6-9fdc35907f18'
186
+ componentId: 'f7663ac4-61e7-4b64-a157-70f002818493'
187
+ operator: is
188
+ type: ListItemRef
189
+ value:
190
+ itemId: 'b6034236-63cf-44af-bb6c-d5d4d3825973'
191
+ listId: 'aac4ee00-fb82-4a37-88ad-b9e10ded92e9'
192
+ - id: 'c5177318-61ec-44ec-b5c2-18c1be1f1e42'
193
+ displayName: Is Concession permit type
194
+ items:
195
+ - id: 'fb75f1be-d471-4020-ac13-f7a2246078bb'
196
+ componentId: 'f7663ac4-61e7-4b64-a157-70f002818493'
197
+ operator: is
198
+ type: ListItemRef
199
+ value:
200
+ itemId: '86baf02e-0db7-4fad-8fce-fa9a30c1b7f0'
201
+ listId: 'aac4ee00-fb82-4a37-88ad-b9e10ded92e9'
202
+ - id: 'c1c1c1c1-6666-4ccc-cccc-000000000007'
203
+ displayName: Is all sites access
204
+ items:
205
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000007'
206
+ componentId: 'a1a1a1a1-1111-4aaa-aaaa-000000000003'
207
+ operator: is
208
+ type: ListItemRef
209
+ value:
210
+ itemId: 'f1f1f1f1-3333-4fff-ffff-000000000001'
211
+ listId: 'b1b1b1b1-1111-4bbb-bbbb-000000000002'
212
+ - id: 'c1c1c1c1-6666-4ccc-cccc-000000000008'
213
+ displayName: Is single site access
214
+ items:
215
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000008'
216
+ componentId: 'a1a1a1a1-1111-4aaa-aaaa-000000000003'
217
+ operator: is
218
+ type: ListItemRef
219
+ value:
220
+ itemId: 'f1f1f1f1-3333-4fff-ffff-000000000002'
221
+ listId: 'b1b1b1b1-1111-4bbb-bbbb-000000000002'
222
+ # =================================================================
223
+ # Page visibility: site access only shown for 12-month permits
224
+ # =================================================================
225
+ - id: 'c1c1c1c1-6666-4ccc-cccc-000000000005'
226
+ displayName: Is 12-month (show site access page)
227
+ items:
228
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000005'
229
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000001'
230
+ # =================================================================
231
+ # 2-way compound AND: duration + type
232
+ # =================================================================
233
+ - id: 'c1c1c1c1-6666-4ccc-cccc-000000000010'
234
+ displayName: 12-month AND Adult
235
+ coordinator: and
236
+ items:
237
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000010'
238
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000001'
239
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000011'
240
+ conditionId: 'dcaa3b3d-5cbc-4be9-b5ce-b2be5c72ccd1'
241
+ - id: 'c1c1c1c1-6666-4ccc-cccc-000000000011'
242
+ displayName: 12-month AND Concession
243
+ coordinator: and
244
+ items:
245
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000012'
246
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000001'
247
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000013'
248
+ conditionId: 'c5177318-61ec-44ec-b5c2-18c1be1f1e42'
249
+ - id: 'd1d1d1d1-8888-4ddd-dddd-000000000005'
250
+ displayName: 8-day AND Adult
251
+ coordinator: and
252
+ items:
253
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000014'
254
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000002'
255
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000015'
256
+ conditionId: 'dcaa3b3d-5cbc-4be9-b5ce-b2be5c72ccd1'
257
+ - id: 'd1d1d1d1-8888-4ddd-dddd-000000000006'
258
+ displayName: 8-day AND Concession
259
+ coordinator: and
260
+ items:
261
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000016'
262
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000002'
263
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000017'
264
+ conditionId: 'c5177318-61ec-44ec-b5c2-18c1be1f1e42'
265
+ # =================================================================
266
+ # 3-way compound AND: duration + type + site (chains 2-way refs)
267
+ # Same pattern as "Bats chargeable use" in protected species form
268
+ # =================================================================
269
+ - id: 'd1d1d1d1-8888-4ddd-dddd-000000000001'
270
+ displayName: 12-month AND Adult AND All sites
271
+ coordinator: and
272
+ items:
273
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000020'
274
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000010'
275
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000021'
276
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000007'
277
+ - id: 'd1d1d1d1-8888-4ddd-dddd-000000000002'
278
+ displayName: 12-month AND Adult AND Single site
279
+ coordinator: and
280
+ items:
281
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000022'
282
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000010'
283
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000023'
284
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000008'
285
+ - id: 'd1d1d1d1-8888-4ddd-dddd-000000000003'
286
+ displayName: 12-month AND Concession AND All sites
287
+ coordinator: and
288
+ items:
289
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000024'
290
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000011'
291
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000025'
292
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000007'
293
+ - id: 'd1d1d1d1-8888-4ddd-dddd-000000000004'
294
+ displayName: 12-month AND Concession AND Single site
295
+ coordinator: and
296
+ items:
297
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000026'
298
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000011'
299
+ - id: 'e1e1e1e1-7777-4eee-eeee-000000000027'
300
+ conditionId: 'c1c1c1c1-6666-4ccc-cccc-000000000008'
301
+ sections: []
302
+ lists:
303
+ - id: 'b1b1b1b1-1111-4bbb-bbbb-000000000001'
304
+ title: Permit durations
305
+ name: permitDurations
306
+ type: string
307
+ items:
308
+ - id: 'f1f1f1f1-1111-4fff-ffff-000000000001'
309
+ text: 12-month permit
310
+ value: 12-month
311
+ - id: 'f1f1f1f1-1111-4fff-ffff-000000000002'
312
+ text: 8-day permit
313
+ value: 8-day
314
+ - id: 'f1f1f1f1-1111-4fff-ffff-000000000003'
315
+ text: 1-day permit
316
+ value: 1-day
317
+ - id: 'aac4ee00-fb82-4a37-88ad-b9e10ded92e9'
318
+ title: Permit types
319
+ name: permitTypes
320
+ type: string
321
+ items:
322
+ - id: 'b6034236-63cf-44af-bb6c-d5d4d3825973'
323
+ text: Adult
324
+ value: Adult
325
+ - id: '86baf02e-0db7-4fad-8fce-fa9a30c1b7f0'
326
+ text: Concession (65+ or disabled)
327
+ value: Concession
328
+ - id: 'f1f1f1f1-2222-4fff-ffff-000000000003'
329
+ text: Junior (13-16 years)
330
+ value: Junior
331
+ - id: 'b1b1b1b1-1111-4bbb-bbbb-000000000002'
332
+ title: Site access
333
+ name: siteAccess
334
+ type: string
335
+ items:
336
+ - id: 'f1f1f1f1-3333-4fff-ffff-000000000001'
337
+ text: All sites
338
+ value: all-sites
339
+ - id: 'f1f1f1f1-3333-4fff-ffff-000000000002'
340
+ text: Single site
341
+ value: single-site
@@ -2,6 +2,7 @@ import { type FormMetadata, type PaymentFieldComponent } from '@defra/forms-mode
2
2
  import { type ObjectSchema } from 'joi';
3
3
  import { FormComponent } from '../../../../server/plugins/engine/components/FormComponent.js';
4
4
  import { type PaymentState } from '../../../../server/plugins/engine/components/PaymentField.types.js';
5
+ import { type FormModel } from '../../../../server/plugins/engine/models/index.js';
5
6
  import { type FormContext, type FormRequestPayload, type FormResponseToolkit } from '../../../../server/plugins/engine/types/index.js';
6
7
  import { type ErrorMessageTemplateList, type FormPayload, type FormState, type FormStateValue, type FormSubmissionError, type FormSubmissionState, type PaymentExternalArgs } from '../../../../server/plugins/engine/types.js';
7
8
  export declare class PaymentField extends FormComponent {
@@ -89,6 +90,12 @@ export declare class PaymentField extends FormComponent {
89
90
  * Static version of getAllPossibleErrors that doesn't require a component instance.
90
91
  */
91
92
  static getAllPossibleErrors(): ErrorMessageTemplateList;
93
+ /**
94
+ * Resolves the payment amount from conditional amounts configuration.
95
+ * Evaluates conditions in order; first true condition wins.
96
+ * Falls back to the default options.amount.
97
+ */
98
+ static resolveAmount(options: PaymentFieldComponent['options'], model: FormModel, state: FormState): number;
92
99
  /**
93
100
  * Dispatcher for external redirect to GOV.UK Pay
94
101
  */
@@ -1,11 +1,13 @@
1
1
  import { randomUUID } from 'node:crypto';
2
2
  import { StatusCodes } from 'http-status-codes';
3
3
  import joi from 'joi';
4
+ import { createLogger } from "../../../common/helpers/logging/logger.js";
4
5
  import { COMPONENT_STATE_ERROR } from "../../../constants.js";
5
6
  import { FormComponent } from "./FormComponent.js";
6
7
  import { createError, getPluginOptions } from "../helpers.js";
7
8
  import { PaymentErrorTypes, PaymentPreAuthError, PaymentSubmissionError } from "../pageControllers/errors.js";
8
9
  import { createPaymentService, formatCurrency } from "../../payment/helper.js";
10
+ const logger = createLogger();
9
11
  export class PaymentField extends FormComponent {
10
12
  isAppendageStateSingleObject = true;
11
13
  constructor(def, props) {
@@ -51,7 +53,9 @@ export class PaymentField extends FormComponent {
51
53
  // Payload is pre-populated from state if a payment has already been made
52
54
  const paymentState = this.isPaymentState(payload[this.name]) ? payload[this.name] : undefined;
53
55
 
54
- // When user initially visits the payment page, there is no payment state yet so the amount is read form the form definition.
56
+ // Use payment state amount if pre-authorized, otherwise use default.
57
+ // The page controller overrides this with the resolved conditional amount
58
+ // using the full form state (which getViewModel doesn't have access to).
55
59
  const amount = paymentState?.amount ?? this.options.amount;
56
60
  return {
57
61
  ...viewModel,
@@ -112,6 +116,33 @@ export class PaymentField extends FormComponent {
112
116
  };
113
117
  }
114
118
 
119
+ /**
120
+ * Resolves the payment amount from conditional amounts configuration.
121
+ * Evaluates conditions in order; first true condition wins.
122
+ * Falls back to the default options.amount.
123
+ */
124
+ static resolveAmount(options, model, state) {
125
+ const {
126
+ conditionalAmounts
127
+ } = options;
128
+ if (!conditionalAmounts?.length) {
129
+ return options.amount;
130
+ }
131
+ for (const {
132
+ condition,
133
+ amount
134
+ } of conditionalAmounts) {
135
+ if (!model.conditions[condition]) {
136
+ logger.warn(`[payment] Condition '${condition}' not found in form conditions. Skipping.`);
137
+ continue;
138
+ }
139
+ if (model.conditions[condition].fn(state)) {
140
+ return amount;
141
+ }
142
+ }
143
+ return options.amount;
144
+ }
145
+
115
146
  /**
116
147
  * Dispatcher for external redirect to GOV.UK Pay
117
148
  */
@@ -138,16 +169,32 @@ export class PaymentField extends FormComponent {
138
169
  const paymentService = await createPaymentService(isLivePayment, formId, formsService);
139
170
  const uuid = randomUUID();
140
171
  const reference = state.$$__referenceNumber;
141
- const amount = options.amount;
172
+ const resolvedAmount = PaymentField.resolveAmount(options, model, state);
173
+
174
+ // Zero-amount safety net (page skip should prevent this, but defensive)
175
+ if (resolvedAmount === 0) {
176
+ return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER);
177
+ }
142
178
  const description = options.description;
143
179
  const slug = `/${model.basePath}`;
144
180
  const payCallbackUrl = `${baseUrl}/payment-callback?uuid=${uuid}`;
145
181
  const paymentPageUrl = args.sourceUrl;
146
- const amountInPence = Math.round(amount * 100);
182
+
183
+ // Prepopulate GOV.UK Pay email if emailField is configured.
184
+ // The referenced EmailAddressField validates with joi.string().email()
185
+ // at input time, so the value in state is already validated.
186
+ let prefilledEmail;
187
+ if (options.emailField) {
188
+ const emailValue = state[options.emailField];
189
+ if (typeof emailValue === 'string' && emailValue) {
190
+ prefilledEmail = emailValue;
191
+ }
192
+ }
193
+ const amountInPence = Math.round(resolvedAmount * 100);
147
194
  const payment = await paymentService.createPayment(amountInPence, description, payCallbackUrl, reference, isLivePayment, {
148
195
  formId,
149
196
  slug
150
- });
197
+ }, prefilledEmail);
151
198
  if (!payment) {
152
199
  const message = isLivePayment ? 'There is a problem and we cannot take a payment. Contact us (details in the footer of this form) or save your progress and return to the form later.' : 'Add a valid test API key before you can preview the payment journey.';
153
200
  const govukError = createError(componentName, message);
@@ -158,7 +205,7 @@ export class PaymentField extends FormComponent {
158
205
  uuid,
159
206
  formId,
160
207
  reference,
161
- amount,
208
+ amount: resolvedAmount,
162
209
  description,
163
210
  paymentId: payment.paymentId,
164
211
  componentName,
@@ -175,6 +222,11 @@ export class PaymentField extends FormComponent {
175
222
  * @see https://docs.payments.service.gov.uk/delayed_capture/#delay-taking-a-payment
176
223
  */
177
224
  async onSubmit(request, _metadata, context) {
225
+ // Zero-amount bypass — no capture needed
226
+ const resolvedAmount = PaymentField.resolveAmount(this.options, this.model, context.state);
227
+ if (resolvedAmount === 0) {
228
+ return;
229
+ }
178
230
  const paymentState = this.getPaymentStateFromState(context.state);
179
231
  if (!paymentState) {
180
232
  throw new PaymentPreAuthError(this, 'Complete the payment to continue', true, PaymentErrorTypes.PaymentIncomplete);
@@ -194,7 +246,7 @@ export class PaymentField extends FormComponent {
194
246
  * @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle
195
247
  */
196
248
  const status = await paymentService.getPaymentStatus(paymentId, isLivePayment);
197
- PaymentSubmissionError.checkPaymentAmount(status.amount, this.options.amount, this);
249
+ PaymentSubmissionError.checkPaymentAmount(status.amount, resolvedAmount, this);
198
250
  if (status.state.status === 'success') {
199
251
  await this.markPaymentCaptured(request, paymentState);
200
252
  return;
@@ -1 +1 @@
1
- {"version":3,"file":"PaymentField.js","names":["randomUUID","StatusCodes","joi","COMPONENT_STATE_ERROR","FormComponent","createError","getPluginOptions","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","createPaymentService","formatCurrency","PaymentField","isAppendageStateSingleObject","constructor","def","props","options","paymentStateSchema","object","paymentId","string","required","reference","amount","number","description","uuid","formId","isLivePayment","boolean","preAuth","status","valid","createdAt","isoDate","unknown","label","formSchema","stateSchema","getPaymentStateFromState","state","value","name","isPaymentState","undefined","getDisplayStringFromState","getViewModel","payload","errors","viewModel","paymentState","Array","isArray","isState","getFormValue","getContextValueFromState","getAllPossibleErrors","baseErrors","type","template","advancedSettingsErrors","dispatcher","request","h","args","componentName","component","model","controller","getState","baseUrl","server","summaryUrl","basePath","existingPaymentState","redirect","code","SEE_OTHER","isLive","isPreview","formsService","services","paymentService","$$__referenceNumber","slug","payCallbackUrl","paymentPageUrl","sourceUrl","amountInPence","Math","round","payment","createPayment","message","govukError","yar","flash","url","pathname","search","sessionData","returnUrl","failureUrl","set","paymentUrl","onSubmit","_metadata","context","PaymentIncomplete","capture","getPaymentStatus","checkPaymentAmount","markPaymentCaptured","PaymentExpired","captured","capturePayment","updatedState","Date","toISOString","page","currentState","mergeState"],"sources":["../../../../../src/server/plugins/engine/components/PaymentField.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\n\nimport {\n type FormMetadata,\n type PaymentFieldComponent\n} from '@defra/forms-model'\nimport { StatusCodes } from 'http-status-codes'\nimport joi, { type ObjectSchema } from 'joi'\n\nimport { COMPONENT_STATE_ERROR } from '~/src/server/constants.js'\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'\nimport {\n createError,\n getPluginOptions\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n type FormContext,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/plugins/engine/types/index.js'\nimport {\n type ErrorMessageTemplateList,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState,\n type PaymentExternalArgs\n} from '~/src/server/plugins/engine/types.js'\nimport {\n createPaymentService,\n formatCurrency\n} from '~/src/server/plugins/payment/helper.js'\n\nexport class PaymentField extends FormComponent {\n declare options: PaymentFieldComponent['options']\n declare formSchema: ObjectSchema\n declare stateSchema: ObjectSchema\n isAppendageStateSingleObject = true\n\n constructor(\n def: PaymentFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n this.options = def.options\n\n const paymentStateSchema = joi\n .object({\n paymentId: joi.string().required(),\n reference: joi.string().required(),\n amount: joi.number().required(),\n description: joi.string().required(),\n uuid: joi.string().uuid().required(),\n formId: joi.string().required(),\n isLivePayment: joi.boolean().required(),\n preAuth: joi\n .object({\n status: joi\n .string()\n .valid('success', 'failed', 'started')\n .required(),\n createdAt: joi.string().isoDate().required()\n })\n .required()\n })\n .unknown(true)\n .label(this.label)\n\n this.formSchema = paymentStateSchema\n // 'required()' forces the payment page to be invalid until we have valid payment state\n // i.e. the user will automatically be directed back to the payment page\n // if they attempt to access future pages when no payment entered yet\n this.stateSchema = paymentStateSchema.required()\n }\n\n /**\n * Gets the PaymentState from form submission state\n */\n getPaymentStateFromState(\n state: FormSubmissionState\n ): PaymentState | undefined {\n const value = state[this.name]\n return this.isPaymentState(value) ? value : undefined\n }\n\n getDisplayStringFromState(state: FormSubmissionState): string {\n const value = this.getPaymentStateFromState(state)\n\n if (!value) {\n return ''\n }\n\n return `${formatCurrency(value.amount)} - ${value.description}`\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n\n // Payload is pre-populated from state if a payment has already been made\n const paymentState = this.isPaymentState(payload[this.name] as unknown)\n ? (payload[this.name] as unknown as PaymentState)\n : undefined\n\n // When user initially visits the payment page, there is no payment state yet so the amount is read form the form definition.\n const amount = paymentState?.amount ?? this.options.amount\n\n return {\n ...viewModel,\n amount: formatCurrency(amount),\n description: this.options.description,\n paymentState\n }\n }\n\n /**\n * Type guard to check if value is PaymentState\n */\n isPaymentState(value: unknown): value is PaymentState {\n return PaymentField.isPaymentState(value)\n }\n\n /**\n * Static type guard to check if value is PaymentState\n */\n static isPaymentState(value: unknown): value is PaymentState {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false\n }\n\n const state = value as PaymentState\n return (\n typeof state.paymentId === 'string' &&\n typeof state.amount === 'number' &&\n typeof state.description === 'string'\n )\n }\n\n /**\n * Override base isState to validate PaymentState\n */\n isState(value?: FormStateValue | FormState): value is FormState {\n return this.isPaymentState(value)\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isPaymentState(value)\n ? (value as unknown as NonNullable<FormStateValue>)\n : undefined\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n return this.isPaymentState(state)\n ? `Reference: ${state.reference}\\nAmount: ${formatCurrency(state.amount)}`\n : ''\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return PaymentField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n {\n type: 'paymentRequired',\n template: 'Complete the payment to continue'\n }\n ],\n advancedSettingsErrors: []\n }\n }\n\n /**\n * Dispatcher for external redirect to GOV.UK Pay\n */\n static async dispatcher(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n args: PaymentExternalArgs\n ): Promise<unknown> {\n const { options, name: componentName } = args.component\n const { model } = args.controller\n\n const state = await args.controller.getState(request)\n const { baseUrl } = getPluginOptions(request.server)\n const summaryUrl = `${baseUrl}/${model.basePath}/summary`\n\n const existingPaymentState = state[componentName]\n if (\n PaymentField.isPaymentState(existingPaymentState) &&\n existingPaymentState.preAuth?.status === 'success'\n ) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const isLivePayment = args.isLive && !args.isPreview\n const formId = args.controller.model.formId\n const formsService = model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n const uuid = randomUUID()\n\n const reference = state.$$__referenceNumber as string\n const amount = options.amount\n\n const description = options.description\n\n const slug = `/${model.basePath}`\n\n const payCallbackUrl = `${baseUrl}/payment-callback?uuid=${uuid}`\n const paymentPageUrl = args.sourceUrl\n\n const amountInPence = Math.round(amount * 100)\n const payment = await paymentService.createPayment(\n amountInPence,\n description,\n payCallbackUrl,\n reference,\n isLivePayment,\n { formId, slug }\n )\n\n if (!payment) {\n const message = isLivePayment\n ? 'There is a problem and we cannot take a payment. Contact us (details in the footer of this form) or save your progress and return to the form later.'\n : 'Add a valid test API key before you can preview the payment journey.'\n const govukError = createError(componentName, message)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n return h\n .redirect(`${request.url.pathname}${request.url.search}`)\n .code(StatusCodes.SEE_OTHER)\n }\n\n const sessionData: PaymentSessionData = {\n uuid,\n formId,\n reference,\n amount,\n description,\n paymentId: payment.paymentId,\n componentName,\n returnUrl: summaryUrl,\n failureUrl: paymentPageUrl,\n isLivePayment\n }\n\n request.yar.set(`payment-${uuid}`, sessionData)\n\n return h.redirect(payment.paymentUrl).code(StatusCodes.SEE_OTHER)\n }\n\n /**\n * Called on form submission to capture the payment\n * @see https://docs.payments.service.gov.uk/delayed_capture/#delay-taking-a-payment\n */\n async onSubmit(\n request: FormRequestPayload,\n _metadata: FormMetadata,\n context: FormContext\n ): Promise<void> {\n const paymentState = this.getPaymentStateFromState(context.state)\n\n if (!paymentState) {\n throw new PaymentPreAuthError(\n this,\n 'Complete the payment to continue',\n true,\n PaymentErrorTypes.PaymentIncomplete\n )\n }\n\n if (paymentState.capture?.status === 'success') {\n return\n }\n\n const { paymentId, isLivePayment, formId } = paymentState\n const formsService = this.model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n /**\n * @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle\n */\n const status = await paymentService.getPaymentStatus(\n paymentId,\n isLivePayment\n )\n\n PaymentSubmissionError.checkPaymentAmount(\n status.amount,\n this.options.amount,\n this\n )\n\n if (status.state.status === 'success') {\n await this.markPaymentCaptured(request, paymentState)\n return\n }\n\n if (status.state.status !== 'capturable') {\n throw new PaymentPreAuthError(\n this,\n 'Your payment authorisation has expired. Please add your payment details again.',\n true,\n PaymentErrorTypes.PaymentExpired\n )\n }\n\n const captured = await paymentService.capturePayment(\n paymentId,\n status.amount\n )\n\n if (!captured) {\n throw new PaymentPreAuthError(\n this,\n 'There was a problem and your form was not submitted. Try submitting the form again.',\n false\n )\n }\n\n await this.markPaymentCaptured(request, paymentState)\n }\n\n /**\n * Updates payment state to mark capture as successful\n * This ensures we don't try to re-capture on submission retry\n */\n private async markPaymentCaptured(\n request: FormRequestPayload,\n paymentState: PaymentState\n ): Promise<void> {\n const updatedState: PaymentState = {\n ...paymentState,\n capture: {\n status: 'success',\n createdAt: new Date().toISOString()\n }\n }\n\n if (this.page) {\n const currentState = await this.page.getState(request)\n await this.page.mergeState(request, currentState, {\n [this.name]: updatedState\n })\n }\n }\n}\n\n/**\n * Session data stored when dispatching to GOV.UK Pay\n */\nexport interface PaymentSessionData {\n uuid: string\n formId: string\n reference: string\n amount: number\n description: string\n paymentId: string\n componentName: string\n returnUrl: string\n failureUrl: string\n isLivePayment: boolean\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"PaymentField.js","names":["randomUUID","StatusCodes","joi","createLogger","COMPONENT_STATE_ERROR","FormComponent","createError","getPluginOptions","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","createPaymentService","formatCurrency","logger","PaymentField","isAppendageStateSingleObject","constructor","def","props","options","paymentStateSchema","object","paymentId","string","required","reference","amount","number","description","uuid","formId","isLivePayment","boolean","preAuth","status","valid","createdAt","isoDate","unknown","label","formSchema","stateSchema","getPaymentStateFromState","state","value","name","isPaymentState","undefined","getDisplayStringFromState","getViewModel","payload","errors","viewModel","paymentState","Array","isArray","isState","getFormValue","getContextValueFromState","getAllPossibleErrors","baseErrors","type","template","advancedSettingsErrors","resolveAmount","model","conditionalAmounts","length","condition","conditions","warn","fn","dispatcher","request","h","args","componentName","component","controller","getState","baseUrl","server","summaryUrl","basePath","existingPaymentState","redirect","code","SEE_OTHER","isLive","isPreview","formsService","services","paymentService","$$__referenceNumber","resolvedAmount","slug","payCallbackUrl","paymentPageUrl","sourceUrl","prefilledEmail","emailField","emailValue","amountInPence","Math","round","payment","createPayment","message","govukError","yar","flash","url","pathname","search","sessionData","returnUrl","failureUrl","set","paymentUrl","onSubmit","_metadata","context","PaymentIncomplete","capture","getPaymentStatus","checkPaymentAmount","markPaymentCaptured","PaymentExpired","captured","capturePayment","updatedState","Date","toISOString","page","currentState","mergeState"],"sources":["../../../../../src/server/plugins/engine/components/PaymentField.ts"],"sourcesContent":["import { randomUUID } from 'node:crypto'\n\nimport {\n type FormMetadata,\n type PaymentFieldComponent\n} from '@defra/forms-model'\nimport { StatusCodes } from 'http-status-codes'\nimport joi, { type ObjectSchema } from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { COMPONENT_STATE_ERROR } from '~/src/server/constants.js'\nimport { FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\nimport { type PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'\nimport {\n createError,\n getPluginOptions\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport {\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n type FormContext,\n type FormRequestPayload,\n type FormResponseToolkit\n} from '~/src/server/plugins/engine/types/index.js'\nimport {\n type ErrorMessageTemplateList,\n type FormPayload,\n type FormState,\n type FormStateValue,\n type FormSubmissionError,\n type FormSubmissionState,\n type PaymentExternalArgs\n} from '~/src/server/plugins/engine/types.js'\nimport {\n createPaymentService,\n formatCurrency\n} from '~/src/server/plugins/payment/helper.js'\n\nconst logger = createLogger()\n\nexport class PaymentField extends FormComponent {\n declare options: PaymentFieldComponent['options']\n declare formSchema: ObjectSchema\n declare stateSchema: ObjectSchema\n isAppendageStateSingleObject = true\n\n constructor(\n def: PaymentFieldComponent,\n props: ConstructorParameters<typeof FormComponent>[1]\n ) {\n super(def, props)\n\n this.options = def.options\n\n const paymentStateSchema = joi\n .object({\n paymentId: joi.string().required(),\n reference: joi.string().required(),\n amount: joi.number().required(),\n description: joi.string().required(),\n uuid: joi.string().uuid().required(),\n formId: joi.string().required(),\n isLivePayment: joi.boolean().required(),\n preAuth: joi\n .object({\n status: joi\n .string()\n .valid('success', 'failed', 'started')\n .required(),\n createdAt: joi.string().isoDate().required()\n })\n .required()\n })\n .unknown(true)\n .label(this.label)\n\n this.formSchema = paymentStateSchema\n // 'required()' forces the payment page to be invalid until we have valid payment state\n // i.e. the user will automatically be directed back to the payment page\n // if they attempt to access future pages when no payment entered yet\n this.stateSchema = paymentStateSchema.required()\n }\n\n /**\n * Gets the PaymentState from form submission state\n */\n getPaymentStateFromState(\n state: FormSubmissionState\n ): PaymentState | undefined {\n const value = state[this.name]\n return this.isPaymentState(value) ? value : undefined\n }\n\n getDisplayStringFromState(state: FormSubmissionState): string {\n const value = this.getPaymentStateFromState(state)\n\n if (!value) {\n return ''\n }\n\n return `${formatCurrency(value.amount)} - ${value.description}`\n }\n\n getViewModel(payload: FormPayload, errors?: FormSubmissionError[]) {\n const viewModel = super.getViewModel(payload, errors)\n\n // Payload is pre-populated from state if a payment has already been made\n const paymentState = this.isPaymentState(payload[this.name] as unknown)\n ? (payload[this.name] as unknown as PaymentState)\n : undefined\n\n // Use payment state amount if pre-authorized, otherwise use default.\n // The page controller overrides this with the resolved conditional amount\n // using the full form state (which getViewModel doesn't have access to).\n const amount = paymentState?.amount ?? this.options.amount\n\n return {\n ...viewModel,\n amount: formatCurrency(amount),\n description: this.options.description,\n paymentState\n }\n }\n\n /**\n * Type guard to check if value is PaymentState\n */\n isPaymentState(value: unknown): value is PaymentState {\n return PaymentField.isPaymentState(value)\n }\n\n /**\n * Static type guard to check if value is PaymentState\n */\n static isPaymentState(value: unknown): value is PaymentState {\n if (!value || typeof value !== 'object' || Array.isArray(value)) {\n return false\n }\n\n const state = value as PaymentState\n return (\n typeof state.paymentId === 'string' &&\n typeof state.amount === 'number' &&\n typeof state.description === 'string'\n )\n }\n\n /**\n * Override base isState to validate PaymentState\n */\n isState(value?: FormStateValue | FormState): value is FormState {\n return this.isPaymentState(value)\n }\n\n getFormValue(value?: FormStateValue | FormState) {\n return this.isPaymentState(value)\n ? (value as unknown as NonNullable<FormStateValue>)\n : undefined\n }\n\n getContextValueFromState(state: FormSubmissionState) {\n return this.isPaymentState(state)\n ? `Reference: ${state.reference}\\nAmount: ${formatCurrency(state.amount)}`\n : ''\n }\n\n /**\n * For error preview page that shows all possible errors on a component\n */\n getAllPossibleErrors(): ErrorMessageTemplateList {\n return PaymentField.getAllPossibleErrors()\n }\n\n /**\n * Static version of getAllPossibleErrors that doesn't require a component instance.\n */\n static getAllPossibleErrors(): ErrorMessageTemplateList {\n return {\n baseErrors: [\n {\n type: 'paymentRequired',\n template: 'Complete the payment to continue'\n }\n ],\n advancedSettingsErrors: []\n }\n }\n\n /**\n * Resolves the payment amount from conditional amounts configuration.\n * Evaluates conditions in order; first true condition wins.\n * Falls back to the default options.amount.\n */\n static resolveAmount(\n options: PaymentFieldComponent['options'],\n model: FormModel,\n state: FormState\n ): number {\n const { conditionalAmounts } = options\n\n if (!conditionalAmounts?.length) {\n return options.amount\n }\n\n for (const { condition, amount } of conditionalAmounts) {\n if (!model.conditions[condition]) {\n logger.warn(\n `[payment] Condition '${condition}' not found in form conditions. Skipping.`\n )\n continue\n }\n if (model.conditions[condition].fn(state)) {\n return amount\n }\n }\n\n return options.amount\n }\n\n /**\n * Dispatcher for external redirect to GOV.UK Pay\n */\n static async dispatcher(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n args: PaymentExternalArgs\n ): Promise<unknown> {\n const { options, name: componentName } = args.component\n const { model } = args.controller\n\n const state = await args.controller.getState(request)\n const { baseUrl } = getPluginOptions(request.server)\n const summaryUrl = `${baseUrl}/${model.basePath}/summary`\n\n const existingPaymentState = state[componentName]\n if (\n PaymentField.isPaymentState(existingPaymentState) &&\n existingPaymentState.preAuth?.status === 'success'\n ) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const isLivePayment = args.isLive && !args.isPreview\n const formId = args.controller.model.formId\n const formsService = model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n const uuid = randomUUID()\n\n const reference = state.$$__referenceNumber as string\n const resolvedAmount = PaymentField.resolveAmount(options, model, state)\n\n // Zero-amount safety net (page skip should prevent this, but defensive)\n if (resolvedAmount === 0) {\n return h.redirect(summaryUrl).code(StatusCodes.SEE_OTHER)\n }\n\n const description = options.description\n\n const slug = `/${model.basePath}`\n\n const payCallbackUrl = `${baseUrl}/payment-callback?uuid=${uuid}`\n const paymentPageUrl = args.sourceUrl\n\n // Prepopulate GOV.UK Pay email if emailField is configured.\n // The referenced EmailAddressField validates with joi.string().email()\n // at input time, so the value in state is already validated.\n let prefilledEmail: string | undefined\n if (options.emailField) {\n const emailValue = state[options.emailField]\n if (typeof emailValue === 'string' && emailValue) {\n prefilledEmail = emailValue\n }\n }\n\n const amountInPence = Math.round(resolvedAmount * 100)\n const payment = await paymentService.createPayment(\n amountInPence,\n description,\n payCallbackUrl,\n reference,\n isLivePayment,\n { formId, slug },\n prefilledEmail\n )\n\n if (!payment) {\n const message = isLivePayment\n ? 'There is a problem and we cannot take a payment. Contact us (details in the footer of this form) or save your progress and return to the form later.'\n : 'Add a valid test API key before you can preview the payment journey.'\n const govukError = createError(componentName, message)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n return h\n .redirect(`${request.url.pathname}${request.url.search}`)\n .code(StatusCodes.SEE_OTHER)\n }\n\n const sessionData: PaymentSessionData = {\n uuid,\n formId,\n reference,\n amount: resolvedAmount,\n description,\n paymentId: payment.paymentId,\n componentName,\n returnUrl: summaryUrl,\n failureUrl: paymentPageUrl,\n isLivePayment\n }\n\n request.yar.set(`payment-${uuid}`, sessionData)\n\n return h.redirect(payment.paymentUrl).code(StatusCodes.SEE_OTHER)\n }\n\n /**\n * Called on form submission to capture the payment\n * @see https://docs.payments.service.gov.uk/delayed_capture/#delay-taking-a-payment\n */\n async onSubmit(\n request: FormRequestPayload,\n _metadata: FormMetadata,\n context: FormContext\n ): Promise<void> {\n // Zero-amount bypass — no capture needed\n const resolvedAmount = PaymentField.resolveAmount(\n this.options,\n this.model,\n context.state\n )\n if (resolvedAmount === 0) {\n return\n }\n\n const paymentState = this.getPaymentStateFromState(context.state)\n\n if (!paymentState) {\n throw new PaymentPreAuthError(\n this,\n 'Complete the payment to continue',\n true,\n PaymentErrorTypes.PaymentIncomplete\n )\n }\n\n if (paymentState.capture?.status === 'success') {\n return\n }\n\n const { paymentId, isLivePayment, formId } = paymentState\n const formsService = this.model.services.formsService\n const paymentService = await createPaymentService(\n isLivePayment,\n formId,\n formsService\n )\n\n /**\n * @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle\n */\n const status = await paymentService.getPaymentStatus(\n paymentId,\n isLivePayment\n )\n\n PaymentSubmissionError.checkPaymentAmount(\n status.amount,\n resolvedAmount,\n this\n )\n\n if (status.state.status === 'success') {\n await this.markPaymentCaptured(request, paymentState)\n return\n }\n\n if (status.state.status !== 'capturable') {\n throw new PaymentPreAuthError(\n this,\n 'Your payment authorisation has expired. Please add your payment details again.',\n true,\n PaymentErrorTypes.PaymentExpired\n )\n }\n\n const captured = await paymentService.capturePayment(\n paymentId,\n status.amount\n )\n\n if (!captured) {\n throw new PaymentPreAuthError(\n this,\n 'There was a problem and your form was not submitted. Try submitting the form again.',\n false\n )\n }\n\n await this.markPaymentCaptured(request, paymentState)\n }\n\n /**\n * Updates payment state to mark capture as successful\n * This ensures we don't try to re-capture on submission retry\n */\n private async markPaymentCaptured(\n request: FormRequestPayload,\n paymentState: PaymentState\n ): Promise<void> {\n const updatedState: PaymentState = {\n ...paymentState,\n capture: {\n status: 'success',\n createdAt: new Date().toISOString()\n }\n }\n\n if (this.page) {\n const currentState = await this.page.getState(request)\n await this.page.mergeState(request, currentState, {\n [this.name]: updatedState\n })\n }\n }\n}\n\n/**\n * Session data stored when dispatching to GOV.UK Pay\n */\nexport interface PaymentSessionData {\n uuid: string\n formId: string\n reference: string\n amount: number\n description: string\n paymentId: string\n componentName: string\n returnUrl: string\n failureUrl: string\n isLivePayment: boolean\n}\n"],"mappings":"AAAA,SAASA,UAAU,QAAQ,aAAa;AAMxC,SAASC,WAAW,QAAQ,mBAAmB;AAC/C,OAAOC,GAAG,MAA6B,KAAK;AAE5C,SAASC,YAAY;AACrB,SAASC,qBAAqB;AAC9B,SAASC,aAAa;AAEtB,SACEC,WAAW,EACXC,gBAAgB;AAGlB,SACEC,iBAAiB,EACjBC,mBAAmB,EACnBC,sBAAsB;AAgBxB,SACEC,oBAAoB,EACpBC,cAAc;AAGhB,MAAMC,MAAM,GAAGV,YAAY,CAAC,CAAC;AAE7B,OAAO,MAAMW,YAAY,SAAST,aAAa,CAAC;EAI9CU,4BAA4B,GAAG,IAAI;EAEnCC,WAAWA,CACTC,GAA0B,EAC1BC,KAAqD,EACrD;IACA,KAAK,CAACD,GAAG,EAAEC,KAAK,CAAC;IAEjB,IAAI,CAACC,OAAO,GAAGF,GAAG,CAACE,OAAO;IAE1B,MAAMC,kBAAkB,GAAGlB,GAAG,CAC3BmB,MAAM,CAAC;MACNC,SAAS,EAAEpB,GAAG,CAACqB,MAAM,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC;MAClCC,SAAS,EAAEvB,GAAG,CAACqB,MAAM,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC;MAClCE,MAAM,EAAExB,GAAG,CAACyB,MAAM,CAAC,CAAC,CAACH,QAAQ,CAAC,CAAC;MAC/BI,WAAW,EAAE1B,GAAG,CAACqB,MAAM,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC;MACpCK,IAAI,EAAE3B,GAAG,CAACqB,MAAM,CAAC,CAAC,CAACM,IAAI,CAAC,CAAC,CAACL,QAAQ,CAAC,CAAC;MACpCM,MAAM,EAAE5B,GAAG,CAACqB,MAAM,CAAC,CAAC,CAACC,QAAQ,CAAC,CAAC;MAC/BO,aAAa,EAAE7B,GAAG,CAAC8B,OAAO,CAAC,CAAC,CAACR,QAAQ,CAAC,CAAC;MACvCS,OAAO,EAAE/B,GAAG,CACTmB,MAAM,CAAC;QACNa,MAAM,EAAEhC,GAAG,CACRqB,MAAM,CAAC,CAAC,CACRY,KAAK,CAAC,SAAS,EAAE,QAAQ,EAAE,SAAS,CAAC,CACrCX,QAAQ,CAAC,CAAC;QACbY,SAAS,EAAElC,GAAG,CAACqB,MAAM,CAAC,CAAC,CAACc,OAAO,CAAC,CAAC,CAACb,QAAQ,CAAC;MAC7C,CAAC,CAAC,CACDA,QAAQ,CAAC;IACd,CAAC,CAAC,CACDc,OAAO,CAAC,IAAI,CAAC,CACbC,KAAK,CAAC,IAAI,CAACA,KAAK,CAAC;IAEpB,IAAI,CAACC,UAAU,GAAGpB,kBAAkB;IACpC;IACA;IACA;IACA,IAAI,CAACqB,WAAW,GAAGrB,kBAAkB,CAACI,QAAQ,CAAC,CAAC;EAClD;;EAEA;AACF;AACA;EACEkB,wBAAwBA,CACtBC,KAA0B,EACA;IAC1B,MAAMC,KAAK,GAAGD,KAAK,CAAC,IAAI,CAACE,IAAI,CAAC;IAC9B,OAAO,IAAI,CAACC,cAAc,CAACF,KAAK,CAAC,GAAGA,KAAK,GAAGG,SAAS;EACvD;EAEAC,yBAAyBA,CAACL,KAA0B,EAAU;IAC5D,MAAMC,KAAK,GAAG,IAAI,CAACF,wBAAwB,CAACC,KAAK,CAAC;IAElD,IAAI,CAACC,KAAK,EAAE;MACV,OAAO,EAAE;IACX;IAEA,OAAO,GAAGhC,cAAc,CAACgC,KAAK,CAAClB,MAAM,CAAC,MAAMkB,KAAK,CAAChB,WAAW,EAAE;EACjE;EAEAqB,YAAYA,CAACC,OAAoB,EAAEC,MAA8B,EAAE;IACjE,MAAMC,SAAS,GAAG,KAAK,CAACH,YAAY,CAACC,OAAO,EAAEC,MAAM,CAAC;;IAErD;IACA,MAAME,YAAY,GAAG,IAAI,CAACP,cAAc,CAACI,OAAO,CAAC,IAAI,CAACL,IAAI,CAAY,CAAC,GAClEK,OAAO,CAAC,IAAI,CAACL,IAAI,CAAC,GACnBE,SAAS;;IAEb;IACA;IACA;IACA,MAAMrB,MAAM,GAAG2B,YAAY,EAAE3B,MAAM,IAAI,IAAI,CAACP,OAAO,CAACO,MAAM;IAE1D,OAAO;MACL,GAAG0B,SAAS;MACZ1B,MAAM,EAAEd,cAAc,CAACc,MAAM,CAAC;MAC9BE,WAAW,EAAE,IAAI,CAACT,OAAO,CAACS,WAAW;MACrCyB;IACF,CAAC;EACH;;EAEA;AACF;AACA;EACEP,cAAcA,CAACF,KAAc,EAAyB;IACpD,OAAO9B,YAAY,CAACgC,cAAc,CAACF,KAAK,CAAC;EAC3C;;EAEA;AACF;AACA;EACE,OAAOE,cAAcA,CAACF,KAAc,EAAyB;IAC3D,IAAI,CAACA,KAAK,IAAI,OAAOA,KAAK,KAAK,QAAQ,IAAIU,KAAK,CAACC,OAAO,CAACX,KAAK,CAAC,EAAE;MAC/D,OAAO,KAAK;IACd;IAEA,MAAMD,KAAK,GAAGC,KAAqB;IACnC,OACE,OAAOD,KAAK,CAACrB,SAAS,KAAK,QAAQ,IACnC,OAAOqB,KAAK,CAACjB,MAAM,KAAK,QAAQ,IAChC,OAAOiB,KAAK,CAACf,WAAW,KAAK,QAAQ;EAEzC;;EAEA;AACF;AACA;EACE4B,OAAOA,CAACZ,KAAkC,EAAsB;IAC9D,OAAO,IAAI,CAACE,cAAc,CAACF,KAAK,CAAC;EACnC;EAEAa,YAAYA,CAACb,KAAkC,EAAE;IAC/C,OAAO,IAAI,CAACE,cAAc,CAACF,KAAK,CAAC,GAC5BA,KAAK,GACNG,SAAS;EACf;EAEAW,wBAAwBA,CAACf,KAA0B,EAAE;IACnD,OAAO,IAAI,CAACG,cAAc,CAACH,KAAK,CAAC,GAC7B,cAAcA,KAAK,CAAClB,SAAS,aAAab,cAAc,CAAC+B,KAAK,CAACjB,MAAM,CAAC,EAAE,GACxE,EAAE;EACR;;EAEA;AACF;AACA;EACEiC,oBAAoBA,CAAA,EAA6B;IAC/C,OAAO7C,YAAY,CAAC6C,oBAAoB,CAAC,CAAC;EAC5C;;EAEA;AACF;AACA;EACE,OAAOA,oBAAoBA,CAAA,EAA6B;IACtD,OAAO;MACLC,UAAU,EAAE,CACV;QACEC,IAAI,EAAE,iBAAiB;QACvBC,QAAQ,EAAE;MACZ,CAAC,CACF;MACDC,sBAAsB,EAAE;IAC1B,CAAC;EACH;;EAEA;AACF;AACA;AACA;AACA;EACE,OAAOC,aAAaA,CAClB7C,OAAyC,EACzC8C,KAAgB,EAChBtB,KAAgB,EACR;IACR,MAAM;MAAEuB;IAAmB,CAAC,GAAG/C,OAAO;IAEtC,IAAI,CAAC+C,kBAAkB,EAAEC,MAAM,EAAE;MAC/B,OAAOhD,OAAO,CAACO,MAAM;IACvB;IAEA,KAAK,MAAM;MAAE0C,SAAS;MAAE1C;IAAO,CAAC,IAAIwC,kBAAkB,EAAE;MACtD,IAAI,CAACD,KAAK,CAACI,UAAU,CAACD,SAAS,CAAC,EAAE;QAChCvD,MAAM,CAACyD,IAAI,CACT,wBAAwBF,SAAS,2CACnC,CAAC;QACD;MACF;MACA,IAAIH,KAAK,CAACI,UAAU,CAACD,SAAS,CAAC,CAACG,EAAE,CAAC5B,KAAK,CAAC,EAAE;QACzC,OAAOjB,MAAM;MACf;IACF;IAEA,OAAOP,OAAO,CAACO,MAAM;EACvB;;EAEA;AACF;AACA;EACE,aAAa8C,UAAUA,CACrBC,OAA2B,EAC3BC,CAAsB,EACtBC,IAAyB,EACP;IAClB,MAAM;MAAExD,OAAO;MAAE0B,IAAI,EAAE+B;IAAc,CAAC,GAAGD,IAAI,CAACE,SAAS;IACvD,MAAM;MAAEZ;IAAM,CAAC,GAAGU,IAAI,CAACG,UAAU;IAEjC,MAAMnC,KAAK,GAAG,MAAMgC,IAAI,CAACG,UAAU,CAACC,QAAQ,CAACN,OAAO,CAAC;IACrD,MAAM;MAAEO;IAAQ,CAAC,GAAGzE,gBAAgB,CAACkE,OAAO,CAACQ,MAAM,CAAC;IACpD,MAAMC,UAAU,GAAG,GAAGF,OAAO,IAAIf,KAAK,CAACkB,QAAQ,UAAU;IAEzD,MAAMC,oBAAoB,GAAGzC,KAAK,CAACiC,aAAa,CAAC;IACjD,IACE9D,YAAY,CAACgC,cAAc,CAACsC,oBAAoB,CAAC,IACjDA,oBAAoB,CAACnD,OAAO,EAAEC,MAAM,KAAK,SAAS,EAClD;MACA,OAAOwC,CAAC,CAACW,QAAQ,CAACH,UAAU,CAAC,CAACI,IAAI,CAACrF,WAAW,CAACsF,SAAS,CAAC;IAC3D;IAEA,MAAMxD,aAAa,GAAG4C,IAAI,CAACa,MAAM,IAAI,CAACb,IAAI,CAACc,SAAS;IACpD,MAAM3D,MAAM,GAAG6C,IAAI,CAACG,UAAU,CAACb,KAAK,CAACnC,MAAM;IAC3C,MAAM4D,YAAY,GAAGzB,KAAK,CAAC0B,QAAQ,CAACD,YAAY;IAChD,MAAME,cAAc,GAAG,MAAMjF,oBAAoB,CAC/CoB,aAAa,EACbD,MAAM,EACN4D,YACF,CAAC;IAED,MAAM7D,IAAI,GAAG7B,UAAU,CAAC,CAAC;IAEzB,MAAMyB,SAAS,GAAGkB,KAAK,CAACkD,mBAA6B;IACrD,MAAMC,cAAc,GAAGhF,YAAY,CAACkD,aAAa,CAAC7C,OAAO,EAAE8C,KAAK,EAAEtB,KAAK,CAAC;;IAExE;IACA,IAAImD,cAAc,KAAK,CAAC,EAAE;MACxB,OAAOpB,CAAC,CAACW,QAAQ,CAACH,UAAU,CAAC,CAACI,IAAI,CAACrF,WAAW,CAACsF,SAAS,CAAC;IAC3D;IAEA,MAAM3D,WAAW,GAAGT,OAAO,CAACS,WAAW;IAEvC,MAAMmE,IAAI,GAAG,IAAI9B,KAAK,CAACkB,QAAQ,EAAE;IAEjC,MAAMa,cAAc,GAAG,GAAGhB,OAAO,0BAA0BnD,IAAI,EAAE;IACjE,MAAMoE,cAAc,GAAGtB,IAAI,CAACuB,SAAS;;IAErC;IACA;IACA;IACA,IAAIC,cAAkC;IACtC,IAAIhF,OAAO,CAACiF,UAAU,EAAE;MACtB,MAAMC,UAAU,GAAG1D,KAAK,CAACxB,OAAO,CAACiF,UAAU,CAAC;MAC5C,IAAI,OAAOC,UAAU,KAAK,QAAQ,IAAIA,UAAU,EAAE;QAChDF,cAAc,GAAGE,UAAU;MAC7B;IACF;IAEA,MAAMC,aAAa,GAAGC,IAAI,CAACC,KAAK,CAACV,cAAc,GAAG,GAAG,CAAC;IACtD,MAAMW,OAAO,GAAG,MAAMb,cAAc,CAACc,aAAa,CAChDJ,aAAa,EACb1E,WAAW,EACXoE,cAAc,EACdvE,SAAS,EACTM,aAAa,EACb;MAAED,MAAM;MAAEiE;IAAK,CAAC,EAChBI,cACF,CAAC;IAED,IAAI,CAACM,OAAO,EAAE;MACZ,MAAME,OAAO,GAAG5E,aAAa,GACzB,sJAAsJ,GACtJ,sEAAsE;MAC1E,MAAM6E,UAAU,GAAGtG,WAAW,CAACsE,aAAa,EAAE+B,OAAO,CAAC;MACtDlC,OAAO,CAACoC,GAAG,CAACC,KAAK,CAAC1G,qBAAqB,EAAEwG,UAAU,EAAE,IAAI,CAAC;MAC1D,OAAOlC,CAAC,CACLW,QAAQ,CAAC,GAAGZ,OAAO,CAACsC,GAAG,CAACC,QAAQ,GAAGvC,OAAO,CAACsC,GAAG,CAACE,MAAM,EAAE,CAAC,CACxD3B,IAAI,CAACrF,WAAW,CAACsF,SAAS,CAAC;IAChC;IAEA,MAAM2B,WAA+B,GAAG;MACtCrF,IAAI;MACJC,MAAM;MACNL,SAAS;MACTC,MAAM,EAAEoE,cAAc;MACtBlE,WAAW;MACXN,SAAS,EAAEmF,OAAO,CAACnF,SAAS;MAC5BsD,aAAa;MACbuC,SAAS,EAAEjC,UAAU;MACrBkC,UAAU,EAAEnB,cAAc;MAC1BlE;IACF,CAAC;IAED0C,OAAO,CAACoC,GAAG,CAACQ,GAAG,CAAC,WAAWxF,IAAI,EAAE,EAAEqF,WAAW,CAAC;IAE/C,OAAOxC,CAAC,CAACW,QAAQ,CAACoB,OAAO,CAACa,UAAU,CAAC,CAAChC,IAAI,CAACrF,WAAW,CAACsF,SAAS,CAAC;EACnE;;EAEA;AACF;AACA;AACA;EACE,MAAMgC,QAAQA,CACZ9C,OAA2B,EAC3B+C,SAAuB,EACvBC,OAAoB,EACL;IACf;IACA,MAAM3B,cAAc,GAAGhF,YAAY,CAACkD,aAAa,CAC/C,IAAI,CAAC7C,OAAO,EACZ,IAAI,CAAC8C,KAAK,EACVwD,OAAO,CAAC9E,KACV,CAAC;IACD,IAAImD,cAAc,KAAK,CAAC,EAAE;MACxB;IACF;IAEA,MAAMzC,YAAY,GAAG,IAAI,CAACX,wBAAwB,CAAC+E,OAAO,CAAC9E,KAAK,CAAC;IAEjE,IAAI,CAACU,YAAY,EAAE;MACjB,MAAM,IAAI5C,mBAAmB,CAC3B,IAAI,EACJ,kCAAkC,EAClC,IAAI,EACJD,iBAAiB,CAACkH,iBACpB,CAAC;IACH;IAEA,IAAIrE,YAAY,CAACsE,OAAO,EAAEzF,MAAM,KAAK,SAAS,EAAE;MAC9C;IACF;IAEA,MAAM;MAAEZ,SAAS;MAAES,aAAa;MAAED;IAAO,CAAC,GAAGuB,YAAY;IACzD,MAAMqC,YAAY,GAAG,IAAI,CAACzB,KAAK,CAAC0B,QAAQ,CAACD,YAAY;IACrD,MAAME,cAAc,GAAG,MAAMjF,oBAAoB,CAC/CoB,aAAa,EACbD,MAAM,EACN4D,YACF,CAAC;;IAED;AACJ;AACA;IACI,MAAMxD,MAAM,GAAG,MAAM0D,cAAc,CAACgC,gBAAgB,CAClDtG,SAAS,EACTS,aACF,CAAC;IAEDrB,sBAAsB,CAACmH,kBAAkB,CACvC3F,MAAM,CAACR,MAAM,EACboE,cAAc,EACd,IACF,CAAC;IAED,IAAI5D,MAAM,CAACS,KAAK,CAACT,MAAM,KAAK,SAAS,EAAE;MACrC,MAAM,IAAI,CAAC4F,mBAAmB,CAACrD,OAAO,EAAEpB,YAAY,CAAC;MACrD;IACF;IAEA,IAAInB,MAAM,CAACS,KAAK,CAACT,MAAM,KAAK,YAAY,EAAE;MACxC,MAAM,IAAIzB,mBAAmB,CAC3B,IAAI,EACJ,gFAAgF,EAChF,IAAI,EACJD,iBAAiB,CAACuH,cACpB,CAAC;IACH;IAEA,MAAMC,QAAQ,GAAG,MAAMpC,cAAc,CAACqC,cAAc,CAClD3G,SAAS,EACTY,MAAM,CAACR,MACT,CAAC;IAED,IAAI,CAACsG,QAAQ,EAAE;MACb,MAAM,IAAIvH,mBAAmB,CAC3B,IAAI,EACJ,qFAAqF,EACrF,KACF,CAAC;IACH;IAEA,MAAM,IAAI,CAACqH,mBAAmB,CAACrD,OAAO,EAAEpB,YAAY,CAAC;EACvD;;EAEA;AACF;AACA;AACA;EACE,MAAcyE,mBAAmBA,CAC/BrD,OAA2B,EAC3BpB,YAA0B,EACX;IACf,MAAM6E,YAA0B,GAAG;MACjC,GAAG7E,YAAY;MACfsE,OAAO,EAAE;QACPzF,MAAM,EAAE,SAAS;QACjBE,SAAS,EAAE,IAAI+F,IAAI,CAAC,CAAC,CAACC,WAAW,CAAC;MACpC;IACF,CAAC;IAED,IAAI,IAAI,CAACC,IAAI,EAAE;MACb,MAAMC,YAAY,GAAG,MAAM,IAAI,CAACD,IAAI,CAACtD,QAAQ,CAACN,OAAO,CAAC;MACtD,MAAM,IAAI,CAAC4D,IAAI,CAACE,UAAU,CAAC9D,OAAO,EAAE6D,YAAY,EAAE;QAChD,CAAC,IAAI,CAACzF,IAAI,GAAGqF;MACf,CAAC,CAAC;IACJ;EACF;AACF;;AAEA;AACA;AACA","ignoreList":[]}
@@ -25,6 +25,8 @@ export declare class SummaryViewModel {
25
25
  allowSaveAndExit: boolean;
26
26
  paymentState?: PaymentState;
27
27
  paymentDetails?: CheckAnswers;
28
+ paymentRequired?: boolean;
29
+ paymentPreAuthorized?: boolean;
28
30
  constructor(request: FormContextRequest, page: PageControllerClass, context: FormContext);
29
31
  private summaryDetails;
30
32
  }
@@ -26,6 +26,8 @@ export class SummaryViewModel {
26
26
  allowSaveAndExit = false;
27
27
  paymentState;
28
28
  paymentDetails;
29
+ paymentRequired;
30
+ paymentPreAuthorized;
29
31
  constructor(request, page, context) {
30
32
  const {
31
33
  model