diff --git a/pom.xml b/pom.xml index 19bf1de..8ea65ce 100644 --- a/pom.xml +++ b/pom.xml @@ -29,7 +29,11 @@ org.springframework.boot spring-boot-starter - + + + org.springframework.boot + spring-boot-starter-validation + org.springframework.boot spring-boot-starter-web diff --git a/src/main/java/com/billpaymentservice/bbps/controller/MasterDataController.java b/src/main/java/com/billpaymentservice/bbps/controller/MasterDataController.java index d485d41..fbde124 100644 --- a/src/main/java/com/billpaymentservice/bbps/controller/MasterDataController.java +++ b/src/main/java/com/billpaymentservice/bbps/controller/MasterDataController.java @@ -17,8 +17,11 @@ import com.billpaymentservice.bbps.dto.request.MasterCategoryRequest; import com.billpaymentservice.bbps.dto.request.MasterRequest; import com.billpaymentservice.bbps.service.MasterDataService; +import jakarta.validation.Valid; + @RequestMapping("/masterData") @RestController +@Validated public class MasterDataController { private static final Logger log = LoggerFactory.getLogger(MasterDataController.class); @@ -32,7 +35,7 @@ public class MasterDataController { /** Get Details of the biller by billId **/ @PostMapping(value = "/getBillerDetails", consumes = MediaType.APPLICATION_JSON_VALUE, produces = MediaType.APPLICATION_JSON_VALUE) public ResponseEntity getBillerDetails( - @Validated @RequestBody BillerDetailRequest billerDetailRequest) { + @Valid @RequestBody BillerDetailRequest billerDetailRequest) { log.info("Received biller details request for billerId: {}", billerDetailRequest.getBillerId()); diff --git a/src/main/java/com/billpaymentservice/bbps/dto/request/BaseClass.java b/src/main/java/com/billpaymentservice/bbps/dto/request/BaseClass.java index b150d3a..41f09ff 100644 --- a/src/main/java/com/billpaymentservice/bbps/dto/request/BaseClass.java +++ b/src/main/java/com/billpaymentservice/bbps/dto/request/BaseClass.java @@ -3,27 +3,31 @@ package com.billpaymentservice.bbps.dto.request; import com.billpaymentservice.bbps.dto.DeviceDetail; import com.fasterxml.jackson.annotation.JsonProperty; +import jakarta.validation.constraints.NotBlank; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; @Getter @Setter @AllArgsConstructor @NoArgsConstructor +@ToString public abstract class BaseClass { - // ✅ Request me required, response me hide honge - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) + @NotBlank(message = "MID cannot be null or blank") private String mid; - - @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) - private String secretKey; - + + @NotBlank(message = "Secret key cannot be null or blank") + private String secretKey; + @NotBlank(message = "Request ID cannot be null or blank") + private String reqId; + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private String txnId; - + @JsonProperty(access = JsonProperty.Access.WRITE_ONLY) private DeviceDetail deviceDetial; diff --git a/src/main/java/com/billpaymentservice/bbps/dto/request/BillerDetailRequest.java b/src/main/java/com/billpaymentservice/bbps/dto/request/BillerDetailRequest.java index 7f4a81d..9d2ba0d 100644 --- a/src/main/java/com/billpaymentservice/bbps/dto/request/BillerDetailRequest.java +++ b/src/main/java/com/billpaymentservice/bbps/dto/request/BillerDetailRequest.java @@ -3,9 +3,11 @@ package com.billpaymentservice.bbps.dto.request; import jakarta.validation.constraints.NotNull; import lombok.Getter; import lombok.Setter; +import lombok.ToString; @Getter @Setter +@ToString public class BillerDetailRequest extends BaseClass{ @NotNull diff --git a/src/main/java/com/billpaymentservice/bbps/entites/InboundTraceLog.java b/src/main/java/com/billpaymentservice/bbps/entites/InboundTraceLog.java new file mode 100644 index 0000000..e2f92a7 --- /dev/null +++ b/src/main/java/com/billpaymentservice/bbps/entites/InboundTraceLog.java @@ -0,0 +1,57 @@ +package com.billpaymentservice.bbps.entites; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Entity +@Getter @Setter @NoArgsConstructor @ToString +public class InboundTraceLog { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 🔹 Identifiers + @Column(length = 50, unique = true) + private String requestId; // external requestId (from client) + @Column(length = 35) + private String txnId; // internally generated transaction id + + // 🔹 Source & API info + private String orgId; // tumhara org code (like ORG001) + private String mid; // merchant id from request + private String apiName; // e.g. getBillerByBillerId + private String httpMethod; // GET / POST + + // 🔹 Request/Response data + @Column(columnDefinition = "TEXT") + private String requestPayload; // jo external ne bheja + @Column(columnDefinition = "TEXT") + private String responsePayload; // jo tumne wapas bheja + + // 🔹 Status & timing + private String status; // SUCCESS / FAILED + private String responseCode; // like 200, 400 etc. + private LocalDateTime requestTime; // jab request aayi + private LocalDateTime responseTime; // jab response gaya + private LocalDateTime createdAt = LocalDateTime.now(); + + @Column(length = 255) + private String remark; // error ya note + public InboundTraceLog(String requestId, String txnId, String apiName, String requestPayload) { + this.requestId = requestId; + this.txnId = txnId; + this.apiName = apiName; + this.requestPayload = requestPayload; + this.requestTime = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/billpaymentservice/bbps/entites/OutboundTraceLog.java b/src/main/java/com/billpaymentservice/bbps/entites/OutboundTraceLog.java new file mode 100644 index 0000000..3fe1ff1 --- /dev/null +++ b/src/main/java/com/billpaymentservice/bbps/entites/OutboundTraceLog.java @@ -0,0 +1,54 @@ +package com.billpaymentservice.bbps.entites; + +import java.time.LocalDateTime; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Entity +@Getter @Setter @NoArgsConstructor @ToString +public class OutboundTraceLog { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + // 🔹 Linking fields + private String requestId; // same as inbound ka requestId + private String txnId; // same txnId for full trace + + // 🔹 API / endpoint info + private String apiName; // e.g. fetchBillerData + private String endpointUrl; // full URL of external API + private String httpMethod; // GET / POST + + // 🔹 Request/Response payloads + @Column(columnDefinition = "TEXT") + private String requestPayload; // jo tumne bheja external ko + @Column(columnDefinition = "TEXT") + private String responsePayload; // jo external ne bheja + + // 🔹 Response/status + private String responseCode; // HTTP status code (200, 500, etc.) + private String status; // SUCCESS / FAILED + private LocalDateTime sendTime; // jab request gayi + private LocalDateTime responseReceiveTime; // jab response aaya + private LocalDateTime createdAt = LocalDateTime.now(); + + @Column(length = 255) + private String remark; + public OutboundTraceLog(String txnId, String apiName, String endpointUrl, String requestPayload) { + this.txnId = txnId; + this.apiName = apiName; + this.endpointUrl = endpointUrl; + this.requestPayload = requestPayload; + this.sendTime = LocalDateTime.now(); + } +} diff --git a/src/main/java/com/billpaymentservice/bbps/exception/GlobalExceptionHandler.java b/src/main/java/com/billpaymentservice/bbps/exception/GlobalExceptionHandler.java index 6dcd370..a695c69 100644 --- a/src/main/java/com/billpaymentservice/bbps/exception/GlobalExceptionHandler.java +++ b/src/main/java/com/billpaymentservice/bbps/exception/GlobalExceptionHandler.java @@ -2,16 +2,41 @@ package com.billpaymentservice.bbps.exception; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; -import com.billpaymentservice.bbps.dto.GeneralResp; + import com.billpaymentservice.bbps.dto.ResponseModel; +import java.util.HashMap; +import java.util.Map; + @ControllerAdvice public class GlobalExceptionHandler { + // ✅ Handle CustomException @ExceptionHandler(CustomException.class) - public ResponseEntity handleCustomException(CustomException ex) { + public ResponseEntity> handleCustomException(CustomException ex) { return new ResponseEntity<>(ex.getResponseModel(), HttpStatus.BAD_REQUEST); } -} \ No newline at end of file + + // ✅ Handle validation errors + @ExceptionHandler(MethodArgumentNotValidException.class) + public ResponseEntity> handleValidationExceptions(MethodArgumentNotValidException ex) { + + Map errors = new HashMap<>(); + + ex.getBindingResult().getFieldErrors().forEach(error -> { + errors.put(error.getField(), error.getDefaultMessage()); + }); + + ResponseModel response = new ResponseModel<>( + "Validation Failed", + "FAILED", + HttpStatus.BAD_REQUEST.value(), + errors + ); + + return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST); + } +} diff --git a/src/main/java/com/billpaymentservice/bbps/repository/InboundTraceLogRepository.java b/src/main/java/com/billpaymentservice/bbps/repository/InboundTraceLogRepository.java new file mode 100644 index 0000000..c5d8313 --- /dev/null +++ b/src/main/java/com/billpaymentservice/bbps/repository/InboundTraceLogRepository.java @@ -0,0 +1,8 @@ +package com.billpaymentservice.bbps.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.billpaymentservice.bbps.entites.InboundTraceLog; +public interface InboundTraceLogRepository extends JpaRepository { + +} diff --git a/src/main/java/com/billpaymentservice/bbps/repository/OutboundTraceLogRepository.java b/src/main/java/com/billpaymentservice/bbps/repository/OutboundTraceLogRepository.java new file mode 100644 index 0000000..f39d8cc --- /dev/null +++ b/src/main/java/com/billpaymentservice/bbps/repository/OutboundTraceLogRepository.java @@ -0,0 +1,9 @@ +package com.billpaymentservice.bbps.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.billpaymentservice.bbps.entites.OutboundTraceLog; + +public interface OutboundTraceLogRepository extends JpaRepository { + +} diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/MasterDataServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/MasterDataServiceImpl.java index be989f0..0c243af 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/MasterDataServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/MasterDataServiceImpl.java @@ -1,16 +1,15 @@ package com.billpaymentservice.bbps.serviceImpl; +import java.time.LocalDateTime; import java.util.List; import java.util.Map; import java.util.stream.Collectors; - 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.stereotype.Service; - import com.billpaymentservice.bbps.dto.GeneralResp; import com.billpaymentservice.bbps.dto.MasterApiResponse; import com.billpaymentservice.bbps.dto.ResponseModel; @@ -30,11 +29,15 @@ import com.billpaymentservice.bbps.dto.response.PlaneCategoriesResponse; import com.billpaymentservice.bbps.entites.BharatConnectMaster; import com.billpaymentservice.bbps.entites.BillerDetails; import com.billpaymentservice.bbps.entites.DispositionDetails; +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.BillerDataRepository; import com.billpaymentservice.bbps.repository.DispositionRepository; +import com.billpaymentservice.bbps.repository.InboundTraceLogRepository; import com.billpaymentservice.bbps.repository.MerchantRepository; +import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository; import com.billpaymentservice.bbps.service.MasterDataService; import com.billpaymentservice.bbps.util.BBPSConstant; import com.billpaymentservice.bbps.util.EntityRelationshipUtil; @@ -59,11 +62,16 @@ public class MasterDataServiceImpl implements MasterDataService { private final BillerDataRepository billerDataRespository; private static final Logger log = LoggerFactory.getLogger(MasterDataServiceImpl.class); private final HttpService httpService; + private final InboundTraceLogRepository inboundTraceLogRepository; + private final OutboundTraceLogRepository outboundTraceLogRepository; private final ObjectMapper objectMapper = new ObjectMapper(); + public MasterDataServiceImpl(MerchantRepository merchantRegisterRepo, HttpService httpService, HttpClientApi httpClientApi, Helper helper, BillerDataRepository billerDataRespository, - DispositionRepository dispositionRepository, BharatConnectMasterRepository beneficiaryDetailsRepository) { + DispositionRepository dispositionRepository, BharatConnectMasterRepository beneficiaryDetailsRepository, + InboundTraceLogRepository inboundTraceLogRepository, + OutboundTraceLogRepository outboundTraceLogRepository) { this.merchantRegisterRepo = merchantRegisterRepo; this.httpService = httpService; this.httpClientApi = httpClientApi; @@ -71,7 +79,10 @@ public class MasterDataServiceImpl implements MasterDataService { this.billerDataRespository = billerDataRespository; this.dispositionRepository = dispositionRepository; this.beneficiaryDetailsRepository = beneficiaryDetailsRepository; - + this.inboundTraceLogRepository = inboundTraceLogRepository; + this.outboundTraceLogRepository = outboundTraceLogRepository; + + } @Override @@ -92,7 +103,7 @@ public class MasterDataServiceImpl implements MasterDataService { Map responseMap = httpService.getRequest(fullUrl); String responseData = responseMap.get("data"); LOGGER.info("Response Data: {}", responseData); - + ObjectMapper objectMapper = new ObjectMapper(); Map dataMap = objectMapper.readValue(responseData, new TypeReference>() { @@ -124,42 +135,76 @@ public class MasterDataServiceImpl implements MasterDataService { @Override public ResponseModel getBillerByBillerId(BillerDetailRequest billerDetailRequest) { - String billerId = billerDetailRequest.getBillerId(); - String fullUrl = masterRegisterUrl + BBPSConstant.BILLER_DATA_FETCH_URL + billerId; + String txnID = helper.generateTxnId(15); + String apiName = "GetBillerByBillerId"; + String requestId = billerDetailRequest.getReqId(); + String fullUrl = masterRegisterUrl + BBPSConstant.BILLER_DATA_FETCH_URL + billerDetailRequest.getBillerId(); - Map responseMap = httpService.getRequest(fullUrl); - String responseData = responseMap.get("data"); - log.info("data is :{}", responseData); + // 🔹 Inbound Log (request received) + InboundTraceLog inboundLog = new InboundTraceLog(requestId, txnID, apiName, billerDetailRequest.toString()); + inboundTraceLogRepository.save(inboundLog); - ObjectMapper objectMapper = new ObjectMapper(); + // 🔹 Outbound Log (before sending external API request) + OutboundTraceLog outboundLog = new OutboundTraceLog(txnID, apiName, fullUrl, "N/A"); + outboundTraceLogRepository.save(outboundLog); - try { - Map dataMap = objectMapper.readValue(responseData, - new TypeReference>() { - }); - if (!dataMap.containsKey("generalResp")) { - 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"))); - } - BillerDataResponse responseWrapper = objectMapper.convertValue(dataMap, BillerDataResponse.class); - GeneralResp generalResp = responseWrapper.getGeneralResp(); - ModelMapper mapper = new ModelMapper(); - mapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT); - BillerDetails entity = mapper.map(responseWrapper.getBillerDAO(), BillerDetails.class); - EntityRelationshipUtil.linkBillerRelationships(entity); - // billerDataRespository.save(entity); - ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), - Integer.parseInt(generalResp.getStatusCode()), responseWrapper); - return responseModel; + // 🔹 Send request + Map responseMap = httpService.getRequest(fullUrl); + String responseData = responseMap.get("data"); - } catch (JsonProcessingException e) { - log.error("Error parsing responseData JSON: {}", e.getMessage()); - throw new CustomException("Invalid JSON received from API"); - } + outboundLog.setResponsePayload(responseData); + outboundLog.setResponseReceiveTime(LocalDateTime.now()); + outboundTraceLogRepository.save(outboundLog); + + // 🔹 Parse the response + ObjectMapper objectMapper = new ObjectMapper(); + Map dataMap; + + try { + dataMap = objectMapper.readValue(responseData, new TypeReference>() {}); + } catch (Exception e) { + // 🟥 Invalid JSON case — update inbound log before throwing + inboundLog.setResponsePayload("Invalid JSON: " + e.getMessage()); + inboundLog.setResponseCode("FAILED"); + inboundLog.setResponseTime(LocalDateTime.now()); + inboundTraceLogRepository.save(inboundLog); + throw new CustomException("Invalid JSON received from upstream", "FAILED", 400, e.getMessage()); + } + + // 🔹 If generalResp is missing — API returned error response + if (!dataMap.containsKey("generalResp")) { + // 🟥 Update inbound log before throwing + inboundLog.setResponsePayload(responseData); + inboundLog.setResponseCode(String.valueOf(dataMap.get("status"))); + inboundLog.setResponseTime(LocalDateTime.now()); + inboundTraceLogRepository.save(inboundLog); + + 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")) + ); + } + + // 🔹 Otherwise, success flow + BillerDataResponse responseWrapper = objectMapper.convertValue(dataMap, BillerDataResponse.class); + GeneralResp generalResp = responseWrapper.getGeneralResp(); + + inboundLog.setResponsePayload(responseData); + inboundLog.setResponseCode(generalResp.getStatus()); + inboundLog.setResponseTime(LocalDateTime.now()); + inboundTraceLogRepository.save(inboundLog); + + return new ResponseModel( + generalResp.getMessage(), + generalResp.getStatus(), + Integer.parseInt(generalResp.getStatusCode()), + responseWrapper + ); } + @Override public ResponseModel getCustomerParamByBillerId(CustomParamRequest customerParamRequest) { diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java index f758fdd..3d0b1d8 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java @@ -117,7 +117,7 @@ public class PaymentServiceImpl implements PaymentService { paymentRepository.save(entity); TransactionStatusRequest transactionStatusRequest=new TransactionStatusRequest(); //call the checkTransactionStatus - transactionStatusRequest.setTxnId(billPaymentResponse.getTxnId()); + transactionStatusRequest.setTxnId(helper.generateTxnId(15)); System.out.println(transactionStatusRequest); responseModel= checkTrasactionStatus(transactionStatusRequest); } catch (Exception e) { diff --git a/src/main/java/com/billpaymentservice/bbps/util/Helper.java b/src/main/java/com/billpaymentservice/bbps/util/Helper.java index da8b545..c52096a 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/Helper.java +++ b/src/main/java/com/billpaymentservice/bbps/util/Helper.java @@ -1,5 +1,6 @@ package com.billpaymentservice.bbps.util; +import java.security.SecureRandom; import java.util.function.Function; import org.slf4j.Logger; @@ -19,6 +20,9 @@ import com.billpaymentservice.bbps.repository.MerchantRepository; public class Helper { private static final Logger LOGGER = LoggerFactory.getLogger(Helper.class); + + private static final String CHARACTERS = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"; + private static final SecureRandom secureRandom = new SecureRandom(); @Autowired private MerchantRepository merchantRegisterRepo; @@ -111,4 +115,14 @@ public class Helper { // LOGGER.info("Merchant is valid and active for MID: {}", requestModel.getMid()); return "Valid"; // Success message } + + public static String generateTxnId(int n) { + StringBuilder sb = new StringBuilder(n); + + for (int i = 0; i < n; i++) { + int index = secureRandom.nextInt(CHARACTERS.length()); + sb.append(CHARACTERS.charAt(index)); + } + return sb.toString(); // Prefix + } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 1f1c54e..44ff981 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -2,7 +2,7 @@ spring.application.name=bbps # Server port -server.port=8080 +server.port=9090 # JPA & Hibernate spring.jpa.hibernate.ddl-auto=update @@ -11,9 +11,9 @@ spring.jpa.properties.hibernate.default_schema=public # DataSource -spring.datasource.url=jdbc:postgresql://localhost:5432/TestingDB +spring.datasource.url=jdbc:postgresql://localhost:5432/BBPS spring.datasource.username=postgres -spring.datasource.password=UAT@DB_2025 +spring.datasource.password=thakur@123 spring.datasource.driver-class-name=org.postgresql.Driver spring.datasource.type=com.zaxxer.hikari.HikariDataSource