@defra/forms-engine-plugin 4.0.43 → 4.0.44
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.public/stylesheets/application.min.css +1 -1
- package/.public/stylesheets/application.min.css.map +1 -1
- package/.server/client/stylesheets/_payment-field.scss +8 -0
- package/.server/client/stylesheets/application.scss +2 -0
- package/.server/index.js +3 -1
- package/.server/index.js.map +1 -1
- package/.server/server/constants.d.ts +1 -0
- package/.server/server/constants.js +1 -0
- package/.server/server/constants.js.map +1 -1
- package/.server/server/forms/payment-test.yaml +42 -0
- package/.server/server/forms/register-as-a-unicorn-breeder.yaml +14 -0
- package/.server/server/plugins/engine/components/FormComponent.d.ts +1 -0
- package/.server/server/plugins/engine/components/FormComponent.js +1 -0
- package/.server/server/plugins/engine/components/FormComponent.js.map +1 -1
- package/.server/server/plugins/engine/components/PaymentField.d.ts +135 -0
- package/.server/server/plugins/engine/components/PaymentField.js +228 -0
- package/.server/server/plugins/engine/components/PaymentField.js.map +1 -0
- package/.server/server/plugins/engine/components/PaymentField.types.d.ts +21 -0
- package/.server/server/plugins/engine/components/PaymentField.types.js +2 -0
- package/.server/server/plugins/engine/components/PaymentField.types.js.map +1 -0
- package/.server/server/plugins/engine/components/UkAddressField.d.ts +1 -1
- package/.server/server/plugins/engine/components/UkAddressField.js +3 -1
- package/.server/server/plugins/engine/components/UkAddressField.js.map +1 -1
- package/.server/server/plugins/engine/components/helpers/components.d.ts +1 -1
- package/.server/server/plugins/engine/components/helpers/components.js +3 -0
- package/.server/server/plugins/engine/components/helpers/components.js.map +1 -1
- package/.server/server/plugins/engine/components/index.d.ts +1 -0
- package/.server/server/plugins/engine/components/index.js +1 -0
- package/.server/server/plugins/engine/components/index.js.map +1 -1
- package/.server/server/plugins/engine/helpers.d.ts +1 -0
- package/.server/server/plugins/engine/models/SummaryViewModel.d.ts +3 -0
- package/.server/server/plugins/engine/models/SummaryViewModel.js +7 -0
- package/.server/server/plugins/engine/models/SummaryViewModel.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js +34 -1
- package/.server/server/plugins/engine/outputFormatters/human/v1.js.map +1 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.d.ts +22 -0
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js +43 -1
- package/.server/server/plugins/engine/outputFormatters/machine/v2.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js +29 -8
- package/.server/server/plugins/engine/pageControllers/QuestionPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/StartPageController.d.ts +2 -0
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.d.ts +17 -0
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js +173 -51
- package/.server/server/plugins/engine/pageControllers/SummaryPageController.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/errors.d.ts +31 -0
- package/.server/server/plugins/engine/pageControllers/errors.js +59 -2
- package/.server/server/plugins/engine/pageControllers/errors.js.map +1 -1
- package/.server/server/plugins/engine/pageControllers/helpers/submission.d.ts +27 -0
- package/.server/server/plugins/engine/pageControllers/helpers/submission.js +77 -0
- package/.server/server/plugins/engine/pageControllers/helpers/submission.js.map +1 -0
- package/.server/server/plugins/engine/plugin.js +4 -1
- package/.server/server/plugins/engine/plugin.js.map +1 -1
- package/.server/server/plugins/engine/routes/index.js +8 -4
- package/.server/server/plugins/engine/routes/index.js.map +1 -1
- package/.server/server/plugins/engine/routes/payment-helper.d.ts +14 -0
- package/.server/server/plugins/engine/routes/payment-helper.js +41 -0
- package/.server/server/plugins/engine/routes/payment-helper.js.map +1 -0
- package/.server/server/plugins/engine/routes/payment-helper.test.js +81 -0
- package/.server/server/plugins/engine/routes/payment-helper.test.js.map +1 -0
- package/.server/server/plugins/engine/routes/payment.d.ts +8 -0
- package/.server/server/plugins/engine/routes/payment.js +140 -0
- package/.server/server/plugins/engine/routes/payment.js.map +1 -0
- package/.server/server/plugins/engine/routes/payment.test.js +187 -0
- package/.server/server/plugins/engine/routes/payment.test.js.map +1 -0
- package/.server/server/plugins/engine/services/localFormsService.js +6 -0
- package/.server/server/plugins/engine/services/localFormsService.js.map +1 -1
- package/.server/server/plugins/engine/types/schema.js +7 -0
- package/.server/server/plugins/engine/types/schema.js.map +1 -1
- package/.server/server/plugins/engine/types.d.ts +19 -1
- package/.server/server/plugins/engine/types.js +4 -0
- package/.server/server/plugins/engine/types.js.map +1 -1
- package/.server/server/plugins/engine/validationHelpers.d.ts +1 -1
- package/.server/server/plugins/engine/validationHelpers.js.map +1 -1
- package/.server/server/plugins/engine/views/components/paymentfield.html +42 -0
- package/.server/server/plugins/engine/views/index.html +9 -1
- package/.server/server/plugins/engine/views/partials/form.html +20 -5
- package/.server/server/plugins/engine/views/summary.html +17 -1
- package/.server/server/plugins/nunjucks/filters/field.d.ts +1 -1
- package/.server/server/plugins/payment/helper.d.ts +30 -0
- package/.server/server/plugins/payment/helper.js +49 -0
- package/.server/server/plugins/payment/helper.js.map +1 -0
- package/.server/server/plugins/payment/helper.test.js +37 -0
- package/.server/server/plugins/payment/helper.test.js.map +1 -0
- package/.server/server/plugins/payment/service.d.ts +40 -0
- package/.server/server/plugins/payment/service.js +129 -0
- package/.server/server/plugins/payment/service.js.map +1 -0
- package/.server/server/plugins/payment/service.test.js +162 -0
- package/.server/server/plugins/payment/service.test.js.map +1 -0
- package/.server/server/plugins/payment/types.d.ts +172 -0
- package/.server/server/plugins/payment/types.js +78 -0
- package/.server/server/plugins/payment/types.js.map +1 -0
- package/.server/server/types.d.ts +2 -0
- package/.server/server/types.js.map +1 -1
- package/.server/typings/hapi/index.d.js.map +1 -1
- package/README.md +12 -9
- package/package.json +2 -2
- package/src/client/stylesheets/_payment-field.scss +8 -0
- package/src/client/stylesheets/application.scss +2 -0
- package/src/index.ts +5 -1
- package/src/server/constants.js +1 -0
- package/src/server/forms/payment-test.yaml +42 -0
- package/src/server/forms/register-as-a-unicorn-breeder.yaml +14 -0
- package/src/server/plugins/engine/components/FormComponent.ts +1 -0
- package/src/server/plugins/engine/components/PaymentField.test.ts +611 -0
- package/src/server/plugins/engine/components/PaymentField.ts +367 -0
- package/src/server/plugins/engine/components/PaymentField.types.ts +21 -0
- package/src/server/plugins/engine/components/UkAddressField.ts +2 -1
- package/src/server/plugins/engine/components/helpers/components.ts +5 -0
- package/src/server/plugins/engine/components/index.ts +1 -0
- package/src/server/plugins/engine/models/SummaryViewModel.ts +8 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.payment.test.ts +147 -0
- package/src/server/plugins/engine/outputFormatters/human/v1.test.ts +105 -103
- package/src/server/plugins/engine/outputFormatters/human/v1.ts +61 -2
- package/src/server/plugins/engine/outputFormatters/machine/v2.payment.test.ts +115 -0
- package/src/server/plugins/engine/outputFormatters/machine/v2.ts +60 -1
- package/src/server/plugins/engine/pageControllers/QuestionPageController.ts +32 -6
- package/src/server/plugins/engine/pageControllers/SummaryPageController.ts +247 -72
- package/src/server/plugins/engine/pageControllers/errors.test.ts +13 -1
- package/src/server/plugins/engine/pageControllers/errors.ts +79 -4
- package/src/server/plugins/engine/pageControllers/helpers/submission.test.ts +299 -0
- package/src/server/plugins/engine/pageControllers/helpers/submission.ts +110 -0
- package/src/server/plugins/engine/plugin.ts +11 -6
- package/src/server/plugins/engine/routes/index.ts +17 -16
- package/src/server/plugins/engine/routes/payment-helper.js +39 -0
- package/src/server/plugins/engine/routes/payment-helper.test.js +90 -0
- package/src/server/plugins/engine/routes/payment.js +151 -0
- package/src/server/plugins/engine/routes/payment.test.js +180 -0
- package/src/server/plugins/engine/services/localFormsService.js +7 -0
- package/src/server/plugins/engine/types/schema.ts +9 -0
- package/src/server/plugins/engine/types.ts +24 -1
- package/src/server/plugins/engine/validationHelpers.ts +1 -1
- package/src/server/plugins/engine/views/components/paymentfield.html +42 -0
- package/src/server/plugins/engine/views/index.html +9 -1
- package/src/server/plugins/engine/views/partials/form.html +20 -5
- package/src/server/plugins/engine/views/summary.html +17 -1
- package/src/server/plugins/payment/helper.js +56 -0
- package/src/server/plugins/payment/helper.test.js +52 -0
- package/src/server/plugins/payment/service.js +171 -0
- package/src/server/plugins/payment/service.test.js +205 -0
- package/src/server/plugins/payment/types.js +77 -0
- package/src/server/types.ts +2 -0
- package/src/typings/hapi/index.d.ts +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"submission.js","names":["PaymentField","getAnswer","formatPaymentAmount","formatPaymentDate","buildMainRecords","items","fieldItems","filter","item","records","field","push","buildPaymentRecords","name","title","label","value","state","format","paymentState","getPaymentStateFromState","description","amount","reference","preAuth","createdAt","buildRepeaterRecords","map","subItems","detailItems","subItem"],"sources":["../../../../../../src/server/plugins/engine/pageControllers/helpers/submission.ts"],"sourcesContent":["import { type SubmitPayload } from '@defra/forms-model'\n\nimport { PaymentField } from '~/src/server/plugins/engine/components/PaymentField.js'\nimport { getAnswer } from '~/src/server/plugins/engine/components/helpers/components.js'\nimport {\n type DetailItem,\n type DetailItemField\n} from '~/src/server/plugins/engine/models/types.js'\nimport {\n formatPaymentAmount,\n formatPaymentDate\n} from '~/src/server/plugins/payment/helper.js'\n\nexport interface SubmitRecord {\n name: string\n title: string\n value: string\n}\n\n/**\n * Builds the main submission records from field items.\n * Regular fields are converted to single records, while PaymentField\n * components are expanded into four separate records.\n */\nexport function buildMainRecords(items: DetailItem[]): SubmitRecord[] {\n const fieldItems = items.filter(\n (item): item is DetailItemField => 'field' in item\n )\n\n const records: SubmitRecord[] = []\n\n for (const item of fieldItems) {\n if (item.field instanceof PaymentField) {\n records.push(...buildPaymentRecords(item))\n } else {\n records.push({\n name: item.name,\n title: item.label,\n value: getAnswer(item.field, item.state, { format: 'data' })\n })\n }\n }\n\n return records\n}\n\n/**\n * Expands a PaymentField into four submission records:\n * - Payment description\n * - Payment amount (formatted with currency symbol)\n * - Payment reference\n * - Payment date (formatted date/time)\n *\n * Returns an empty array if no payment state exists.\n */\nexport function buildPaymentRecords(item: DetailItemField): SubmitRecord[] {\n const paymentState = (item.field as PaymentField).getPaymentStateFromState(\n item.state\n )\n\n if (!paymentState) {\n return []\n }\n\n return [\n {\n name: `${item.name}_paymentDescription`,\n title: 'Payment description',\n value: paymentState.description\n },\n {\n name: `${item.name}_paymentAmount`,\n title: 'Payment amount',\n value: formatPaymentAmount(paymentState.amount)\n },\n {\n name: `${item.name}_paymentReference`,\n title: 'Payment reference',\n value: paymentState.reference\n },\n {\n name: `${item.name}_paymentDate`,\n title: 'Payment date',\n value: paymentState.preAuth?.createdAt\n ? formatPaymentDate(paymentState.preAuth.createdAt)\n : ''\n }\n ]\n}\n\n/**\n * Builds the repeater submission records from repeater items.\n */\nexport function buildRepeaterRecords(\n items: DetailItem[]\n): SubmitPayload['repeaters'] {\n return items\n .filter((item) => 'subItems' in item)\n .map((item) => ({\n name: item.name,\n title: item.label,\n value: item.subItems.map((detailItems) =>\n detailItems.map((subItem) => ({\n name: subItem.name,\n title: subItem.label,\n value: getAnswer(subItem.field, subItem.state, { format: 'data' })\n }))\n )\n }))\n}\n"],"mappings":"AAEA,SAASA,YAAY;AACrB,SAASC,SAAS;AAKlB,SACEC,mBAAmB,EACnBC,iBAAiB;AASnB;AACA;AACA;AACA;AACA;AACA,OAAO,SAASC,gBAAgBA,CAACC,KAAmB,EAAkB;EACpE,MAAMC,UAAU,GAAGD,KAAK,CAACE,MAAM,CAC5BC,IAAI,IAA8B,OAAO,IAAIA,IAChD,CAAC;EAED,MAAMC,OAAuB,GAAG,EAAE;EAElC,KAAK,MAAMD,IAAI,IAAIF,UAAU,EAAE;IAC7B,IAAIE,IAAI,CAACE,KAAK,YAAYV,YAAY,EAAE;MACtCS,OAAO,CAACE,IAAI,CAAC,GAAGC,mBAAmB,CAACJ,IAAI,CAAC,CAAC;IAC5C,CAAC,MAAM;MACLC,OAAO,CAACE,IAAI,CAAC;QACXE,IAAI,EAAEL,IAAI,CAACK,IAAI;QACfC,KAAK,EAAEN,IAAI,CAACO,KAAK;QACjBC,KAAK,EAAEf,SAAS,CAACO,IAAI,CAACE,KAAK,EAAEF,IAAI,CAACS,KAAK,EAAE;UAAEC,MAAM,EAAE;QAAO,CAAC;MAC7D,CAAC,CAAC;IACJ;EACF;EAEA,OAAOT,OAAO;AAChB;;AAEA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,SAASG,mBAAmBA,CAACJ,IAAqB,EAAkB;EACzE,MAAMW,YAAY,GAAIX,IAAI,CAACE,KAAK,CAAkBU,wBAAwB,CACxEZ,IAAI,CAACS,KACP,CAAC;EAED,IAAI,CAACE,YAAY,EAAE;IACjB,OAAO,EAAE;EACX;EAEA,OAAO,CACL;IACEN,IAAI,EAAE,GAAGL,IAAI,CAACK,IAAI,qBAAqB;IACvCC,KAAK,EAAE,qBAAqB;IAC5BE,KAAK,EAAEG,YAAY,CAACE;EACtB,CAAC,EACD;IACER,IAAI,EAAE,GAAGL,IAAI,CAACK,IAAI,gBAAgB;IAClCC,KAAK,EAAE,gBAAgB;IACvBE,KAAK,EAAEd,mBAAmB,CAACiB,YAAY,CAACG,MAAM;EAChD,CAAC,EACD;IACET,IAAI,EAAE,GAAGL,IAAI,CAACK,IAAI,mBAAmB;IACrCC,KAAK,EAAE,mBAAmB;IAC1BE,KAAK,EAAEG,YAAY,CAACI;EACtB,CAAC,EACD;IACEV,IAAI,EAAE,GAAGL,IAAI,CAACK,IAAI,cAAc;IAChCC,KAAK,EAAE,cAAc;IACrBE,KAAK,EAAEG,YAAY,CAACK,OAAO,EAAEC,SAAS,GAClCtB,iBAAiB,CAACgB,YAAY,CAACK,OAAO,CAACC,SAAS,CAAC,GACjD;EACN,CAAC,CACF;AACH;;AAEA;AACA;AACA;AACA,OAAO,SAASC,oBAAoBA,CAClCrB,KAAmB,EACS;EAC5B,OAAOA,KAAK,CACTE,MAAM,CAAEC,IAAI,IAAK,UAAU,IAAIA,IAAI,CAAC,CACpCmB,GAAG,CAAEnB,IAAI,KAAM;IACdK,IAAI,EAAEL,IAAI,CAACK,IAAI;IACfC,KAAK,EAAEN,IAAI,CAACO,KAAK;IACjBC,KAAK,EAAER,IAAI,CAACoB,QAAQ,CAACD,GAAG,CAAEE,WAAW,IACnCA,WAAW,CAACF,GAAG,CAAEG,OAAO,KAAM;MAC5BjB,IAAI,EAAEiB,OAAO,CAACjB,IAAI;MAClBC,KAAK,EAAEgB,OAAO,CAACf,KAAK;MACpBC,KAAK,EAAEf,SAAS,CAAC6B,OAAO,CAACpB,KAAK,EAAEoB,OAAO,CAACb,KAAK,EAAE;QAAEC,MAAM,EAAE;MAAO,CAAC;IACnE,CAAC,CAAC,CACJ;EACF,CAAC,CAAC,CAAC;AACP","ignoreList":[]}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { validatePluginOptions } from "./options.js";
|
|
2
2
|
import { getRoutes as getFileUploadStatusRoutes } from "./routes/file-upload.js";
|
|
3
3
|
import { makeLoadFormPreHandler } from "./routes/index.js";
|
|
4
|
+
import { getRoutes as getPaymentRoutes } from "./routes/payment.js";
|
|
4
5
|
import { getRoutes as getQuestionRoutes } from "./routes/questions.js";
|
|
5
6
|
import { getRoutes as getRepeaterItemDeleteRoutes } from "./routes/repeaters/item-delete.js";
|
|
6
7
|
import { getRoutes as getRepeaterSummaryRoutes } from "./routes/repeaters/summary.js";
|
|
@@ -23,6 +24,7 @@ export const plugin = {
|
|
|
23
24
|
preparePageEventRequestOptions,
|
|
24
25
|
onRequest,
|
|
25
26
|
ordnanceSurveyApiKey,
|
|
27
|
+
baseUrl,
|
|
26
28
|
ordnanceSurveyApiSecret
|
|
27
29
|
} = options;
|
|
28
30
|
const cacheService = typeof cache === 'string' ? new CacheService({
|
|
@@ -55,6 +57,7 @@ export const plugin = {
|
|
|
55
57
|
server.expose('viewContext', viewContext);
|
|
56
58
|
server.expose('cacheService', cacheService);
|
|
57
59
|
server.expose('saveAndExit', saveAndExit);
|
|
60
|
+
server.expose('baseUrl', baseUrl);
|
|
58
61
|
server.app.model = model;
|
|
59
62
|
|
|
60
63
|
// In-memory cache of FormModel items, exposed
|
|
@@ -75,7 +78,7 @@ export const plugin = {
|
|
|
75
78
|
method: loadFormPreHandler
|
|
76
79
|
}]
|
|
77
80
|
};
|
|
78
|
-
const routes = [...
|
|
81
|
+
const routes = [...getPaymentRoutes(), ...getFileUploadStatusRoutes(), ...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions, onRequest), ...getRepeaterItemDeleteRoutes(getRouteOptions, postRouteOptions, onRequest), ...getQuestionRoutes(getRouteOptions, postRouteOptions, preparePageEventRequestOptions, onRequest)];
|
|
79
82
|
server.route(routes); // TODO
|
|
80
83
|
}
|
|
81
84
|
};
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.js","names":["validatePluginOptions","getRoutes","getFileUploadStatusRoutes","makeLoadFormPreHandler","getQuestionRoutes","getRepeaterItemDeleteRoutes","getRepeaterSummaryRoutes","registerVision","mapPlugin","postcodeLookupPlugin","CacheService","plugin","name","dependencies","multiple","register","server","options","model","cache","saveAndExit","nunjucks","nunjucksOptions","viewContext","preparePageEventRequestOptions","onRequest","ordnanceSurveyApiKey","ordnanceSurveyApiSecret","cacheService","cacheName","expose","baseLayoutPath","app","itemCache","Map","models","loadFormPreHandler","getRouteOptions","pre","method","postRouteOptions","payload","parse","routes","route"],"sources":["../../../../src/server/plugins/engine/plugin.ts"],"sourcesContent":["import {\n type Lifecycle,\n type Plugin,\n type RouteOptions,\n type Server,\n type ServerRoute\n} from '@hapi/hapi'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\nimport { getRoutes as getFileUploadStatusRoutes } from '~/src/server/plugins/engine/routes/file-upload.js'\nimport { makeLoadFormPreHandler } from '~/src/server/plugins/engine/routes/index.js'\nimport { getRoutes as getQuestionRoutes } from '~/src/server/plugins/engine/routes/questions.js'\nimport { getRoutes as getRepeaterItemDeleteRoutes } from '~/src/server/plugins/engine/routes/repeaters/item-delete.js'\nimport { getRoutes as getRepeaterSummaryRoutes } from '~/src/server/plugins/engine/routes/repeaters/summary.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { registerVision } from '~/src/server/plugins/engine/vision.js'\nimport { mapPlugin } from '~/src/server/plugins/map/index.js'\nimport { postcodeLookupPlugin } from '~/src/server/plugins/postcode-lookup/index.js'\nimport {\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport { CacheService } from '~/src/server/services/index.js'\n\nexport const plugin = {\n name: '@defra/forms-engine-plugin',\n dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],\n multiple: true,\n async register(server: Server, options: PluginOptions) {\n options = validatePluginOptions(options)\n\n const {\n model,\n cache,\n saveAndExit,\n nunjucks: nunjucksOptions,\n viewContext,\n preparePageEventRequestOptions,\n onRequest,\n ordnanceSurveyApiKey,\n ordnanceSurveyApiSecret\n } = options\n\n const cacheService =\n typeof cache === 'string'\n ? new CacheService({ server, cacheName: cache })\n : cache\n\n await registerVision(server, options)\n\n // Register the postcode lookup plugin only if we have an OS api key\n if (ordnanceSurveyApiKey) {\n await server.register({\n plugin: postcodeLookupPlugin,\n options: {\n ordnanceSurveyApiKey\n }\n })\n }\n\n // Register the maps plugin only if we have an OS api key & secret\n if (ordnanceSurveyApiKey && ordnanceSurveyApiSecret) {\n await server.register({\n plugin: mapPlugin,\n options: {\n ordnanceSurveyApiKey,\n ordnanceSurveyApiSecret\n }\n })\n }\n\n server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath)\n server.expose('viewContext', viewContext)\n server.expose('cacheService', cacheService)\n server.expose('saveAndExit', saveAndExit)\n\n server.app.model = model\n\n // In-memory cache of FormModel items, exposed\n // (for testing purposes) through `server.app.models`\n const itemCache = new Map<string, { model: FormModel; updatedAt: Date }>()\n server.app.models = itemCache\n\n const loadFormPreHandler = makeLoadFormPreHandler(server, options)\n\n const getRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestRefs>\n }\n ]\n }\n\n const postRouteOptions: RouteOptions<FormRequestPayloadRefs> = {\n payload: {\n parse: true\n },\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestPayloadRefs>\n }\n ]\n }\n\n const routes = [\n ...
|
|
1
|
+
{"version":3,"file":"plugin.js","names":["validatePluginOptions","getRoutes","getFileUploadStatusRoutes","makeLoadFormPreHandler","getPaymentRoutes","getQuestionRoutes","getRepeaterItemDeleteRoutes","getRepeaterSummaryRoutes","registerVision","mapPlugin","postcodeLookupPlugin","CacheService","plugin","name","dependencies","multiple","register","server","options","model","cache","saveAndExit","nunjucks","nunjucksOptions","viewContext","preparePageEventRequestOptions","onRequest","ordnanceSurveyApiKey","baseUrl","ordnanceSurveyApiSecret","cacheService","cacheName","expose","baseLayoutPath","app","itemCache","Map","models","loadFormPreHandler","getRouteOptions","pre","method","postRouteOptions","payload","parse","routes","route"],"sources":["../../../../src/server/plugins/engine/plugin.ts"],"sourcesContent":["import {\n type Lifecycle,\n type Plugin,\n type RouteOptions,\n type Server,\n type ServerRoute\n} from '@hapi/hapi'\n\nimport { type FormModel } from '~/src/server/plugins/engine/models/index.js'\nimport { validatePluginOptions } from '~/src/server/plugins/engine/options.js'\nimport { getRoutes as getFileUploadStatusRoutes } from '~/src/server/plugins/engine/routes/file-upload.js'\nimport { makeLoadFormPreHandler } from '~/src/server/plugins/engine/routes/index.js'\nimport { getRoutes as getPaymentRoutes } from '~/src/server/plugins/engine/routes/payment.js'\nimport { getRoutes as getQuestionRoutes } from '~/src/server/plugins/engine/routes/questions.js'\nimport { getRoutes as getRepeaterItemDeleteRoutes } from '~/src/server/plugins/engine/routes/repeaters/item-delete.js'\nimport { getRoutes as getRepeaterSummaryRoutes } from '~/src/server/plugins/engine/routes/repeaters/summary.js'\nimport { type PluginOptions } from '~/src/server/plugins/engine/types.js'\nimport { registerVision } from '~/src/server/plugins/engine/vision.js'\nimport { mapPlugin } from '~/src/server/plugins/map/index.js'\nimport { postcodeLookupPlugin } from '~/src/server/plugins/postcode-lookup/index.js'\nimport {\n type FormRequestPayloadRefs,\n type FormRequestRefs\n} from '~/src/server/routes/types.js'\nimport { CacheService } from '~/src/server/services/index.js'\n\nexport const plugin = {\n name: '@defra/forms-engine-plugin',\n dependencies: ['@hapi/crumb', '@hapi/yar', 'hapi-pino'],\n multiple: true,\n async register(server: Server, options: PluginOptions) {\n options = validatePluginOptions(options)\n\n const {\n model,\n cache,\n saveAndExit,\n nunjucks: nunjucksOptions,\n viewContext,\n preparePageEventRequestOptions,\n onRequest,\n ordnanceSurveyApiKey,\n baseUrl,\n ordnanceSurveyApiSecret\n } = options\n\n const cacheService =\n typeof cache === 'string'\n ? new CacheService({ server, cacheName: cache })\n : cache\n\n await registerVision(server, options)\n\n // Register the postcode lookup plugin only if we have an OS api key\n if (ordnanceSurveyApiKey) {\n await server.register({\n plugin: postcodeLookupPlugin,\n options: {\n ordnanceSurveyApiKey\n }\n })\n }\n\n // Register the maps plugin only if we have an OS api key & secret\n if (ordnanceSurveyApiKey && ordnanceSurveyApiSecret) {\n await server.register({\n plugin: mapPlugin,\n options: {\n ordnanceSurveyApiKey,\n ordnanceSurveyApiSecret\n }\n })\n }\n\n server.expose('baseLayoutPath', nunjucksOptions.baseLayoutPath)\n server.expose('viewContext', viewContext)\n server.expose('cacheService', cacheService)\n server.expose('saveAndExit', saveAndExit)\n server.expose('baseUrl', baseUrl)\n\n server.app.model = model\n\n // In-memory cache of FormModel items, exposed\n // (for testing purposes) through `server.app.models`\n const itemCache = new Map<string, { model: FormModel; updatedAt: Date }>()\n server.app.models = itemCache\n\n const loadFormPreHandler = makeLoadFormPreHandler(server, options)\n\n const getRouteOptions: RouteOptions<FormRequestRefs> = {\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestRefs>\n }\n ]\n }\n\n const postRouteOptions: RouteOptions<FormRequestPayloadRefs> = {\n payload: {\n parse: true\n },\n pre: [\n {\n method:\n loadFormPreHandler as unknown as Lifecycle.Method<FormRequestPayloadRefs>\n }\n ]\n }\n\n const routes = [\n ...getPaymentRoutes(),\n ...getFileUploadStatusRoutes(),\n ...getRepeaterSummaryRoutes(getRouteOptions, postRouteOptions, onRequest),\n ...getRepeaterItemDeleteRoutes(\n getRouteOptions,\n postRouteOptions,\n onRequest\n ),\n\n ...getQuestionRoutes(\n getRouteOptions,\n postRouteOptions,\n preparePageEventRequestOptions,\n onRequest\n )\n ]\n\n server.route(routes as unknown as ServerRoute[]) // TODO\n }\n} satisfies Plugin<PluginOptions>\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -76,13 +76,17 @@ async function importExternalComponentState(request, page, state) {
|
|
|
76
76
|
if (!isStateValid) {
|
|
77
77
|
throw new Error(`State for component ${componentName} is invalid`);
|
|
78
78
|
}
|
|
79
|
-
|
|
79
|
+
|
|
80
|
+
// Create state structure from appendage state
|
|
81
|
+
// Some components use a record structure with properties of the format of '<compName>__<fieldName>'
|
|
82
|
+
// e.g. UKAddressField
|
|
83
|
+
// Some components use a single object structure e.g. PaymentField
|
|
84
|
+
const componentState = isFormState(stateAppendage) && !component.isAppendageStateSingleObject ? Object.fromEntries(Object.entries(stateAppendage).map(([key, value]) => [`${componentName}__${key}`, value])) : {
|
|
80
85
|
[componentName]: stateAppendage
|
|
81
86
|
};
|
|
82
87
|
|
|
83
|
-
// Save the external component state
|
|
84
|
-
const
|
|
85
|
-
const savedState = await page.mergeState(request, state, pageState);
|
|
88
|
+
// Save the external component state directly (already has correct key format)
|
|
89
|
+
const savedState = await page.mergeState(request, state, componentState);
|
|
86
90
|
|
|
87
91
|
// Merge any stashed payload into the local state
|
|
88
92
|
const payload = request.yar.flash(EXTERNAL_STATE_PAYLOAD);
|
|
@@ -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","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","startsWith","isForceAccess","redirectTo","next","length","query","returnUrl","getHref","externalComponentData","yar","Array","isArray","typedStateAppendage","componentName","component","stateAppendage","data","componentMap","get","Error","TypeError","isStateValid","isState","componentState","Object","fromEntries","entries","map","key","value","pageState","getStateFromValidForm","savedState","payload","stashedPayload","localState","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 { 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 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 // 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\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 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 const componentState = isFormState(stateAppendage)\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 immediately\n const pageState = page.getStateFromValidForm(\n request,\n state,\n componentState as FormPayload\n )\n const savedState = await page.mergeState(request, state, pageState)\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":"AAAA,OAAOA,IAAI,MAAM,YAAY;AAO7B,SACEC,wBAAwB,EACxBC,sBAAsB;AAExB,SAASC,gBAAgB;AACzB,SACEC,aAAa,EACbC,WAAW;AAEb,SACEC,eAAe,EACfC,QAAQ,EACRC,eAAe,EACfC,OAAO,EACPC,YAAY,EACZC,OAAO;AAGT,SAASC,uBAAuB;AAChC,OAAO,KAAKC,eAAe;AAe3B,OAAO,eAAeC,qBAAqBA,CACzCC,OAAuB,EACvBC,CAAsB,EACtBC,SAAwC,EACxCC,WAG6C,EAC7C;EACA,MAAM;IAAEC,GAAG;IAAEC;EAAO,CAAC,GAAGL,OAAO;EAC/B,MAAM;IAAEM;EAAM,CAAC,GAAGF,GAAG;EAErB,IAAI,CAACE,KAAK,EAAE;IACV,MAAMrB,IAAI,CAACsB,QAAQ,CAAC,uBAAuBF,MAAM,CAACG,IAAI,EAAE,CAAC;EAC3D;EAEA,MAAMC,YAAY,GAAGhB,eAAe,CAACO,OAAO,CAACU,MAAM,CAAC;EACpD,MAAMC,IAAI,GAAGjB,OAAO,CAACY,KAAK,EAAEN,OAAO,CAAC;EACpC,IAAIY,KAAK,GAAG,MAAMD,IAAI,CAACE,QAAQ,CAACb,OAAO,CAAC;EAExC,IAAI,CAACY,KAAK,CAACE,mBAAmB,EAAE;IAC9B,MAAMC,MAAM,GAAGT,KAAK,CAACU,GAAG,CAACC,QAAQ,EAAEC,qBAAqB,IAAI,EAAE;IAE9D,IAAI,OAAOH,MAAM,KAAK,QAAQ,EAAE;MAC9B,MAAM9B,IAAI,CAACkC,iBAAiB,CAC1B,uDACF,CAAC;IACH;IAEA,MAAMC,eAAe,GAAGvB,uBAAuB,CAACkB,MAAM,CAAC;IACvDH,KAAK,GAAG,MAAMD,IAAI,CAACU,UAAU,CAACrB,OAAO,EAAEY,KAAK,EAAE;MAC5CE,mBAAmB,EAAEM;IACvB,CAAC,CAAC;EACJ;EAEAR,KAAK,GAAG,MAAMU,4BAA4B,CAACtB,OAAO,EAAEW,IAAI,EAAEC,KAAK,CAAC;EAEhE,MAAMW,KAAK,GAAGd,YAAY,CAACe,QAAQ,CAACxB,OAAO,CAAC;EAC5C,MAAMyB,OAAO,GAAGnB,KAAK,CAACoB,cAAc,CAAC1B,OAAO,EAAEY,KAAK,EAAEW,KAAK,EAAEI,MAAM,CAAC;EACnE,MAAMC,YAAY,GAAGjB,IAAI,CAACkB,eAAe,CAAC7B,OAAO,EAAEyB,OAAO,CAAC;EAC3D,MAAMK,WAAW,GAAGnB,IAAI,CAACoB,cAAc,CAAC,CAAC;;EAEzC;EACA,IAAI7B,SAAS,EAAE;IACb,MAAM8B,MAAM,GAAG,MAAM9B,SAAS,CAACF,OAAO,EAAEC,CAAC,EAAEwB,OAAO,CAAC;IACnD,IAAIO,MAAM,KAAK/B,CAAC,CAACgC,QAAQ,EAAE;MACzB,OAAOD,MAAM;IACf;EACF;;EAEA;EACA,IAAIJ,YAAY,CAACM,UAAU,CAACvB,IAAI,CAACH,IAAI,CAAC,IAAIiB,OAAO,CAACU,aAAa,EAAE;IAC/D,OAAOhC,WAAW,CAACQ,IAAI,EAAEc,OAAO,CAAC;EACnC;;EAEA;EACA,MAAMW,UAAU,GAAG5C,QAAQ,CAACc,KAAK,EAAEsB,YAAY,CAAC;;EAEhD;EACA,IAAIQ,UAAU,EAAEC,IAAI,CAACC,MAAM,EAAE;IAC3BtC,OAAO,CAACuC,KAAK,CAACC,SAAS,GAAG7B,IAAI,CAAC8B,OAAO,CAACX,WAAW,CAAC;EACrD;EAEA,OAAOlC,OAAO,CAACI,OAAO,EAAEC,CAAC,EAAEU,IAAI,CAAC8B,OAAO,CAACb,YAAY,CAAC,CAAC;AACxD;AAEA,eAAeN,4BAA4BA,CACzCtB,OAAuB,EACvBW,IAAyB,EACzBC,KAA0B,EACI;EAC9B,MAAM8B,qBAAqB,GAAG1C,OAAO,CAAC2C,GAAG,CAACpB,KAAK,CAACrC,wBAAwB,CAAC;EAEzE,IAAI0D,KAAK,CAACC,OAAO,CAACH,qBAAqB,CAAC,EAAE;IACxC,OAAO9B,KAAK;EACd;EAEA,MAAMkC,mBAAmB,GAAGJ,qBAA+C;EAC3E,MAAMK,aAAa,GAAGD,mBAAmB,CAACE,SAAS;EACnD,MAAMC,cAAc,GAAGH,mBAAmB,CAACI,IAAI;EAC/C,MAAMF,SAAS,GAAGhD,OAAO,CAACI,GAAG,CAACE,KAAK,EAAE6C,YAAY,CAACC,GAAG,CAACL,aAAa,CAAC;EAEpE,IAAI,CAACC,SAAS,EAAE;IACd,MAAM,IAAIK,KAAK,CAAC,aAAaN,aAAa,oBAAoB,CAAC;EACjE;EAEA,IAAI,EAAEC,SAAS,YAAY3D,aAAa,CAAC,EAAE;IACzC,MAAM,IAAIiE,SAAS,CACjB,aAAaP,aAAa,sDAC5B,CAAC;EACH;EAEA,MAAMQ,YAAY,GAAGP,SAAS,CAACQ,OAAO,CAACP,cAAc,CAAC;EAEtD,IAAI,CAACM,YAAY,EAAE;IACjB,MAAM,IAAIF,KAAK,CAAC,uBAAuBN,aAAa,aAAa,CAAC;EACpE;EAEA,MAAMU,cAAc,GAAGnE,WAAW,CAAC2D,cAAc,CAAC,GAC9CS,MAAM,CAACC,WAAW,CAChBD,MAAM,CAACE,OAAO,CAACX,cAAc,CAAC,CAACY,GAAG,CAAC,CAAC,CAACC,GAAG,EAAEC,KAAK,CAAC,KAAK,CACnD,GAAGhB,aAAa,KAAKe,GAAG,EAAE,EAC1BC,KAAK,CACN,CACH,CAAC,GACD;IAAE,CAAChB,aAAa,GAAGE;EAAe,CAAC;;EAEvC;EACA,MAAMe,SAAS,GAAGrD,IAAI,CAACsD,qBAAqB,CAC1CjE,OAAO,EACPY,KAAK,EACL6C,cACF,CAAC;EACD,MAAMS,UAAU,GAAG,MAAMvD,IAAI,CAACU,UAAU,CAACrB,OAAO,EAAEY,KAAK,EAAEoD,SAAS,CAAC;;EAEnE;EACA,MAAMG,OAAO,GAAGnE,OAAO,CAAC2C,GAAG,CAACpB,KAAK,CAACpC,sBAAsB,CAAC;EACzD,MAAMiF,cAAc,GAAGxB,KAAK,CAACC,OAAO,CAACsB,OAAO,CAAC,GAAG,CAAC,CAAC,GAAIA,OAAuB;EAE7E,MAAME,UAAU,GAAG1D,IAAI,CAACsD,qBAAqB,CAACjE,OAAO,EAAEkE,UAAU,EAAE;IACjE,GAAGE,cAAc;IACjB,GAAGX;EACL,CAAgB,CAAC;EAEjB,OAAO;IAAE,GAAGS,UAAU;IAAE,GAAGG;EAAW,CAAC;AACzC;AAEA,OAAO,SAASC,sBAAsBA,CAAC5D,MAAc,EAAE6D,OAAsB,EAAE;EAC7E;EACA,MAAMxD,MAAM,GAAGL,MAAM,CAAC8D,KAAK,CAACC,SAAS,CAACC,KAAK,CAAC3D,MAAM,IAAI,EAAE;EAExD,MAAM;IACJ4D,QAAQ,GAAG7E,eAAe;IAC1B8E,WAAW;IACXC;EACF,CAAC,GAAGN,OAAO;EAEX,eAAeO,OAAOA,CAAC9E,OAAuB,EAAEC,CAAkB,EAAE;IAClE,IAAIS,MAAM,CAACN,GAAG,CAACE,KAAK,EAAE;MACpBN,OAAO,CAACI,GAAG,CAACE,KAAK,GAAGI,MAAM,CAACN,GAAG,CAACE,KAAK;MAEpC,OAAOL,CAAC,CAACgC,QAAQ;IACnB;IAEA,MAAM;MAAE5B;IAAO,CAAC,GAAGL,OAAO;IAC1B,MAAM;MAAE+E;IAAK,CAAC,GAAG1E,MAAM;IACvB,MAAM;MAAE2E,SAAS;MAAEpE,KAAK,EAAEqE;IAAU,CAAC,GAAG1F,eAAe,CAACc,MAAM,CAAC;IAE/D,MAAMC,KAAK,GAAG,MAAMlB,gBAAgB,CAACsB,MAAM,EAAEqE,IAAI,EAAEE,SAAS,EAAE;MAC5DN,QAAQ;MACRC,WAAW;MACXC,oBAAoB;MACpBK,WAAW,EAAEnE,MAAM;MACnBiE;IACF,CAAC,CAAC;IAEFhF,OAAO,CAACI,GAAG,CAACE,KAAK,GAAGA,KAAK;IAEzB,OAAOL,CAAC,CAACgC,QAAQ;EACnB;EAEA,OAAO6C,OAAO;AAChB;AAEA,OAAO,SAASK,eAAeA,CAACnF,OAAoB,EAAEC,CAAsB,EAAE;EAC5E,MAAM;IAAEK;EAAM,CAAC,GAAGN,OAAO,CAACI,GAAG;EAE7B,MAAMgF,WAAW,GAAG9E,KAAK,GAAG,IAAIA,KAAK,CAAC+E,QAAQ,EAAE,GAAG,EAAE;EACrD,OAAOzF,OAAO,CAACI,OAAO,EAAEC,CAAC,EAAE,GAAGmF,WAAW,GAAGzF,YAAY,CAACW,KAAK,CAAC,EAAE,CAAC;AACpE","ignoreList":[]}
|
|
1
|
+
{"version":3,"file":"index.js","names":["Boom","EXTERNAL_STATE_APPENDAGE","EXTERNAL_STATE_PAYLOAD","resolveFormModel","FormComponent","isFormState","checkFormStatus","findPage","getCacheService","getPage","getStartPath","proceed","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","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 { 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 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 // 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\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":[]}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validates session data and retrieves payment status
|
|
3
|
+
* @param {Request} request - the request
|
|
4
|
+
* @param {string} uuid - the payment UUID
|
|
5
|
+
* @returns {Promise<{ session: PaymentSessionData, sessionKey: string, paymentStatus: GetPaymentResponse }>}
|
|
6
|
+
*/
|
|
7
|
+
export function getPaymentContext(request: Request, uuid: string): Promise<{
|
|
8
|
+
session: PaymentSessionData;
|
|
9
|
+
sessionKey: string;
|
|
10
|
+
paymentStatus: GetPaymentResponse;
|
|
11
|
+
}>;
|
|
12
|
+
import type { Request } from '@hapi/hapi';
|
|
13
|
+
import type { PaymentSessionData } from '~/src/server/plugins/payment/types.js';
|
|
14
|
+
import type { GetPaymentResponse } from '~/src/server/plugins/payment/types.js';
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Boom from '@hapi/boom';
|
|
2
|
+
import { PAYMENT_SESSION_PREFIX } from "./payment.js";
|
|
3
|
+
import { getPaymentApiKey } from "../../payment/helper.js";
|
|
4
|
+
import { PaymentService } from "../../payment/service.js";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Validates session data and retrieves payment status
|
|
8
|
+
* @param {Request} request - the request
|
|
9
|
+
* @param {string} uuid - the payment UUID
|
|
10
|
+
* @returns {Promise<{ session: PaymentSessionData, sessionKey: string, paymentStatus: GetPaymentResponse }>}
|
|
11
|
+
*/
|
|
12
|
+
export async function getPaymentContext(request, uuid) {
|
|
13
|
+
const sessionKey = `${PAYMENT_SESSION_PREFIX}${uuid}`;
|
|
14
|
+
const session = /** @type {PaymentSessionData | null} */
|
|
15
|
+
request.yar.get(sessionKey);
|
|
16
|
+
if (!session) {
|
|
17
|
+
throw Boom.badRequest(`No payment session found for uuid=${uuid}`);
|
|
18
|
+
}
|
|
19
|
+
const {
|
|
20
|
+
paymentId,
|
|
21
|
+
isLivePayment,
|
|
22
|
+
formId
|
|
23
|
+
} = session;
|
|
24
|
+
if (!paymentId) {
|
|
25
|
+
throw Boom.badRequest('No paymentId in session');
|
|
26
|
+
}
|
|
27
|
+
const apiKey = getPaymentApiKey(isLivePayment, formId);
|
|
28
|
+
const paymentService = new PaymentService(apiKey);
|
|
29
|
+
const paymentStatus = await paymentService.getPaymentStatus(paymentId);
|
|
30
|
+
return {
|
|
31
|
+
session,
|
|
32
|
+
sessionKey,
|
|
33
|
+
paymentStatus
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* @import { Request } from '@hapi/hapi'
|
|
39
|
+
* @import { GetPaymentResponse, PaymentSessionData } from '~/src/server/plugins/payment/types.js'
|
|
40
|
+
*/
|
|
41
|
+
//# sourceMappingURL=payment-helper.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-helper.js","names":["Boom","PAYMENT_SESSION_PREFIX","getPaymentApiKey","PaymentService","getPaymentContext","request","uuid","sessionKey","session","yar","get","badRequest","paymentId","isLivePayment","formId","apiKey","paymentService","paymentStatus","getPaymentStatus"],"sources":["../../../../../src/server/plugins/engine/routes/payment-helper.js"],"sourcesContent":["import Boom from '@hapi/boom'\n\nimport { PAYMENT_SESSION_PREFIX } from '~/src/server/plugins/engine/routes/payment.js'\nimport { getPaymentApiKey } from '~/src/server/plugins/payment/helper.js'\nimport { PaymentService } from '~/src/server/plugins/payment/service.js'\n\n/**\n * Validates session data and retrieves payment status\n * @param {Request} request - the request\n * @param {string} uuid - the payment UUID\n * @returns {Promise<{ session: PaymentSessionData, sessionKey: string, paymentStatus: GetPaymentResponse }>}\n */\nexport async function getPaymentContext(request, uuid) {\n const sessionKey = `${PAYMENT_SESSION_PREFIX}${uuid}`\n const session = /** @type {PaymentSessionData | null} */ (\n request.yar.get(sessionKey)\n )\n\n if (!session) {\n throw Boom.badRequest(`No payment session found for uuid=${uuid}`)\n }\n\n const { paymentId, isLivePayment, formId } = session\n\n if (!paymentId) {\n throw Boom.badRequest('No paymentId in session')\n }\n\n const apiKey = getPaymentApiKey(isLivePayment, formId)\n const paymentService = new PaymentService(apiKey)\n const paymentStatus = await paymentService.getPaymentStatus(paymentId)\n\n return { session, sessionKey, paymentStatus }\n}\n\n/**\n * @import { Request } from '@hapi/hapi'\n * @import { GetPaymentResponse, PaymentSessionData } from '~/src/server/plugins/payment/types.js'\n */\n"],"mappings":"AAAA,OAAOA,IAAI,MAAM,YAAY;AAE7B,SAASC,sBAAsB;AAC/B,SAASC,gBAAgB;AACzB,SAASC,cAAc;;AAEvB;AACA;AACA;AACA;AACA;AACA;AACA,OAAO,eAAeC,iBAAiBA,CAACC,OAAO,EAAEC,IAAI,EAAE;EACrD,MAAMC,UAAU,GAAG,GAAGN,sBAAsB,GAAGK,IAAI,EAAE;EACrD,MAAME,OAAO,GAAG;EACdH,OAAO,CAACI,GAAG,CAACC,GAAG,CAACH,UAAU,CAC3B;EAED,IAAI,CAACC,OAAO,EAAE;IACZ,MAAMR,IAAI,CAACW,UAAU,CAAC,qCAAqCL,IAAI,EAAE,CAAC;EACpE;EAEA,MAAM;IAAEM,SAAS;IAAEC,aAAa;IAAEC;EAAO,CAAC,GAAGN,OAAO;EAEpD,IAAI,CAACI,SAAS,EAAE;IACd,MAAMZ,IAAI,CAACW,UAAU,CAAC,yBAAyB,CAAC;EAClD;EAEA,MAAMI,MAAM,GAAGb,gBAAgB,CAACW,aAAa,EAAEC,MAAM,CAAC;EACtD,MAAME,cAAc,GAAG,IAAIb,cAAc,CAACY,MAAM,CAAC;EACjD,MAAME,aAAa,GAAG,MAAMD,cAAc,CAACE,gBAAgB,CAACN,SAAS,CAAC;EAEtE,OAAO;IAAEJ,OAAO;IAAED,UAAU;IAAEU;EAAc,CAAC;AAC/C;;AAEA;AACA;AACA;AACA","ignoreList":[]}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { getPaymentContext } from "./payment-helper.js";
|
|
2
|
+
import { get } from "../../../services/httpService.js";
|
|
3
|
+
jest.mock("../../../services/httpService.ts");
|
|
4
|
+
describe('payment helper', () => {
|
|
5
|
+
const uuid = '5a54c2fe-da49-4202-8cd3-2121eaca03c3';
|
|
6
|
+
it('should throw if no session', async () => {
|
|
7
|
+
const mockRequest = {
|
|
8
|
+
yar: {
|
|
9
|
+
get: jest.fn().mockReturnValueOnce(undefined)
|
|
10
|
+
}
|
|
11
|
+
};
|
|
12
|
+
// @ts-expect-error - partial request mock
|
|
13
|
+
await expect(() => getPaymentContext(mockRequest, uuid)).rejects.toThrow('No payment session found for uuid=5a54c2fe-da49-4202-8cd3-2121eaca03c3');
|
|
14
|
+
});
|
|
15
|
+
it('should throw if no payment id', async () => {
|
|
16
|
+
const mockRequest = {
|
|
17
|
+
yar: {
|
|
18
|
+
get: jest.fn().mockReturnValueOnce({})
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
// @ts-expect-error - partial request mock
|
|
22
|
+
await expect(() => getPaymentContext(mockRequest, uuid)).rejects.toThrow('No paymentId in session');
|
|
23
|
+
});
|
|
24
|
+
it('should get context successfully', async () => {
|
|
25
|
+
const mockRequest = {
|
|
26
|
+
yar: {
|
|
27
|
+
get: jest.fn().mockReturnValueOnce({
|
|
28
|
+
paymentId: 'payment-id',
|
|
29
|
+
isLivePayment: false,
|
|
30
|
+
formId: 'formid'
|
|
31
|
+
})
|
|
32
|
+
}
|
|
33
|
+
};
|
|
34
|
+
const getPaymentStatusApiResult = {
|
|
35
|
+
payment_id: 'payment-id-12345',
|
|
36
|
+
_links: {
|
|
37
|
+
next_url: {
|
|
38
|
+
href: 'http://next-url-href/payment'
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
state: {
|
|
42
|
+
status: 'created'
|
|
43
|
+
}
|
|
44
|
+
};
|
|
45
|
+
jest.mocked(get).mockResolvedValueOnce({
|
|
46
|
+
res: (/** @type {IncomingMessage} */{
|
|
47
|
+
statusCode: 200,
|
|
48
|
+
headers: {}
|
|
49
|
+
}),
|
|
50
|
+
payload: getPaymentStatusApiResult,
|
|
51
|
+
error: undefined
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
// @ts-expect-error - partial request mock
|
|
55
|
+
const res = await getPaymentContext(mockRequest, uuid);
|
|
56
|
+
expect(res).toEqual({
|
|
57
|
+
paymentStatus: {
|
|
58
|
+
paymentId: 'payment-id-12345',
|
|
59
|
+
_links: {
|
|
60
|
+
next_url: {
|
|
61
|
+
href: 'http://next-url-href/payment'
|
|
62
|
+
}
|
|
63
|
+
},
|
|
64
|
+
state: {
|
|
65
|
+
status: 'created'
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
session: {
|
|
69
|
+
formId: 'formid',
|
|
70
|
+
isLivePayment: false,
|
|
71
|
+
paymentId: 'payment-id'
|
|
72
|
+
},
|
|
73
|
+
sessionKey: 'payment-5a54c2fe-da49-4202-8cd3-2121eaca03c3'
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @import { IncomingMessage } from 'node:http'
|
|
80
|
+
*/
|
|
81
|
+
//# sourceMappingURL=payment-helper.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment-helper.test.js","names":["getPaymentContext","get","jest","mock","describe","uuid","it","mockRequest","yar","fn","mockReturnValueOnce","undefined","expect","rejects","toThrow","paymentId","isLivePayment","formId","getPaymentStatusApiResult","payment_id","_links","next_url","href","state","status","mocked","mockResolvedValueOnce","res","statusCode","headers","payload","error","toEqual","paymentStatus","session","sessionKey"],"sources":["../../../../../src/server/plugins/engine/routes/payment-helper.test.js"],"sourcesContent":["import { getPaymentContext } from '~/src/server/plugins/engine/routes/payment-helper.js'\nimport { get } from '~/src/server/services/httpService.js'\n\njest.mock('~/src/server/services/httpService.ts')\n\ndescribe('payment helper', () => {\n const uuid = '5a54c2fe-da49-4202-8cd3-2121eaca03c3'\n it('should throw if no session', async () => {\n const mockRequest = {\n yar: {\n get: jest.fn().mockReturnValueOnce(undefined)\n }\n }\n // @ts-expect-error - partial request mock\n await expect(() => getPaymentContext(mockRequest, uuid)).rejects.toThrow(\n 'No payment session found for uuid=5a54c2fe-da49-4202-8cd3-2121eaca03c3'\n )\n })\n\n it('should throw if no payment id', async () => {\n const mockRequest = {\n yar: {\n get: jest.fn().mockReturnValueOnce({})\n }\n }\n // @ts-expect-error - partial request mock\n await expect(() => getPaymentContext(mockRequest, uuid)).rejects.toThrow(\n 'No paymentId in session'\n )\n })\n\n it('should get context successfully', async () => {\n const mockRequest = {\n yar: {\n get: jest.fn().mockReturnValueOnce({\n paymentId: 'payment-id',\n isLivePayment: false,\n formId: 'formid'\n })\n }\n }\n\n const getPaymentStatusApiResult = {\n payment_id: 'payment-id-12345',\n _links: {\n next_url: {\n href: 'http://next-url-href/payment'\n }\n },\n state: {\n status: 'created'\n }\n }\n\n jest.mocked(get).mockResolvedValueOnce({\n res: /** @type {IncomingMessage} */ ({\n statusCode: 200,\n headers: {}\n }),\n payload: getPaymentStatusApiResult,\n error: undefined\n })\n\n // @ts-expect-error - partial request mock\n const res = await getPaymentContext(mockRequest, uuid)\n expect(res).toEqual({\n paymentStatus: {\n paymentId: 'payment-id-12345',\n _links: {\n next_url: {\n href: 'http://next-url-href/payment'\n }\n },\n state: {\n status: 'created'\n }\n },\n session: {\n formId: 'formid',\n isLivePayment: false,\n paymentId: 'payment-id'\n },\n sessionKey: 'payment-5a54c2fe-da49-4202-8cd3-2121eaca03c3'\n })\n })\n})\n\n/**\n * @import { IncomingMessage } from 'node:http'\n */\n"],"mappings":"AAAA,SAASA,iBAAiB;AAC1B,SAASC,GAAG;AAEZC,IAAI,CAACC,IAAI,mCAAuC,CAAC;AAEjDC,QAAQ,CAAC,gBAAgB,EAAE,MAAM;EAC/B,MAAMC,IAAI,GAAG,sCAAsC;EACnDC,EAAE,CAAC,4BAA4B,EAAE,YAAY;IAC3C,MAAMC,WAAW,GAAG;MAClBC,GAAG,EAAE;QACHP,GAAG,EAAEC,IAAI,CAACO,EAAE,CAAC,CAAC,CAACC,mBAAmB,CAACC,SAAS;MAC9C;IACF,CAAC;IACD;IACA,MAAMC,MAAM,CAAC,MAAMZ,iBAAiB,CAACO,WAAW,EAAEF,IAAI,CAAC,CAAC,CAACQ,OAAO,CAACC,OAAO,CACtE,wEACF,CAAC;EACH,CAAC,CAAC;EAEFR,EAAE,CAAC,+BAA+B,EAAE,YAAY;IAC9C,MAAMC,WAAW,GAAG;MAClBC,GAAG,EAAE;QACHP,GAAG,EAAEC,IAAI,CAACO,EAAE,CAAC,CAAC,CAACC,mBAAmB,CAAC,CAAC,CAAC;MACvC;IACF,CAAC;IACD;IACA,MAAME,MAAM,CAAC,MAAMZ,iBAAiB,CAACO,WAAW,EAAEF,IAAI,CAAC,CAAC,CAACQ,OAAO,CAACC,OAAO,CACtE,yBACF,CAAC;EACH,CAAC,CAAC;EAEFR,EAAE,CAAC,iCAAiC,EAAE,YAAY;IAChD,MAAMC,WAAW,GAAG;MAClBC,GAAG,EAAE;QACHP,GAAG,EAAEC,IAAI,CAACO,EAAE,CAAC,CAAC,CAACC,mBAAmB,CAAC;UACjCK,SAAS,EAAE,YAAY;UACvBC,aAAa,EAAE,KAAK;UACpBC,MAAM,EAAE;QACV,CAAC;MACH;IACF,CAAC;IAED,MAAMC,yBAAyB,GAAG;MAChCC,UAAU,EAAE,kBAAkB;MAC9BC,MAAM,EAAE;QACNC,QAAQ,EAAE;UACRC,IAAI,EAAE;QACR;MACF,CAAC;MACDC,KAAK,EAAE;QACLC,MAAM,EAAE;MACV;IACF,CAAC;IAEDtB,IAAI,CAACuB,MAAM,CAACxB,GAAG,CAAC,CAACyB,qBAAqB,CAAC;MACrCC,GAAG,GAAE,8BAAgC;QACnCC,UAAU,EAAE,GAAG;QACfC,OAAO,EAAE,CAAC;MACZ,CAAC,CAAC;MACFC,OAAO,EAAEZ,yBAAyB;MAClCa,KAAK,EAAEpB;IACT,CAAC,CAAC;;IAEF;IACA,MAAMgB,GAAG,GAAG,MAAM3B,iBAAiB,CAACO,WAAW,EAAEF,IAAI,CAAC;IACtDO,MAAM,CAACe,GAAG,CAAC,CAACK,OAAO,CAAC;MAClBC,aAAa,EAAE;QACblB,SAAS,EAAE,kBAAkB;QAC7BK,MAAM,EAAE;UACNC,QAAQ,EAAE;YACRC,IAAI,EAAE;UACR;QACF,CAAC;QACDC,KAAK,EAAE;UACLC,MAAM,EAAE;QACV;MACF,CAAC;MACDU,OAAO,EAAE;QACPjB,MAAM,EAAE,QAAQ;QAChBD,aAAa,EAAE,KAAK;QACpBD,SAAS,EAAE;MACb,CAAC;MACDoB,UAAU,EAAE;IACd,CAAC,CAAC;EACJ,CAAC,CAAC;AACJ,CAAC,CAAC;;AAEF;AACA;AACA","ignoreList":[]}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Gets the payment routes for handling GOV.UK Pay callbacks
|
|
3
|
+
* @returns {ServerRoute[]}
|
|
4
|
+
*/
|
|
5
|
+
export function getRoutes(): ServerRoute[];
|
|
6
|
+
export const PAYMENT_RETURN_PATH: "/payment-callback";
|
|
7
|
+
export const PAYMENT_SESSION_PREFIX: "payment-";
|
|
8
|
+
import type { ServerRoute } from '@hapi/hapi';
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import Boom from '@hapi/boom';
|
|
2
|
+
import { StatusCodes } from 'http-status-codes';
|
|
3
|
+
import Joi from 'joi';
|
|
4
|
+
import { EXTERNAL_STATE_APPENDAGE } from "../../../constants.js";
|
|
5
|
+
import { getPaymentContext } from "./payment-helper.js";
|
|
6
|
+
export const PAYMENT_RETURN_PATH = '/payment-callback';
|
|
7
|
+
export const PAYMENT_SESSION_PREFIX = 'payment-';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Flash form component state after successful payment
|
|
11
|
+
* @param {Request} request - the request
|
|
12
|
+
* @param {PaymentSessionData} session - the session data containing payment state
|
|
13
|
+
* @param {GetPaymentResponse} paymentStatus - the payment status response from GOV.UK Pay
|
|
14
|
+
*/
|
|
15
|
+
function flashComponentState(request, session, paymentStatus) {
|
|
16
|
+
/** @type {PaymentState} */
|
|
17
|
+
const paymentState = {
|
|
18
|
+
paymentId: paymentStatus.paymentId,
|
|
19
|
+
reference: session.reference,
|
|
20
|
+
amount: session.amount,
|
|
21
|
+
description: session.description,
|
|
22
|
+
uuid: session.uuid,
|
|
23
|
+
formId: session.formId,
|
|
24
|
+
isLivePayment: session.isLivePayment,
|
|
25
|
+
payerEmail: paymentStatus.email,
|
|
26
|
+
preAuth: {
|
|
27
|
+
status: 'success',
|
|
28
|
+
createdAt: new Date().toISOString()
|
|
29
|
+
}
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/** @type {ExternalStateAppendage} */
|
|
33
|
+
const appendage = {
|
|
34
|
+
component: session.componentName,
|
|
35
|
+
data: (/** @type {FormState} */ /** @type {unknown} */paymentState)
|
|
36
|
+
};
|
|
37
|
+
request.yar.flash(EXTERNAL_STATE_APPENDAGE, appendage, true);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Gets the payment routes for handling GOV.UK Pay callbacks
|
|
42
|
+
* @returns {ServerRoute[]}
|
|
43
|
+
*/
|
|
44
|
+
export function getRoutes() {
|
|
45
|
+
return [getReturnRoute()];
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Handles successful payment states (capturable/success)
|
|
50
|
+
* @param {Request} request - the request
|
|
51
|
+
* @param {ResponseToolkit} h - the response toolkit
|
|
52
|
+
* @param {PaymentSessionData} session - the session data
|
|
53
|
+
* @param {string} sessionKey - the session key
|
|
54
|
+
* @param {GetPaymentResponse} paymentStatus - the payment status from GOV.UK Pay
|
|
55
|
+
*/
|
|
56
|
+
function handlePaymentSuccess(request, h, session, sessionKey, paymentStatus) {
|
|
57
|
+
flashComponentState(request, session, paymentStatus);
|
|
58
|
+
request.yar.clear(sessionKey);
|
|
59
|
+
return h.redirect(session.returnUrl).code(StatusCodes.SEE_OTHER);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Handles failed/cancelled/error payment states
|
|
64
|
+
* @param {Request} request - the request
|
|
65
|
+
* @param {ResponseToolkit} h - the response toolkit
|
|
66
|
+
* @param {PaymentSessionData} session - the session data
|
|
67
|
+
* @param {string} sessionKey - the session key
|
|
68
|
+
*/
|
|
69
|
+
function handlePaymentFailure(request, h, session, sessionKey) {
|
|
70
|
+
request.yar.clear(sessionKey);
|
|
71
|
+
return h.redirect(session.failureUrl).code(StatusCodes.SEE_OTHER);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* Route handler for payment return URL
|
|
76
|
+
* This is called when GOV.UK Pay redirects the user back after payment
|
|
77
|
+
* @returns {ServerRoute}
|
|
78
|
+
*/
|
|
79
|
+
function getReturnRoute() {
|
|
80
|
+
return {
|
|
81
|
+
method: 'GET',
|
|
82
|
+
path: PAYMENT_RETURN_PATH,
|
|
83
|
+
async handler(request, h) {
|
|
84
|
+
const {
|
|
85
|
+
uuid
|
|
86
|
+
} = /** @type {{ uuid: string }} */request.query;
|
|
87
|
+
const {
|
|
88
|
+
session,
|
|
89
|
+
sessionKey,
|
|
90
|
+
paymentStatus
|
|
91
|
+
} = await getPaymentContext(request, uuid);
|
|
92
|
+
|
|
93
|
+
/**
|
|
94
|
+
* @see https://docs.payments.service.gov.uk/api_reference/#payment-status-lifecycle
|
|
95
|
+
*/
|
|
96
|
+
const {
|
|
97
|
+
status
|
|
98
|
+
} = paymentStatus.state;
|
|
99
|
+
switch (status) {
|
|
100
|
+
case 'capturable':
|
|
101
|
+
case 'success':
|
|
102
|
+
return handlePaymentSuccess(request, h, session, sessionKey, paymentStatus);
|
|
103
|
+
case 'cancelled':
|
|
104
|
+
case 'failed':
|
|
105
|
+
case 'error':
|
|
106
|
+
return handlePaymentFailure(request, h, session, sessionKey);
|
|
107
|
+
case 'created':
|
|
108
|
+
case 'started':
|
|
109
|
+
case 'submitted':
|
|
110
|
+
{
|
|
111
|
+
const nextUrl = paymentStatus._links.next_url?.href;
|
|
112
|
+
if (!nextUrl) {
|
|
113
|
+
throw Boom.badRequest(`Payment in state '${status}' but no next_url available`);
|
|
114
|
+
}
|
|
115
|
+
return h.redirect(nextUrl).code(StatusCodes.SEE_OTHER);
|
|
116
|
+
}
|
|
117
|
+
default:
|
|
118
|
+
{
|
|
119
|
+
const unknownStatus = /** @type {string} */status;
|
|
120
|
+
throw Boom.internal(`Unknown payment status: ${unknownStatus}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
options: {
|
|
125
|
+
validate: {
|
|
126
|
+
query: Joi.object().keys({
|
|
127
|
+
uuid: Joi.string().uuid().required()
|
|
128
|
+
}).required()
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
};
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* @import { Request, ResponseToolkit, ServerRoute } from '@hapi/hapi'
|
|
136
|
+
* @import { GetPaymentResponse, PaymentSessionData } from '~/src/server/plugins/payment/types.js'
|
|
137
|
+
* @import { PaymentState } from '~/src/server/plugins/engine/components/PaymentField.types.js'
|
|
138
|
+
* @import { ExternalStateAppendage, FormState } from '~/src/server/plugins/engine/types.js'
|
|
139
|
+
*/
|
|
140
|
+
//# sourceMappingURL=payment.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"payment.js","names":["Boom","StatusCodes","Joi","EXTERNAL_STATE_APPENDAGE","getPaymentContext","PAYMENT_RETURN_PATH","PAYMENT_SESSION_PREFIX","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","handlePaymentSuccess","h","sessionKey","clear","redirect","returnUrl","code","SEE_OTHER","handlePaymentFailure","failureUrl","method","path","handler","query","state","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 { EXTERNAL_STATE_APPENDAGE } from '~/src/server/constants.js'\nimport { getPaymentContext } from '~/src/server/plugins/engine/routes/payment-helper.js'\n\nexport const PAYMENT_RETURN_PATH = '/payment-callback'\nexport const PAYMENT_SESSION_PREFIX = 'payment-'\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 * 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 const { session, sessionKey, paymentStatus } = await getPaymentContext(\n request,\n uuid\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 return handlePaymentSuccess(\n request,\n h,\n session,\n sessionKey,\n paymentStatus\n )\n\n case 'cancelled':\n case 'failed':\n case 'error':\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 */\n"],"mappings":"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","ignoreList":[]}
|