package com.billpaymentservice.bbps.serviceImpl; import java.util.Map; import java.util.Optional; import org.modelmapper.ModelMapper; import org.modelmapper.convention.MatchingStrategies; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Value; 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.request.BillPaymentRequest; import com.billpaymentservice.bbps.dto.request.TransactionStatusRequest; import com.billpaymentservice.bbps.dto.request.TrasactionStatusByClientReqIdRequest; import com.billpaymentservice.bbps.dto.response.BillPaymentResponse; import com.billpaymentservice.bbps.dto.response.TransactionStatusResponse; import com.billpaymentservice.bbps.dto.sentRequest.BillPaymentForwardRequest; import com.billpaymentservice.bbps.entites.AgentFundTransfer; 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; import com.billpaymentservice.bbps.util.BBPSConstant; import com.billpaymentservice.bbps.util.BillerMapperHelper; import com.billpaymentservice.bbps.util.EntityRelationshipUtil; import com.billpaymentservice.bbps.util.Helper; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.core.type.TypeReference; import com.fasterxml.jackson.databind.ObjectMapper; @Service public class PaymentServiceImpl implements PaymentService { @Value("${payment.data.url}") private String paymentUrl; private final BharatConnectMasterRepository beneficiaryDetailsRepository; private final HttpService httpService; private final Helper helper; 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; // Shared reusable instances private final ObjectMapper objectMapper; private final ModelMapper modelMapper; private static final Logger LOGGER = LoggerFactory.getLogger(PaymentServiceImpl.class); public PaymentServiceImpl(BharatConnectMasterRepository beneficiaryDetailsRepository, HttpService httpService, Helper helper, PaymentRepository paymentRepository, InboundTraceLogRepository inboundTraceLogRepository, OutboundTraceLogRepository outboundTraceLogRepository, PaymentIntiateRepository paymentIntiateRepository, TransactionMasterRepository transactionMasterRepository, AgentFundTransferRepository agentFundTransferRepository, BillerMapperHelper mapperHelper) { this.beneficiaryDetailsRepository = beneficiaryDetailsRepository; this.httpService = httpService; this.helper = helper; this.paymentRepository = paymentRepository; this.inboundTraceLogRepository = inboundTraceLogRepository; this.outboundTraceLogRepository = outboundTraceLogRepository; this.paymentIntiateRepository = paymentIntiateRepository; this.transactionMasterRepository = transactionMasterRepository; this.agentFundTransferRepository = agentFundTransferRepository; this.mapperHelper = mapperHelper; this.objectMapper = new ObjectMapper(); this.objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); this.modelMapper = new ModelMapper(); this.modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); } @Override public ResponseModel initiatePayment(BillPaymentRequest billPaymentRequest) { String apiName = BBPSConstant.API_INITIATE_PAYMENT; LOGGER.info("[{}] START | reqId={} | mid={}", apiName, billPaymentRequest.getReqId(), billPaymentRequest.getMid()); String txnId = helper.generateTxnId(15); String requestId = billPaymentRequest.getReqId(); String userId = billPaymentRequest.getClientReqId(); String url = paymentUrl + BBPSConstant.PAYMENT_URL; billPaymentRequest.setClientReqId(txnId); LOGGER.info("Initiating payment for reqId={} and txnId={} and userId={}", requestId, txnId, userId); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); HttpHeaders headers = helper.buildHeaders(headersData); BillPaymentForwardRequest forwardReq = mapperHelper.billPaymentForwardRequest(billPaymentRequest); String forwardjson = helper.convertObjectToJson(forwardReq); String requestJson = helper.convertObjectToJson(billPaymentRequest); InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnId, apiName, billPaymentRequest.getMid(), 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); 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); 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); throw new CustomException(BBPSConstant.EMPTY_RESPONSE_FROM_BBPS); } 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()); } 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()); 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); 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()); } 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); throw new CustomException("Invalid JSON received from API"); } } public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest, InboundTraceLog inboundLog) { String apiName = BBPSConstant.API_CHECK_TRANSACTION_STATUS; String txnId = helper.generateTxnId(15); String requestId = transactionStatusRequest.getReqId(); // START log for internal transaction status check LOGGER.info("[{}] START (internal) | checkTxnId={} | reqId={} | bankTxnId={}", apiName, txnId, requestId, transactionStatusRequest.getTxnId()); String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId(); LOGGER.info("Checking transaction status (internal) for txnId={} and reqId={}", transactionStatusRequest.getTxnId(), requestId); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); HttpHeaders headers = helper.buildHeaders(headersData); OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, BBPSConstant.HTTP_GET, null); LOGGER.debug("Outbound log created for internal status check | txnId={} | reqId={}", txnId, 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); 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("Invalid JSON received from API"); } } @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(); 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); 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); 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); 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"))); } TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, TransactionStatusResponse.class); GeneralResp generalResp = responseWrapper.getGeneralResp(); String status = generalResp.getStatus(); LOGGER.info("Current transaction status: {}", status); 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("Invalid JSON received from API"); } } @Override public ResponseModel checkTrasactionStatusByClientReqId( TrasactionStatusByClientReqIdRequest transactionStatusRequest) { helper.validateMerchant(transactionStatusRequest.getMid()); String txnID = helper.generateTxnId(15); String apiName = BBPSConstant.API_CHECK_BY_CLIENT_REQ_ID; String requestId = transactionStatusRequest.getReqId(); String clientReqId = transactionStatusRequest.getClientReqId(); String requestJson = helper.convertObjectToJson(transactionStatusRequest); LOGGER.info("[{}] START | reqId={} | mid={} | clientReqId={}", apiName, requestId, transactionStatusRequest.getMid(), clientReqId); Optional paymentInitiate = paymentIntiateRepository.findByReqId(clientReqId); if (paymentInitiate.isEmpty()) { throw new CustomException(BBPSConstant.NO_BILL_FOUND); } String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_BY_CLIENT_REQ_ID_URL + paymentInitiate.get().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); LOGGER.debug("Inbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID); OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET, null); LOGGER.debug("Outbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID); 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); } catch (JsonProcessingException e) { LOGGER.error("Error parsing JSON in transaction status API: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); throw new CustomException("Invalid JSON received from API"); } } }