Merge pull request 'feature/id-length-fix' (#38) from feature/id-length-fix into develop

Reviewed-on: https://git.zeptic.in/himanshu/bbps/pulls/38
This commit is contained in:
Mohd_Arham
2025-12-19 12:00:26 +05:30
23 changed files with 733 additions and 578 deletions
@@ -1,116 +0,0 @@
package com.billpaymentservice.bbps.config;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
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.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.billpaymentservice.bbps.util.BBPSConstant;
import com.billpaymentservice.bbps.dto.ResponseModel;
import io.github.resilience4j.ratelimiter.RateLimiter;
import io.github.resilience4j.ratelimiter.RateLimiterRegistry;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
@Order(1)
public class RateLimitingFilter extends OncePerRequestFilter {
private final RateLimiterRegistry rateLimiterRegistry;
private static final Logger LOGGER = LoggerFactory.getLogger(RateLimitingFilter.class);
// Har MID ke liye alag limiter
private final Map<String, RateLimiter> limiterCache = new ConcurrentHashMap<>();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
public RateLimitingFilter(RateLimiterRegistry rateLimiterRegistry) {
this.rateLimiterRegistry = rateLimiterRegistry;
}
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {
String mid = request.getHeader("mid");
if(mid == null) {
mid = "GLOBAL";
}
// Validate MID
if (mid == null || mid.isBlank()) {
response.setStatus(HttpServletResponse.SC_BAD_REQUEST);
response.setContentType("application/json");
ResponseModel<Object> apiResponse = new ResponseModel<>("mid cannot be null or blank", "FAILURE", HttpServletResponse.SC_BAD_REQUEST, null);
response.getWriter().write(OBJECT_MAPPER.writeValueAsString(apiResponse));
return;
}
try {
RateLimiter rateLimiter = limiterCache.computeIfAbsent(
mid,
key -> rateLimiterRegistry.rateLimiter("default")
);
// Rate limit check
if (!rateLimiter.acquirePermission()) {
// Prefer existing constant if it maps to 429, otherwise fallback to literal 429
int tooManyRequestsStatus =429;
response.setStatus(tooManyRequestsStatus);
response.setContentType("application/json");
ResponseModel<Object> apiResponse = new ResponseModel<>("Rate limit exceeded for MID: " + mid, "FAILURE", tooManyRequestsStatus, null);
response.getWriter().write(OBJECT_MAPPER.writeValueAsString(apiResponse));
return;
}
// Proceed with the chain and catch downstream exceptions to return consistent model
try {
filterChain.doFilter(request, response);
} catch (Exception ex) {
LOGGER.error("Unhandled exception in filter chain for MID: {}", mid, ex);
if (!response.isCommitted()) {
response.reset();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType("application/json");
ResponseModel<Object> apiResponse = new ResponseModel<>("Internal server error", "FAILURE", HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
response.getWriter().write(OBJECT_MAPPER.writeValueAsString(apiResponse));
}
}
} catch (Exception e) {
LOGGER.error("Error while rate limiting request", e);
if (!response.isCommitted()) {
response.reset();
response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
response.setContentType("application/json");
ResponseModel<Object> apiResponse = new ResponseModel<>("Internal server error", "FAILURE", HttpServletResponse.SC_INTERNAL_SERVER_ERROR, null);
response.getWriter().write(OBJECT_MAPPER.writeValueAsString(apiResponse));
}
return;
}
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) {
String path = request.getRequestURI();
return path.startsWith("/swagger")
|| path.startsWith("/v3/api-docs")
|| path.startsWith("/actuator");
}
}
@@ -0,0 +1,17 @@
package com.billpaymentservice.bbps.dto.response;
import com.billpaymentservice.bbps.entites.PaymentStartRecord;
import com.billpaymentservice.bbps.entites.TransactionMaster;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
public class PaymentStartWithTransactionDto {
private PaymentStartRecord paymentStartRecord;
private TransactionMaster transactionMaster;
}
@@ -6,29 +6,37 @@ import java.time.LocalDateTime;
import jakarta.persistence.PrePersist; import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate; import jakarta.persistence.PreUpdate;
import java.lang.reflect.Field;
import java.util.Date;
import jakarta.persistence.PrePersist;
import jakarta.persistence.PreUpdate;
public class AuditListener { public class AuditListener {
@PrePersist @PrePersist
public void prePersist(Object entity) { public void prePersist(Object entity) {
LocalDateTime now = LocalDateTime.now(); Date now = new Date(); // current date + time
// Creation fields // creation fields
setIfNull(entity, "createDateTime", now); setIfNull(entity, "createDateTime", now);
setIfNull(entity, "createdBy", null); // handle manually in service if needed setIfNull(entity, "createdBy", null);
setIfNull(entity, "createdEpochDate", System.currentTimeMillis()); setIfNull(entity, "createdEpochDate", now.getTime());
// Set isDeleted = false on creation // default soft delete flag
setAlways(entity, "isDeleted", Boolean.FALSE); setAlways(entity, "isDeleted", Boolean.FALSE);
} }
/* @PreUpdate
* @PreUpdate public void preUpdate(Object entity) { LocalDateTime now = public void preUpdate(Object entity) {
* LocalDateTime.now(); Date now = new Date();
*
* // Update fields setAlways(entity, "updatedDateTime", now); setIfNull(entity, // update fields
* "updatedBy", null); // you can override manually in service setAlways(entity, setAlways(entity, "updatedDateTime", now);
* "modifiedEpochDate", System.currentTimeMillis()); } setIfNull(entity, "updatedBy", null);
*/ setAlways(entity, "modifiedEpochDate", now.getTime());
}
private void setIfNull(Object entity, String fieldName, Object value) { private void setIfNull(Object entity, String fieldName, Object value) {
try { try {
Field f = findField(entity.getClass(), fieldName); Field f = findField(entity.getClass(), fieldName);
@@ -37,8 +45,7 @@ public class AuditListener {
boolean accessible = f.canAccess(entity); boolean accessible = f.canAccess(entity);
f.setAccessible(true); f.setAccessible(true);
Object currentValue = f.get(entity); if (f.get(entity) == null) {
if (currentValue == null) {
f.set(entity, value); f.set(entity, value);
} }
@@ -53,9 +60,7 @@ public class AuditListener {
boolean accessible = f.canAccess(entity); boolean accessible = f.canAccess(entity);
f.setAccessible(true); f.setAccessible(true);
f.set(entity, value); f.set(entity, value);
f.setAccessible(accessible); f.setAccessible(accessible);
} catch (Exception ignored) {} } catch (Exception ignored) {}
} }
@@ -1,6 +1,8 @@
package com.billpaymentservice.bbps.entites; package com.billpaymentservice.bbps.entites;
import java.io.Serializable;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.util.Date;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.EntityListeners; import jakarta.persistence.EntityListeners;
@@ -16,18 +18,18 @@ import lombok.Setter;
@EntityListeners(AuditListener.class) @EntityListeners(AuditListener.class)
@Getter @Getter
@Setter @Setter
public abstract class Auditable { public abstract class Auditable implements Serializable {
@Column(name = "create_date_time") @Column(name = "create_date_time")
private LocalDateTime createDateTime; private Date createDateTime;
@Column(name = "updated_date_time") @Column(name = "updated_date_time")
private LocalDateTime updatedDateTime; private Date updatedDateTime;
@Column(name = "create_by") @Column(name = "create_by")
private LocalDateTime createdBy; private String createdBy;
@Column(name = "updated_by") @Column(name = "updated_by")
private LocalDateTime updatedBy; private String updatedBy;
@Column(name = "created_epoch_date") @Column(name = "created_epoch_date")
private Long createdEpochDate; private Long createdEpochDate;
@@ -1,9 +1,5 @@
package com.billpaymentservice.bbps.entites; package com.billpaymentservice.bbps.entites;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
@@ -11,30 +7,15 @@ import lombok.NoArgsConstructor;
@Data @Data
@AllArgsConstructor @AllArgsConstructor
@NoArgsConstructor @NoArgsConstructor
@Entity public class PaymentInitiate {
@Table(name = "bbps_payment_initiate")
public class PaymentInitiate extends Auditable { // Kept as a POJO for compatibility but not annotated as @Entity so JPA won't map it
private String reqId;
private String bankTxnId;
private String clientUserId;
private String mid;
private String txnId;
private String status;
private String userId;
@Id
@Column(name = "req_id")
private String reqId;
@Column(name = "bank_txn_Id", nullable = false)
private String bankTxnId;
@Column(name = "client_user_id")
private String clientUserId;
@Column(name = "mid")
private String mid;
@Column(name = "txn_id")
private String txnId;
@Column(name = "status")
private String status;
@Column(name = "user_id")
private String userId;
} }
@@ -26,7 +26,7 @@ import jakarta.persistence.FetchType;
public class PaymentStartRecord extends Auditable { public class PaymentStartRecord extends Auditable {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id; private Long id;
@@ -47,16 +47,12 @@ public class PaymentStartRecord extends Auditable {
@Column(name = "amount", nullable = false) @Column(name = "amount", nullable = false)
private String amount; private String amount;
@Column(name = "txn_id", unique = true, nullable = false) @Column(name = "txn_id")
private String txnId; private String txnId;
@Column(name = "qr_string", columnDefinition = "TEXT") @Column(name = "qr_string", columnDefinition = "TEXT")
private String qrString; private String qrString;
// `createdOn` is provided by the base `Auditable` class as a LocalDateTime field,
// so we must not redeclare it here (it caused an incompatible return-type error).
// The Auditable base class already defines `createdOn` of type LocalDateTime.
// New callback-related fields // New callback-related fields
@Column(name = "callback_status") @Column(name = "callback_status")
private String callbackStatus; private String callbackStatus;
@@ -67,13 +63,6 @@ public class PaymentStartRecord extends Auditable {
@Column(name = "signature") @Column(name = "signature")
private String signature; private String signature;
// Store original request body and full response body
@Column(name = "request_body", columnDefinition = "TEXT")
private String requestBody;
@Column(name = "response_body", columnDefinition = "TEXT")
private String responseBody;
@Column(name = "response_type") @Column(name = "response_type")
private String responseType; // e.g., "QR", "HTML", "JSON" private String responseType; // e.g., "QR", "HTML", "JSON"
@@ -9,6 +9,8 @@ import jakarta.persistence.CascadeType;
import jakarta.persistence.Column; import jakarta.persistence.Column;
import jakarta.persistence.Entity; import jakarta.persistence.Entity;
import jakarta.persistence.FetchType; import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id; import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn; import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne; import jakarta.persistence.OneToOne;
@@ -28,17 +30,24 @@ import lombok.ToString;
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public class TransactionMaster extends Auditable { public class TransactionMaster extends Auditable {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
// 👇 JSON me "txn_id" aayega, ye DB me bank_txn_id column me save hoga
@Id
@JsonProperty("txn_id") @JsonProperty("txn_id")
@Column(name = "bank_txn_id", nullable = false, unique = true) @Column(name = "bank_txn_id",unique = true)
private String bankTxnId; private String bankTxnId;
// 👇 Apna internal transaction id, manually generate or system ke through
@Column(name = "txn_id") @Column(name = "txn_id")
private String txnId; private String txnId;
@Column(name = "initiator_txn_id")
private String initiatorTxnId;
@Column(name = "checker_txn_id")
private String checkerTxnId;
@Column(name = "client_user_id") @Column(name = "client_user_id")
private String clientUserId; private String clientUserId;
@@ -1,9 +1,16 @@
package com.billpaymentservice.bbps.repository; package com.billpaymentservice.bbps.repository;
import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import com.billpaymentservice.bbps.entites.OutboundTraceLog; import com.billpaymentservice.bbps.entites.OutboundTraceLog;
public interface OutboundTraceLogRepository extends JpaRepository<OutboundTraceLog, Long> { public interface OutboundTraceLogRepository extends JpaRepository<OutboundTraceLog, Long> {
Optional<OutboundTraceLog> findTopByTxnIdAndApiNameOrderByIdDesc(String txnId, String apiName);
List<OutboundTraceLog> findByTxnId(String txnId);
} }
@@ -1,17 +1,6 @@
package com.billpaymentservice.bbps.repository; package com.billpaymentservice.bbps.repository;
import java.util.Optional; // Legacy stub: PaymentInitiate table removed; keep a placeholder class to avoid compilation errors if referenced.
public class PaymentIntiateRepository {
import org.springframework.data.jpa.repository.JpaRepository; // Intentionally empty - PaymentInitiate is no longer a JPA entity and this repo is retired.
import org.springframework.data.jpa.repository.Query;
import com.billpaymentservice.bbps.entites.PaymentInitiate;
public interface PaymentIntiateRepository extends JpaRepository<PaymentInitiate, String> {
// Optional<PaymentInitiate> findByReqId(String clientReqId);
@Query(value = "SELECT * FROM bbps_payment_initiate WHERE req_id = :clientReqId", nativeQuery = true)
Optional<PaymentInitiate> findByReqId(String clientReqId);
} }
@@ -1,6 +1,7 @@
package com.billpaymentservice.bbps.repository; package com.billpaymentservice.bbps.repository;
import java.util.List; import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
@@ -10,7 +11,7 @@ import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import com.billpaymentservice.bbps.entites.TransactionMaster; import com.billpaymentservice.bbps.entites.TransactionMaster;
public interface TransactionMasterRepository extends JpaRepository<TransactionMaster, String> { public interface TransactionMasterRepository extends JpaRepository<TransactionMaster, Long> {
// @Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid OR t.clientUserId = :clientUserId") // @Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid OR t.clientUserId = :clientUserId")
// List<TransactionMaster> findByMidOrClientUserId(@Param("mid") String mid, // List<TransactionMaster> findByMidOrClientUserId(@Param("mid") String mid,
@@ -30,4 +31,16 @@ public interface TransactionMasterRepository extends JpaRepository<TransactionMa
// Find TransactionMaster(s) by the payment_ref_id column (references PaymentStartRecord.txn_id) // Find TransactionMaster(s) by the payment_ref_id column (references PaymentStartRecord.txn_id)
List<TransactionMaster> findByPaymentRefId(String paymentRefId); List<TransactionMaster> findByPaymentRefId(String paymentRefId);
// New finders to replace PaymentInitiate usage
List<TransactionMaster> findByClientReqId(String clientReqId);
List<TransactionMaster> findByStatus(String status);
List<TransactionMaster> findByStatusIn(List<String> statuses);
// Find by initiatorTxnId so services can look up records created during initiatePayment
TransactionMaster findByInitiatorTxnId(String initiatorTxnId);
Optional<TransactionMaster> findByBankTxnId(String txnId);
} }
@@ -0,0 +1,162 @@
package com.billpaymentservice.bbps.scheduler;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import org.modelmapper.ModelMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import com.billpaymentservice.bbps.dto.GeneralResp;
import com.billpaymentservice.bbps.dto.response.BillPaymentResponse;
import com.billpaymentservice.bbps.dto.request.TransactionStatusRequest;
import com.billpaymentservice.bbps.entites.BillPayment;
import com.billpaymentservice.bbps.entites.BharatConnectMaster;
import com.billpaymentservice.bbps.entites.OutboundTraceLog;
import com.billpaymentservice.bbps.entites.TransactionMaster;
import com.billpaymentservice.bbps.repository.BharatConnectMasterRepository;
import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository;
import com.billpaymentservice.bbps.repository.TransactionMasterRepository;
import com.billpaymentservice.bbps.repository.PaymentRepository;
import com.billpaymentservice.bbps.service.PaymentService;
import com.billpaymentservice.bbps.serviceImpl.HttpService;
import com.billpaymentservice.bbps.util.BBPSConstant;
import com.billpaymentservice.bbps.util.Helper;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
@Component
public class PaymentRetryScheduler {
private static final Logger LOGGER = LoggerFactory.getLogger(PaymentRetryScheduler.class);
private final TransactionMasterRepository transactionMasterRepository;
private final OutboundTraceLogRepository outboundTraceLogRepository;
private final BharatConnectMasterRepository beneficiaryDetailsRepository;
private final Helper helper;
private final HttpService httpService;
private final PaymentRepository paymentRepository;
private final PaymentService paymentService;
private final ObjectMapper objectMapper;
private final ModelMapper modelMapper;
@Autowired
public PaymentRetryScheduler(TransactionMasterRepository transactionMasterRepository,
OutboundTraceLogRepository outboundTraceLogRepository,
BharatConnectMasterRepository beneficiaryDetailsRepository, Helper helper, HttpService httpService,
PaymentRepository paymentRepository, PaymentService paymentService) {
this.transactionMasterRepository = transactionMasterRepository;
this.outboundTraceLogRepository = outboundTraceLogRepository;
this.beneficiaryDetailsRepository = beneficiaryDetailsRepository;
this.helper = helper;
this.httpService = httpService;
this.paymentRepository = paymentRepository;
this.paymentService = paymentService;
this.objectMapper = new ObjectMapper();
this.modelMapper = new ModelMapper();
}
// runs every 60 seconds by default; configurable via property scheduler.retry.delay.ms
@Scheduled(fixedDelayString = "${scheduler.retry.delay.ms:60000}")
public void retryPendingPayments() {
LOGGER.info("PaymentRetryScheduler triggered");
List<String> statuses = Arrays.asList("PENDING_UPSTREAM", "INVALID_RESPONSE_FROM_BBPS",
"INVALID_JSON_FROM_UPSTREAM", "Failed", "FAILED");
List<TransactionMaster> pendingList = transactionMasterRepository.findByStatusIn(statuses);
if (pendingList == null || pendingList.isEmpty()) {
LOGGER.debug("No pending TransactionMaster entries found");
return;
}
for (TransactionMaster tm : pendingList) {
try {
LOGGER.info("Retrying transaction txnId={} status={}", tm.getTxnId(), tm.getStatus());
Optional<OutboundTraceLog> outOpt = outboundTraceLogRepository
.findTopByTxnIdAndApiNameOrderByIdDesc(tm.getTxnId(), BBPSConstant.API_INITIATE_PAYMENT);
if (outOpt.isEmpty()) {
LOGGER.warn("No outbound trace log found for txnId={}", tm.getTxnId());
continue;
}
OutboundTraceLog outbound = outOpt.get();
String payload = outbound.getRequestPayload();
if (payload == null || payload.isEmpty()) {
LOGGER.warn("Outbound request payload empty for txnId={}", tm.getTxnId());
continue;
}
BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID);
HttpHeaders headers = helper.buildHeaders(headersData);
LOGGER.info("Resending request to BBPS for txnId={} url={}", tm.getTxnId(), outbound.getEndpointUrl());
Map<String, String> responseMap = httpService.postRequest(outbound.getEndpointUrl(), payload, headers);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
LOGGER.info("Retry response for txnId={} => {}", tm.getTxnId(), responseData);
helper.updateOutboundLogResponse(outbound, responseData);
outboundTraceLogRepository.save(outbound);
if (responseData == null || responseData.isEmpty()) {
LOGGER.warn("Empty response on retry for txnId={}", tm.getTxnId());
// leave as pending for next run
continue;
}
Map<String, Object> dataMap = objectMapper.readValue(responseData, new TypeReference<Map<String, Object>>() {});
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERIC_KEY)) {
LOGGER.warn("Retry response invalid structure for txnId={}", tm.getTxnId());
// update TransactionMaster status for visibility
tm.setStatus("INVALID_RESPONSE_FROM_BBPS");
transactionMasterRepository.save(tm);
continue;
}
BillPaymentResponse billResp = objectMapper.convertValue(dataMap, BillPaymentResponse.class);
GeneralResp gen = billResp.getGenericResponse();
if (gen == null) {
LOGGER.warn("Generic response null after retry for txnId={}", tm.getTxnId());
continue;
}
if (!"Failed".equalsIgnoreCase(gen.getStatus())) {
// success path
BillPayment entity = modelMapper.map(billResp, BillPayment.class);
paymentRepository.save(entity);
tm.setBankTxnId(billResp.getTxnId());
tm.setStatus(billResp.getStatus());
transactionMasterRepository.save(tm);
// trigger transaction status check (best-effort)
try {
TransactionStatusRequest tsReq = new TransactionStatusRequest();
tsReq.setTxnId(billResp.getTxnId());
tsReq.setReqId(tm.getReqId());
tsReq.setMid(tm.getMid());
tsReq.setClientUserId(tm.getClientUserId());
paymentService.checkTrasactionStatus(tsReq);
} catch (Exception e) {
LOGGER.warn("Transaction status check failed after retry for txnId={} : {}", tm.getTxnId(), e.getMessage());
}
} else {
// upstream returned failed status
tm.setBankTxnId(billResp.getTxnId());
tm.setStatus(gen.getStatus());
transactionMasterRepository.save(tm);
}
} catch (Exception ex) {
LOGGER.error("Error while retrying transaction txnId={}: {}", tm.getTxnId(), ex.getMessage(), ex);
}
}
}
}
@@ -468,14 +468,7 @@ public class AgentServiceImpl implements AgentService {
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
// ================================== Bill fetch and Bill Validate by Client Req
// Id==================================//
// =======
// ... remaining methods unchanged except where headers/objectMapper were
// previously created (they now use buildHeaders() and the class-level
// objectMapper)
// >>>>>>>d0812656ff2aee80b1eb0c0ab8e29a01d7d66553
@Override @Override
public ResponseModel fetchBillValidateByClientReqId(BillValidateByClientIdRequest billValidateByClientIdRequest) { public ResponseModel fetchBillValidateByClientReqId(BillValidateByClientIdRequest billValidateByClientIdRequest) {
@@ -127,7 +127,7 @@ public class ComplainServiceImpl implements ComplainService {
try { try {
Optional<TransactionMaster> dataById = transactionMasterRepository.findById(complainRequestDTO.getTxnId()); Optional<TransactionMaster> dataById = transactionMasterRepository.findByBankTxnId(complainRequestDTO.getTxnId());
if (dataById == null || dataById.isEmpty()) { if (dataById == null || dataById.isEmpty()) {
LOGGER.warn("Txn Data Not Found This Txn Id is null"); LOGGER.warn("Txn Data Not Found This Txn Id is null");
throw new CustomException(BBPSConstant.USER_Txn_IS_NOT_FOUND, "FAILED", BBPSConstant.BADREQUEST, null); throw new CustomException(BBPSConstant.USER_Txn_IS_NOT_FOUND, "FAILED", BBPSConstant.BADREQUEST, null);
@@ -181,20 +181,15 @@ public class HttpService {
try { try {
HttpHeaders headers = new HttpHeaders(); HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED); headers.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
log.info("🌐 Sending FORM POST to: {}", url); log.info("🌐 Sending FORM POST to: {}", url);
log.info("📦 Payload: {}", formBody); log.info("📦 Payload: {}", formBody);
ResponseEntity<String> restResponse = restClient.post() ResponseEntity<String> restResponse = restClient.post()
.uri(url) .uri(url)
.headers(h -> h.addAll(headers)) .headers(h -> h.addAll(headers))
.body(formBody) .body(formBody)
.retrieve() .retrieve()
.toEntity(String.class); .toEntity(String.class);
response.put("respCode", String.valueOf(restResponse.getStatusCode().value())); response.put("respCode", String.valueOf(restResponse.getStatusCode().value()));
// HTML RESPONSE directly stored
response.put("data", restResponse.getBody()); response.put("data", restResponse.getBody());
log.info("✅ FORM POST Response Status: {}", restResponse.getStatusCode()); log.info("✅ FORM POST Response Status: {}", restResponse.getStatusCode());
@@ -1,5 +1,6 @@
package com.billpaymentservice.bbps.serviceImpl; package com.billpaymentservice.bbps.serviceImpl;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@@ -12,6 +13,7 @@ import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import com.billpaymentservice.bbps.dto.GeneralResp; import com.billpaymentservice.bbps.dto.GeneralResp;
import com.billpaymentservice.bbps.dto.ResponseModel; import com.billpaymentservice.bbps.dto.ResponseModel;
import com.billpaymentservice.bbps.dto.TransactionDTO;
import com.billpaymentservice.bbps.dto.request.BillPaymentRequest; import com.billpaymentservice.bbps.dto.request.BillPaymentRequest;
import com.billpaymentservice.bbps.dto.request.TransactionStatusRequest; import com.billpaymentservice.bbps.dto.request.TransactionStatusRequest;
import com.billpaymentservice.bbps.dto.request.TrasactionStatusByClientReqIdRequest; import com.billpaymentservice.bbps.dto.request.TrasactionStatusByClientReqIdRequest;
@@ -23,14 +25,12 @@ import com.billpaymentservice.bbps.entites.BharatConnectMaster;
import com.billpaymentservice.bbps.entites.BillPayment; import com.billpaymentservice.bbps.entites.BillPayment;
import com.billpaymentservice.bbps.entites.InboundTraceLog; import com.billpaymentservice.bbps.entites.InboundTraceLog;
import com.billpaymentservice.bbps.entites.OutboundTraceLog; import com.billpaymentservice.bbps.entites.OutboundTraceLog;
import com.billpaymentservice.bbps.entites.PaymentInitiate;
import com.billpaymentservice.bbps.entites.TransactionMaster; import com.billpaymentservice.bbps.entites.TransactionMaster;
import com.billpaymentservice.bbps.exception.CustomException; import com.billpaymentservice.bbps.exception.CustomException;
import com.billpaymentservice.bbps.repository.AgentFundTransferRepository; import com.billpaymentservice.bbps.repository.AgentFundTransferRepository;
import com.billpaymentservice.bbps.repository.BharatConnectMasterRepository; import com.billpaymentservice.bbps.repository.BharatConnectMasterRepository;
import com.billpaymentservice.bbps.repository.InboundTraceLogRepository; import com.billpaymentservice.bbps.repository.InboundTraceLogRepository;
import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository; import com.billpaymentservice.bbps.repository.OutboundTraceLogRepository;
import com.billpaymentservice.bbps.repository.PaymentIntiateRepository;
import com.billpaymentservice.bbps.repository.PaymentRepository; import com.billpaymentservice.bbps.repository.PaymentRepository;
import com.billpaymentservice.bbps.repository.TransactionMasterRepository; import com.billpaymentservice.bbps.repository.TransactionMasterRepository;
import com.billpaymentservice.bbps.service.PaymentService; import com.billpaymentservice.bbps.service.PaymentService;
@@ -54,7 +54,6 @@ public class PaymentServiceImpl implements PaymentService {
private final PaymentRepository paymentRepository; private final PaymentRepository paymentRepository;
private final InboundTraceLogRepository inboundTraceLogRepository; private final InboundTraceLogRepository inboundTraceLogRepository;
private final OutboundTraceLogRepository outboundTraceLogRepository; private final OutboundTraceLogRepository outboundTraceLogRepository;
private final PaymentIntiateRepository paymentIntiateRepository;
private final TransactionMasterRepository transactionMasterRepository; private final TransactionMasterRepository transactionMasterRepository;
private final AgentFundTransferRepository agentFundTransferRepository; private final AgentFundTransferRepository agentFundTransferRepository;
private final BillerMapperHelper mapperHelper; private final BillerMapperHelper mapperHelper;
@@ -66,8 +65,7 @@ public class PaymentServiceImpl implements PaymentService {
public PaymentServiceImpl(BharatConnectMasterRepository beneficiaryDetailsRepository, HttpService httpService, public PaymentServiceImpl(BharatConnectMasterRepository beneficiaryDetailsRepository, HttpService httpService,
Helper helper, PaymentRepository paymentRepository, InboundTraceLogRepository inboundTraceLogRepository, Helper helper, PaymentRepository paymentRepository, InboundTraceLogRepository inboundTraceLogRepository,
OutboundTraceLogRepository outboundTraceLogRepository, PaymentIntiateRepository paymentIntiateRepository, OutboundTraceLogRepository outboundTraceLogRepository, TransactionMasterRepository transactionMasterRepository,
TransactionMasterRepository transactionMasterRepository,
AgentFundTransferRepository agentFundTransferRepository, BillerMapperHelper mapperHelper) { AgentFundTransferRepository agentFundTransferRepository, BillerMapperHelper mapperHelper) {
this.beneficiaryDetailsRepository = beneficiaryDetailsRepository; this.beneficiaryDetailsRepository = beneficiaryDetailsRepository;
this.httpService = httpService; this.httpService = httpService;
@@ -75,7 +73,6 @@ public class PaymentServiceImpl implements PaymentService {
this.paymentRepository = paymentRepository; this.paymentRepository = paymentRepository;
this.inboundTraceLogRepository = inboundTraceLogRepository; this.inboundTraceLogRepository = inboundTraceLogRepository;
this.outboundTraceLogRepository = outboundTraceLogRepository; this.outboundTraceLogRepository = outboundTraceLogRepository;
this.paymentIntiateRepository = paymentIntiateRepository;
this.transactionMasterRepository = transactionMasterRepository; this.transactionMasterRepository = transactionMasterRepository;
this.agentFundTransferRepository = agentFundTransferRepository; this.agentFundTransferRepository = agentFundTransferRepository;
this.mapperHelper = mapperHelper; this.mapperHelper = mapperHelper;
@@ -108,97 +105,76 @@ public class PaymentServiceImpl implements PaymentService {
String requestJson = helper.convertObjectToJson(billPaymentRequest); String requestJson = helper.convertObjectToJson(billPaymentRequest);
InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnId, apiName, billPaymentRequest.getMid(), InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnId, apiName, billPaymentRequest.getMid(),
BBPSConstant.HTTP_POST, requestJson); BBPSConstant.HTTP_POST, requestJson);
LOGGER.debug("Inbound log created for api={} | reqId={} | txnId={}", apiName, requestId, txnId); LOGGER.debug("Inbound log created for api={} | reqId={} | txnId={}", apiName, requestId, txnId);
OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, BBPSConstant.HTTP_POST, OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, BBPSConstant.HTTP_POST,
forwardjson); forwardjson);
LOGGER.debug("Outbound log created for api={} | reqId={} | txnId={}", apiName, requestId, txnId); LOGGER.debug("Outbound log created for api={} | reqId={} | txnId={}", apiName, requestId, txnId);
try { TransactionMaster preTransaction=null;
LOGGER.info("Calling BBPS payment URL: {}", url);
Map<String, String> responseMap = httpService.postRequest(url, forwardjson, headers);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
LOGGER.info("Payment API Response: {}", responseData);
helper.updateOutboundLogResponse(outboundLog, responseData); preTransaction = new TransactionMaster();
LOGGER.debug("Outbound log response updated for txnId={}", txnId); preTransaction.setTxnId(txnId);
preTransaction.setInitiatorTxnId(txnId);
preTransaction.setReqId(requestId);
preTransaction.setMid(billPaymentRequest.getMid());
preTransaction.setClientUserId(billPaymentRequest.getClientUserId());
preTransaction.setClientReqId(billPaymentRequest.getClientReqId());
preTransaction.setUserId(userId);
preTransaction.setStatus("PENDING");
EntityRelationshipUtil.linkTransactionMasterRelationships(preTransaction);
// ensure txnId exists (no uniqueness suffixing)
preTransaction.setTxnId(ensureTxnId(preTransaction.getTxnId()));
transactionMasterRepository.save(preTransaction);
LOGGER.info("Pre-request TransactionMaster saved with status=INITIATED for txnId={}", txnId);
if (responseData == null || responseData.isEmpty()) { LOGGER.info("Calling BBPS payment URL: {}", url);
helper.updateInboundLogFailure(inboundLog, BBPSConstant.EMPTY_RESPONSE_FROM_BBPS, Map<String, String> responseMap = httpService.postRequest(url, forwardjson, headers);
BBPSConstant.FAILED_STR);
LOGGER.warn("Empty response received from BBPS for txnId={}", txnId);
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, String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
new TypeReference<Map<String, Object>>() { LOGGER.info("Payment API Response: {}", responseData);
});
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERIC_KEY)) {
helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_STRUCTURE);
LOGGER.warn("Invalid response structure received for txnId={}", txnId);
throw new CustomException(BBPSConstant.INVALID_STRUCTURE);
}
BillPaymentResponse billPaymentResponse = objectMapper.convertValue(dataMap, BillPaymentResponse.class);
GeneralResp generalResponse = billPaymentResponse.getGenericResponse();
LOGGER.info("genric Response" + generalResponse);
if(generalResponse.getStatus().equalsIgnoreCase("Failed")) {
throw new CustomException(generalResponse.getMessage(), generalResponse.getStatus(),
Integer.parseInt(generalResponse.getStatusCode()), generalResponse.getRemarks());
}
BillPayment entity = modelMapper.map(billPaymentResponse, BillPayment.class); helper.updateOutboundLogResponse(outboundLog, responseData);
paymentRepository.save(entity); LOGGER.debug("Outbound log response updated for txnId={}", txnId);
LOGGER.info("BillPayment entity saved successfully for txnId={}", txnId);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus());
PaymentInitiate paymentInitiate = new PaymentInitiate();
paymentInitiate.setTxnId(txnId);
paymentInitiate.setUserId(userId);
paymentInitiate.setReqId(requestId);
paymentInitiate.setMid(billPaymentRequest.getMid());
paymentInitiate.setClientUserId(billPaymentRequest.getClientUserId());
paymentInitiate.setBankTxnId(billPaymentResponse.getTxnId());
paymentInitiate.setStatus(billPaymentResponse.getStatus());
paymentIntiateRepository.save(paymentInitiate);
LOGGER.info("PaymentInitiate entry saved successfully for txnId={}", txnId);
TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest(); Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog);
transactionStatusRequest.setTxnId(billPaymentResponse.getTxnId());
transactionStatusRequest.setReqId(requestId); BillPaymentResponse billPaymentResponse = objectMapper.convertValue(dataMap, BillPaymentResponse.class);
transactionStatusRequest.setMid(billPaymentRequest.getMid());
transactionStatusRequest.setClientUserId(billPaymentRequest.getClientUserId()); GeneralResp generalResponse = billPaymentResponse.getGenericResponse();
LOGGER.info("genric Response" + generalResponse);
BillPayment entity = modelMapper.map(billPaymentResponse, BillPayment.class);
paymentRepository.save(entity);
LOGGER.info("BillPayment entity saved successfully for txnId={}", txnId);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResponse.getStatus());
preTransaction.setBankTxnId(entity.getTxnId());
transactionMasterRepository.save(preTransaction);
LOGGER.info("updated TransactionMaster with bankTxnId for txnId={}", txnId);
TransactionStatusRequest transactionStatusRequest = new TransactionStatusRequest();
transactionStatusRequest.setReqId(billPaymentRequest.getReqId());
transactionStatusRequest.setTxnId(entity.getTxnId());
ResponseModel responseModel = checkTrasactionStatus(transactionStatusRequest, inboundLog);
return responseModel;
try {
Thread.sleep((long) BBPSConstant.PAYMENT_STATUS_DELAY_MS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
LOGGER.warn("Thread interrupted during payment delay: {}", e.getMessage());
}
LOGGER.info("Checking transaction status for txnId={}", billPaymentResponse.getTxnId());
LOGGER.info("[{}] END | reqId={} | txnId={}", apiName, requestId, txnId);
return checkTrasactionStatus(transactionStatusRequest, inboundLog);
} catch (JsonProcessingException e) {
LOGGER.error("JSON Parsing Error in initiatePayment: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
}
} }
public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest, public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest,
InboundTraceLog inboundLog) { InboundTraceLog inboundLog) {
String apiName = BBPSConstant.API_CHECK_TRANSACTION_STATUS; String apiName = BBPSConstant.API_CHECK_TRANSACTION_STATUS;
String txnId = helper.generateTxnId(15); String checkerTxnId = helper.generateTxnId(15);
String requestId = transactionStatusRequest.getReqId(); String requestId = transactionStatusRequest.getReqId();
// START log for internal transaction status check // START log for internal transaction status check
LOGGER.info("[{}] START (internal) | checkTxnId={} | reqId={} | bankTxnId={}", apiName, txnId, requestId, LOGGER.info("[{}] START (internal) | checkTxnId={} | reqId={} | bankTxnId={}", apiName, checkerTxnId, requestId,
transactionStatusRequest.getTxnId()); transactionStatusRequest.getTxnId());
String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId(); String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId();
@@ -209,132 +185,142 @@ public class PaymentServiceImpl implements PaymentService {
BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID);
HttpHeaders headers = helper.buildHeaders(headersData); HttpHeaders headers = helper.buildHeaders(headersData);
OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, url, BBPSConstant.HTTP_GET, OutboundTraceLog outboundLog = helper.createOutboundLog(checkerTxnId, requestId, apiName, url, BBPSConstant.HTTP_GET,
null); null);
LOGGER.debug("Outbound log created for internal status check | txnId={} | reqId={}", txnId, requestId); LOGGER.debug("Outbound log created for internal status check | txnId={} | reqId={}", checkerTxnId, requestId);
Map<String, String> responseMap = httpService.getRequest(url, headers); Map<String, String> responseMap = httpService.getRequest(url, headers);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
LOGGER.info("Transaction status response: {}", responseData);
helper.updateOutboundLogResponse(outboundLog, responseData); helper.updateOutboundLogResponse(outboundLog, responseData);
LOGGER.debug("Outbound log response updated for internal status check | txnId={}", txnId); LOGGER.debug("Outbound log response updated for internal status check | txnId={}", checkerTxnId);
try { Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog);
Map<String, Object> dataMap = objectMapper.readValue(responseData, if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) {
new TypeReference<Map<String, Object>>() { helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE);
}); LOGGER.warn("Invalid transaction status response for txnId={}", transactionStatusRequest.getTxnId());
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE); Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
LOGGER.warn("Invalid transaction status response for txnId={}", transactionStatusRequest.getTxnId()); String.valueOf(dataMap.get("remarks")));
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
String.valueOf(dataMap.get("remarks")));
}
TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
TransactionStatusResponse.class);
GeneralResp generalResp = responseWrapper.getGeneralResp();
LOGGER.info("Transaction status received: {}", generalResp.getStatus());
TransactionMaster transactionMaster = modelMapper.map(responseWrapper.getTransactionDAO(),
TransactionMaster.class);
transactionMaster.setTxnId(txnId);
transactionMaster.setReqId(transactionStatusRequest.getReqId());
transactionMaster.setMid(transactionStatusRequest.getMid());
transactionMaster.setClientUserId(transactionStatusRequest.getClientUserId());
EntityRelationshipUtil.linkTransactionMasterRelationships(transactionMaster);
transactionMasterRepository.save(transactionMaster);
LOGGER.info("TransactionMaster saved successfully for txnId={}", txnId);
AgentFundTransfer agentFundTransfer = modelMapper.map(responseWrapper.getAgentFundTransfer(),
AgentFundTransfer.class);
agentFundTransfer.setTxnId(txnId);
agentFundTransfer.setReqId(transactionStatusRequest.getReqId());
agentFundTransfer.setTransactionMasterId(transactionMaster.getBankTxnId());
agentFundTransferRepository.save(agentFundTransfer);
LOGGER.info("AgentFundTransfer entry saved successfully for txnId={}", txnId);
ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("[{}] END (internal) | checkTxnId={} | reqId={} | status={}", apiName, txnId, requestId,
generalResp.getStatus());
return responseModel;
} catch (JsonProcessingException e) {
LOGGER.error("Error parsing transaction status JSON: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
TransactionStatusResponse.class);
GeneralResp generalResp = responseWrapper.getGeneralResp();
TransactionDTO txDao = responseWrapper.getTransactionDAO();
TransactionMaster transactionMaster = modelMapper.map(txDao,
TransactionMaster.class);
LOGGER.info("INBOUND TRANSACTION ID:{}",inboundLog.getTxnId());
TransactionMaster
existingTransaction=transactionMasterRepository.findByInitiatorTxnId(
inboundLog.getTxnId());
LOGGER.info("existingTransaction :{}",existingTransaction);
if(existingTransaction != null) {
LOGGER.info("Existing TransactionMaster found for bankTxnId");
// Merge incoming non-null fields into the existing DB entity so we preserve DB values
helper.mergeNonNullFields(transactionMaster, existingTransaction);
transactionMaster = existingTransaction;
}
transactionMaster.setCheckerTxnId(checkerTxnId);
EntityRelationshipUtil.linkTransactionMasterRelationships(transactionMaster);
AgentFundTransfer agentFundTransfer = null;
if (responseWrapper.getAgentFundTransfer() != null) {
agentFundTransfer = modelMapper.map(responseWrapper.getAgentFundTransfer(), AgentFundTransfer.class);
} else {
agentFundTransfer = new AgentFundTransfer();
}
agentFundTransfer.setTxnId(transactionMaster.getTxnId());
agentFundTransfer.setReqId(transactionStatusRequest.getReqId());
agentFundTransfer.setTransactionMasterId(transactionMaster.getBankTxnId());
transactionMasterRepository.save(transactionMaster);
if (agentFundTransfer != null) { agentFundTransferRepository.save(agentFundTransfer); }
LOGGER.info("Persisted TransactionMaster and AgentFundTransfer for txnId={} bankTxnId={}"
, transactionMaster.getTxnId(), transactionMaster.getBankTxnId());
ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("[{}] END (internal) | checkTxnId={} | reqId={} | status={}", apiName, checkerTxnId, requestId, generalResp.getStatus());
return responseModel;
} }
@Override @Override
public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest) { public ResponseModel checkTrasactionStatus(TransactionStatusRequest transactionStatusRequest) {
helper.validateMerchant(transactionStatusRequest.getMid()); helper.validateMerchant(transactionStatusRequest.getMid());
String txnID = helper.generateTxnId(15); String txnID = helper.generateTxnId(15);
String apiName = BBPSConstant.API_CHECK_TRANSACTION_PUBLIC; String apiName = BBPSConstant.API_CHECK_TRANSACTION_PUBLIC;
String requestId = transactionStatusRequest.getReqId(); String requestId = transactionStatusRequest.getReqId();
LOGGER.info("[{}] START (public) | reqId={} | mid={} | checkBankTxnId={}", apiName, requestId, LOGGER.info("[{}] START (public) | reqId={} | mid={} | checkBankTxnId={}", apiName, requestId,
transactionStatusRequest.getMid(), transactionStatusRequest.getTxnId()); transactionStatusRequest.getMid(), transactionStatusRequest.getTxnId());
String requestJson = helper.convertObjectToJson(transactionStatusRequest); String requestJson = helper.convertObjectToJson(transactionStatusRequest);
String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId(); String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_URL + transactionStatusRequest.getTxnId();
LOGGER.info("Checking transaction status (public) for txnId={} and reqId={}", LOGGER.info("Checking transaction status (public) for txnId={} and reqId{}",
transactionStatusRequest.getTxnId(), requestId); transactionStatusRequest.getTxnId(), requestId);
BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); // First, attempt to find an existing TransactionMaster by initiatorTxnId and update it
HttpHeaders headers = helper.buildHeaders(headersData); String initiatorTxnId = transactionStatusRequest.getTxnId();
if (initiatorTxnId != null && !initiatorTxnId.isBlank()) {
// logic intentionally left minimal here — any runtime exception should bubble
// to the global exception handler so failures are visible and handled centrally.
}
InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName, LOGGER.info("Falling back to external BBPS status check URL={}", url);
transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson);
LOGGER.debug("Inbound log created for public status check | reqId={} | txnId={}", requestId, txnID);
OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET,
null);
LOGGER.debug("Outbound log created for public status check | reqId={} | txnId={}", requestId, txnID);
Map<String, String> responseMap = httpService.getRequest(url, headers); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY); HttpHeaders headers = helper.buildHeaders(headersData);
LOGGER.info("Transaction status response data: {}", responseData);
helper.updateOutboundLogResponse(outboundLog, responseData);
LOGGER.debug("Outbound log response updated for public status check | reqId={} | txnId={}", requestId, txnID);
try { InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName,
Map<String, Object> dataMap = objectMapper.readValue(responseData, transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson);
new TypeReference<Map<String, Object>>() { LOGGER.debug("Inbound log created for public status check | reqId={} | txnId={}", requestId, txnID);
}); OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET,
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) { null);
helper.updateInboundLogFailure(inboundLog, responseData, LOGGER.debug("Outbound log created for public status check | reqId={} | txnId={}", requestId, txnID);
String.valueOf(dataMap.get(BBPSConstant.RESPONSE_STATUS_KEY)));
LOGGER.warn("Invalid transaction status response for txnId={} from BBPS",
transactionStatusRequest.getTxnId());
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
String.valueOf(dataMap.get("remarks")));
}
TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap, Map<String, String> responseMap = httpService.getRequest(url, headers);
TransactionStatusResponse.class); String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
GeneralResp generalResp = responseWrapper.getGeneralResp(); LOGGER.info("Transaction status response data: {}", responseData);
helper.updateOutboundLogResponse(outboundLog, responseData);
LOGGER.debug("Outbound log response updated for public status check | reqId={} | txnId={}", requestId, txnID);
String status = generalResp.getStatus(); Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog);
LOGGER.info("Current transaction status: {}", status); if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) {
helper.updateInboundLogFailure(inboundLog, responseData,
String.valueOf(dataMap.get(BBPSConstant.RESPONSE_STATUS_KEY)));
LOGGER.warn("Invalid transaction status response for txnId={} from BBPS",
transactionStatusRequest.getTxnId());
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
String.valueOf(dataMap.get("remarks")));
}
ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(), TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
Integer.parseInt(generalResp.getStatusCode()), responseWrapper); TransactionStatusResponse.class);
GeneralResp generalResp = responseWrapper.getGeneralResp();
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus()); String status = generalResp.getStatus();
LOGGER.info("[{}] END (public) | reqId={} | txnId={} | status={}", apiName, requestId, txnID, LOGGER.info("Current transaction status: {}", status);
generalResp.getStatus());
return responseModel;
} catch (JsonProcessingException e) { // --- NEW: attempt to persist/update TransactionMaster so DB reflects new status/bankTxnId ---
LOGGER.error("Error parsing responseData JSON: {}", e.getMessage(), e); TransactionDTO txDao = responseWrapper.getTransactionDAO();
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); TransactionMaster updatedEntity = null;
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
}
} ResponseModel responseModel = new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("[{}] END (public) | reqId={} | txnId={} | status={}", apiName, requestId, txnID,
generalResp.getStatus());
return responseModel;
}
@Override @Override
public ResponseModel checkTrasactionStatusByClientReqId( public ResponseModel checkTrasactionStatusByClientReqId(
@@ -350,72 +336,92 @@ public class PaymentServiceImpl implements PaymentService {
LOGGER.info("[{}] START | reqId={} | mid={} | clientReqId={}", apiName, requestId, LOGGER.info("[{}] START | reqId={} | mid={} | clientReqId={}", apiName, requestId,
transactionStatusRequest.getMid(), clientReqId); transactionStatusRequest.getMid(), clientReqId);
Optional<PaymentInitiate> paymentInitiate = paymentIntiateRepository.findByReqId(clientReqId); // look up TransactionMaster(s) by clientReqId instead of PaymentInitiate
if (paymentInitiate.isEmpty()) { List<TransactionMaster> transactionMasters = transactionMasterRepository.findByClientReqId(clientReqId);
if (transactionMasters == null || transactionMasters.isEmpty()) {
throw new CustomException( throw new CustomException(
BBPSConstant.NO_BILL_FOUND, // message // status BBPSConstant.NO_BILL_FOUND, // message // status
BBPSConstant.FAILED_STATUS, BBPSConstant.FAILED_STATUS,
BBPSConstant.HTTP_NOT_FOUND, // statusCode BBPSConstant.HTTP_NOT_FOUND, // statusCode
"Bill not found in database for given clientReqId" "Bill not found in database for given clientReqId"
); );
} }
// prefer a TransactionMaster with a real bankTxnId (not a placeholder)
TransactionMaster chosen = null;
for (TransactionMaster t : transactionMasters) {
if (t.getBankTxnId() != null && !t.getBankTxnId().startsWith("PENDING_") && !t.getBankTxnId().startsWith("INVALID_") && !t.getBankTxnId().startsWith("INVALID_JSON_")) {
chosen = t;
break;
}
}
if (chosen == null) chosen = transactionMasters.get(0);
String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_BY_CLIENT_REQ_ID_URL String url = paymentUrl + BBPSConstant.CHECK_TRANSACTION_BY_CLIENT_REQ_ID_URL
+ paymentInitiate.get().getTxnId(); + chosen.getTxnId();
BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID); BharatConnectMaster headersData = beneficiaryDetailsRepository.findByOrgId(BBPSConstant.ORG_ID);
HttpHeaders headers = helper.buildHeaders(headersData); HttpHeaders headers = helper.buildHeaders(headersData);
InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName, InboundTraceLog inboundLog = helper.createInboundLog(requestId, txnID, apiName,
transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson); transactionStatusRequest.getMid(), BBPSConstant.HTTP_POST, requestJson);
LOGGER.debug("Inbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID); LOGGER.debug("Inbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID);
OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET, OutboundTraceLog outboundLog = helper.createOutboundLog(txnID, requestId, apiName, url, BBPSConstant.HTTP_GET,
null); null);
LOGGER.debug("Outbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID); LOGGER.debug("Outbound log created for clientReqId check | reqId={} | txnId={}", requestId, txnID);
LOGGER.info("Calling BBPS Transaction Status API URL={}", url);
Map<String, String> responseMap = httpService.getRequest(url, headers);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
LOGGER.info("Transaction Status Response: {}", responseData);
helper.updateOutboundLogResponse(outboundLog, responseData);
LOGGER.debug("Outbound log response updated for clientReqId check | reqId={} | txnId={}", requestId, txnID);
Map<String, Object> dataMap = parseJsonToMap(responseData, inboundLog);
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) {
helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE);
LOGGER.warn("Invalid transaction status response for clientReqId={} | Response={}", clientReqId,
responseData);
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
String.valueOf(dataMap.get("remarks")));
}
TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
TransactionStatusResponse.class);
GeneralResp generalResp = responseWrapper.getGeneralResp();
LOGGER.info("Transaction Status for clientReqId={} => {}", clientReqId, generalResp.getStatus());
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("[{}] END | reqId={} | clientReqId={} | status={}", apiName, requestId, clientReqId,
generalResp.getStatus());
return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
}
private String ensureTxnId(String txnId) {
if (txnId == null || txnId.isBlank()) {
return helper.generateTxnId(15);
}
return txnId;
}
private Map<String, Object> parseJsonToMap(String responseData, InboundTraceLog inboundLog) {
try { try {
LOGGER.info("Calling BBPS Transaction Status API URL={}", url); return objectMapper.readValue(responseData, new TypeReference<Map<String, Object>>() {});
Map<String, String> responseMap = httpService.getRequest(url, headers);
String responseData = responseMap.get(BBPSConstant.RESPONSE_DATA_KEY);
LOGGER.info("Transaction Status Response: {}", responseData);
helper.updateOutboundLogResponse(outboundLog, responseData);
LOGGER.debug("Outbound log response updated for clientReqId check | reqId={} | txnId={}", requestId, txnID);
Map<String, Object> dataMap = objectMapper.readValue(responseData,
new TypeReference<Map<String, Object>>() {
});
if (!dataMap.containsKey(BBPSConstant.RESPONSE_GENERAL_RESP)) {
helper.updateInboundLogFailure(inboundLog, responseData, BBPSConstant.INVALID_RESPONSE);
LOGGER.warn("Invalid transaction status response for clientReqId={} | Response={}", clientReqId,
responseData);
throw new CustomException(String.valueOf(dataMap.get("message")), String.valueOf(dataMap.get("status")),
Integer.parseInt(String.valueOf(dataMap.get("statusCode"))),
String.valueOf(dataMap.get("remarks")));
}
TransactionStatusResponse responseWrapper = objectMapper.convertValue(dataMap,
TransactionStatusResponse.class);
GeneralResp generalResp = responseWrapper.getGeneralResp();
LOGGER.info("Transaction Status for clientReqId={} => {}", clientReqId, generalResp.getStatus());
helper.updateInboundLogSuccess(inboundLog, responseData, generalResp.getStatus());
LOGGER.info("[{}] END | reqId={} | clientReqId={} | status={}", apiName, requestId, clientReqId,
generalResp.getStatus());
return new ResponseModel(generalResp.getMessage(), generalResp.getStatus(),
Integer.parseInt(generalResp.getStatusCode()), responseWrapper);
} catch (JsonProcessingException e) { } catch (JsonProcessingException e) {
LOGGER.error("Error parsing JSON in transaction status API: {}", e.getMessage(), e); LOGGER.error("Error parsing JSON response: {}", e.getMessage(), e);
helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR); helper.updateInboundLogFailure(inboundLog, e.getMessage(), BBPSConstant.JSON_ERROR);
throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM,BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage()); throw new CustomException(BBPSConstant.MSG_INVALID_JSON_FROM_UPSTREAM, BBPSConstant.FAILED_STATUS, BBPSConstant.INVALID_JSON_STATUS_CODE, e.getMessage());
} }
} }
@@ -87,7 +87,6 @@ public class PaymentStartServiceImpl implements PaymentStartService {
String txnId = helper.generateTxnId(15); String txnId = helper.generateTxnId(15);
LOGGER.info("MID IS:{}",paymentMid); LOGGER.info("MID IS:{}",paymentMid);
LOGGER.info("[startIntentPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid()); LOGGER.info("[startIntentPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid());
helper.validateMerchant(request.getMid()); helper.validateMerchant(request.getMid());
// inbound log // inbound log
@@ -112,8 +111,12 @@ public class PaymentStartServiceImpl implements PaymentStartService {
LOGGER.info("[startIntentPayment] outbound payload prepared txnId={} orderNo={}", txnId, orderNo); LOGGER.info("[startIntentPayment] outbound payload prepared txnId={} orderNo={}", txnId, orderNo);
OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_INTENT, BBPSConstant.HTTP_POST, forwardJson); OutboundTraceLog outboundLog = helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_INTENT, BBPSConstant.HTTP_POST, forwardJson);
PaymentStartRecord paymentRecord=bbpsMapperHelper.savePaymentStartRecord(request, null, txnId, orderNo, requestJson, null, null);
// call external API // call external API
Map<String, String> responseMap = httpService.postRequest(BBPSConstant.UAT_PAYMENT_URL_INTENT, forwardJson, null); Map<String, String> responseMap = httpService.postRequest(BBPSConstant.UAT_PAYMENT_URL_INTENT, forwardJson, null);
LOGGER.debug("[startIntentPayment] raw response map: {}", responseMap); LOGGER.debug("[startIntentPayment] raw response map: {}", responseMap);
int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR))); int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)));
@@ -124,12 +127,15 @@ public class PaymentStartServiceImpl implements PaymentStartService {
if (httpCode != BBPSConstant.HTTP_OK_INT) { if (httpCode != BBPSConstant.HTTP_OK_INT) {
helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode)); helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode));
LOGGER.error("[startIntentPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody); LOGGER.error("[startIntentPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody);
paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR);
paymentStartRecordRepository.save(paymentRecord);
throw new CustomException(BBPSConstant.MSG_UPSTREAM_HTTP_ERROR, BBPSConstant.FAILED_STR, httpCode, respBody); throw new CustomException(BBPSConstant.MSG_UPSTREAM_HTTP_ERROR, BBPSConstant.FAILED_STR, httpCode, respBody);
} }
if (respBody == null || respBody.isEmpty()) { if (respBody == null || respBody.isEmpty()) {
helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)); helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR));
LOGGER.error("[startIntentPayment] empty response txnId={}", txnId); LOGGER.error("[startIntentPayment] empty response txnId={}", txnId);
paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR);
throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null); throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PROVIDER, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null);
} }
@@ -211,79 +217,83 @@ public class PaymentStartServiceImpl implements PaymentStartService {
return sent; return sent;
} }
@Override @Override
public ResponseModel startWebPayment(PaymentStartUserRequest request) { public ResponseModel startWebPayment(PaymentStartUserRequest request) {
String requestId = request.getReqId(); String requestId = request.getReqId();
String apiName = BBPSConstant.API_START_PAYMENT; String apiName = BBPSConstant.API_START_PAYMENT;
String txnId = helper.generateTxnId(15); String txnId = helper.generateTxnId(15);
LOGGER.info("[startWebPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid());
LOGGER.info("[startWebPayment] enter requestId={} txnId={} mid={}", requestId, txnId, request.getMid()); helper.validateMerchant(request.getMid());
helper.validateMerchant(request.getMid()); // inbound log
String requestJson = helper.convertObjectToJson(request);
// inbound log InboundTraceLog inboundLog =
String requestJson = helper.convertObjectToJson(request);
InboundTraceLog inboundLog =
helper.createInboundLog(requestId, txnId, apiName, request.getMid(), helper.createInboundLog(requestId, txnId, apiName, request.getMid(),
BBPSConstant.HTTP_POST, requestJson); BBPSConstant.HTTP_POST, requestJson);
LOGGER.debug("[startWebPayment] inbound log created txnId={} requestId={}", txnId, requestId); LOGGER.debug("[startWebPayment] inbound log created txnId={} requestId={}", txnId, requestId);
// Prepare request payload // Prepare request payload
PaymentDetailSentRequest sent = createPaymentSentRequest(request); PaymentDetailSentRequest sent = createPaymentSentRequest(request);
String orderNo = "ORD" + helper.generateTxnId(12); String orderNo = "ORD" + helper.generateTxnId(12);
sent.setOrderNo(orderNo); sent.setOrderNo(orderNo);
String plaintext = helper.convertObjectToJson(sent); String plaintext = helper.convertObjectToJson(sent);
String encKey = paymentEncryKey; String encKey = paymentEncryKey;
sent.setEnckey(encKey); sent.setEnckey(encKey);
LOGGER.debug("[startWebPayment] plaintext prepared txnId={} orderNo={}", txnId, orderNo); LOGGER.debug("[startWebPayment] plaintext prepared txnId={} orderNo={}", txnId, orderNo);
String encrypted = encryptionUtil.encryptMData(plaintext, encKey); String encrypted = encryptionUtil.encryptMData(plaintext, encKey);
LOGGER.info("[startWebPayment] payload encrypted txnId={} orderNo={}", txnId, orderNo); LOGGER.info("[startWebPayment] payload encrypted txnId={} orderNo={}", txnId, orderNo);
// ----------- BUILD x-www-form-urlencoded PAYLOAD ----------- // ----------- BUILD x-www-form-urlencoded PAYLOAD -----------
String formPayload = String formPayload =
"mid=" + URLEncoder.encode(paymentMid, StandardCharsets.UTF_8) + "mid=" + URLEncoder.encode(paymentMid, StandardCharsets.UTF_8) +
"&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8); "&payload=" + URLEncoder.encode(encrypted, StandardCharsets.UTF_8);
LOGGER.info("[startWebPayment] Form Payload: {}", formPayload); LOGGER.info("[startWebPayment] Form Payload: {}", formPayload);
OutboundTraceLog outboundLog = OutboundTraceLog outboundLog =
helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_WEB, helper.createOutboundLog(txnId, requestId, apiName, BBPSConstant.UAT_PAYMENT_URL_WEB,
BBPSConstant.HTTP_POST, formPayload); BBPSConstant.HTTP_POST, formPayload);
LOGGER.debug("[startWebPayment] outbound log created txnId={} url={}", txnId, BBPSConstant.UAT_PAYMENT_URL_WEB); LOGGER.debug("[startWebPayment] outbound log created txnId={} url={}", txnId, BBPSConstant.UAT_PAYMENT_URL_WEB);
PaymentStartRecord paymentRecord= bbpsMapperHelper.savePaymentStartRecord(request, null, txnId, orderNo, requestJson, null, null);
// ----------- CALL API (FORM URL-ENCODED) ----------- // ----------- CALL API (FORM URL-ENCODED) -----------
Map<String, String> responseMap = Map<String, String> responseMap = httpService.postFormRequest(BBPSConstant.UAT_PAYMENT_URL_WEB, formPayload);
httpService.postFormRequest(BBPSConstant.UAT_PAYMENT_URL_WEB, formPayload);
LOGGER.info("[startWebPayment] Payment Start Response: {}", responseMap); LOGGER.info("[startWebPayment] Payment Start Response: {}", responseMap);
// parse http code and response body using centralized keys // parse http code and response body using centralized keys
int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR))); int httpCode = Integer.parseInt(responseMap.getOrDefault(BBPSConstant.RESP_CODE_KEY, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)));
String respBody = responseMap.getOrDefault(BBPSConstant.RESPONSE_DATA_KEY, ""); String respBody = responseMap.getOrDefault(BBPSConstant.RESPONSE_DATA_KEY, "");
helper.updateOutboundLogResponse(outboundLog, respBody); helper.updateOutboundLogResponse(outboundLog, respBody);
LOGGER.debug("[startWebPayment] updated outbound log response txnId={} httpCode={}", txnId, httpCode); LOGGER.debug("[startWebPayment] updated outbound log response txnId={} httpCode={}", txnId, httpCode);
// ---- HTTP FAILURE ---- // ---- HTTP FAILURE ----
if (httpCode != BBPSConstant.HTTP_OK_INT) { if (httpCode != BBPSConstant.HTTP_OK_INT) {
helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode)); helper.updateInboundLogFailure(inboundLog, respBody, String.valueOf(httpCode));
LOGGER.error("[startWebPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody); LOGGER.error("[startWebPayment] upstream HTTP error txnId={} httpCode={} body={}", txnId, httpCode, respBody);
throw new CustomException("Upstream HTTP Error", BBPSConstant.FAILED_STR, httpCode, respBody); paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR);
} paymentStartRecordRepository.save(paymentRecord);
throw new CustomException("Upstream HTTP Error", BBPSConstant.FAILED_STR, httpCode, respBody);
}
// ---- EMPTY RESPONSE ---- // ---- EMPTY RESPONSE ----
if (respBody == null || respBody.isEmpty()) { if (respBody == null || respBody.isEmpty()) {
helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PG, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR)); helper.updateInboundLogFailure(inboundLog, BBPSConstant.MSG_EMPTY_RESPONSE_PG, String.valueOf(BBPSConstant.HTTP_INTERNAL_SERVER_ERROR));
LOGGER.error("[startWebPayment] empty response from PG txnId={}", txnId); LOGGER.error("[startWebPayment] empty response from PG txnId={}", txnId);
throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PG, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null); paymentRecord.setTxnStatus(BBPSConstant.FAILED_STR);
} paymentStartRecordRepository.save(paymentRecord);
throw new CustomException(BBPSConstant.MSG_EMPTY_RESPONSE_PG, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, null);
}
// PG RETURNS HTML → NO JSON PARSING REQUIRED // PG RETURNS HTML → NO JSON PARSING REQUIRED
helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(BBPSConstant.HTTP_OK_INT)); helper.updateInboundLogSuccess(inboundLog, respBody, String.valueOf(BBPSConstant.HTTP_OK_INT));
LOGGER.info("[startWebPayment] received HTML response txnId={} length={}", txnId, respBody.length()); LOGGER.info("[startWebPayment] received HTML response txnId={} length={}", txnId, respBody.length());
// Save record with HTML (persist original request and HTML response) // Save record with HTML (persist original request and HTML response)
String responseTypeWeb = BBPSConstant.RESPONSE_TYPE_HTML; String responseTypeWeb = BBPSConstant.RESPONSE_TYPE_HTML;
String rawResponseWeb = respBody == null ? "" : respBody; String rawResponseWeb = respBody == null ? "" : respBody;
@@ -291,8 +301,8 @@ public class PaymentStartServiceImpl implements PaymentStartService {
Map<String, String> parsed = parseHtmlResponse(rawResponseWeb); Map<String, String> parsed = parseHtmlResponse(rawResponseWeb);
// Build a minimal PaymentStartApiResponse so savePaymentStartRecord() can safely read fields // Build a minimal PaymentStartApiResponse so savePaymentStartRecord() can safely read fields
com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse apiResp = new com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse(); PaymentStartApiResponse apiResp = new PaymentStartApiResponse();
com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse.ResponseData rd = new com.billpaymentservice.bbps.dto.response.PaymentStartApiResponse.ResponseData(); PaymentStartApiResponse.ResponseData rd = new PaymentStartApiResponse.ResponseData();
String parsedTxn = parsed.getOrDefault("txnId", ""); String parsedTxn = parsed.getOrDefault("txnId", "");
String parsedAmount = parsed.getOrDefault("amount", "0.00"); String parsedAmount = parsed.getOrDefault("amount", "0.00");
@@ -353,14 +363,8 @@ public class PaymentStartServiceImpl implements PaymentStartService {
LOGGER.info("[{}] Decrypted data for mid={}", apiName, mid); LOGGER.info("[{}] Decrypted data for mid={}", apiName, mid);
// Parse decrypted JSON // Parse decrypted JSON (convert checked Jackson exception into unchecked CustomException so global handler can catch it)
Map<String, Object> payloadMap; Map<String, Object> payloadMap = parseJsonToMap(decrypted, apiName);
try {
payloadMap = objectMapper.readValue(decrypted, new TypeReference<Map<String, Object>>(){});
} catch (JsonProcessingException e) {
LOGGER.error("[{}] Failed to parse decrypted callback JSON: {}", apiName, e.getMessage(), e);
return new ResponseModel(BBPSConstant.MSG_INVALID_DECRYPTED_JSON, String.valueOf(BBPSConstant.STATUS_CODE_207), BBPSConstant.HTTP_OK_INT, null);
}
String txnId = String.valueOf(payloadMap.getOrDefault("txnId", payloadMap.getOrDefault("txn_id", ""))); String txnId = String.valueOf(payloadMap.getOrDefault("txnId", payloadMap.getOrDefault("txn_id", "")));
String orderNo = String.valueOf(payloadMap.getOrDefault("orderNo", payloadMap.getOrDefault("order_no", ""))); String orderNo = String.valueOf(payloadMap.getOrDefault("orderNo", payloadMap.getOrDefault("order_no", "")));
@@ -402,7 +406,7 @@ public class PaymentStartServiceImpl implements PaymentStartService {
record.setSignature(signature); record.setSignature(signature);
// update txn status and callback timestamp // update txn status and callback timestamp
record.setTxnStatus(txnStatus); record.setTxnStatus(txnStatus);
record.setCallbackReceivedOn(java.time.LocalDateTime.now().toString()); record.setCallbackReceivedOn(LocalDateTime.now().toString());
String merchantStatus; String merchantStatus;
if (!signatureMatches) { if (!signatureMatches) {
@@ -437,30 +441,44 @@ public class PaymentStartServiceImpl implements PaymentStartService {
return new ResponseModel(BBPSConstant.MSG_CALLBACK_PROCESSED, merchantStatus, BBPSConstant.HTTP_OK_INT, null); return new ResponseModel(BBPSConstant.MSG_CALLBACK_PROCESSED, merchantStatus, BBPSConstant.HTTP_OK_INT, null);
} }
public Map<String, String> parseHtmlResponse(String html) { public Map<String, String> parseHtmlResponse(String html) {
Map<String, String> map = new HashMap<>(); Map<String, String> map = new HashMap<>();
try { Document doc = Jsoup.parse(html);
Document doc = Jsoup.parse(html);
Element txnId = doc.selectFirst("input[name=txnId]"); Element txnId = doc.selectFirst("input[name=txnId]");
Element amount = doc.selectFirst("input[name=amount]"); Element amount = doc.selectFirst("input[name=amount]");
Element mid = doc.selectFirst("input[name=mid]"); Element mid = doc.selectFirst("input[name=mid]");
if (txnId != null) map.put("txnId", txnId.attr("value")); if (txnId != null) map.put("txnId", txnId.attr("value"));
if (amount != null) map.put("amount", amount.attr("value")); if (amount != null) map.put("amount", amount.attr("value"));
if (mid != null) map.put("mid", mid.attr("value")); if (mid != null) map.put("mid", mid.attr("value"));
LOGGER.debug("[parseHtmlResponse] parsed txnId={} amount={} mid={}", map.get("txnId"), map.get("amount"), map.get("mid")); LOGGER.debug("[parseHtmlResponse] parsed txnId={} amount={} mid={}", map.get("txnId"), map.get("amount"), map.get("mid"));
} catch (Exception e) {
LOGGER.error("[parseHtmlResponse] error parsing HTML: {}", e.getMessage(), e);
}
return map; return map;
} }
// Helper: parse JSON string into Map and rethrow Jackson checked exceptions as CustomException (unchecked)
private Map<String, Object> parseJsonToMap(String json, String apiName) {
if (json == null || json.trim().isEmpty()) {
LOGGER.error("[{}] Decrypted callback JSON is empty or null (length={})", apiName, json == null ? 0 : json.length());
throw new CustomException(BBPSConstant.MSG_INVALID_DECRYPTED_JSON, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, json);
}
// Log a short preview (avoid full sensitive data in logs)
int previewLen = Math.min(120, json.length());
String preview = json.substring(0, previewLen) + (json.length() > previewLen ? "..." : "");
LOGGER.debug("[{}] parsing decrypted JSON preview (len={}): {}", apiName, json.length(), preview);
try {
return objectMapper.readValue(json, new TypeReference<Map<String, Object>>(){});
} catch (Exception e) {
// Jackson throws JsonProcessingException / IOException; catch any parsing error and rethrow as unchecked
LOGGER.error("[{}] Failed to parse decrypted callback JSON (len={}) error={}", apiName, json.length(), e.getMessage(), e);
throw new CustomException(BBPSConstant.MSG_INVALID_DECRYPTED_JSON, BBPSConstant.FAILED_STR, BBPSConstant.HTTP_INTERNAL_SERVER_ERROR, json);
}
}
} }
@@ -23,7 +23,6 @@ public class PollBackServiceImpl implements PollBackService {
this.helper = helper; this.helper = helper;
} }
@Override @Override
public ResponseModel managePollBack(PollBackRequest request) { public ResponseModel managePollBack(PollBackRequest request) {
@@ -109,7 +109,6 @@ public class TxnHistoryServiceImpl implements TxnHistoryService {
String mid = request.getMid(); String mid = request.getMid();
String clientUserId = request.getClientUserId(); String clientUserId = request.getClientUserId();
log.info("[{}] START | reqId={} | mid={} | userId={}", apiName, requestId, mid, clientUserId); log.info("[{}] START | reqId={} | mid={} | userId={}", apiName, requestId, mid, clientUserId);
String txnId = helper.generateTxnId(15); String txnId = helper.generateTxnId(15);
String fullUrl = "this api fetch data from DB not BBPS url !"; String fullUrl = "this api fetch data from DB not BBPS url !";
log.info("[{}] Full URL={}", apiName, fullUrl); log.info("[{}] Full URL={}", apiName, fullUrl);
@@ -147,7 +147,6 @@ public class BBPSConstant extends BBPS_RESPONSE_COD_CONSTANT {
public static final String UAT_PAYMENT_URL_WEB = "https://uat.godemo.in/api/UATIntegration/uat/payin/paymentrequest"; public static final String UAT_PAYMENT_URL_WEB = "https://uat.godemo.in/api/UATIntegration/uat/payin/paymentrequest";
public static final String UAT_PAYMENT_URL_INTENT = "https://uat.godemo.in/api/UATIntegration/paymentrequest/seamless"; public static final String UAT_PAYMENT_URL_INTENT = "https://uat.godemo.in/api/UATIntegration/paymentrequest/seamless";
public static final String RESP_CODE_KEY = "respCode"; // key used by HttpService responses public static final String RESP_CODE_KEY = "respCode"; // key used by HttpService responses
public static final String RESPONSE_TYPE_HTML = "HTML"; public static final String RESPONSE_TYPE_HTML = "HTML";
public static final String RESPONSE_TYPE_QR = "QR"; public static final String RESPONSE_TYPE_QR = "QR";
public static final String RESPONSE_TYPE_JSON = "JSON"; public static final String RESPONSE_TYPE_JSON = "JSON";
@@ -2,6 +2,7 @@ package com.billpaymentservice.bbps.util;
import java.time.LocalDateTime; import java.time.LocalDateTime;
import java.time.OffsetDateTime; import java.time.OffsetDateTime;
import java.time.ZoneId;
import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit; import java.time.temporal.ChronoUnit;
import java.util.ArrayList; import java.util.ArrayList;
@@ -715,9 +716,9 @@ public class BillerMapperHelper {
parent.setActionType(responseWrapper.getActionType()); parent.setActionType(responseWrapper.getActionType());
parent.setUserId(responseWrapper.getUserId()); parent.setUserId(responseWrapper.getUserId());
// Map incoming DTO date strings into Auditable LocalDateTime fields // Map incoming DTO date strings into Auditable LocalDateTime fields
LocalDateTime parentCreatedOn = parseToLocalDateTime(responseWrapper.getCreationDate()); Date parentCreatedOn = parseToDate(responseWrapper.getCreationDate());
LocalDateTime parentUpdatedOn = parseToLocalDateTime(responseWrapper.getModifiedDate()); Date parentUpdatedOn = parseToDate(responseWrapper.getModifiedDate());
parent.setCreatedEpochDate(responseWrapper.getCreatedEpochDate()); parent.setCreatedEpochDate(responseWrapper.getCreatedEpochDate());
parent.setModifiedEpochDate(responseWrapper.getModifiedEpochDate()); parent.setModifiedEpochDate(responseWrapper.getModifiedEpochDate());
parent.setReqId(reqId); parent.setReqId(reqId);
@@ -959,9 +960,9 @@ public class BillerMapperHelper {
// DATES // DATES
// Map incoming DTO date strings into Auditable LocalDateTime fields // Map incoming DTO date strings into Auditable LocalDateTime fields
LocalDateTime bmCreatedOn = parseToLocalDateTime(responseWrapper.getCreationDate()); Date bmCreatedOn = parseToDate(responseWrapper.getCreationDate());
LocalDateTime bmUpdatedOn = parseToLocalDateTime(responseWrapper.getModifiedDate()); Date bmUpdatedOn = parseToDate(responseWrapper.getModifiedDate());
if (bmUpdatedOn != null) { if (bmUpdatedOn != null) {
billMaster.setUpdatedDateTime(bmUpdatedOn); billMaster.setUpdatedDateTime(bmUpdatedOn);
} }
@@ -973,26 +974,17 @@ public class BillerMapperHelper {
// Helper to parse incoming ISO date/time strings (with optional offset) into // Helper to parse incoming ISO date/time strings (with optional offset) into
// LocalDateTime // LocalDateTime
private LocalDateTime parseToLocalDateTime(String dateStr) { private Date parseToDate(String dateStr) {
if (dateStr == null || dateStr.isBlank()) { try {
return null; // example format: 2025-12-17T14:30:45
} LocalDateTime ldt = LocalDateTime.parse(dateStr);
try { return Date.from(ldt.atZone(ZoneId.systemDefault()).toInstant());
// Try parsing with offset first (e.g., 2023-04-19T17:28:05.508679Z or with } catch (Exception e) {
// +05:30) return null;
OffsetDateTime odt = OffsetDateTime.parse(dateStr); }
return odt.toLocalDateTime();
} catch (Exception e) {
try {
// Fallback to LocalDateTime without offset
return LocalDateTime.parse(dateStr, DateTimeFormatter.ISO_LOCAL_DATE_TIME);
} catch (Exception ex) {
LOGGER.warn("Unable to parse date string to LocalDateTime: {}", dateStr);
return null;
}
}
} }
// Convert TransactionMaster to master DTO // Convert TransactionMaster to master DTO
public TransactionHistoryMasterDTO toDTO(TransactionMaster transactionMaster) { public TransactionHistoryMasterDTO toDTO(TransactionMaster transactionMaster) {
if (transactionMaster == null) if (transactionMaster == null)
@@ -1082,38 +1074,76 @@ public class BillerMapperHelper {
// Map PaymentStartUserRequest + API response into PaymentStartRecord and // Map PaymentStartUserRequest + API response into PaymentStartRecord and
// persist // persist
public void savePaymentStartRecord(PaymentStartUserRequest request, PaymentStartApiResponse apiResponse, public PaymentStartRecord savePaymentStartRecord(
String txnId, String orderNo, String requestJson, String rawResponse, String responseType) { PaymentStartUserRequest request,
PaymentStartRecord record = new PaymentStartRecord(); PaymentStartApiResponse apiResponse,
record.setOrderNo(orderNo); String txnId,
record.setRequestId(request.getReqId()); String orderNo,
record.setClientMid(request.getMid()); String requestJson,
record.setClientUserId(request.getClientUserId()); String rawResponse,
record.setInternalTxnId(txnId); String responseType) {
if (apiResponse != null && PaymentStartRecord record;
apiResponse.getResponseData() != null) {
record.setAmount(Optional.ofNullable(apiResponse.getResponseData().getAmount(
)).orElse("0.00"));
record.setTxnId(Optional.ofNullable(apiResponse.getResponseData().getTxnId()) boolean createNewRecord =
.orElse("DUMMY_TXN")); apiResponse == null &&
record.setQrString(Optional.ofNullable(apiResponse.getResponseData(). rawResponse == null &&
getQrString()).orElse("NO_QR")); // parse createdOn string into LocalDateTime and set on Auditable responseType == null;
LocalDateTime createdOn =parseToLocalDateTime(apiResponse.getResponseData().getCreatedOn());
record.setTxnStatus("PENDING"); } else { record.setAmount("0.00"); if (createNewRecord) {
record.setTxnStatus("PENDING"); } // 🔒 Duplicate check
if (paymentStartRecordRepository.findByOrderNo(orderNo).isPresent()) {
throw new CustomException(
"Duplicate OrderNo found",
"DUPLICATE_ORDERNO",
400,
"OrderNo already exists"
);
}
record = new PaymentStartRecord();
} else {
record = paymentStartRecordRepository.findByOrderNo(orderNo)
.orElseThrow(() -> new CustomException(
"OrderNo not found",
"ORDERNO_NOT_FOUND",
404,
"No record exists for given orderNo"
));
}
if (orderNo != null) {
record.setOrderNo(orderNo);
record.setRequestId(request.getReqId());
record.setClientMid(request.getMid());
record.setClientUserId(request.getClientUserId());
record.setInternalTxnId(txnId);
}
if (apiResponse != null && apiResponse.getResponseData() != null) {
record.setAmount(Optional.ofNullable(
apiResponse.getResponseData().getAmount()).orElse("0.00"));
record.setTxnId(Optional.ofNullable(
apiResponse.getResponseData().getTxnId()).orElse("DUMMY_TXN"));
record.setQrString(Optional.ofNullable(
apiResponse.getResponseData().getQrString()).orElse("NO_QR"));
Date createdOn = parseToDate(apiResponse.getResponseData().getCreatedOn());
record.setTxnStatus("PENDING");
} else {
record.setAmount("0.00");
record.setTxnStatus("PENDING");
}
// store the original request body and raw response record.setResponseType(responseType);
record.setRequestBody(requestJson);
record.setResponseBody(rawResponse);
record.setResponseType(responseType);
paymentStartRecordRepository.save(record); return paymentStartRecordRepository.save(record);
} }
// Map ComplaintRequestDTO to forward request for complaint API // Map ComplaintRequestDTO to forward request for complaint API
public FetchComplaintForwardRequest fetchComplaintForwardRequestMap(ComplaintRequestDTO complainRequestDTO) { public FetchComplaintForwardRequest fetchComplaintForwardRequestMap(ComplaintRequestDTO complainRequestDTO) {
FetchComplaintForwardRequest forward = new FetchComplaintForwardRequest(); FetchComplaintForwardRequest forward = new FetchComplaintForwardRequest();
@@ -29,7 +29,6 @@ public class EncryptionUtil {
SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES"); SecretKeySpec secretKeySpec = new SecretKeySpec(keyBytes, "AES");
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec); cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec);
byte[] ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); byte[] ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedData = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8)); byte[] encryptedData = cipher.doFinal(plaintext.getBytes(StandardCharsets.UTF_8));
@@ -18,6 +18,7 @@ import com.billpaymentservice.bbps.entites.BBPSMastersData;
import com.billpaymentservice.bbps.entites.BharatConnectMaster; import com.billpaymentservice.bbps.entites.BharatConnectMaster;
import com.billpaymentservice.bbps.entites.InboundTraceLog; import com.billpaymentservice.bbps.entites.InboundTraceLog;
import com.billpaymentservice.bbps.entites.OutboundTraceLog; import com.billpaymentservice.bbps.entites.OutboundTraceLog;
import com.billpaymentservice.bbps.entites.TransactionMaster;
import com.billpaymentservice.bbps.entites.UserEntity; import com.billpaymentservice.bbps.entites.UserEntity;
import com.billpaymentservice.bbps.exception.CustomException; import com.billpaymentservice.bbps.exception.CustomException;
import com.billpaymentservice.bbps.repository.BBPSMasterDataRepo; import com.billpaymentservice.bbps.repository.BBPSMasterDataRepo;
@@ -225,5 +226,63 @@ public class Helper {
return userId; return userId;
} }
public void mergeNonNullFields(TransactionMaster src, TransactionMaster dest) {
/*
* LOGGER.info("TransactionMaster src: {}", src);
*/ // Only copy values that are non-null (and non-blank for Strings)
if (src.getBankTxnId() != null && !src.getBankTxnId().isBlank()) dest.setBankTxnId(src.getBankTxnId());
if (src.getTxnId() != null && !src.getTxnId().isBlank()) dest.setTxnId(src.getTxnId());
if (src.getInitiatorTxnId() != null && !src.getInitiatorTxnId().isBlank()) dest.setInitiatorTxnId(src.getInitiatorTxnId());
if (src.getCheckerTxnId() != null && !src.getCheckerTxnId().isBlank()) dest.setCheckerTxnId(src.getCheckerTxnId());
if (src.getReqId() != null && !src.getReqId().isBlank()) dest.setReqId(src.getReqId());
if (src.getMid() != null && !src.getMid().isBlank()) dest.setMid(src.getMid());
if (src.getClientUserId() != null && !src.getClientUserId().isBlank()) dest.setClientUserId(src.getClientUserId());
if (src.getClientReqId() != null && !src.getClientReqId().isBlank()) dest.setClientReqId(src.getClientReqId());
if (src.getUserId() != null && !src.getUserId().isBlank()) dest.setUserId(src.getUserId());
if (src.getBillerId() != null && !src.getBillerId().isBlank()) dest.setBillerId(src.getBillerId());
if (src.getTxnAmount() != null) dest.setTxnAmount(src.getTxnAmount());
if (src.getStatus() != null && !src.getStatus().isBlank()) dest.setStatus(src.getStatus());
if (src.getPaymentInformation() != null && !src.getPaymentInformation().isBlank()) dest.setPaymentInformation(src.getPaymentInformation());
if (src.getCreationDateBank() != null) dest.setCreationDateBank(src.getCreationDateBank());
if (src.getModifiedDateBank() != null) dest.setModifiedDateBank(src.getModifiedDateBank());
if (src.getCreatedEpochDateBank() != null) dest.setCreatedEpochDateBank(src.getCreatedEpochDateBank());
if (src.getModifiedEpochDateBank() != null) dest.setModifiedEpochDateBank(src.getModifiedEpochDateBank());
/* ---- additional fields ---- */
if (src.getRefId() != null && !src.getRefId().isBlank()) dest.setRefId(src.getRefId());
if (src.getPaymentRefId() != null && !src.getPaymentRefId().isBlank()) dest.setPaymentRefId(src.getPaymentRefId());
if (src.getRemitterName() != null && !src.getRemitterName().isBlank()) dest.setRemitterName(src.getRemitterName());
if (src.getOrderId() != null && !src.getOrderId().isBlank()) dest.setOrderId(src.getOrderId());
if (src.getBillId() != null && !src.getBillId().isBlank()) dest.setBillId(src.getBillId());
if (src.getUserType() != null && !src.getUserType().isBlank()) dest.setUserType(src.getUserType());
if (src.getTotalTxnAmount() != null) dest.setTotalTxnAmount(src.getTotalTxnAmount());
if (src.getBillerCategory() != null && !src.getBillerCategory().isBlank()) dest.setBillerCategory(src.getBillerCategory());
if (src.getBillerName() != null && !src.getBillerName().isBlank()) dest.setBillerName(src.getBillerName());
if (src.getOffUsPay() != null) dest.setOffUsPay(src.getOffUsPay());
if (src.getSplitPay() != null) dest.setSplitPay(src.getSplitPay());
if (src.getQuickPay() != null) dest.setQuickPay(src.getQuickPay());
if (src.getPaymentMode() != null && !src.getPaymentMode().isBlank()) dest.setPaymentMode(src.getPaymentMode());
if (src.getAgentPaymentInformation() != null && !src.getAgentPaymentInformation().isBlank()) dest.setAgentPaymentInformation(src.getAgentPaymentInformation());
if (src.getCustConvFee() != null) dest.setCustConvFee(src.getCustConvFee());
if (src.getCurrency() != null && !src.getCurrency().isBlank()) dest.setCurrency(src.getCurrency());
if (src.getRequestedIp() != null && !src.getRequestedIp().isBlank()) dest.setRequestedIp(src.getRequestedIp());
if (src.getCustomerMobNo() != null && !src.getCustomerMobNo().isBlank()) dest.setCustomerMobNo(src.getCustomerMobNo());
if (src.getMacAddress() != null && !src.getMacAddress().isBlank()) dest.setMacAddress(src.getMacAddress());
if (src.getSplitPayAmount() != null) dest.setSplitPayAmount(src.getSplitPayAmount());
if (src.getCouCustConvFee() != null) dest.setCouCustConvFee(src.getCouCustConvFee());
if (src.getSettlementVerification() != null) dest.setSettlementVerification(src.getSettlementVerification());
if (src.getFundTxnType() != null && !src.getFundTxnType().isBlank()) dest.setFundTxnType(src.getFundTxnType());
if (src.getReason() != null && !src.getReason().isBlank()) dest.setReason(src.getReason());
if (src.getAmountTags() != null && !src.getAmountTags().isBlank()) dest.setAmountTags(src.getAmountTags());
if (src.getPlanId() != null && !src.getPlanId().isBlank()) dest.setPlanId(src.getPlanId());
if (src.getPlanType() != null && !src.getPlanType().isBlank()) dest.setPlanType(src.getPlanType());
if (src.getTxnNpciId() != null && !src.getTxnNpciId().isBlank()) dest.setTxnNpciId(src.getTxnNpciId());
/* ---- lists (direct replace when provided) ---- */
if (src.getDeviceBlockTags() != null && !src.getDeviceBlockTags().isEmpty()) dest.setDeviceBlockTags(src.getDeviceBlockTags());
if (src.getCustomerParams() != null && !src.getCustomerParams().isEmpty()) dest.setCustomerParams(src.getCustomerParams());
LOGGER.info("TransactionMaster dest after merge: {}", dest);
}
} }
+1 -1
View File
@@ -76,7 +76,7 @@ jwt.secret=6C756148487475787A414646736D4B6E444E585A766145396E6A4555594A595566657
# Resilience4j Rate Limiter # Resilience4j Rate Limiter
# =============================== # ===============================
resilience4j.ratelimiter.configs.default.limit-for-period=5 resilience4j.ratelimiter.configs.default.limit-for-period=1000
resilience4j.ratelimiter.configs.default.limit-refresh-period=1m resilience4j.ratelimiter.configs.default.limit-refresh-period=1m
resilience4j.ratelimiter.configs.default.timeout-duration=0 resilience4j.ratelimiter.configs.default.timeout-duration=0
# Resilience4j Retry # Resilience4j Retry