@defra/forms-engine-plugin 4.7.3 → 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.
- package/.public/javascripts/shared.min.js +1 -1
- package/.public/javascripts/shared.min.js.map +1 -1
- package/.server/client/javascripts/map.d.ts +4 -0
- package/.server/client/javascripts/map.js +9 -1
- package/.server/client/javascripts/map.js.map +1 -1
- package/.server/server/forms/payment-v2-test.yaml +341 -0
- package/.server/server/plugins/engine/components/PaymentField.d.ts +7 -0
- package/.server/server/plugins/engine/components/PaymentField.js +58 -6
- package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
- package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +2 -0
- package/.server/server/plugins/engine/models/SummaryViewModel.js +2 -0
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +24 -2
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +10 -0
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +57 -13
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/errors.d.ts +1 -1
- package/.server/server/plugins/engine/pageControllers/errors.js +2 -2
- package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -1
- package/.server/server/plugins/engine/routes/index.js +5 -2
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/.server/server/plugins/engine/routes/payment.js +6 -1
- package/.server/server/plugins/engine/routes/payment.js.map +1 -1
- package/.server/server/plugins/engine/routes/payment.test.js +3 -3
- package/.server/server/plugins/engine/routes/payment.test.js.map +1 -1
- 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/views/summary.html +2 -1
- package/.server/server/plugins/map/routes/vts/esri-aerial.json +23 -0
- package/.server/server/plugins/payment/service.d.ts +2 -1
- package/.server/server/plugins/payment/service.js +11 -3
- package/.server/server/plugins/payment/service.js.map +1 -1
- package/.server/server/plugins/payment/types.d.ts +4 -0
- package/.server/server/plugins/payment/types.js +1 -0
- package/.server/server/plugins/payment/types.js.map +1 -1
- package/package.json +2 -2
- package/src/client/javascripts/map.js +10 -1
- package/src/server/forms/payment-v2-test.yaml +341 -0
- package/src/server/plugins/engine/components/PaymentField.ts +70 -6
- package/src/server/plugins/engine/models/SummaryViewModel.ts +2 -0
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +32 -1
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +99 -17
- package/src/server/plugins/engine/pageControllers/errors.ts +2 -2
- package/src/server/plugins/engine/routes/index.ts +9 -2
- package/src/server/plugins/engine/routes/payment.js +7 -1
- package/src/server/plugins/engine/services/localFormsService.js +7 -0
- package/src/server/plugins/engine/views/summary.html +2 -1
- package/src/server/plugins/map/routes/vts/esri-aerial.json +23 -0
- package/src/server/plugins/payment/service.js +13 -3
- package/src/server/plugins/payment/types.js +1 -0
|
@@ -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":"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","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
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SummaryViewModel.js","names":["SchemaVersion","PaymentField","getAnswer","evaluateTemplate","getError","getPageHref","RepeatPageController","validationOptions","opts","SummaryViewModel","page","pageTitle","declaration","details","checkAnswers","context","name","backLink","feedbackLink","phaseTag","errors","serviceUrl","hasMissingNotificationEmail","components","allowSaveAndExit","paymentState","paymentDetails","constructor","request","model","basePath","def","sections","isForceAccess","title","schema","V2","result","makeFilteredSchema","relevantPages","validate","relevantState","stripUnknown","error","map","summaryDetails","detail","rows","items","item","push","href","text","classes","visuallyHiddenText","label","key","value","html","actions","undefined","summaryList","state","forEach","section","sectionPages","filter","collection","path","ItemRepeat","getSummaryPath","field","fields","ItemField","length","options","repeat","values","getListFromState","unit","returnUrl","subItems","repeatState","required","getFirstError"],"sources":["../../../../../src/server/plugins/engine/models/SummaryViewModel.ts"],"sourcesContent":["import { SchemaVersion, type Section } from '@defra/forms-model'\n\nimport { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'\nimport { type PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n type BackLink,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport {\n evaluateTemplate,\n getError,\n getPageHref\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type CheckAnswers,\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type SummaryListAction,\n type SummaryListRow\n} from '~/src/server/plugins/engine/types.js'\n\nexport class SummaryViewModel {\n /**\n * Responsible for parsing state values to the govuk-frontend summary list template\n */\n\n page: PageControllerClass\n pageTitle: string\n declaration?: string\n details: Detail[]\n checkAnswers: CheckAnswers[]\n context: FormContext\n name: string | undefined\n backLink?: BackLink\n feedbackLink?: string\n phaseTag?: string\n errors?: FormSubmissionError[]\n serviceUrl: string\n hasMissingNotificationEmail?: boolean\n components?: ComponentViewModel[]\n allowSaveAndExit = false\n paymentState?: PaymentState\n paymentDetails?: CheckAnswers\n\n constructor(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n ) {\n const { model } = page\n const { basePath, def, sections } = model\n const { isForceAccess } = context\n\n this.page = page\n this.pageTitle = page.title\n if (def.schema === SchemaVersion.V2 && !page.title) {\n this.pageTitle = 'Check your answers before sending your form'\n }\n\n this.serviceUrl = `/${basePath}`\n this.name = def.name\n this.declaration = def.declaration\n this.context = context\n\n const result = model\n .makeFilteredSchema(this.context.relevantPages)\n .validate(this.context.relevantState, { ...opts, stripUnknown: true })\n\n // Format errors\n this.errors = result.error?.details.map(getError)\n this.details = this.summaryDetails(request, sections)\n\n // Format check answers\n this.checkAnswers = this.details.map((detail): CheckAnswers => {\n const { title } = detail\n\n const rows = detail.items.map((item): SummaryListRow => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: item.href,\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: item.label\n })\n }\n\n return {\n key: {\n text: evaluateTemplate(item.title, context)\n },\n value: {\n classes: 'app-prose-scope',\n html: item.value || 'Not provided'\n },\n actions: {\n items\n }\n }\n })\n\n return {\n title: title ? { text: title } : undefined,\n summaryList: { rows }\n }\n })\n }\n\n private summaryDetails(request: FormContextRequest, sections: Section[]) {\n const { context, errors } = this\n const { relevantPages, state } = context\n\n const details: Detail[] = []\n\n ;[...sections, undefined].forEach((section) => {\n const items: DetailItem[] = []\n\n const sectionPages = relevantPages.filter(\n (page) => page.section === section\n )\n\n sectionPages.forEach((page) => {\n const { collection, path } = page\n\n if (page instanceof RepeatPageController) {\n items.push(\n ItemRepeat(page, state, {\n path: page.getSummaryPath(request),\n errors\n })\n )\n } else {\n for (const field of collection.fields) {\n // PaymentField is rendered in its own section, skip it here\n if (field instanceof PaymentField) {\n continue\n }\n items.push(ItemField(page, state, field, { path, errors }))\n }\n }\n })\n\n if (items.length) {\n details.push({\n name: section?.name,\n title: section?.title,\n items\n })\n }\n })\n\n return details\n }\n}\n\n/**\n * Creates a repeater detail item\n * @see {@link DetailItemField}\n */\nfunction ItemRepeat(\n page: RepeatPageController,\n state: FormState,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemRepeat {\n const { collection, repeat } = page\n const { name, title } = repeat.options\n\n const values = page.getListFromState(state)\n const unit = values.length === 1 ? 'answer' : 'answers'\n\n return {\n name,\n label: title,\n title,\n value: values.length ? `You have added ${values.length} ${unit}` : '',\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n\n // Repeater field detail items\n subItems: values.map((repeatState) =>\n collection.fields.map((field) =>\n ItemField(page, repeatState, field, options)\n )\n )\n }\n}\n\n/**\n * Creates a form field detail item\n * @see {@link DetailItemField}\n */\nexport function ItemField(\n page: PageControllerClass,\n state: FormState,\n field: Field,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemField {\n return {\n name: field.name,\n label: field.title,\n title:\n field.options.required === false\n ? `${field.label} (optional)`\n : field.label,\n error: field.getFirstError(options.errors),\n value: getAnswer(field, state),\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n field\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"SummaryViewModel.js","names":["SchemaVersion","PaymentField","getAnswer","evaluateTemplate","getError","getPageHref","RepeatPageController","validationOptions","opts","SummaryViewModel","page","pageTitle","declaration","details","checkAnswers","context","name","backLink","feedbackLink","phaseTag","errors","serviceUrl","hasMissingNotificationEmail","components","allowSaveAndExit","paymentState","paymentDetails","paymentRequired","paymentPreAuthorized","constructor","request","model","basePath","def","sections","isForceAccess","title","schema","V2","result","makeFilteredSchema","relevantPages","validate","relevantState","stripUnknown","error","map","summaryDetails","detail","rows","items","item","push","href","text","classes","visuallyHiddenText","label","key","value","html","actions","undefined","summaryList","state","forEach","section","sectionPages","filter","collection","path","ItemRepeat","getSummaryPath","field","fields","ItemField","length","options","repeat","values","getListFromState","unit","returnUrl","subItems","repeatState","required","getFirstError"],"sources":["../../../../../src/server/plugins/engine/models/SummaryViewModel.ts"],"sourcesContent":["import { SchemaVersion, type Section } from '@defra/forms-model'\n\nimport { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'\nimport { type PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'\nimport {\n getAnswer,\n type Field\n} from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n type BackLink,\n type ComponentViewModel\n} from '~/src/server/plugins/engine/components/types.js'\nimport {\n evaluateTemplate,\n getError,\n getPageHref\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField,\n type DetailItemRepeat\n} from '~/src/server/plugins/engine/models/types.js'\nimport { RepeatPageController } from '~/src/server/plugins/engine/pageControllers/RepeatPageController.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport { validationOptions as opts } from '~/src/server/plugins/engine/pageControllers/validationOptions.js'\nimport {\n type CheckAnswers,\n type FormContext,\n type FormContextRequest,\n type FormState,\n type FormSubmissionError,\n type SummaryListAction,\n type SummaryListRow\n} from '~/src/server/plugins/engine/types.js'\n\nexport class SummaryViewModel {\n /**\n * Responsible for parsing state values to the govuk-frontend summary list template\n */\n\n page: PageControllerClass\n pageTitle: string\n declaration?: string\n details: Detail[]\n checkAnswers: CheckAnswers[]\n context: FormContext\n name: string | undefined\n backLink?: BackLink\n feedbackLink?: string\n phaseTag?: string\n errors?: FormSubmissionError[]\n serviceUrl: string\n hasMissingNotificationEmail?: boolean\n components?: ComponentViewModel[]\n allowSaveAndExit = false\n paymentState?: PaymentState\n paymentDetails?: CheckAnswers\n paymentRequired?: boolean\n paymentPreAuthorized?: boolean\n\n constructor(\n request: FormContextRequest,\n page: PageControllerClass,\n context: FormContext\n ) {\n const { model } = page\n const { basePath, def, sections } = model\n const { isForceAccess } = context\n\n this.page = page\n this.pageTitle = page.title\n if (def.schema === SchemaVersion.V2 && !page.title) {\n this.pageTitle = 'Check your answers before sending your form'\n }\n\n this.serviceUrl = `/${basePath}`\n this.name = def.name\n this.declaration = def.declaration\n this.context = context\n\n const result = model\n .makeFilteredSchema(this.context.relevantPages)\n .validate(this.context.relevantState, { ...opts, stripUnknown: true })\n\n // Format errors\n this.errors = result.error?.details.map(getError)\n this.details = this.summaryDetails(request, sections)\n\n // Format check answers\n this.checkAnswers = this.details.map((detail): CheckAnswers => {\n const { title } = detail\n\n const rows = detail.items.map((item): SummaryListRow => {\n const items: SummaryListAction[] = []\n\n // Remove summary list actions from previews\n if (!isForceAccess) {\n items.push({\n href: item.href,\n text: 'Change',\n classes: 'govuk-link--no-visited-state',\n visuallyHiddenText: item.label\n })\n }\n\n return {\n key: {\n text: evaluateTemplate(item.title, context)\n },\n value: {\n classes: 'app-prose-scope',\n html: item.value || 'Not provided'\n },\n actions: {\n items\n }\n }\n })\n\n return {\n title: title ? { text: title } : undefined,\n summaryList: { rows }\n }\n })\n }\n\n private summaryDetails(request: FormContextRequest, sections: Section[]) {\n const { context, errors } = this\n const { relevantPages, state } = context\n\n const details: Detail[] = []\n\n ;[...sections, undefined].forEach((section) => {\n const items: DetailItem[] = []\n\n const sectionPages = relevantPages.filter(\n (page) => page.section === section\n )\n\n sectionPages.forEach((page) => {\n const { collection, path } = page\n\n if (page instanceof RepeatPageController) {\n items.push(\n ItemRepeat(page, state, {\n path: page.getSummaryPath(request),\n errors\n })\n )\n } else {\n for (const field of collection.fields) {\n // PaymentField is rendered in its own section, skip it here\n if (field instanceof PaymentField) {\n continue\n }\n items.push(ItemField(page, state, field, { path, errors }))\n }\n }\n })\n\n if (items.length) {\n details.push({\n name: section?.name,\n title: section?.title,\n items\n })\n }\n })\n\n return details\n }\n}\n\n/**\n * Creates a repeater detail item\n * @see {@link DetailItemField}\n */\nfunction ItemRepeat(\n page: RepeatPageController,\n state: FormState,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemRepeat {\n const { collection, repeat } = page\n const { name, title } = repeat.options\n\n const values = page.getListFromState(state)\n const unit = values.length === 1 ? 'answer' : 'answers'\n\n return {\n name,\n label: title,\n title,\n value: values.length ? `You have added ${values.length} ${unit}` : '',\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n\n // Repeater field detail items\n subItems: values.map((repeatState) =>\n collection.fields.map((field) =>\n ItemField(page, repeatState, field, options)\n )\n )\n }\n}\n\n/**\n * Creates a form field detail item\n * @see {@link DetailItemField}\n */\nexport function ItemField(\n page: PageControllerClass,\n state: FormState,\n field: Field,\n options: {\n path: string\n errors?: FormSubmissionError[]\n }\n): DetailItemField {\n return {\n name: field.name,\n label: field.title,\n title:\n field.options.required === false\n ? `${field.label} (optional)`\n : field.label,\n error: field.getFirstError(options.errors),\n value: getAnswer(field, state),\n href: getPageHref(page, options.path, {\n returnUrl: getPageHref(page, page.getSummaryPath())\n }),\n state,\n page,\n field\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import { ComponentType, ControllerType, Engine, hasComponents, hasNext, hasRepeater } from '@defra/forms-model';
|
|
1
|
+
import { ComponentType, ControllerType, Engine, hasComponents, hasNext, hasRepeater, isPaymentPage } from '@defra/forms-model';
|
|
2
2
|
import Boom from '@hapi/boom';
|
|
3
3
|
import { COMPONENT_STATE_ERROR, EXTERNAL_STATE_APPENDAGE, EXTERNAL_STATE_PAYLOAD, PAYMENT_EXPIRED_NOTIFICATION } from "../../../constants.js";
|
|
4
4
|
import { ComponentCollection } from "../components/ComponentCollection.js";
|
|
5
|
+
import { PaymentField } from "../components/PaymentField.js";
|
|
5
6
|
import { optionalText } from "../components/constants.js";
|
|
6
7
|
import { checkFormStatus, getCacheService, getErrors, getSaveAndExitHelpers, normalisePath, proceed } from "../helpers.js";
|
|
7
8
|
import { PageController } from "./PageController.js";
|
|
8
9
|
import { prefillStateFromQueryParameters } from "./helpers/state.js";
|
|
9
10
|
import { getComponentsByType } from "../validationHelpers.js";
|
|
11
|
+
import { formatCurrency } from "../../payment/helper.js";
|
|
10
12
|
import { FormAction, FormStatus } from "../../../routes/types.js";
|
|
11
13
|
import { actionSchema, crumbSchema, paramsSchema } from "../../../schemas/index.js";
|
|
12
14
|
import { merge } from "../../../services/cacheService.js";
|
|
@@ -132,6 +134,19 @@ export class QuestionPageController extends PageController {
|
|
|
132
134
|
}
|
|
133
135
|
}
|
|
134
136
|
}
|
|
137
|
+
|
|
138
|
+
// Override payment amount display with resolved conditional amount
|
|
139
|
+
// getViewModel() only has the current page's payload, not full form state.
|
|
140
|
+
// The full state is available via context.evaluationState.
|
|
141
|
+
for (const comp of components) {
|
|
142
|
+
if ('amount' in comp.model && 'paymentState' in comp.model) {
|
|
143
|
+
const paymentField = this.collection.fields.find(f => f instanceof PaymentField);
|
|
144
|
+
if (paymentField) {
|
|
145
|
+
const resolvedAmount = PaymentField.resolveAmount(paymentField.options, this.model, context.evaluationState);
|
|
146
|
+
comp.model.amount = formatCurrency(resolvedAmount);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
}
|
|
135
150
|
const hasIncompletePayment = components.some(({
|
|
136
151
|
model
|
|
137
152
|
}) => {
|
|
@@ -148,7 +163,7 @@ export class QuestionPageController extends PageController {
|
|
|
148
163
|
showTitle,
|
|
149
164
|
components,
|
|
150
165
|
errors,
|
|
151
|
-
allowSaveAndExit: this.shouldShowSaveAndExit(request.server),
|
|
166
|
+
allowSaveAndExit: this.shouldShowSaveAndExit(request.server) && !isPaymentPage(this.pageDef),
|
|
152
167
|
showSubmitButton: !hasIncompletePayment
|
|
153
168
|
};
|
|
154
169
|
}
|
|
@@ -198,6 +213,13 @@ export class QuestionPageController extends PageController {
|
|
|
198
213
|
return false;
|
|
199
214
|
}
|
|
200
215
|
}
|
|
216
|
+
|
|
217
|
+
// Skip payment pages in the normal page walk.
|
|
218
|
+
// Users reach the payment page via "Pay and submit" on CYA,
|
|
219
|
+
// not by navigating forward through the form.
|
|
220
|
+
if (isPaymentPage(page.pageDef)) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
201
223
|
return true;
|
|
202
224
|
});
|
|
203
225
|
return nextPage?.path ?? defaultPath;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"QuestionPageController.js","names":["ComponentType","ControllerType","Engine","hasComponents","hasNext","hasRepeater","Boom","COMPONENT_STATE_ERROR","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","PAYMENT_EXPIRED_NOTIFICATION","ComponentCollection","optionalText","checkFormStatus","getCacheService","getErrors","getSaveAndExitHelpers","normalisePath","proceed","PageController","prefillStateFromQueryParameters","getComponentsByType","FormAction","FormStatus","actionSchema","crumbSchema","paramsSchema","merge","QuestionPageController","collection","errorSummaryTitle","allowSaveAndExit","constructor","model","pageDef","components","page","formSchema","keys","crumb","action","next","def","filter","path","linkPath","pages","some","pagePath","allowContinue","engine","V2","controller","Terminal","length","getItemId","request","itemId","getFormParams","params","getViewModel","context","viewModel","query","payload","errors","pageTitle","showTitle","formComponents","isFormComponent","fieldset","label","isPageHeading","labelOrLegend","legend","size","classes","isOptional","fields","at","options","required","text","hasIncompletePayment","paymentState","preAuth","status","backLink","getBackLink","shouldShowSaveAndExit","server","showSubmitButton","getRelevantPath","paths","startPath","getStartPath","relevantPath","getNextPath","evaluationState","summaryPath","getSummaryPath","statusPath","getStatusPath","defaultPath","undefined","pageIndex","indexOf","nextPage","slice","find","condition","conditionResult","fn","nextLink","link","conditions","getFormDataFromState","state","result","validate","abortEarly","stripUnknown","value","getStateFromValidForm","details","getState","cacheService","setState","mergeState","update","updated","filterConditionalComponents","filtered","component","content","type","Details","map","evaluatedComponent","Array","isArray","item","items","makeGetRouteHandler","h","viewName","redirect","url","pathname","getViewErrors","flashedError","yar","flash","flashedErrors","concat","paymentExpiredFlash","showPaymentExpiredNotification","hasMissingNotificationEmail","view","isForceAccess","formsService","services","getFormMetadata","includes","notificationEmail","slug","returnUrl","href","backPath","endsWith","getHref","makePostRouteHandler","startsWith","External","dispatchExternal","SaveAndExit","handleSaveAndExit","externalComponents","externalActionsWithArgs","split","externalActionArgs","arg","args","Object","fromEntries","componentName","componentDefMap","get","componentType","internal","selectedComponent","stashedPayload","clear","isPreview","isLive","Live","dispatcher","sourceUrl","search","actionArgs","nextPath","nextUrl","saveAndExit","getRouteOptions","ext","onPostHandler","method","_request","continue","postRouteOptions","parse","maxBytes","Number","MAX_SAFE_INTEGER","failAction"],"sources":["../../../../../src/server/plugins/engine/pageControllers/QuestionPageController.ts"],"sourcesContent":["import {\n ComponentType,\n ControllerType,\n Engine,\n hasComponents,\n hasNext,\n hasRepeater,\n type Link,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n COMPONENT_STATE_ERROR,\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD,\n PAYMENT_EXPIRED_NOTIFICATION\n} from '~/src/server/constants.js'\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { optionalText } from '~/src/server/plugins/engine/components/constants.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n checkFormStatus,\n getCacheService,\n getErrors,\n getSaveAndExitHelpers,\n normalisePath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { prefillStateFromQueryParameters } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormPayload,\n type FormPayloadParams,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { getComponentsByType } from '~/src/server/plugins/engine/validationHelpers.js'\nimport {\n FormAction,\n FormStatus,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n paramsSchema\n} from '~/src/server/schemas/index.js'\nimport { merge } from '~/src/server/services/cacheService.js'\n\nexport class QuestionPageController extends PageController {\n collection: ComponentCollection\n errorSummaryTitle = 'There is a problem'\n allowSaveAndExit = true\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponents(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n\n this.collection.formSchema = this.collection.formSchema.keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n }\n\n get next(): Link[] {\n const { def, pageDef } = this\n\n if (!hasNext(pageDef)) {\n return []\n }\n\n // Remove stale links\n return pageDef.next.filter(({ path }) => {\n const linkPath = normalisePath(path)\n\n return def.pages.some((page) => {\n const pagePath = normalisePath(page.path)\n return pagePath === linkPath\n })\n })\n }\n\n get allowContinue(): boolean {\n if (this.model.engine === Engine.V2) {\n return this.pageDef.controller !== ControllerType.Terminal\n }\n\n return this.next.length > 0\n }\n\n getItemId(request?: FormContextRequest) {\n const { itemId } = this.getFormParams(request)\n return itemId ?? request?.params.itemId\n }\n\n /**\n * Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered\n * @param request - the hapi request\n * @param context - the form context\n */\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { collection, viewModel } = this\n const { query } = request\n const { payload, errors } = context\n\n let { pageTitle, showTitle } = viewModel\n\n const components = collection.getViewModel(payload, errors, query)\n const formComponents = components.filter(\n ({ isFormComponent }) => isFormComponent\n )\n\n // Single form component? Hide title and customise label or legend instead\n if (formComponents.length === 1) {\n const { model } = formComponents[0]\n const { fieldset, label } = model\n\n // Set as page heading when not following other content\n const isPageHeading = formComponents[0] === components[0]\n\n // Check for legend or label\n const labelOrLegend = fieldset?.legend ?? label\n\n // Use legend or label as page heading\n if (labelOrLegend) {\n const size = isPageHeading ? 'l' : 'm'\n\n labelOrLegend.classes =\n labelOrLegend === label\n ? `govuk-label--${size}`\n : `govuk-fieldset__legend--${size}`\n\n if (isPageHeading) {\n labelOrLegend.isPageHeading = isPageHeading\n\n // Check for optional in label\n const isOptional =\n this.collection.fields.at(0)?.options.required === false\n\n if (pageTitle) {\n labelOrLegend.text = isOptional\n ? `${pageTitle}${optionalText}`\n : pageTitle\n }\n\n pageTitle = pageTitle || labelOrLegend.text\n }\n }\n\n showTitle = !isPageHeading\n } else if (formComponents.length > 1) {\n // When there is more than one form component,\n // adjust the label/legends to give equal prominence\n for (const { model } of formComponents) {\n if (model.fieldset?.legend) {\n model.fieldset.legend.classes = 'govuk-fieldset__legend--m'\n }\n if (model.label) {\n model.label.classes = 'govuk-label--m'\n }\n }\n }\n\n const hasIncompletePayment = components.some(({ model }) => {\n if ('paymentState' in model) {\n const paymentState = model.paymentState as\n | { preAuth?: { status?: string } }\n | undefined\n return !paymentState?.preAuth?.status\n }\n return false\n })\n\n return {\n ...viewModel,\n backLink: this.getBackLink(request, context),\n context,\n showTitle,\n components,\n errors,\n allowSaveAndExit: this.shouldShowSaveAndExit(request.server),\n showSubmitButton: !hasIncompletePayment\n }\n }\n\n getRelevantPath(request: AnyFormRequest, context: FormContext) {\n const { paths } = context\n\n const startPath = this.getStartPath()\n const relevantPath = paths.at(-1) ?? startPath\n\n return !paths.length\n ? startPath // First possible path\n : relevantPath // Last possible path\n }\n\n /**\n * Apply conditions to evaluation state to determine next page path\n */\n getNextPath(context: FormContext) {\n const { model, next, path } = this\n const { evaluationState } = context\n\n const summaryPath = this.getSummaryPath()\n const statusPath = this.getStatusPath()\n\n // Walk from summary page (no next links) to status page\n let defaultPath = path === summaryPath ? statusPath : undefined\n\n if (model.engine === Engine.V2) {\n if (this.pageDef.controller !== ControllerType.Terminal) {\n const { pages } = this.model\n const pageIndex = pages.indexOf(this)\n\n // The \"next\" page is the first found after the current which is\n // either unconditional or has a condition that evaluates to \"true\"\n const nextPage = pages.slice(pageIndex + 1).find((page) => {\n const { condition } = page\n\n if (condition) {\n const conditionResult = condition.fn(evaluationState)\n\n if (!conditionResult) {\n return false\n }\n }\n\n return true\n })\n\n return nextPage?.path ?? defaultPath\n } else {\n return defaultPath\n }\n }\n\n const nextLink = next.find((link) => {\n const { condition } = link\n\n if (condition) {\n return model.conditions[condition]?.fn(evaluationState) ?? false\n }\n\n defaultPath = link.path\n return false\n })\n\n return nextLink?.path ?? defaultPath\n }\n\n /**\n * Gets the form payload (from state) for this page only\n */\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ): FormPayload {\n const { collection } = this\n\n // Form params from request\n const params = this.getFormParams(request)\n\n // Form payload from state\n const payload = collection.getFormDataFromState(state)\n\n return {\n ...params,\n ...payload\n }\n }\n\n /**\n * Gets form params (from payload) for this page only\n */\n getFormParams(request?: FormContextRequest): FormPayloadParams {\n const { payload } = request ?? {}\n\n const result = paramsSchema.validate(payload, {\n abortEarly: false,\n stripUnknown: true\n })\n\n return result.value as FormPayloadParams\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ): FormState {\n return this.collection.getStateFromValidForm(payload)\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n return getErrors(details)\n }\n\n async getState(request: AnyFormRequest) {\n const { query } = request\n\n // Skip get for preview URL direct access\n if ('force' in query) {\n return {}\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.getState(request)\n }\n\n async setState(request: AnyFormRequest, state: FormSubmissionState) {\n const { query } = request\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return state\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, state)\n }\n\n async mergeState(\n request: AnyFormRequest,\n state: FormSubmissionState,\n update: object\n ) {\n const { query } = request\n\n // Merge state before set\n const updated = merge(state, update)\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return updated\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, updated)\n }\n\n filterConditionalComponents(\n viewModel: FormPageViewModel,\n model: FormModel,\n evaluationState: Partial<Record<string, FormStateValue>>\n ) {\n // Filter our components based on their conditions using our evaluated state\n let filtered = viewModel.components.filter((component) => {\n if (\n (!!component.model.content ||\n component.type === ComponentType.Details) &&\n component.model.condition\n ) {\n const condition = model.conditions[component.model.condition]\n return condition?.fn(evaluationState)\n }\n return true\n })\n\n /**\n * For conditional reveal components (which we no longer support until GDS resolves the related accessibility issues {@link https://github.com/alphagov/govuk-frontend/issues/1991}\n */\n filtered = filtered.map((component) => {\n const evaluatedComponent = component\n const content = evaluatedComponent.model.content\n if (Array.isArray(content)) {\n evaluatedComponent.model.content = content.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n // apply condition to items for radios, checkboxes etc\n const items = evaluatedComponent.model.items\n\n if (Array.isArray(items)) {\n evaluatedComponent.model.items = items.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n\n return evaluatedComponent\n })\n\n return filtered\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { collection, model, viewName } = this\n const { evaluationState } = context\n\n // Copy any URL params into the form state (if not already done so)\n if (await prefillStateFromQueryParameters(request, this)) {\n // Forward to same page without query string\n return h.redirect(request.url.pathname)\n }\n\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n const flashedError = request.yar.flash(COMPONENT_STATE_ERROR)\n const flashedErrors = !Array.isArray(flashedError) ? [flashedError] : []\n\n viewModel.errors = (viewModel.errors ?? []).concat(flashedErrors)\n\n const paymentExpiredFlash = request.yar.flash(\n PAYMENT_EXPIRED_NOTIFICATION\n )\n viewModel.showPaymentExpiredNotification =\n !Array.isArray(paymentExpiredFlash)\n\n /**\n * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it\n */\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n async hasMissingNotificationEmail(\n request: FormRequest,\n context: FormContext\n ) {\n const { path } = this\n const { params } = request\n const { isForceAccess } = context\n\n const startPath = this.getStartPath()\n const summaryPath = this.getSummaryPath()\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Warn the user if the form has no notification email set only on start page and summary page\n if ([startPath, summaryPath].includes(path) && !isForceAccess) {\n const { notificationEmail } = await getFormMetadata(params.slug)\n return !notificationEmail\n }\n\n return false\n }\n\n /**\n * Get the back link for a given progress.\n */\n protected getBackLink(\n request: FormContextRequest,\n context: FormContext\n ): BackLink | undefined {\n const { pageDef } = this\n const { path, query } = request\n const { returnUrl } = query\n const { paths } = context\n\n const itemId = this.getItemId(request)\n\n // Check answers back link\n if (returnUrl) {\n return {\n text:\n hasRepeater(pageDef) && itemId\n ? 'Go back to add another'\n : 'Go back to check answers',\n href: returnUrl\n }\n }\n\n // Item delete pages etc\n const backPath =\n itemId && !path.endsWith(itemId)\n ? paths.at(-1) // Back to main page\n : paths.at(-2) // Back to previous page\n\n // No back link\n if (!backPath) {\n return\n }\n\n // Default back link\n return {\n text: 'Back',\n href: this.getHref(backPath)\n }\n }\n\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { collection, viewName, model } = this\n const { isForceAccess, state, evaluationState } = context\n const action = request.payload.action\n\n if (action?.startsWith(FormAction.External)) {\n return await this.dispatchExternal(request, h, context)\n }\n\n /**\n * If there are any errors, render the page with the parsed errors\n * @todo Refactor to match POST REDIRECT GET pattern\n */\n if (context.errors || isForceAccess) {\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n return h.view(viewName, viewModel)\n }\n\n // Save state\n await this.setState(request, state)\n\n // Check if this is a save-and-exit action\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n // Proceed to the next page\n return this.proceed(request, h, this.getNextPath(context))\n }\n }\n\n private async dispatchExternal(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n ) {\n const { externalComponents } = getComponentsByType()\n const action = request.payload.action ?? ''\n\n // Find the external action and arguments\n // `external-{componentName}--{argname1}:{argvalue1}--{argname2}:{argvalue2}`\n // E.g. external-abcdef--amount:10--step:manual\n const externalActionsWithArgs = action\n .slice(`${FormAction.External}-`.length)\n .split('--')\n\n const externalActionArgs = externalActionsWithArgs\n .slice(1)\n .map((arg) => arg.split(':'))\n\n const args = Object.fromEntries(externalActionArgs) as Record<\n string,\n string\n >\n\n const componentName = externalActionsWithArgs[0]\n const component = this.model.componentDefMap.get(componentName)\n const componentType = component?.type\n\n if (!componentType) {\n throw Boom.internal(\n `External component of type ${componentType} not found`\n )\n }\n\n const selectedComponent = externalComponents.get(componentType)\n\n if (!selectedComponent) {\n throw Boom.internal(`External component ${componentName} not found`)\n }\n\n // Stash payload without crumb and action\n const stashedPayload = {\n ...context.payload,\n crumb: undefined,\n action: undefined\n }\n request.yar.flash(EXTERNAL_STATE_PAYLOAD, stashedPayload, true)\n\n // Clear any previous state appendage\n request.yar.clear(EXTERNAL_STATE_APPENDAGE)\n\n // Determine if this is a live form (not preview/draft)\n const { state, isPreview } = checkFormStatus(request.params)\n const isLive = state === FormStatus.Live\n\n return await selectedComponent.dispatcher(request, h, {\n component,\n controller: this,\n sourceUrl: `${request.url.pathname}${request.url.search}`,\n actionArgs: args,\n isLive,\n isPreview\n })\n }\n\n proceed(\n request: FormContextRequest,\n h: FormResponseToolkit,\n nextPath?: string\n ) {\n const nextUrl = nextPath\n ? this.getHref(nextPath) // Redirect to next page\n : this.href // Redirect to current page (refresh)\n\n return proceed(request, h, nextUrl)\n }\n\n /**\n * Handle save-and-exit action\n */\n handleSaveAndExit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const saveAndExit = getSaveAndExitHelpers(request.server)\n\n if (!saveAndExit) {\n throw Boom.internal('Server misconfigured for save and exit')\n }\n\n return saveAndExit(request, h, context)\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n payload: {\n parse: true,\n maxBytes: Number.MAX_SAFE_INTEGER,\n failAction: 'ignore'\n },\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"QuestionPageController.js","names":["ComponentType","ControllerType","Engine","hasComponents","hasNext","hasRepeater","isPaymentPage","Boom","COMPONENT_STATE_ERROR","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","PAYMENT_EXPIRED_NOTIFICATION","ComponentCollection","PaymentField","optionalText","checkFormStatus","getCacheService","getErrors","getSaveAndExitHelpers","normalisePath","proceed","PageController","prefillStateFromQueryParameters","getComponentsByType","formatCurrency","FormAction","FormStatus","actionSchema","crumbSchema","paramsSchema","merge","QuestionPageController","collection","errorSummaryTitle","allowSaveAndExit","constructor","model","pageDef","components","page","formSchema","keys","crumb","action","next","def","filter","path","linkPath","pages","some","pagePath","allowContinue","engine","V2","controller","Terminal","length","getItemId","request","itemId","getFormParams","params","getViewModel","context","viewModel","query","payload","errors","pageTitle","showTitle","formComponents","isFormComponent","fieldset","label","isPageHeading","labelOrLegend","legend","size","classes","isOptional","fields","at","options","required","text","comp","paymentField","find","f","resolvedAmount","resolveAmount","evaluationState","amount","hasIncompletePayment","paymentState","preAuth","status","backLink","getBackLink","shouldShowSaveAndExit","server","showSubmitButton","getRelevantPath","paths","startPath","getStartPath","relevantPath","getNextPath","summaryPath","getSummaryPath","statusPath","getStatusPath","defaultPath","undefined","pageIndex","indexOf","nextPage","slice","condition","conditionResult","fn","nextLink","link","conditions","getFormDataFromState","state","result","validate","abortEarly","stripUnknown","value","getStateFromValidForm","details","getState","cacheService","setState","mergeState","update","updated","filterConditionalComponents","filtered","component","content","type","Details","map","evaluatedComponent","Array","isArray","item","items","makeGetRouteHandler","h","viewName","redirect","url","pathname","getViewErrors","flashedError","yar","flash","flashedErrors","concat","paymentExpiredFlash","showPaymentExpiredNotification","hasMissingNotificationEmail","view","isForceAccess","formsService","services","getFormMetadata","includes","notificationEmail","slug","returnUrl","href","backPath","endsWith","getHref","makePostRouteHandler","startsWith","External","dispatchExternal","SaveAndExit","handleSaveAndExit","externalComponents","externalActionsWithArgs","split","externalActionArgs","arg","args","Object","fromEntries","componentName","componentDefMap","get","componentType","internal","selectedComponent","stashedPayload","clear","isPreview","isLive","Live","dispatcher","sourceUrl","search","actionArgs","nextPath","nextUrl","saveAndExit","getRouteOptions","ext","onPostHandler","method","_request","continue","postRouteOptions","parse","maxBytes","Number","MAX_SAFE_INTEGER","failAction"],"sources":["../../../../../src/server/plugins/engine/pageControllers/QuestionPageController.ts"],"sourcesContent":["import {\n ComponentType,\n ControllerType,\n Engine,\n hasComponents,\n hasNext,\n hasRepeater,\n isPaymentPage,\n type Link,\n type Page\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\nimport { type ValidationErrorItem } from 'joi'\n\nimport {\n COMPONENT_STATE_ERROR,\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD,\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 { optionalText } from '~/src/server/plugins/engine/components/constants.js'\nimport { type BackLink } from '~/src/server/plugins/engine/components/types.js'\nimport {\n checkFormStatus,\n getCacheService,\n getErrors,\n getSaveAndExitHelpers,\n normalisePath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { PageController } from '~/src/server/plugins/engine/pageControllers/PageController.js'\nimport { prefillStateFromQueryParameters } from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport {\n type AnyFormRequest,\n type FormContext,\n type FormContextRequest,\n type FormPageViewModel,\n type FormPayload,\n type FormPayloadParams,\n type FormState,\n type FormStateValue,\n type FormSubmissionState\n} from '~/src/server/plugins/engine/types.js'\nimport { getComponentsByType } from '~/src/server/plugins/engine/validationHelpers.js'\nimport { formatCurrency } from '~/src/server/plugins/payment/helper.js'\nimport {\n FormAction,\n FormStatus,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormRequestRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\nimport {\n actionSchema,\n crumbSchema,\n paramsSchema\n} from '~/src/server/schemas/index.js'\nimport { merge } from '~/src/server/services/cacheService.js'\n\nexport class QuestionPageController extends PageController {\n collection: ComponentCollection\n errorSummaryTitle = 'There is a problem'\n allowSaveAndExit = true\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponents(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n\n this.collection.formSchema = this.collection.formSchema.keys({\n crumb: crumbSchema,\n action: actionSchema\n })\n }\n\n get next(): Link[] {\n const { def, pageDef } = this\n\n if (!hasNext(pageDef)) {\n return []\n }\n\n // Remove stale links\n return pageDef.next.filter(({ path }) => {\n const linkPath = normalisePath(path)\n\n return def.pages.some((page) => {\n const pagePath = normalisePath(page.path)\n return pagePath === linkPath\n })\n })\n }\n\n get allowContinue(): boolean {\n if (this.model.engine === Engine.V2) {\n return this.pageDef.controller !== ControllerType.Terminal\n }\n\n return this.next.length > 0\n }\n\n getItemId(request?: FormContextRequest) {\n const { itemId } = this.getFormParams(request)\n return itemId ?? request?.params.itemId\n }\n\n /**\n * Used for mapping form payloads and errors to govuk-frontend's template api, so a page can be rendered\n * @param request - the hapi request\n * @param context - the form context\n */\n getViewModel(\n request: FormContextRequest,\n context: FormContext\n ): FormPageViewModel {\n const { collection, viewModel } = this\n const { query } = request\n const { payload, errors } = context\n\n let { pageTitle, showTitle } = viewModel\n\n const components = collection.getViewModel(payload, errors, query)\n const formComponents = components.filter(\n ({ isFormComponent }) => isFormComponent\n )\n\n // Single form component? Hide title and customise label or legend instead\n if (formComponents.length === 1) {\n const { model } = formComponents[0]\n const { fieldset, label } = model\n\n // Set as page heading when not following other content\n const isPageHeading = formComponents[0] === components[0]\n\n // Check for legend or label\n const labelOrLegend = fieldset?.legend ?? label\n\n // Use legend or label as page heading\n if (labelOrLegend) {\n const size = isPageHeading ? 'l' : 'm'\n\n labelOrLegend.classes =\n labelOrLegend === label\n ? `govuk-label--${size}`\n : `govuk-fieldset__legend--${size}`\n\n if (isPageHeading) {\n labelOrLegend.isPageHeading = isPageHeading\n\n // Check for optional in label\n const isOptional =\n this.collection.fields.at(0)?.options.required === false\n\n if (pageTitle) {\n labelOrLegend.text = isOptional\n ? `${pageTitle}${optionalText}`\n : pageTitle\n }\n\n pageTitle = pageTitle || labelOrLegend.text\n }\n }\n\n showTitle = !isPageHeading\n } else if (formComponents.length > 1) {\n // When there is more than one form component,\n // adjust the label/legends to give equal prominence\n for (const { model } of formComponents) {\n if (model.fieldset?.legend) {\n model.fieldset.legend.classes = 'govuk-fieldset__legend--m'\n }\n if (model.label) {\n model.label.classes = 'govuk-label--m'\n }\n }\n }\n\n // Override payment amount display with resolved conditional amount\n // getViewModel() only has the current page's payload, not full form state.\n // The full state is available via context.evaluationState.\n for (const comp of components) {\n if ('amount' in comp.model && 'paymentState' in comp.model) {\n const paymentField = this.collection.fields.find(\n (f): f is PaymentField => f instanceof PaymentField\n )\n if (paymentField) {\n const resolvedAmount = PaymentField.resolveAmount(\n paymentField.options,\n this.model,\n context.evaluationState\n )\n comp.model.amount = formatCurrency(resolvedAmount)\n }\n }\n }\n\n const hasIncompletePayment = components.some(({ model }) => {\n if ('paymentState' in model) {\n const paymentState = model.paymentState as\n | { preAuth?: { status?: string } }\n | undefined\n return !paymentState?.preAuth?.status\n }\n return false\n })\n\n return {\n ...viewModel,\n backLink: this.getBackLink(request, context),\n context,\n showTitle,\n components,\n errors,\n allowSaveAndExit:\n this.shouldShowSaveAndExit(request.server) &&\n !isPaymentPage(this.pageDef),\n showSubmitButton: !hasIncompletePayment\n }\n }\n\n getRelevantPath(request: AnyFormRequest, context: FormContext) {\n const { paths } = context\n\n const startPath = this.getStartPath()\n const relevantPath = paths.at(-1) ?? startPath\n\n return !paths.length\n ? startPath // First possible path\n : relevantPath // Last possible path\n }\n\n /**\n * Apply conditions to evaluation state to determine next page path\n */\n getNextPath(context: FormContext) {\n const { model, next, path } = this\n const { evaluationState } = context\n\n const summaryPath = this.getSummaryPath()\n const statusPath = this.getStatusPath()\n\n // Walk from summary page (no next links) to status page\n let defaultPath = path === summaryPath ? statusPath : undefined\n\n if (model.engine === Engine.V2) {\n if (this.pageDef.controller !== ControllerType.Terminal) {\n const { pages } = this.model\n const pageIndex = pages.indexOf(this)\n\n // The \"next\" page is the first found after the current which is\n // either unconditional or has a condition that evaluates to \"true\"\n const nextPage = pages.slice(pageIndex + 1).find((page) => {\n const { condition } = page\n\n if (condition) {\n const conditionResult = condition.fn(evaluationState)\n\n if (!conditionResult) {\n return false\n }\n }\n\n // Skip payment pages in the normal page walk.\n // Users reach the payment page via \"Pay and submit\" on CYA,\n // not by navigating forward through the form.\n if (isPaymentPage(page.pageDef)) {\n return false\n }\n\n return true\n })\n\n return nextPage?.path ?? defaultPath\n } else {\n return defaultPath\n }\n }\n\n const nextLink = next.find((link) => {\n const { condition } = link\n\n if (condition) {\n return model.conditions[condition]?.fn(evaluationState) ?? false\n }\n\n defaultPath = link.path\n return false\n })\n\n return nextLink?.path ?? defaultPath\n }\n\n /**\n * Gets the form payload (from state) for this page only\n */\n getFormDataFromState(\n request: FormContextRequest | undefined,\n state: FormSubmissionState\n ): FormPayload {\n const { collection } = this\n\n // Form params from request\n const params = this.getFormParams(request)\n\n // Form payload from state\n const payload = collection.getFormDataFromState(state)\n\n return {\n ...params,\n ...payload\n }\n }\n\n /**\n * Gets form params (from payload) for this page only\n */\n getFormParams(request?: FormContextRequest): FormPayloadParams {\n const { payload } = request ?? {}\n\n const result = paramsSchema.validate(payload, {\n abortEarly: false,\n stripUnknown: true\n })\n\n return result.value as FormPayloadParams\n }\n\n getStateFromValidForm(\n request: FormContextRequest,\n state: FormSubmissionState,\n payload: FormPayload\n ): FormState {\n return this.collection.getStateFromValidForm(payload)\n }\n\n getErrors(details?: ValidationErrorItem[]) {\n return getErrors(details)\n }\n\n async getState(request: AnyFormRequest) {\n const { query } = request\n\n // Skip get for preview URL direct access\n if ('force' in query) {\n return {}\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.getState(request)\n }\n\n async setState(request: AnyFormRequest, state: FormSubmissionState) {\n const { query } = request\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return state\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, state)\n }\n\n async mergeState(\n request: AnyFormRequest,\n state: FormSubmissionState,\n update: object\n ) {\n const { query } = request\n\n // Merge state before set\n const updated = merge(state, update)\n\n // Skip set for preview URL direct access\n if ('force' in query) {\n return updated\n }\n\n const cacheService = getCacheService(request.server)\n\n return cacheService.setState(request, updated)\n }\n\n filterConditionalComponents(\n viewModel: FormPageViewModel,\n model: FormModel,\n evaluationState: Partial<Record<string, FormStateValue>>\n ) {\n // Filter our components based on their conditions using our evaluated state\n let filtered = viewModel.components.filter((component) => {\n if (\n (!!component.model.content ||\n component.type === ComponentType.Details) &&\n component.model.condition\n ) {\n const condition = model.conditions[component.model.condition]\n return condition?.fn(evaluationState)\n }\n return true\n })\n\n /**\n * For conditional reveal components (which we no longer support until GDS resolves the related accessibility issues {@link https://github.com/alphagov/govuk-frontend/issues/1991}\n */\n filtered = filtered.map((component) => {\n const evaluatedComponent = component\n const content = evaluatedComponent.model.content\n if (Array.isArray(content)) {\n evaluatedComponent.model.content = content.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n // apply condition to items for radios, checkboxes etc\n const items = evaluatedComponent.model.items\n\n if (Array.isArray(items)) {\n evaluatedComponent.model.items = items.filter((item) =>\n item.condition\n ? model.conditions[item.condition]?.fn(evaluationState)\n : true\n )\n }\n\n return evaluatedComponent\n })\n\n return filtered\n }\n\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { collection, model, viewName } = this\n const { evaluationState } = context\n\n // Copy any URL params into the form state (if not already done so)\n if (await prefillStateFromQueryParameters(request, this)) {\n // Forward to same page without query string\n return h.redirect(request.url.pathname)\n }\n\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n const flashedError = request.yar.flash(COMPONENT_STATE_ERROR)\n const flashedErrors = !Array.isArray(flashedError) ? [flashedError] : []\n\n viewModel.errors = (viewModel.errors ?? []).concat(flashedErrors)\n\n const paymentExpiredFlash = request.yar.flash(\n PAYMENT_EXPIRED_NOTIFICATION\n )\n viewModel.showPaymentExpiredNotification =\n !Array.isArray(paymentExpiredFlash)\n\n /**\n * Content components can be hidden based on a condition. If the condition evaluates to true, it is safe to be kept, otherwise discard it\n */\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n async hasMissingNotificationEmail(\n request: FormRequest,\n context: FormContext\n ) {\n const { path } = this\n const { params } = request\n const { isForceAccess } = context\n\n const startPath = this.getStartPath()\n const summaryPath = this.getSummaryPath()\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n // Warn the user if the form has no notification email set only on start page and summary page\n if ([startPath, summaryPath].includes(path) && !isForceAccess) {\n const { notificationEmail } = await getFormMetadata(params.slug)\n return !notificationEmail\n }\n\n return false\n }\n\n /**\n * Get the back link for a given progress.\n */\n protected getBackLink(\n request: FormContextRequest,\n context: FormContext\n ): BackLink | undefined {\n const { pageDef } = this\n const { path, query } = request\n const { returnUrl } = query\n const { paths } = context\n\n const itemId = this.getItemId(request)\n\n // Check answers back link\n if (returnUrl) {\n return {\n text:\n hasRepeater(pageDef) && itemId\n ? 'Go back to add another'\n : 'Go back to check answers',\n href: returnUrl\n }\n }\n\n // Item delete pages etc\n const backPath =\n itemId && !path.endsWith(itemId)\n ? paths.at(-1) // Back to main page\n : paths.at(-2) // Back to previous page\n\n // No back link\n if (!backPath) {\n return\n }\n\n // Default back link\n return {\n text: 'Back',\n href: this.getHref(backPath)\n }\n }\n\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { collection, viewName, model } = this\n const { isForceAccess, state, evaluationState } = context\n const action = request.payload.action\n\n if (action?.startsWith(FormAction.External)) {\n return await this.dispatchExternal(request, h, context)\n }\n\n /**\n * If there are any errors, render the page with the parsed errors\n * @todo Refactor to match POST REDIRECT GET pattern\n */\n if (context.errors || isForceAccess) {\n const viewModel = this.getViewModel(request, context)\n viewModel.errors = collection.getViewErrors(viewModel.errors)\n\n // Filter our components based on their conditions using our evaluated state\n viewModel.components = this.filterConditionalComponents(\n viewModel,\n model,\n evaluationState\n )\n\n return h.view(viewName, viewModel)\n }\n\n // Save state\n await this.setState(request, state)\n\n // Check if this is a save-and-exit action\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n // Proceed to the next page\n return this.proceed(request, h, this.getNextPath(context))\n }\n }\n\n private async dispatchExternal(\n request: FormRequestPayload,\n h: FormResponseToolkit,\n context: FormContext\n ) {\n const { externalComponents } = getComponentsByType()\n const action = request.payload.action ?? ''\n\n // Find the external action and arguments\n // `external-{componentName}--{argname1}:{argvalue1}--{argname2}:{argvalue2}`\n // E.g. external-abcdef--amount:10--step:manual\n const externalActionsWithArgs = action\n .slice(`${FormAction.External}-`.length)\n .split('--')\n\n const externalActionArgs = externalActionsWithArgs\n .slice(1)\n .map((arg) => arg.split(':'))\n\n const args = Object.fromEntries(externalActionArgs) as Record<\n string,\n string\n >\n\n const componentName = externalActionsWithArgs[0]\n const component = this.model.componentDefMap.get(componentName)\n const componentType = component?.type\n\n if (!componentType) {\n throw Boom.internal(\n `External component of type ${componentType} not found`\n )\n }\n\n const selectedComponent = externalComponents.get(componentType)\n\n if (!selectedComponent) {\n throw Boom.internal(`External component ${componentName} not found`)\n }\n\n // Stash payload without crumb and action\n const stashedPayload = {\n ...context.payload,\n crumb: undefined,\n action: undefined\n }\n request.yar.flash(EXTERNAL_STATE_PAYLOAD, stashedPayload, true)\n\n // Clear any previous state appendage\n request.yar.clear(EXTERNAL_STATE_APPENDAGE)\n\n // Determine if this is a live form (not preview/draft)\n const { state, isPreview } = checkFormStatus(request.params)\n const isLive = state === FormStatus.Live\n\n return await selectedComponent.dispatcher(request, h, {\n component,\n controller: this,\n sourceUrl: `${request.url.pathname}${request.url.search}`,\n actionArgs: args,\n isLive,\n isPreview\n })\n }\n\n proceed(\n request: FormContextRequest,\n h: FormResponseToolkit,\n nextPath?: string\n ) {\n const nextUrl = nextPath\n ? this.getHref(nextPath) // Redirect to next page\n : this.href // Redirect to current page (refresh)\n\n return proceed(request, h, nextUrl)\n }\n\n /**\n * Handle save-and-exit action\n */\n handleSaveAndExit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const saveAndExit = getSaveAndExitHelpers(request.server)\n\n if (!saveAndExit) {\n throw Boom.internal('Server misconfigured for save and exit')\n }\n\n return saveAndExit(request, h, context)\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get getRouteOptions(): RouteOptions<FormRequestRefs> {\n return {\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n\n /**\n * {@link https://hapi.dev/api/?v=20.1.2#route-options}\n */\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n payload: {\n parse: true,\n maxBytes: Number.MAX_SAFE_INTEGER,\n failAction: 'ignore'\n },\n ext: {\n onPostHandler: {\n method(_request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -11,6 +11,11 @@ export declare class SummaryPageController extends QuestionPageController {
|
|
|
11
11
|
/**
|
|
12
12
|
* The controller which is used when Page["controller"] is defined as "./pages/summary.js"
|
|
13
13
|
*/
|
|
14
|
+
/**
|
|
15
|
+
* Finds the PaymentField component across all pages in the model.
|
|
16
|
+
* Payment pages are skipped in the normal page walk
|
|
17
|
+
*/
|
|
18
|
+
private findPaymentField;
|
|
14
19
|
constructor(model: FormModel, pageDef: Page);
|
|
15
20
|
getSummaryViewModel(request: FormContextRequest, context: FormContext): SummaryViewModel;
|
|
16
21
|
private buildPaymentDetails;
|
|
@@ -18,6 +23,11 @@ export declare class SummaryPageController extends QuestionPageController {
|
|
|
18
23
|
* Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,
|
|
19
24
|
*/
|
|
20
25
|
makeGetRouteHandler(): (request: FormRequest, context: FormContext, h: FormResponseToolkit) => Promise<import("@hapi/hapi").ResponseObject>;
|
|
26
|
+
/**
|
|
27
|
+
* Checks if the resolved payment amount has changed since pre-auth
|
|
28
|
+
* and invalidates stale payment state if so.
|
|
29
|
+
*/
|
|
30
|
+
private reconcilePaymentState;
|
|
21
31
|
/**
|
|
22
32
|
* Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.
|
|
23
33
|
* If a form is incomplete, a user will be redirected to the start page.
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { hasComponentsEvenIfNoNext } from '@defra/forms-model';
|
|
2
2
|
import Boom from '@hapi/boom';
|
|
3
|
+
import { StatusCodes } from 'http-status-codes';
|
|
3
4
|
import { COMPONENT_STATE_ERROR, PAYMENT_EXPIRED_NOTIFICATION } from "../../../constants.js";
|
|
4
5
|
import { ComponentCollection } from "../components/ComponentCollection.js";
|
|
5
6
|
import { PaymentField } from "../components/PaymentField.js";
|
|
@@ -17,6 +18,13 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
17
18
|
* The controller which is used when Page["controller"] is defined as "./pages/summary.js"
|
|
18
19
|
*/
|
|
19
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Finds the PaymentField component across all pages in the model.
|
|
23
|
+
* Payment pages are skipped in the normal page walk
|
|
24
|
+
*/
|
|
25
|
+
findPaymentField() {
|
|
26
|
+
return this.model.pages.flatMap(page => page.collection.fields).find(field => field instanceof PaymentField);
|
|
27
|
+
}
|
|
20
28
|
constructor(model, pageDef) {
|
|
21
29
|
super(model, pageDef);
|
|
22
30
|
this.viewName = 'summary';
|
|
@@ -37,13 +45,20 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
37
45
|
errors,
|
|
38
46
|
state
|
|
39
47
|
} = context;
|
|
40
|
-
const paymentField =
|
|
48
|
+
const paymentField = this.findPaymentField();
|
|
41
49
|
if (paymentField) {
|
|
50
|
+
const resolvedAmount = PaymentField.resolveAmount(paymentField.options, this.model, state);
|
|
42
51
|
const paymentState = paymentField.getPaymentStateFromState(state);
|
|
43
|
-
if (paymentState) {
|
|
52
|
+
if (paymentState?.amount === resolvedAmount) {
|
|
44
53
|
viewModel.paymentState = paymentState;
|
|
45
54
|
viewModel.paymentDetails = this.buildPaymentDetails(paymentField, paymentState);
|
|
46
55
|
}
|
|
56
|
+
if (resolvedAmount > 0) {
|
|
57
|
+
viewModel.paymentRequired = true;
|
|
58
|
+
}
|
|
59
|
+
if (paymentState && paymentState.preAuth?.status === 'success' && paymentState.amount === resolvedAmount) {
|
|
60
|
+
viewModel.paymentPreAuthorized = true;
|
|
61
|
+
}
|
|
47
62
|
}
|
|
48
63
|
const components = this.collection.getViewModel(payload, errors, query);
|
|
49
64
|
viewModel.backLink = this.getBackLink(request, context);
|
|
@@ -105,12 +120,36 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
105
120
|
const {
|
|
106
121
|
viewName
|
|
107
122
|
} = this;
|
|
123
|
+
|
|
124
|
+
// After GOV.UK Pay callback, auto-submit the form instead of
|
|
125
|
+
// showing CYA again. The payment is already pre-authorized.
|
|
126
|
+
if (request.query.paymentComplete === 'true') {
|
|
127
|
+
return this.handleFormSubmit(request, context, h);
|
|
128
|
+
}
|
|
129
|
+
await this.reconcilePaymentState(request, context);
|
|
108
130
|
const viewModel = this.getSummaryViewModel(request, context);
|
|
109
131
|
viewModel.hasMissingNotificationEmail = await this.hasMissingNotificationEmail(request, context);
|
|
110
132
|
return h.view(viewName, viewModel);
|
|
111
133
|
};
|
|
112
134
|
}
|
|
113
135
|
|
|
136
|
+
/**
|
|
137
|
+
* Checks if the resolved payment amount has changed since pre-auth
|
|
138
|
+
* and invalidates stale payment state if so.
|
|
139
|
+
*/
|
|
140
|
+
async reconcilePaymentState(request, context) {
|
|
141
|
+
const paymentField = this.findPaymentField();
|
|
142
|
+
if (!paymentField) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
const resolvedAmount = PaymentField.resolveAmount(paymentField.options, this.model, context.state);
|
|
146
|
+
const paymentState = paymentField.getPaymentStateFromState(context.state);
|
|
147
|
+
if (paymentState && paymentState.amount !== resolvedAmount) {
|
|
148
|
+
const cacheService = getCacheService(request.server);
|
|
149
|
+
await cacheService.resetComponentStates(request, [paymentField.name]);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
114
153
|
/**
|
|
115
154
|
* Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.
|
|
116
155
|
* If a form is incomplete, a user will be redirected to the start page.
|
|
@@ -204,6 +243,13 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
204
243
|
return this.proceed(request, h, error.component.page?.path);
|
|
205
244
|
}
|
|
206
245
|
}
|
|
246
|
+
|
|
247
|
+
// Payment page is skipped in the page walk, so proceed() would redirect
|
|
248
|
+
// to an earlier page. For PaymentIncomplete, redirect directly to the
|
|
249
|
+
// payment page using its href.
|
|
250
|
+
if (error.errorType === PaymentErrorTypes.PaymentIncomplete && error.component.page) {
|
|
251
|
+
return h.redirect(error.component.page.getHref(error.component.page.path)).code(StatusCodes.SEE_OTHER);
|
|
252
|
+
}
|
|
207
253
|
const govukError = createError(error.component.name, error.userMessage);
|
|
208
254
|
request.yar.flash(COMPONENT_STATE_ERROR, govukError, true);
|
|
209
255
|
const redirectPath = error.shouldResetState ? error.component.page?.path : undefined;
|
|
@@ -233,8 +279,8 @@ export class SummaryPageController extends QuestionPageController {
|
|
|
233
279
|
}
|
|
234
280
|
}
|
|
235
281
|
export async function submitForm(context, formMetadata, request, summaryViewModel, model, emailAddress) {
|
|
236
|
-
await finaliseComponents(request, formMetadata, context);
|
|
237
|
-
const paymentWasCaptured = hasPaymentBeenCaptured(context);
|
|
282
|
+
await finaliseComponents(request, formMetadata, context, model);
|
|
283
|
+
const paymentWasCaptured = hasPaymentBeenCaptured(context, model);
|
|
238
284
|
const formStatus = checkFormStatus(request.params);
|
|
239
285
|
const logTags = ['submit', 'submissionApi'];
|
|
240
286
|
request.logger.info(logTags, 'Preparing email', formStatus);
|
|
@@ -257,8 +303,8 @@ export async function submitForm(context, formMetadata, request, summaryViewMode
|
|
|
257
303
|
/**
|
|
258
304
|
* Checks if any payment component has been captured
|
|
259
305
|
*/
|
|
260
|
-
function hasPaymentBeenCaptured(context) {
|
|
261
|
-
for (const page of
|
|
306
|
+
function hasPaymentBeenCaptured(context, model) {
|
|
307
|
+
for (const page of model.pages) {
|
|
262
308
|
for (const field of page.collection.fields) {
|
|
263
309
|
if (field instanceof PaymentField) {
|
|
264
310
|
const paymentState = field.getPaymentStateFromState(context.state);
|
|
@@ -278,13 +324,11 @@ function hasPaymentBeenCaptured(context) {
|
|
|
278
324
|
* - file uploads which are 'persisted' before submission
|
|
279
325
|
* - payments which are 'captured' before submission
|
|
280
326
|
*/
|
|
281
|
-
async function finaliseComponents(request, metadata, context) {
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
by handleFormSubmit
|
|
287
|
-
*/
|
|
327
|
+
async function finaliseComponents(request, metadata, context, model) {
|
|
328
|
+
// Get fields from relevant pages (normal components)
|
|
329
|
+
// plus PaymentField from all pages (payment page is skipped in the page walk)
|
|
330
|
+
const allFields = new Set([...context.relevantPages.flatMap(page => page.collection.fields), ...model.pages.flatMap(page => page.collection.fields).filter(field => field instanceof PaymentField)]);
|
|
331
|
+
for (const component of allFields) {
|
|
288
332
|
await component.onSubmit(request, metadata, context);
|
|
289
333
|
}
|
|
290
334
|
}
|