added code

This commit is contained in:
Mohd-Arham
2025-12-16 09:59:05 +05:30
12 changed files with 265 additions and 228 deletions
+176
View File
@@ -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<Long> 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<String, Object> 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);
}
}
@@ -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;
}
@@ -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<CustomerParamTransaction> 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;
@@ -27,4 +27,7 @@ public interface TransactionMasterRepository extends JpaRepository<TransactionMa
@Query("SELECT t FROM TransactionMaster t WHERE t.txnId = :txnId")
List<TransactionMaster> findByTxnId(@Param("txnId") String txnId);
}
// Find TransactionMaster(s) by the payment_ref_id column (references PaymentStartRecord.txn_id)
List<TransactionMaster> findByPaymentRefId(String paymentRefId);
}
@@ -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> 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> 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());
}
}
@@ -208,7 +208,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());
}
}
@@ -291,7 +291,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());
}
}
@@ -357,7 +357,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());
}
}
@@ -421,7 +421,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());
}
}
@@ -533,7 +533,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());
}
}
@@ -600,7 +600,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());
}
}
@@ -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());
}
}
}
}
@@ -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<String, Object> 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> 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());
}
}
}
}
@@ -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
}
@@ -1,5 +1,7 @@
package com.billpaymentservice.bbps.util;
public class BBPS_RESPONSE_COD_CONSTANT {
public static final int INVALID_JSON_STATUS_CODE = 603;
}
@@ -21,6 +21,7 @@ swagger.server.description=Local Development Server
#spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_new_updated
spring.datasource.url=jdbc:postgresql://localhost:5432/bbps_enhanced
spring.datasource.username=postgres
spring.datasource.password=UAT@DB_2025
@@ -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<String> 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<String> 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<Future<?>> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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<String> 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());
}
}
}
}