Files
bbps-backend/src/main/java/com/billpaymentservice/bbps/scheduler/PaymentRetryScheduler.java
T
2025-12-17 15:30:33 +05:30

162 lines
7.8 KiB
Java

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);
}
}
}
}