Skip to content

Feat/df 623 payment#293

Merged
jbarnsley10 merged 83 commits intomainfrom
feat/df-623-payment
Feb 4, 2026
Merged

Feat/df 623 payment#293
jbarnsley10 merged 83 commits intomainfrom
feat/df-623-payment

Conversation

@mokhld
Copy link
Contributor

@mokhld mokhld commented Jan 21, 2026

Proposed change

Handle online payments

NOTE - requires a merge of designer PR DEFRA/forms-designer#1275 to change the structure of the PaymentField properties

Jira ticket: DF-623

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Misc. (documentation, build updates, etc)

Checklist

  • You have executed this code locally and it performs as expected.
  • You have added tests to verify your code works.
  • You have added code comments and JSDoc, where appropriate.
  • There is no commented-out code.
  • You have added developer docs in README.md and docs/* (where appropriate, e.g. new features).
  • The tests are passing (npm run test).
  • The linting checks are passing (npm run lint).
  • The code has been formatted (npm run format).

mokhld and others added 24 commits January 20, 2026 16:41
Created skeleton PaymentService
Comment on lines 265 to 271
paymentProviderApiKeyTest: {
doc: 'A test API key for integrating with a payment provider',
format: String,
nullable: true,
default: undefined,
env: 'PAYMENT_PROVIDER_API_KEY_TEST'
} as SchemaObj<string | undefined>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need for a global value here, it'll be per form

@@ -0,0 +1,31 @@
---
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👌 always helpful

}

return this.isValue(value) ? value : null
return this.isValue(value) ? (value as Item['value']) : null
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected

Comment on lines 96 to 97
const amount = this.options.amount ?? 0
const formattedAmount = amount.toFixed(2)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

better if this comes from state, not the component options

we've got a difference in sources of truth:

getDisplayStringFromState's value comes from state
getViewModel's value comes from the component def

better if we keep this consistent and use paymentState everywhere. Maybe pull this out to a function getPaidAmount that returns the amount from state, which we can reuse in both places?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected

Comment on lines 276 to 280
throw new InvalidComponentStateError(
this,
'Your payment authorisation has expired. Please add your payment details again.',
{ shouldResetState: true, isPaymentExpired: true }
)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

glad to see this being used 💪

/**
* Whether to reset the component state and redirect to the component's page.
* - `true`: Reset state and redirect (e.g., payment expired - user must re-enter)
* - `false`: Keep state and stay on current page with error (e.g., capture failed - user can retry)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this feels like the wrong exception to use for the false scenario. This exception is specifically for when the component state is invalid and should be cleared.

We can make another exception class or use Boom for 'standard' errors.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected

* When true, an "Important" notification banner will be shown on the payment page.
* @default false
*/
isPaymentExpired?: boolean
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

could we make this a bit more generic? I generally would like to avoid page controllers being coupled to specific components

in this example, we could make the terminology a bit more generic but retain the same functionality. Maybe rather than isPaymentExpired we have notificationType: 'error' | 'important' or something to that effect? That way it's reusable between components.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Corrected

exampleField: 'hello world',
exampleField2: 'hello world'
},
payments: {},
Copy link
Contributor

@alexluckett alexluckett Jan 29, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see a corresponding adapter/v1.ts change in this file, but why does payments need its own entry?

We could just have a single payment component (fixed component name). That way to access a payment you just do the usual data.main.payment rather than needing special handling via data.payments?

@davidjamesstone
Copy link
Contributor

When I checkout out this branch in install npm deps, I get a change on package-lock.json - I think it may need syncing up.

this.stateSchema = paymentStateSchema.default(null).allow(null)
// 'required()' forces the payment page to be invalid until we have valid payment state
// i.e. the user will automatically be directed back to the payment page
// if they attempt to access future pages wen no payment entered yet
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Typo "wen"

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks - corrected now

@sonarqubecloud
Copy link

sonarqubecloud bot commented Feb 4, 2026

Quality Gate Failed Quality Gate failed

Failed conditions
86.6% Coverage on New Code (required ≥ 90%)

See analysis details on SonarQube Cloud

@jbarnsley10 jbarnsley10 merged commit db2ce3b into main Feb 4, 2026
23 checks passed
@jbarnsley10 jbarnsley10 deleted the feat/df-623-payment branch February 4, 2026 11:22
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants