From d34b2e1b845cf444a3ee1cf66d6107c59c5705b7 Mon Sep 17 00:00:00 2001 From: Lincpay Tech Date: Thu, 18 Dec 2025 10:20:49 +0530 Subject: [PATCH] changed trasaction master saving --- .../bbps/entites/TransactionMaster.java | 11 +- .../TransactionMasterRepository.java | 3 + .../bbps/serviceImpl/PaymentServiceImpl.java | 528 ++++++++++-------- .../bbps/util/BBPSConstant.java | 4 +- 4 files changed, 306 insertions(+), 240 deletions(-) diff --git a/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java b/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java index d563f8d..88124d4 100644 --- a/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java +++ b/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java @@ -30,7 +30,7 @@ import lombok.ToString; public class TransactionMaster extends Auditable { // 👇 JSON me "txn_id" aayega, ye DB me bank_txn_id column me save hoga - @Id + @JsonProperty("txn_id") @Column(name = "bank_txn_id", nullable = false, unique = true) private String bankTxnId; @@ -38,6 +38,15 @@ public class TransactionMaster extends Auditable { // 👇 Apna internal transaction id, manually generate or system ke through @Column(name = "txn_id") private String txnId; + + // 👇 New: txn id generated at the time of initiatePayment + @Id + @Column(name = "initiator_txn_id") + private String initiatorTxnId; + + // 👇 New: txn id generated during internal check transaction status + @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/TransactionMasterRepository.java b/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java index 1faa4b7..00ae295 100644 --- a/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java +++ b/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java @@ -37,4 +37,7 @@ public interface TransactionMasterRepository extends JpaRepository findByStatusIn(List statuses); + // Find by initiatorTxnId so services can look up records created during initiatePayment + TransactionMaster findByInitiatorTxnId(String initiatorTxnId); + } \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java index 4549eef..2443c0b 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java @@ -13,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; @@ -115,22 +116,23 @@ public class PaymentServiceImpl implements PaymentService { // save here should not prevent the outgoing request, so we catch and log errors. try { TransactionMaster preTransaction = new TransactionMaster(); - preTransaction.setBankTxnId("INITIATED_" + txnId); + preTransaction.setBankTxnId(txnId+"bank"); 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("INITIATED"); + preTransaction.setStatus("PENDING"); EntityRelationshipUtil.linkTransactionMasterRelationships(preTransaction); - saveTransactionMasterWithRetry(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); } catch (Exception e) { LOGGER.warn("Failed to persist pre-request TransactionMaster for txnId={}: {}", txnId, e.getMessage()); } - - try { LOGGER.info("Calling BBPS payment URL: {}", url); Map responseMap = httpService.postRequest(url, forwardjson, headers); String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); @@ -139,139 +141,47 @@ public class PaymentServiceImpl implements PaymentService { helper.updateOutboundLogResponse(outboundLog, responseData); LOGGER.debug("Outbound log response updated 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); - // Persist a TransactionMaster placeholder so scheduler can pick it up later - TransactionMaster tm = new TransactionMaster(); - tm.setBankTxnId("PENDING_" + txnId); - tm.setTxnId(txnId); - tm.setReqId(requestId); - tm.setMid(billPaymentRequest.getMid()); - tm.setClientUserId(billPaymentRequest.getClientUserId()); - tm.setClientReqId(billPaymentRequest.getClientReqId()); - tm.setUserId(userId); - tm.setStatus("PENDING_UPSTREAM"); - transactionMasterRepository.save(tm); - LOGGER.info("TransactionMaster placeholder saved with status=PENDING_UPSTREAM for txnId={}", txnId); - return new ResponseModel(BBPSConstant.EMPTY_RESPONSE_FROM_BBPS, "PENDING_UPSTREAM", 502, null); - } - Map dataMap = objectMapper.readValue(responseData, + Map dataMap; + try { + 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); - // Save a TransactionMaster placeholder so scheduler/admin can reconcile later - TransactionMaster tm = new TransactionMaster(); - tm.setBankTxnId("INVALID_" + txnId); - tm.setTxnId(txnId); - tm.setReqId(requestId); - tm.setMid(billPaymentRequest.getMid()); - tm.setClientUserId(billPaymentRequest.getClientUserId()); - tm.setClientReqId(billPaymentRequest.getClientReqId()); - tm.setUserId(userId); - tm.setStatus("INVALID_RESPONSE_FROM_BBPS"); - transactionMasterRepository.save(tm); - LOGGER.info("TransactionMaster placeholder saved with status=INVALID_RESPONSE_FROM_BBPS for txnId={}", txnId); - return new ResponseModel(BBPSConstant.INVALID_STRUCTURE, "INVALID_RESPONSE_FROM_BBPS", 502, null); - } + + BillPaymentResponse billPaymentResponse = objectMapper.convertValue(dataMap, BillPaymentResponse.class); GeneralResp generalResponse = billPaymentResponse.getGenericResponse(); LOGGER.info("genric Response" + generalResponse); - if(generalResponse.getStatus().equalsIgnoreCase("Failed")) { - // Save TransactionMaster with failed status so it can be reconciled later - TransactionMaster tm = new TransactionMaster(); - tm.setBankTxnId(billPaymentResponse.getTxnId()); - tm.setTxnId(txnId); - tm.setReqId(requestId); - tm.setMid(billPaymentRequest.getMid()); - tm.setClientUserId(billPaymentRequest.getClientUserId()); - tm.setClientReqId(billPaymentRequest.getClientReqId()); - tm.setUserId(userId); - tm.setStatus(generalResponse.getStatus()); - // ensure internal txnId uniqueness and save - tm.setTxnId(makeUniqueTxnId(tm.getTxnId())); - saveTransactionMasterWithRetry(tm); - LOGGER.info("TransactionMaster saved with failed status={} for txnId={}", generalResponse.getStatus(), txnId); - return new ResponseModel(generalResponse.getMessage(), generalResponse.getStatus(), - Integer.parseInt(generalResponse.getStatusCode()), billPaymentResponse); - } - + BillPayment entity = modelMapper.map(billPaymentResponse, BillPayment.class); paymentRepository.save(entity); LOGGER.info("BillPayment entity saved successfully for txnId={}", txnId); + helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus()); - // Create and save TransactionMaster for this initiate flow so everything is stored in the same table - try { - TransactionMaster transactionMaster = new TransactionMaster(); - // bankTxnId is BBPS txn id (primary key) - transactionMaster.setBankTxnId(billPaymentResponse.getTxnId()); - // use internal txnId generated earlier for tracing - transactionMaster.setTxnId(txnId); - transactionMaster.setReqId(requestId); - transactionMaster.setMid(billPaymentRequest.getMid()); - transactionMaster.setClientUserId(billPaymentRequest.getClientUserId()); - transactionMaster.setClientReqId(billPaymentRequest.getClientReqId()); - transactionMaster.setUserId(userId); - transactionMaster.setBillerId(billPaymentResponse.getBillerId()); - transactionMaster.setTxnAmount(billPaymentResponse.getTxnAmount()); - transactionMaster.setStatus(billPaymentResponse.getStatus()); - EntityRelationshipUtil.linkTransactionMasterRelationships(transactionMaster); - // ensure internal txnId uniqueness and save (handles collisions) - transactionMaster.setTxnId(makeUniqueTxnId(transactionMaster.getTxnId())); - saveTransactionMasterWithRetry(transactionMaster); - LOGGER.info("TransactionMaster created during initiatePayment for internalTxnId={} bankTxnId={}", transactionMaster.getTxnId(), transactionMaster.getBankTxnId()); - } catch (Exception e) { - LOGGER.warn("Failed to persist TransactionMaster during initiatePayment for txnId={}: {}", txnId, e.getMessage()); - } TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest(); - transactionStatusRequest.setTxnId(billPaymentResponse.getTxnId()); - transactionStatusRequest.setReqId(requestId); - transactionStatusRequest.setMid(billPaymentRequest.getMid()); - transactionStatusRequest.setClientUserId(billPaymentRequest.getClientUserId()); - - try { - Thread.sleep((long) BBPSConstant.PAYMENT_STATUS_DELAY_MS); - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - LOGGER.warn("Thread interrupted during payment delay: {}", e.getMessage()); + transactionStatusRequest.setReqId(billPaymentRequest.getReqId()); + transactionStatusRequest.setTxnId(entity.getTxnId()); + ResponseModel responseModel = checkTrasactionStatus(transactionStatusRequest, inboundLog); + return responseModel; + } catch (JsonProcessingException 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()); } - LOGGER.info("Checking transaction status for txnId={}", billPaymentResponse.getTxnId()); - LOGGER.info("[{}] END | reqId={} | txnId={}", apiName, requestId, txnId); - return checkTrasactionStatus(transactionStatusRequest, inboundLog); - - } catch (JsonProcessingException e) { - LOGGER.error("JSON Parsing Error in initiatePayment: {}", e.getMessage(), e); - helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - // Persist TransactionMaster placeholder so admin/scheduler can reconcile later - TransactionMaster tm = new TransactionMaster(); - tm.setBankTxnId("INVALID_JSON_" + txnId); - tm.setTxnId(txnId); - tm.setUserId(billPaymentRequest.getClientReqId()); - tm.setReqId(billPaymentRequest.getReqId()); - tm.setMid(billPaymentRequest.getMid()); - tm.setClientUserId(billPaymentRequest.getClientUserId()); - tm.setClientReqId(billPaymentRequest.getClientReqId()); - tm.setStatus("INVALID_JSON_FROM_UPSTREAM"); - transactionMasterRepository.save(tm); - LOGGER.info("TransactionMaster saved with status=INVALID_JSON_FROM_UPSTREAM for txnId={}", txnId); - throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); - } - } + + + + } 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(); @@ -282,21 +192,20 @@ 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>() { - }); + 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()); @@ -306,38 +215,83 @@ public class PaymentServiceImpl implements PaymentService { } TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, - TransactionStatusResponse.class); + TransactionStatusResponse.class); GeneralResp generalResp = responseWrapper.getGeneralResp(); - LOGGER.info("Transaction status received: {}", generalResp.getStatus()); - - TransactionMaster transactionMaster = modelMapper.map(responseWrapper.getTransactionDAO(), + // Map DTO -> Entity, but explicitly copy bankTxnId from DTO to entity to avoid mapping gaps + TransactionDTO txDao = responseWrapper.getTransactionDAO(); + + TransactionMaster transactionMaster = modelMapper.map(txDao, TransactionMaster.class); - transactionMaster.setTxnId(txnId); + LOGGER.info("INBOPUND 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 + mergeNonNullFields(transactionMaster, existingTransaction); + transactionMaster = existingTransaction; + } + + // Ensure bankTxnId from DTO is set if provided + if (txDao != null && txDao.getBankTxnId() != null && !txDao.getBankTxnId().isBlank()) { + transactionMaster.setBankTxnId(txDao.getBankTxnId()); + } + + // do not overwrite existing txnId if present; set checker txn id when missing + if (transactionMaster.getTxnId() == null || transactionMaster.getTxnId().isBlank()) { + transactionMaster.setTxnId(checkerTxnId); + } + transactionMaster.setCheckerTxnId(checkerTxnId); transactionMaster.setReqId(transactionStatusRequest.getReqId()); transactionMaster.setMid(transactionStatusRequest.getMid()); transactionMaster.setClientUserId(transactionStatusRequest.getClientUserId()); EntityRelationshipUtil.linkTransactionMasterRelationships(transactionMaster); - // Ensure internal txnId is unique; if collisions occur append suffix (_1, _2...) - transactionMaster.setTxnId(makeUniqueTxnId(transactionMaster.getTxnId())); - saveTransactionMasterWithRetry(transactionMaster); - LOGGER.info("TransactionMaster saved successfully for txnId={}", txnId); + // Ensure internal txnId exists + transactionMaster.setTxnId(ensureTxnId(transactionMaster.getTxnId())); - 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); + // If bankTxnId (primary key) is missing, assign a placeholder so the entity can be saved + if (transactionMaster.getBankTxnId() == null || transactionMaster.getBankTxnId().isBlank()) { + String placeholderBankId = "GEN_" + transactionMaster.getTxnId(); + transactionMaster.setBankTxnId(placeholderBankId); + LOGGER.warn("bankTxnId missing in response; assigning placeholder bankTxnId={} for txnId={}", placeholderBankId, transactionMaster.getTxnId()); + } + + // Map and persist AgentFundTransfer if present + AgentFundTransfer agentFundTransfer = null; + try { + 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()); + } catch (Exception e) { + LOGGER.debug("No AgentFundTransfer in response or mapping failed: {}", e.getMessage()); + } + + // Persist updated transaction master and agent fund transfer so DB reflects merged non-null fields + try { + transactionMasterRepository.save(transactionMaster); + if (agentFundTransfer != null) { agentFundTransferRepository.save(agentFundTransfer); } + LOGGER.info("Persisted TransactionMaster and AgentFundTransfer for txnId={} bankTxnId={}" + , transactionMaster.getTxnId(), transactionMaster.getBankTxnId()); + } catch (Exception e) { + LOGGER.warn("Failed to persist TransactionMaster/AgentFundTransfer for txnId={}: {}", transactionMaster.getTxnId(), e.getMessage(), e); + } 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()); + LOGGER.info("[{}] END (internal) | checkTxnId={} | reqId={} | status={}", apiName, checkerTxnId, 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); @@ -348,68 +302,141 @@ public class PaymentServiceImpl implements PaymentService { @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 + try { + String initiatorTxnId = transactionStatusRequest.getTxnId(); + if (initiatorTxnId != null && !initiatorTxnId.isBlank()) { + + } + } + catch (Exception e) { + // Log but continue to fallback behavior + LOGGER.warn("Error while updating by initiatorTxnId: {}", e.getMessage()); + } - 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); + 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"))); + } - 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 --- + try { + TransactionDTO txDao = responseWrapper.getTransactionDAO(); + TransactionMaster updatedEntity = null; + + // 1) Try to find by initiatorTxnId (client may pass initiator txn id) + if (transactionStatusRequest.getTxnId() != null && !transactionStatusRequest.getTxnId().isBlank()) { + TransactionMaster byInitiator = transactionMasterRepository.findByInitiatorTxnId(transactionStatusRequest.getTxnId()); + if (byInitiator != null) { + updatedEntity = byInitiator; + } + } + + // 2) If not found, try find by internal txnId (txn_id column) + if (updatedEntity == null && txDao != null && txDao.getBankTxnId() != null && !txDao.getBankTxnId().isBlank()) { + try { + java.util.List byTxnIdList = transactionMasterRepository.findByTxnId(txDao.getBankTxnId()); + if (byTxnIdList != null && !byTxnIdList.isEmpty()) { + updatedEntity = byTxnIdList.get(0); + } + } catch (Exception ignore) { + LOGGER.debug("Error while searching by txnId: {}", ignore.getMessage()); + } + } + + // 3) As a last attempt, if request passed an id that matches internal txnId column + if (updatedEntity == null && transactionStatusRequest.getTxnId() != null && !transactionStatusRequest.getTxnId().isBlank()) { + try { + java.util.List byTxnIdList2 = transactionMasterRepository.findByTxnId(transactionStatusRequest.getTxnId()); + if (byTxnIdList2 != null && !byTxnIdList2.isEmpty()) { + updatedEntity = byTxnIdList2.get(0); + } + } catch (Exception ignore) { + LOGGER.debug("Error while searching by txnId (fallback): {}", ignore.getMessage()); + } + } + + // If we found an entity, merge non-null fields from DTO and save + if (updatedEntity != null) { + TransactionMaster mapped = modelMapper.map(txDao, TransactionMaster.class); + mergeNonNullFields(mapped, updatedEntity); + // ensure internal txnId exists + updatedEntity.setTxnId(ensureTxnId(updatedEntity.getTxnId())); + EntityRelationshipUtil.linkTransactionMasterRelationships(updatedEntity); + try { + transactionMasterRepository.save(updatedEntity); + LOGGER.info("Updated TransactionMaster in DB for initiatorTxnId={} (bankTxnId={})", + updatedEntity.getInitiatorTxnId(), updatedEntity.getBankTxnId()); + } catch (Exception e) { + LOGGER.warn("Failed to persist updated TransactionMaster: {}", e.getMessage(), e); + } + } else { + LOGGER.debug("No matching TransactionMaster found to update for txnId={}", transactionStatusRequest.getTxnId()); + } + } catch (Exception e) { + LOGGER.warn("Error while attempting to persist transaction status update: {}", e.getMessage()); + } + // --- END NEW PERSISTENCE BLOCK --- + + 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; + + } 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()); + } + } @Override public ResponseModel checkTrasactionStatusByClientReqId( @@ -471,14 +498,14 @@ public class PaymentServiceImpl implements PaymentService { LOGGER.debug("Outbound log response updated for clientReqId check | reqId={} | txnId={}", requestId, txnID); Map dataMap = objectMapper.readValue(responseData, - new TypeReference>() { - }); + 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); + responseData); throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")), Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), @@ -486,7 +513,7 @@ public class PaymentServiceImpl implements PaymentService { } TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, - TransactionStatusResponse.class); + TransactionStatusResponse.class); GeneralResp generalResp = responseWrapper.getGeneralResp(); @@ -494,7 +521,7 @@ public class PaymentServiceImpl implements PaymentService { helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); LOGGER.info("[{}] END | reqId={} | clientReqId={} | status={}", apiName, requestId, clientReqId, - generalResp.getStatus()); + generalResp.getStatus()); return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), Integer.parseInt(generalResp.getStatusCode()), responseWrapper); @@ -506,51 +533,78 @@ public class PaymentServiceImpl implements PaymentService { } /** - * Ensure the provided internal txnId is unique in bbps_transaction_master.txn_id. - * If a record already exists with the same txnId, append _1, _2, ... until unique. + * Return the provided txnId or generate a new one when the provided value is null/blank. + * This intentionally does NOT try to ensure database-level uniqueness (no suffixing or retries). */ - private String makeUniqueTxnId(String baseTxnId) { - if (baseTxnId == null || baseTxnId.isBlank()) return baseTxnId; - List existing = transactionMasterRepository.findByTxnId(baseTxnId); - if (existing == null || existing.isEmpty()) { - return baseTxnId; - } - int suffix = 1; - String candidate; - while (true) { - candidate = baseTxnId + "_" + suffix; - List coll = transactionMasterRepository.findByTxnId(candidate); - if (coll == null || coll.isEmpty()) { - return candidate; - } - suffix++; + private String ensureTxnId(String txnId) { + if (txnId == null || txnId.isBlank()) { + return helper.generateTxnId(15); } + return txnId; } /** - * Try to save TransactionMaster and handle unique txnId collisions that may occur under concurrency. - * This method will attempt to save up to maxAttempts; on duplicate collision it will append a numeric suffix. + * Merge non-null fields from src into dest. This keeps existing DB fields when the incoming + * payload doesn't provide them. Only non-null (and for Strings, non-blank) fields from src + * will overwrite dest fields. */ - private void saveTransactionMasterWithRetry(TransactionMaster transactionMaster) { - final int maxAttempts = 5; - String baseTxnId = transactionMaster.getTxnId(); - for (int attempt = 0; attempt < maxAttempts; attempt++) { - try { - if (attempt == 0) { - transactionMaster.setTxnId(makeUniqueTxnId(baseTxnId)); - } else { - transactionMaster.setTxnId(baseTxnId + "_" + attempt); - } - transactionMasterRepository.save(transactionMaster); - return; // success - } catch (org.springframework.dao.DataIntegrityViolationException dex) { - LOGGER.warn("Duplicate txnId detected when saving TransactionMaster (attempt {}) for baseTxnId={}: {}", attempt, baseTxnId, dex.getMessage()); - // retry with next suffix - } - } - // if we reach here, all attempts failed - LOGGER.error("Failed to save TransactionMaster after {} attempts for baseTxnId={}", maxAttempts, transactionMaster.getTxnId()); - throw new CustomException("Unable to save TransactionMaster due to txnId collisions", BBPSConstant.FAILED_STATUS, 500, "Duplicate txnId"); + private 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.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); } } diff --git a/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java b/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java index c151e3d..d8b5e09 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java +++ b/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java @@ -51,8 +51,8 @@ public class BBPSConstant extends BBPS_RESPONSE_COD_CONSTANT { public static final String BILL_FETCH_BY_ID = "/couapp/bills/fetch/byId?billId="; public static final String BILL_FETCH_BY_CLIENT_REQ_ID = "/couapp/bills/fetch/byClientId?clientReqId="; public static final String BILL_VALIDATE_BY_CLIENT_REQ_ID = "/couapp/bills/validate/byClientId?clientReqId="; - public static final String PAYMENT_URL = "/agent/bills/paymhent"; - public static final String CHECK_TRANSACTION_URL = "/transactions/payment/byTxnId?txnId=j"; + public static final String PAYMENT_URL = "/agent/bills/payment"; + public static final String CHECK_TRANSACTION_URL = "/transactions/payment/byTxnId?txnId="; public static final String CHECK_TRANSACTION_BY_CLIENT_URL = "/bills/fetch/byClientId?clientReqId="; public static final String CHECK_TRANSACTION_BY_CLIENT_REQ_ID_URL = "/transactions/payment/byClientId?clientReqId=";