From 304d2fe6b9de8bccac76a5f88851863625238e34 Mon Sep 17 00:00:00 2001 From: tvr-solirius Date: Wed, 1 Apr 2026 10:23:30 +0100 Subject: [PATCH 1/4] Add fee payment persistence, status tracking, and callback handling - Create `PaymentStatus` enum - Add `FeePaymentEntity` and `FeePaymentRepository` - Add migration `V075__fee_payment.sql` - Extend `PaymentService` to save new fee payments and update status on callback - Implement `PaymentCallBackController` for payment updates - Add `ServiceRequestUpdate` DTO - Update tests for new functionality --- .../entity/feesandpay/FeePaymentEntity.java | 68 +++++++++++++++++++ .../feeandpay/FeePaymentRepository.java | 13 ++++ .../endpoint/PaymentCallBackController.java | 42 ++++++++++++ .../pcs/feesandpay/model/PaymentStatus.java | 11 +++ .../model/ServiceRequestUpdate.java | 27 ++++++++ .../feesandpay/service/PaymentService.java | 60 +++++++++++++++- .../task/FeesAndPayTaskComponent.java | 1 + .../db/migration/V075__fee_payment.sql | 14 ++++ .../service/PaymentServiceTest.java | 38 +++++++++-- 9 files changed, 269 insertions(+), 5 deletions(-) create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/feesandpay/FeePaymentEntity.java create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/ccd/repository/feeandpay/FeePaymentRepository.java create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/PaymentStatus.java create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/ServiceRequestUpdate.java create mode 100644 src/main/resources/db/migration/V075__fee_payment.sql diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/feesandpay/FeePaymentEntity.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/feesandpay/FeePaymentEntity.java new file mode 100644 index 0000000000..76610e8152 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/feesandpay/FeePaymentEntity.java @@ -0,0 +1,68 @@ +package uk.gov.hmcts.reform.pcs.ccd.entity.feesandpay; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.hibernate.annotations.CreationTimestamp; +import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyEntity; +import uk.gov.hmcts.reform.pcs.feesandpay.model.PaymentStatus; + +import java.math.BigDecimal; +import java.time.LocalDateTime; +import java.util.UUID; + +import static jakarta.persistence.FetchType.LAZY; + +@Entity +@Builder +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Table(name = "fee_payment") +public class FeePaymentEntity { + + @Id + @GeneratedValue(strategy = GenerationType.UUID) + private UUID id; + + @ManyToOne(fetch = LAZY) + @JoinColumn(name = "claim_id", nullable = false) + @JsonBackReference + private ClaimEntity claim; + + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn(name = "party_id", nullable = false) + @JsonBackReference + private PartyEntity party; + + @CreationTimestamp + @Column(updatable = false, nullable = false) + private LocalDateTime requestDate; + + // Service Request Reference from the createRequest + private String requestReference; + + private String externalReference; + + private BigDecimal amount; + + @Enumerated(EnumType.STRING) + private PaymentStatus paymentStatus; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/repository/feeandpay/FeePaymentRepository.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/repository/feeandpay/FeePaymentRepository.java new file mode 100644 index 0000000000..9edfdf66b0 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/repository/feeandpay/FeePaymentRepository.java @@ -0,0 +1,13 @@ +package uk.gov.hmcts.reform.pcs.ccd.repository.feeandpay; + +import org.springframework.data.jpa.repository.JpaRepository; +import uk.gov.hmcts.reform.pcs.ccd.entity.feesandpay.FeePaymentEntity; + +import java.util.Optional; +import java.util.UUID; + +public interface FeePaymentRepository extends JpaRepository { + + Optional findByRequestReference(String requestReference); + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java new file mode 100644 index 0000000000..d82780739f --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java @@ -0,0 +1,42 @@ +package uk.gov.hmcts.reform.pcs.feesandpay.endpoint; + +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import lombok.AllArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RestController; +import uk.gov.hmcts.reform.pcs.feesandpay.model.ServiceRequestUpdate; +import uk.gov.hmcts.reform.pcs.feesandpay.service.PaymentService; + +import static com.azure.core.http.ContentType.APPLICATION_JSON; + +@RestController +@AllArgsConstructor +public class PaymentCallBackController { + + private final PaymentService paymentService; + + @PostMapping(path = "/service-request-update", consumes = APPLICATION_JSON, produces = APPLICATION_JSON) + @Operation(description = "Callback to create Fee and Pay service request") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "Callback processed.", + content = @Content(mediaType = "application/json", + schema = @Schema(implementation = uk.gov.hmcts.reform.ccd.client.model.CallbackResponse.class))), + @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content)}) + public void ccdSubmitted( + @RequestHeader(HttpHeaders.AUTHORIZATION) @Parameter(hidden = true) String authorisation, + @RequestHeader("ServiceAuthorization") String s2sToken, + @RequestBody ServiceRequestUpdate serviceRequestUpdate) { + + paymentService.processPaymentResponse(serviceRequestUpdate); + + } + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/PaymentStatus.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/PaymentStatus.java new file mode 100644 index 0000000000..645eec4618 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/PaymentStatus.java @@ -0,0 +1,11 @@ +package uk.gov.hmcts.reform.pcs.feesandpay.model; + +public enum PaymentStatus { + + PENDING, + SUCCESS, + CANCELLED, + FAILED, + ERROR + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/ServiceRequestUpdate.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/ServiceRequestUpdate.java new file mode 100644 index 0000000000..9744c4e5a1 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/ServiceRequestUpdate.java @@ -0,0 +1,27 @@ +package uk.gov.hmcts.reform.pcs.feesandpay.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import uk.gov.hmcts.reform.payments.client.models.PaymentDto; + +@Data +@Builder +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class ServiceRequestUpdate { + + @JsonProperty("service_request_reference") + private String serviceRequestReference; + @JsonProperty("ccd_case_number") + private String ccdCaseNumber; + @JsonProperty("service_request_amount") + private String serviceRequestAmount; + @JsonProperty("service_request_status") + private String serviceRequestStatus; + @JsonProperty("payment") + private PaymentDto payment; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java index fb50931a63..4c0faa922a 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java @@ -9,10 +9,23 @@ import uk.gov.hmcts.reform.payments.client.models.FeeDto; import uk.gov.hmcts.reform.payments.request.CreateServiceRequestDTO; import uk.gov.hmcts.reform.payments.response.PaymentServiceResponse; +import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.feesandpay.FeePaymentEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.party.ClaimPartyEntity; +import uk.gov.hmcts.reform.pcs.ccd.repository.feeandpay.FeePaymentRepository; +import uk.gov.hmcts.reform.pcs.ccd.service.PcsCaseService; import uk.gov.hmcts.reform.pcs.feesandpay.mapper.PaymentRequestMapper; import uk.gov.hmcts.reform.pcs.feesandpay.model.FeeDetails; +import uk.gov.hmcts.reform.pcs.feesandpay.model.PaymentStatus; +import uk.gov.hmcts.reform.pcs.feesandpay.model.ServiceRequestUpdate; import uk.gov.hmcts.reform.pcs.idam.IdamService; +import java.time.LocalDateTime; +import java.util.Optional; + +import static uk.gov.hmcts.reform.pcs.feesandpay.model.PaymentStatus.PENDING; + @Slf4j @Service @RequiredArgsConstructor @@ -21,6 +34,8 @@ public class PaymentService { private final PaymentsClient paymentsClient; private final PaymentRequestMapper paymentRequestMapper; private final IdamService idamService; + private final FeePaymentRepository feePaymentRepository; + private final PcsCaseService pcsCaseService; @Value("${payments.api.callback-url}") private String callbackUrl; @@ -65,9 +80,52 @@ public PaymentServiceResponse createServiceRequest( .hmctsOrgId(hmctsOrgId) .build(); - return paymentsClient.createServiceRequest( + PaymentServiceResponse paymentServiceResponse = paymentsClient.createServiceRequest( idamService.getSystemUserAuthorisation(), requestDto ); + + saveNewFeePayment(caseReference, feeDto, paymentServiceResponse.getServiceRequestReference(), responsibleParty); + + return paymentServiceResponse; + } + + public void saveNewFeePayment(String caseReference, FeeDto feeDto, String serviceRequestReference, + String responsibleParty) { + ClaimEntity claimEntity = retrieveClaimEntity(caseReference); + ClaimPartyEntity claimParty = claimEntity.getClaimParties() + .stream() + .filter(party -> party.getParty().getOrgName().equals(responsibleParty)) + .findFirst() + .orElseThrow(() -> new IllegalStateException("Matching PartyEntity not found")); + + FeePaymentEntity feePaymentEntity = FeePaymentEntity.builder() + .claim(claimEntity) + .requestDate(LocalDateTime.now()) + .requestReference(serviceRequestReference) + .amount(feeDto.getCalculatedAmount()) + .paymentStatus(PENDING) + .party(claimParty.getParty()) + .build(); + + feePaymentRepository.save(feePaymentEntity); + } + + private ClaimEntity retrieveClaimEntity(String caseReference) { + PcsCaseEntity pcsCaseEntity = pcsCaseService.loadCase(Long.parseLong(caseReference)); + // Assuming 1 claim per PcsCase + return pcsCaseEntity.getClaims().getFirst(); } + + public void processPaymentResponse(ServiceRequestUpdate serviceRequestUpdate) { + log.info("ServiceRequestUpdate: {}", serviceRequestUpdate); + Optional byCaseReference = feePaymentRepository + .findByRequestReference(serviceRequestUpdate.getServiceRequestReference()); + if (byCaseReference.isPresent()) { + FeePaymentEntity feePaymentEntity = byCaseReference.get(); + feePaymentEntity.setPaymentStatus(PaymentStatus.valueOf(serviceRequestUpdate.getServiceRequestStatus())); + feePaymentRepository.save(feePaymentEntity); + } + } + } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/task/FeesAndPayTaskComponent.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/task/FeesAndPayTaskComponent.java index 2feae00d32..f301ef0697 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/task/FeesAndPayTaskComponent.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/task/FeesAndPayTaskComponent.java @@ -83,4 +83,5 @@ public CustomTask feePaymentTask() { } }); } + } diff --git a/src/main/resources/db/migration/V075__fee_payment.sql b/src/main/resources/db/migration/V075__fee_payment.sql new file mode 100644 index 0000000000..1e7cb8b0f7 --- /dev/null +++ b/src/main/resources/db/migration/V075__fee_payment.sql @@ -0,0 +1,14 @@ +CREATE TABLE fee_payment ( + id UUID NOT NULL, + claim_id UUID NOT NULL, + party_id UUID NOT NULL, + request_date TIMESTAMP NOT NULL, + request_reference VARCHAR(255), + external_reference VARCHAR(255), + amount NUMERIC(19, 2), + payment_status VARCHAR(50), + + CONSTRAINT pk_fee_payment PRIMARY KEY (id), + CONSTRAINT fk_fee_payment_claim FOREIGN KEY (claim_id) REFERENCES claim (id), + CONSTRAINT fk_fee_payment_party FOREIGN KEY (party_id) REFERENCES party (id) +); diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java index 5db46b76db..54fb45e9c2 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java @@ -13,11 +13,17 @@ import uk.gov.hmcts.reform.payments.client.models.FeeDto; import uk.gov.hmcts.reform.payments.request.CreateServiceRequestDTO; import uk.gov.hmcts.reform.payments.response.PaymentServiceResponse; +import uk.gov.hmcts.reform.pcs.ccd.entity.feesandpay.FeePaymentEntity; +import uk.gov.hmcts.reform.pcs.ccd.repository.feeandpay.FeePaymentRepository; import uk.gov.hmcts.reform.pcs.feesandpay.mapper.PaymentRequestMapper; import uk.gov.hmcts.reform.pcs.feesandpay.model.FeeDetails; +import uk.gov.hmcts.reform.pcs.feesandpay.model.PaymentStatus; +import uk.gov.hmcts.reform.pcs.feesandpay.model.ServiceRequestUpdate; import uk.gov.hmcts.reform.pcs.idam.IdamService; import java.math.BigDecimal; +import java.util.Optional; +import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -38,8 +44,11 @@ class PaymentServiceTest { @Mock private IdamService idamService; + @Mock + private FeePaymentRepository feePaymentRepository; + @InjectMocks - private PaymentService paymentService; + private PaymentService underTest; @Captor private ArgumentCaptor createServiceRequestCaptor; @@ -48,11 +57,11 @@ class PaymentServiceTest { void setUp() throws Exception { var callbackUrlField = PaymentService.class.getDeclaredField("callbackUrl"); callbackUrlField.setAccessible(true); - callbackUrlField.set(paymentService, "https://callback"); + callbackUrlField.set(underTest, "https://callback"); var hmctsOrgIdField = PaymentService.class.getDeclaredField("hmctsOrgId"); hmctsOrgIdField.setAccessible(true); - hmctsOrgIdField.set(paymentService, "TEST_ORG"); + hmctsOrgIdField.set(underTest, "TEST_ORG"); } @Test @@ -87,7 +96,7 @@ void shouldCreateServiceRequestSuccessfully() { when(paymentsClient.createServiceRequest(eq(systemToken), any(CreateServiceRequestDTO.class))) .thenReturn(paymentResponse); - PaymentServiceResponse result = paymentService.createServiceRequest( + PaymentServiceResponse result = underTest.createServiceRequest( caseReference, ccdCaseNumber, feeDetails, @@ -109,4 +118,25 @@ void shouldCreateServiceRequestSuccessfully() { assertThat(sent.getFees()[0]).isEqualTo(mappedFee); assertThat(sent.getCasePaymentRequest()).isEqualTo(casePaymentRequestDto); } + + @Test + void shouldProcessPaymentResponse() { + // Given + String requestReference = UUID.randomUUID().toString(); + ServiceRequestUpdate serviceRequestUpdate = ServiceRequestUpdate.builder() + .serviceRequestReference(requestReference) + .serviceRequestStatus(PaymentStatus.SUCCESS.name()) + .build(); + FeePaymentEntity feePaymentEntity = FeePaymentEntity.builder().build(); + when(feePaymentRepository.findByRequestReference(requestReference)).thenReturn(Optional.of(feePaymentEntity)); + + // When + underTest.processPaymentResponse(serviceRequestUpdate); + + // Then + verify(feePaymentRepository).findByRequestReference(requestReference); + verify(feePaymentRepository).save(any(FeePaymentEntity.class)); + } + + } From 9ab370fb56f9b4b04db5cd2a7ecc0543e05223b9 Mon Sep 17 00:00:00 2001 From: tvr-solirius Date: Wed, 1 Apr 2026 13:56:56 +0100 Subject: [PATCH 2/4] Refactor PaymentService to use ClaimEntity and ClaimPartyEntity for request creation and simplify method signatures. Remove duplicate callback handler and add helper for party lookup. Ensure new logic persists fee payments using claim data. --- .../feesandpay/service/PaymentService.java | 53 +++-- .../service/PaymentServiceTest.java | 193 ++++++++++++------ 2 files changed, 158 insertions(+), 88 deletions(-) diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java index 4c0faa922a..4ab80a294a 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java @@ -59,17 +59,12 @@ public class PaymentService { * @param responsibleParty the party responsible for the payment * @return {@link PaymentServiceResponse} containing the service request reference */ - public PaymentServiceResponse createServiceRequest( - String caseReference, - String ccdCaseNumber, - FeeDetails feeDetails, - int volume, - String responsibleParty - ) { + public PaymentServiceResponse createServiceRequest(String caseReference, String ccdCaseNumber, + FeeDetails feeDetails, int volume, String responsibleParty) { + ClaimEntity claimEntity = retrieveClaimEntity(caseReference); + ClaimPartyEntity claimPartyEntity = retrieveClaimPartyEntity(claimEntity, responsibleParty); FeeDto feeDto = paymentRequestMapper.toFeeDto(feeDetails, volume); - - CasePaymentRequestDto casePaymentRequest = - paymentRequestMapper.toCasePaymentRequest(responsibleParty); + CasePaymentRequestDto casePaymentRequest = paymentRequestMapper.toCasePaymentRequest(responsibleParty); CreateServiceRequestDTO requestDto = CreateServiceRequestDTO.builder() .callBackUrl(callbackUrl) @@ -81,24 +76,34 @@ public PaymentServiceResponse createServiceRequest( .build(); PaymentServiceResponse paymentServiceResponse = paymentsClient.createServiceRequest( - idamService.getSystemUserAuthorisation(), - requestDto - ); + idamService.getSystemUserAuthorisation(), requestDto); - saveNewFeePayment(caseReference, feeDto, paymentServiceResponse.getServiceRequestReference(), responsibleParty); + saveNewFeePayment(claimEntity, claimPartyEntity, feeDto, paymentServiceResponse.getServiceRequestReference()); return paymentServiceResponse; } - public void saveNewFeePayment(String caseReference, FeeDto feeDto, String serviceRequestReference, - String responsibleParty) { - ClaimEntity claimEntity = retrieveClaimEntity(caseReference); - ClaimPartyEntity claimParty = claimEntity.getClaimParties() + public void processPaymentResponse(ServiceRequestUpdate serviceRequestUpdate) { + log.info("ServiceRequestUpdate: {}", serviceRequestUpdate); + Optional byCaseReference = feePaymentRepository + .findByRequestReference(serviceRequestUpdate.getServiceRequestReference()); + if (byCaseReference.isPresent()) { + FeePaymentEntity feePaymentEntity = byCaseReference.get(); + feePaymentEntity.setPaymentStatus(PaymentStatus.valueOf(serviceRequestUpdate.getServiceRequestStatus())); + feePaymentRepository.save(feePaymentEntity); + } + } + + private ClaimPartyEntity retrieveClaimPartyEntity(ClaimEntity claimEntity, String responsibleParty) { + return claimEntity.getClaimParties() .stream() .filter(party -> party.getParty().getOrgName().equals(responsibleParty)) .findFirst() .orElseThrow(() -> new IllegalStateException("Matching PartyEntity not found")); + } + private void saveNewFeePayment(ClaimEntity claimEntity, ClaimPartyEntity claimParty, FeeDto feeDto, + String serviceRequestReference) { FeePaymentEntity feePaymentEntity = FeePaymentEntity.builder() .claim(claimEntity) .requestDate(LocalDateTime.now()) @@ -107,7 +112,6 @@ public void saveNewFeePayment(String caseReference, FeeDto feeDto, String servic .paymentStatus(PENDING) .party(claimParty.getParty()) .build(); - feePaymentRepository.save(feePaymentEntity); } @@ -117,15 +121,4 @@ private ClaimEntity retrieveClaimEntity(String caseReference) { return pcsCaseEntity.getClaims().getFirst(); } - public void processPaymentResponse(ServiceRequestUpdate serviceRequestUpdate) { - log.info("ServiceRequestUpdate: {}", serviceRequestUpdate); - Optional byCaseReference = feePaymentRepository - .findByRequestReference(serviceRequestUpdate.getServiceRequestReference()); - if (byCaseReference.isPresent()) { - FeePaymentEntity feePaymentEntity = byCaseReference.get(); - feePaymentEntity.setPaymentStatus(PaymentStatus.valueOf(serviceRequestUpdate.getServiceRequestStatus())); - feePaymentRepository.save(feePaymentEntity); - } - } - } diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java index 54fb45e9c2..21e9c688fa 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java @@ -13,8 +13,14 @@ import uk.gov.hmcts.reform.payments.client.models.FeeDto; import uk.gov.hmcts.reform.payments.request.CreateServiceRequestDTO; import uk.gov.hmcts.reform.payments.response.PaymentServiceResponse; +import uk.gov.hmcts.reform.pcs.ccd.entity.ClaimEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.PcsCaseEntity; import uk.gov.hmcts.reform.pcs.ccd.entity.feesandpay.FeePaymentEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.party.ClaimPartyEntity; +import uk.gov.hmcts.reform.pcs.ccd.entity.party.PartyEntity; import uk.gov.hmcts.reform.pcs.ccd.repository.feeandpay.FeePaymentRepository; +import uk.gov.hmcts.reform.pcs.ccd.service.PcsCaseService; +import uk.gov.hmcts.reform.pcs.exception.CaseNotFoundException; import uk.gov.hmcts.reform.pcs.feesandpay.mapper.PaymentRequestMapper; import uk.gov.hmcts.reform.pcs.feesandpay.model.FeeDetails; import uk.gov.hmcts.reform.pcs.feesandpay.model.PaymentStatus; @@ -22,11 +28,14 @@ import uk.gov.hmcts.reform.pcs.idam.IdamService; import java.math.BigDecimal; +import java.util.List; import java.util.Optional; import java.util.UUID; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyLong; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.verify; @@ -35,17 +44,28 @@ @ExtendWith(MockitoExtension.class) class PaymentServiceTest { + private static final long CASE_REFERENCE = 123L; + private static final String CCD_CASE_NUMBER = "1111-2222-3333-4444"; + private static final int VOLUME = 2; + private static final String RESPONSIBLE_PARTY = "Applicant"; + private static final String SYSTEM_TOKEN = "Bearer sys-token"; + private static final BigDecimal CALCULATED_AMOUNT = new BigDecimal("808.00"); + private static final String FEE_CODE = "FEE0412"; + private static final String FEE_VERSION = "4"; + private static final String SERVICE_REQUEST_REFERENCE = "SR-123"; + private static final String CALLBACK_URL = "https://etc:123/service-request-update"; + private static final String HMCTS_ORG_ID = "TEST_ORG"; + @Mock private PaymentsClient paymentsClient; - @Mock private PaymentRequestMapper paymentRequestMapper; - @Mock private IdamService idamService; - @Mock private FeePaymentRepository feePaymentRepository; + @Mock + private PcsCaseService pcsCaseService; @InjectMocks private PaymentService underTest; @@ -54,69 +74,60 @@ class PaymentServiceTest { private ArgumentCaptor createServiceRequestCaptor; @BeforeEach - void setUp() throws Exception { - var callbackUrlField = PaymentService.class.getDeclaredField("callbackUrl"); - callbackUrlField.setAccessible(true); - callbackUrlField.set(underTest, "https://callback"); - - var hmctsOrgIdField = PaymentService.class.getDeclaredField("hmctsOrgId"); - hmctsOrgIdField.setAccessible(true); - hmctsOrgIdField.set(underTest, "TEST_ORG"); + void setUp() { + setPrivateField(underTest, "callbackUrl", CALLBACK_URL); + setPrivateField(underTest, "hmctsOrgId", HMCTS_ORG_ID); } @Test void shouldCreateServiceRequestSuccessfully() { - String caseReference = "BUS-123"; - String ccdCaseNumber = "1111-2222-3333-4444"; - int volume = 2; - String responsibleParty = "Applicant"; - String systemToken = "Bearer sys-token"; - - FeeDto mappedFee = FeeDto.builder() - .calculatedAmount(new BigDecimal("808.00")) - .code("FEE0412") - .version("4") - .volume(volume) - .build(); + // Given + FeeDetails feeDetails = mock(FeeDetails.class); + paymentsClientDependencies(feeDetails); + ClaimPartyEntity claimPartyEntity = claimPartyEntity(); + PcsCaseEntity pcsCaseEntity = setupPcsCase(claimPartyEntity); + when(pcsCaseService.loadCase(CASE_REFERENCE)).thenReturn(pcsCaseEntity); + PaymentServiceResponse expectedResponse = createPaymentServiceResponse(); + when(paymentsClient.createServiceRequest(any(), any(CreateServiceRequestDTO.class))) + .thenReturn(expectedResponse); - CasePaymentRequestDto casePaymentRequestDto = CasePaymentRequestDto.builder() - .action("payment") - .responsibleParty(responsibleParty) - .build(); + // When + PaymentServiceResponse result = underTest.createServiceRequest(String.valueOf(CASE_REFERENCE), CCD_CASE_NUMBER, + feeDetails, VOLUME, RESPONSIBLE_PARTY); - PaymentServiceResponse paymentResponse = PaymentServiceResponse.builder() - .serviceRequestReference("SR-123") - .build(); + // Then + assertServiceRequestCreation(result); + } + @Test + void shouldCreateServiceRequest_NoPCSCase() { + // Given FeeDetails feeDetails = mock(FeeDetails.class); - when(paymentRequestMapper.toFeeDto(feeDetails, volume)).thenReturn(mappedFee); - when(paymentRequestMapper.toCasePaymentRequest(responsibleParty)) - .thenReturn(casePaymentRequestDto); - when(idamService.getSystemUserAuthorisation()).thenReturn(systemToken); - when(paymentsClient.createServiceRequest(eq(systemToken), any(CreateServiceRequestDTO.class))) - .thenReturn(paymentResponse); - - PaymentServiceResponse result = underTest.createServiceRequest( - caseReference, - ccdCaseNumber, - feeDetails, - volume, - responsibleParty - ); + when(pcsCaseService.loadCase(anyLong())).thenThrow(new CaseNotFoundException(222L)); - assertThat(result).isNotNull(); - assertThat(result.getServiceRequestReference()).isEqualTo("SR-123"); + // When + assertThatThrownBy(() -> underTest.createServiceRequest("222", CCD_CASE_NUMBER, + feeDetails, VOLUME, RESPONSIBLE_PARTY)) + .isInstanceOf(CaseNotFoundException.class); + } + + @Test + void shouldCreateServiceRequest_NoClaim() { + // Given + FeeDetails feeDetails = mock(FeeDetails.class); + ClaimPartyEntity claimPartyEntity = mock(ClaimPartyEntity.class); + PartyEntity partyEntity = mock(PartyEntity.class); + when(partyEntity.getOrgName()).thenReturn("different"); + when(claimPartyEntity.getParty()).thenReturn(partyEntity); + PcsCaseEntity pcsCaseEntity = setupPcsCase(claimPartyEntity); + when(pcsCaseService.loadCase(CASE_REFERENCE)).thenReturn(pcsCaseEntity); + + // When ... Then + assertThatThrownBy(() -> underTest.createServiceRequest(String.valueOf(CASE_REFERENCE), CCD_CASE_NUMBER, + feeDetails, VOLUME, RESPONSIBLE_PARTY)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Matching PartyEntity not found"); - verify(paymentsClient).createServiceRequest(eq(systemToken), createServiceRequestCaptor.capture()); - CreateServiceRequestDTO sent = createServiceRequestCaptor.getValue(); - assertThat(sent.getCallBackUrl()).isEqualTo("https://callback"); - assertThat(sent.getHmctsOrgId()).isEqualTo("TEST_ORG"); - assertThat(sent.getCaseReference()).isEqualTo(caseReference); - assertThat(sent.getCcdCaseNumber()).isEqualTo(ccdCaseNumber); - assertThat(sent.getFees()).isNotNull(); - assertThat(sent.getFees()).hasSize(1); - assertThat(sent.getFees()[0]).isEqualTo(mappedFee); - assertThat(sent.getCasePaymentRequest()).isEqualTo(casePaymentRequestDto); } @Test @@ -124,8 +135,7 @@ void shouldProcessPaymentResponse() { // Given String requestReference = UUID.randomUUID().toString(); ServiceRequestUpdate serviceRequestUpdate = ServiceRequestUpdate.builder() - .serviceRequestReference(requestReference) - .serviceRequestStatus(PaymentStatus.SUCCESS.name()) + .serviceRequestReference(requestReference).serviceRequestStatus(PaymentStatus.SUCCESS.name()) .build(); FeePaymentEntity feePaymentEntity = FeePaymentEntity.builder().build(); when(feePaymentRepository.findByRequestReference(requestReference)).thenReturn(Optional.of(feePaymentEntity)); @@ -138,5 +148,72 @@ void shouldProcessPaymentResponse() { verify(feePaymentRepository).save(any(FeePaymentEntity.class)); } + private void paymentsClientDependencies(FeeDetails feeDetails) { + FeeDto mappedFee = createFeeDto(); + CasePaymentRequestDto casePaymentRequestDto = createCasePaymentRequestDto(); + when(paymentRequestMapper.toFeeDto(feeDetails, VOLUME)).thenReturn(mappedFee); + when(paymentRequestMapper.toCasePaymentRequest(RESPONSIBLE_PARTY)) + .thenReturn(casePaymentRequestDto); + when(idamService.getSystemUserAuthorisation()).thenReturn(SYSTEM_TOKEN); + } + + private PcsCaseEntity setupPcsCase(ClaimPartyEntity claimPartyEntity) { + ClaimEntity claimEntity = mock(ClaimEntity.class); + when(claimEntity.getClaimParties()).thenReturn(List.of(claimPartyEntity)); + PcsCaseEntity pcsCaseEntity = mock(PcsCaseEntity.class); + when(pcsCaseEntity.getClaims()).thenReturn(List.of(claimEntity)); + return pcsCaseEntity; + } + + private ClaimPartyEntity claimPartyEntity() { + ClaimPartyEntity claimPartyEntity = mock(ClaimPartyEntity.class); + PartyEntity partyEntity = mock(PartyEntity.class); + when(partyEntity.getOrgName()).thenReturn(RESPONSIBLE_PARTY); + when(claimPartyEntity.getParty()).thenReturn(partyEntity); + return claimPartyEntity; + } + + private CasePaymentRequestDto createCasePaymentRequestDto() { + return CasePaymentRequestDto.builder().action("payment").responsibleParty(RESPONSIBLE_PARTY).build(); + } + + private PaymentServiceResponse createPaymentServiceResponse() { + return PaymentServiceResponse.builder().serviceRequestReference(SERVICE_REQUEST_REFERENCE).build(); + } + + private FeeDto createFeeDto() { + return FeeDto.builder().calculatedAmount(CALCULATED_AMOUNT).code(FEE_CODE).version(FEE_VERSION) + .volume(VOLUME).build(); + } + + private void assertServiceRequestCreation(PaymentServiceResponse result) { + assertThat(result).isNotNull(); + assertThat(result.getServiceRequestReference()).isEqualTo(SERVICE_REQUEST_REFERENCE); + + verify(paymentsClient).createServiceRequest(eq(SYSTEM_TOKEN), createServiceRequestCaptor.capture()); + CreateServiceRequestDTO sent = createServiceRequestCaptor.getValue(); + + assertCreateServiceRequestDTO(sent); + } + + private void assertCreateServiceRequestDTO(CreateServiceRequestDTO sent) { + assertThat(sent.getCallBackUrl()).isEqualTo(CALLBACK_URL); + assertThat(sent.getHmctsOrgId()).isEqualTo(HMCTS_ORG_ID); + assertThat(sent.getCaseReference()).isEqualTo(String.valueOf(CASE_REFERENCE)); + assertThat(sent.getCcdCaseNumber()).isEqualTo(CCD_CASE_NUMBER); + assertThat(sent.getFees()).isNotNull(); + assertThat(sent.getFees()).hasSize(1); + assertThat(sent.getFees()[0]).isEqualTo(createFeeDto()); + } + + private static void setPrivateField(T object, String fieldName, Object value) { + try { + var field = PaymentService.class.getDeclaredField(fieldName); + field.setAccessible(true); + field.set(object, value); + } catch (Exception e) { + throw new RuntimeException("Failed to set private field", e); + } + } } From 93f780ed71f7cbf9bcc109a9d7323a70b4016daa Mon Sep 17 00:00:00 2001 From: tvr-solirius Date: Thu, 2 Apr 2026 09:56:59 +0100 Subject: [PATCH 3/4] Add local WireMock service config and mappings, streamline PaymentCallBackController, and update logging and test names. --- .../TestSupportEnvironment.java | 1 + .../endpoint/PaymentCallBackController.java | 11 +--- .../feesandpay/service/PaymentService.java | 2 +- .../service/PaymentServiceTest.java | 2 +- .../wiremock/mappings/default-response.json | 13 ++++ .../wiremock/mappings/payment-success.json | 61 +++++++++++++++++++ wiremock-local.yml | 9 +++ 7 files changed, 87 insertions(+), 12 deletions(-) create mode 100644 src/test/resources/wiremock/mappings/default-response.json create mode 100644 src/test/resources/wiremock/mappings/payment-success.json create mode 100644 wiremock-local.yml diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/testcasesupport/TestSupportEnvironment.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/testcasesupport/TestSupportEnvironment.java index 908442db38..8dea559fa2 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/testcasesupport/TestSupportEnvironment.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/testcasesupport/TestSupportEnvironment.java @@ -32,5 +32,6 @@ private static boolean isStubEnvironment(String value) { String lower = value.toLowerCase(Locale.UK); return lower.contains("dev") || lower.contains("preview") || lower.contains("aat"); } + } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java index d82780739f..7f82fcc69e 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java @@ -2,10 +2,6 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; -import io.swagger.v3.oas.annotations.media.Content; -import io.swagger.v3.oas.annotations.media.Schema; -import io.swagger.v3.oas.annotations.responses.ApiResponse; -import io.swagger.v3.oas.annotations.responses.ApiResponses; import lombok.AllArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.PostMapping; @@ -23,13 +19,8 @@ public class PaymentCallBackController { private final PaymentService paymentService; - @PostMapping(path = "/service-request-update", consumes = APPLICATION_JSON, produces = APPLICATION_JSON) + @PostMapping(path = "/service-request-update", consumes = APPLICATION_JSON) @Operation(description = "Callback to create Fee and Pay service request") - @ApiResponses(value = { - @ApiResponse(responseCode = "200", description = "Callback processed.", - content = @Content(mediaType = "application/json", - schema = @Schema(implementation = uk.gov.hmcts.reform.ccd.client.model.CallbackResponse.class))), - @ApiResponse(responseCode = "400", description = "Bad Request", content = @Content)}) public void ccdSubmitted( @RequestHeader(HttpHeaders.AUTHORIZATION) @Parameter(hidden = true) String authorisation, @RequestHeader("ServiceAuthorization") String s2sToken, diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java index 4ab80a294a..20b1201072 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java @@ -84,7 +84,7 @@ public PaymentServiceResponse createServiceRequest(String caseReference, String } public void processPaymentResponse(ServiceRequestUpdate serviceRequestUpdate) { - log.info("ServiceRequestUpdate: {}", serviceRequestUpdate); + log.info("ServiceRequestUpdate status: {}", serviceRequestUpdate.getServiceRequestStatus()); Optional byCaseReference = feePaymentRepository .findByRequestReference(serviceRequestUpdate.getServiceRequestReference()); if (byCaseReference.isPresent()) { diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java index 21e9c688fa..f345211d6e 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java @@ -112,7 +112,7 @@ void shouldCreateServiceRequest_NoPCSCase() { } @Test - void shouldCreateServiceRequest_NoClaim() { + void shouldCreateServiceRequest_NoClaimPartyEntityFound() { // Given FeeDetails feeDetails = mock(FeeDetails.class); ClaimPartyEntity claimPartyEntity = mock(ClaimPartyEntity.class); diff --git a/src/test/resources/wiremock/mappings/default-response.json b/src/test/resources/wiremock/mappings/default-response.json new file mode 100644 index 0000000000..dcc0c92ce3 --- /dev/null +++ b/src/test/resources/wiremock/mappings/default-response.json @@ -0,0 +1,13 @@ +{ + "request": { + "method": "ANY", + "urlPathPattern": "/.*" + }, + "response": { + "status": 200, + "body": "{\"message\": \"Local Wiremock service is running\"}", + "headers": { + "Content-Type": "application/json" + } + } +} diff --git a/src/test/resources/wiremock/mappings/payment-success.json b/src/test/resources/wiremock/mappings/payment-success.json new file mode 100644 index 0000000000..b66bd9e2bd --- /dev/null +++ b/src/test/resources/wiremock/mappings/payment-success.json @@ -0,0 +1,61 @@ +{ + "request": { + "method": "POST", + "urlPath": "/api/payment", + "bodyPatterns": [ + { + "matchesJsonPath": "$.call_back_url" + }, + { + "matchesJsonPath": "$.ccd_case_number" + }, + { + "matchesJsonPath": "$.case_reference" + } + ] + }, + "response": { + "status": 201, + "headers": { + "Content-Type": "application/json" + }, + "jsonBody": { + "service_request_reference": "SR-{{randomValue length=13 type='NUMERIC'}}", + "ccd_case_number": "{{jsonPath request.body '$.ccd_case_number'}}", + "service_request_amount": 2500, + "service_request_status": "Paid", + "payment": { + "payment_amount": 2500, + "payment_reference": "RC-{{date format='yyyy'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}", + "payment_method": "payment by account", + "case_reference": "{{jsonPath request.body '$.case_reference'}}", + "account_number": "PBA{{randomValue length=7 type='NUMERIC'}}" + } + } + }, + "serveEventListeners": [ + { + "name": "webhook", + "parameters": { + "method": "POST", + "url": "{{jsonPath request.body '$.call_back_url'}}", + "headers": { + "Content-Type": "application/json" + }, + "body": { + "service_request_reference": "SR-{{randomValue length=13 type='NUMERIC'}}", + "ccd_case_number": "{{jsonPath originalRequest.body '$.ccd_case_number'}}", + "service_request_amount": 2500, + "service_request_status": "Paid", + "payment": { + "payment_amount": 2500, + "payment_reference": "RC-{{date format='yyyy'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}", + "payment_method": "payment by account", + "case_reference": "{{jsonPath originalRequest.body '$.case_reference'}}", + "account_number": "PBA{{randomValue length=7 type='NUMERIC'}}" + } + } + } + } + ] +} diff --git a/wiremock-local.yml b/wiremock-local.yml new file mode 100644 index 0000000000..8618624489 --- /dev/null +++ b/wiremock-local.yml @@ -0,0 +1,9 @@ +services: + wiremock: + image: wiremock/wiremock:latest + container_name: wiremock-local + ports: + - "8083:8083" + volumes: + - ./src/test/resources/wiremock/mappings:/home/wiremock/mappings:ro + command: --global-response-templating --verbose --port 8083 From e8c0bf9315adeaaf7aad5319bc8615898aaaeda5 Mon Sep 17 00:00:00 2001 From: tvr-solirius Date: Thu, 2 Apr 2026 14:49:59 +0100 Subject: [PATCH 4/4] Updates for round tripping using Wiremock locally for mocking the payment system for the callback. --- .../entity/feesandpay/FeePaymentEntity.java | 3 +- .../endpoint/PaymentCallBackController.java | 5 +- .../reform/pcs/feesandpay/model/Payment.java | 28 +++++++++++ .../pcs/feesandpay/model/PaymentStatus.java | 25 ++++++++-- .../model/ServiceRequestUpdate.java | 7 +-- .../feesandpay/service/PaymentService.java | 12 ++--- src/main/resources/application.yaml | 6 +-- .../service/PaymentServiceTest.java | 2 +- .../wiremock/mappings/default-response.json | 13 ----- .../wiremock/mappings/payment-success.json | 49 +++++-------------- 10 files changed, 76 insertions(+), 74 deletions(-) create mode 100644 src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/Payment.java delete mode 100644 src/test/resources/wiremock/mappings/default-response.json diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/feesandpay/FeePaymentEntity.java b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/feesandpay/FeePaymentEntity.java index 76610e8152..18bf49d39a 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/feesandpay/FeePaymentEntity.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/ccd/entity/feesandpay/FeePaymentEntity.java @@ -62,7 +62,6 @@ public class FeePaymentEntity { private BigDecimal amount; - @Enumerated(EnumType.STRING) - private PaymentStatus paymentStatus; + private String paymentStatus; } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java index 7f82fcc69e..027ba0e277 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/endpoint/PaymentCallBackController.java @@ -1,7 +1,6 @@ package uk.gov.hmcts.reform.pcs.feesandpay.endpoint; import io.swagger.v3.oas.annotations.Operation; -import io.swagger.v3.oas.annotations.Parameter; import lombok.AllArgsConstructor; import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.PostMapping; @@ -22,8 +21,8 @@ public class PaymentCallBackController { @PostMapping(path = "/service-request-update", consumes = APPLICATION_JSON) @Operation(description = "Callback to create Fee and Pay service request") public void ccdSubmitted( - @RequestHeader(HttpHeaders.AUTHORIZATION) @Parameter(hidden = true) String authorisation, - @RequestHeader("ServiceAuthorization") String s2sToken, + @RequestHeader(value = HttpHeaders.AUTHORIZATION, required = false) String authorisation, + @RequestHeader(value = "ServiceAuthorization", required = false) String s2sToken, @RequestBody ServiceRequestUpdate serviceRequestUpdate) { paymentService.processPaymentResponse(serviceRequestUpdate); diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/Payment.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/Payment.java new file mode 100644 index 0000000000..4e95c491d6 --- /dev/null +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/Payment.java @@ -0,0 +1,28 @@ +package uk.gov.hmcts.reform.pcs.feesandpay.model; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; + +import java.math.BigDecimal; + +@Data +@Builder +@AllArgsConstructor +@JsonIgnoreProperties(ignoreUnknown = true) +public class Payment { + + @JsonProperty("payment_amount") + private BigDecimal paymentAmount; + @JsonProperty("payment_reference") + private String paymentReference; + @JsonProperty("payment_method") + private String paymentMethod; + @JsonProperty("case_reference") + private String caseReference; + @JsonProperty("account_number") + private String accountNumber; + +} diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/PaymentStatus.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/PaymentStatus.java index 645eec4618..476bb34319 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/PaymentStatus.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/PaymentStatus.java @@ -1,11 +1,26 @@ package uk.gov.hmcts.reform.pcs.feesandpay.model; +import lombok.Getter; + +@Getter public enum PaymentStatus { - PENDING, - SUCCESS, - CANCELLED, - FAILED, - ERROR + PAID("Paid"), + NOT_PAID("Not paid"), + PARTIALLY_PAID("Partially paid"); + + private String value; + + public static PaymentStatus fromValue(String value) { + for (PaymentStatus status : values()) { + if (status.value.equalsIgnoreCase(value)) { + return status; + } + } + throw new IllegalArgumentException("Unknown PaymentStatus value: " + value); + } + PaymentStatus(String s) { + value = s; + } } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/ServiceRequestUpdate.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/ServiceRequestUpdate.java index 9744c4e5a1..4f4d2e0eea 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/ServiceRequestUpdate.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/model/ServiceRequestUpdate.java @@ -5,7 +5,8 @@ import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; -import uk.gov.hmcts.reform.payments.client.models.PaymentDto; + +import java.math.BigDecimal; @Data @Builder @@ -18,10 +19,10 @@ public class ServiceRequestUpdate { @JsonProperty("ccd_case_number") private String ccdCaseNumber; @JsonProperty("service_request_amount") - private String serviceRequestAmount; + private BigDecimal serviceRequestAmount; @JsonProperty("service_request_status") private String serviceRequestStatus; @JsonProperty("payment") - private PaymentDto payment; + private Payment payment; } diff --git a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java index 20b1201072..9d48dcc144 100644 --- a/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java +++ b/src/main/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentService.java @@ -1,5 +1,6 @@ package uk.gov.hmcts.reform.pcs.feesandpay.service; +import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; @@ -24,8 +25,6 @@ import java.time.LocalDateTime; import java.util.Optional; -import static uk.gov.hmcts.reform.pcs.feesandpay.model.PaymentStatus.PENDING; - @Slf4j @Service @RequiredArgsConstructor @@ -59,10 +58,10 @@ public class PaymentService { * @param responsibleParty the party responsible for the payment * @return {@link PaymentServiceResponse} containing the service request reference */ + @Transactional public PaymentServiceResponse createServiceRequest(String caseReference, String ccdCaseNumber, FeeDetails feeDetails, int volume, String responsibleParty) { - ClaimEntity claimEntity = retrieveClaimEntity(caseReference); - ClaimPartyEntity claimPartyEntity = retrieveClaimPartyEntity(claimEntity, responsibleParty); + FeeDto feeDto = paymentRequestMapper.toFeeDto(feeDetails, volume); CasePaymentRequestDto casePaymentRequest = paymentRequestMapper.toCasePaymentRequest(responsibleParty); @@ -78,6 +77,8 @@ public PaymentServiceResponse createServiceRequest(String caseReference, String PaymentServiceResponse paymentServiceResponse = paymentsClient.createServiceRequest( idamService.getSystemUserAuthorisation(), requestDto); + ClaimEntity claimEntity = retrieveClaimEntity(caseReference); + ClaimPartyEntity claimPartyEntity = retrieveClaimPartyEntity(claimEntity, responsibleParty); saveNewFeePayment(claimEntity, claimPartyEntity, feeDto, paymentServiceResponse.getServiceRequestReference()); return paymentServiceResponse; @@ -89,7 +90,7 @@ public void processPaymentResponse(ServiceRequestUpdate serviceRequestUpdate) { .findByRequestReference(serviceRequestUpdate.getServiceRequestReference()); if (byCaseReference.isPresent()) { FeePaymentEntity feePaymentEntity = byCaseReference.get(); - feePaymentEntity.setPaymentStatus(PaymentStatus.valueOf(serviceRequestUpdate.getServiceRequestStatus())); + feePaymentEntity.setPaymentStatus(serviceRequestUpdate.getServiceRequestStatus()); feePaymentRepository.save(feePaymentEntity); } } @@ -109,7 +110,6 @@ private void saveNewFeePayment(ClaimEntity claimEntity, ClaimPartyEntity claimPa .requestDate(LocalDateTime.now()) .requestReference(serviceRequestReference) .amount(feeDto.getCalculatedAmount()) - .paymentStatus(PENDING) .party(claimParty.getParty()) .build(); feePaymentRepository.save(feePaymentEntity); diff --git a/src/main/resources/application.yaml b/src/main/resources/application.yaml index f2bdbaa71a..4c00cd8e71 100644 --- a/src/main/resources/application.yaml +++ b/src/main/resources/application.yaml @@ -188,12 +188,12 @@ access-code: payments: api: - url: ${PAYMENT_API_URL:http://localhost:8083} - callback-url: ${PAY_CALLBACK_URL:http://host.docker.internal:8096/service-request-update} + url: ${PAYMENT_API_URL:http://localhost:8083/payments} + callback-url: ${PAY_CALLBACK_URL:http://host.docker.internal:3206/service-request-update} params: organisationUrn: Mortgage and Landlord Possession Claims hmctsOrgId: ${hmcts.hmctsOrgId} core_case_data: api: - url: ${CCD_DATA_STORE_URL:localhost:4452} \ No newline at end of file + url: ${CCD_DATA_STORE_URL:localhost:4452} diff --git a/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java b/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java index f345211d6e..5ca4f44ccd 100644 --- a/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java +++ b/src/test/java/uk/gov/hmcts/reform/pcs/feesandpay/service/PaymentServiceTest.java @@ -135,7 +135,7 @@ void shouldProcessPaymentResponse() { // Given String requestReference = UUID.randomUUID().toString(); ServiceRequestUpdate serviceRequestUpdate = ServiceRequestUpdate.builder() - .serviceRequestReference(requestReference).serviceRequestStatus(PaymentStatus.SUCCESS.name()) + .serviceRequestReference(requestReference).serviceRequestStatus(PaymentStatus.PAID.getValue()) .build(); FeePaymentEntity feePaymentEntity = FeePaymentEntity.builder().build(); when(feePaymentRepository.findByRequestReference(requestReference)).thenReturn(Optional.of(feePaymentEntity)); diff --git a/src/test/resources/wiremock/mappings/default-response.json b/src/test/resources/wiremock/mappings/default-response.json deleted file mode 100644 index dcc0c92ce3..0000000000 --- a/src/test/resources/wiremock/mappings/default-response.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "request": { - "method": "ANY", - "urlPathPattern": "/.*" - }, - "response": { - "status": 200, - "body": "{\"message\": \"Local Wiremock service is running\"}", - "headers": { - "Content-Type": "application/json" - } - } -} diff --git a/src/test/resources/wiremock/mappings/payment-success.json b/src/test/resources/wiremock/mappings/payment-success.json index b66bd9e2bd..acf8632086 100644 --- a/src/test/resources/wiremock/mappings/payment-success.json +++ b/src/test/resources/wiremock/mappings/payment-success.json @@ -1,60 +1,33 @@ { "request": { "method": "POST", - "urlPath": "/api/payment", + "urlPath": "/payments/service-request", "bodyPatterns": [ - { - "matchesJsonPath": "$.call_back_url" - }, - { - "matchesJsonPath": "$.ccd_case_number" - }, - { - "matchesJsonPath": "$.case_reference" - } + { "matchesJsonPath": "$.ccd_case_number" } ] }, "response": { - "status": 201, + "status": 200, "headers": { "Content-Type": "application/json" }, "jsonBody": { - "service_request_reference": "SR-{{randomValue length=13 type='NUMERIC'}}", - "ccd_case_number": "{{jsonPath request.body '$.ccd_case_number'}}", - "service_request_amount": 2500, - "service_request_status": "Paid", - "payment": { - "payment_amount": 2500, - "payment_reference": "RC-{{date format='yyyy'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}", - "payment_method": "payment by account", - "case_reference": "{{jsonPath request.body '$.case_reference'}}", - "account_number": "PBA{{randomValue length=7 type='NUMERIC'}}" - } - } + "service_request_reference": "SR-{{{jsonPath request.body '$.ccd_case_number'}}}" + }, + "transformers": ["response-template"] }, "serveEventListeners": [ { "name": "webhook", "parameters": { "method": "POST", - "url": "{{jsonPath request.body '$.call_back_url'}}", + "url": "{{{jsonPath originalRequest.body '$.call_back_url'}}}", "headers": { - "Content-Type": "application/json" + "Content-Type": "application/json", + "Authorization": "{{{originalRequest.headers.Authorization}}}", + "ServiceAuthorization": "{{{originalRequest.headers.ServiceAuthorization}}}" }, - "body": { - "service_request_reference": "SR-{{randomValue length=13 type='NUMERIC'}}", - "ccd_case_number": "{{jsonPath originalRequest.body '$.ccd_case_number'}}", - "service_request_amount": 2500, - "service_request_status": "Paid", - "payment": { - "payment_amount": 2500, - "payment_reference": "RC-{{date format='yyyy'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}-{{randomValue length=4 type='NUMERIC'}}", - "payment_method": "payment by account", - "case_reference": "{{jsonPath originalRequest.body '$.case_reference'}}", - "account_number": "PBA{{randomValue length=7 type='NUMERIC'}}" - } - } + "body": "{\"service_request_reference\": \"SR-{{{jsonPath originalRequest.body '$.ccd_case_number'}}}\", \"ccd_case_number\": \"{{{jsonPath originalRequest.body '$.ccd_case_number'}}}\", \"service_request_amount\": 2500, \"service_request_status\": \"Paid\", \"payment\": { \"payment_amount\": 2500, \"payment_reference\": \"RC-{{date format='yyyy'}}-{{randomValue length=4 type='NUMERIC'}}\", \"payment_method\": \"payment by account\", \"case_reference\": \"{{{jsonPath originalRequest.body '$.case_reference'}}}\", \"account_number\": \"PBA{{randomValue length=7 type='NUMERIC'}}\" }}" } } ]