@defra/forms-engine-plugin 4.0.43 → 4.0.44
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/.public/stylesheets/application.min.css +1 -1
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/stylesheets/_payment-field.scss +8 -0
- package/.server/client/stylesheets/application.scss +2 -0
- package/.server/index.js +3 -1
- package/.server/index.js.map +1 -1
- package/.server/server/constants.d.ts +1 -0
- package/.server/server/constants.js +1 -0
- package/.server/server/constants.js.map +1 -1
- package/.server/server/forms/payment-test.yaml +42 -0
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +14 -0
- package/.server/server/plugins/engine/components/FormComponent.d.ts +1 -0
- package/.server/server/plugins/engine/components/FormComponent.js +1 -0
- package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/PaymentField.d.ts +135 -0
- package/.server/server/plugins/engine/components/PaymentField.js +228 -0
- package/.server/server/plugins/engine/components/PaymentField.js.map +1 -0
- package/.server/server/plugins/engine/components/PaymentField.types.d.ts +21 -0
- package/.server/server/plugins/engine/components/PaymentField.types.js +2 -0
- package/.server/server/plugins/engine/components/PaymentField.types.js.map +1 -0
- package/.server/server/plugins/engine/components/UkAddressField.d.ts +1 -1
- package/.server/server/plugins/engine/components/UkAddressField.js +3 -1
- package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
- package/.server/server/plugins/engine/components/helpers/components.js +3 -0
- package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
- package/.server/server/plugins/engine/components/index.d.ts +1 -0
- package/.server/server/plugins/engine/components/index.js +1 -0
- package/.server/server/plugins/engine/components/index.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +1 -0
- package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +3 -0
- package/.server/server/plugins/engine/models/SummaryViewModel.js +7 -0
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js +34 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +22 -0
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js +43 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +29 -8
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +2 -0
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +17 -0
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +173 -51
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/errors.d.ts +31 -0
- package/.server/server/plugins/engine/pageControllers/errors.js +59 -2
- package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +27 -0
- package/.server/server/plugins/engine/pageControllers/helpers/submission.js +77 -0
- package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -0
- package/.server/server/plugins/engine/plugin.js +4 -1
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/routes/index.js +8 -4
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/.server/server/plugins/engine/routes/payment-helper.d.ts +14 -0
- package/.server/server/plugins/engine/routes/payment-helper.js +41 -0
- package/.server/server/plugins/engine/routes/payment-helper.js.map +1 -0
- package/.server/server/plugins/engine/routes/payment-helper.test.js +81 -0
- package/.server/server/plugins/engine/routes/payment-helper.test.js.map +1 -0
- package/.server/server/plugins/engine/routes/payment.d.ts +8 -0
- package/.server/server/plugins/engine/routes/payment.js +140 -0
- package/.server/server/plugins/engine/routes/payment.js.map +1 -0
- package/.server/server/plugins/engine/routes/payment.test.js +187 -0
- package/.server/server/plugins/engine/routes/payment.test.js.map +1 -0
- package/.server/server/plugins/engine/services/localFormsService.js +6 -0
- package/.server/server/plugins/engine/services/localFormsService.js.map +1 -1
- package/.server/server/plugins/engine/types/schema.js +7 -0
- package/.server/server/plugins/engine/types/schema.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +19 -1
- package/.server/server/plugins/engine/types.js +4 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/validationHelpers.d.ts +1 -1
- package/.server/server/plugins/engine/validationHelpers.js.map +1 -1
- package/.server/server/plugins/engine/views/components/paymentfield.html +42 -0
- package/.server/server/plugins/engine/views/index.html +9 -1
- package/.server/server/plugins/engine/views/partials/form.html +20 -5
- package/.server/server/plugins/engine/views/summary.html +17 -1
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/plugins/payment/helper.d.ts +30 -0
- package/.server/server/plugins/payment/helper.js +49 -0
- package/.server/server/plugins/payment/helper.js.map +1 -0
- package/.server/server/plugins/payment/helper.test.js +37 -0
- package/.server/server/plugins/payment/helper.test.js.map +1 -0
- package/.server/server/plugins/payment/service.d.ts +40 -0
- package/.server/server/plugins/payment/service.js +129 -0
- package/.server/server/plugins/payment/service.js.map +1 -0
- package/.server/server/plugins/payment/service.test.js +162 -0
- package/.server/server/plugins/payment/service.test.js.map +1 -0
- package/.server/server/plugins/payment/types.d.ts +172 -0
- package/.server/server/plugins/payment/types.js +78 -0
- package/.server/server/plugins/payment/types.js.map +1 -0
- package/.server/server/types.d.ts +2 -0
- package/.server/server/types.js.map +1 -1
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/README.md +12 -9
- package/package.json +2 -2
- package/src/client/stylesheets/_payment-field.scss +8 -0
- package/src/client/stylesheets/application.scss +2 -0
- package/src/index.ts +5 -1
- package/src/server/constants.js +1 -0
- package/src/server/forms/payment-test.yaml +42 -0
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +14 -0
- package/src/server/plugins/engine/components/FormComponent.ts +1 -0
- package/src/server/plugins/engine/components/PaymentField.test.ts +611 -0
- package/src/server/plugins/engine/components/PaymentField.ts +367 -0
- package/src/server/plugins/engine/components/PaymentField.types.ts +21 -0
- package/src/server/plugins/engine/components/UkAddressField.ts +2 -1
- package/src/server/plugins/engine/components/helpers/components.ts +5 -0
- package/src/server/plugins/engine/components/index.ts +1 -0
- package/src/server/plugins/engine/models/SummaryViewModel.ts +8 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.payment.test.ts +147 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +105 -103
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +61 -2
- package/src/server/plugins/engine/outputFormatters/machine/v2.payment.test.ts +115 -0
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +60 -1
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +32 -6
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +247 -72
- package/src/server/plugins/engine/pageControllers/errors.test.ts +13 -1
- package/src/server/plugins/engine/pageControllers/errors.ts +79 -4
- package/src/server/plugins/engine/pageControllers/helpers/submission.test.ts +299 -0
- package/src/server/plugins/engine/pageControllers/helpers/submission.ts +110 -0
- package/src/server/plugins/engine/plugin.ts +11 -6
- package/src/server/plugins/engine/routes/index.ts +17 -16
- package/src/server/plugins/engine/routes/payment-helper.js +39 -0
- package/src/server/plugins/engine/routes/payment-helper.test.js +90 -0
- package/src/server/plugins/engine/routes/payment.js +151 -0
- package/src/server/plugins/engine/routes/payment.test.js +180 -0
- package/src/server/plugins/engine/services/localFormsService.js +7 -0
- package/src/server/plugins/engine/types/schema.ts +9 -0
- package/src/server/plugins/engine/types.ts +24 -1
- package/src/server/plugins/engine/validationHelpers.ts +1 -1
- package/src/server/plugins/engine/views/components/paymentfield.html +42 -0
- package/src/server/plugins/engine/views/index.html +9 -1
- package/src/server/plugins/engine/views/partials/form.html +20 -5
- package/src/server/plugins/engine/views/summary.html +17 -1
- package/src/server/plugins/payment/helper.js +56 -0
- package/src/server/plugins/payment/helper.test.js +52 -0
- package/src/server/plugins/payment/service.js +171 -0
- package/src/server/plugins/payment/service.test.js +205 -0
- package/src/server/plugins/payment/types.js +77 -0
- package/src/server/types.ts +2 -0
- package/src/typings/hapi/index.d.ts +1 -0
|
@@ -1,12 +1,14 @@
|
|
|
1
1
|
import { hasComponentsEvenIfNoNext } from '@defra/forms-model';
|
|
2
2
|
import Boom from '@hapi/boom';
|
|
3
|
-
import { COMPONENT_STATE_ERROR } from "../../../constants.js";
|
|
3
|
+
import { COMPONENT_STATE_ERROR, PAYMENT_EXPIRED_NOTIFICATION } from "../../../constants.js";
|
|
4
4
|
import { ComponentCollection } from "../components/ComponentCollection.js";
|
|
5
|
-
import {
|
|
5
|
+
import { PaymentField } from "../components/PaymentField.js";
|
|
6
6
|
import { checkEmailAddressForLiveFormSubmission, checkFormStatus, createError, getCacheService } from "../helpers.js";
|
|
7
7
|
import { SummaryViewModel } from "../models/index.js";
|
|
8
8
|
import { QuestionPageController } from "./QuestionPageController.js";
|
|
9
|
-
import { InvalidComponentStateError } from "./errors.js";
|
|
9
|
+
import { InvalidComponentStateError, PaymentErrorTypes, PaymentPreAuthError, PaymentSubmissionError } from "./errors.js";
|
|
10
|
+
import { buildMainRecords, buildRepeaterRecords } from "./helpers/submission.js";
|
|
11
|
+
import { DEFAULT_PAYMENT_HELP_URL, formatPaymentAmount, formatPaymentDate } from "../../payment/helper.js";
|
|
10
12
|
import { FormAction } from "../../../routes/types.js";
|
|
11
13
|
export class SummaryPageController extends QuestionPageController {
|
|
12
14
|
allowSaveAndExit = true;
|
|
@@ -32,12 +34,18 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
32
34
|
} = request;
|
|
33
35
|
const {
|
|
34
36
|
payload,
|
|
35
|
-
errors
|
|
37
|
+
errors,
|
|
38
|
+
state
|
|
36
39
|
} = context;
|
|
40
|
+
const paymentField = context.relevantPages.flatMap(page => page.collection.fields).find(field => field instanceof PaymentField);
|
|
41
|
+
if (paymentField) {
|
|
42
|
+
const paymentState = paymentField.getPaymentStateFromState(state);
|
|
43
|
+
if (paymentState) {
|
|
44
|
+
viewModel.paymentState = paymentState;
|
|
45
|
+
viewModel.paymentDetails = this.buildPaymentDetails(paymentField, paymentState);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
37
48
|
const components = this.collection.getViewModel(payload, errors, query);
|
|
38
|
-
|
|
39
|
-
// We already figure these out in the base page controller. Take them and apply them to our page-specific model.
|
|
40
|
-
// This is a stop-gap until we can add proper inheritance in place.
|
|
41
49
|
viewModel.backLink = this.getBackLink(request, context);
|
|
42
50
|
viewModel.feedbackLink = this.feedbackLink;
|
|
43
51
|
viewModel.phaseTag = this.phaseTag;
|
|
@@ -46,6 +54,48 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
46
54
|
viewModel.errors = errors;
|
|
47
55
|
return viewModel;
|
|
48
56
|
}
|
|
57
|
+
buildPaymentDetails(paymentField, paymentState) {
|
|
58
|
+
const rows = [{
|
|
59
|
+
key: {
|
|
60
|
+
text: 'Payment for'
|
|
61
|
+
},
|
|
62
|
+
value: {
|
|
63
|
+
text: paymentState.description
|
|
64
|
+
}
|
|
65
|
+
}, {
|
|
66
|
+
key: {
|
|
67
|
+
text: 'Total amount'
|
|
68
|
+
},
|
|
69
|
+
value: {
|
|
70
|
+
text: formatPaymentAmount(paymentState.amount)
|
|
71
|
+
}
|
|
72
|
+
}, {
|
|
73
|
+
key: {
|
|
74
|
+
text: 'Reference'
|
|
75
|
+
},
|
|
76
|
+
value: {
|
|
77
|
+
text: paymentState.reference
|
|
78
|
+
}
|
|
79
|
+
}];
|
|
80
|
+
if (paymentState.preAuth?.createdAt) {
|
|
81
|
+
rows.push({
|
|
82
|
+
key: {
|
|
83
|
+
text: 'Date of payment'
|
|
84
|
+
},
|
|
85
|
+
value: {
|
|
86
|
+
text: formatPaymentDate(paymentState.preAuth.createdAt)
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
return {
|
|
91
|
+
title: {
|
|
92
|
+
text: 'Payment details'
|
|
93
|
+
},
|
|
94
|
+
summaryList: {
|
|
95
|
+
rows
|
|
96
|
+
}
|
|
97
|
+
};
|
|
98
|
+
}
|
|
49
99
|
|
|
50
100
|
/**
|
|
51
101
|
* Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,
|
|
@@ -67,7 +117,6 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
67
117
|
*/
|
|
68
118
|
makePostRouteHandler() {
|
|
69
119
|
return async (request, context, h) => {
|
|
70
|
-
// Check if this is a save-and-exit action
|
|
71
120
|
const {
|
|
72
121
|
action
|
|
73
122
|
} = request.payload;
|
|
@@ -91,8 +140,6 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
91
140
|
const {
|
|
92
141
|
getFormMetadata
|
|
93
142
|
} = formsService;
|
|
94
|
-
|
|
95
|
-
// Get the form metadata using the `slug` param
|
|
96
143
|
const formMetadata = await getFormMetadata(params.slug);
|
|
97
144
|
const {
|
|
98
145
|
notificationEmail
|
|
@@ -101,20 +148,12 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
101
148
|
isPreview
|
|
102
149
|
} = checkFormStatus(request.params);
|
|
103
150
|
checkEmailAddressForLiveFormSubmission(notificationEmail, isPreview);
|
|
104
|
-
|
|
105
|
-
// Send submission email
|
|
106
151
|
if (notificationEmail) {
|
|
107
152
|
const viewModel = this.getSummaryViewModel(request, context);
|
|
108
153
|
try {
|
|
109
154
|
await submitForm(context, formMetadata, request, viewModel, model, notificationEmail, formMetadata);
|
|
110
155
|
} catch (error) {
|
|
111
|
-
|
|
112
|
-
const govukError = createError(error.component.name, error.userMessage);
|
|
113
|
-
request.yar.flash(COMPONENT_STATE_ERROR, govukError, true);
|
|
114
|
-
await cacheService.resetComponentStates(request, error.getStateKeys());
|
|
115
|
-
return this.proceed(request, h, error.component.page?.path);
|
|
116
|
-
}
|
|
117
|
-
throw error;
|
|
156
|
+
return this.handleSubmissionError(error, request, h);
|
|
118
157
|
}
|
|
119
158
|
}
|
|
120
159
|
await cacheService.setConfirmationState(request, {
|
|
@@ -122,11 +161,65 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
122
161
|
formId: context.state.formId,
|
|
123
162
|
referenceNumber: context.referenceNumber
|
|
124
163
|
});
|
|
125
|
-
|
|
126
|
-
// Clear all form data
|
|
127
164
|
await cacheService.clearState(request);
|
|
128
165
|
return this.proceed(request, h, this.getStatusPath());
|
|
129
166
|
}
|
|
167
|
+
|
|
168
|
+
/**
|
|
169
|
+
* Handles errors during form submission
|
|
170
|
+
*/
|
|
171
|
+
async handleSubmissionError(error, request, h) {
|
|
172
|
+
if (error instanceof InvalidComponentStateError) {
|
|
173
|
+
return this.handleInvalidComponentStateError(error, request, h);
|
|
174
|
+
}
|
|
175
|
+
if (error instanceof PaymentPreAuthError) {
|
|
176
|
+
return this.handlePaymentPreAuthError(error, request, h);
|
|
177
|
+
}
|
|
178
|
+
if (error instanceof PaymentSubmissionError) {
|
|
179
|
+
return this.handlePaymentSubmissionError(error, request, h);
|
|
180
|
+
}
|
|
181
|
+
throw error;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
/**
|
|
185
|
+
* Handles InvalidComponentStateError during submission
|
|
186
|
+
*/
|
|
187
|
+
async handleInvalidComponentStateError(error, request, h) {
|
|
188
|
+
const cacheService = getCacheService(request.server);
|
|
189
|
+
const govukError = createError(error.component.name, error.userMessage);
|
|
190
|
+
request.yar.flash(COMPONENT_STATE_ERROR, govukError, true);
|
|
191
|
+
await cacheService.resetComponentStates(request, error.getStateKeys());
|
|
192
|
+
return this.proceed(request, h, error.component.page?.path);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* Handles PaymentPreAuthError during submission
|
|
197
|
+
*/
|
|
198
|
+
async handlePaymentPreAuthError(error, request, h) {
|
|
199
|
+
const cacheService = getCacheService(request.server);
|
|
200
|
+
if (error.shouldResetState) {
|
|
201
|
+
await cacheService.resetComponentStates(request, error.getStateKeys());
|
|
202
|
+
if (error.errorType === PaymentErrorTypes.PaymentExpired) {
|
|
203
|
+
request.yar.flash(PAYMENT_EXPIRED_NOTIFICATION, true, true);
|
|
204
|
+
return this.proceed(request, h, error.component.page?.path);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
const govukError = createError(error.component.name, error.userMessage);
|
|
208
|
+
request.yar.flash(COMPONENT_STATE_ERROR, govukError, true);
|
|
209
|
+
const redirectPath = error.shouldResetState ? error.component.page?.path : undefined;
|
|
210
|
+
return this.proceed(request, h, redirectPath);
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Handles PaymentSubmissionError during submission
|
|
215
|
+
*/
|
|
216
|
+
handlePaymentSubmissionError(error, request, h) {
|
|
217
|
+
const helpUrl = error.helpLink ?? DEFAULT_PAYMENT_HELP_URL;
|
|
218
|
+
const helpLinkHtml = ` or you can <a href="${helpUrl}" target="_blank" rel="noopener noreferrer" class="govuk-link">contact us (opens in new tab)</a> and quote your reference number to arrange a refund`;
|
|
219
|
+
const govukError = createError('submission', `There was a problem and your form was not submitted. Try submitting the form again${helpLinkHtml}.`);
|
|
220
|
+
request.yar.flash(COMPONENT_STATE_ERROR, govukError, true);
|
|
221
|
+
return this.proceed(request, h);
|
|
222
|
+
}
|
|
130
223
|
get postRouteOptions() {
|
|
131
224
|
return {
|
|
132
225
|
ext: {
|
|
@@ -141,20 +234,41 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
141
234
|
}
|
|
142
235
|
export async function submitForm(context, metadata, request, summaryViewModel, model, emailAddress, formMetadata) {
|
|
143
236
|
await finaliseComponents(request, metadata, context);
|
|
237
|
+
const paymentWasCaptured = hasPaymentBeenCaptured(context);
|
|
144
238
|
const formStatus = checkFormStatus(request.params);
|
|
145
239
|
const logTags = ['submit', 'submissionApi'];
|
|
146
240
|
request.logger.info(logTags, 'Preparing email', formStatus);
|
|
147
|
-
|
|
148
|
-
// Get detail items
|
|
149
241
|
const items = getFormSubmissionData(summaryViewModel.context, summaryViewModel.details);
|
|
242
|
+
try {
|
|
243
|
+
request.logger.info(logTags, 'Submitting data');
|
|
244
|
+
const submitResponse = await submitData(model, items, emailAddress, request.yar.id);
|
|
245
|
+
if (submitResponse === undefined) {
|
|
246
|
+
throw Boom.badRequest('Unexpected empty response from submit api');
|
|
247
|
+
}
|
|
248
|
+
await model.services.outputService.submit(context, request, model, emailAddress, items, submitResponse, formMetadata);
|
|
249
|
+
} catch (err) {
|
|
250
|
+
if (paymentWasCaptured) {
|
|
251
|
+
throw new PaymentSubmissionError(context.referenceNumber, formMetadata.contact?.online?.url);
|
|
252
|
+
}
|
|
253
|
+
throw err;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
150
256
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
257
|
+
/**
|
|
258
|
+
* Checks if any payment component has been captured
|
|
259
|
+
*/
|
|
260
|
+
function hasPaymentBeenCaptured(context) {
|
|
261
|
+
for (const page of context.relevantPages) {
|
|
262
|
+
for (const field of page.collection.fields) {
|
|
263
|
+
if (field instanceof PaymentField) {
|
|
264
|
+
const paymentState = field.getPaymentStateFromState(context.state);
|
|
265
|
+
if (paymentState?.capture?.status === 'success') {
|
|
266
|
+
return true;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
156
270
|
}
|
|
157
|
-
return
|
|
271
|
+
return false;
|
|
158
272
|
}
|
|
159
273
|
|
|
160
274
|
/**
|
|
@@ -184,37 +298,45 @@ function submitData(model, items, retrievalKey, sessionId) {
|
|
|
184
298
|
const payload = {
|
|
185
299
|
sessionId,
|
|
186
300
|
retrievalKey,
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
name: item.name,
|
|
190
|
-
title: item.label,
|
|
191
|
-
value: getAnswer(item.field, item.state, {
|
|
192
|
-
format: 'data'
|
|
193
|
-
})
|
|
194
|
-
})),
|
|
195
|
-
// Repeater form answers
|
|
196
|
-
repeaters: items.filter(item => 'subItems' in item).map(item => ({
|
|
197
|
-
name: item.name,
|
|
198
|
-
title: item.label,
|
|
199
|
-
// Repeater item values
|
|
200
|
-
value: item.subItems.map(detailItems => detailItems.map(subItem => ({
|
|
201
|
-
name: subItem.name,
|
|
202
|
-
title: subItem.label,
|
|
203
|
-
value: getAnswer(subItem.field, subItem.state, {
|
|
204
|
-
format: 'data'
|
|
205
|
-
})
|
|
206
|
-
})))
|
|
207
|
-
}))
|
|
301
|
+
main: buildMainRecords(items),
|
|
302
|
+
repeaters: buildRepeaterRecords(items)
|
|
208
303
|
};
|
|
209
304
|
return submit(payload);
|
|
210
305
|
}
|
|
211
306
|
export function getFormSubmissionData(context, details) {
|
|
212
|
-
|
|
307
|
+
const items = context.relevantPages.map(({
|
|
213
308
|
href
|
|
214
309
|
}) => details.flatMap(({
|
|
215
310
|
items
|
|
216
311
|
}) => items.filter(({
|
|
217
312
|
page
|
|
218
313
|
}) => page.href === href))).flat();
|
|
314
|
+
const paymentItems = getPaymentFieldItems(context);
|
|
315
|
+
return [...items, ...paymentItems];
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
/**
|
|
319
|
+
* Gets DetailItems for PaymentField components
|
|
320
|
+
* PaymentField is excluded from summaryDetails for UI but needs to be in submission data
|
|
321
|
+
*/
|
|
322
|
+
function getPaymentFieldItems(context) {
|
|
323
|
+
const items = [];
|
|
324
|
+
for (const page of context.relevantPages) {
|
|
325
|
+
for (const field of page.collection.fields) {
|
|
326
|
+
if (field instanceof PaymentField) {
|
|
327
|
+
items.push({
|
|
328
|
+
name: field.name,
|
|
329
|
+
page,
|
|
330
|
+
title: field.title,
|
|
331
|
+
label: field.label,
|
|
332
|
+
field,
|
|
333
|
+
state: context.state,
|
|
334
|
+
href: page.href,
|
|
335
|
+
value: field.getDisplayStringFromState(context.state)
|
|
336
|
+
});
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
}
|
|
340
|
+
return items;
|
|
219
341
|
}
|
|
220
342
|
//# sourceMappingURL=SummaryPageController.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","COMPONENT_STATE_ERROR","ComponentCollection","getAnswer","checkEmailAddressForLiveFormSubmission","checkFormStatus","createError","getCacheService","SummaryViewModel","QuestionPageController","InvalidComponentStateError","FormAction","SummaryPageController","allowSaveAndExit","constructor","model","pageDef","viewName","collection","components","page","getSummaryViewModel","request","context","viewModel","query","payload","errors","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","shouldShowSaveAndExit","server","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","action","SaveAndExit","handleSaveAndExit","handleFormSubmit","params","cacheService","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","submitForm","error","govukError","component","name","userMessage","yar","flash","resetComponentStates","getStateKeys","proceed","path","setConfirmationState","confirmed","formId","state","referenceNumber","clearState","getStatusPath","postRouteOptions","ext","onPreHandler","method","continue","metadata","summaryViewModel","emailAddress","finaliseComponents","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","id","undefined","badRequest","outputService","submit","relevantFields","relevantPages","flatMap","fields","onSubmit","retrievalKey","sessionId","formSubmissionService","main","filter","item","map","title","label","value","field","format","repeaters","subItems","detailItems","subItem","href","flat"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type FormMetadata,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\n\nimport { COMPONENT_STATE_ERROR } from '~/src/server/constants.js'\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n createError,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport { InvalidComponentStateError } from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n type FormConfirmationState,\n type FormContext,\n type FormContextRequest\n} from '~/src/server/plugins/engine/types.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n allowSaveAndExit = true\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors } = context\n const components = this.collection.getViewModel(payload, errors, query)\n\n // We already figure these out in the base page controller. Take them and apply them to our page-specific model.\n // This is a stop-gap until we can add proper inheritance in place.\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server)\n viewModel.errors = errors\n\n return viewModel\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n // Check if this is a save-and-exit action\n const { action } = request.payload\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n return this.handleFormSubmit(request, context, h)\n }\n }\n\n async handleFormSubmit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const { model } = this\n const { params } = request\n\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Get the form metadata using the `slug` param\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n\n checkEmailAddressForLiveFormSubmission(notificationEmail, isPreview)\n\n // Send submission email\n if (notificationEmail) {\n const viewModel = this.getSummaryViewModel(request, context)\n\n try {\n await submitForm(\n context,\n formMetadata,\n request,\n viewModel,\n model,\n notificationEmail,\n formMetadata\n )\n } catch (error) {\n if (error instanceof InvalidComponentStateError) {\n const govukError = createError(\n error.component.name,\n error.userMessage\n )\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n return this.proceed(request, h, error.component.page?.path)\n }\n\n throw error\n }\n }\n\n await cacheService.setConfirmationState(request, {\n confirmed: true,\n formId: context.state.formId,\n referenceNumber: context.referenceNumber\n } as FormConfirmationState)\n\n // Clear all form data\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nexport async function submitForm(\n context: FormContext,\n metadata: FormMetadata,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string,\n formMetadata: FormMetadata\n) {\n await finaliseComponents(request, metadata, context)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n // Get detail items\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n // Submit data\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n return model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n}\n\n/**\n * Finalises any components that need post-processing before form submission. Candidates usually involve\n * those that have external state.\n * Examples include:\n * - file uploads which are 'persisted' before submission\n * - payments which are 'captured' before submission\n */\nasync function finaliseComponents(\n request: FormRequestPayload,\n metadata: FormMetadata,\n context: FormContext\n) {\n const relevantFields = context.relevantPages.flatMap(\n (page) => page.collection.fields\n )\n\n for (const component of relevantFields) {\n /*\n Each component will throw InvalidComponent if its state is invalid, which is handled\n by handleFormSubmit\n */\n await component.onSubmit(request, metadata, context)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n\n // Main form answers\n main: items\n .filter((item) => 'field' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n value: getAnswer(item.field, item.state, { format: 'data' })\n })),\n\n // Repeater form answers\n repeaters: items\n .filter((item) => 'subItems' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n\n // Repeater item values\n value: item.subItems.map((detailItems) =>\n detailItems.map((subItem) => ({\n name: subItem.name,\n title: subItem.label,\n value: getAnswer(subItem.field, subItem.state, { format: 'data' })\n }))\n )\n }))\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n return context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","COMPONENT_STATE_ERROR","PAYMENT_EXPIRED_NOTIFICATION","ComponentCollection","PaymentField","checkEmailAddressForLiveFormSubmission","checkFormStatus","createError","getCacheService","SummaryViewModel","QuestionPageController","InvalidComponentStateError","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","buildMainRecords","buildRepeaterRecords","DEFAULT_PAYMENT_HELP_URL","formatPaymentAmount","formatPaymentDate","FormAction","SummaryPageController","allowSaveAndExit","constructor","model","pageDef","viewName","collection","components","page","getSummaryViewModel","request","context","viewModel","query","payload","errors","state","paymentField","relevantPages","flatMap","fields","find","field","paymentState","getPaymentStateFromState","paymentDetails","buildPaymentDetails","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","shouldShowSaveAndExit","server","rows","key","text","value","description","amount","reference","preAuth","createdAt","push","title","summaryList","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","action","SaveAndExit","handleSaveAndExit","handleFormSubmit","params","cacheService","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","submitForm","error","handleSubmissionError","setConfirmationState","confirmed","formId","referenceNumber","clearState","proceed","getStatusPath","handleInvalidComponentStateError","handlePaymentPreAuthError","handlePaymentSubmissionError","govukError","component","name","userMessage","yar","flash","resetComponentStates","getStateKeys","path","shouldResetState","errorType","PaymentExpired","redirectPath","undefined","helpUrl","helpLink","helpLinkHtml","postRouteOptions","ext","onPreHandler","method","continue","metadata","summaryViewModel","emailAddress","finaliseComponents","paymentWasCaptured","hasPaymentBeenCaptured","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","id","badRequest","outputService","submit","err","contact","online","url","capture","status","relevantFields","onSubmit","retrievalKey","sessionId","formSubmissionService","main","repeaters","map","href","filter","flat","paymentItems","getPaymentFieldItems","label","getDisplayStringFromState"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type FormMetadata,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\n\nimport {\n COMPONENT_STATE_ERROR,\n PAYMENT_EXPIRED_NOTIFICATION\n} from '~/src/server/constants.js'\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n createError,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n InvalidComponentStateError,\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n buildMainRecords,\n buildRepeaterRecords\n} from '~/src/server/plugins/engine/pageControllers/helpers/submission.js'\nimport {\n type FormConfirmationState,\n type FormContext,\n type FormContextRequest\n} from '~/src/server/plugins/engine/types.js'\nimport {\n DEFAULT_PAYMENT_HELP_URL,\n formatPaymentAmount,\n formatPaymentDate\n} from '~/src/server/plugins/payment/helper.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n allowSaveAndExit = true\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors, state } = context\n\n const paymentField = context.relevantPages\n .flatMap((page) => page.collection.fields)\n .find((field): field is PaymentField => field instanceof PaymentField)\n\n if (paymentField) {\n const paymentState = paymentField.getPaymentStateFromState(state)\n if (paymentState) {\n viewModel.paymentState = paymentState\n viewModel.paymentDetails = this.buildPaymentDetails(\n paymentField,\n paymentState\n )\n }\n }\n\n const components = this.collection.getViewModel(payload, errors, query)\n\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server)\n viewModel.errors = errors\n\n return viewModel\n }\n\n private buildPaymentDetails(\n paymentField: PaymentField,\n paymentState: NonNullable<\n ReturnType<PaymentField['getPaymentStateFromState']>\n >\n ) {\n const rows = [\n {\n key: { text: 'Payment for' },\n value: { text: paymentState.description }\n },\n {\n key: { text: 'Total amount' },\n value: { text: formatPaymentAmount(paymentState.amount) }\n },\n {\n key: { text: 'Reference' },\n value: { text: paymentState.reference }\n }\n ]\n\n if (paymentState.preAuth?.createdAt) {\n rows.push({\n key: { text: 'Date of payment' },\n value: { text: formatPaymentDate(paymentState.preAuth.createdAt) }\n })\n }\n\n return {\n title: { text: 'Payment details' },\n summaryList: { rows }\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { action } = request.payload\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n return this.handleFormSubmit(request, context, h)\n }\n }\n\n async handleFormSubmit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const { model } = this\n const { params } = request\n\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n\n checkEmailAddressForLiveFormSubmission(notificationEmail, isPreview)\n\n if (notificationEmail) {\n const viewModel = this.getSummaryViewModel(request, context)\n\n try {\n await submitForm(\n context,\n formMetadata,\n request,\n viewModel,\n model,\n notificationEmail,\n formMetadata\n )\n } catch (error) {\n return this.handleSubmissionError(error, request, h)\n }\n }\n\n await cacheService.setConfirmationState(request, {\n confirmed: true,\n formId: context.state.formId,\n referenceNumber: context.referenceNumber\n } as FormConfirmationState)\n\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n\n /**\n * Handles errors during form submission\n */\n private async handleSubmissionError(\n error: unknown,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n if (error instanceof InvalidComponentStateError) {\n return this.handleInvalidComponentStateError(error, request, h)\n }\n\n if (error instanceof PaymentPreAuthError) {\n return this.handlePaymentPreAuthError(error, request, h)\n }\n\n if (error instanceof PaymentSubmissionError) {\n return this.handlePaymentSubmissionError(error, request, h)\n }\n\n throw error\n }\n\n /**\n * Handles InvalidComponentStateError during submission\n */\n private async handleInvalidComponentStateError(\n error: InvalidComponentStateError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const cacheService = getCacheService(request.server)\n\n const govukError = createError(error.component.name, error.userMessage)\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n return this.proceed(request, h, error.component.page?.path)\n }\n\n /**\n * Handles PaymentPreAuthError during submission\n */\n private async handlePaymentPreAuthError(\n error: PaymentPreAuthError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const cacheService = getCacheService(request.server)\n\n if (error.shouldResetState) {\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n if (error.errorType === PaymentErrorTypes.PaymentExpired) {\n request.yar.flash(PAYMENT_EXPIRED_NOTIFICATION, true, true)\n return this.proceed(request, h, error.component.page?.path)\n }\n }\n\n const govukError = createError(error.component.name, error.userMessage)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n const redirectPath = error.shouldResetState\n ? error.component.page?.path\n : undefined\n\n return this.proceed(request, h, redirectPath)\n }\n\n /**\n * Handles PaymentSubmissionError during submission\n */\n private handlePaymentSubmissionError(\n error: PaymentSubmissionError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const helpUrl = error.helpLink ?? DEFAULT_PAYMENT_HELP_URL\n const helpLinkHtml = ` or you can <a href=\"${helpUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"govuk-link\">contact us (opens in new tab)</a> and quote your reference number to arrange a refund`\n\n const govukError = createError(\n 'submission',\n `There was a problem and your form was not submitted. Try submitting the form again${helpLinkHtml}.`\n )\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n return this.proceed(request, h)\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nexport async function submitForm(\n context: FormContext,\n metadata: FormMetadata,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string,\n formMetadata: FormMetadata\n) {\n await finaliseComponents(request, metadata, context)\n\n const paymentWasCaptured = hasPaymentBeenCaptured(context)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n try {\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n await model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n } catch (err) {\n if (paymentWasCaptured) {\n throw new PaymentSubmissionError(\n context.referenceNumber,\n formMetadata.contact?.online?.url\n )\n }\n throw err\n }\n}\n\n/**\n * Checks if any payment component has been captured\n */\nfunction hasPaymentBeenCaptured(context: FormContext): boolean {\n for (const page of context.relevantPages) {\n for (const field of page.collection.fields) {\n if (field instanceof PaymentField) {\n const paymentState = field.getPaymentStateFromState(context.state)\n if (paymentState?.capture?.status === 'success') {\n return true\n }\n }\n }\n }\n return false\n}\n\n/**\n * Finalises any components that need post-processing before form submission. Candidates usually involve\n * those that have external state.\n * Examples include:\n * - file uploads which are 'persisted' before submission\n * - payments which are 'captured' before submission\n */\nasync function finaliseComponents(\n request: FormRequestPayload,\n metadata: FormMetadata,\n context: FormContext\n) {\n const relevantFields = context.relevantPages.flatMap(\n (page) => page.collection.fields\n )\n\n for (const component of relevantFields) {\n /*\n Each component will throw InvalidComponent if its state is invalid, which is handled\n by handleFormSubmit\n */\n await component.onSubmit(request, metadata, context)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n main: buildMainRecords(items),\n repeaters: buildRepeaterRecords(items)\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n const items = context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n\n const paymentItems = getPaymentFieldItems(context)\n\n return [...items, ...paymentItems]\n}\n\n/**\n * Gets DetailItems for PaymentField components\n * PaymentField is excluded from summaryDetails for UI but needs to be in submission data\n */\nfunction getPaymentFieldItems(context: FormContext): DetailItemField[] {\n const items: DetailItemField[] = []\n\n for (const page of context.relevantPages) {\n for (const field of page.collection.fields) {\n if (field instanceof PaymentField) {\n items.push({\n name: field.name,\n page,\n title: field.title,\n label: field.label,\n field,\n state: context.state,\n href: page.href,\n value: field.getDisplayStringFromState(context.state)\n })\n }\n }\n }\n\n return items\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,4 +1,35 @@
|
|
|
1
1
|
import { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js';
|
|
2
|
+
export declare enum PaymentErrorTypes {
|
|
3
|
+
PaymentExpired = "PaymentExpired",
|
|
4
|
+
PaymentIncomplete = "PaymentIncomplete",
|
|
5
|
+
PaymentAmountMismatch = "PaymentAmountMismatch"
|
|
6
|
+
}
|
|
7
|
+
export declare class PaymentPreAuthError extends Error {
|
|
8
|
+
readonly component: FormComponent;
|
|
9
|
+
readonly userMessage: string;
|
|
10
|
+
/**
|
|
11
|
+
* Whether to reset the component state and redirect to the component's page.
|
|
12
|
+
* - `true`: Reset state and redirect (e.g., payment expired - user must re-enter)
|
|
13
|
+
* - `false`: Keep state and stay on current page with error (e.g., capture failed - user can retry)
|
|
14
|
+
*/
|
|
15
|
+
readonly shouldResetState: boolean;
|
|
16
|
+
/**
|
|
17
|
+
* When supplied, an "Important" notification banner will be shown based on the value.
|
|
18
|
+
*/
|
|
19
|
+
readonly errorType: PaymentErrorTypes | undefined;
|
|
20
|
+
constructor(component: FormComponent, userMessage: string, shouldResetState: boolean, errorType?: PaymentErrorTypes);
|
|
21
|
+
getStateKeys(): string[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Thrown when form submission fails after payment has been captured.
|
|
25
|
+
* User needs to retry or contact support for a refund.
|
|
26
|
+
*/
|
|
27
|
+
export declare class PaymentSubmissionError extends Error {
|
|
28
|
+
readonly referenceNumber: string;
|
|
29
|
+
readonly helpLink?: string;
|
|
30
|
+
constructor(referenceNumber: string, helpLink?: string);
|
|
31
|
+
static checkPaymentAmount(stateAmount: number, definitionAmount: number | undefined, component: FormComponent): void;
|
|
32
|
+
}
|
|
2
33
|
/**
|
|
3
34
|
* Thrown when a component has an invalid state. This is typically only required where state needs
|
|
4
35
|
* to be checked against an external source upon submission of a form. For example: file upload
|
|
@@ -1,3 +1,61 @@
|
|
|
1
|
+
export let PaymentErrorTypes = /*#__PURE__*/function (PaymentErrorTypes) {
|
|
2
|
+
PaymentErrorTypes["PaymentExpired"] = "PaymentExpired";
|
|
3
|
+
PaymentErrorTypes["PaymentIncomplete"] = "PaymentIncomplete";
|
|
4
|
+
PaymentErrorTypes["PaymentAmountMismatch"] = "PaymentAmountMismatch";
|
|
5
|
+
return PaymentErrorTypes;
|
|
6
|
+
}({});
|
|
7
|
+
function getStateKeys(component) {
|
|
8
|
+
const extraStateKeys = component.page?.getStateKeys(component) ?? [];
|
|
9
|
+
return [component.name].concat(extraStateKeys);
|
|
10
|
+
}
|
|
11
|
+
export class PaymentPreAuthError extends Error {
|
|
12
|
+
component;
|
|
13
|
+
userMessage;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Whether to reset the component state and redirect to the component's page.
|
|
17
|
+
* - `true`: Reset state and redirect (e.g., payment expired - user must re-enter)
|
|
18
|
+
* - `false`: Keep state and stay on current page with error (e.g., capture failed - user can retry)
|
|
19
|
+
*/
|
|
20
|
+
shouldResetState;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* When supplied, an "Important" notification banner will be shown based on the value.
|
|
24
|
+
*/
|
|
25
|
+
errorType;
|
|
26
|
+
constructor(component, userMessage, shouldResetState, errorType) {
|
|
27
|
+
super('Payment capture failed');
|
|
28
|
+
this.name = 'PaymentPreAuthError';
|
|
29
|
+
this.component = component;
|
|
30
|
+
this.userMessage = userMessage;
|
|
31
|
+
this.shouldResetState = shouldResetState;
|
|
32
|
+
this.errorType = errorType;
|
|
33
|
+
}
|
|
34
|
+
getStateKeys() {
|
|
35
|
+
return getStateKeys(this.component);
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Thrown when form submission fails after payment has been captured.
|
|
41
|
+
* User needs to retry or contact support for a refund.
|
|
42
|
+
*/
|
|
43
|
+
export class PaymentSubmissionError extends Error {
|
|
44
|
+
referenceNumber;
|
|
45
|
+
helpLink;
|
|
46
|
+
constructor(referenceNumber, helpLink) {
|
|
47
|
+
super('Form submission failed after payment capture');
|
|
48
|
+
this.name = 'PaymentSubmissionError';
|
|
49
|
+
this.referenceNumber = referenceNumber;
|
|
50
|
+
this.helpLink = helpLink;
|
|
51
|
+
}
|
|
52
|
+
static checkPaymentAmount(stateAmount, definitionAmount, component) {
|
|
53
|
+
if (stateAmount / 100 !== definitionAmount) {
|
|
54
|
+
throw new PaymentPreAuthError(component, 'The pre-authorised payment amount is somehow different from that requested. Try adding payment details again.', true, PaymentErrorTypes.PaymentIncomplete);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
1
59
|
/**
|
|
2
60
|
* Thrown when a component has an invalid state. This is typically only required where state needs
|
|
3
61
|
* to be checked against an external source upon submission of a form. For example: file upload
|
|
@@ -17,8 +75,7 @@ export class InvalidComponentStateError extends Error {
|
|
|
17
75
|
this.userMessage = userMessage;
|
|
18
76
|
}
|
|
19
77
|
getStateKeys() {
|
|
20
|
-
|
|
21
|
-
return [this.component.name].concat(extraStateKeys);
|
|
78
|
+
return getStateKeys(this.component);
|
|
22
79
|
}
|
|
23
80
|
}
|
|
24
81
|
//# sourceMappingURL=errors.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"errors.js","names":["
|
|
1
|
+
{"version":3,"file":"errors.js","names":["PaymentErrorTypes","getStateKeys","component","extraStateKeys","page","name","concat","PaymentPreAuthError","Error","userMessage","shouldResetState","errorType","constructor","PaymentSubmissionError","referenceNumber","helpLink","checkPaymentAmount","stateAmount","definitionAmount","PaymentIncomplete","InvalidComponentStateError","message"],"sources":["../../../../../src/server/plugins/engine/pageControllers/errors.ts"],"sourcesContent":["import { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\n\nexport enum PaymentErrorTypes {\n PaymentExpired = 'PaymentExpired',\n PaymentIncomplete = 'PaymentIncomplete',\n PaymentAmountMismatch = 'PaymentAmountMismatch'\n}\n\nfunction getStateKeys(component: FormComponent) {\n const extraStateKeys = component.page?.getStateKeys(component) ?? []\n\n return [component.name].concat(extraStateKeys)\n}\n\nexport class PaymentPreAuthError extends Error {\n public readonly component: FormComponent\n public readonly userMessage: string\n\n /**\n * Whether to reset the component state and redirect to the component's page.\n * - `true`: Reset state and redirect (e.g., payment expired - user must re-enter)\n * - `false`: Keep state and stay on current page with error (e.g., capture failed - user can retry)\n */\n public readonly shouldResetState: boolean\n\n /**\n * When supplied, an \"Important\" notification banner will be shown based on the value.\n */\n public readonly errorType: PaymentErrorTypes | undefined\n\n constructor(\n component: FormComponent,\n userMessage: string,\n shouldResetState: boolean,\n errorType?: PaymentErrorTypes\n ) {\n super('Payment capture failed')\n this.name = 'PaymentPreAuthError'\n this.component = component\n this.userMessage = userMessage\n this.shouldResetState = shouldResetState\n this.errorType = errorType\n }\n\n getStateKeys() {\n return getStateKeys(this.component)\n }\n}\n\n/**\n * Thrown when form submission fails after payment has been captured.\n * User needs to retry or contact support for a refund.\n */\nexport class PaymentSubmissionError extends Error {\n public readonly referenceNumber: string\n public readonly helpLink?: string\n\n constructor(referenceNumber: string, helpLink?: string) {\n super('Form submission failed after payment capture')\n this.name = 'PaymentSubmissionError'\n this.referenceNumber = referenceNumber\n this.helpLink = helpLink\n }\n\n static checkPaymentAmount(\n stateAmount: number,\n definitionAmount: number | undefined,\n component: FormComponent\n ) {\n if (stateAmount / 100 !== definitionAmount) {\n throw new PaymentPreAuthError(\n component,\n 'The pre-authorised payment amount is somehow different from that requested. Try adding payment details again.',\n true,\n PaymentErrorTypes.PaymentIncomplete\n )\n }\n }\n}\n\n/**\n * Thrown when a component has an invalid state. This is typically only required where state needs\n * to be checked against an external source upon submission of a form. For example: file upload\n * has internal state (file upload IDs) but also external state (files in S3). The internal state\n * is always validated by the engine, but the external state needs validating too.\n *\n * This should be used within a formComponent.onSubmit(...).\n */\nexport class InvalidComponentStateError extends Error {\n public readonly component: FormComponent\n public readonly userMessage: string\n\n constructor(component: FormComponent, userMessage: string) {\n const message = `Invalid component state for: ${component.name}`\n super(message)\n this.name = 'InvalidComponentStateError'\n this.component = component\n this.userMessage = userMessage\n }\n\n getStateKeys() {\n return getStateKeys(this.component)\n }\n}\n"],"mappings":"AAEA,WAAYA,iBAAiB,0BAAjBA,iBAAiB;EAAjBA,iBAAiB;EAAjBA,iBAAiB;EAAjBA,iBAAiB;EAAA,OAAjBA,iBAAiB;AAAA;AAM7B,SAASC,YAAYA,CAACC,SAAwB,EAAE;EAC9C,MAAMC,cAAc,GAAGD,SAAS,CAACE,IAAI,EAAEH,YAAY,CAACC,SAAS,CAAC,IAAI,EAAE;EAEpE,OAAO,CAACA,SAAS,CAACG,IAAI,CAAC,CAACC,MAAM,CAACH,cAAc,CAAC;AAChD;AAEA,OAAO,MAAMI,mBAAmB,SAASC,KAAK,CAAC;EAC7BN,SAAS;EACTO,WAAW;;EAE3B;AACF;AACA;AACA;AACA;EACkBC,gBAAgB;;EAEhC;AACF;AACA;EACkBC,SAAS;EAEzBC,WAAWA,CACTV,SAAwB,EACxBO,WAAmB,EACnBC,gBAAyB,EACzBC,SAA6B,EAC7B;IACA,KAAK,CAAC,wBAAwB,CAAC;IAC/B,IAAI,CAACN,IAAI,GAAG,qBAAqB;IACjC,IAAI,CAACH,SAAS,GAAGA,SAAS;IAC1B,IAAI,CAACO,WAAW,GAAGA,WAAW;IAC9B,IAAI,CAACC,gBAAgB,GAAGA,gBAAgB;IACxC,IAAI,CAACC,SAAS,GAAGA,SAAS;EAC5B;EAEAV,YAAYA,CAAA,EAAG;IACb,OAAOA,YAAY,CAAC,IAAI,CAACC,SAAS,CAAC;EACrC;AACF;;AAEA;AACA;AACA;AACA;AACA,OAAO,MAAMW,sBAAsB,SAASL,KAAK,CAAC;EAChCM,eAAe;EACfC,QAAQ;EAExBH,WAAWA,CAACE,eAAuB,EAAEC,QAAiB,EAAE;IACtD,KAAK,CAAC,8CAA8C,CAAC;IACrD,IAAI,CAACV,IAAI,GAAG,wBAAwB;IACpC,IAAI,CAACS,eAAe,GAAGA,eAAe;IACtC,IAAI,CAACC,QAAQ,GAAGA,QAAQ;EAC1B;EAEA,OAAOC,kBAAkBA,CACvBC,WAAmB,EACnBC,gBAAoC,EACpChB,SAAwB,EACxB;IACA,IAAIe,WAAW,GAAG,GAAG,KAAKC,gBAAgB,EAAE;MAC1C,MAAM,IAAIX,mBAAmB,CAC3BL,SAAS,EACT,+GAA+G,EAC/G,IAAI,EACJF,iBAAiB,CAACmB,iBACpB,CAAC;IACH;EACF;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,0BAA0B,SAASZ,KAAK,CAAC;EACpCN,SAAS;EACTO,WAAW;EAE3BG,WAAWA,CAACV,SAAwB,EAAEO,WAAmB,EAAE;IACzD,MAAMY,OAAO,GAAG,gCAAgCnB,SAAS,CAACG,IAAI,EAAE;IAChE,KAAK,CAACgB,OAAO,CAAC;IACd,IAAI,CAAChB,IAAI,GAAG,4BAA4B;IACxC,IAAI,CAACH,SAAS,GAAGA,SAAS;IAC1B,IAAI,CAACO,WAAW,GAAGA,WAAW;EAChC;EAEAR,YAAYA,CAAA,EAAG;IACb,OAAOA,YAAY,CAAC,IAAI,CAACC,SAAS,CAAC;EACrC;AACF","ignoreList":[]}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type SubmitPayload } from '@defra/forms-model';
|
|
2
|
+
import { type DetailItem, type DetailItemField } from '~/src/server/plugins/engine/models/types.js';
|
|
3
|
+
export interface SubmitRecord {
|
|
4
|
+
name: string;
|
|
5
|
+
title: string;
|
|
6
|
+
value: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Builds the main submission records from field items.
|
|
10
|
+
* Regular fields are converted to single records, while PaymentField
|
|
11
|
+
* components are expanded into four separate records.
|
|
12
|
+
*/
|
|
13
|
+
export declare function buildMainRecords(items: DetailItem[]): SubmitRecord[];
|
|
14
|
+
/**
|
|
15
|
+
* Expands a PaymentField into four submission records:
|
|
16
|
+
* - Payment description
|
|
17
|
+
* - Payment amount (formatted with currency symbol)
|
|
18
|
+
* - Payment reference
|
|
19
|
+
* - Payment date (formatted date/time)
|
|
20
|
+
*
|
|
21
|
+
* Returns an empty array if no payment state exists.
|
|
22
|
+
*/
|
|
23
|
+
export declare function buildPaymentRecords(item: DetailItemField): SubmitRecord[];
|
|
24
|
+
/**
|
|
25
|
+
* Builds the repeater submission records from repeater items.
|
|
26
|
+
*/
|
|
27
|
+
export declare function buildRepeaterRecords(items: DetailItem[]): SubmitPayload['repeaters'];
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import { PaymentField } from "../../components/PaymentField.js";
|
|
2
|
+
import { getAnswer } from "../../components/helpers/components.js";
|
|
3
|
+
import { formatPaymentAmount, formatPaymentDate } from "../../../payment/helper.js";
|
|
4
|
+
/**
|
|
5
|
+
* Builds the main submission records from field items.
|
|
6
|
+
* Regular fields are converted to single records, while PaymentField
|
|
7
|
+
* components are expanded into four separate records.
|
|
8
|
+
*/
|
|
9
|
+
export function buildMainRecords(items) {
|
|
10
|
+
const fieldItems = items.filter(item => 'field' in item);
|
|
11
|
+
const records = [];
|
|
12
|
+
for (const item of fieldItems) {
|
|
13
|
+
if (item.field instanceof PaymentField) {
|
|
14
|
+
records.push(...buildPaymentRecords(item));
|
|
15
|
+
} else {
|
|
16
|
+
records.push({
|
|
17
|
+
name: item.name,
|
|
18
|
+
title: item.label,
|
|
19
|
+
value: getAnswer(item.field, item.state, {
|
|
20
|
+
format: 'data'
|
|
21
|
+
})
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
return records;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Expands a PaymentField into four submission records:
|
|
30
|
+
* - Payment description
|
|
31
|
+
* - Payment amount (formatted with currency symbol)
|
|
32
|
+
* - Payment reference
|
|
33
|
+
* - Payment date (formatted date/time)
|
|
34
|
+
*
|
|
35
|
+
* Returns an empty array if no payment state exists.
|
|
36
|
+
*/
|
|
37
|
+
export function buildPaymentRecords(item) {
|
|
38
|
+
const paymentState = item.field.getPaymentStateFromState(item.state);
|
|
39
|
+
if (!paymentState) {
|
|
40
|
+
return [];
|
|
41
|
+
}
|
|
42
|
+
return [{
|
|
43
|
+
name: `${item.name}_paymentDescription`,
|
|
44
|
+
title: 'Payment description',
|
|
45
|
+
value: paymentState.description
|
|
46
|
+
}, {
|
|
47
|
+
name: `${item.name}_paymentAmount`,
|
|
48
|
+
title: 'Payment amount',
|
|
49
|
+
value: formatPaymentAmount(paymentState.amount)
|
|
50
|
+
}, {
|
|
51
|
+
name: `${item.name}_paymentReference`,
|
|
52
|
+
title: 'Payment reference',
|
|
53
|
+
value: paymentState.reference
|
|
54
|
+
}, {
|
|
55
|
+
name: `${item.name}_paymentDate`,
|
|
56
|
+
title: 'Payment date',
|
|
57
|
+
value: paymentState.preAuth?.createdAt ? formatPaymentDate(paymentState.preAuth.createdAt) : ''
|
|
58
|
+
}];
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Builds the repeater submission records from repeater items.
|
|
63
|
+
*/
|
|
64
|
+
export function buildRepeaterRecords(items) {
|
|
65
|
+
return items.filter(item => 'subItems' in item).map(item => ({
|
|
66
|
+
name: item.name,
|
|
67
|
+
title: item.label,
|
|
68
|
+
value: item.subItems.map(detailItems => detailItems.map(subItem => ({
|
|
69
|
+
name: subItem.name,
|
|
70
|
+
title: subItem.label,
|
|
71
|
+
value: getAnswer(subItem.field, subItem.state, {
|
|
72
|
+
format: 'data'
|
|
73
|
+
})
|
|
74
|
+
})))
|
|
75
|
+
}));
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=submission.js.map
|