@defra/forms-engine-plugin 4.8.0 → 4.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (50) hide show
  1. package/.public/javascripts/shared.min.js +1 -1
  2. package/.public/javascripts/shared.min.js.map +1 -1
  3. package/.server/client/images/esri-logo.png +0 -0
  4. package/.server/client/javascripts/map.js +2 -0
  5. package/.server/client/javascripts/map.js.map +1 -1
  6. package/.server/server/forms/payment-v2-test.yaml +341 -0
  7. package/.server/server/plugins/engine/components/PaymentField.d.ts +7 -0
  8. package/.server/server/plugins/engine/components/PaymentField.js +58 -6
  9. package/.server/server/plugins/engine/components/PaymentField.js.map +1 -1
  10. package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +2 -0
  11. package/.server/server/plugins/engine/models/SummaryViewModel.js +2 -0
  12. package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
  13. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +24 -2
  14. package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
  15. package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +10 -0
  16. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +57 -13
  17. package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
  18. package/.server/server/plugins/engine/pageControllers/errors.d.ts +1 -1
  19. package/.server/server/plugins/engine/pageControllers/errors.js +2 -2
  20. package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -1
  21. package/.server/server/plugins/engine/routes/index.js +5 -2
  22. package/.server/server/plugins/engine/routes/index.js.map +1 -1
  23. package/.server/server/plugins/engine/routes/payment.js +6 -1
  24. package/.server/server/plugins/engine/routes/payment.js.map +1 -1
  25. package/.server/server/plugins/engine/routes/payment.test.js +3 -3
  26. package/.server/server/plugins/engine/routes/payment.test.js.map +1 -1
  27. package/.server/server/plugins/engine/services/localFormsService.js +6 -0
  28. package/.server/server/plugins/engine/services/localFormsService.js.map +1 -1
  29. package/.server/server/plugins/engine/views/summary.html +2 -1
  30. package/.server/server/plugins/payment/service.d.ts +2 -1
  31. package/.server/server/plugins/payment/service.js +11 -3
  32. package/.server/server/plugins/payment/service.js.map +1 -1
  33. package/.server/server/plugins/payment/types.d.ts +4 -0
  34. package/.server/server/plugins/payment/types.js +1 -0
  35. package/.server/server/plugins/payment/types.js.map +1 -1
  36. package/package.json +2 -2
  37. package/src/client/images/esri-logo.png +0 -0
  38. package/src/client/javascripts/map.js +2 -0
  39. package/src/server/forms/payment-v2-test.yaml +341 -0
  40. package/src/server/plugins/engine/components/PaymentField.ts +70 -6
  41. package/src/server/plugins/engine/models/SummaryViewModel.ts +2 -0
  42. package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +32 -1
  43. package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +99 -17
  44. package/src/server/plugins/engine/pageControllers/errors.ts +2 -2
  45. package/src/server/plugins/engine/routes/index.ts +9 -2
  46. package/src/server/plugins/engine/routes/payment.js +7 -1
  47. package/src/server/plugins/engine/services/localFormsService.js +7 -0
  48. package/src/server/plugins/engine/views/summary.html +2 -1
  49. package/src/server/plugins/payment/service.js +13 -3
  50. package/src/server/plugins/payment/types.js +1 -0
@@ -1 +1 @@
1
- {"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","COMPONENT_STATE_ERROR","PAYMENT_EXPIRED_NOTIFICATION","ComponentCollection","PaymentField","checkEmailAddressForLiveFormSubmission","checkFormStatus","createError","getCacheService","SummaryViewModel","QuestionPageController","InvalidComponentStateError","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","buildMainRecords","buildRepeaterRecords","DEFAULT_PAYMENT_HELP_URL","formatCurrency","formatPaymentDate","FormAction","SummaryPageController","allowSaveAndExit","constructor","model","pageDef","viewName","collection","components","page","getSummaryViewModel","request","context","viewModel","query","payload","errors","state","paymentField","relevantPages","flatMap","fields","find","field","paymentState","getPaymentStateFromState","paymentDetails","buildPaymentDetails","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","shouldShowSaveAndExit","server","rows","key","text","value","description","amount","reference","preAuth","createdAt","push","title","summaryList","makeGetRouteHandler","h","hasMissingNotificationEmail","view","makePostRouteHandler","action","SaveAndExit","handleSaveAndExit","handleFormSubmit","params","cacheService","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","submitForm","error","handleSubmissionError","setConfirmationState","confirmed","formId","referenceNumber","clearState","proceed","getStatusPath","handleInvalidComponentStateError","handlePaymentPreAuthError","handlePaymentSubmissionError","govukError","component","name","userMessage","yar","flash","resetComponentStates","getStateKeys","path","shouldResetState","errorType","PaymentExpired","redirectPath","undefined","helpUrl","helpLink","helpLinkHtml","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","emailAddress","finaliseComponents","paymentWasCaptured","hasPaymentBeenCaptured","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","id","badRequest","outputService","submit","err","contact","online","url","capture","status","metadata","relevantFields","onSubmit","retrievalKey","sessionId","formSubmissionService","main","repeaters","map","href","filter","flat","paymentItems","getPaymentFieldItems","label","getDisplayStringFromState"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type FormMetadata,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\n\nimport {\n COMPONENT_STATE_ERROR,\n PAYMENT_EXPIRED_NOTIFICATION\n} from '~/src/server/constants.js'\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n createError,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n InvalidComponentStateError,\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n buildMainRecords,\n buildRepeaterRecords\n} from '~/src/server/plugins/engine/pageControllers/helpers/submission.js'\nimport {\n type FormConfirmationState,\n type FormContext,\n type FormContextRequest\n} from '~/src/server/plugins/engine/types.js'\nimport {\n DEFAULT_PAYMENT_HELP_URL,\n formatCurrency,\n formatPaymentDate\n} from '~/src/server/plugins/payment/helper.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n allowSaveAndExit = true\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors, state } = context\n\n const paymentField = context.relevantPages\n .flatMap((page) => page.collection.fields)\n .find((field): field is PaymentField => field instanceof PaymentField)\n\n if (paymentField) {\n const paymentState = paymentField.getPaymentStateFromState(state)\n if (paymentState) {\n viewModel.paymentState = paymentState\n viewModel.paymentDetails = this.buildPaymentDetails(\n paymentField,\n paymentState\n )\n }\n }\n\n const components = this.collection.getViewModel(payload, errors, query)\n\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server)\n viewModel.errors = errors\n\n return viewModel\n }\n\n private buildPaymentDetails(\n paymentField: PaymentField,\n paymentState: NonNullable<\n ReturnType<PaymentField['getPaymentStateFromState']>\n >\n ) {\n const rows = [\n {\n key: { text: 'Payment for' },\n value: { text: paymentState.description }\n },\n {\n key: { text: 'Total amount' },\n value: { text: formatCurrency(paymentState.amount) }\n },\n {\n key: { text: 'Reference' },\n value: { text: paymentState.reference }\n }\n ]\n\n if (paymentState.preAuth?.createdAt) {\n rows.push({\n key: { text: 'Date of payment' },\n value: { text: formatPaymentDate(paymentState.preAuth.createdAt) }\n })\n }\n\n return {\n title: { text: 'Payment details' },\n summaryList: { rows }\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewName } = this\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { action } = request.payload\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n return this.handleFormSubmit(request, context, h)\n }\n }\n\n async handleFormSubmit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const { model } = this\n const { params } = request\n\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n\n checkEmailAddressForLiveFormSubmission(notificationEmail, isPreview)\n\n if (notificationEmail) {\n const viewModel = this.getSummaryViewModel(request, context)\n\n try {\n await submitForm(\n context,\n formMetadata,\n request,\n viewModel,\n model,\n notificationEmail\n )\n } catch (error) {\n return this.handleSubmissionError(error, request, h)\n }\n }\n\n await cacheService.setConfirmationState(request, {\n confirmed: true,\n formId: context.state.formId,\n referenceNumber: context.referenceNumber\n } as FormConfirmationState)\n\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n\n /**\n * Handles errors during form submission\n */\n private async handleSubmissionError(\n error: unknown,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n if (error instanceof InvalidComponentStateError) {\n return this.handleInvalidComponentStateError(error, request, h)\n }\n\n if (error instanceof PaymentPreAuthError) {\n return this.handlePaymentPreAuthError(error, request, h)\n }\n\n if (error instanceof PaymentSubmissionError) {\n return this.handlePaymentSubmissionError(error, request, h)\n }\n\n throw error\n }\n\n /**\n * Handles InvalidComponentStateError during submission\n */\n private async handleInvalidComponentStateError(\n error: InvalidComponentStateError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const cacheService = getCacheService(request.server)\n\n const govukError = createError(error.component.name, error.userMessage)\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n return this.proceed(request, h, error.component.page?.path)\n }\n\n /**\n * Handles PaymentPreAuthError during submission\n */\n private async handlePaymentPreAuthError(\n error: PaymentPreAuthError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const cacheService = getCacheService(request.server)\n\n if (error.shouldResetState) {\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n if (error.errorType === PaymentErrorTypes.PaymentExpired) {\n request.yar.flash(PAYMENT_EXPIRED_NOTIFICATION, true, true)\n return this.proceed(request, h, error.component.page?.path)\n }\n }\n\n const govukError = createError(error.component.name, error.userMessage)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n const redirectPath = error.shouldResetState\n ? error.component.page?.path\n : undefined\n\n return this.proceed(request, h, redirectPath)\n }\n\n /**\n * Handles PaymentSubmissionError during submission\n */\n private handlePaymentSubmissionError(\n error: PaymentSubmissionError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const helpUrl = error.helpLink ?? DEFAULT_PAYMENT_HELP_URL\n const helpLinkHtml = ` or you can <a href=\"${helpUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"govuk-link\">contact us (opens in new tab)</a> and quote your reference number to arrange a refund`\n\n const govukError = createError(\n 'submission',\n `There was a problem and your form was not submitted. Try submitting the form again${helpLinkHtml}.`\n )\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n return this.proceed(request, h)\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nexport async function submitForm(\n context: FormContext,\n formMetadata: FormMetadata,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string\n) {\n await finaliseComponents(request, formMetadata, context)\n\n const paymentWasCaptured = hasPaymentBeenCaptured(context)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n try {\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n await model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n } catch (err) {\n if (paymentWasCaptured) {\n throw new PaymentSubmissionError(\n context.referenceNumber,\n formMetadata.contact?.online?.url\n )\n }\n throw err\n }\n}\n\n/**\n * Checks if any payment component has been captured\n */\nfunction hasPaymentBeenCaptured(context: FormContext): boolean {\n for (const page of context.relevantPages) {\n for (const field of page.collection.fields) {\n if (field instanceof PaymentField) {\n const paymentState = field.getPaymentStateFromState(context.state)\n if (paymentState?.capture?.status === 'success') {\n return true\n }\n }\n }\n }\n return false\n}\n\n/**\n * Finalises any components that need post-processing before form submission. Candidates usually involve\n * those that have external state.\n * Examples include:\n * - file uploads which are 'persisted' before submission\n * - payments which are 'captured' before submission\n */\nasync function finaliseComponents(\n request: FormRequestPayload,\n metadata: FormMetadata,\n context: FormContext\n) {\n const relevantFields = context.relevantPages.flatMap(\n (page) => page.collection.fields\n )\n\n for (const component of relevantFields) {\n /*\n Each component will throw InvalidComponent if its state is invalid, which is handled\n by handleFormSubmit\n */\n await component.onSubmit(request, metadata, context)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n main: buildMainRecords(items),\n repeaters: buildRepeaterRecords(items)\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n const items = context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n\n const paymentItems = getPaymentFieldItems(context)\n\n return [...items, ...paymentItems]\n}\n\n/**\n * Gets DetailItems for PaymentField components\n * PaymentField is excluded from summaryDetails for UI but needs to be in submission data\n */\nfunction getPaymentFieldItems(context: FormContext): DetailItemField[] {\n const items: DetailItemField[] = []\n\n for (const page of context.relevantPages) {\n for (const field of page.collection.fields) {\n if (field instanceof PaymentField) {\n items.push({\n name: field.name,\n page,\n title: field.title,\n label: field.label,\n field,\n state: context.state,\n href: page.href,\n value: field.getDisplayStringFromState(context.state)\n })\n }\n }\n }\n\n return items\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"SummaryPageController.js","names":["hasComponentsEvenIfNoNext","Boom","StatusCodes","COMPONENT_STATE_ERROR","PAYMENT_EXPIRED_NOTIFICATION","ComponentCollection","PaymentField","checkEmailAddressForLiveFormSubmission","checkFormStatus","createError","getCacheService","SummaryViewModel","QuestionPageController","InvalidComponentStateError","PaymentErrorTypes","PaymentPreAuthError","PaymentSubmissionError","buildMainRecords","buildRepeaterRecords","DEFAULT_PAYMENT_HELP_URL","formatCurrency","formatPaymentDate","FormAction","SummaryPageController","allowSaveAndExit","findPaymentField","model","pages","flatMap","page","collection","fields","find","field","constructor","pageDef","viewName","components","getSummaryViewModel","request","context","viewModel","query","payload","errors","state","paymentField","resolvedAmount","resolveAmount","options","paymentState","getPaymentStateFromState","amount","paymentDetails","buildPaymentDetails","paymentRequired","preAuth","status","paymentPreAuthorized","getViewModel","backLink","getBackLink","feedbackLink","phaseTag","shouldShowSaveAndExit","server","rows","key","text","value","description","reference","createdAt","push","title","summaryList","makeGetRouteHandler","h","paymentComplete","handleFormSubmit","reconcilePaymentState","hasMissingNotificationEmail","view","cacheService","resetComponentStates","name","makePostRouteHandler","action","SaveAndExit","handleSaveAndExit","params","formsService","services","getFormMetadata","formMetadata","slug","notificationEmail","isPreview","submitForm","error","handleSubmissionError","setConfirmationState","confirmed","formId","referenceNumber","clearState","proceed","getStatusPath","handleInvalidComponentStateError","handlePaymentPreAuthError","handlePaymentSubmissionError","govukError","component","userMessage","yar","flash","getStateKeys","path","shouldResetState","errorType","PaymentExpired","PaymentIncomplete","redirect","getHref","code","SEE_OTHER","redirectPath","undefined","helpUrl","helpLink","helpLinkHtml","postRouteOptions","ext","onPreHandler","method","continue","summaryViewModel","emailAddress","finaliseComponents","paymentWasCaptured","hasPaymentBeenCaptured","formStatus","logTags","logger","info","items","getFormSubmissionData","details","submitResponse","submitData","id","badRequest","outputService","submit","err","contact","online","url","capture","metadata","allFields","Set","relevantPages","filter","onSubmit","retrievalKey","sessionId","formSubmissionService","main","repeaters","map","href","flat","paymentItems","getPaymentFieldItems","label","getDisplayStringFromState"],"sources":["../../../../../src/server/plugins/engine/pageControllers/SummaryPageController.ts"],"sourcesContent":["import {\n hasComponentsEvenIfNoNext,\n type FormMetadata,\n type Page,\n type SubmitPayload\n} from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport { type RouteOptions } from '@hapi/hapi'\nimport { StatusCodes } from 'http-status-codes'\n\nimport {\n COMPONENT_STATE_ERROR,\n PAYMENT_EXPIRED_NOTIFICATION\n} from '~/src/server/constants.js'\nimport { ComponentCollection } from '~/src/server/plugins/engine/components/ComponentCollection.js'\nimport { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'\nimport {\n checkEmailAddressForLiveFormSubmission,\n checkFormStatus,\n createError,\n getCacheService\n} from '~/src/server/plugins/engine/helpers.js'\nimport {\n SummaryViewModel,\n type FormModel\n} from '~/src/server/plugins/engine/models/index.js'\nimport {\n type Detail,\n type DetailItem,\n type DetailItemField\n} from '~/src/server/plugins/engine/models/types.js'\nimport { QuestionPageController } from '~/src/server/plugins/engine/pageControllers/QuestionPageController.js'\nimport {\n InvalidComponentStateError,\n PaymentErrorTypes,\n PaymentPreAuthError,\n PaymentSubmissionError\n} from '~/src/server/plugins/engine/pageControllers/errors.js'\nimport {\n buildMainRecords,\n buildRepeaterRecords\n} from '~/src/server/plugins/engine/pageControllers/helpers/submission.js'\nimport {\n type FormConfirmationState,\n type FormContext,\n type FormContextRequest\n} from '~/src/server/plugins/engine/types.js'\nimport {\n DEFAULT_PAYMENT_HELP_URL,\n formatCurrency,\n formatPaymentDate\n} from '~/src/server/plugins/payment/helper.js'\nimport {\n FormAction,\n type FormRequest,\n type FormRequestPayload,\n type FormRequestPayloadRefs,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport class SummaryPageController extends QuestionPageController {\n declare pageDef: Page\n allowSaveAndExit = true\n\n /**\n * The controller which is used when Page[\"controller\"] is defined as \"./pages/summary.js\"\n */\n\n /**\n * Finds the PaymentField component across all pages in the model.\n * Payment pages are skipped in the normal page walk\n */\n private findPaymentField(): PaymentField | undefined {\n return this.model.pages\n .flatMap((page) => page.collection.fields)\n .find((field): field is PaymentField => field instanceof PaymentField)\n }\n\n constructor(model: FormModel, pageDef: Page) {\n super(model, pageDef)\n this.viewName = 'summary'\n\n // Components collection\n this.collection = new ComponentCollection(\n hasComponentsEvenIfNoNext(pageDef) ? pageDef.components : [],\n { model, page: this }\n )\n }\n\n getSummaryViewModel(\n request: FormContextRequest,\n context: FormContext\n ): SummaryViewModel {\n const viewModel = new SummaryViewModel(request, this, context)\n\n const { query } = request\n const { payload, errors, state } = context\n\n const paymentField = this.findPaymentField()\n\n if (paymentField) {\n const resolvedAmount = PaymentField.resolveAmount(\n paymentField.options,\n this.model,\n state\n )\n const paymentState = paymentField.getPaymentStateFromState(state)\n\n if (paymentState?.amount === resolvedAmount) {\n viewModel.paymentState = paymentState\n viewModel.paymentDetails = this.buildPaymentDetails(\n paymentField,\n paymentState\n )\n }\n\n if (resolvedAmount > 0) {\n viewModel.paymentRequired = true\n }\n if (\n paymentState &&\n paymentState.preAuth?.status === 'success' &&\n paymentState.amount === resolvedAmount\n ) {\n viewModel.paymentPreAuthorized = true\n }\n }\n\n const components = this.collection.getViewModel(payload, errors, query)\n\n viewModel.backLink = this.getBackLink(request, context)\n viewModel.feedbackLink = this.feedbackLink\n viewModel.phaseTag = this.phaseTag\n viewModel.components = components\n viewModel.allowSaveAndExit = this.shouldShowSaveAndExit(request.server)\n viewModel.errors = errors\n\n return viewModel\n }\n\n private buildPaymentDetails(\n paymentField: PaymentField,\n paymentState: NonNullable<\n ReturnType<PaymentField['getPaymentStateFromState']>\n >\n ) {\n const rows = [\n {\n key: { text: 'Payment for' },\n value: { text: paymentState.description }\n },\n {\n key: { text: 'Total amount' },\n value: { text: formatCurrency(paymentState.amount) }\n },\n {\n key: { text: 'Reference' },\n value: { text: paymentState.reference }\n }\n ]\n\n if (paymentState.preAuth?.createdAt) {\n rows.push({\n key: { text: 'Date of payment' },\n value: { text: formatPaymentDate(paymentState.preAuth.createdAt) }\n })\n }\n\n return {\n title: { text: 'Payment details' },\n summaryList: { rows }\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a GET request at `/{id}/{path*}`,\n */\n makeGetRouteHandler() {\n return async (\n request: FormRequest,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { viewName } = this\n\n // After GOV.UK Pay callback, auto-submit the form instead of\n // showing CYA again. The payment is already pre-authorized.\n if (request.query.paymentComplete === 'true') {\n return this.handleFormSubmit(\n request as unknown as FormRequestPayload,\n context,\n h\n )\n }\n\n await this.reconcilePaymentState(request, context)\n\n const viewModel = this.getSummaryViewModel(request, context)\n\n viewModel.hasMissingNotificationEmail =\n await this.hasMissingNotificationEmail(request, context)\n\n return h.view(viewName, viewModel)\n }\n }\n\n /**\n * Checks if the resolved payment amount has changed since pre-auth\n * and invalidates stale payment state if so.\n */\n private async reconcilePaymentState(\n request: FormRequest,\n context: FormContext\n ) {\n const paymentField = this.findPaymentField()\n\n if (!paymentField) {\n return\n }\n\n const resolvedAmount = PaymentField.resolveAmount(\n paymentField.options,\n this.model,\n context.state\n )\n const paymentState = paymentField.getPaymentStateFromState(context.state)\n\n if (paymentState && paymentState.amount !== resolvedAmount) {\n const cacheService = getCacheService(request.server)\n await cacheService.resetComponentStates(request, [paymentField.name])\n }\n }\n\n /**\n * Returns an async function. This is called in plugin.ts when there is a POST request at `/{id}/{path*}`.\n * If a form is incomplete, a user will be redirected to the start page.\n */\n makePostRouteHandler() {\n return async (\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) => {\n const { action } = request.payload\n if (action === FormAction.SaveAndExit) {\n return this.handleSaveAndExit(request, context, h)\n }\n\n return this.handleFormSubmit(request, context, h)\n }\n }\n\n async handleFormSubmit(\n request: FormRequestPayload,\n context: FormContext,\n h: FormResponseToolkit\n ) {\n const { model } = this\n const { params } = request\n\n const cacheService = getCacheService(request.server)\n\n const { formsService } = this.model.services\n const { getFormMetadata } = formsService\n\n const formMetadata = await getFormMetadata(params.slug)\n const { notificationEmail } = formMetadata\n const { isPreview } = checkFormStatus(request.params)\n\n checkEmailAddressForLiveFormSubmission(notificationEmail, isPreview)\n\n if (notificationEmail) {\n const viewModel = this.getSummaryViewModel(request, context)\n\n try {\n await submitForm(\n context,\n formMetadata,\n request,\n viewModel,\n model,\n notificationEmail\n )\n } catch (error) {\n return this.handleSubmissionError(error, request, h)\n }\n }\n\n await cacheService.setConfirmationState(request, {\n confirmed: true,\n formId: context.state.formId,\n referenceNumber: context.referenceNumber\n } as FormConfirmationState)\n\n await cacheService.clearState(request)\n\n return this.proceed(request, h, this.getStatusPath())\n }\n\n /**\n * Handles errors during form submission\n */\n private async handleSubmissionError(\n error: unknown,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n if (error instanceof InvalidComponentStateError) {\n return this.handleInvalidComponentStateError(error, request, h)\n }\n\n if (error instanceof PaymentPreAuthError) {\n return this.handlePaymentPreAuthError(error, request, h)\n }\n\n if (error instanceof PaymentSubmissionError) {\n return this.handlePaymentSubmissionError(error, request, h)\n }\n\n throw error\n }\n\n /**\n * Handles InvalidComponentStateError during submission\n */\n private async handleInvalidComponentStateError(\n error: InvalidComponentStateError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const cacheService = getCacheService(request.server)\n\n const govukError = createError(error.component.name, error.userMessage)\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n return this.proceed(request, h, error.component.page?.path)\n }\n\n /**\n * Handles PaymentPreAuthError during submission\n */\n private async handlePaymentPreAuthError(\n error: PaymentPreAuthError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const cacheService = getCacheService(request.server)\n\n if (error.shouldResetState) {\n await cacheService.resetComponentStates(request, error.getStateKeys())\n\n if (error.errorType === PaymentErrorTypes.PaymentExpired) {\n request.yar.flash(PAYMENT_EXPIRED_NOTIFICATION, true, true)\n return this.proceed(request, h, error.component.page?.path)\n }\n }\n\n // Payment page is skipped in the page walk, so proceed() would redirect\n // to an earlier page. For PaymentIncomplete, redirect directly to the\n // payment page using its href.\n if (\n error.errorType === PaymentErrorTypes.PaymentIncomplete &&\n error.component.page\n ) {\n return h\n .redirect(error.component.page.getHref(error.component.page.path))\n .code(StatusCodes.SEE_OTHER)\n }\n\n const govukError = createError(error.component.name, error.userMessage)\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n const redirectPath = error.shouldResetState\n ? error.component.page?.path\n : undefined\n\n return this.proceed(request, h, redirectPath)\n }\n\n /**\n * Handles PaymentSubmissionError during submission\n */\n private handlePaymentSubmissionError(\n error: PaymentSubmissionError,\n request: FormRequestPayload,\n h: FormResponseToolkit\n ) {\n const helpUrl = error.helpLink ?? DEFAULT_PAYMENT_HELP_URL\n const helpLinkHtml = ` or you can <a href=\"${helpUrl}\" target=\"_blank\" rel=\"noopener noreferrer\" class=\"govuk-link\">contact us (opens in new tab)</a> and quote your reference number to arrange a refund`\n\n const govukError = createError(\n 'submission',\n `There was a problem and your form was not submitted. Try submitting the form again${helpLinkHtml}.`\n )\n\n request.yar.flash(COMPONENT_STATE_ERROR, govukError, true)\n\n return this.proceed(request, h)\n }\n\n get postRouteOptions(): RouteOptions<FormRequestPayloadRefs> {\n return {\n ext: {\n onPreHandler: {\n method(request, h) {\n return h.continue\n }\n }\n }\n }\n }\n}\n\nexport async function submitForm(\n context: FormContext,\n formMetadata: FormMetadata,\n request: FormRequestPayload,\n summaryViewModel: SummaryViewModel,\n model: FormModel,\n emailAddress: string\n) {\n await finaliseComponents(request, formMetadata, context, model)\n\n const paymentWasCaptured = hasPaymentBeenCaptured(context, model)\n\n const formStatus = checkFormStatus(request.params)\n const logTags = ['submit', 'submissionApi']\n\n request.logger.info(logTags, 'Preparing email', formStatus)\n\n const items = getFormSubmissionData(\n summaryViewModel.context,\n summaryViewModel.details\n )\n\n try {\n request.logger.info(logTags, 'Submitting data')\n const submitResponse = await submitData(\n model,\n items,\n emailAddress,\n request.yar.id\n )\n\n if (submitResponse === undefined) {\n throw Boom.badRequest('Unexpected empty response from submit api')\n }\n\n await model.services.outputService.submit(\n context,\n request,\n model,\n emailAddress,\n items,\n submitResponse,\n formMetadata\n )\n } catch (err) {\n if (paymentWasCaptured) {\n throw new PaymentSubmissionError(\n context.referenceNumber,\n formMetadata.contact?.online?.url\n )\n }\n throw err\n }\n}\n\n/**\n * Checks if any payment component has been captured\n */\nfunction hasPaymentBeenCaptured(\n context: FormContext,\n model: FormModel\n): boolean {\n for (const page of model.pages) {\n for (const field of page.collection.fields) {\n if (field instanceof PaymentField) {\n const paymentState = field.getPaymentStateFromState(context.state)\n if (paymentState?.capture?.status === 'success') {\n return true\n }\n }\n }\n }\n return false\n}\n\n/**\n * Finalises any components that need post-processing before form submission. Candidates usually involve\n * those that have external state.\n * Examples include:\n * - file uploads which are 'persisted' before submission\n * - payments which are 'captured' before submission\n */\nasync function finaliseComponents(\n request: FormRequestPayload,\n metadata: FormMetadata,\n context: FormContext,\n model: FormModel\n) {\n // Get fields from relevant pages (normal components)\n // plus PaymentField from all pages (payment page is skipped in the page walk)\n const allFields = new Set([\n ...context.relevantPages.flatMap((page) => page.collection.fields),\n ...model.pages\n .flatMap((page) => page.collection.fields)\n .filter((field) => field instanceof PaymentField)\n ])\n\n for (const component of allFields) {\n await component.onSubmit(request, metadata, context)\n }\n}\n\nfunction submitData(\n model: FormModel,\n items: DetailItem[],\n retrievalKey: string,\n sessionId: string\n) {\n const { formSubmissionService } = model.services\n const { submit } = formSubmissionService\n\n const payload: SubmitPayload = {\n sessionId,\n retrievalKey,\n main: buildMainRecords(items),\n repeaters: buildRepeaterRecords(items)\n }\n\n return submit(payload)\n}\n\nexport function getFormSubmissionData(context: FormContext, details: Detail[]) {\n const items = context.relevantPages\n .map(({ href }) =>\n details.flatMap(({ items }) =>\n items.filter(({ page }) => page.href === href)\n )\n )\n .flat()\n\n const paymentItems = getPaymentFieldItems(context)\n\n return [...items, ...paymentItems]\n}\n\n/**\n * Gets DetailItems for PaymentField components\n * PaymentField is excluded from summaryDetails for UI but needs to be in submission data\n */\nfunction getPaymentFieldItems(context: FormContext): DetailItemField[] {\n const items: DetailItemField[] = []\n\n for (const page of context.relevantPages) {\n for (const field of page.collection.fields) {\n if (field instanceof PaymentField) {\n items.push({\n name: field.name,\n page,\n title: field.title,\n label: field.label,\n field,\n state: context.state,\n href: page.href,\n value: field.getDisplayStringFromState(context.state)\n })\n }\n }\n }\n\n return items\n}\n"],"mappings":"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","ignoreList":[]}
@@ -28,7 +28,7 @@ export declare class PaymentSubmissionError extends Error {
28
28
  readonly referenceNumber: string;
29
29
  readonly helpLink?: string;
30
30
  constructor(referenceNumber: string, helpLink?: string);
31
- static checkPaymentAmount(stateAmount: number, definitionAmount: number | undefined, component: FormComponent): void;
31
+ static checkPaymentAmount(stateAmount: number, expectedAmount: number | undefined, component: FormComponent): void;
32
32
  }
33
33
  /**
34
34
  * Thrown when a component has an invalid state. This is typically only required where state needs
@@ -49,8 +49,8 @@ export class PaymentSubmissionError extends Error {
49
49
  this.referenceNumber = referenceNumber;
50
50
  this.helpLink = helpLink;
51
51
  }
52
- static checkPaymentAmount(stateAmount, definitionAmount, component) {
53
- if (stateAmount / 100 !== definitionAmount) {
52
+ static checkPaymentAmount(stateAmount, expectedAmount, component) {
53
+ if (stateAmount / 100 !== expectedAmount) {
54
54
  throw new PaymentPreAuthError(component, 'The pre-authorised payment amount is somehow different from that requested. Try adding payment details again.', true, PaymentErrorTypes.PaymentIncomplete);
55
55
  }
56
56
  }
@@ -1 +1 @@
1
- {"version":3,"file":"errors.js","names":["PaymentErrorTypes","getStateKeys","component","extraStateKeys","page","name","concat","PaymentPreAuthError","Error","userMessage","shouldResetState","errorType","constructor","PaymentSubmissionError","referenceNumber","helpLink","checkPaymentAmount","stateAmount","definitionAmount","PaymentIncomplete","InvalidComponentStateError","message"],"sources":["../../../../../src/server/plugins/engine/pageControllers/errors.ts"],"sourcesContent":["import { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\n\nexport enum PaymentErrorTypes {\n PaymentExpired = 'PaymentExpired',\n PaymentIncomplete = 'PaymentIncomplete',\n PaymentAmountMismatch = 'PaymentAmountMismatch'\n}\n\nfunction getStateKeys(component: FormComponent) {\n const extraStateKeys = component.page?.getStateKeys(component) ?? []\n\n return [component.name].concat(extraStateKeys)\n}\n\nexport class PaymentPreAuthError extends Error {\n public readonly component: FormComponent\n public readonly userMessage: string\n\n /**\n * Whether to reset the component state and redirect to the component's page.\n * - `true`: Reset state and redirect (e.g., payment expired - user must re-enter)\n * - `false`: Keep state and stay on current page with error (e.g., capture failed - user can retry)\n */\n public readonly shouldResetState: boolean\n\n /**\n * When supplied, an \"Important\" notification banner will be shown based on the value.\n */\n public readonly errorType: PaymentErrorTypes | undefined\n\n constructor(\n component: FormComponent,\n userMessage: string,\n shouldResetState: boolean,\n errorType?: PaymentErrorTypes\n ) {\n super('Payment capture failed')\n this.name = 'PaymentPreAuthError'\n this.component = component\n this.userMessage = userMessage\n this.shouldResetState = shouldResetState\n this.errorType = errorType\n }\n\n getStateKeys() {\n return getStateKeys(this.component)\n }\n}\n\n/**\n * Thrown when form submission fails after payment has been captured.\n * User needs to retry or contact support for a refund.\n */\nexport class PaymentSubmissionError extends Error {\n public readonly referenceNumber: string\n public readonly helpLink?: string\n\n constructor(referenceNumber: string, helpLink?: string) {\n super('Form submission failed after payment capture')\n this.name = 'PaymentSubmissionError'\n this.referenceNumber = referenceNumber\n this.helpLink = helpLink\n }\n\n static checkPaymentAmount(\n stateAmount: number,\n definitionAmount: number | undefined,\n component: FormComponent\n ) {\n if (stateAmount / 100 !== definitionAmount) {\n throw new PaymentPreAuthError(\n component,\n 'The pre-authorised payment amount is somehow different from that requested. Try adding payment details again.',\n true,\n PaymentErrorTypes.PaymentIncomplete\n )\n }\n }\n}\n\n/**\n * Thrown when a component has an invalid state. This is typically only required where state needs\n * to be checked against an external source upon submission of a form. For example: file upload\n * has internal state (file upload IDs) but also external state (files in S3). The internal state\n * is always validated by the engine, but the external state needs validating too.\n *\n * This should be used within a formComponent.onSubmit(...).\n */\nexport class InvalidComponentStateError extends Error {\n public readonly component: FormComponent\n public readonly userMessage: string\n\n constructor(component: FormComponent, userMessage: string) {\n const message = `Invalid component state for: ${component.name}`\n super(message)\n this.name = 'InvalidComponentStateError'\n this.component = component\n this.userMessage = userMessage\n }\n\n getStateKeys() {\n return getStateKeys(this.component)\n }\n}\n"],"mappings":"AAEA,WAAYA,iBAAiB,0BAAjBA,iBAAiB;EAAjBA,iBAAiB;EAAjBA,iBAAiB;EAAjBA,iBAAiB;EAAA,OAAjBA,iBAAiB;AAAA;AAM7B,SAASC,YAAYA,CAACC,SAAwB,EAAE;EAC9C,MAAMC,cAAc,GAAGD,SAAS,CAACE,IAAI,EAAEH,YAAY,CAACC,SAAS,CAAC,IAAI,EAAE;EAEpE,OAAO,CAACA,SAAS,CAACG,IAAI,CAAC,CAACC,MAAM,CAACH,cAAc,CAAC;AAChD;AAEA,OAAO,MAAMI,mBAAmB,SAASC,KAAK,CAAC;EAC7BN,SAAS;EACTO,WAAW;;EAE3B;AACF;AACA;AACA;AACA;EACkBC,gBAAgB;;EAEhC;AACF;AACA;EACkBC,SAAS;EAEzBC,WAAWA,CACTV,SAAwB,EACxBO,WAAmB,EACnBC,gBAAyB,EACzBC,SAA6B,EAC7B;IACA,KAAK,CAAC,wBAAwB,CAAC;IAC/B,IAAI,CAACN,IAAI,GAAG,qBAAqB;IACjC,IAAI,CAACH,SAAS,GAAGA,SAAS;IAC1B,IAAI,CAACO,WAAW,GAAGA,WAAW;IAC9B,IAAI,CAACC,gBAAgB,GAAGA,gBAAgB;IACxC,IAAI,CAACC,SAAS,GAAGA,SAAS;EAC5B;EAEAV,YAAYA,CAAA,EAAG;IACb,OAAOA,YAAY,CAAC,IAAI,CAACC,SAAS,CAAC;EACrC;AACF;;AAEA;AACA;AACA;AACA;AACA,OAAO,MAAMW,sBAAsB,SAASL,KAAK,CAAC;EAChCM,eAAe;EACfC,QAAQ;EAExBH,WAAWA,CAACE,eAAuB,EAAEC,QAAiB,EAAE;IACtD,KAAK,CAAC,8CAA8C,CAAC;IACrD,IAAI,CAACV,IAAI,GAAG,wBAAwB;IACpC,IAAI,CAACS,eAAe,GAAGA,eAAe;IACtC,IAAI,CAACC,QAAQ,GAAGA,QAAQ;EAC1B;EAEA,OAAOC,kBAAkBA,CACvBC,WAAmB,EACnBC,gBAAoC,EACpChB,SAAwB,EACxB;IACA,IAAIe,WAAW,GAAG,GAAG,KAAKC,gBAAgB,EAAE;MAC1C,MAAM,IAAIX,mBAAmB,CAC3BL,SAAS,EACT,+GAA+G,EAC/G,IAAI,EACJF,iBAAiB,CAACmB,iBACpB,CAAC;IACH;EACF;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,0BAA0B,SAASZ,KAAK,CAAC;EACpCN,SAAS;EACTO,WAAW;EAE3BG,WAAWA,CAACV,SAAwB,EAAEO,WAAmB,EAAE;IACzD,MAAMY,OAAO,GAAG,gCAAgCnB,SAAS,CAACG,IAAI,EAAE;IAChE,KAAK,CAACgB,OAAO,CAAC;IACd,IAAI,CAAChB,IAAI,GAAG,4BAA4B;IACxC,IAAI,CAACH,SAAS,GAAGA,SAAS;IAC1B,IAAI,CAACO,WAAW,GAAGA,WAAW;EAChC;EAEAR,YAAYA,CAAA,EAAG;IACb,OAAOA,YAAY,CAAC,IAAI,CAACC,SAAS,CAAC;EACrC;AACF","ignoreList":[]}
1
+ {"version":3,"file":"errors.js","names":["PaymentErrorTypes","getStateKeys","component","extraStateKeys","page","name","concat","PaymentPreAuthError","Error","userMessage","shouldResetState","errorType","constructor","PaymentSubmissionError","referenceNumber","helpLink","checkPaymentAmount","stateAmount","expectedAmount","PaymentIncomplete","InvalidComponentStateError","message"],"sources":["../../../../../src/server/plugins/engine/pageControllers/errors.ts"],"sourcesContent":["import { type FormComponent } from '~/src/server/plugins/engine/components/FormComponent.js'\n\nexport enum PaymentErrorTypes {\n PaymentExpired = 'PaymentExpired',\n PaymentIncomplete = 'PaymentIncomplete',\n PaymentAmountMismatch = 'PaymentAmountMismatch'\n}\n\nfunction getStateKeys(component: FormComponent) {\n const extraStateKeys = component.page?.getStateKeys(component) ?? []\n\n return [component.name].concat(extraStateKeys)\n}\n\nexport class PaymentPreAuthError extends Error {\n public readonly component: FormComponent\n public readonly userMessage: string\n\n /**\n * Whether to reset the component state and redirect to the component's page.\n * - `true`: Reset state and redirect (e.g., payment expired - user must re-enter)\n * - `false`: Keep state and stay on current page with error (e.g., capture failed - user can retry)\n */\n public readonly shouldResetState: boolean\n\n /**\n * When supplied, an \"Important\" notification banner will be shown based on the value.\n */\n public readonly errorType: PaymentErrorTypes | undefined\n\n constructor(\n component: FormComponent,\n userMessage: string,\n shouldResetState: boolean,\n errorType?: PaymentErrorTypes\n ) {\n super('Payment capture failed')\n this.name = 'PaymentPreAuthError'\n this.component = component\n this.userMessage = userMessage\n this.shouldResetState = shouldResetState\n this.errorType = errorType\n }\n\n getStateKeys() {\n return getStateKeys(this.component)\n }\n}\n\n/**\n * Thrown when form submission fails after payment has been captured.\n * User needs to retry or contact support for a refund.\n */\nexport class PaymentSubmissionError extends Error {\n public readonly referenceNumber: string\n public readonly helpLink?: string\n\n constructor(referenceNumber: string, helpLink?: string) {\n super('Form submission failed after payment capture')\n this.name = 'PaymentSubmissionError'\n this.referenceNumber = referenceNumber\n this.helpLink = helpLink\n }\n\n static checkPaymentAmount(\n stateAmount: number,\n expectedAmount: number | undefined,\n component: FormComponent\n ) {\n if (stateAmount / 100 !== expectedAmount) {\n throw new PaymentPreAuthError(\n component,\n 'The pre-authorised payment amount is somehow different from that requested. Try adding payment details again.',\n true,\n PaymentErrorTypes.PaymentIncomplete\n )\n }\n }\n}\n\n/**\n * Thrown when a component has an invalid state. This is typically only required where state needs\n * to be checked against an external source upon submission of a form. For example: file upload\n * has internal state (file upload IDs) but also external state (files in S3). The internal state\n * is always validated by the engine, but the external state needs validating too.\n *\n * This should be used within a formComponent.onSubmit(...).\n */\nexport class InvalidComponentStateError extends Error {\n public readonly component: FormComponent\n public readonly userMessage: string\n\n constructor(component: FormComponent, userMessage: string) {\n const message = `Invalid component state for: ${component.name}`\n super(message)\n this.name = 'InvalidComponentStateError'\n this.component = component\n this.userMessage = userMessage\n }\n\n getStateKeys() {\n return getStateKeys(this.component)\n }\n}\n"],"mappings":"AAEA,WAAYA,iBAAiB,0BAAjBA,iBAAiB;EAAjBA,iBAAiB;EAAjBA,iBAAiB;EAAjBA,iBAAiB;EAAA,OAAjBA,iBAAiB;AAAA;AAM7B,SAASC,YAAYA,CAACC,SAAwB,EAAE;EAC9C,MAAMC,cAAc,GAAGD,SAAS,CAACE,IAAI,EAAEH,YAAY,CAACC,SAAS,CAAC,IAAI,EAAE;EAEpE,OAAO,CAACA,SAAS,CAACG,IAAI,CAAC,CAACC,MAAM,CAACH,cAAc,CAAC;AAChD;AAEA,OAAO,MAAMI,mBAAmB,SAASC,KAAK,CAAC;EAC7BN,SAAS;EACTO,WAAW;;EAE3B;AACF;AACA;AACA;AACA;EACkBC,gBAAgB;;EAEhC;AACF;AACA;EACkBC,SAAS;EAEzBC,WAAWA,CACTV,SAAwB,EACxBO,WAAmB,EACnBC,gBAAyB,EACzBC,SAA6B,EAC7B;IACA,KAAK,CAAC,wBAAwB,CAAC;IAC/B,IAAI,CAACN,IAAI,GAAG,qBAAqB;IACjC,IAAI,CAACH,SAAS,GAAGA,SAAS;IAC1B,IAAI,CAACO,WAAW,GAAGA,WAAW;IAC9B,IAAI,CAACC,gBAAgB,GAAGA,gBAAgB;IACxC,IAAI,CAACC,SAAS,GAAGA,SAAS;EAC5B;EAEAV,YAAYA,CAAA,EAAG;IACb,OAAOA,YAAY,CAAC,IAAI,CAACC,SAAS,CAAC;EACrC;AACF;;AAEA;AACA;AACA;AACA;AACA,OAAO,MAAMW,sBAAsB,SAASL,KAAK,CAAC;EAChCM,eAAe;EACfC,QAAQ;EAExBH,WAAWA,CAACE,eAAuB,EAAEC,QAAiB,EAAE;IACtD,KAAK,CAAC,8CAA8C,CAAC;IACrD,IAAI,CAACV,IAAI,GAAG,wBAAwB;IACpC,IAAI,CAACS,eAAe,GAAGA,eAAe;IACtC,IAAI,CAACC,QAAQ,GAAGA,QAAQ;EAC1B;EAEA,OAAOC,kBAAkBA,CACvBC,WAAmB,EACnBC,cAAkC,EAClChB,SAAwB,EACxB;IACA,IAAIe,WAAW,GAAG,GAAG,KAAKC,cAAc,EAAE;MACxC,MAAM,IAAIX,mBAAmB,CAC3BL,SAAS,EACT,+GAA+G,EAC/G,IAAI,EACJF,iBAAiB,CAACmB,iBACpB,CAAC;IACH;EACF;AACF;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMC,0BAA0B,SAASZ,KAAK,CAAC;EACpCN,SAAS;EACTO,WAAW;EAE3BG,WAAWA,CAACV,SAAwB,EAAEO,WAAmB,EAAE;IACzD,MAAMY,OAAO,GAAG,gCAAgCnB,SAAS,CAACG,IAAI,EAAE;IAChE,KAAK,CAACgB,OAAO,CAAC;IACd,IAAI,CAAChB,IAAI,GAAG,4BAA4B;IACxC,IAAI,CAACH,SAAS,GAAGA,SAAS;IAC1B,IAAI,CAACO,WAAW,GAAGA,WAAW;EAChC;EAEAR,YAAYA,CAAA,EAAG;IACb,OAAOA,YAAY,CAAC,IAAI,CAACC,SAAS,CAAC;EACrC;AACF","ignoreList":[]}
@@ -1,3 +1,4 @@
1
+ import { isPaymentPage } from '@defra/forms-model';
1
2
  import Boom from '@hapi/boom';
2
3
  import { EXTERNAL_STATE_APPENDAGE, EXTERNAL_STATE_PAYLOAD } from "../../../constants.js";
3
4
  import { resolveFormModel } from "../beta/form-context.js";
@@ -51,8 +52,10 @@ export async function redirectOrMakeHandler(request, h, onRequest, makeHandler)
51
52
  return proceed(request, h, resumeInRepeaterUrl);
52
53
  }
53
54
 
54
- // Return handler for relevant pages or preview URL direct access
55
- if (relevantPath.startsWith(page.path) || context.isForceAccess) {
55
+ // Return handler for relevant pages, payment pages, or preview URL direct access.
56
+ // Payment pages are skipped in the normal page walk but must render when the user
57
+ // is redirected there from CYA "Pay and submit".
58
+ if (relevantPath.startsWith(page.path) || isPaymentPage(page.pageDef) || context.isForceAccess) {
56
59
  return makeHandler(page, context);
57
60
  }
58
61
 
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["Boom","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","resolveFormModel","FormComponent","isFormState","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","checkSaveAndExitRepeater","copyNotYetValidatedState","generateUniqueReference","defaultServices","redirectOrMakeHandler","request","h","onRequest","makeHandler","app","params","model","notFound","path","cacheService","server","page","state","getState","$$__referenceNumber","prefix","def","metadata","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","importExternalComponentState","flash","getFlash","context","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","result","continue","resumeInRepeaterUrl","startsWith","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","externalComponentData","yar","Array","isArray","typedStateAppendage","componentName","component","stateAppendage","data","componentMap","get","Error","TypeError","isStateValid","isState","componentState","isAppendageStateSingleObject","Object","fromEntries","entries","map","key","value","savedState","payload","stashedPayload","localState","getStateFromValidForm","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","ordnanceSurveyApiKey","handler","slug","isPreview","formState","routePrefix","dispatchHandler","servicePath","basePath"],"sources":["../../../../../src/server/plugins/engine/routes/index.ts"],"sourcesContent":["import Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type Server\n} from '@hapi/hapi'\n\nimport {\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { resolveFormModel } from '~/src/server/plugins/engine/beta/form-context.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n checkSaveAndExitRepeater,\n copyNotYetValidatedState\n} from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type AnyFormRequest,\n type ExternalStateAppendage,\n type FormContext,\n type FormPayload,\n type FormSubmissionState,\n type OnRequestCallback,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport async function redirectOrMakeHandler(\n request: AnyFormRequest,\n h: FormResponseToolkit,\n onRequest: OnRequestCallback | undefined,\n makeHandler: (\n page: PageControllerClass,\n context: FormContext\n ) => ResponseObject | Promise<ResponseObject>\n) {\n const { app, params } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n const cacheService = getCacheService(request.server)\n const page = getPage(model, request)\n let state = await page.getState(request)\n\n if (!state.$$__referenceNumber) {\n const prefix = model.def.metadata?.referenceNumberPrefix ?? ''\n\n if (typeof prefix !== 'string') {\n throw Boom.badImplementation(\n 'Reference number prefix must be a string or undefined'\n )\n }\n\n const referenceNumber = generateUniqueReference(prefix)\n state = await page.mergeState(request, state, {\n $$__referenceNumber: referenceNumber\n })\n }\n\n state = await importExternalComponentState(request, page, state)\n\n const flash = cacheService.getFlash(request)\n const context = model.getFormContext(request, state, flash?.errors)\n\n await copyNotYetValidatedState(request, context)\n\n const relevantPath = page.getRelevantPath(request, context)\n const summaryPath = page.getSummaryPath()\n\n // Call the onRequest callback if it has been supplied\n if (onRequest) {\n const result = await onRequest(request, h, context)\n if (result !== h.continue) {\n return result\n }\n }\n\n // Check whether save-and-exit should resume from within a repeater\n const resumeInRepeaterUrl = checkSaveAndExitRepeater(context, model)\n if (resumeInRepeaterUrl) {\n return proceed(request, h, resumeInRepeaterUrl)\n }\n\n // Return handler for relevant pages or preview URL direct access\n if (relevantPath.startsWith(page.path) || context.isForceAccess) {\n return makeHandler(page, context)\n }\n\n // Redirect back to last relevant page\n const redirectTo = findPage(model, relevantPath)\n // Set the return URL unless an exit page\n if (redirectTo?.next.length) {\n request.query.returnUrl = page.getHref(summaryPath)\n }\n\n return proceed(request, h, page.getHref(relevantPath))\n}\n\nasync function importExternalComponentState(\n request: AnyFormRequest,\n page: PageControllerClass,\n state: FormSubmissionState\n): Promise<FormSubmissionState> {\n const externalComponentData = request.yar.flash(EXTERNAL_STATE_APPENDAGE)\n\n if (Array.isArray(externalComponentData)) {\n return state\n }\n\n const typedStateAppendage = externalComponentData as ExternalStateAppendage\n const componentName = typedStateAppendage.component\n const stateAppendage = typedStateAppendage.data\n\n const component = request.app.model?.componentMap.get(componentName)\n\n if (!component) {\n throw new Error(`Component ${componentName} not found in form`)\n }\n\n if (!(component instanceof FormComponent)) {\n throw new TypeError(\n `Component ${componentName} is not a FormComponent and does not support isState`\n )\n }\n\n const isStateValid = component.isState(stateAppendage)\n\n if (!isStateValid) {\n throw new Error(`State for component ${componentName} is invalid`)\n }\n\n // Create state structure from appendage state\n // Some components use a record structure with properties of the format of '<compName>__<fieldName>'\n // e.g. UKAddressField\n // Some components use a single object structure e.g. PaymentField\n const componentState =\n isFormState(stateAppendage) && !component.isAppendageStateSingleObject\n ? Object.fromEntries(\n Object.entries(stateAppendage).map(([key, value]) => [\n `${componentName}__${key}`,\n value\n ])\n )\n : { [componentName]: stateAppendage }\n\n // Save the external component state directly (already has correct key format)\n const savedState = await page.mergeState(request, state, componentState)\n\n // Merge any stashed payload into the local state\n const payload = request.yar.flash(EXTERNAL_STATE_PAYLOAD)\n const stashedPayload = Array.isArray(payload) ? {} : (payload as FormPayload)\n\n const localState = page.getStateFromValidForm(request, savedState, {\n ...stashedPayload,\n ...componentState\n } as FormPayload)\n\n return { ...savedState, ...localState }\n}\n\nexport function makeLoadFormPreHandler(server: Server, options: PluginOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong\n const prefix = server.realm.modifiers.route.prefix ?? ''\n\n const {\n services = defaultServices,\n controllers,\n ordnanceSurveyApiKey\n } = options\n\n async function handler(request: AnyFormRequest, h: ResponseToolkit) {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(params)\n\n const model = await resolveFormModel(server, slug, formState, {\n services,\n controllers,\n ordnanceSurveyApiKey,\n routePrefix: prefix,\n isPreview\n })\n\n request.app.model = model\n\n return h.continue\n }\n\n return handler\n}\n\nexport function dispatchHandler(request: FormRequest, h: FormResponseToolkit) {\n const { model } = request.app\n\n const servicePath = model ? `/${model.basePath}` : ''\n return proceed(request, h, `${servicePath}${getStartPath(model)}`)\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"index.js","names":["isPaymentPage","Boom","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","resolveFormModel","FormComponent","isFormState","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","checkSaveAndExitRepeater","copyNotYetValidatedState","generateUniqueReference","defaultServices","redirectOrMakeHandler","request","h","onRequest","makeHandler","app","params","model","notFound","path","cacheService","server","page","state","getState","$$__referenceNumber","prefix","def","metadata","referenceNumberPrefix","badImplementation","referenceNumber","mergeState","importExternalComponentState","flash","getFlash","context","getFormContext","errors","relevantPath","getRelevantPath","summaryPath","getSummaryPath","result","continue","resumeInRepeaterUrl","startsWith","pageDef","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","externalComponentData","yar","Array","isArray","typedStateAppendage","componentName","component","stateAppendage","data","componentMap","get","Error","TypeError","isStateValid","isState","componentState","isAppendageStateSingleObject","Object","fromEntries","entries","map","key","value","savedState","payload","stashedPayload","localState","getStateFromValidForm","makeLoadFormPreHandler","options","realm","modifiers","route","services","controllers","ordnanceSurveyApiKey","handler","slug","isPreview","formState","routePrefix","dispatchHandler","servicePath","basePath"],"sources":["../../../../../src/server/plugins/engine/routes/index.ts"],"sourcesContent":["import { isPaymentPage } from '@defra/forms-model'\nimport Boom from '@hapi/boom'\nimport {\n type ResponseObject,\n type ResponseToolkit,\n type Server\n} from '@hapi/hapi'\n\nimport {\n EXTERNAL_STATE_APPENDAGE,\n EXTERNAL_STATE_PAYLOAD\n} from '~/src/server/constants.js'\nimport { resolveFormModel } from '~/src/server/plugins/engine/beta/form-context.js'\nimport {\n FormComponent,\n isFormState\n} from '~/src/server/plugins/engine/components/FormComponent.js'\nimport {\n checkFormStatus,\n findPage,\n getCacheService,\n getPage,\n getStartPath,\n proceed\n} from '~/src/server/plugins/engine/helpers.js'\nimport { type PageControllerClass } from '~/src/server/plugins/engine/pageControllers/helpers/pages.js'\nimport {\n checkSaveAndExitRepeater,\n copyNotYetValidatedState\n} from '~/src/server/plugins/engine/pageControllers/helpers/state.js'\nimport { generateUniqueReference } from '~/src/server/plugins/engine/referenceNumbers.js'\nimport * as defaultServices from '~/src/server/plugins/engine/services/index.js'\nimport {\n type AnyFormRequest,\n type ExternalStateAppendage,\n type FormContext,\n type FormPayload,\n type FormSubmissionState,\n type OnRequestCallback,\n type PluginOptions\n} from '~/src/server/plugins/engine/types.js'\nimport {\n type FormRequest,\n type FormResponseToolkit\n} from '~/src/server/routes/types.js'\n\nexport async function redirectOrMakeHandler(\n request: AnyFormRequest,\n h: FormResponseToolkit,\n onRequest: OnRequestCallback | undefined,\n makeHandler: (\n page: PageControllerClass,\n context: FormContext\n ) => ResponseObject | Promise<ResponseObject>\n) {\n const { app, params } = request\n const { model } = app\n\n if (!model) {\n throw Boom.notFound(`No model found for /${params.path}`)\n }\n\n const cacheService = getCacheService(request.server)\n const page = getPage(model, request)\n let state = await page.getState(request)\n\n if (!state.$$__referenceNumber) {\n const prefix = model.def.metadata?.referenceNumberPrefix ?? ''\n\n if (typeof prefix !== 'string') {\n throw Boom.badImplementation(\n 'Reference number prefix must be a string or undefined'\n )\n }\n\n const referenceNumber = generateUniqueReference(prefix)\n state = await page.mergeState(request, state, {\n $$__referenceNumber: referenceNumber\n })\n }\n\n state = await importExternalComponentState(request, page, state)\n\n const flash = cacheService.getFlash(request)\n const context = model.getFormContext(request, state, flash?.errors)\n\n await copyNotYetValidatedState(request, context)\n\n const relevantPath = page.getRelevantPath(request, context)\n const summaryPath = page.getSummaryPath()\n\n // Call the onRequest callback if it has been supplied\n if (onRequest) {\n const result = await onRequest(request, h, context)\n if (result !== h.continue) {\n return result\n }\n }\n\n // Check whether save-and-exit should resume from within a repeater\n const resumeInRepeaterUrl = checkSaveAndExitRepeater(context, model)\n if (resumeInRepeaterUrl) {\n return proceed(request, h, resumeInRepeaterUrl)\n }\n\n // Return handler for relevant pages, payment pages, or preview URL direct access.\n // Payment pages are skipped in the normal page walk but must render when the user\n // is redirected there from CYA \"Pay and submit\".\n if (\n relevantPath.startsWith(page.path) ||\n isPaymentPage(page.pageDef) ||\n context.isForceAccess\n ) {\n return makeHandler(page, context)\n }\n\n // Redirect back to last relevant page\n const redirectTo = findPage(model, relevantPath)\n // Set the return URL unless an exit page\n if (redirectTo?.next.length) {\n request.query.returnUrl = page.getHref(summaryPath)\n }\n\n return proceed(request, h, page.getHref(relevantPath))\n}\n\nasync function importExternalComponentState(\n request: AnyFormRequest,\n page: PageControllerClass,\n state: FormSubmissionState\n): Promise<FormSubmissionState> {\n const externalComponentData = request.yar.flash(EXTERNAL_STATE_APPENDAGE)\n\n if (Array.isArray(externalComponentData)) {\n return state\n }\n\n const typedStateAppendage = externalComponentData as ExternalStateAppendage\n const componentName = typedStateAppendage.component\n const stateAppendage = typedStateAppendage.data\n\n const component = request.app.model?.componentMap.get(componentName)\n\n if (!component) {\n throw new Error(`Component ${componentName} not found in form`)\n }\n\n if (!(component instanceof FormComponent)) {\n throw new TypeError(\n `Component ${componentName} is not a FormComponent and does not support isState`\n )\n }\n\n const isStateValid = component.isState(stateAppendage)\n\n if (!isStateValid) {\n throw new Error(`State for component ${componentName} is invalid`)\n }\n\n // Create state structure from appendage state\n // Some components use a record structure with properties of the format of '<compName>__<fieldName>'\n // e.g. UKAddressField\n // Some components use a single object structure e.g. PaymentField\n const componentState =\n isFormState(stateAppendage) && !component.isAppendageStateSingleObject\n ? Object.fromEntries(\n Object.entries(stateAppendage).map(([key, value]) => [\n `${componentName}__${key}`,\n value\n ])\n )\n : { [componentName]: stateAppendage }\n\n // Save the external component state directly (already has correct key format)\n const savedState = await page.mergeState(request, state, componentState)\n\n // Merge any stashed payload into the local state\n const payload = request.yar.flash(EXTERNAL_STATE_PAYLOAD)\n const stashedPayload = Array.isArray(payload) ? {} : (payload as FormPayload)\n\n const localState = page.getStateFromValidForm(request, savedState, {\n ...stashedPayload,\n ...componentState\n } as FormPayload)\n\n return { ...savedState, ...localState }\n}\n\nexport function makeLoadFormPreHandler(server: Server, options: PluginOptions) {\n // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- hapi types are wrong\n const prefix = server.realm.modifiers.route.prefix ?? ''\n\n const {\n services = defaultServices,\n controllers,\n ordnanceSurveyApiKey\n } = options\n\n async function handler(request: AnyFormRequest, h: ResponseToolkit) {\n if (server.app.model) {\n request.app.model = server.app.model\n\n return h.continue\n }\n\n const { params } = request\n const { slug } = params\n const { isPreview, state: formState } = checkFormStatus(params)\n\n const model = await resolveFormModel(server, slug, formState, {\n services,\n controllers,\n ordnanceSurveyApiKey,\n routePrefix: prefix,\n isPreview\n })\n\n request.app.model = model\n\n return h.continue\n }\n\n return handler\n}\n\nexport function dispatchHandler(request: FormRequest, h: FormResponseToolkit) {\n const { model } = request.app\n\n const servicePath = model ? `/${model.basePath}` : ''\n return proceed(request, h, `${servicePath}${getStartPath(model)}`)\n}\n"],"mappings":"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","ignoreList":[]}
@@ -77,7 +77,12 @@ function logPaymentFailure(session, paymentStatus) {
77
77
  function handlePaymentSuccess(request, h, session, sessionKey, paymentStatus) {
78
78
  flashComponentState(request, session, paymentStatus);
79
79
  request.yar.clear(sessionKey);
80
- return h.redirect(session.returnUrl).code(StatusCodes.SEE_OTHER);
80
+
81
+ // Append paymentComplete flag so the summary page auto-submits
82
+ // instead of showing CYA again
83
+ const separator = session.returnUrl.includes('?') ? '&' : '?';
84
+ const returnUrl = `${session.returnUrl}${separator}paymentComplete=true`;
85
+ return h.redirect(returnUrl).code(StatusCodes.SEE_OTHER);
81
86
  }
82
87
 
83
88
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"payment.js","names":["Boom","StatusCodes","Joi","createLogger","EXTERNAL_STATE_APPENDAGE","getPluginOptions","buildPaymentInfo","convertPenceToPounds","getPaymentContext","PAYMENT_RETURN_PATH","PAYMENT_SESSION_PREFIX","logger","flashComponentState","request","session","paymentStatus","paymentState","paymentId","reference","amount","description","uuid","formId","isLivePayment","payerEmail","email","preAuth","status","createdAt","Date","toISOString","appendage","component","componentName","data","yar","flash","getRoutes","getReturnRoute","logPaymentSuccess","info","state","logPaymentFailure","handlePaymentSuccess","h","sessionKey","clear","redirect","returnUrl","code","SEE_OTHER","handlePaymentFailure","failureUrl","method","path","handler","query","services","server","formsService","nextUrl","_links","next_url","href","badRequest","unknownStatus","internal","options","validate","object","keys","string","required"],"sources":["../../../../../src/server/plugins/engine/routes/payment.js"],"sourcesContent":["import Boom from '@hapi/boom'\nimport { StatusCodes } from 'http-status-codes'\nimport Joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { EXTERNAL_STATE_APPENDAGE } from '~/src/server/constants.js'\nimport { getPluginOptions } from '~/src/server/plugins/engine/helpers.js'\nimport {\n buildPaymentInfo,\n convertPenceToPounds,\n getPaymentContext\n} from '~/src/server/plugins/engine/routes/payment-helper.js'\n\nexport const PAYMENT_RETURN_PATH = '/payment-callback'\nexport const PAYMENT_SESSION_PREFIX = 'payment-'\n\nconst logger = createLogger()\n\n/**\n * Flash form component state after successful payment\n * @param {Request} request - the request\n * @param {PaymentSessionData} session - the session data containing payment state\n * @param {GetPaymentResponse} paymentStatus - the payment status response from GOV.UK Pay\n */\nfunction flashComponentState(request, session, paymentStatus) {\n /** @type {PaymentState} */\n const paymentState = {\n paymentId: paymentStatus.paymentId,\n reference: session.reference,\n amount: session.amount,\n description: session.description,\n uuid: session.uuid,\n formId: session.formId,\n isLivePayment: session.isLivePayment,\n payerEmail: paymentStatus.email,\n preAuth: {\n status: 'success',\n createdAt: new Date().toISOString()\n }\n }\n\n /** @type {ExternalStateAppendage} */\n const appendage = {\n component: session.componentName,\n data: /** @type {FormState} */ (/** @type {unknown} */ (paymentState))\n }\n\n request.yar.flash(EXTERNAL_STATE_APPENDAGE, appendage, true)\n}\n\n/**\n * Gets the payment routes for handling GOV.UK Pay callbacks\n * @returns {ServerRoute[]}\n */\nexport function getRoutes() {\n return [getReturnRoute()]\n}\n\n/**\n * Logs successful payment\n * @param {PaymentSessionData} session - the session data\n * @param {GetPaymentResponse} paymentStatus - the payment status from GOV.UK Pay\n */\nfunction logPaymentSuccess(session, paymentStatus) {\n logger.info(\n buildPaymentInfo(\n 'pre-auth',\n 'success',\n `${paymentStatus.state.status} amount=${convertPenceToPounds(paymentStatus.amount)}`,\n session.isLivePayment,\n paymentStatus.paymentId\n ),\n `[payment] Successful pre-auth for paymentId=${paymentStatus.paymentId}`\n )\n}\n\n/**\n * Logs failed/cancelled payment\n * @param {PaymentSessionData} session - the session data\n * @param {GetPaymentResponse} paymentStatus - the payment status from GOV.UK Pay\n */\nfunction logPaymentFailure(session, paymentStatus) {\n logger.info(\n buildPaymentInfo(\n 'pre-auth',\n 'failed/cancelled',\n `${paymentStatus.state.status} amount=${convertPenceToPounds(paymentStatus.amount)}`,\n session.isLivePayment,\n paymentStatus.paymentId\n ),\n `[payment] Failed/cancelled pre-auth for paymentId=${paymentStatus.paymentId}`\n )\n}\n\n/**\n * Handles successful payment states (capturable/success)\n * @param {Request} request - the request\n * @param {ResponseToolkit} h - the response toolkit\n * @param {PaymentSessionData} session - the session data\n * @param {string} sessionKey - the session key\n * @param {GetPaymentResponse} paymentStatus - the payment status from GOV.UK Pay\n */\nfunction handlePaymentSuccess(request, h, session, sessionKey, paymentStatus) {\n flashComponentState(request, session, paymentStatus)\n request.yar.clear(sessionKey)\n return h.redirect(session.returnUrl).code(StatusCodes.SEE_OTHER)\n}\n\n/**\n * Handles failed/cancelled/error payment states\n * @param {Request} request - the request\n * @param {ResponseToolkit} h - the response toolkit\n * @param {PaymentSessionData} session - the session data\n * @param {string} sessionKey - the session key\n */\nfunction handlePaymentFailure(request, h, session, sessionKey) {\n request.yar.clear(sessionKey)\n return h.redirect(session.failureUrl).code(StatusCodes.SEE_OTHER)\n}\n\n/**\n * Route handler for payment return URL\n * This is called when GOV.UK Pay redirects the user back after payment\n * @returns {ServerRoute}\n */\nfunction getReturnRoute() {\n return {\n method: 'GET',\n path: PAYMENT_RETURN_PATH,\n async handler(request, h) {\n const { uuid } = /** @type {{ uuid: string }} */ (request.query)\n\n const { services } = getPluginOptions(request.server)\n\n const { session, sessionKey, paymentStatus } = await getPaymentContext(\n request,\n uuid,\n /** @type {FormsService} */ (services?.formsService)\n )\n\n /**\n * @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle\n */\n const { status } = paymentStatus.state\n\n switch (status) {\n case 'capturable':\n case 'success':\n logPaymentSuccess(session, paymentStatus)\n return handlePaymentSuccess(\n request,\n h,\n session,\n sessionKey,\n paymentStatus\n )\n\n case 'cancelled':\n case 'failed':\n case 'error':\n logPaymentFailure(session, paymentStatus)\n return handlePaymentFailure(request, h, session, sessionKey)\n\n case 'created':\n case 'started':\n case 'submitted': {\n const nextUrl = paymentStatus._links.next_url?.href\n\n if (!nextUrl) {\n throw Boom.badRequest(\n `Payment in state '${status}' but no next_url available`\n )\n }\n\n return h.redirect(nextUrl).code(StatusCodes.SEE_OTHER)\n }\n\n default: {\n const unknownStatus = /** @type {string} */ (status)\n throw Boom.internal(`Unknown payment status: ${unknownStatus}`)\n }\n }\n },\n options: {\n validate: {\n query: Joi.object()\n .keys({\n uuid: Joi.string().uuid().required()\n })\n .required()\n }\n }\n }\n}\n\n/**\n * @import { Request, ResponseToolkit, ServerRoute } from '@hapi/hapi'\n * @import { GetPaymentResponse, PaymentSessionData } from '~/src/server/plugins/payment/types.js'\n * @import { PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'\n * @import { ExternalStateAppendage, FormState } from '~/src/server/plugins/engine/types.js'\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n * @import { FormsService } from '~/src/server/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"file":"payment.js","names":["Boom","StatusCodes","Joi","createLogger","EXTERNAL_STATE_APPENDAGE","getPluginOptions","buildPaymentInfo","convertPenceToPounds","getPaymentContext","PAYMENT_RETURN_PATH","PAYMENT_SESSION_PREFIX","logger","flashComponentState","request","session","paymentStatus","paymentState","paymentId","reference","amount","description","uuid","formId","isLivePayment","payerEmail","email","preAuth","status","createdAt","Date","toISOString","appendage","component","componentName","data","yar","flash","getRoutes","getReturnRoute","logPaymentSuccess","info","state","logPaymentFailure","handlePaymentSuccess","h","sessionKey","clear","separator","returnUrl","includes","redirect","code","SEE_OTHER","handlePaymentFailure","failureUrl","method","path","handler","query","services","server","formsService","nextUrl","_links","next_url","href","badRequest","unknownStatus","internal","options","validate","object","keys","string","required"],"sources":["../../../../../src/server/plugins/engine/routes/payment.js"],"sourcesContent":["import Boom from '@hapi/boom'\nimport { StatusCodes } from 'http-status-codes'\nimport Joi from 'joi'\n\nimport { createLogger } from '~/src/server/common/helpers/logging/logger.js'\nimport { EXTERNAL_STATE_APPENDAGE } from '~/src/server/constants.js'\nimport { getPluginOptions } from '~/src/server/plugins/engine/helpers.js'\nimport {\n buildPaymentInfo,\n convertPenceToPounds,\n getPaymentContext\n} from '~/src/server/plugins/engine/routes/payment-helper.js'\n\nexport const PAYMENT_RETURN_PATH = '/payment-callback'\nexport const PAYMENT_SESSION_PREFIX = 'payment-'\n\nconst logger = createLogger()\n\n/**\n * Flash form component state after successful payment\n * @param {Request} request - the request\n * @param {PaymentSessionData} session - the session data containing payment state\n * @param {GetPaymentResponse} paymentStatus - the payment status response from GOV.UK Pay\n */\nfunction flashComponentState(request, session, paymentStatus) {\n /** @type {PaymentState} */\n const paymentState = {\n paymentId: paymentStatus.paymentId,\n reference: session.reference,\n amount: session.amount,\n description: session.description,\n uuid: session.uuid,\n formId: session.formId,\n isLivePayment: session.isLivePayment,\n payerEmail: paymentStatus.email,\n preAuth: {\n status: 'success',\n createdAt: new Date().toISOString()\n }\n }\n\n /** @type {ExternalStateAppendage} */\n const appendage = {\n component: session.componentName,\n data: /** @type {FormState} */ (/** @type {unknown} */ (paymentState))\n }\n\n request.yar.flash(EXTERNAL_STATE_APPENDAGE, appendage, true)\n}\n\n/**\n * Gets the payment routes for handling GOV.UK Pay callbacks\n * @returns {ServerRoute[]}\n */\nexport function getRoutes() {\n return [getReturnRoute()]\n}\n\n/**\n * Logs successful payment\n * @param {PaymentSessionData} session - the session data\n * @param {GetPaymentResponse} paymentStatus - the payment status from GOV.UK Pay\n */\nfunction logPaymentSuccess(session, paymentStatus) {\n logger.info(\n buildPaymentInfo(\n 'pre-auth',\n 'success',\n `${paymentStatus.state.status} amount=${convertPenceToPounds(paymentStatus.amount)}`,\n session.isLivePayment,\n paymentStatus.paymentId\n ),\n `[payment] Successful pre-auth for paymentId=${paymentStatus.paymentId}`\n )\n}\n\n/**\n * Logs failed/cancelled payment\n * @param {PaymentSessionData} session - the session data\n * @param {GetPaymentResponse} paymentStatus - the payment status from GOV.UK Pay\n */\nfunction logPaymentFailure(session, paymentStatus) {\n logger.info(\n buildPaymentInfo(\n 'pre-auth',\n 'failed/cancelled',\n `${paymentStatus.state.status} amount=${convertPenceToPounds(paymentStatus.amount)}`,\n session.isLivePayment,\n paymentStatus.paymentId\n ),\n `[payment] Failed/cancelled pre-auth for paymentId=${paymentStatus.paymentId}`\n )\n}\n\n/**\n * Handles successful payment states (capturable/success)\n * @param {Request} request - the request\n * @param {ResponseToolkit} h - the response toolkit\n * @param {PaymentSessionData} session - the session data\n * @param {string} sessionKey - the session key\n * @param {GetPaymentResponse} paymentStatus - the payment status from GOV.UK Pay\n */\nfunction handlePaymentSuccess(request, h, session, sessionKey, paymentStatus) {\n flashComponentState(request, session, paymentStatus)\n request.yar.clear(sessionKey)\n\n // Append paymentComplete flag so the summary page auto-submits\n // instead of showing CYA again\n const separator = session.returnUrl.includes('?') ? '&' : '?'\n const returnUrl = `${session.returnUrl}${separator}paymentComplete=true`\n\n return h.redirect(returnUrl).code(StatusCodes.SEE_OTHER)\n}\n\n/**\n * Handles failed/cancelled/error payment states\n * @param {Request} request - the request\n * @param {ResponseToolkit} h - the response toolkit\n * @param {PaymentSessionData} session - the session data\n * @param {string} sessionKey - the session key\n */\nfunction handlePaymentFailure(request, h, session, sessionKey) {\n request.yar.clear(sessionKey)\n return h.redirect(session.failureUrl).code(StatusCodes.SEE_OTHER)\n}\n\n/**\n * Route handler for payment return URL\n * This is called when GOV.UK Pay redirects the user back after payment\n * @returns {ServerRoute}\n */\nfunction getReturnRoute() {\n return {\n method: 'GET',\n path: PAYMENT_RETURN_PATH,\n async handler(request, h) {\n const { uuid } = /** @type {{ uuid: string }} */ (request.query)\n\n const { services } = getPluginOptions(request.server)\n\n const { session, sessionKey, paymentStatus } = await getPaymentContext(\n request,\n uuid,\n /** @type {FormsService} */ (services?.formsService)\n )\n\n /**\n * @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle\n */\n const { status } = paymentStatus.state\n\n switch (status) {\n case 'capturable':\n case 'success':\n logPaymentSuccess(session, paymentStatus)\n return handlePaymentSuccess(\n request,\n h,\n session,\n sessionKey,\n paymentStatus\n )\n\n case 'cancelled':\n case 'failed':\n case 'error':\n logPaymentFailure(session, paymentStatus)\n return handlePaymentFailure(request, h, session, sessionKey)\n\n case 'created':\n case 'started':\n case 'submitted': {\n const nextUrl = paymentStatus._links.next_url?.href\n\n if (!nextUrl) {\n throw Boom.badRequest(\n `Payment in state '${status}' but no next_url available`\n )\n }\n\n return h.redirect(nextUrl).code(StatusCodes.SEE_OTHER)\n }\n\n default: {\n const unknownStatus = /** @type {string} */ (status)\n throw Boom.internal(`Unknown payment status: ${unknownStatus}`)\n }\n }\n },\n options: {\n validate: {\n query: Joi.object()\n .keys({\n uuid: Joi.string().uuid().required()\n })\n .required()\n }\n }\n }\n}\n\n/**\n * @import { Request, ResponseToolkit, ServerRoute } from '@hapi/hapi'\n * @import { GetPaymentResponse, PaymentSessionData } from '~/src/server/plugins/payment/types.js'\n * @import { PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'\n * @import { ExternalStateAppendage, FormState } from '~/src/server/plugins/engine/types.js'\n * @import { PluginOptions } from '~/src/server/plugins/engine/types.js'\n * @import { FormsService } from '~/src/server/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
@@ -34,10 +34,10 @@ describe('Payment routes', () => {
34
34
  const sessionKey = 'session-key';
35
35
  test.each([{
36
36
  status: 'capturable',
37
- finalUrl: 'http://host.com/return-url'
37
+ finalUrl: 'http://host.com/return-url?paymentComplete=true'
38
38
  }, {
39
39
  status: 'success',
40
- finalUrl: 'http://host.com/return-url'
40
+ finalUrl: 'http://host.com/return-url?paymentComplete=true'
41
41
  }, {
42
42
  status: 'cancelled',
43
43
  finalUrl: 'http://host.com/failure-url'
@@ -175,7 +175,7 @@ describe('Payment routes', () => {
175
175
  response
176
176
  } = await renderResponse(server, options);
177
177
  expect(response.statusCode).toBe(StatusCodes.SEE_OTHER);
178
- expect(response.headers.location).toBe('http://host.com/return-url');
178
+ expect(response.headers.location).toBe('http://host.com/return-url?paymentComplete=true');
179
179
  });
180
180
  });
181
181
  });
@@ -1 +1 @@
1
- {"version":3,"file":"payment.test.js","names":["StatusCodes","createServer","getPaymentContext","renderResponse","jest","mock","describe","server","beforeAll","initialize","beforeEach","resetAllMocks","uuid","options","method","url","paymentSessionData","formId","reference","paymentId","amount","description","isLivePayment","componentName","returnUrl","failureUrl","sessionKey","test","each","status","finalUrl","row","paymentStatus","_links","next_url","href","self","state","finished","mocked","mockResolvedValueOnce","session","response","expect","statusCode","toBe","SEE_OTHER","headers","location","it","BAD_REQUEST","result","message","INTERNAL_SERVER_ERROR","payment_id","email"],"sources":["../../../../../src/server/plugins/engine/routes/payment.test.js"],"sourcesContent":["import { StatusCodes } from 'http-status-codes'\n\nimport { createServer } from '~/src/server/index.js'\nimport { getPaymentContext } from '~/src/server/plugins/engine/routes/payment-helper.js'\nimport { renderResponse } from '~/test/helpers/component-helpers.js'\n\njest.mock('~/src/server/plugins/engine/routes/payment-helper.js')\n\ndescribe('Payment routes', () => {\n /** @type {Server} */\n let server\n\n beforeAll(async () => {\n server = await createServer()\n await server.initialize()\n })\n\n beforeEach(() => {\n jest.resetAllMocks()\n })\n\n describe('Return route /payment-callback', () => {\n const uuid = '06a5b11e-e3e0-48a2-8ac3-56c0fcb6c20d'\n const options = {\n method: 'get',\n url: `/payment-callback?uuid=${uuid}`\n }\n\n const paymentSessionData = {\n uuid,\n formId: 'form-id',\n reference: 'form-ref-123',\n paymentId: 'payment-id',\n amount: 123,\n description: 'Payment desc',\n isLivePayment: false,\n componentName: 'my-component',\n returnUrl: 'http://host.com/return-url',\n failureUrl: 'http://host.com/failure-url'\n }\n const sessionKey = 'session-key'\n\n test.each([\n { status: 'capturable', finalUrl: 'http://host.com/return-url' },\n { status: 'success', finalUrl: 'http://host.com/return-url' },\n { status: 'cancelled', finalUrl: 'http://host.com/failure-url' },\n { status: 'failed', finalUrl: 'http://host.com/failure-url' },\n { status: 'error', finalUrl: 'http://host.com/failure-url' },\n { status: 'created', finalUrl: '/next-url' },\n { status: 'started', finalUrl: '/next-url' },\n { status: 'submitted', finalUrl: '/next-url' }\n ])('should handle payment status of $row.status', async (row) => {\n const paymentStatus = {\n paymentId: 'new-payment-id',\n amount: 125,\n _links: {\n next_url: {\n href: '/next-url',\n method: 'get'\n },\n self: {\n href: '/self',\n method: 'get'\n }\n },\n state: /** @type {PaymentResponseState} */ ({\n status: row.status,\n finished: true\n })\n }\n jest.mocked(getPaymentContext).mockResolvedValueOnce({\n session: paymentSessionData,\n sessionKey,\n paymentStatus\n })\n const { response } = await renderResponse(server, options)\n\n expect(response.statusCode).toBe(StatusCodes.SEE_OTHER)\n expect(response.headers.location).toBe(row.finalUrl)\n })\n\n it('should throw if nextUrl is missing', async () => {\n const paymentStatus = {\n paymentId: 'new-payment-id',\n _links: {\n next_url: {},\n self: {\n href: '/self',\n method: 'get'\n }\n },\n state: /** @type {PaymentResponseState} */ ({\n status: 'created',\n finished: true\n })\n }\n jest.mocked(getPaymentContext).mockResolvedValueOnce({\n session: paymentSessionData,\n sessionKey,\n // @ts-expect-error - deliberate missing element from object\n paymentStatus\n })\n const { response } = await renderResponse(server, options)\n\n expect(response.statusCode).toBe(StatusCodes.BAD_REQUEST)\n // @ts-expect-error - error object\n expect(response.result?.message).toBe(\n \"Payment in state 'created' but no next_url available\"\n )\n })\n\n it('should throw if invalid status', async () => {\n const paymentStatus = {\n paymentId: 'new-payment-id',\n _links: {\n next_url: {\n href: '/next-url',\n method: 'get'\n },\n self: {\n href: '/self',\n method: 'get'\n }\n },\n state: {\n status: 'invalid',\n finished: true\n }\n }\n jest.mocked(getPaymentContext).mockResolvedValueOnce({\n session: paymentSessionData,\n sessionKey,\n // @ts-expect-error - deliberate invalid value which doesnt meet type\n paymentStatus\n })\n const { response } = await renderResponse(server, options)\n\n expect(response.statusCode).toBe(StatusCodes.INTERNAL_SERVER_ERROR)\n // @ts-expect-error - error object\n expect(response.result?.message).toBe('Unknown payment status: invalid')\n })\n\n it('should handle payment with email from GOV.UK Pay response', async () => {\n const paymentStatus = {\n paymentId: 'new-payment-id',\n payment_id: 'new-payment-id',\n amount: 125,\n email: 'payer@example.com',\n _links: {\n next_url: {\n href: '/next-url',\n method: 'get'\n },\n self: {\n href: '/self',\n method: 'get'\n }\n },\n state: /** @type {PaymentResponseState} */ ({\n status: 'success',\n finished: true\n })\n }\n jest.mocked(getPaymentContext).mockResolvedValueOnce({\n session: paymentSessionData,\n sessionKey,\n paymentStatus\n })\n const { response } = await renderResponse(server, options)\n\n expect(response.statusCode).toBe(StatusCodes.SEE_OTHER)\n expect(response.headers.location).toBe('http://host.com/return-url')\n })\n })\n})\n\n/**\n * @import { Server } from '@hapi/hapi'\n * @import { PaymentResponseState } from '~/src/server/plugins/payment/types.js'\n */\n"],"mappings":"AAAA,SAASA,WAAW,QAAQ,mBAAmB;AAE/C,SAASC,YAAY;AACrB,SAASC,iBAAiB;AAC1B,SAASC,cAAc;AAEvBC,IAAI,CAACC,IAAI,sBAAuD,CAAC;AAEjEC,QAAQ,CAAC,gBAAgB,EAAE,MAAM;EAC/B;EACA,IAAIC,MAAM;EAEVC,SAAS,CAAC,YAAY;IACpBD,MAAM,GAAG,MAAMN,YAAY,CAAC,CAAC;IAC7B,MAAMM,MAAM,CAACE,UAAU,CAAC,CAAC;EAC3B,CAAC,CAAC;EAEFC,UAAU,CAAC,MAAM;IACfN,IAAI,CAACO,aAAa,CAAC,CAAC;EACtB,CAAC,CAAC;EAEFL,QAAQ,CAAC,gCAAgC,EAAE,MAAM;IAC/C,MAAMM,IAAI,GAAG,sCAAsC;IACnD,MAAMC,OAAO,GAAG;MACdC,MAAM,EAAE,KAAK;MACbC,GAAG,EAAE,0BAA0BH,IAAI;IACrC,CAAC;IAED,MAAMI,kBAAkB,GAAG;MACzBJ,IAAI;MACJK,MAAM,EAAE,SAAS;MACjBC,SAAS,EAAE,cAAc;MACzBC,SAAS,EAAE,YAAY;MACvBC,MAAM,EAAE,GAAG;MACXC,WAAW,EAAE,cAAc;MAC3BC,aAAa,EAAE,KAAK;MACpBC,aAAa,EAAE,cAAc;MAC7BC,SAAS,EAAE,4BAA4B;MACvCC,UAAU,EAAE;IACd,CAAC;IACD,MAAMC,UAAU,GAAG,aAAa;IAEhCC,IAAI,CAACC,IAAI,CAAC,CACR;MAAEC,MAAM,EAAE,YAAY;MAAEC,QAAQ,EAAE;IAA6B,CAAC,EAChE;MAAED,MAAM,EAAE,SAAS;MAAEC,QAAQ,EAAE;IAA6B,CAAC,EAC7D;MAAED,MAAM,EAAE,WAAW;MAAEC,QAAQ,EAAE;IAA8B,CAAC,EAChE;MAAED,MAAM,EAAE,QAAQ;MAAEC,QAAQ,EAAE;IAA8B,CAAC,EAC7D;MAAED,MAAM,EAAE,OAAO;MAAEC,QAAQ,EAAE;IAA8B,CAAC,EAC5D;MAAED,MAAM,EAAE,SAAS;MAAEC,QAAQ,EAAE;IAAY,CAAC,EAC5C;MAAED,MAAM,EAAE,SAAS;MAAEC,QAAQ,EAAE;IAAY,CAAC,EAC5C;MAAED,MAAM,EAAE,WAAW;MAAEC,QAAQ,EAAE;IAAY,CAAC,CAC/C,CAAC,CAAC,6CAA6C,EAAE,MAAOC,GAAG,IAAK;MAC/D,MAAMC,aAAa,GAAG;QACpBb,SAAS,EAAE,gBAAgB;QAC3BC,MAAM,EAAE,GAAG;QACXa,MAAM,EAAE;UACNC,QAAQ,EAAE;YACRC,IAAI,EAAE,WAAW;YACjBrB,MAAM,EAAE;UACV,CAAC;UACDsB,IAAI,EAAE;YACJD,IAAI,EAAE,OAAO;YACbrB,MAAM,EAAE;UACV;QACF,CAAC;QACDuB,KAAK,GAAE,mCAAqC;UAC1CR,MAAM,EAAEE,GAAG,CAACF,MAAM;UAClBS,QAAQ,EAAE;QACZ,CAAC;MACH,CAAC;MACDlC,IAAI,CAACmC,MAAM,CAACrC,iBAAiB,CAAC,CAACsC,qBAAqB,CAAC;QACnDC,OAAO,EAAEzB,kBAAkB;QAC3BU,UAAU;QACVM;MACF,CAAC,CAAC;MACF,MAAM;QAAEU;MAAS,CAAC,GAAG,MAAMvC,cAAc,CAACI,MAAM,EAAEM,OAAO,CAAC;MAE1D8B,MAAM,CAACD,QAAQ,CAACE,UAAU,CAAC,CAACC,IAAI,CAAC7C,WAAW,CAAC8C,SAAS,CAAC;MACvDH,MAAM,CAACD,QAAQ,CAACK,OAAO,CAACC,QAAQ,CAAC,CAACH,IAAI,CAACd,GAAG,CAACD,QAAQ,CAAC;IACtD,CAAC,CAAC;IAEFmB,EAAE,CAAC,oCAAoC,EAAE,YAAY;MACnD,MAAMjB,aAAa,GAAG;QACpBb,SAAS,EAAE,gBAAgB;QAC3Bc,MAAM,EAAE;UACNC,QAAQ,EAAE,CAAC,CAAC;UACZE,IAAI,EAAE;YACJD,IAAI,EAAE,OAAO;YACbrB,MAAM,EAAE;UACV;QACF,CAAC;QACDuB,KAAK,GAAE,mCAAqC;UAC1CR,MAAM,EAAE,SAAS;UACjBS,QAAQ,EAAE;QACZ,CAAC;MACH,CAAC;MACDlC,IAAI,CAACmC,MAAM,CAACrC,iBAAiB,CAAC,CAACsC,qBAAqB,CAAC;QACnDC,OAAO,EAAEzB,kBAAkB;QAC3BU,UAAU;QACV;QACAM;MACF,CAAC,CAAC;MACF,MAAM;QAAEU;MAAS,CAAC,GAAG,MAAMvC,cAAc,CAACI,MAAM,EAAEM,OAAO,CAAC;MAE1D8B,MAAM,CAACD,QAAQ,CAACE,UAAU,CAAC,CAACC,IAAI,CAAC7C,WAAW,CAACkD,WAAW,CAAC;MACzD;MACAP,MAAM,CAACD,QAAQ,CAACS,MAAM,EAAEC,OAAO,CAAC,CAACP,IAAI,CACnC,sDACF,CAAC;IACH,CAAC,CAAC;IAEFI,EAAE,CAAC,gCAAgC,EAAE,YAAY;MAC/C,MAAMjB,aAAa,GAAG;QACpBb,SAAS,EAAE,gBAAgB;QAC3Bc,MAAM,EAAE;UACNC,QAAQ,EAAE;YACRC,IAAI,EAAE,WAAW;YACjBrB,MAAM,EAAE;UACV,CAAC;UACDsB,IAAI,EAAE;YACJD,IAAI,EAAE,OAAO;YACbrB,MAAM,EAAE;UACV;QACF,CAAC;QACDuB,KAAK,EAAE;UACLR,MAAM,EAAE,SAAS;UACjBS,QAAQ,EAAE;QACZ;MACF,CAAC;MACDlC,IAAI,CAACmC,MAAM,CAACrC,iBAAiB,CAAC,CAACsC,qBAAqB,CAAC;QACnDC,OAAO,EAAEzB,kBAAkB;QAC3BU,UAAU;QACV;QACAM;MACF,CAAC,CAAC;MACF,MAAM;QAAEU;MAAS,CAAC,GAAG,MAAMvC,cAAc,CAACI,MAAM,EAAEM,OAAO,CAAC;MAE1D8B,MAAM,CAACD,QAAQ,CAACE,UAAU,CAAC,CAACC,IAAI,CAAC7C,WAAW,CAACqD,qBAAqB,CAAC;MACnE;MACAV,MAAM,CAACD,QAAQ,CAACS,MAAM,EAAEC,OAAO,CAAC,CAACP,IAAI,CAAC,iCAAiC,CAAC;IAC1E,CAAC,CAAC;IAEFI,EAAE,CAAC,2DAA2D,EAAE,YAAY;MAC1E,MAAMjB,aAAa,GAAG;QACpBb,SAAS,EAAE,gBAAgB;QAC3BmC,UAAU,EAAE,gBAAgB;QAC5BlC,MAAM,EAAE,GAAG;QACXmC,KAAK,EAAE,mBAAmB;QAC1BtB,MAAM,EAAE;UACNC,QAAQ,EAAE;YACRC,IAAI,EAAE,WAAW;YACjBrB,MAAM,EAAE;UACV,CAAC;UACDsB,IAAI,EAAE;YACJD,IAAI,EAAE,OAAO;YACbrB,MAAM,EAAE;UACV;QACF,CAAC;QACDuB,KAAK,GAAE,mCAAqC;UAC1CR,MAAM,EAAE,SAAS;UACjBS,QAAQ,EAAE;QACZ,CAAC;MACH,CAAC;MACDlC,IAAI,CAACmC,MAAM,CAACrC,iBAAiB,CAAC,CAACsC,qBAAqB,CAAC;QACnDC,OAAO,EAAEzB,kBAAkB;QAC3BU,UAAU;QACVM;MACF,CAAC,CAAC;MACF,MAAM;QAAEU;MAAS,CAAC,GAAG,MAAMvC,cAAc,CAACI,MAAM,EAAEM,OAAO,CAAC;MAE1D8B,MAAM,CAACD,QAAQ,CAACE,UAAU,CAAC,CAACC,IAAI,CAAC7C,WAAW,CAAC8C,SAAS,CAAC;MACvDH,MAAM,CAACD,QAAQ,CAACK,OAAO,CAACC,QAAQ,CAAC,CAACH,IAAI,CAAC,4BAA4B,CAAC;IACtE,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ,CAAC,CAAC;;AAEF;AACA;AACA;AACA","ignoreList":[]}
1
+ {"version":3,"file":"payment.test.js","names":["StatusCodes","createServer","getPaymentContext","renderResponse","jest","mock","describe","server","beforeAll","initialize","beforeEach","resetAllMocks","uuid","options","method","url","paymentSessionData","formId","reference","paymentId","amount","description","isLivePayment","componentName","returnUrl","failureUrl","sessionKey","test","each","status","finalUrl","row","paymentStatus","_links","next_url","href","self","state","finished","mocked","mockResolvedValueOnce","session","response","expect","statusCode","toBe","SEE_OTHER","headers","location","it","BAD_REQUEST","result","message","INTERNAL_SERVER_ERROR","payment_id","email"],"sources":["../../../../../src/server/plugins/engine/routes/payment.test.js"],"sourcesContent":["import { StatusCodes } from 'http-status-codes'\n\nimport { createServer } from '~/src/server/index.js'\nimport { getPaymentContext } from '~/src/server/plugins/engine/routes/payment-helper.js'\nimport { renderResponse } from '~/test/helpers/component-helpers.js'\n\njest.mock('~/src/server/plugins/engine/routes/payment-helper.js')\n\ndescribe('Payment routes', () => {\n /** @type {Server} */\n let server\n\n beforeAll(async () => {\n server = await createServer()\n await server.initialize()\n })\n\n beforeEach(() => {\n jest.resetAllMocks()\n })\n\n describe('Return route /payment-callback', () => {\n const uuid = '06a5b11e-e3e0-48a2-8ac3-56c0fcb6c20d'\n const options = {\n method: 'get',\n url: `/payment-callback?uuid=${uuid}`\n }\n\n const paymentSessionData = {\n uuid,\n formId: 'form-id',\n reference: 'form-ref-123',\n paymentId: 'payment-id',\n amount: 123,\n description: 'Payment desc',\n isLivePayment: false,\n componentName: 'my-component',\n returnUrl: 'http://host.com/return-url',\n failureUrl: 'http://host.com/failure-url'\n }\n const sessionKey = 'session-key'\n\n test.each([\n {\n status: 'capturable',\n finalUrl: 'http://host.com/return-url?paymentComplete=true'\n },\n {\n status: 'success',\n finalUrl: 'http://host.com/return-url?paymentComplete=true'\n },\n { status: 'cancelled', finalUrl: 'http://host.com/failure-url' },\n { status: 'failed', finalUrl: 'http://host.com/failure-url' },\n { status: 'error', finalUrl: 'http://host.com/failure-url' },\n { status: 'created', finalUrl: '/next-url' },\n { status: 'started', finalUrl: '/next-url' },\n { status: 'submitted', finalUrl: '/next-url' }\n ])('should handle payment status of $row.status', async (row) => {\n const paymentStatus = {\n paymentId: 'new-payment-id',\n amount: 125,\n _links: {\n next_url: {\n href: '/next-url',\n method: 'get'\n },\n self: {\n href: '/self',\n method: 'get'\n }\n },\n state: /** @type {PaymentResponseState} */ ({\n status: row.status,\n finished: true\n })\n }\n jest.mocked(getPaymentContext).mockResolvedValueOnce({\n session: paymentSessionData,\n sessionKey,\n paymentStatus\n })\n const { response } = await renderResponse(server, options)\n\n expect(response.statusCode).toBe(StatusCodes.SEE_OTHER)\n expect(response.headers.location).toBe(row.finalUrl)\n })\n\n it('should throw if nextUrl is missing', async () => {\n const paymentStatus = {\n paymentId: 'new-payment-id',\n _links: {\n next_url: {},\n self: {\n href: '/self',\n method: 'get'\n }\n },\n state: /** @type {PaymentResponseState} */ ({\n status: 'created',\n finished: true\n })\n }\n jest.mocked(getPaymentContext).mockResolvedValueOnce({\n session: paymentSessionData,\n sessionKey,\n // @ts-expect-error - deliberate missing element from object\n paymentStatus\n })\n const { response } = await renderResponse(server, options)\n\n expect(response.statusCode).toBe(StatusCodes.BAD_REQUEST)\n // @ts-expect-error - error object\n expect(response.result?.message).toBe(\n \"Payment in state 'created' but no next_url available\"\n )\n })\n\n it('should throw if invalid status', async () => {\n const paymentStatus = {\n paymentId: 'new-payment-id',\n _links: {\n next_url: {\n href: '/next-url',\n method: 'get'\n },\n self: {\n href: '/self',\n method: 'get'\n }\n },\n state: {\n status: 'invalid',\n finished: true\n }\n }\n jest.mocked(getPaymentContext).mockResolvedValueOnce({\n session: paymentSessionData,\n sessionKey,\n // @ts-expect-error - deliberate invalid value which doesnt meet type\n paymentStatus\n })\n const { response } = await renderResponse(server, options)\n\n expect(response.statusCode).toBe(StatusCodes.INTERNAL_SERVER_ERROR)\n // @ts-expect-error - error object\n expect(response.result?.message).toBe('Unknown payment status: invalid')\n })\n\n it('should handle payment with email from GOV.UK Pay response', async () => {\n const paymentStatus = {\n paymentId: 'new-payment-id',\n payment_id: 'new-payment-id',\n amount: 125,\n email: 'payer@example.com',\n _links: {\n next_url: {\n href: '/next-url',\n method: 'get'\n },\n self: {\n href: '/self',\n method: 'get'\n }\n },\n state: /** @type {PaymentResponseState} */ ({\n status: 'success',\n finished: true\n })\n }\n jest.mocked(getPaymentContext).mockResolvedValueOnce({\n session: paymentSessionData,\n sessionKey,\n paymentStatus\n })\n const { response } = await renderResponse(server, options)\n\n expect(response.statusCode).toBe(StatusCodes.SEE_OTHER)\n expect(response.headers.location).toBe(\n 'http://host.com/return-url?paymentComplete=true'\n )\n })\n })\n})\n\n/**\n * @import { Server } from '@hapi/hapi'\n * @import { PaymentResponseState } from '~/src/server/plugins/payment/types.js'\n */\n"],"mappings":"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","ignoreList":[]}
@@ -63,6 +63,12 @@ export const formsService = async () => {
63
63
  title: 'Payment Test Form',
64
64
  slug: 'payment-test'
65
65
  });
66
+ await loader.addForm('src/server/forms/payment-v2-test.yaml', {
67
+ ...metadata,
68
+ id: 'c3d4e5f6-a7b8-9012-cdef-012345678901',
69
+ title: 'Apply for a lock and weir fishing permit (v2 payment test)',
70
+ slug: 'payment-v2-test'
71
+ });
66
72
  return loader.toFormsService();
67
73
  };
68
74
  //# sourceMappingURL=localFormsService.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"localFormsService.js","names":["config","FileFormService","now","Date","user","id","displayName","author","createdAt","createdBy","updatedAt","updatedBy","metadata","organisation","teamName","teamEmail","submissionGuidance","notificationEmail","get","live","formsService","loader","addForm","title","slug","toFormsService"],"sources":["../../../../../src/server/plugins/engine/services/localFormsService.js"],"sourcesContent":["import { config } from '~/src/config/index.js'\nimport { FileFormService } from '~/src/server/utils/file-form-service.js'\n\n// Create shared form metadata\nconst now = new Date()\nconst user = { id: 'user', displayName: 'Username' }\nconst author = {\n createdAt: now,\n createdBy: user,\n updatedAt: now,\n updatedBy: user\n}\nconst metadata = {\n organisation: 'Defra',\n teamName: 'Team name',\n teamEmail: 'team@defra.gov.uk',\n submissionGuidance: \"Thanks for your submission, we'll be in touch\",\n notificationEmail: config.get('submissionEmailAddress'),\n ...author,\n live: author\n}\n\n/**\n * Return an function rather than the service directly. This is to prevent consumer applications\n * blowing up as they won't have these files on disk. We can defer the execution until when it's\n * needed, i.e. the createServer function of the devtool.\n */\nexport const formsService = async () => {\n // Instantiate the file loader form service\n const loader = new FileFormService()\n\n // Add a Yaml form\n await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.yaml', {\n ...metadata,\n id: '641aeafd-13dd-40fa-9186-001703800efb',\n title: 'Register as a unicorn breeder',\n slug: 'register-as-a-unicorn-breeder'\n })\n\n await loader.addForm('src/server/forms/page-events.yaml', {\n ...metadata,\n id: '511db05e-ebbd-42e8-8270-5fe93f5c9762',\n title: 'Page events demo',\n slug: 'page-events-demo'\n })\n\n await loader.addForm('src/server/forms/components.json', {\n ...metadata,\n id: '6a872d3b-13f9e-804ce3e-4830-5c45fb32',\n title: 'Components',\n slug: 'components'\n })\n\n await loader.addForm('src/server/forms/simple-form.yaml', {\n ...metadata,\n id: 'a1b2c3d4-e5f6-7890-abcd-ef0123456789',\n title: 'Simple Form',\n slug: 'simple-form'\n })\n\n await loader.addForm('src/server/forms/payment-test.yaml', {\n ...metadata,\n id: 'b2c3d4e5-f6a7-8901-bcde-f01234567890',\n title: 'Payment Test Form',\n slug: 'payment-test'\n })\n\n return loader.toFormsService()\n}\n"],"mappings":"AAAA,SAASA,MAAM;AACf,SAASC,eAAe;;AAExB;AACA,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;AACtB,MAAMC,IAAI,GAAG;EAAEC,EAAE,EAAE,MAAM;EAAEC,WAAW,EAAE;AAAW,CAAC;AACpD,MAAMC,MAAM,GAAG;EACbC,SAAS,EAAEN,GAAG;EACdO,SAAS,EAAEL,IAAI;EACfM,SAAS,EAAER,GAAG;EACdS,SAAS,EAAEP;AACb,CAAC;AACD,MAAMQ,QAAQ,GAAG;EACfC,YAAY,EAAE,OAAO;EACrBC,QAAQ,EAAE,WAAW;EACrBC,SAAS,EAAE,mBAAmB;EAC9BC,kBAAkB,EAAE,+CAA+C;EACnEC,iBAAiB,EAAEjB,MAAM,CAACkB,GAAG,CAAC,wBAAwB,CAAC;EACvD,GAAGX,MAAM;EACTY,IAAI,EAAEZ;AACR,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMa,YAAY,GAAG,MAAAA,CAAA,KAAY;EACtC;EACA,MAAMC,MAAM,GAAG,IAAIpB,eAAe,CAAC,CAAC;;EAEpC;EACA,MAAMoB,MAAM,CAACC,OAAO,CAAC,qDAAqD,EAAE;IAC1E,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,+BAA+B;IACtCC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,mCAAmC,EAAE;IACxD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,kBAAkB;IACzBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,kCAAkC,EAAE;IACvD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,YAAY;IACnBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,mCAAmC,EAAE;IACxD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,aAAa;IACpBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,oCAAoC,EAAE;IACzD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,mBAAmB;IAC1BC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,OAAOH,MAAM,CAACI,cAAc,CAAC,CAAC;AAChC,CAAC","ignoreList":[]}
1
+ {"version":3,"file":"localFormsService.js","names":["config","FileFormService","now","Date","user","id","displayName","author","createdAt","createdBy","updatedAt","updatedBy","metadata","organisation","teamName","teamEmail","submissionGuidance","notificationEmail","get","live","formsService","loader","addForm","title","slug","toFormsService"],"sources":["../../../../../src/server/plugins/engine/services/localFormsService.js"],"sourcesContent":["import { config } from '~/src/config/index.js'\nimport { FileFormService } from '~/src/server/utils/file-form-service.js'\n\n// Create shared form metadata\nconst now = new Date()\nconst user = { id: 'user', displayName: 'Username' }\nconst author = {\n createdAt: now,\n createdBy: user,\n updatedAt: now,\n updatedBy: user\n}\nconst metadata = {\n organisation: 'Defra',\n teamName: 'Team name',\n teamEmail: 'team@defra.gov.uk',\n submissionGuidance: \"Thanks for your submission, we'll be in touch\",\n notificationEmail: config.get('submissionEmailAddress'),\n ...author,\n live: author\n}\n\n/**\n * Return an function rather than the service directly. This is to prevent consumer applications\n * blowing up as they won't have these files on disk. We can defer the execution until when it's\n * needed, i.e. the createServer function of the devtool.\n */\nexport const formsService = async () => {\n // Instantiate the file loader form service\n const loader = new FileFormService()\n\n // Add a Yaml form\n await loader.addForm('src/server/forms/register-as-a-unicorn-breeder.yaml', {\n ...metadata,\n id: '641aeafd-13dd-40fa-9186-001703800efb',\n title: 'Register as a unicorn breeder',\n slug: 'register-as-a-unicorn-breeder'\n })\n\n await loader.addForm('src/server/forms/page-events.yaml', {\n ...metadata,\n id: '511db05e-ebbd-42e8-8270-5fe93f5c9762',\n title: 'Page events demo',\n slug: 'page-events-demo'\n })\n\n await loader.addForm('src/server/forms/components.json', {\n ...metadata,\n id: '6a872d3b-13f9e-804ce3e-4830-5c45fb32',\n title: 'Components',\n slug: 'components'\n })\n\n await loader.addForm('src/server/forms/simple-form.yaml', {\n ...metadata,\n id: 'a1b2c3d4-e5f6-7890-abcd-ef0123456789',\n title: 'Simple Form',\n slug: 'simple-form'\n })\n\n await loader.addForm('src/server/forms/payment-test.yaml', {\n ...metadata,\n id: 'b2c3d4e5-f6a7-8901-bcde-f01234567890',\n title: 'Payment Test Form',\n slug: 'payment-test'\n })\n\n await loader.addForm('src/server/forms/payment-v2-test.yaml', {\n ...metadata,\n id: 'c3d4e5f6-a7b8-9012-cdef-012345678901',\n title: 'Apply for a lock and weir fishing permit (v2 payment test)',\n slug: 'payment-v2-test'\n })\n\n return loader.toFormsService()\n}\n"],"mappings":"AAAA,SAASA,MAAM;AACf,SAASC,eAAe;;AAExB;AACA,MAAMC,GAAG,GAAG,IAAIC,IAAI,CAAC,CAAC;AACtB,MAAMC,IAAI,GAAG;EAAEC,EAAE,EAAE,MAAM;EAAEC,WAAW,EAAE;AAAW,CAAC;AACpD,MAAMC,MAAM,GAAG;EACbC,SAAS,EAAEN,GAAG;EACdO,SAAS,EAAEL,IAAI;EACfM,SAAS,EAAER,GAAG;EACdS,SAAS,EAAEP;AACb,CAAC;AACD,MAAMQ,QAAQ,GAAG;EACfC,YAAY,EAAE,OAAO;EACrBC,QAAQ,EAAE,WAAW;EACrBC,SAAS,EAAE,mBAAmB;EAC9BC,kBAAkB,EAAE,+CAA+C;EACnEC,iBAAiB,EAAEjB,MAAM,CAACkB,GAAG,CAAC,wBAAwB,CAAC;EACvD,GAAGX,MAAM;EACTY,IAAI,EAAEZ;AACR,CAAC;;AAED;AACA;AACA;AACA;AACA;AACA,OAAO,MAAMa,YAAY,GAAG,MAAAA,CAAA,KAAY;EACtC;EACA,MAAMC,MAAM,GAAG,IAAIpB,eAAe,CAAC,CAAC;;EAEpC;EACA,MAAMoB,MAAM,CAACC,OAAO,CAAC,qDAAqD,EAAE;IAC1E,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,+BAA+B;IACtCC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,mCAAmC,EAAE;IACxD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,kBAAkB;IACzBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,kCAAkC,EAAE;IACvD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,YAAY;IACnBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,mCAAmC,EAAE;IACxD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,aAAa;IACpBC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,oCAAoC,EAAE;IACzD,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,mBAAmB;IAC1BC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,MAAMH,MAAM,CAACC,OAAO,CAAC,uCAAuC,EAAE;IAC5D,GAAGV,QAAQ;IACXP,EAAE,EAAE,sCAAsC;IAC1CkB,KAAK,EAAE,4DAA4D;IACnEC,IAAI,EAAE;EACR,CAAC,CAAC;EAEF,OAAOH,MAAM,CAACI,cAAc,CAAC,CAAC;AAChC,CAAC","ignoreList":[]}
@@ -73,9 +73,10 @@
73
73
 
74
74
  <div class="govuk-button-group">
75
75
  {% set isDeclaration = declaration or components | length %}
76
+ {% set paymentPending = paymentRequired and not paymentState %}
76
77
 
77
78
  {{ govukButton({
78
- text: "Accept and submit" if isDeclaration else "Submit",
79
+ text: "Pay and submit" if paymentPending else ("Accept and submit" if isDeclaration else "Submit"),
79
80
  name: "action",
80
81
  value: "send",
81
82
  preventDoubleClick: true
@@ -11,11 +11,12 @@ export class PaymentService {
11
11
  * @param {string} reference
12
12
  * @param {boolean} isLivePayment
13
13
  * @param {{ formId: string, slug: string } | undefined } metadata
14
+ * @param {string} [email] - optional email to prepopulate on GOV.UK Pay
14
15
  */
15
16
  createPayment(amount: number, description: string, returnUrl: string, reference: string, isLivePayment: boolean, metadata: {
16
17
  formId: string;
17
18
  slug: string;
18
- } | undefined): Promise<{
19
+ } | undefined, email?: string): Promise<{
19
20
  paymentId: string;
20
21
  paymentUrl: string;
21
22
  } | undefined>;
@@ -34,17 +34,25 @@ export class PaymentService {
34
34
  * @param {string} reference
35
35
  * @param {boolean} isLivePayment
36
36
  * @param {{ formId: string, slug: string } | undefined } metadata
37
+ * @param {string} [email] - optional email to prepopulate on GOV.UK Pay
37
38
  */
38
- async createPayment(amount, description, returnUrl, reference, isLivePayment, metadata) {
39
+ async createPayment(amount, description, returnUrl, reference, isLivePayment, metadata, email) {
39
40
  try {
40
- const response = await this.postToPayProvider({
41
+ /** @type {CreatePaymentRequest} */
42
+ const payload = {
41
43
  amount,
42
44
  description,
43
45
  reference,
44
46
  metadata,
45
47
  return_url: returnUrl,
46
48
  delayed_capture: true
47
- });
49
+ };
50
+
51
+ // Prepopulate email on GOV.UK Pay if provided
52
+ if (email) {
53
+ payload.email = email;
54
+ }
55
+ const response = await this.postToPayProvider(payload);
48
56
  logger.info(buildPaymentInfo('create-payment', 'success', `amount=${convertPenceToPounds(amount)}`, isLivePayment, response.payment_id), `[payment] Created payment and user taken to enter pre-auth details for paymentId=${response.payment_id}`);
49
57
  return {
50
58
  paymentId: response.payment_id,