Skip to content
6 changes: 6 additions & 0 deletions app/connectors/NationalDirectDebitConnector.scala
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,12 @@ class NationalDirectDebitConnector @Inject() (config: ServicesConfig, http: Http
.execute[PaymentPlanResponse]
}

def lockPaymentPlan(directDebitReference: String, paymentPlanReference: String)(implicit hc: HeaderCarrier): Future[AmendLockResponse] = {
http
.put(url"$nationalDirectDebitBaseUrl/direct-debits/$directDebitReference/payment-plans/$paymentPlanReference/lock")(hc)
.execute[AmendLockResponse]
}

def isDuplicatePaymentPlan(directDebitReference: String, request: PaymentPlanDuplicateCheckRequest)(implicit
hc: HeaderCarrier
): Future[DuplicateCheckResponse] = {
Expand Down
13 changes: 12 additions & 1 deletion app/controllers/AmendPaymentPlanConfirmationController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,18 @@ class AmendPaymentPlanConfirmationController @Inject() (
nddService.submitChrisData(chrisRequest).flatMap { success =>
if (success) {
logger.info(s"CHRIS submission successful for DDI Ref [$ddiReference]")
Future.successful(Redirect(routes.AmendPaymentPlanUpdateController.onPageLoad()))
for {
directDebitReference <- Future.fromTry(Try(ua.get(DirectDebitReferenceQuery).get))
paymentPlanReference <- Future.fromTry(Try(ua.get(PaymentPlanReferenceQuery).get))
lockResponse <- nddService.lockPaymentPlan(directDebitReference, paymentPlanReference)
} yield {
if (lockResponse.lockSuccessful) {
logger.info(s"Payment plan lock returns: ${lockResponse.lockSuccessful}")
} else {
logger.error(s"Payment plan lock returns: ${lockResponse.lockSuccessful}")
}
Redirect(routes.AmendPaymentPlanUpdateController.onPageLoad())
}
} else {
logger.error(s"CHRIS submission failed for DDI Ref [$ddiReference]")
Future.successful(
Expand Down
17 changes: 14 additions & 3 deletions app/controllers/CancelPaymentPlanController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import views.html.CancelPaymentPlanView

import javax.inject.Inject
import scala.concurrent.{ExecutionContext, Future}
import scala.util.Try

class CancelPaymentPlanController @Inject() (
override val messagesApi: MessagesApi,
Expand Down Expand Up @@ -118,9 +119,19 @@ class CancelPaymentPlanController @Inject() (
logger.info(s"CHRIS Cancel payment plan payload submission successful for DDI Ref [$ddiReference]")

for {
updatedAnswers <- Future.fromTry(ua.set(CancelPaymentPlanPage, value))
_ <- sessionRepository.set(updatedAnswers)
} yield Redirect(navigator.nextPage(CancelPaymentPlanPage, NormalMode, updatedAnswers))
updatedAnswers <- Future.fromTry(ua.set(CancelPaymentPlanPage, value))
directDebitReference <- Future.fromTry(Try(ua.get(DirectDebitReferenceQuery).get))
paymentPlanReference <- Future.fromTry(Try(ua.get(PaymentPlanReferenceQuery).get))
lockResponse <- nddService.lockPaymentPlan(directDebitReference, paymentPlanReference)
_ <- sessionRepository.set(updatedAnswers)
} yield {
if (lockResponse.lockSuccessful) {
logger.info(s"Payment plan lock returns: ${lockResponse.lockSuccessful}")
} else {
logger.error(s"Payment plan lock returns: ${lockResponse.lockSuccessful}")
}
Redirect(navigator.nextPage(CancelPaymentPlanPage, NormalMode, updatedAnswers))
}
case false =>
logger.error(s"CHRIS Cancel plan submission failed for DDI Ref [$ddiReference]")
Future.successful(
Expand Down
25 changes: 25 additions & 0 deletions app/models/responses/AmendLockResponse.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
/*
* Copyright 2025 HM Revenue & Customs
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package models.responses

import play.api.libs.json.*

case class AmendLockResponse(lockSuccessful: Boolean)

object AmendLockResponse {
implicit val format: OFormat[AmendLockResponse] = Json.format
}
5 changes: 5 additions & 0 deletions app/services/NationalDirectDebitService.scala
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,10 @@ class NationalDirectDebitService @Inject() (nddConnector: NationalDirectDebitCon
nddConnector.getPaymentPlanDetails(directDebitReference, paymentPlanReference)
}

def lockPaymentPlan(directDebitReference: String, paymentPlanReference: String)(implicit hc: HeaderCarrier): Future[AmendLockResponse] = {
nddConnector.lockPaymentPlan(directDebitReference, paymentPlanReference)
}

def isDuplicatePaymentPlan(ua: UserAnswers)(implicit hc: HeaderCarrier, request: Request[?]): Future[DuplicateCheckResponse] = {
ua.get(PaymentPlansCountQuery) match {
case Some(count) => {
Expand Down Expand Up @@ -393,4 +397,5 @@ class NationalDirectDebitService @Inject() (nddConnector: NationalDirectDebitCon
).contains(planType)
)
}

}
53 changes: 39 additions & 14 deletions it/test/connectors/NationalDirectDebitConnectorSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -288,28 +288,28 @@ class NationalDirectDebitConnectorSpec extends ApplicationWithWiremock with Matc
)

val submission = ChrisSubmissionRequest(
serviceType = DirectDebitSource.TC,
paymentPlanType = PaymentPlanType.TaxCreditRepaymentPlan,
serviceType = DirectDebitSource.TC,
paymentPlanType = PaymentPlanType.TaxCreditRepaymentPlan,
paymentPlanReferenceNumber = None,
paymentFrequency = Some(PaymentsFrequency.Monthly.toString),
paymentFrequency = Some(PaymentsFrequency.Monthly.toString),
yourBankDetailsWithAuddisStatus = YourBankDetailsWithAuddisStatus(
accountHolderName = "Test",
sortCode = "123456",
accountNumber = "12345678",
auddisStatus = false,
accountVerified = false
),
planStartDate = Some(planStartDateDetails),
planEndDate = None,
paymentDate = Some(paymentDateDetails),
yearEndAndMonth = None,
ddiReferenceNo = "DDI123456789",
paymentReference = "testReference",
totalAmountDue = Some(BigDecimal(200)),
paymentAmount = Some(BigDecimal(100.00)),
planStartDate = Some(planStartDateDetails),
planEndDate = None,
paymentDate = Some(paymentDateDetails),
yearEndAndMonth = None,
ddiReferenceNo = "DDI123456789",
paymentReference = "testReference",
totalAmountDue = Some(BigDecimal(200)),
paymentAmount = Some(BigDecimal(100.00)),
regularPaymentAmount = Some(BigDecimal(90.00)),
amendPaymentAmount = None,
calculation = None
amendPaymentAmount = None,
calculation = None
)

"successfully return true when CHRIS submission succeeds with 200 OK" in {
Expand Down Expand Up @@ -894,7 +894,6 @@ class NationalDirectDebitConnectorSpec extends ApplicationWithWiremock with Matc
}

"successfully retrieve payment plan details when hodService is MGD" in {

val responseJson =
"""
|{
Expand Down Expand Up @@ -941,6 +940,32 @@ class NationalDirectDebitConnectorSpec extends ApplicationWithWiremock with Matc
}
}

"lockPaymentPlan" should {
"successfully lock payment plan" in {

val responseJson =
"""
|{
| "lockSuccessful": true
|}
""".stripMargin

stubFor(
put(urlEqualTo("/national-direct-debit/direct-debits/test-dd-ref/payment-plans/test-pp-ref/lock"))
.willReturn(
aResponse()
.withStatus(OK)
.withBody(responseJson)
)
)

val result = connector.lockPaymentPlan("test-dd-ref", "test-pp-ref").futureValue

result.lockSuccessful shouldBe true

}
}

"isDuplicatePaymentPlan" should {

val currentDate = LocalDate.now()
Expand Down
2 changes: 1 addition & 1 deletion project/AppDependencies.scala
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ object AppDependencies {

val compile: Seq[ModuleID] = Seq(
play.sbt.PlayImport.ws,
"uk.gov.hmrc" %% "play-frontend-hmrc-play-30" % "12.17.0",
"uk.gov.hmrc" %% "play-frontend-hmrc-play-30" % "12.18.0",
"uk.gov.hmrc" %% "bootstrap-frontend-play-30" % bootstrapVersion,
"uk.gov.hmrc.mongo" %% "hmrc-mongo-play-30" % hmrcMongoVersion
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
package controllers

import base.SpecBase
import models.responses.{DirectDebitDetails, PaymentPlanDetails, PaymentPlanResponse}
import models.responses.*
import models.{NormalMode, PaymentPlanType, UserAnswers}
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.when
Expand Down Expand Up @@ -278,6 +278,8 @@ class AmendPaymentPlanConfirmationControllerSpec extends SpecBase with DirectDeb

when(mockNddService.submitChrisData(any())(any[HeaderCarrier]))
.thenReturn(Future.successful(true))
when(mockNddService.lockPaymentPlan(any(), any())(any[HeaderCarrier]))
.thenReturn(Future.successful(AmendLockResponse(lockSuccessful = true)))

val directDebitReference = "DDI123456789"

Expand Down
2 changes: 1 addition & 1 deletion test/controllers/AmendPlanEndDateControllerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ import models.responses.{DirectDebitDetails, PaymentPlanDetails, PaymentPlanResp
import models.{NextPaymentValidationResult, NormalMode, PaymentPlanType}
import models.responses.{DirectDebitDetails, DuplicateCheckResponse, PaymentPlanDetails, PaymentPlanResponse}
import models.{NextPaymentValidationResult, NormalMode}
import models.{NormalMode, PaymentPlanType}
import models.PaymentPlanType
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.when
import org.scalatestplus.mockito.MockitoSugar
Expand Down
3 changes: 3 additions & 0 deletions test/controllers/CancelPaymentPlanControllerSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package controllers
import base.SpecBase
import forms.CancelPaymentPlanFormProvider
import models.PaymentPlanType
import models.responses.*
import navigation.{FakeNavigator, Navigator}
import org.mockito.ArgumentMatchers.any
import org.mockito.Mockito.when
Expand Down Expand Up @@ -240,6 +241,8 @@ class CancelPaymentPlanControllerSpec extends SpecBase with MockitoSugar {

when(mockSessionRepository.set(any())) thenReturn Future.successful(true)
when(mockNddService.submitChrisData(any())(any[HeaderCarrier])) thenReturn Future.successful(true)
when(mockNddService.lockPaymentPlan(any(), any())(any[HeaderCarrier]))
.thenReturn(Future.successful(AmendLockResponse(lockSuccessful = true)))

val onwardRoute = Call("GET", "/foo")

Expand Down
21 changes: 20 additions & 1 deletion test/services/NationalDirectDebitServiceSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,26 @@ class NationalDirectDebitServiceSpec extends SpecBase with MockitoSugar with Dir
}
}

"lockPaymentPlan" - {
"must successfully return Ok" in {
when(mockConnector.lockPaymentPlan(any(), any())(any()))
.thenReturn(Future.successful(AmendLockResponse(lockSuccessful = true)))

val result = service.lockPaymentPlan("test-dd-ref", "test-pp-ref").futureValue

result mustBe AmendLockResponse(lockSuccessful = true)
}

"fail when the connector call fails" in {
when(mockConnector.lockPaymentPlan(any(), any())(any()))
.thenReturn(Future.failed(new Exception("bang")))

val result = intercept[Exception](service.lockPaymentPlan("test-dd-ref", "test-pp-ref").futureValue)

result.getMessage must include("bang")
}
}

"isDuplicatePaymentPlan" - {

"return true when count is more than 1 payment plan and it is single payment plan so connector returns true" in {
Expand Down Expand Up @@ -798,7 +818,6 @@ class NationalDirectDebitServiceSpec extends SpecBase with MockitoSugar with Dir

result.nextPaymentDateValid mustBe true
}

}

"isPaymentPlanCancellable" - {
Expand Down