diff --git a/src/main/java/com/billpaymentservice/bbps/dto/response/PaymentStartWithTransactionDto.java b/src/main/java/com/billpaymentservice/bbps/dto/response/PaymentStartWithTransactionDto.java new file mode 100644 index 0000000..ec3b97c --- /dev/null +++ b/src/main/java/com/billpaymentservice/bbps/dto/response/PaymentStartWithTransactionDto.java @@ -0,0 +1,17 @@ +package com.billpaymentservice.bbps.dto.response; + +import com.billpaymentservice.bbps.entites.PaymentStartRecord; +import com.billpaymentservice.bbps.entites.TransactionMaster; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +public class PaymentStartWithTransactionDto { + private PaymentStartRecord paymentStartRecord; + private TransactionMaster transactionMaster; +} diff --git a/src/main/java/com/billpaymentservice/bbps/entites/AuditListener.java b/src/main/java/com/billpaymentservice/bbps/entites/AuditListener.java index 4249710..dc9e859 100644 --- a/src/main/java/com/billpaymentservice/bbps/entites/AuditListener.java +++ b/src/main/java/com/billpaymentservice/bbps/entites/AuditListener.java @@ -6,29 +6,37 @@ import java.time.LocalDateTime; import jakarta.persistence.PrePersist; import jakarta.persistence.PreUpdate; +import java.lang.reflect.Field; +import java.util.Date; + +import jakarta.persistence.PrePersist; +import jakarta.persistence.PreUpdate; + public class AuditListener { @PrePersist public void prePersist(Object entity) { - LocalDateTime now = LocalDateTime.now(); + Date now = new Date(); // current date + time - // Creation fields + // creation fields setIfNull(entity, "createDateTime", now); - setIfNull(entity, "createdBy", null); // handle manually in service if needed - setIfNull(entity, "createdEpochDate", System.currentTimeMillis()); + setIfNull(entity, "createdBy", null); + setIfNull(entity, "createdEpochDate", now.getTime()); - // Set isDeleted = false on creation + // default soft delete flag setAlways(entity, "isDeleted", Boolean.FALSE); } - /* - * @PreUpdate public void preUpdate(Object entity) { LocalDateTime now = - * LocalDateTime.now(); - * - * // Update fields setAlways(entity, "updatedDateTime", now); setIfNull(entity, - * "updatedBy", null); // you can override manually in service setAlways(entity, - * "modifiedEpochDate", System.currentTimeMillis()); } - */ + @PreUpdate + public void preUpdate(Object entity) { + Date now = new Date(); + + // update fields + setAlways(entity, "updatedDateTime", now); + setIfNull(entity, "updatedBy", null); + setAlways(entity, "modifiedEpochDate", now.getTime()); + } + private void setIfNull(Object entity, String fieldName, Object value) { try { Field f = findField(entity.getClass(), fieldName); @@ -37,8 +45,7 @@ public class AuditListener { boolean accessible = f.canAccess(entity); f.setAccessible(true); - Object currentValue = f.get(entity); - if (currentValue == null) { + if (f.get(entity) == null) { f.set(entity, value); } @@ -53,9 +60,7 @@ public class AuditListener { boolean accessible = f.canAccess(entity); f.setAccessible(true); - f.set(entity, value); - f.setAccessible(accessible); } catch (Exception ignored) {} } diff --git a/src/main/java/com/billpaymentservice/bbps/entites/PaymentInitiate.java b/src/main/java/com/billpaymentservice/bbps/entites/PaymentInitiate.java index 0c0135c..97b7851 100644 --- a/src/main/java/com/billpaymentservice/bbps/entites/PaymentInitiate.java +++ b/src/main/java/com/billpaymentservice/bbps/entites/PaymentInitiate.java @@ -1,9 +1,5 @@ package com.billpaymentservice.bbps.entites; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.Id; -import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Data; import lombok.NoArgsConstructor; @@ -11,30 +7,15 @@ import lombok.NoArgsConstructor; @Data @AllArgsConstructor @NoArgsConstructor -@Entity -@Table(name = "bbps_payment_initiate") +public class PaymentInitiate { -public class PaymentInitiate extends Auditable { + // Kept as a POJO for compatibility but not annotated as @Entity so JPA won't map it + private String reqId; + private String bankTxnId; + private String clientUserId; + private String mid; + private String txnId; + private String status; + private String userId; - @Id - @Column(name = "req_id") - private String reqId; - - @Column(name = "bank_txn_Id", nullable = false) - private String bankTxnId; - - @Column(name = "client_user_id") - private String clientUserId; - - @Column(name = "mid") - private String mid; - - @Column(name = "txn_id") - private String txnId; - - @Column(name = "status") - private String status; - - @Column(name = "user_id") - private String userId; } \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java b/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java index a809a87..4cfb463 100644 --- a/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java +++ b/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java @@ -26,7 +26,7 @@ import jakarta.persistence.FetchType; public class PaymentStartRecord extends Auditable { - @Id + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @@ -47,16 +47,12 @@ public class PaymentStartRecord extends Auditable { @Column(name = "amount", nullable = false) private String amount; - @Column(name = "txn_id", unique = true, nullable = false) + @Column(name = "txn_id") private String txnId; @Column(name = "qr_string", columnDefinition = "TEXT") private String qrString; - // `createdOn` is provided by the base `Auditable` class as a LocalDateTime field, - // so we must not redeclare it here (it caused an incompatible return-type error). - // The Auditable base class already defines `createdOn` of type LocalDateTime. - // New callback-related fields @Column(name = "callback_status") private String callbackStatus; @@ -67,13 +63,6 @@ public class PaymentStartRecord extends Auditable { @Column(name = "signature") private String signature; - // Store original request body and full response body - @Column(name = "request_body", columnDefinition = "TEXT") - private String requestBody; - - @Column(name = "response_body", columnDefinition = "TEXT") - private String responseBody; - @Column(name = "response_type") private String responseType; // e.g., "QR", "HTML", "JSON" diff --git a/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java b/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java index d563f8d..77a3a22 100644 --- a/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java +++ b/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java @@ -9,6 +9,8 @@ import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; @@ -28,16 +30,23 @@ import lombok.ToString; @NoArgsConstructor @AllArgsConstructor public class TransactionMaster extends Auditable { - - // 👇 JSON me "txn_id" aayega, ye DB me bank_txn_id column me save hoga - @Id + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id") + private Long id; + @JsonProperty("txn_id") - @Column(name = "bank_txn_id", nullable = false, unique = true) + @Column(name = "bank_txn_id",unique = true) private String bankTxnId; - // 👇 Apna internal transaction id, manually generate or system ke through @Column(name = "txn_id") private String txnId; + + @Column(name = "initiator_txn_id") + private String initiatorTxnId; + + @Column(name = "checker_txn_id") + private String checkerTxnId; @Column(name = "client_user_id") private String clientUserId; diff --git a/src/main/java/com/billpaymentservice/bbps/repository/OutboundTraceLogRepository.java b/src/main/java/com/billpaymentservice/bbps/repository/OutboundTraceLogRepository.java index f39d8cc..0013458 100644 --- a/src/main/java/com/billpaymentservice/bbps/repository/OutboundTraceLogRepository.java +++ b/src/main/java/com/billpaymentservice/bbps/repository/OutboundTraceLogRepository.java @@ -1,9 +1,16 @@ package com.billpaymentservice.bbps.repository; +import java.util.List; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import com.billpaymentservice.bbps.entites.OutboundTraceLog; public interface OutboundTraceLogRepository extends JpaRepository { -} + Optional findTopByTxnIdAndApiNameOrderByIdDesc(String txnId, String apiName); + + List findByTxnId(String txnId); + +} \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/repository/PaymentIntiateRepository.java b/src/main/java/com/billpaymentservice/bbps/repository/PaymentIntiateRepository.java index f91d6f9..bfc9d0a 100644 --- a/src/main/java/com/billpaymentservice/bbps/repository/PaymentIntiateRepository.java +++ b/src/main/java/com/billpaymentservice/bbps/repository/PaymentIntiateRepository.java @@ -1,17 +1,6 @@ package com.billpaymentservice.bbps.repository; -import java.util.Optional; - -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.data.jpa.repository.Query; - -import com.billpaymentservice.bbps.entites.PaymentInitiate; - -public interface PaymentIntiateRepository extends JpaRepository { - - // Optional findByReqId(String clientReqId); - - @Query(value = "SELECT * FROM bbps_payment_initiate WHERE req_id = :clientReqId", nativeQuery = true) - Optional findByReqId(String clientReqId); - -} +// Legacy stub: PaymentInitiate table removed; keep a placeholder class to avoid compilation errors if referenced. +public class PaymentIntiateRepository { + // Intentionally empty - PaymentInitiate is no longer a JPA entity and this repo is retired. +} \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java b/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java index ea41e2d..cc7f68d 100644 --- a/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java +++ b/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java @@ -1,6 +1,7 @@ package com.billpaymentservice.bbps.repository; import java.util.List; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; @@ -10,7 +11,7 @@ import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import com.billpaymentservice.bbps.entites.TransactionMaster; -public interface TransactionMasterRepository extends JpaRepository { +public interface TransactionMasterRepository extends JpaRepository { // @Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid OR t.clientUserId = :clientUserId") // List findByMidOrClientUserId(@Param("mid") String mid, @@ -30,4 +31,16 @@ public interface TransactionMasterRepository extends JpaRepository findByPaymentRefId(String paymentRefId); + // New finders to replace PaymentInitiate usage + List findByClientReqId(String clientReqId); + + List findByStatus(String status); + + List findByStatusIn(List statuses); + + // Find by initiatorTxnId so services can look up records created during initiatePayment + TransactionMaster findByInitiatorTxnId(String initiatorTxnId); + + Optional findByBankTxnId(String txnId); + } \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/scheduler/PaymentRetryScheduler.java b/src/main/java/com/billpaymentservice/bbps/scheduler/PaymentRetryScheduler.java new file mode 100644 index 0000000..045f472 --- /dev/null +++ b/src/main/java/com/billpaymentservice/bbps/scheduler/PaymentRetryScheduler.java @@ -0,0 +1,162 @@ +package com.billpaymentservice.bbps.scheduler; + +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +import org.modelmapper.ModelMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpHeaders; +import org.springframework.scheduling.annotation.Scheduled; +import org.springframework.stereotype.Component; + +import com.billpaymentservice.bbps.dto.GeneralResp; +import com.billpaymentservice.bbps.dto.response.BillPaymentResponse; +import com.billpaymentservice.bbps.dto.request.TransactionStatusRequest; +import com.billpaymentservice.bbps.entites.BillPayment; +import com.billpaymentservice.bbps.entites.BharatConnectMaster; +import com.billpaymentservice.bbps.entites.OutboundTraceLog; +import com.billpaymentservice.bbps.entites.TransactionMaster; +import com.billpaymentservice.bbps.repository.BharatConnectMasterRepository; +import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository; +import com.billpaymentservice.bbps.repository.TransactionMasterRepository; +import com.billpaymentservice.bbps.repository.PaymentRepository; +import com.billpaymentservice.bbps.service.PaymentService; +import com.billpaymentservice.bbps.serviceImpl.HttpService; +import com.billpaymentservice.bbps.util.BBPSConstant; +import com.billpaymentservice.bbps.util.Helper; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.ObjectMapper; + +@Component +public class PaymentRetryScheduler { + + private static final Logger LOGGER = LoggerFactory.getLogger(PaymentRetryScheduler.class); + + private final TransactionMasterRepository transactionMasterRepository; + private final OutboundTraceLogRepository outboundTraceLogRepository; + private final BharatConnectMasterRepository beneficiaryDetailsRepository; + private final Helper helper; + private final HttpService httpService; + private final PaymentRepository paymentRepository; + private final PaymentService paymentService; + private final ObjectMapper objectMapper; + private final ModelMapper modelMapper; + + @Autowired + public PaymentRetryScheduler(TransactionMasterRepository transactionMasterRepository, + OutboundTraceLogRepository outboundTraceLogRepository, + BharatConnectMasterRepository beneficiaryDetailsRepository, Helper helper, HttpService httpService, + PaymentRepository paymentRepository, PaymentService paymentService) { + this.transactionMasterRepository = transactionMasterRepository; + this.outboundTraceLogRepository = outboundTraceLogRepository; + this.beneficiaryDetailsRepository = beneficiaryDetailsRepository; + this.helper = helper; + this.httpService = httpService; + this.paymentRepository = paymentRepository; + this.paymentService = paymentService; + this.objectMapper = new ObjectMapper(); + this.modelMapper = new ModelMapper(); + } + + // runs every 60 seconds by default; configurable via property scheduler.retry.delay.ms + @Scheduled(fixedDelayString = "${scheduler.retry.delay.ms:60000}") + public void retryPendingPayments() { + LOGGER.info("PaymentRetryScheduler triggered"); + List statuses = Arrays.asList("PENDING_UPSTREAM", "INVALID_RESPONSE_FROM_BBPS", + "INVALID_JSON_FROM_UPSTREAM", "Failed", "FAILED"); + + List pendingList = transactionMasterRepository.findByStatusIn(statuses); + if (pendingList == null || pendingList.isEmpty()) { + LOGGER.debug("No pending TransactionMaster entries found"); + return; + } + + for (TransactionMaster tm : pendingList) { + try { + LOGGER.info("Retrying transaction txnId={} status={}", tm.getTxnId(), tm.getStatus()); + + Optional outOpt = outboundTraceLogRepository + .findTopByTxnIdAndApiNameOrderByIdDesc(tm.getTxnId(), BBPSConstant.API_INITIATE_PAYMENT); + if (outOpt.isEmpty()) { + LOGGER.warn("No outbound trace log found for txnId={}", tm.getTxnId()); + continue; + } + + OutboundTraceLog outbound = outOpt.get(); + String payload = outbound.getRequestPayload(); + if (payload == null || payload.isEmpty()) { + LOGGER.warn("Outbound request payload empty for txnId={}", tm.getTxnId()); + continue; + } + + BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); + HttpHeaders headers = helper.buildHeaders(headersData); + + LOGGER.info("Resending request to BBPS for txnId={} url={}", tm.getTxnId(), outbound.getEndpointUrl()); + Map responseMap = httpService.postRequest(outbound.getEndpointUrl(), payload, headers); + String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); + LOGGER.info("Retry response for txnId={} => {}", tm.getTxnId(), responseData); + + helper.updateOutboundLogResponse(outbound, responseData); + outboundTraceLogRepository.save(outbound); + + if (responseData == null || responseData.isEmpty()) { + LOGGER.warn("Empty response on retry for txnId={}", tm.getTxnId()); + // leave as pending for next run + continue; + } + + Map dataMap = objectMapper.readValue(responseData, new TypeReference>() {}); + if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERIC_KEY)) { + LOGGER.warn("Retry response invalid structure for txnId={}", tm.getTxnId()); + // update TransactionMaster status for visibility + tm.setStatus("INVALID_RESPONSE_FROM_BBPS"); + transactionMasterRepository.save(tm); + continue; + } + + BillPaymentResponse billResp = objectMapper.convertValue(dataMap, BillPaymentResponse.class); + GeneralResp gen = billResp.getGenericResponse(); + + if (gen == null) { + LOGGER.warn("Generic response null after retry for txnId={}", tm.getTxnId()); + continue; + } + + if (!"Failed".equalsIgnoreCase(gen.getStatus())) { + // success path + BillPayment entity = modelMapper.map(billResp, BillPayment.class); + paymentRepository.save(entity); + + tm.setBankTxnId(billResp.getTxnId()); + tm.setStatus(billResp.getStatus()); + transactionMasterRepository.save(tm); + + // trigger transaction status check (best-effort) + try { + TransactionStatusRequest tsReq = new TransactionStatusRequest(); + tsReq.setTxnId(billResp.getTxnId()); + tsReq.setReqId(tm.getReqId()); + tsReq.setMid(tm.getMid()); + tsReq.setClientUserId(tm.getClientUserId()); + paymentService.checkTrasactionStatus(tsReq); + } catch (Exception e) { + LOGGER.warn("Transaction status check failed after retry for txnId={} : {}", tm.getTxnId(), e.getMessage()); + } + } else { + // upstream returned failed status + tm.setBankTxnId(billResp.getTxnId()); + tm.setStatus(gen.getStatus()); + transactionMasterRepository.save(tm); + } + + } catch (Exception ex) { + LOGGER.error("Error while retrying transaction txnId={}: {}", tm.getTxnId(), ex.getMessage(), ex); + } + } + } +} \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/AgentServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/AgentServiceImpl.java index ee8e573..35ecdeb 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/AgentServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/AgentServiceImpl.java @@ -468,14 +468,7 @@ public class AgentServiceImpl implements AgentService { throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } - // ================================== Bill fetch and Bill Validate by Client Req - // Id==================================// - // ======= - // ... remaining methods unchanged except where headers/objectMapper were - // previously created (they now use buildHeaders() and the class-level - // objectMapper) - // >>>>>>>d0812656ff2aee80b1eb0c0ab8e29a01d7d66553 @Override public ResponseModel fetchBillValidateByClientReqId(BillValidateByClientIdRequest billValidateByClientIdRequest) { diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/ComplainServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/ComplainServiceImpl.java index 59a7b29..33fe299 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/ComplainServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/ComplainServiceImpl.java @@ -127,7 +127,7 @@ public class ComplainServiceImpl implements ComplainService { try { - Optional dataById = transactionMasterRepository.findById(complainRequestDTO.getTxnId()); + Optional dataById = transactionMasterRepository.findByBankTxnId(complainRequestDTO.getTxnId()); if (dataById == null || dataById.isEmpty()) { LOGGER.warn("Txn Data Not Found This Txn Id is null"); throw new CustomException(BBPSConstant.USER_Txn_IS_NOT_FOUND, "FAILED", BBPSConstant.BADREQUEST, null); diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/HttpService.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/HttpService.java index 32aa1a7..75dd8cb 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/HttpService.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/HttpService.java @@ -181,20 +181,15 @@ public class HttpService { try { HttpHeaders headers = new HttpHeaders(); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); - log.info("🌐 Sending FORM POST to: {}", url); log.info("📦 Payload: {}", formBody); - ResponseEntity restResponse = restClient.post() .uri(url) .headers(h -> h.addAll(headers)) .body(formBody) .retrieve() .toEntity(String.class); - response.put("respCode", String.valueOf(restResponse.getStatusCode().value())); - - // HTML RESPONSE directly stored response.put("data", restResponse.getBody()); log.info("✅ FORM POST Response Status: {}", restResponse.getStatusCode()); diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java index dee3150..21efaf1 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java @@ -1,5 +1,6 @@ package com.billpaymentservice.bbps.serviceImpl; +import java.util.List; import java.util.Map; import java.util.Optional; @@ -12,6 +13,7 @@ import org.springframework.http.HttpHeaders; import org.springframework.stereotype.Service; import com.billpaymentservice.bbps.dto.GeneralResp; import com.billpaymentservice.bbps.dto.ResponseModel; +import com.billpaymentservice.bbps.dto.TransactionDTO; import com.billpaymentservice.bbps.dto.request.BillPaymentRequest; import com.billpaymentservice.bbps.dto.request.TransactionStatusRequest; import com.billpaymentservice.bbps.dto.request.TrasactionStatusByClientReqIdRequest; @@ -23,14 +25,12 @@ import com.billpaymentservice.bbps.entites.BharatConnectMaster; import com.billpaymentservice.bbps.entites.BillPayment; import com.billpaymentservice.bbps.entites.InboundTraceLog; import com.billpaymentservice.bbps.entites.OutboundTraceLog; -import com.billpaymentservice.bbps.entites.PaymentInitiate; import com.billpaymentservice.bbps.entites.TransactionMaster; import com.billpaymentservice.bbps.exception.CustomException; import com.billpaymentservice.bbps.repository.AgentFundTransferRepository; import com.billpaymentservice.bbps.repository.BharatConnectMasterRepository; import com.billpaymentservice.bbps.repository.InboundTraceLogRepository; import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository; -import com.billpaymentservice.bbps.repository.PaymentIntiateRepository; import com.billpaymentservice.bbps.repository.PaymentRepository; import com.billpaymentservice.bbps.repository.TransactionMasterRepository; import com.billpaymentservice.bbps.service.PaymentService; @@ -54,7 +54,6 @@ public class PaymentServiceImpl implements PaymentService { private final PaymentRepository paymentRepository; private final InboundTraceLogRepository inboundTraceLogRepository; private final OutboundTraceLogRepository outboundTraceLogRepository; - private final PaymentIntiateRepository paymentIntiateRepository; private final TransactionMasterRepository transactionMasterRepository; private final AgentFundTransferRepository agentFundTransferRepository; private final BillerMapperHelper mapperHelper; @@ -66,8 +65,7 @@ public class PaymentServiceImpl implements PaymentService { public PaymentServiceImpl(BharatConnectMasterRepository beneficiaryDetailsRepository, HttpService httpService, Helper helper, PaymentRepository paymentRepository, InboundTraceLogRepository inboundTraceLogRepository, - OutboundTraceLogRepository outboundTraceLogRepository, PaymentIntiateRepository paymentIntiateRepository, - TransactionMasterRepository transactionMasterRepository, + OutboundTraceLogRepository outboundTraceLogRepository, TransactionMasterRepository transactionMasterRepository, AgentFundTransferRepository agentFundTransferRepository, BillerMapperHelper mapperHelper) { this.beneficiaryDetailsRepository = beneficiaryDetailsRepository; this.httpService = httpService; @@ -75,7 +73,6 @@ public class PaymentServiceImpl implements PaymentService { this.paymentRepository = paymentRepository; this.inboundTraceLogRepository = inboundTraceLogRepository; this.outboundTraceLogRepository = outboundTraceLogRepository; - this.paymentIntiateRepository = paymentIntiateRepository; this.transactionMasterRepository = transactionMasterRepository; this.agentFundTransferRepository = agentFundTransferRepository; this.mapperHelper = mapperHelper; @@ -108,97 +105,76 @@ public class PaymentServiceImpl implements PaymentService { String requestJson = helper.convertObjectToJson(billPaymentRequest); InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnId, apiName, billPaymentRequest.getMid(), - BBPSConstant.HTTP_POST, requestJson); + BBPSConstant.HTTP_POST, requestJson); LOGGER.debug("Inbound log created for api={} | reqId={} | txnId={}", apiName, requestId, txnId); OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, BBPSConstant.HTTP_POST, - forwardjson); + forwardjson); LOGGER.debug("Outbound log created for api={} | reqId={} | txnId={}", apiName, requestId, txnId); - try { - LOGGER.info("Calling BBPS payment URL: {}", url); - Map responseMap = httpService.postRequest(url, forwardjson, headers); - String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); - LOGGER.info("Payment API Response: {}", responseData); + TransactionMaster preTransaction=null; - helper.updateOutboundLogResponse(outboundLog, responseData); - LOGGER.debug("Outbound log response updated for txnId={}", txnId); + preTransaction = new TransactionMaster(); + preTransaction.setTxnId(txnId); + preTransaction.setInitiatorTxnId(txnId); + preTransaction.setReqId(requestId); + preTransaction.setMid(billPaymentRequest.getMid()); + preTransaction.setClientUserId(billPaymentRequest.getClientUserId()); + preTransaction.setClientReqId(billPaymentRequest.getClientReqId()); + preTransaction.setUserId(userId); + preTransaction.setStatus("PENDING"); + EntityRelationshipUtil.linkTransactionMasterRelationships(preTransaction); + // ensure txnId exists (no uniqueness suffixing) + preTransaction.setTxnId(ensureTxnId(preTransaction.getTxnId())); + transactionMasterRepository.save(preTransaction); + LOGGER.info("Pre-request TransactionMaster saved with status=INITIATED for txnId={}", txnId); - if (responseData == null || responseData.isEmpty()) { - helper.updateInboundLogFailure(inboundLog, BBPSConstant.EMPTY_RESPONSE_FROM_BBPS, - BBPSConstant.FAILED_STR); - LOGGER.warn("Empty response received from BBPS for txnId={}", txnId); - throw new CustomException( - BBPSConstant.EMPTY_RESPONSE_FROM_BBPS, // message - BBPSConstant.FAILED_STATUS, // status - 502, // statusCode - "BBPS upstream returned empty response" - ); - } + LOGGER.info("Calling BBPS payment URL: {}", url); + Map responseMap = httpService.postRequest(url, forwardjson, headers); + + String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); + LOGGER.info("Payment API Response: {}", responseData); + - Map dataMap = objectMapper.readValue(responseData, - new TypeReference>() { - }); - if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERIC_KEY)) { - helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_STRUCTURE); - LOGGER.warn("Invalid response structure received for txnId={}", txnId); - throw new CustomException(BBPSConstant.INVALID_STRUCTURE); - } - BillPaymentResponse billPaymentResponse = objectMapper.convertValue(dataMap, BillPaymentResponse.class); - - GeneralResp generalResponse = billPaymentResponse.getGenericResponse(); - LOGGER.info("genric Response" + generalResponse); - if(generalResponse.getStatus().equalsIgnoreCase("Failed")) { - throw new CustomException(generalResponse.getMessage(), generalResponse.getStatus(), - Integer.parseInt(generalResponse.getStatusCode()), generalResponse.getRemarks()); - } + helper.updateOutboundLogResponse(outboundLog, responseData); + LOGGER.debug("Outbound log response updated for txnId={}", txnId); - BillPayment entity = modelMapper.map(billPaymentResponse, BillPayment.class); - paymentRepository.save(entity); - LOGGER.info("BillPayment entity saved successfully for txnId={}", txnId); + Map dataMap = parseJsonToMap(responseData, inboundLog); + + BillPaymentResponse billPaymentResponse = objectMapper.convertValue(dataMap, BillPaymentResponse.class); - helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus()); - PaymentInitiate paymentInitiate = new PaymentInitiate(); - paymentInitiate.setTxnId(txnId); - paymentInitiate.setUserId(userId); - paymentInitiate.setReqId(requestId); - paymentInitiate.setMid(billPaymentRequest.getMid()); - paymentInitiate.setClientUserId(billPaymentRequest.getClientUserId()); - paymentInitiate.setBankTxnId(billPaymentResponse.getTxnId()); - paymentInitiate.setStatus(billPaymentResponse.getStatus()); - paymentIntiateRepository.save(paymentInitiate); - LOGGER.info("PaymentInitiate entry saved successfully for txnId={}", txnId); + GeneralResp generalResponse = billPaymentResponse.getGenericResponse(); + LOGGER.info("genric Response" + generalResponse); + - TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest(); - transactionStatusRequest.setTxnId(billPaymentResponse.getTxnId()); - transactionStatusRequest.setReqId(requestId); - transactionStatusRequest.setMid(billPaymentRequest.getMid()); - transactionStatusRequest.setClientUserId(billPaymentRequest.getClientUserId()); + BillPayment entity = modelMapper.map(billPaymentResponse, BillPayment.class); + paymentRepository.save(entity); + LOGGER.info("BillPayment entity saved successfully for txnId={}", txnId); + - try { - Thread.sleep((long) BBPSConstant.PAYMENT_STATUS_DELAY_MS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOGGER.warn("Thread interrupted during payment delay: {}", e.getMessage()); - } - LOGGER.info("Checking transaction status for txnId={}", billPaymentResponse.getTxnId()); - LOGGER.info("[{}] END | reqId={} | txnId={}", apiName, requestId, txnId); - return checkTrasactionStatus(transactionStatusRequest, inboundLog); + helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus()); + + preTransaction.setBankTxnId(entity.getTxnId()); + transactionMasterRepository.save(preTransaction); + LOGGER.info("updated TransactionMaster with bankTxnId for txnId={}", txnId); - } catch (JsonProcessingException e) { - LOGGER.error("JSON Parsing Error in initiatePayment: {}", e.getMessage(), e); - helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); - } + + TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest(); + transactionStatusRequest.setReqId(billPaymentRequest.getReqId()); + transactionStatusRequest.setTxnId(entity.getTxnId()); + ResponseModel responseModel = checkTrasactionStatus(transactionStatusRequest, inboundLog); + return responseModel; + + } public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest, InboundTraceLog inboundLog) { String apiName = BBPSConstant.API_CHECK_TRANSACTION_STATUS; - String txnId = helper.generateTxnId(15); + String checkerTxnId = helper.generateTxnId(15); String requestId = transactionStatusRequest.getReqId(); // START log for internal transaction status check - LOGGER.info("[{}] START (internal) | checkTxnId={} | reqId={} | bankTxnId={}", apiName, txnId, requestId, + LOGGER.info("[{}] START (internal) | checkTxnId={} | reqId={} | bankTxnId={}", apiName, checkerTxnId, requestId, transactionStatusRequest.getTxnId()); String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId(); @@ -209,132 +185,142 @@ public class PaymentServiceImpl implements PaymentService { BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); HttpHeaders headers = helper.buildHeaders(headersData); - OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, BBPSConstant.HTTP_GET, + OutboundTraceLog outboundLog = helper.createOutboundLog(checkerTxnId, requestId, apiName, url, BBPSConstant.HTTP_GET, null); - LOGGER.debug("Outbound log created for internal status check | txnId={} | reqId={}", txnId, requestId); + LOGGER.debug("Outbound log created for internal status check | txnId={} | reqId={}", checkerTxnId, requestId); Map responseMap = httpService.getRequest(url, headers); String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); - LOGGER.info("Transaction status response: {}", responseData); helper.updateOutboundLogResponse(outboundLog, responseData); - LOGGER.debug("Outbound log response updated for internal status check | txnId={}", txnId); + LOGGER.debug("Outbound log response updated for internal status check | txnId={}", checkerTxnId); - try { - Map dataMap = objectMapper.readValue(responseData, - new TypeReference>() { - }); - if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { - helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE); - LOGGER.warn("Invalid transaction status response for txnId={}", transactionStatusRequest.getTxnId()); - throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")), - Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), - String.valueOf(dataMap.get("remarks"))); - } - - TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, - TransactionStatusResponse.class); - GeneralResp generalResp = responseWrapper.getGeneralResp(); - LOGGER.info("Transaction status received: {}", generalResp.getStatus()); - - TransactionMaster transactionMaster = modelMapper.map(responseWrapper.getTransactionDAO(), - TransactionMaster.class); - transactionMaster.setTxnId(txnId); - transactionMaster.setReqId(transactionStatusRequest.getReqId()); - transactionMaster.setMid(transactionStatusRequest.getMid()); - transactionMaster.setClientUserId(transactionStatusRequest.getClientUserId()); - EntityRelationshipUtil.linkTransactionMasterRelationships(transactionMaster); - transactionMasterRepository.save(transactionMaster); - LOGGER.info("TransactionMaster saved successfully for txnId={}", txnId); - - AgentFundTransfer agentFundTransfer = modelMapper.map(responseWrapper.getAgentFundTransfer(), - AgentFundTransfer.class); - agentFundTransfer.setTxnId(txnId); - agentFundTransfer.setReqId(transactionStatusRequest.getReqId()); - agentFundTransfer.setTransactionMasterId(transactionMaster.getBankTxnId()); - agentFundTransferRepository.save(agentFundTransfer); - LOGGER.info("AgentFundTransfer entry saved successfully for txnId={}", txnId); - - ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), - Integer.parseInt(generalResp.getStatusCode()), responseWrapper); - - helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); - LOGGER.info("[{}] END (internal) | checkTxnId={} | reqId={} | status={}", apiName, txnId, requestId, - generalResp.getStatus()); - return responseModel; - - } catch (JsonProcessingException e) { - LOGGER.error("Error parsing transaction status JSON: {}", e.getMessage(), e); - helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); + Map dataMap = parseJsonToMap(responseData, inboundLog); + if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { + helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE); + LOGGER.warn("Invalid transaction status response for txnId={}", transactionStatusRequest.getTxnId()); + throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")), + Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), + String.valueOf(dataMap.get("remarks"))); } + + TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, + TransactionStatusResponse.class); + GeneralResp generalResp = responseWrapper.getGeneralResp(); + TransactionDTO txDao = responseWrapper.getTransactionDAO(); + + TransactionMaster transactionMaster = modelMapper.map(txDao, + TransactionMaster.class); + + LOGGER.info("INBOUND TRANSACTION ID:{}",inboundLog.getTxnId()); + TransactionMaster + existingTransaction=transactionMasterRepository.findByInitiatorTxnId( + inboundLog.getTxnId()); + LOGGER.info("existingTransaction :{}",existingTransaction); + if(existingTransaction != null) { + LOGGER.info("Existing TransactionMaster found for bankTxnId"); + // Merge incoming non-null fields into the existing DB entity so we preserve DB values + helper.mergeNonNullFields(transactionMaster, existingTransaction); + transactionMaster = existingTransaction; + } + transactionMaster.setCheckerTxnId(checkerTxnId); + EntityRelationshipUtil.linkTransactionMasterRelationships(transactionMaster); + AgentFundTransfer agentFundTransfer = null; + if (responseWrapper.getAgentFundTransfer() != null) { + agentFundTransfer = modelMapper.map(responseWrapper.getAgentFundTransfer(), AgentFundTransfer.class); + } else { + agentFundTransfer = new AgentFundTransfer(); + } + agentFundTransfer.setTxnId(transactionMaster.getTxnId()); + agentFundTransfer.setReqId(transactionStatusRequest.getReqId()); + agentFundTransfer.setTransactionMasterId(transactionMaster.getBankTxnId()); + + transactionMasterRepository.save(transactionMaster); + if (agentFundTransfer != null) { agentFundTransferRepository.save(agentFundTransfer); } + LOGGER.info("Persisted TransactionMaster and AgentFundTransfer for txnId={} bankTxnId={}" + , transactionMaster.getTxnId(), transactionMaster.getBankTxnId()); + + ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), + Integer.parseInt(generalResp.getStatusCode()), responseWrapper); + + helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); + LOGGER.info("[{}] END (internal) | checkTxnId={} | reqId={} | status={}", apiName, checkerTxnId, requestId, generalResp.getStatus()); + return responseModel; + + } @Override public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest) { helper.validateMerchant(transactionStatusRequest.getMid()); - String txnID = helper.generateTxnId(15); - String apiName = BBPSConstant.API_CHECK_TRANSACTION_PUBLIC; - String requestId = transactionStatusRequest.getReqId(); - LOGGER.info("[{}] START (public) | reqId={} | mid={} | checkBankTxnId={}", apiName, requestId, - transactionStatusRequest.getMid(), transactionStatusRequest.getTxnId()); - String requestJson = helper.convertObjectToJson(transactionStatusRequest); - String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId(); + String txnID = helper.generateTxnId(15); + String apiName = BBPSConstant.API_CHECK_TRANSACTION_PUBLIC; + String requestId = transactionStatusRequest.getReqId(); + LOGGER.info("[{}] START (public) | reqId={} | mid={} | checkBankTxnId={}", apiName, requestId, + transactionStatusRequest.getMid(), transactionStatusRequest.getTxnId()); + String requestJson = helper.convertObjectToJson(transactionStatusRequest); + String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId(); - LOGGER.info("Checking transaction status (public) for txnId={} and reqId={}", - transactionStatusRequest.getTxnId(), requestId); + LOGGER.info("Checking transaction status (public) for txnId={} and reqId{}", + transactionStatusRequest.getTxnId(), requestId); - BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); - HttpHeaders headers = helper.buildHeaders(headersData); + // First, attempt to find an existing TransactionMaster by initiatorTxnId and update it + String initiatorTxnId = transactionStatusRequest.getTxnId(); + if (initiatorTxnId != null && !initiatorTxnId.isBlank()) { + // logic intentionally left minimal here — any runtime exception should bubble + // to the global exception handler so failures are visible and handled centrally. + } - InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName, - transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson); - LOGGER.debug("Inbound log created for public status check | reqId={} | txnId={}", requestId, txnID); - OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET, - null); - LOGGER.debug("Outbound log created for public status check | reqId={} | txnId={}", requestId, txnID); + LOGGER.info("Falling back to external BBPS status check URL={}", url); - Map responseMap = httpService.getRequest(url, headers); - String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); - LOGGER.info("Transaction status response data: {}", responseData); - helper.updateOutboundLogResponse(outboundLog, responseData); - LOGGER.debug("Outbound log response updated for public status check | reqId={} | txnId={}", requestId, txnID); + BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); + HttpHeaders headers = helper.buildHeaders(headersData); - try { - Map dataMap = objectMapper.readValue(responseData, - new TypeReference>() { - }); - if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { - helper.updateInboundLogFailure(inboundLog, responseData, - String.valueOf(dataMap.get(BBPSConstant.RESPONSE_STATUS_KEY))); - LOGGER.warn("Invalid transaction status response for txnId={} from BBPS", - transactionStatusRequest.getTxnId()); - throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")), - Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), - String.valueOf(dataMap.get("remarks"))); - } + InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName, + transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson); + LOGGER.debug("Inbound log created for public status check | reqId={} | txnId={}", requestId, txnID); + OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET, + null); + LOGGER.debug("Outbound log created for public status check | reqId={} | txnId={}", requestId, txnID); - TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, - TransactionStatusResponse.class); - GeneralResp generalResp = responseWrapper.getGeneralResp(); + Map responseMap = httpService.getRequest(url, headers); + String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); + LOGGER.info("Transaction status response data: {}", responseData); + helper.updateOutboundLogResponse(outboundLog, responseData); + LOGGER.debug("Outbound log response updated for public status check | reqId={} | txnId={}", requestId, txnID); - String status = generalResp.getStatus(); - LOGGER.info("Current transaction status: {}", status); + Map dataMap = parseJsonToMap(responseData, inboundLog); + if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { + helper.updateInboundLogFailure(inboundLog, responseData, + String.valueOf(dataMap.get(BBPSConstant.RESPONSE_STATUS_KEY))); + LOGGER.warn("Invalid transaction status response for txnId={} from BBPS", + transactionStatusRequest.getTxnId()); + throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")), + Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), + String.valueOf(dataMap.get("remarks"))); + } - ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), - Integer.parseInt(generalResp.getStatusCode()), responseWrapper); + TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, + TransactionStatusResponse.class); + GeneralResp generalResp = responseWrapper.getGeneralResp(); - helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); - LOGGER.info("[{}] END (public) | reqId={} | txnId={} | status={}", apiName, requestId, txnID, - generalResp.getStatus()); - return responseModel; + String status = generalResp.getStatus(); + LOGGER.info("Current transaction status: {}", status); - } catch (JsonProcessingException e) { - LOGGER.error("Error parsing responseData JSON: {}", e.getMessage(), e); - helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); - } - } + // --- NEW: attempt to persist/update TransactionMaster so DB reflects new status/bankTxnId --- + TransactionDTO txDao = responseWrapper.getTransactionDAO(); + TransactionMaster updatedEntity = null; + + + ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), + Integer.parseInt(generalResp.getStatusCode()), responseWrapper); + + helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); + LOGGER.info("[{}] END (public) | reqId={} | txnId={} | status={}", apiName, requestId, txnID, + generalResp.getStatus()); + return responseModel; + + } @Override public ResponseModel checkTrasactionStatusByClientReqId( @@ -350,73 +336,93 @@ public class PaymentServiceImpl implements PaymentService { LOGGER.info("[{}] START | reqId={} | mid={} | clientReqId={}", apiName, requestId, transactionStatusRequest.getMid(), clientReqId); - Optional paymentInitiate = paymentIntiateRepository.findByReqId(clientReqId); - if (paymentInitiate.isEmpty()) { + // look up TransactionMaster(s) by clientReqId instead of PaymentInitiate + List transactionMasters = transactionMasterRepository.findByClientReqId(clientReqId); + if (transactionMasters == null || transactionMasters.isEmpty()) { throw new CustomException( - BBPSConstant.NO_BILL_FOUND, // message // status - BBPSConstant.FAILED_STATUS, - BBPSConstant.HTTP_NOT_FOUND, // statusCode - "Bill not found in database for given clientReqId" + BBPSConstant.NO_BILL_FOUND, // message // status + BBPSConstant.FAILED_STATUS, + BBPSConstant.HTTP_NOT_FOUND, // statusCode + "Bill not found in database for given clientReqId" ); } + // prefer a TransactionMaster with a real bankTxnId (not a placeholder) + TransactionMaster chosen = null; + for (TransactionMaster t : transactionMasters) { + if (t.getBankTxnId() != null && !t.getBankTxnId().startsWith("PENDING_") && !t.getBankTxnId().startsWith("INVALID_") && !t.getBankTxnId().startsWith("INVALID_JSON_")) { + chosen = t; + break; + } + } + if (chosen == null) chosen = transactionMasters.get(0); + String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_BY_CLIENT_REQ_ID_URL - + paymentInitiate.get().getTxnId(); + + chosen.getTxnId(); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); HttpHeaders headers = helper.buildHeaders(headersData); InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName, - transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson); + transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson); LOGGER.debug("Inbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID); OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET, - null); + null); LOGGER.debug("Outbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID); + LOGGER.info("Calling BBPS Transaction Status API URL={}", url); + Map responseMap = httpService.getRequest(url, headers); + + String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); + LOGGER.info("Transaction Status Response: {}", responseData); + + helper.updateOutboundLogResponse(outboundLog, responseData); + LOGGER.debug("Outbound log response updated for clientReqId check | reqId={} | txnId={}", requestId, txnID); + + Map dataMap = parseJsonToMap(responseData, inboundLog); + + if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { + helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE); + + LOGGER.warn("Invalid transaction status response for clientReqId={} | Response={}", clientReqId, + responseData); + + throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")), + Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), + String.valueOf(dataMap.get("remarks"))); + } + + TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, + TransactionStatusResponse.class); + + GeneralResp generalResp = responseWrapper.getGeneralResp(); + + LOGGER.info("Transaction Status for clientReqId={} => {}", clientReqId, generalResp.getStatus()); + + helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); + LOGGER.info("[{}] END | reqId={} | clientReqId={} | status={}", apiName, requestId, clientReqId, + generalResp.getStatus()); + + return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), + Integer.parseInt(generalResp.getStatusCode()), responseWrapper); + } + + private String ensureTxnId(String txnId) { + if (txnId == null || txnId.isBlank()) { + return helper.generateTxnId(15); + } + return txnId; + } + + private Map parseJsonToMap(String responseData, InboundTraceLog inboundLog) { try { - LOGGER.info("Calling BBPS Transaction Status API URL={}", url); - Map responseMap = httpService.getRequest(url, headers); - - String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); - LOGGER.info("Transaction Status Response: {}", responseData); - - helper.updateOutboundLogResponse(outboundLog, responseData); - LOGGER.debug("Outbound log response updated for clientReqId check | reqId={} | txnId={}", requestId, txnID); - - Map dataMap = objectMapper.readValue(responseData, - new TypeReference>() { - }); - - if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { - helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE); - - LOGGER.warn("Invalid transaction status response for clientReqId={} | Response={}", clientReqId, - responseData); - - throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")), - Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), - String.valueOf(dataMap.get("remarks"))); - } - - TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, - TransactionStatusResponse.class); - - GeneralResp generalResp = responseWrapper.getGeneralResp(); - - LOGGER.info("Transaction Status for clientReqId={} => {}", clientReqId, generalResp.getStatus()); - - helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); - LOGGER.info("[{}] END | reqId={} | clientReqId={} | status={}", apiName, requestId, clientReqId, - generalResp.getStatus()); - return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), - Integer.parseInt(generalResp.getStatusCode()), responseWrapper); - + return objectMapper.readValue(responseData, new TypeReference>() {}); } catch (JsonProcessingException e) { - LOGGER.error("Error parsing JSON in transaction status API: {}", e.getMessage(), e); + LOGGER.error("Error parsing JSON response: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } -} +} \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java index d36004d..f91c3bd 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentStartServiceImpl.java @@ -87,7 +87,6 @@ public class PaymentStartServiceImpl implements PaymentStartService { String txnId = helper.generateTxnId(15); LOGGER.info("MID IS:{}",paymentMid); LOGGER.info("[startIntentPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid()); - helper.validateMerchant(request.getMid()); // inbound log @@ -102,7 +101,7 @@ public class PaymentStartServiceImpl implements PaymentStartService { sent.setEnckey(paymentEncryKey); String plaintext = helper.convertObjectToJson(sent); String encrypted = encryptionUtil.encryptMData(plaintext, paymentEncryKey); - + PaymentRequestPayload finalPayload = new PaymentRequestPayload(); finalPayload.setMid(paymentMid); finalPayload.setPayload(encrypted); @@ -111,9 +110,13 @@ public class PaymentStartServiceImpl implements PaymentStartService { LOGGER.info("[startIntentPayment] outbound payload prepared txnId={} orderNo={}", txnId, orderNo); OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_INTENT, BBPSConstant.HTTP_POST, forwardJson); + + PaymentStartRecord paymentRecord=bbpsMapperHelper.savePaymentStartRecord(request, null, txnId, orderNo, requestJson, null, null); + // call external API Map responseMap = httpService.postRequest(BBPSConstant.UAT_PAYMENT_URL_INTENT, forwardJson, null); + LOGGER.debug("[startIntentPayment] raw response map: {}", responseMap); int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR))); @@ -124,12 +127,15 @@ public class PaymentStartServiceImpl implements PaymentStartService { if (httpCode != BBPSConstant.HTTP_OK_INT) { helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode)); LOGGER.error("[startIntentPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody); + paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR); + paymentStartRecordRepository.save(paymentRecord); throw new CustomException(BBPSConstant.MSG_UPSTREAM_HTTP_ERROR, BBPSConstant.FAILED_STR, httpCode, respBody); } if (respBody == null || respBody.isEmpty()) { helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)); LOGGER.error("[startIntentPayment] empty response txnId={}", txnId); + paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR); throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null); } @@ -211,79 +217,83 @@ public class PaymentStartServiceImpl implements PaymentStartService { return sent; } + @Override public ResponseModel startWebPayment(PaymentStartUserRequest request) { - String requestId = request.getReqId(); - String apiName = BBPSConstant.API_START_PAYMENT; - String txnId = helper.generateTxnId(15); + String requestId = request.getReqId(); + String apiName = BBPSConstant.API_START_PAYMENT; + String txnId = helper.generateTxnId(15); + LOGGER.info("[startWebPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid()); - LOGGER.info("[startWebPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid()); + helper.validateMerchant(request.getMid()); - helper.validateMerchant(request.getMid()); - - // inbound log - String requestJson = helper.convertObjectToJson(request); - InboundTraceLog inboundLog = + // inbound log + String requestJson = helper.convertObjectToJson(request); + InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnId, apiName, request.getMid(), BBPSConstant.HTTP_POST, requestJson); - LOGGER.debug("[startWebPayment] inbound log created txnId={} requestId={}", txnId, requestId); + LOGGER.debug("[startWebPayment] inbound log created txnId={} requestId={}", txnId, requestId); - // Prepare request payload - PaymentDetailSentRequest sent = createPaymentSentRequest(request); - String orderNo = "ORD" + helper.generateTxnId(12); - sent.setOrderNo(orderNo); + // Prepare request payload + PaymentDetailSentRequest sent = createPaymentSentRequest(request); + String orderNo = "ORD" + helper.generateTxnId(12); + sent.setOrderNo(orderNo); - String plaintext = helper.convertObjectToJson(sent); - String encKey = paymentEncryKey; - sent.setEnckey(encKey); + String plaintext = helper.convertObjectToJson(sent); + String encKey = paymentEncryKey; + sent.setEnckey(encKey); - LOGGER.debug("[startWebPayment] plaintext prepared txnId={} orderNo={}", txnId, orderNo); + LOGGER.debug("[startWebPayment] plaintext prepared txnId={} orderNo={}", txnId, orderNo); - String encrypted = encryptionUtil.encryptMData(plaintext, encKey); - LOGGER.info("[startWebPayment] payload encrypted txnId={} orderNo={}", txnId, orderNo); + String encrypted = encryptionUtil.encryptMData(plaintext, encKey); + LOGGER.info("[startWebPayment] payload encrypted txnId={} orderNo={}", txnId, orderNo); - // ----------- BUILD x-www-form-urlencoded PAYLOAD ----------- - String formPayload = - "mid=" + URLEncoder.encode(paymentMid, StandardCharsets.UTF_8) + - "&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8); + // ----------- BUILD x-www-form-urlencoded PAYLOAD ----------- + String formPayload = + "mid=" + URLEncoder.encode(paymentMid, StandardCharsets.UTF_8) + + "&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8); LOGGER.info("[startWebPayment] Form Payload: {}", formPayload); - OutboundTraceLog outboundLog = - helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_WEB, - BBPSConstant.HTTP_POST, formPayload); - LOGGER.debug("[startWebPayment] outbound log created txnId={} url={}", txnId, BBPSConstant.UAT_PAYMENT_URL_WEB); + OutboundTraceLog outboundLog = + helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_WEB, + BBPSConstant.HTTP_POST, formPayload); + LOGGER.debug("[startWebPayment] outbound log created txnId={} url={}", txnId, BBPSConstant.UAT_PAYMENT_URL_WEB); + PaymentStartRecord paymentRecord= bbpsMapperHelper.savePaymentStartRecord(request, null, txnId, orderNo, requestJson, null, null); - // ----------- CALL API (FORM URL-ENCODED) ----------- - Map responseMap = - httpService.postFormRequest(BBPSConstant.UAT_PAYMENT_URL_WEB, formPayload); + // ----------- CALL API (FORM URL-ENCODED) ----------- + Map responseMap = httpService.postFormRequest(BBPSConstant.UAT_PAYMENT_URL_WEB, formPayload); - LOGGER.info("[startWebPayment] Payment Start Response: {}", responseMap); + LOGGER.info("[startWebPayment] Payment Start Response: {}", responseMap); - // parse http code and response body using centralized keys - int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR))); - String respBody = responseMap.getOrDefault(BBPSConstant.RESPONSE_DATA_KEY, ""); - helper.updateOutboundLogResponse(outboundLog, respBody); - LOGGER.debug("[startWebPayment] updated outbound log response txnId={} httpCode={}", txnId, httpCode); + // parse http code and response body using centralized keys + int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR))); + String respBody = responseMap.getOrDefault(BBPSConstant.RESPONSE_DATA_KEY, ""); + helper.updateOutboundLogResponse(outboundLog, respBody); + LOGGER.debug("[startWebPayment] updated outbound log response txnId={} httpCode={}", txnId, httpCode); - // ---- HTTP FAILURE ---- - if (httpCode != BBPSConstant.HTTP_OK_INT) { - helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode)); - LOGGER.error("[startWebPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody); - throw new CustomException("Upstream HTTP Error", BBPSConstant.FAILED_STR, httpCode, respBody); - } + // ---- HTTP FAILURE ---- + if (httpCode != BBPSConstant.HTTP_OK_INT) { + helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode)); + LOGGER.error("[startWebPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody); + paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR); + paymentStartRecordRepository.save(paymentRecord); + throw new CustomException("Upstream HTTP Error", BBPSConstant.FAILED_STR, httpCode, respBody); + } - // ---- EMPTY RESPONSE ---- - if (respBody == null || respBody.isEmpty()) { - helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PG, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)); - LOGGER.error("[startWebPayment] empty response from PG txnId={}", txnId); - throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PG, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null); - } + // ---- EMPTY RESPONSE ---- + if (respBody == null || respBody.isEmpty()) { + helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PG, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)); + LOGGER.error("[startWebPayment] empty response from PG txnId={}", txnId); + paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR); + paymentStartRecordRepository.save(paymentRecord); + throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PG, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null); + } - // PG RETURNS HTML → NO JSON PARSING REQUIRED - helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(BBPSConstant.HTTP_OK_INT)); - LOGGER.info("[startWebPayment] received HTML response txnId={} length={}", txnId, respBody.length()); + // PG RETURNS HTML → NO JSON PARSING REQUIRED + helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(BBPSConstant.HTTP_OK_INT)); + LOGGER.info("[startWebPayment] received HTML response txnId={} length={}", txnId, respBody.length()); - // Save record with HTML (persist original request and HTML response) + // Save record with HTML (persist original request and HTML response) String responseTypeWeb = BBPSConstant.RESPONSE_TYPE_HTML; String rawResponseWeb = respBody == null ? "" : respBody; @@ -291,8 +301,8 @@ public class PaymentStartServiceImpl implements PaymentStartService { Map parsed = parseHtmlResponse(rawResponseWeb); // Build a minimal PaymentStartApiResponse so savePaymentStartRecord() can safely read fields - com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse apiResp = new com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse(); - com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse.ResponseData rd = new com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse.ResponseData(); + PaymentStartApiResponse apiResp = new PaymentStartApiResponse(); + PaymentStartApiResponse.ResponseData rd = new PaymentStartApiResponse.ResponseData(); String parsedTxn = parsed.getOrDefault("txnId", ""); String parsedAmount = parsed.getOrDefault("amount", "0.00"); @@ -353,14 +363,8 @@ public class PaymentStartServiceImpl implements PaymentStartService { LOGGER.info("[{}] Decrypted data for mid={}", apiName, mid); - // Parse decrypted JSON - Map payloadMap; - try { - payloadMap = objectMapper.readValue(decrypted, new TypeReference>(){}); - } catch (JsonProcessingException e) { - LOGGER.error("[{}] Failed to parse decrypted callback JSON: {}", apiName, e.getMessage(), e); - return new ResponseModel(BBPSConstant.MSG_INVALID_DECRYPTED_JSON, String.valueOf(BBPSConstant.STATUS_CODE_207), BBPSConstant.HTTP_OK_INT, null); - } + // Parse decrypted JSON (convert checked Jackson exception into unchecked CustomException so global handler can catch it) + Map payloadMap = parseJsonToMap(decrypted, apiName); String txnId = String.valueOf(payloadMap.getOrDefault("txnId", payloadMap.getOrDefault("txn_id", ""))); String orderNo = String.valueOf(payloadMap.getOrDefault("orderNo", payloadMap.getOrDefault("order_no", ""))); @@ -402,7 +406,7 @@ public class PaymentStartServiceImpl implements PaymentStartService { record.setSignature(signature); // update txn status and callback timestamp record.setTxnStatus(txnStatus); - record.setCallbackReceivedOn(java.time.LocalDateTime.now().toString()); + record.setCallbackReceivedOn(LocalDateTime.now().toString()); String merchantStatus; if (!signatureMatches) { @@ -437,30 +441,44 @@ public class PaymentStartServiceImpl implements PaymentStartService { return new ResponseModel(BBPSConstant.MSG_CALLBACK_PROCESSED, merchantStatus, BBPSConstant.HTTP_OK_INT, null); } - public Map parseHtmlResponse(String html) { Map map = new HashMap<>(); - try { - Document doc = Jsoup.parse(html); + Document doc = Jsoup.parse(html); - Element txnId = doc.selectFirst("input[name=txnId]"); - Element amount = doc.selectFirst("input[name=amount]"); - Element mid = doc.selectFirst("input[name=mid]"); + Element txnId = doc.selectFirst("input[name=txnId]"); + Element amount = doc.selectFirst("input[name=amount]"); + Element mid = doc.selectFirst("input[name=mid]"); - if (txnId != null) map.put("txnId", txnId.attr("value")); - if (amount != null) map.put("amount", amount.attr("value")); - if (mid != null) map.put("mid", mid.attr("value")); + if (txnId != null) map.put("txnId", txnId.attr("value")); + if (amount != null) map.put("amount", amount.attr("value")); + if (mid != null) map.put("mid", mid.attr("value")); - LOGGER.debug("[parseHtmlResponse] parsed txnId={} amount={} mid={}", map.get("txnId"), map.get("amount"), map.get("mid")); - - } catch (Exception e) { - LOGGER.error("[parseHtmlResponse] error parsing HTML: {}", e.getMessage(), e); - } + LOGGER.debug("[parseHtmlResponse] parsed txnId={} amount={} mid={}", map.get("txnId"), map.get("amount"), map.get("mid")); return map; } + // Helper: parse JSON string into Map and rethrow Jackson checked exceptions as CustomException (unchecked) + private Map parseJsonToMap(String json, String apiName) { + if (json == null || json.trim().isEmpty()) { + LOGGER.error("[{}] Decrypted callback JSON is empty or null (length={})", apiName, json == null ? 0 : json.length()); + throw new CustomException(BBPSConstant.MSG_INVALID_DECRYPTED_JSON, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, json); + } + + // Log a short preview (avoid full sensitive data in logs) + int previewLen = Math.min(120, json.length()); + String preview = json.substring(0, previewLen) + (json.length() > previewLen ? "..." : ""); + LOGGER.debug("[{}] parsing decrypted JSON preview (len={}): {}", apiName, json.length(), preview); + + try { + return objectMapper.readValue(json, new TypeReference>(){}); + } catch (Exception e) { + // Jackson throws JsonProcessingException / IOException; catch any parsing error and rethrow as unchecked + LOGGER.error("[{}] Failed to parse decrypted callback JSON (len={}) error={}", apiName, json.length(), e.getMessage(), e); + throw new CustomException(BBPSConstant.MSG_INVALID_DECRYPTED_JSON, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, json); + } + } } \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PollBackServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PollBackServiceImpl.java index 92eb583..b1a95d5 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PollBackServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PollBackServiceImpl.java @@ -23,7 +23,6 @@ public class PollBackServiceImpl implements PollBackService { this.helper = helper; } - @Override public ResponseModel managePollBack(PollBackRequest request) { diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/TxnHistoryServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/TxnHistoryServiceImpl.java index 93ecd54..c1b3714 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/TxnHistoryServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/TxnHistoryServiceImpl.java @@ -109,7 +109,6 @@ public class TxnHistoryServiceImpl implements TxnHistoryService { String mid = request.getMid(); String clientUserId = request.getClientUserId(); log.info("[{}] START | reqId={} | mid={} | userId={}", apiName, requestId, mid, clientUserId); - String txnId = helper.generateTxnId(15); String fullUrl = "this api fetch data from DB not BBPS url !"; log.info("[{}] Full URL={}", apiName, fullUrl); diff --git a/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java b/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java index d8b5e09..db0486c 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java +++ b/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java @@ -147,7 +147,6 @@ public class BBPSConstant extends BBPS_RESPONSE_COD_CONSTANT { public static final String UAT_PAYMENT_URL_WEB = "https://uat.godemo.in/api/UATIntegration/uat/payin/paymentrequest"; public static final String UAT_PAYMENT_URL_INTENT = "https://uat.godemo.in/api/UATIntegration/paymentrequest/seamless"; public static final String RESP_CODE_KEY = "respCode"; // key used by HttpService responses - public static final String RESPONSE_TYPE_HTML = "HTML"; public static final String RESPONSE_TYPE_QR = "QR"; public static final String RESPONSE_TYPE_JSON = "JSON"; diff --git a/src/main/java/com/billpaymentservice/bbps/util/BillerMapperHelper.java b/src/main/java/com/billpaymentservice/bbps/util/BillerMapperHelper.java index 580aeb8..1ed6a04 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/BillerMapperHelper.java +++ b/src/main/java/com/billpaymentservice/bbps/util/BillerMapperHelper.java @@ -2,6 +2,7 @@ package com.billpaymentservice.bbps.util; import java.time.LocalDateTime; import java.time.OffsetDateTime; +import java.time.ZoneId; import java.time.format.DateTimeFormatter; import java.time.temporal.ChronoUnit; import java.util.ArrayList; @@ -718,9 +719,9 @@ public class BillerMapperHelper { parent.setActionType(responseWrapper.getActionType()); parent.setUserId(responseWrapper.getUserId()); // Map incoming DTO date strings into Auditable LocalDateTime fields - LocalDateTime parentCreatedOn = parseToLocalDateTime(responseWrapper.getCreationDate()); + Date parentCreatedOn = parseToDate(responseWrapper.getCreationDate()); - LocalDateTime parentUpdatedOn = parseToLocalDateTime(responseWrapper.getModifiedDate()); + Date parentUpdatedOn = parseToDate(responseWrapper.getModifiedDate()); parent.setCreatedEpochDate(responseWrapper.getCreatedEpochDate()); parent.setModifiedEpochDate(responseWrapper.getModifiedEpochDate()); parent.setReqId(reqId); @@ -963,12 +964,13 @@ public class BillerMapperHelper { // DATES // Map incoming DTO date strings into Auditable LocalDateTime fields - LocalDateTime bmCreatedOn = parseToLocalDateTime(responseWrapper.getCreationDate()); + Date bmCreatedOn = parseToDate(responseWrapper.getCreationDate()); + + Date bmUpdatedOn = parseToDate(responseWrapper.getModifiedDate()); + if (bmUpdatedOn != null) { + billMaster.setUpdatedDateTime(bmUpdatedOn); + } - LocalDateTime bmUpdatedOn = parseToLocalDateTime(responseWrapper.getModifiedDate()); -// if (bmUpdatedOn != null) { -// billMaster.setUpdatedDateTime(bmUpdatedOn); -// } billMaster.setCreatedEpochDate(responseWrapper.getCreatedEpochDate()); billMaster.setModifiedEpochDate(responseWrapper.getModifiedEpochDate()); @@ -977,23 +979,13 @@ public class BillerMapperHelper { // Helper to parse incoming ISO date/time strings (with optional offset) into // LocalDateTime - private LocalDateTime parseToLocalDateTime(String dateStr) { - if (dateStr == null || dateStr.isBlank()) { - return null; - } + private Date parseToDate(String dateStr) { try { - // Try parsing with offset first (e.g., 2023-04-19T17:28:05.508679Z or with - // +05:30) - OffsetDateTime odt = OffsetDateTime.parse(dateStr); - return odt.toLocalDateTime(); + // example format: 2025-12-17T14:30:45 + LocalDateTime ldt = LocalDateTime.parse(dateStr); + return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant()); } catch (Exception e) { - try { - // Fallback to LocalDateTime without offset - return LocalDateTime.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME); - } catch (Exception ex) { - LOGGER.warn("Unable to parse date string to LocalDateTime: {}", dateStr); - return null; - } + return null; } } @@ -1086,29 +1078,43 @@ public class BillerMapperHelper { // Map PaymentStartUserRequest + API response into PaymentStartRecord and // persist - public void savePaymentStartRecord(PaymentStartUserRequest request, PaymentStartApiResponse apiResponse, - String txnId, String orderNo, String requestJson, String rawResponse, String responseType) { - PaymentStartRecord record = new PaymentStartRecord(); - record.setOrderNo(orderNo); - record.setRequestId(request.getReqId()); - record.setClientMid(request.getMid()); - record.setClientUserId(request.getClientUserId()); - record.setInternalTxnId(txnId); + public PaymentStartRecord savePaymentStartRecord(PaymentStartUserRequest request, + PaymentStartApiResponse apiResponse, String txnId, String orderNo, String requestJson, String rawResponse, + String responseType) { + + PaymentStartRecord record; + + boolean createNewRecord = apiResponse == null && rawResponse == null && responseType == null; + + if (createNewRecord) { + // 🔒 Duplicate check + if (paymentStartRecordRepository.findByOrderNo(orderNo).isPresent()) { + throw new CustomException("Duplicate OrderNo found", "DUPLICATE_ORDERNO", 400, + "OrderNo already exists"); + } + record = new PaymentStartRecord(); + } else { + record = paymentStartRecordRepository.findByOrderNo(orderNo) + .orElseThrow(() -> new CustomException("OrderNo not found", "ORDERNO_NOT_FOUND", 404, + "No record exists for given orderNo")); + } + + if (orderNo != null) { + record.setOrderNo(orderNo); + record.setRequestId(request.getReqId()); + record.setClientMid(request.getMid()); + record.setClientUserId(request.getClientUserId()); + record.setInternalTxnId(txnId); + } if (apiResponse != null && apiResponse.getResponseData() != null) { record.setAmount(Optional.ofNullable(apiResponse.getResponseData().getAmount()).orElse("0.00")); record.setTxnId(Optional.ofNullable(apiResponse.getResponseData().getTxnId()).orElse("DUMMY_TXN")); - record.setQrString(Optional.ofNullable(apiResponse.getResponseData().getQrString()).orElse("NO_QR")); // parse - // createdOn - // string - // into - // LocalDateTime - // and - // set - // on - // Auditable - LocalDateTime createdOn = parseToLocalDateTime(apiResponse.getResponseData().getCreatedOn()); + + record.setQrString(Optional.ofNullable(apiResponse.getResponseData().getQrString()).orElse("NO_QR")); + + Date createdOn = parseToDate(apiResponse.getResponseData().getCreatedOn()); record.setTxnStatus("PENDING"); } else { @@ -1116,12 +1122,9 @@ public class BillerMapperHelper { record.setTxnStatus("PENDING"); } - // store the original request body and raw response - record.setRequestBody(requestJson); - record.setResponseBody(rawResponse); record.setResponseType(responseType); - paymentStartRecordRepository.save(record); + return paymentStartRecordRepository.save(record); } // Map ComplaintRequestDTO to forward request for complaint API diff --git a/src/main/java/com/billpaymentservice/bbps/util/EncryptionUtil.java b/src/main/java/com/billpaymentservice/bbps/util/EncryptionUtil.java index 96a47d4..f7703d5 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/EncryptionUtil.java +++ b/src/main/java/com/billpaymentservice/bbps/util/EncryptionUtil.java @@ -29,7 +29,6 @@ public class EncryptionUtil { SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); - byte[] ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); byte[] encryptedData = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); diff --git a/src/main/java/com/billpaymentservice/bbps/util/Helper.java b/src/main/java/com/billpaymentservice/bbps/util/Helper.java index 6c62baf..827aff5 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/Helper.java +++ b/src/main/java/com/billpaymentservice/bbps/util/Helper.java @@ -18,6 +18,7 @@ import com.billpaymentservice.bbps.entites.BBPSMastersData; import com.billpaymentservice.bbps.entites.BharatConnectMaster; import com.billpaymentservice.bbps.entites.InboundTraceLog; import com.billpaymentservice.bbps.entites.OutboundTraceLog; +import com.billpaymentservice.bbps.entites.TransactionMaster; import com.billpaymentservice.bbps.entites.UserEntity; import com.billpaymentservice.bbps.exception.CustomException; import com.billpaymentservice.bbps.repository.BBPSMasterDataRepo; @@ -225,5 +226,63 @@ public class Helper { return userId; } + public void mergeNonNullFields(TransactionMaster src, TransactionMaster dest) { + /* + * LOGGER.info("TransactionMaster src: {}", src); + */ // Only copy values that are non-null (and non-blank for Strings) + if (src.getBankTxnId() != null && !src.getBankTxnId().isBlank()) dest.setBankTxnId(src.getBankTxnId()); + if (src.getTxnId() != null && !src.getTxnId().isBlank()) dest.setTxnId(src.getTxnId()); + if (src.getInitiatorTxnId() != null && !src.getInitiatorTxnId().isBlank()) dest.setInitiatorTxnId(src.getInitiatorTxnId()); + if (src.getCheckerTxnId() != null && !src.getCheckerTxnId().isBlank()) dest.setCheckerTxnId(src.getCheckerTxnId()); + if (src.getReqId() != null && !src.getReqId().isBlank()) dest.setReqId(src.getReqId()); + if (src.getMid() != null && !src.getMid().isBlank()) dest.setMid(src.getMid()); + if (src.getClientUserId() != null && !src.getClientUserId().isBlank()) dest.setClientUserId(src.getClientUserId()); + if (src.getClientReqId() != null && !src.getClientReqId().isBlank()) dest.setClientReqId(src.getClientReqId()); + if (src.getUserId() != null && !src.getUserId().isBlank()) dest.setUserId(src.getUserId()); + if (src.getBillerId() != null && !src.getBillerId().isBlank()) dest.setBillerId(src.getBillerId()); + if (src.getTxnAmount() != null) dest.setTxnAmount(src.getTxnAmount()); + if (src.getStatus() != null && !src.getStatus().isBlank()) dest.setStatus(src.getStatus()); + if (src.getPaymentInformation() != null && !src.getPaymentInformation().isBlank()) dest.setPaymentInformation(src.getPaymentInformation()); + if (src.getCreationDateBank() != null) dest.setCreationDateBank(src.getCreationDateBank()); + if (src.getModifiedDateBank() != null) dest.setModifiedDateBank(src.getModifiedDateBank()); + if (src.getCreatedEpochDateBank() != null) dest.setCreatedEpochDateBank(src.getCreatedEpochDateBank()); + if (src.getModifiedEpochDateBank() != null) dest.setModifiedEpochDateBank(src.getModifiedEpochDateBank()); + /* ---- additional fields ---- */ + if (src.getRefId() != null && !src.getRefId().isBlank()) dest.setRefId(src.getRefId()); + if (src.getPaymentRefId() != null && !src.getPaymentRefId().isBlank()) dest.setPaymentRefId(src.getPaymentRefId()); + if (src.getRemitterName() != null && !src.getRemitterName().isBlank()) dest.setRemitterName(src.getRemitterName()); + if (src.getOrderId() != null && !src.getOrderId().isBlank()) dest.setOrderId(src.getOrderId()); + if (src.getBillId() != null && !src.getBillId().isBlank()) dest.setBillId(src.getBillId()); + if (src.getUserType() != null && !src.getUserType().isBlank()) dest.setUserType(src.getUserType()); + if (src.getTotalTxnAmount() != null) dest.setTotalTxnAmount(src.getTotalTxnAmount()); + if (src.getBillerCategory() != null && !src.getBillerCategory().isBlank()) dest.setBillerCategory(src.getBillerCategory()); + if (src.getBillerName() != null && !src.getBillerName().isBlank()) dest.setBillerName(src.getBillerName()); + if (src.getOffUsPay() != null) dest.setOffUsPay(src.getOffUsPay()); + if (src.getSplitPay() != null) dest.setSplitPay(src.getSplitPay()); + if (src.getQuickPay() != null) dest.setQuickPay(src.getQuickPay()); + if (src.getPaymentMode() != null && !src.getPaymentMode().isBlank()) dest.setPaymentMode(src.getPaymentMode()); + if (src.getAgentPaymentInformation() != null && !src.getAgentPaymentInformation().isBlank()) dest.setAgentPaymentInformation(src.getAgentPaymentInformation()); + if (src.getCustConvFee() != null) dest.setCustConvFee(src.getCustConvFee()); + if (src.getCurrency() != null && !src.getCurrency().isBlank()) dest.setCurrency(src.getCurrency()); + if (src.getRequestedIp() != null && !src.getRequestedIp().isBlank()) dest.setRequestedIp(src.getRequestedIp()); + if (src.getCustomerMobNo() != null && !src.getCustomerMobNo().isBlank()) dest.setCustomerMobNo(src.getCustomerMobNo()); + if (src.getMacAddress() != null && !src.getMacAddress().isBlank()) dest.setMacAddress(src.getMacAddress()); + if (src.getSplitPayAmount() != null) dest.setSplitPayAmount(src.getSplitPayAmount()); + if (src.getCouCustConvFee() != null) dest.setCouCustConvFee(src.getCouCustConvFee()); + if (src.getSettlementVerification() != null) dest.setSettlementVerification(src.getSettlementVerification()); + if (src.getFundTxnType() != null && !src.getFundTxnType().isBlank()) dest.setFundTxnType(src.getFundTxnType()); + if (src.getReason() != null && !src.getReason().isBlank()) dest.setReason(src.getReason()); + if (src.getAmountTags() != null && !src.getAmountTags().isBlank()) dest.setAmountTags(src.getAmountTags()); + if (src.getPlanId() != null && !src.getPlanId().isBlank()) dest.setPlanId(src.getPlanId()); + if (src.getPlanType() != null && !src.getPlanType().isBlank()) dest.setPlanType(src.getPlanType()); + if (src.getTxnNpciId() != null && !src.getTxnNpciId().isBlank()) dest.setTxnNpciId(src.getTxnNpciId()); + + /* ---- lists (direct replace when provided) ---- */ + if (src.getDeviceBlockTags() != null && !src.getDeviceBlockTags().isEmpty()) dest.setDeviceBlockTags(src.getDeviceBlockTags()); + if (src.getCustomerParams() != null && !src.getCustomerParams().isEmpty()) dest.setCustomerParams(src.getCustomerParams()); + + LOGGER.info("TransactionMaster dest after merge: {}", dest); + } + } \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 7efe143..3a7a57a 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -76,7 +76,7 @@ jwt.secret=6C756148487475787A414646736D4B6E444E585A766145396E6A4555594A595566657 # Resilience4j Rate Limiter # =============================== -resilience4j.ratelimiter.configs.default.limit-for-period=5 +resilience4j.ratelimiter.configs.default.limit-for-period=1000 resilience4j.ratelimiter.configs.default.limit-refresh-period=1m resilience4j.ratelimiter.configs.default.timeout-duration=0 # Resilience4j Retry