improved the error handling and updated link paymentstartrecord and transactionmaster
This commit is contained in:
@@ -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;
|
||||
|
||||
|
||||
+4
-1
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user