Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion app/controllers/YourBankDetailsController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,9 @@ class YourBankDetailsController @Inject() (
}
}

private def normaliseSortCode(value: String): String =
value.replaceAll("[\\s-]", "")

private def startVerification(
accountType: PersonalOrBusinessAccount,
bankDetails: YourBankDetails,
Expand All @@ -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,
Expand Down
28 changes: 21 additions & 7 deletions app/forms/YourBankDetailsFormProvider.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -29,19 +30,32 @@ 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")
.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(
firstError(
minLength(MAX_ACCOUNT_NUMBER_LENGTH, "yourBankDetails.error.accountNumber.tooShort"),
Expand Down
8 changes: 6 additions & 2 deletions app/models/requests/ChrisSubmissionRequest.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
2 changes: 0 additions & 2 deletions app/services/BarsService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand All @@ -93,7 +92,6 @@ 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") =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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"))
Expand Down
53 changes: 53 additions & 0 deletions test/forms/YourBankDetailsFormProviderSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -81,11 +81,51 @@ 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
}

"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 "12-34-56"
}

"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
// Form now preserves raw input (including surrounding spaces)
bound.value.get.sortCode mustBe " 12 34 56 "
}

behave like mandatoryField(
form,
fieldName,
Expand Down Expand Up @@ -125,6 +165,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,
Expand Down