diff --git a/RateLimitingFilter.java b/RateLimitingFilter.java new file mode 100644 index 0000000..0d35a5a --- /dev/null +++ b/RateLimitingFilter.java @@ -0,0 +1,176 @@ +package com.billpaymentservice.bbps.filter; + +import java.io.IOException; +import java.time.Instant; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import jakarta.servlet.FilterChain; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.http.MediaType; +import org.springframework.web.filter.OncePerRequestFilter; +import org.springframework.data.redis.core.StringRedisTemplate; +import org.springframework.data.redis.core.script.DefaultRedisScript; +import org.springframework.data.redis.RedisConnectionFailureException; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.billpaymentservice.bbps.ratelimit.LocalBucketManager; +import com.billpaymentservice.bbps.util.Helper; + +import io.github.bucket4j.Bucket; + + +public class RateLimitingFilter extends OncePerRequestFilter { + + private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitingFilter.class); + + private static final long RATE_PER_WINDOW = 1L; + private static final long BURST = 0L; + private static final long WINDOW_MILLIS = 60_000L; + + private final StringRedisTemplate redisTemplate; + private final LocalBucketManager localBucketManager; + private final ObjectMapper mapper = new ObjectMapper(); + + private final DefaultRedisScript incrWithExpireScript; + + public RateLimitingFilter(StringRedisTemplate redisTemplate, LocalBucketManager localBucketManager) { + this.redisTemplate = redisTemplate; + this.localBucketManager = localBucketManager; + String script = + "local v = redis.call('INCR', KEYS[1]);" + + "if v == 1 then redis.call('EXPIRE', KEYS[1], ARGV[1]) end;" + + "return v"; + + this.incrWithExpireScript = new DefaultRedisScript<>(script, Long.class); + } + + @Override + protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, + FilterChain filterChain) throws ServletException, IOException { + + String mid = validateAndExtractMid(request, response); + if (mid == null) return; + + LOGGER.info("RateLimiter activated → path={} mid={}", request.getRequestURI(), mid); + response.setHeader("X-RateLimit-Filter", "invoked"); + + long limit = RATE_PER_WINDOW + BURST; + String redisKey = buildRedisKey(mid); + + // 1️⃣ Local Bucket Check + if (!consumeLocalToken(mid, response)) { + long resetAt = calculateResetTime(); + setRateHeaders(response, limit, 0, resetAt); + writeErrorResponse(request, response, 429, "Too Many Requests", "Rate limit exceeded (local)", mid); + return; + } + + // 2️⃣ Redis Counter + Long count = incrementRedisCounter(redisKey); + long resetAt = calculateResetTime(); + long remaining = Math.max(0, limit - count); + + setRateHeaders(response, limit, remaining, resetAt); + response.setHeader("X-RateLimit-Count", String.valueOf(count)); + + if (count > limit) { + handleGlobalLimitExceeded(mid, response, request, resetAt); + return; + } + + filterChain.doFilter(request, response); + + } + + // -------------------------HELPERS------------------------- + + + private String validateAndExtractMid(HttpServletRequest request, HttpServletResponse response) throws IOException + { String mid = request.getHeader("mid"); + if (mid == null || mid.trim().isEmpty()) + { writeErrorResponse(request, response, 400, "Bad Request", "Required header 'mid' is missing or empty", null); + return null; } + return mid.trim(); } + + private boolean consumeLocalToken(String mid, HttpServletResponse response) { + Bucket bucket = localBucketManager.getBucket("local:mid:" + mid); + boolean consumed = bucket.tryConsume(1); + response.setHeader("X-RateLimit-Local-Remaining", String.valueOf(bucket.getAvailableTokens())); + return consumed; + } + + private Long incrementRedisCounter(String key) { + long ttlSeconds = (WINDOW_MILLIS / 1000L) + 2; + + try { + Long result = redisTemplate.execute( + incrWithExpireScript, + Collections.singletonList(key), + String.valueOf(ttlSeconds) + ); + return (result == null ? 1 : result); + } catch (Exception e) { + LOGGER.warn("Redis unavailable → fallback mode"); + return 1L; + } + } + + private long calculateResetTime() { + long nowSec = System.currentTimeMillis() / 1000L; + long win = WINDOW_MILLIS / 1000L; + return ((nowSec / win) * win) + win; + } + + private String buildRedisKey(String mid) { + long index = System.currentTimeMillis() / WINDOW_MILLIS; + return "ratelimit:mid:" + mid + ":ts:" + index; + } + + private void setRateHeaders(HttpServletResponse response, long limit, long remaining, long resetAt) { + response.setHeader("X-RateLimit-Limit", String.valueOf(limit)); + response.setHeader("X-RateLimit-Remaining", String.valueOf(remaining)); + response.setHeader("X-RateLimit-Reset", String.valueOf(resetAt)); + } + + private void handleGlobalLimitExceeded(String mid, + HttpServletResponse res, + HttpServletRequest req, + long resetAt) throws IOException { + + long now = System.currentTimeMillis() / 1000L; + res.setHeader("Retry-After", String.valueOf(Math.max(1, resetAt - now))); + + writeErrorResponse(req, res, 429, + "Too Many Requests", "Rate limit exceeded (global)", mid); + } + + private void writeErrorResponse(HttpServletRequest request, + HttpServletResponse response, + int status, String error, + String message, String mid) throws IOException { + + if (response.isCommitted()) return; + + Map body = new HashMap<>(); + body.put("timestamp", Instant.now().toString()); + body.put("status", status); + body.put("error", error); + body.put("message", message); + body.put("path", request.getRequestURI()); + + if (mid != null) body.put("mid", mid); + + response.setStatus(status); + response.setContentType(MediaType.APPLICATION_JSON_VALUE); + mapper.writeValue(response.getWriter(), body); + } +} + + + diff --git a/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java b/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java index 6649448..3f45dba 100644 --- a/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java +++ b/src/main/java/com/billpaymentservice/bbps/entites/PaymentStartRecord.java @@ -11,6 +11,11 @@ import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import com.fasterxml.jackson.annotation.JsonIgnore; + +import jakarta.persistence.OneToOne; +import jakarta.persistence.FetchType; + @AllArgsConstructor @NoArgsConstructor @Entity @@ -80,4 +85,8 @@ public class PaymentStartRecord extends Auditable { @Column(name = "callback_received_on") private String callbackReceivedOn; + + @OneToOne(mappedBy = "paymentStartRecord", fetch = FetchType.LAZY) + @JsonIgnore + private TransactionMaster transactionMaster; } \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java b/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java index b4249d2..3033829 100644 --- a/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java +++ b/src/main/java/com/billpaymentservice/bbps/entites/TransactionMaster.java @@ -2,6 +2,7 @@ package com.billpaymentservice.bbps.entites; import java.util.List; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonProperty; import jakarta.persistence.CascadeType; @@ -9,6 +10,8 @@ import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.OneToOne; import jakarta.persistence.OneToMany; import jakarta.persistence.Table; import lombok.AllArgsConstructor; @@ -149,6 +152,12 @@ public class TransactionMaster extends Auditable { @OneToMany(mappedBy = "transactionMaster", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List customerParams; + // ✅ Relation with PaymentStartRecord (one-to-one) + @OneToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "payment_ref_id", referencedColumnName = "txn_id", insertable = false, updatable = false) + @JsonIgnore + private PaymentStartRecord paymentStartRecord; + @Column(name = "plan_id") private String planId; diff --git a/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java b/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java index d4d7dee..ea41e2d 100644 --- a/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java +++ b/src/main/java/com/billpaymentservice/bbps/repository/TransactionMasterRepository.java @@ -27,4 +27,7 @@ public interface TransactionMasterRepository extends JpaRepository findByTxnId(@Param("txnId") String txnId); -} + // Find TransactionMaster(s) by the payment_ref_id column (references PaymentStartRecord.txn_id) + List findByPaymentRefId(String paymentRefId); + +} \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/AgentServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/AgentServiceImpl.java index 0fd168b..692bcc7 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/AgentServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/AgentServiceImpl.java @@ -1,4 +1,3 @@ - package com.billpaymentservice.bbps.serviceImpl; import java.util.Map; @@ -153,7 +152,8 @@ public class AgentServiceImpl implements AgentService { } catch (JsonProcessingException e) { LOGGER.error("JSON Parsing Error: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + // Use standardized user-facing message and status, keep internal details in remarks + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -206,7 +206,7 @@ public class AgentServiceImpl implements AgentService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing BillById JSON: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -265,7 +265,7 @@ public class AgentServiceImpl implements AgentService { catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON for billId={} : {}", billId, e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -349,7 +349,7 @@ public class AgentServiceImpl implements AgentService { } catch (JsonProcessingException e) { LOGGER.error("JSON Parsing Error: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -404,7 +404,7 @@ public class AgentServiceImpl implements AgentService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing BillById JSON: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -465,7 +465,7 @@ public class AgentServiceImpl implements AgentService { } catch (com.fasterxml.jackson.core.JsonProcessingException e) { LOGGER.error("Error parsing ValidateByBillId JSON: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } // ================================== Bill fetch and Bill Validate by Client Req @@ -494,7 +494,12 @@ public class AgentServiceImpl implements AgentService { helper.validateMerchant(billValidateByClientIdRequest.getMid()); Optional billInitiate = billIntiateRepository.findByReqId(clientReqId); if (billInitiate.isEmpty()) { - throw new CustomException(BBPSConstant.NO_BILL_FOUND); + new CustomException( + BBPSConstant.NO_BILL_FOUND, // message // status + BBPSConstant.FAILED_STATUS, + BBPSConstant.HTTP_NOT_FOUND, // statusCode + "Bill not found in database for given clientReqId" + ); } String fullUrl = agentDtaUrl + BBPSConstant.BILL_VALIDATE_BY_CLIENT_REQ_ID + billInitiate.get().getTxnId(); @@ -531,7 +536,7 @@ public class AgentServiceImpl implements AgentService { } catch (JsonProcessingException e) { helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -552,7 +557,12 @@ public class AgentServiceImpl implements AgentService { Optional billInitiate = billIntiateRepository.findByReqId(clientReqId); if (billInitiate.isEmpty()) { - throw new CustomException(BBPSConstant.NO_BILL_FOUND); + throw new CustomException( + BBPSConstant.NO_BILL_FOUND, // message // status + BBPSConstant.FAILED_STATUS, + BBPSConstant.HTTP_NOT_FOUND, // statusCode + "Bill not found in database for given clientReqId" + ); } String fullUrl = agentDtaUrl + BBPSConstant.BILL_VALIDATE_BY_CLIENT_REQ_ID + billInitiate.get().getTxnId(); @@ -588,7 +598,7 @@ public class AgentServiceImpl implements AgentService { } catch (JsonProcessingException e) { helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/ComplainServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/ComplainServiceImpl.java index fcefd00..bc348f4 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/ComplainServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/ComplainServiceImpl.java @@ -207,7 +207,7 @@ public class ComplainServiceImpl implements ComplainService { } catch (JsonProcessingException e) { LOGGER.error("JSON Parsing Error: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -290,7 +290,7 @@ public class ComplainServiceImpl implements ComplainService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing JSON in complaint status API: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -356,7 +356,7 @@ public class ComplainServiceImpl implements ComplainService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -420,7 +420,7 @@ public class ComplainServiceImpl implements ComplainService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -532,7 +532,7 @@ public class ComplainServiceImpl implements ComplainService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -599,7 +599,7 @@ public class ComplainServiceImpl implements ComplainService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing Complaint Id JSON: {}", e.getMessage(), e); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } diff --git a/src/main/java/com/billpaymentservice/bbps/serviceImpl/MasterDataServiceImpl.java b/src/main/java/com/billpaymentservice/bbps/serviceImpl/MasterDataServiceImpl.java index b32d7d7..5c6f38a 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/MasterDataServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/MasterDataServiceImpl.java @@ -164,7 +164,8 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + // Use user-friendly message constant and keep internal details in remarks + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } @@ -227,7 +228,8 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + // Unified constant for invalid JSON + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -298,7 +300,7 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -364,7 +366,7 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -431,7 +433,7 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -507,7 +509,7 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -570,7 +572,7 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -636,7 +638,7 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -700,7 +702,7 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -757,7 +759,7 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -829,7 +831,7 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {}", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -887,8 +889,8 @@ public class MasterDataServiceImpl implements MasterDataService { } catch (JsonProcessingException e) { LOGGER.error("Error parsing responseData JSON: {} :: class :: method ", e.getMessage()); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); - throw new CustomException("Invalid JSON received from API"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } -} \ 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 a93c6d5..c9e560f 100644 --- a/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java +++ b/src/main/java/com/billpaymentservice/bbps/serviceImpl/PaymentServiceImpl.java @@ -127,7 +127,12 @@ public class PaymentServiceImpl implements PaymentService { 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); + throw new CustomException( + BBPSConstant.EMPTY_RESPONSE_FROM_BBPS, // message + BBPSConstant.FAILED_STATUS, // status + 502, // statusCode + "BBPS upstream returned empty response" + ); } Map dataMap = objectMapper.readValue(responseData, @@ -183,7 +188,7 @@ public class PaymentServiceImpl implements PaymentService { } 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"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -261,7 +266,7 @@ public class PaymentServiceImpl implements PaymentService { } 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"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -327,7 +332,7 @@ public class PaymentServiceImpl implements PaymentService { } 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"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } @@ -347,7 +352,12 @@ public class PaymentServiceImpl implements PaymentService { Optional paymentInitiate = paymentIntiateRepository.findByReqId(clientReqId); if (paymentInitiate.isEmpty()) { - throw new CustomException(BBPSConstant.NO_BILL_FOUND); + throw new CustomException( + BBPSConstant.NO_BILL_FOUND, // message // status + BBPSConstant.FAILED_STATUS, + BBPSConstant.HTTP_NOT_FOUND, // statusCode + "Bill not found in database for given clientReqId" + ); } String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_BY_CLIENT_REQ_ID_URL @@ -405,9 +415,8 @@ public class PaymentServiceImpl implements PaymentService { } 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"); + throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, "FAILED", BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); } } -} \ No newline at end of file +} diff --git a/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java b/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java index 553d2ba..763d2f4 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java +++ b/src/main/java/com/billpaymentservice/bbps/util/BBPSConstant.java @@ -195,6 +195,9 @@ public class BBPSConstant extends BBPS_RESPONSE_COD_CONSTANT { public static final String MSG_EMPTY_RESPONSE_PG = "Empty response from PG"; public static final String MSG_INVALID_JSON_FROM_API = "Invalid JSON received from payment API"; public static final String MSG_TXN_PROCESSING = "Transaction processing"; + // User-facing message for malformed responses from upstream services (used with status code 603) + // Short, non-leaking and actionable message + public static final String MSG_INVALID_JSON_FROM_UPSTREAM = "Temporary upstream data error. Please try again later."; public static final String MSG_WARNING_FROM_MERCHANT = "Warning from merchant"; public static final String MSG_FRAUD_REJECTED = "Fraud Rejected"; public static final String MSG_DECRYPTION_FAILED = "Decryption failed"; @@ -220,4 +223,6 @@ public class BBPSConstant extends BBPS_RESPONSE_COD_CONSTANT { public static final String USER_UNAUTHORIZED_STR = "401"; public static final String OK_STR = "200"; + // Newly added constant for invalid JSON status code + } \ No newline at end of file diff --git a/src/main/java/com/billpaymentservice/bbps/util/BBPS_RESPONSE_COD_CONSTANT.java b/src/main/java/com/billpaymentservice/bbps/util/BBPS_RESPONSE_COD_CONSTANT.java index 8bd51a9..b9277af 100644 --- a/src/main/java/com/billpaymentservice/bbps/util/BBPS_RESPONSE_COD_CONSTANT.java +++ b/src/main/java/com/billpaymentservice/bbps/util/BBPS_RESPONSE_COD_CONSTANT.java @@ -1,5 +1,7 @@ package com.billpaymentservice.bbps.util; public class BBPS_RESPONSE_COD_CONSTANT { + public static final int INVALID_JSON_STATUS_CODE = 603; + } diff --git a/src/main/resources/application-local.properties b/src/main/resources/application-local.properties index 6722a87..16caf94 100644 --- a/src/main/resources/application-local.properties +++ b/src/main/resources/application-local.properties @@ -18,9 +18,9 @@ swagger.server.description=Local Development Server # DataSource -spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_new_updated +#spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_new_updated -#spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_enhanced +spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_enhanced >>>>>>> Stashed changes spring.datasource.username=postgres #spring.datasource.password=UAT@DB_2025 diff --git a/src/test/java/com/billpaymentservice/bbps/BBPSAutomationTest.java b/src/test/java/com/billpaymentservice/bbps/BBPSAutomationTest.java deleted file mode 100644 index 2c67322..0000000 --- a/src/test/java/com/billpaymentservice/bbps/BBPSAutomationTest.java +++ /dev/null @@ -1,189 +0,0 @@ -package com.billpaymentservice.bbps; - -import java.net.URI; -import java.net.http.*; -import java.util.*; -import java.util.concurrent.*; -import org.json.*; - -public class BBPSAutomationTest { - - private static final String BASE_URL = "http://localhost:9090/masterData"; - private static final String BILLER_API = "https://bbps-staging.digiledge.in/agent/cou-master/masters/billers"; - private static final HttpClient client = HttpClient.newHttpClient(); - - // Run 10 threads in parallel - private static final ExecutorService executor = Executors.newFixedThreadPool(10); - - public static void main(String[] args) { - try { - System.out.println("🚀 Starting BBPS Test Flow (Multi-Threaded)...\n"); - - List categories = getAllCategories(); - if (categories.isEmpty()) { - System.out.println("❌ No categories found. Exiting..."); - return; - } - - System.out.println("\n✅ Total Categories Found: " + categories.size()); - - int count = 1; - for (String category : categories) { - System.out.println("\n📂 [" + count++ + "/" + categories.size() + "] Category: " + category); - - List billerIds = Collections.emptyList(); - try { - billerIds = fetchBillersByCategory(category); - } catch (Exception ex) { - System.err.println("⚠️ Skipping category '" + category + "' due to error: " + ex.getMessage()); - continue; // skip and go to next category - } - - if (billerIds == null || billerIds.isEmpty()) { - System.out.println("⚠️ No billers found for: " + category + " — Skipping..."); - continue; - } - - System.out.println("🔹 Found " + billerIds.size() + " billers. Fetching details in parallel..."); - - // Run all biller calls in parallel threads - List> futures = new ArrayList<>(); - for (String billerId : billerIds) { - futures.add(executor.submit(() -> { - try { - sendBillerDetails(billerId); - } catch (Exception e) { - System.err.println("❌ Error with biller " + billerId + ": " + e.getMessage()); - } - })); - } - - // Wait for all billers to finish - for (Future f : futures) { - try { - f.get(); - } catch (Exception e) { - System.err.println("⚠️ Thread interrupted: " + e.getMessage()); - } - } - - System.out.println("✅ Completed all billers for category: " + category); - } - - executor.shutdown(); - System.out.println("\n🎯 All categories processed successfully (multi-threaded & safe)!"); - } catch (Exception e) { - e.printStackTrace(); - } - } - - // Step 1 - Get All Categories - private static List getAllCategories() throws Exception { - String reqId = UUID.randomUUID().toString(); - - JSONObject body = new JSONObject(); - body.put("mid", "ALLCOS00117BHO"); - body.put("reqId", reqId); - body.put("secretKey", "ABCD1234SECRET"); - - JSONObject device = new JSONObject(); - device.put("deviceId", "DEVICE001"); - device.put("deviceName", "Samsung Galaxy S23"); - device.put("os", "Android 14"); - device.put("appVersion", "1.0.5"); - device.put("ipAddress", "192.168.1.101"); - body.put("deviceDetial", device); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(BASE_URL + "/getAllMastersData")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(body.toString())) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - if (response.statusCode() != 200) { - System.out.println("❌ getAllMastersData failed | HTTP " + response.statusCode()); - return Collections.emptyList(); - } - - JSONObject json = new JSONObject(response.body()); - JSONArray catArray = json.optJSONObject("data") != null - ? json.getJSONObject("data").optJSONArray("categoryRespList") - : null; - - List categories = new ArrayList<>(); - if (catArray != null && catArray.length() > 0) { - for (int i = 0; i < catArray.length(); i++) { - categories.add(catArray.getJSONObject(i).getString("categoryName")); - } - } - - System.out.println("✅ Categories found: " + categories.size()); - return categories; - } - - // Step 2 - Fetch Billers by Category - private static List fetchBillersByCategory(String category) throws Exception { - // Encode category to handle spaces & special characters safely - String encodedCategory = java.net.URLEncoder.encode(category, java.nio.charset.StandardCharsets.UTF_8); - String url = BILLER_API + "?category=" + encodedCategory + "&page=0&pagesize=50"; - - HttpRequest request = HttpRequest.newBuilder().uri(URI.create(url)).GET().build(); - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - - if (response.statusCode() != 200) { - System.out.println("❌ Failed to fetch billers for " + category + " | HTTP " + response.statusCode()); - return Collections.emptyList(); - } - - JSONObject json = new JSONObject(response.body()); - JSONArray billersArray = json.optJSONArray("billerResp"); - - if (billersArray == null || billersArray.length() == 0) { - System.out.println("⚠️ billerResp empty or null for category: " + category); - return Collections.emptyList(); - } - - List billerIds = new ArrayList<>(); - for (int i = 0; i < billersArray.length(); i++) { - billerIds.add(billersArray.getJSONObject(i).getString("billerId")); - } - - return billerIds; - } - - // Step 3 - Get Biller Details - private static void sendBillerDetails(String billerId) throws Exception { - String reqId = UUID.randomUUID().toString(); - - JSONObject body = new JSONObject(); - body.put("mid", "ALLCOS00117BHO"); - body.put("reqId", reqId); - body.put("billerId", billerId); - body.put("secretKey", "ABCD1234SECRET"); - - JSONObject device = new JSONObject(); - device.put("deviceId", "DEVICE001"); - device.put("deviceName", "Samsung Galaxy S23"); - device.put("os", "Android 14"); - device.put("appVersion", "1.0.5"); - device.put("ipAddress", "192.168.1.101"); - body.put("deviceDetial", device); - - HttpRequest request = HttpRequest.newBuilder() - .uri(URI.create(BASE_URL + "/getBillerDetails")) - .header("Content-Type", "application/json") - .POST(HttpRequest.BodyPublishers.ofString(body.toString())) - .build(); - - HttpResponse response = client.send(request, HttpResponse.BodyHandlers.ofString()); - - synchronized (System.out) { // thread-safe log printing - if (response.statusCode() == 200) { - System.out.println("✅ Success: " + billerId); - } else { - System.out.println("❌ Failed: " + billerId + " | HTTP " + response.statusCode()); - } - } - } -}