package com.billpaymentservice.bbps.serviceImpl; import java.time.LocalDateTime; import java.util.Map; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; import org.springframework.web.client.RestClient; import com.billpaymentservice.bbps.dto.GeneralResp; import com.billpaymentservice.bbps.dto.ResponseModel; import com.billpaymentservice.bbps.dto.request.BillFetchByBillIdRequest; import com.billpaymentservice.bbps.dto.request.FetchBillRequest; import com.billpaymentservice.bbps.dto.request.FetchBillValidatestRequest; import com.billpaymentservice.bbps.dto.response.BillDataResponse; import com.billpaymentservice.bbps.dto.response.FetchBillResponse; import com.billpaymentservice.bbps.dto.response.FetchBillValidatestResponse; import com.billpaymentservice.bbps.entites.BharatConnectMaster; import com.billpaymentservice.bbps.entites.InboundTraceLog; import com.billpaymentservice.bbps.entites.OutboundTraceLog; import com.billpaymentservice.bbps.exception.CustomException; import com.billpaymentservice.bbps.repository.BharatConnectMasterRepository; import com.billpaymentservice.bbps.repository.InboundTraceLogRepository; import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository; import com.billpaymentservice.bbps.service.AgentService; import com.billpaymentservice.bbps.util.BBPSConstant; 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; import lombok.extern.slf4j.Slf4j; @Service @Slf4j public class AgentServiceImpl implements AgentService { @Value("${agent.data.url}") private String agentDtaUrl; private final RestClient restClient; private final HttpService httpService; private final Helper helper; private final BharatConnectMasterRepository beneficiaryDetailsRepository; public AgentServiceImpl(RestClient restClient, HttpService httpService, Helper helper, BharatConnectMasterRepository beneficiaryDetailsRepository,InboundTraceLogRepository inboundTraceLogRepository, OutboundTraceLogRepository outboundTraceLogRepository) { this.restClient = restClient; this.httpService = httpService; this.helper = helper; this.beneficiaryDetailsRepository = beneficiaryDetailsRepository; } @Override public ResponseModel initiateBillFetch(FetchBillRequest fetchBillRequest) { String apiName = "InitiateBillFetch"; String txnId = helper.generateTxnId(15); String requestId = fetchBillRequest.getReqId(); String url = agentDtaUrl + BBPSConstant.BILL_FETCH_INTI; helper.validateMerchant(fetchBillRequest.getMid()); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId("ORG001"); HttpHeaders headers = new HttpHeaders(); headers.set("x-digiledge-key", headersData.getXDigiledgeKey()); headers.set("agentId", headersData.getAgentId()); headers.set("tenantId", headersData.getTenantId()); headers.setContentType(MediaType.APPLICATION_JSON); ObjectMapper objectMapper = new ObjectMapper(); objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String requestJson = helper.convertObjectToJson(fetchBillRequest); // ✅ Create inbound & outbound logs InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnId, apiName, fetchBillRequest.getMid(), "POST", requestJson); OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, "POST", requestJson); try { Map responseMap = httpService.postRequest(url, requestJson, headers); String responseData = responseMap.get("data"); log.info("📦 Bill Fetch API Response: {}", responseData); helper.updateOutboundLogResponse(outboundLog, responseData); if (responseData == null || responseData.isEmpty()) { helper.updateInboundLogFailure(inboundLog, "Empty response from BBPS", "FAILED"); throw new CustomException("Empty response from BBPS server"); } Map dataMap = objectMapper.readValue(responseData, new TypeReference>() {}); if (!dataMap.containsKey("genericResponse")) { helper.updateInboundLogFailure(inboundLog, responseData, "INVALID_STRUCTURE"); throw new CustomException("Invalid response structure from BBPS"); } FetchBillResponse fetchBillResponse = objectMapper.convertValue(dataMap, FetchBillResponse.class); GeneralResp generalResponse = fetchBillResponse.getGenericResponse(); helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus()); // ✅ Directly call fetchBillById (no polling) BillFetchByBillIdRequest billFetchByBillIdRequest = new BillFetchByBillIdRequest(); billFetchByBillIdRequest.setBillId(fetchBillResponse.getBillId()); billFetchByBillIdRequest.setMid(fetchBillRequest.getMid()); billFetchByBillIdRequest.setReqId(requestId); return fetchBillById(billFetchByBillIdRequest, inboundLog); } catch (JsonProcessingException e) { log.error("❌ JSON Parsing Error: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), "JSON_ERROR"); throw new CustomException("Invalid JSON received from API"); } catch (CustomException ce) { throw ce; } catch (Exception e) { log.error("❌ Exception in initiateBillFetch(): {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), "EXCEPTION"); throw new CustomException("Bill Fetch initiation failed: " + e.getMessage()); } } public ResponseModel fetchBillById(BillFetchByBillIdRequest billFetchByBillIdRequest, InboundTraceLog inboundLog) { String apiName = "FetchBillById"; String txnId = helper.generateTxnId(15); String requestId = billFetchByBillIdRequest.getReqId(); String billId = billFetchByBillIdRequest.getBillId(); String url = agentDtaUrl + BBPSConstant.BILL_FETCH_BY_ID + "1ced7566-1add-4e01-9ed6-04cd67ac9684"; helper.validateMerchant(billFetchByBillIdRequest.getMid()); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId("ORG001"); HttpHeaders headers = new HttpHeaders(); headers.set("x-digiledge-key", headersData.getXDigiledgeKey()); headers.set("agentId", headersData.getAgentId()); headers.set("tenantId", headersData.getTenantId()); headers.setContentType(MediaType.APPLICATION_JSON); ObjectMapper objectMapper = new ObjectMapper(); OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, "GET", null); // ✅ Loop setup long startTime = System.currentTimeMillis(); long maxWaitTimeMs = 40000; // 40 seconds long intervalMs = 5000; // 5 seconds while (true) { try { Map responseMap = httpService.getRequest(url, headers); String responseData = responseMap.get("data"); log.info("📦 Bill FetchById Response: {}", responseData); helper.updateOutboundLogResponse(outboundLog, responseData); Map dataMap = objectMapper.readValue(responseData, new TypeReference>() {}); if (!dataMap.containsKey("genericResponse")) { helper.updateInboundLogFailure(inboundLog, responseData, "INVALID_RESPONSE"); 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")) ); } BillDataResponse responseWrapper = objectMapper.convertValue(dataMap, BillDataResponse.class); GeneralResp generalResp = responseWrapper.getGenericResponse(); String status = generalResp.getStatus(); log.info("📡 Current Bill Status: {}", status); // ✅ Check for "INPROGRESS" or pending state if ("INPROGRESS".equalsIgnoreCase(status)) { if (System.currentTimeMillis() - startTime > maxWaitTimeMs) { helper.updateInboundLogFailure(inboundLog, "Timeout after 40 seconds", "FAILED"); throw new CustomException("Timeout waiting for bill fetch completion", "FAILED", 408, "Bill fetch still in progress"); } Thread.sleep(intervalMs); continue; // retry again } // ✅ Final successful response helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); return new ResponseModel( generalResp.getMessage(), generalResp.getStatus(), Integer.parseInt(generalResp.getStatusCode()), responseWrapper ); } catch (JsonProcessingException e) { log.error("Error parsing BillById JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), "JSON_ERROR"); throw new CustomException("Invalid JSON received from API"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); helper.updateInboundLogFailure(inboundLog, e.getMessage(), "INTERRUPTED"); throw new CustomException("Thread interrupted during bill status check"); } catch (CustomException ce) { throw ce; } catch (Exception e) { log.error("❌ Exception in fetchBillById(): {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), "EXCEPTION"); throw new CustomException("Bill Fetch by ID failed: " + e.getMessage()); } } } @Override public ResponseModel fetchBillById(BillFetchByBillIdRequest billFetchByBillIdRequest) { try { helper.validateMerchant(billFetchByBillIdRequest.getMid()); String billId = billFetchByBillIdRequest.getBillId(); String fullUrl = agentDtaUrl + BBPSConstant.BILL_FETCH_BY_ID + billId; System.out.print(fullUrl); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId("ORG001"); HttpHeaders headers = new HttpHeaders(); headers.set("x-digiledge-key", headersData.getXDigiledgeKey()); headers.set("agentId", headersData.getAgentId()); headers.set("tenantId", headersData.getTenantId()); headers.setContentType(MediaType.APPLICATION_JSON); Map responseMap = httpService.getRequest(fullUrl, headers); String responseData = responseMap.get("data"); log.info("data is :{}", responseData); ObjectMapper objectMapper = new ObjectMapper(); Map dataMap = objectMapper.readValue(responseData, new TypeReference>() { }); if (!dataMap.containsKey("genericResponse")) { throw new CustomException(String.valueOf(dataMap.get("message")), // message String.valueOf(dataMap.get("status")), // status Integer.parseInt(String.valueOf(dataMap.get("statusCode"))), // statusCode (int) String.valueOf(dataMap.get("remarks"))); } BillDataResponse responseWrapper = objectMapper.convertValue(dataMap, BillDataResponse.class); GeneralResp generalResp = responseWrapper.getGenericResponse(); ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), Integer.parseInt(generalResp.getStatusCode()), responseWrapper); return responseModel; } catch (JsonProcessingException e) { log.error("Error parsing responseData JSON: {}", e.getMessage()); throw new CustomException("Invalid JSON received from API"); } catch (CustomException ex) { log.warn("Merchant validation failed: {}", ex.getMessage()); return ex.getResponseModel(); // Directly return ResponseModel from exception } } @Override public ResponseModel initiateBillValidate(FetchBillValidatestRequest fetchBillValidateRequest) { // TODO Auto-generated method stub return null; } @Override public ResponseModel initiateBillValidateByBillId(BillFetchByBillIdRequest billRequest) { // TODO Auto-generated method stub return null; } /* * @Override public ResponseModel * initiateBillValidate(FetchBillValidatestRequest fetchBillValidateRequest) { * * ResponseModel responseModel = new ResponseModel(); try { * * helper.validateMerchant(fetchBillValidateRequest.getMid()); * * BharatConnectMaster headersData = * beneficiaryDetailsRepository.findByOrgId("ORG001"); * * if (headersData == null) { responseModel = new * ResponseModel("Tenant Id Is Null", "Not Found", BBPSConstant.BADREQUEST, * headersData); return responseModel; } HttpHeaders headers = new * HttpHeaders(); headers.set("x-digiledge-key", * headersData.getXDigiledgeKey()); headers.set("agentId", * headersData.getAgentId()); headers.set("tenantId", * headersData.getTenantId()); * headers.setContentType(MediaType.APPLICATION_JSON); * * ObjectMapper objectMapper = new ObjectMapper(); * objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String * json = objectMapper.writeValueAsString(fetchBillValidateRequest); String url * = agentDtaUrl + BBPSConstant.BILLER_VALIDATE_URL; Map * responseMap = httpService.postRequest(url, json, headers); String * responseData = responseMap.get("data"); * * log.info("📦 Bill Fetch API Response: {}", responseData); * * if (responseData == null || responseData.isEmpty()) { throw new * CustomException("Empty response from BBPS server"); } * * // ✅ Convert JSON string → Map Map dataMap = * objectMapper.readValue(responseData, new TypeReference>() * { }); * * // ✅ Validate generalResp if (!dataMap.containsKey("genericResponse")) { * throw new CustomException("Invalid response structure from BBPS"); } * * // ✅ Convert to your custom DTO FetchBillValidatestResponse fetchBillResponse * = objectMapper.convertValue(dataMap, FetchBillValidatestResponse.class); * GeneralResp generalResponse = fetchBillResponse.getGenericResponse(); * * // ✅ Build final ResponseModel responseModel = new * ResponseModel(generalResponse.getMessage(), generalResponse.getStatus(), * Integer.parseInt(generalResponse.getStatusCode()), fetchBillResponse); * * } catch (CustomException ex) { log.warn("Merchant validation failed: {}", * ex.getMessage()); return ex.getResponseModel(); // Directly return * ResponseModel from exception } catch (Exception e) { * log.error("❌ Exception in initiateBillFetch(): {}", e.getMessage(), e); * responseModel.setStatus("500"); * responseModel.setMessage("Exception occurred"); * responseModel.setData(e.getMessage()); } * * return responseModel; } * * @Override public ResponseModel * initiateBillValidateByBillId(BillFetchByBillIdRequest billRequest) { * * ResponseModel responseModel = new ResponseModel(); try { * helper.validateMerchant(billRequest.getMid()); * * BharatConnectMaster headersData = * beneficiaryDetailsRepository.findByOrgId("ORG001"); * * if (headersData == null) { responseModel = new * ResponseModel("Tenant Id Is Null", "Not Found", BBPSConstant.BADREQUEST, * headersData); return responseModel; } * * HttpHeaders headers = new HttpHeaders(); headers.set("x-digiledge-key", * headersData.getXDigiledgeKey()); headers.set("agentId", * headersData.getAgentId()); headers.set("tenantId", * headersData.getTenantId()); * headers.setContentType(MediaType.APPLICATION_JSON); ObjectMapper objectMapper * = new ObjectMapper(); * objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); String * json = objectMapper.writeValueAsString(billRequest); String url = agentDtaUrl * + BBPSConstant.BILL_ID_URL + billRequest.getBillId(); * * log.info("Full URL" + url); * objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); * * // ✅ Call the API using httpService Map responseMap = * httpService.getRequest(url, headers); String responseData = * responseMap.get("data"); * * log.info("📦 Bill Fetch API Response: {}", responseData); * * if (responseData == null || responseData.isEmpty()) { throw new * CustomException("Empty response from BBPS server"); } * * // ✅ Convert JSON string → Map Map dataMap = * objectMapper.readValue(responseData, new TypeReference>() * { }); * * // ✅ Validate generalResp if (!dataMap.containsKey("genericResponse")) { * throw new CustomException("Invalid response structure from BBPS"); } * * // ✅ Convert to your custom DTO BillDataResponse fetchBillResponse = * objectMapper.convertValue(dataMap, BillDataResponse.class); GeneralResp * generalResponse = fetchBillResponse.getGenericResponse(); * * // ✅ Build final ResponseModel responseModel = new * ResponseModel(generalResponse.getRemarks(), generalResponse.getStatus(), * Integer.parseInt(generalResponse.getStatusCode()), fetchBillResponse); * * } catch (CustomException ex) { log.warn("Merchant validation failed: {}", * ex.getMessage()); return ex.getResponseModel(); // Directly return * ResponseModel from exception } catch (Exception e) { * log.error("❌ Exception in initiateBillFetch(): {}", e.getMessage(), e); * responseModel.setStatus("500"); * responseModel.setMessage("Exception occurred"); * responseModel.setData(e.getMessage()); } * * return responseModel; * * } */ }