Skip to content

Commit 4dd2cdb

Browse files
authored
Merge pull request #402 from DEFRA/fix/df-1098-payment-details
Fix/df 1098: Short-term fix of missing payment details
2 parents da6fe7d + 091fcaf commit 4dd2cdb

9 files changed

Lines changed: 143 additions & 14 deletions

File tree

src/server/plugins/engine/models/FormModel.test.ts

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
import {
2+
ComponentType,
23
SchemaVersion,
34
formDefinitionSchema,
45
formDefinitionV2Schema,
5-
type FormDefinition
6+
type ComponentDef,
7+
type FormDefinition,
8+
type PageQuestion
69
} from '@defra/forms-model'
710

811
import { todayAsDateOnly } from '~/src/server/plugins/engine/date-helper.js'
@@ -16,6 +19,7 @@ import conditionsListDefinition from '~/test/form/definitions/conditions-list.js
1619
import relativeDatesDefinition from '~/test/form/definitions/conditions-relative-dates-v2.js'
1720
import fieldsRequiredDefinition from '~/test/form/definitions/fields-required.js'
1821
import joinedConditionsDefinition from '~/test/form/definitions/joined-conditions-simple-v2.js'
22+
import paymentDefinition from '~/test/form/definitions/payment.js'
1923

2024
jest.mock('~/src/server/plugins/engine/date-helper.ts')
2125

@@ -722,4 +726,36 @@ describe('FormModel - Joined Conditions', () => {
722726
expect(model.getSection('nonexistent')).toBeUndefined()
723727
})
724728
})
729+
730+
describe('moreThanOnePaymentQuestion', () => {
731+
it('should return false if no payment questions', () => {
732+
const model = new FormModel(definition, { basePath: 'test' })
733+
expect(model.moreThanOnePaymentQuestion()).toBe(false)
734+
})
735+
736+
it('should return false if only one payment question', () => {
737+
const definition = {
738+
...paymentDefinition
739+
}
740+
741+
const model = new FormModel(definition, { basePath: 'test' })
742+
expect(model.moreThanOnePaymentQuestion()).toBe(false)
743+
})
744+
745+
it('should throw if more than one payment questions', () => {
746+
const definition = {
747+
...paymentDefinition
748+
}
749+
const extraPaymentComponent = {
750+
type: ComponentType.PaymentField,
751+
name: 'paymentField'
752+
} as ComponentDef
753+
const page = definition.pages[0] as PageQuestion
754+
page.components.push(extraPaymentComponent)
755+
756+
expect(() => new FormModel(definition, { basePath: 'test' })).toThrow(
757+
'Invalid form definition: Only one payment question is allowed per form'
758+
)
759+
})
760+
})
725761
})

src/server/plugins/engine/models/FormModel.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
formDefinitionV2Schema,
1010
generateConditionAlias,
1111
hasComponents,
12+
hasComponentsEvenIfNoNext,
1213
hasRepeater,
1314
isConditionWrapperV2,
1415
yesNoListId,
@@ -159,6 +160,13 @@ export class FormModel {
159160
this.services = services
160161
this.controllers = controllers
161162

163+
// Assert that there is only one payment question (if any)
164+
if (this.moreThanOnePaymentQuestion()) {
165+
throw new Error(
166+
'Invalid form definition: Only one payment question is allowed per form'
167+
)
168+
}
169+
162170
this.pageDefMap = new Map(def.pages.map((page) => [page.path, page]))
163171
this.listDefMap = new Map(def.lists.map((list) => [list.name, list]))
164172
this.listDefIdMap = new Map(
@@ -543,6 +551,18 @@ export class FormModel {
543551
.filter(isConditionWrapperV2)
544552
.find((condition) => condition.id === conditionId)
545553
}
554+
555+
/**
556+
* Checks that only one payment field exists (if any payments fields exist)
557+
*/
558+
moreThanOnePaymentQuestion() {
559+
const numOfPaymentFields = this.def.pages
560+
.flatMap((page) =>
561+
hasComponentsEvenIfNoNext(page) ? page.components : []
562+
)
563+
.filter((comp) => comp.type === ComponentType.PaymentField).length
564+
return numOfPaymentFields > 1
565+
}
546566
}
547567

548568
/**

src/server/plugins/engine/outputFormatters/human/v1.payment.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,8 @@ describe('v1 human formatter', () => {
8282

8383
const itemsPayment = getFormSubmissionData(
8484
summaryViewModelPayment.context,
85-
summaryViewModelPayment.details
85+
summaryViewModelPayment.details,
86+
modelPayment
8687
)
8788

8889
it('should add payment details', () => {

src/server/plugins/engine/outputFormatters/human/v1.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,8 @@ describe('v1 human formatter', () => {
7070

7171
const items = getFormSubmissionData(
7272
summaryViewModel.context,
73-
summaryViewModel.details
73+
summaryViewModel.details,
74+
model
7475
)
7576

7677
describe('getPersonalisation', () => {

src/server/plugins/engine/outputFormatters/machine/v2.payment.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,7 +79,8 @@ const summaryViewModel = controller.getSummaryViewModel(request, context)
7979

8080
const items = getFormSubmissionData(
8181
summaryViewModel.context,
82-
summaryViewModel.details
82+
summaryViewModel.details,
83+
model
8384
)
8485

8586
describe('getPersonalisation', () => {

src/server/plugins/engine/outputFormatters/machine/v2.test.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -280,7 +280,8 @@ describe('getPersonalisation', () => {
280280

281281
const items = getFormSubmissionData(
282282
summaryViewModel.context,
283-
summaryViewModel.details
283+
summaryViewModel.details,
284+
model
284285
)
285286

286287
const body = format(context, items, model, submitResponse, formStatus)

src/server/plugins/engine/pageControllers/SummaryPageController.test.ts

Lines changed: 60 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -356,12 +356,15 @@ describe('SummaryPageController - Payment (DF-832)', () => {
356356
} as FormSubmissionState)
357357

358358
const outputSubmit = jest.fn()
359+
const formSubmissionSubmit = jest.fn()
359360

360361
model.services = {
361362
...model.services,
362363
formSubmissionService: {
363364
...model.services.formSubmissionService,
364-
submit: jest.fn().mockResolvedValue({ data: { reference: 'r' } })
365+
submit: formSubmissionSubmit.mockResolvedValue({
366+
data: { reference: 'r' }
367+
})
365368
},
366369
outputService: {
367370
...model.services.outputService,
@@ -387,7 +390,14 @@ describe('SummaryPageController - Payment (DF-832)', () => {
387390
contact: { online: { url: '/help' } }
388391
} as unknown as Parameters<typeof submitForm>[1]
389392

390-
return { request, context, viewModel, formMetadata, outputSubmit }
393+
return {
394+
request,
395+
context,
396+
viewModel,
397+
formMetadata,
398+
outputSubmit,
399+
formSubmissionSubmit
400+
}
391401
}
392402

393403
it('re-throws as PaymentSubmissionError when outputService fails and a payment has been captured', async () => {
@@ -428,5 +438,53 @@ describe('SummaryPageController - Payment (DF-832)', () => {
428438
)
429439
).rejects.toBe(err)
430440
})
441+
442+
it('submits with correct payload', async () => {
443+
const {
444+
request,
445+
context,
446+
viewModel,
447+
formMetadata,
448+
formSubmissionSubmit
449+
} = buildSubmitHarness({ captured: true })
450+
451+
await submitForm(
452+
context,
453+
formMetadata,
454+
request,
455+
viewModel,
456+
model,
457+
'notify@example.com'
458+
)
459+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
460+
const paymentCall = formSubmissionSubmit.mock.calls[0][0]
461+
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
462+
const paymentItems = paymentCall.main as unknown as {
463+
name: string
464+
title: string
465+
value: string
466+
}[]
467+
expect(paymentItems).toHaveLength(4)
468+
expect(paymentItems[0]).toEqual({
469+
name: 'paymentField_paymentDescription',
470+
title: 'Payment description',
471+
value: 'Test payment'
472+
})
473+
expect(paymentItems[1]).toEqual({
474+
name: 'paymentField_paymentAmount',
475+
title: 'Payment amount',
476+
value: '£99.00'
477+
})
478+
expect(paymentItems[2]).toEqual({
479+
name: 'paymentField_paymentReference',
480+
title: 'Payment reference',
481+
value: 'ref-1'
482+
})
483+
expect(paymentItems[3]).toEqual({
484+
name: 'paymentField_paymentDate',
485+
title: 'Payment date',
486+
value: ''
487+
})
488+
})
431489
})
432490
})

src/server/plugins/engine/pageControllers/SummaryPageController.ts

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,8 @@ export async function submitForm(
433433

434434
const items = getFormSubmissionData(
435435
summaryViewModel.context,
436-
summaryViewModel.details
436+
summaryViewModel.details,
437+
model
437438
)
438439

439440
try {
@@ -531,11 +532,14 @@ function submitData(
531532
main: buildMainRecords(items),
532533
repeaters: buildRepeaterRecords(items)
533534
}
534-
535535
return submit(payload)
536536
}
537537

538-
export function getFormSubmissionData(context: FormContext, details: Detail[]) {
538+
export function getFormSubmissionData(
539+
context: FormContext,
540+
details: Detail[],
541+
model: FormModel
542+
) {
539543
const items = context.relevantPages
540544
.map(({ href }) =>
541545
details.flatMap(({ items }) =>
@@ -544,7 +548,7 @@ export function getFormSubmissionData(context: FormContext, details: Detail[]) {
544548
)
545549
.flat()
546550

547-
const paymentItems = getPaymentFieldItems(context)
551+
const paymentItems = getPaymentFieldItems(context, model)
548552

549553
return [...items, ...paymentItems]
550554
}
@@ -553,10 +557,13 @@ export function getFormSubmissionData(context: FormContext, details: Detail[]) {
553557
* Gets DetailItems for PaymentField components
554558
* PaymentField is excluded from summaryDetails for UI but needs to be in submission data
555559
*/
556-
function getPaymentFieldItems(context: FormContext): DetailItemField[] {
560+
function getPaymentFieldItems(
561+
context: FormContext,
562+
model: FormModel
563+
): DetailItemField[] {
557564
const items: DetailItemField[] = []
558565

559-
for (const page of context.relevantPages) {
566+
for (const page of model.pages) {
560567
for (const field of page.collection.fields) {
561568
if (field instanceof PaymentField) {
562569
items.push({

src/server/plugins/engine/routes/questions.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,11 @@ async function handleHttpEvent(
5959
// TODO: Update structured data POST payload with when helper
6060
// is updated to removing the dependency on `SummaryViewModel` etc.
6161
const viewModel = new SummaryViewModel(request, page, context)
62-
const items = getFormSubmissionData(viewModel.context, viewModel.details)
62+
const items = getFormSubmissionData(
63+
viewModel.context,
64+
viewModel.details,
65+
model
66+
)
6367

6468
// @ts-expect-error - function signature will be refactored in the next iteration of the formatter
6569
const payload = format(context, items, model, undefined, undefined)

0 commit comments

Comments
 (0)