From 3ed5327ccd15d5e42cf5c19ce974fede4e215e0b Mon Sep 17 00:00:00 2001 From: sonalgupta1227 Date: Thu, 12 Feb 2026 16:48:18 +0000 Subject: [PATCH 1/5] DTR-3183: Allowing hyphens, space for sort code and spaces only for accountNumber --- app/forms/YourBankDetailsFormProvider.scala | 2 + .../YourBankDetailsFormProviderSpec.scala | 44 +++++++++++++++++++ 2 files changed, 46 insertions(+) diff --git a/app/forms/YourBankDetailsFormProvider.scala b/app/forms/YourBankDetailsFormProvider.scala index 10b0dd3c..2446113f 100644 --- a/app/forms/YourBankDetailsFormProvider.scala +++ b/app/forms/YourBankDetailsFormProvider.scala @@ -34,6 +34,7 @@ class YourBankDetailsFormProvider @Inject() extends Mappings { "accountHolderName" -> text("yourBankDetails.error.accountHolderName.required") .verifying(maxLength(MAX_ACCOUNT_HOLDER_NAME_LENGTH, "yourBankDetails.error.accountHolderName.length")), "sortCode" -> text("yourBankDetails.error.sortCode.required") + .transform[String](value => value.replaceAll("[\\s-]", ""), identity) .verifying( firstError( minLength(MAX_SORT_CODE_LENGTH, "yourBankDetails.error.sortCode.tooShort"), @@ -42,6 +43,7 @@ class YourBankDetailsFormProvider @Inject() extends Mappings { ) ), "accountNumber" -> text("yourBankDetails.error.accountNumber.required") + .transform[String](value => value.replaceAll("\\s", ""), identity) .verifying( firstError( minLength(MAX_ACCOUNT_NUMBER_LENGTH, "yourBankDetails.error.accountNumber.tooShort"), diff --git a/test/forms/YourBankDetailsFormProviderSpec.scala b/test/forms/YourBankDetailsFormProviderSpec.scala index 91060477..822b7042 100644 --- a/test/forms/YourBankDetailsFormProviderSpec.scala +++ b/test/forms/YourBankDetailsFormProviderSpec.scala @@ -27,6 +27,11 @@ class YourBankDetailsFormProviderSpec extends StringFieldBehaviours { def numericStringOfLength(n: Int): Gen[String] = Gen.listOfN(n, Gen.numChar).map(_.mkString) + def numericStringWithSpacesOrHyphens(length: Int): Gen[String] = + numericStringOfLength(length).map { s => + s.grouped(2).mkString("-") + } + ".accountHolderName" - { val fieldName = "accountHolderName" @@ -86,6 +91,32 @@ class YourBankDetailsFormProviderSpec extends StringFieldBehaviours { result.errors.exists(_.message == numericOnlyKey) mustBe true } + "bind valid sort code with spaces or hyphens" in { + val bound = form.bind( + Map( + "accountHolderName" -> "John Doe", + "sortCode" -> "12-34-56", + "accountNumber" -> "12345678" + ) + ) + + bound.errors mustBe empty + bound.value.get.sortCode mustBe "123456" + } + + "bind valid sort code with spaces" in { + val bound = form.bind( + Map( + "accountHolderName" -> "John Doe", + "sortCode" -> "12 34 56", + "accountNumber" -> "12345678" + ) + ) + + bound.errors mustBe empty + bound.value.get.sortCode mustBe "123456" + } + behave like mandatoryField( form, fieldName, @@ -125,6 +156,19 @@ class YourBankDetailsFormProviderSpec extends StringFieldBehaviours { result.errors.exists(_.message == numericOnlyKey) mustBe true } + "bind valid account number with spaces" in { + val bound = form.bind( + Map( + "accountHolderName" -> "John Doe", + "sortCode" -> "123456", + "accountNumber" -> "12 34 56 78" + ) + ) + + bound.errors mustBe empty + bound.value.get.accountNumber mustBe "12345678" + } + behave like mandatoryField( form, fieldName, From 18cf4f81a7887a1ce471bb19e3bb7e746bd1cf33 Mon Sep 17 00:00:00 2001 From: sonalgupta1227 Date: Fri, 13 Feb 2026 14:25:01 +0000 Subject: [PATCH 2/5] DTR-3183: minor welsh quote fix --- conf/messages.cy | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/conf/messages.cy b/conf/messages.cy index 226ff24a..11574b04 100644 --- a/conf/messages.cy +++ b/conf/messages.cy @@ -85,7 +85,7 @@ yourBankDetails.sortCode.hint = Mae’n rhaid iddo fod yn 6 digid yourBankDetails.accountNumber = Rhif y cyfrif yourBankDetails.accountNumber.hint = Mae’n rhaid iddo fod yn 8 digid yourBankDetails.error.accountHolderName.required = Nodwch yr enw sydd ar y cyfrif -yourBankDetails.error.accountHolderName.length = Rhaid i'r enw ar y cyfrif fod yn 35 nod neu lai +yourBankDetails.error.accountHolderName.length = Rhaid i’r enw ar y cyfrif fod yn 35 nod neu lai yourBankDetails.error.sortCode.required = Nodwch eich cod didoli yourBankDetails.error.sortCode.length = Mae’n rhaid i’r cod didoli fod yn 6 digid yourBankDetails.error.sortCode.tooShort = Mae’n rhaid i’r cod didoli fod yn 6 digid From 4c86734a310ad9b6330e1c9a071ec1dfca6d331d Mon Sep 17 00:00:00 2001 From: sonalgupta1227 Date: Fri, 13 Feb 2026 14:58:10 +0000 Subject: [PATCH 3/5] DTR-3183: Minor test case changes --- test/forms/YourBankDetailsFormProviderSpec.scala | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/test/forms/YourBankDetailsFormProviderSpec.scala b/test/forms/YourBankDetailsFormProviderSpec.scala index 822b7042..f4e07357 100644 --- a/test/forms/YourBankDetailsFormProviderSpec.scala +++ b/test/forms/YourBankDetailsFormProviderSpec.scala @@ -27,11 +27,6 @@ class YourBankDetailsFormProviderSpec extends StringFieldBehaviours { def numericStringOfLength(n: Int): Gen[String] = Gen.listOfN(n, Gen.numChar).map(_.mkString) - def numericStringWithSpacesOrHyphens(length: Int): Gen[String] = - numericStringOfLength(length).map { s => - s.grouped(2).mkString("-") - } - ".accountHolderName" - { val fieldName = "accountHolderName" @@ -108,7 +103,7 @@ class YourBankDetailsFormProviderSpec extends StringFieldBehaviours { val bound = form.bind( Map( "accountHolderName" -> "John Doe", - "sortCode" -> "12 34 56", + "sortCode" -> " 12 34 56 ", "accountNumber" -> "12345678" ) ) @@ -161,7 +156,7 @@ class YourBankDetailsFormProviderSpec extends StringFieldBehaviours { Map( "accountHolderName" -> "John Doe", "sortCode" -> "123456", - "accountNumber" -> "12 34 56 78" + "accountNumber" -> " 12 34 56 78 " ) ) From 0ce12f333a879a5a3de41daf3cc31b80a3442b17 Mon Sep 17 00:00:00 2001 From: javed Iqbal <131012491+javed-iqbal1@users.noreply.github.com> Date: Thu, 19 Feb 2026 13:59:18 +0000 Subject: [PATCH 4/5] DTR-3183 preserve user input for sort code --- .../YourBankDetailsController.scala | 8 +++++- app/forms/YourBankDetailsFormProvider.scala | 28 +++++++++++++------ .../requests/ChrisSubmissionRequest.scala | 8 ++++-- app/services/BarsService.scala | 4 +-- .../YourBankDetailsSortCodeSummary.scala | 2 +- .../YourBankDetailsFormProviderSpec.scala | 18 ++++++++++-- 6 files changed, 51 insertions(+), 17 deletions(-) diff --git a/app/controllers/YourBankDetailsController.scala b/app/controllers/YourBankDetailsController.scala index 7894af90..a2c3ea91 100644 --- a/app/controllers/YourBankDetailsController.scala +++ b/app/controllers/YourBankDetailsController.scala @@ -92,6 +92,9 @@ class YourBankDetailsController @Inject() ( } } + private def normaliseSortCode(value: String): String = + value.replaceAll("[\\s-]", "") + private def startVerification( accountType: PersonalOrBusinessAccount, bankDetails: YourBankDetails, @@ -100,7 +103,10 @@ class YourBankDetailsController @Inject() ( mode: Mode )(implicit hc: HeaderCarrier, request: DataRequest[?]): Future[Result] = { - barsService.barsVerification(accountType.toString, bankDetails).flatMap { + val bankDetailsForBars = + bankDetails.copy(sortCode = normaliseSortCode(bankDetails.sortCode)) + + barsService.barsVerification(accountType.toString, bankDetailsForBars).flatMap { case Right((verificationResponse, bank)) => onSuccessfulVerification( userAnswers, diff --git a/app/forms/YourBankDetailsFormProvider.scala b/app/forms/YourBankDetailsFormProvider.scala index 2446113f..ffc585ce 100644 --- a/app/forms/YourBankDetailsFormProvider.scala +++ b/app/forms/YourBankDetailsFormProvider.scala @@ -20,6 +20,7 @@ import forms.mappings.Mappings import models.YourBankDetails import play.api.data.Form import play.api.data.Forms.* +import play.api.data.validation.{Constraint, Invalid, Valid} import javax.inject.Inject @@ -29,19 +30,30 @@ class YourBankDetailsFormProvider @Inject() extends Mappings { val MAX_SORT_CODE_LENGTH = 6 val MAX_ACCOUNT_NUMBER_LENGTH = 8 + private def normaliseSortCode(value: String): String = + value.replaceAll("[\\s-]", "") + + private val sortCodeConstraint: Constraint[String] = + Constraint("constraints.sortCode") { value => + val normalised = normaliseSortCode(value) + + if (normalised.length < MAX_SORT_CODE_LENGTH) { + Invalid("yourBankDetails.error.sortCode.tooShort", MAX_SORT_CODE_LENGTH) + } else if (normalised.length > MAX_SORT_CODE_LENGTH) { + Invalid("yourBankDetails.error.sortCode.length", MAX_SORT_CODE_LENGTH) + } else if (!normalised.forall(_.isDigit)) { + Invalid("yourBankDetails.error.sortCode.numericOnly") + } else { + Valid + } + } + def apply(): Form[YourBankDetails] = Form( mapping( "accountHolderName" -> text("yourBankDetails.error.accountHolderName.required") .verifying(maxLength(MAX_ACCOUNT_HOLDER_NAME_LENGTH, "yourBankDetails.error.accountHolderName.length")), "sortCode" -> text("yourBankDetails.error.sortCode.required") - .transform[String](value => value.replaceAll("[\\s-]", ""), identity) - .verifying( - firstError( - minLength(MAX_SORT_CODE_LENGTH, "yourBankDetails.error.sortCode.tooShort"), - maxLength(MAX_SORT_CODE_LENGTH, "yourBankDetails.error.sortCode.length"), - regexp(NumericRegex, "yourBankDetails.error.sortCode.numericOnly") - ) - ), + .verifying(sortCodeConstraint), "accountNumber" -> text("yourBankDetails.error.accountNumber.required") .transform[String](value => value.replaceAll("\\s", ""), identity) .verifying( diff --git a/app/models/requests/ChrisSubmissionRequest.scala b/app/models/requests/ChrisSubmissionRequest.scala index 73a6b28c..c4e15661 100644 --- a/app/models/requests/ChrisSubmissionRequest.scala +++ b/app/models/requests/ChrisSubmissionRequest.scala @@ -81,12 +81,16 @@ object ChrisSubmissionRequest { case Some(existingDd) => YourBankDetailsWithAuddisStatus( accountHolderName = existingDd.bankAccountName, - sortCode = existingDd.bankSortCode, + sortCode = existingDd.bankSortCode.replaceAll("[\\s-]", ""), accountNumber = existingDd.bankAccountNumber, auddisStatus = existingDd.auDdisFlag, accountVerified = true ) - case _ => required(YourBankDetailsPage) + case _ => + val existing = required(YourBankDetailsPage) + existing.copy( + sortCode = existing.sortCode.replaceAll("[\\s-]", "") + ) } ChrisSubmissionRequest( diff --git a/app/services/BarsService.scala b/app/services/BarsService.scala index a3cf2e80..2ff1c05e 100644 --- a/app/services/BarsService.scala +++ b/app/services/BarsService.scala @@ -76,7 +76,6 @@ case class BarsService @Inject() ( def barsVerification(personalOrBusiness: String, bankDetails: YourBankDetails)(implicit hc: HeaderCarrier ): Future[Either[BarsErrors, (BarsVerificationResponse, Bank)]] = { - val (endpoint, requestJson) = if (personalOrBusiness.toLowerCase == "personal") { "personal" -> Json.toJson( BarsPersonalRequest( @@ -92,8 +91,7 @@ case class BarsService @Inject() ( ) ) } - - // Call BARS and map known errors + val verificationFuture: Future[Either[BarsErrors, BarsVerificationResponse]] = barsConnector.verify(endpoint, requestJson).map(Right(_)).recover { case e: UpstreamBarsException if e.status == 400 && e.errorCode.contains("SORT_CODE_ON_DENY_LIST") => diff --git a/app/viewmodels/checkAnswers/YourBankDetailsSortCodeSummary.scala b/app/viewmodels/checkAnswers/YourBankDetailsSortCodeSummary.scala index 9ea367fe..4d5d87a2 100644 --- a/app/viewmodels/checkAnswers/YourBankDetailsSortCodeSummary.scala +++ b/app/viewmodels/checkAnswers/YourBankDetailsSortCodeSummary.scala @@ -35,7 +35,7 @@ object YourBankDetailsSortCodeSummary { SummaryListRowViewModel( key = "bankDetailsCheckYourAnswer.account.sort.code", - value = ValueViewModel(HtmlContent(value)), + value = ValueViewModel(HtmlContent(value.replaceAll("[\\s-]", ""))), actions = Seq( ActionItemViewModel("site.change", routes.YourBankDetailsController.onPageLoad(CheckMode).url + "#sortCode") .withVisuallyHiddenText(messages("bankDetailsCheckYourAnswer.account.sort.code")) diff --git a/test/forms/YourBankDetailsFormProviderSpec.scala b/test/forms/YourBankDetailsFormProviderSpec.scala index f4e07357..7986fa98 100644 --- a/test/forms/YourBankDetailsFormProviderSpec.scala +++ b/test/forms/YourBankDetailsFormProviderSpec.scala @@ -81,6 +81,19 @@ class YourBankDetailsFormProviderSpec extends StringFieldBehaviours { result.errors must contain only FormError(fieldName, tooShortKey, Seq(maxLength)) } + "bind valid sort code with multiple hyphens" in { + val bound = form.bind( + Map( + "accountHolderName" -> "John Doe", + "sortCode" -> "20--71--02", + "accountNumber" -> "12345678" + ) + ) + + bound.errors mustBe empty + bound.value.get.sortCode mustBe "20--71--02" + } + "not bind non-numeric input" in { val result = form.bind(Map(fieldName -> "12A456")).apply(fieldName) result.errors.exists(_.message == numericOnlyKey) mustBe true @@ -96,7 +109,7 @@ class YourBankDetailsFormProviderSpec extends StringFieldBehaviours { ) bound.errors mustBe empty - bound.value.get.sortCode mustBe "123456" + bound.value.get.sortCode mustBe "12-34-56" } "bind valid sort code with spaces" in { @@ -109,7 +122,8 @@ class YourBankDetailsFormProviderSpec extends StringFieldBehaviours { ) bound.errors mustBe empty - bound.value.get.sortCode mustBe "123456" + // Form now preserves raw input (including surrounding spaces) + bound.value.get.sortCode mustBe " 12 34 56 " } behave like mandatoryField( From d5ecf93673006e346e180267ac12a41eb8e328b9 Mon Sep 17 00:00:00 2001 From: javed Iqbal <131012491+javed-iqbal1@users.noreply.github.com> Date: Thu, 19 Feb 2026 14:12:26 +0000 Subject: [PATCH 5/5] DTR-3183 format code --- app/services/BarsService.scala | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/BarsService.scala b/app/services/BarsService.scala index 2ff1c05e..43abc4f6 100644 --- a/app/services/BarsService.scala +++ b/app/services/BarsService.scala @@ -91,7 +91,7 @@ case class BarsService @Inject() ( ) ) } - + val verificationFuture: Future[Either[BarsErrors, BarsVerificationResponse]] = barsConnector.verify(endpoint, requestJson).map(Right(_)).recover { case e: UpstreamBarsException if e.status == 400 && e.errorCode.contains("SORT_CODE_ON_DENY_LIST") =>