transaction master and payment start record new entity with update
This commit is contained in:
@@ -30,6 +30,7 @@ public class BillMaster extends Auditable {
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
private Long id;
|
||||
|
||||
@Column(name = "bill_id", unique = true)
|
||||
private String billId; // primary key
|
||||
private String reqId; // client se aaya hua
|
||||
|
||||
@@ -35,11 +35,11 @@ public class PaymentStartRecord extends Auditable {
|
||||
@Column(name = "mid", nullable = false)
|
||||
private String mid;
|
||||
|
||||
@Column(name = "client_user_id")
|
||||
private String clientUserId;
|
||||
@Column(name = "user_id")
|
||||
private String userId;
|
||||
|
||||
@Column(name = "internal_txn_id", unique = true, nullable = false)
|
||||
private String internalTxnId;
|
||||
@Column(name = "txn_id", unique = true, nullable = false)
|
||||
private String txnId;
|
||||
|
||||
@Column(name = "order_no", unique = true, nullable = false)
|
||||
private String orderNo;
|
||||
@@ -47,8 +47,8 @@ public class PaymentStartRecord extends Auditable {
|
||||
@Column(name = "amount", nullable = false)
|
||||
private String amount;
|
||||
|
||||
@Column(name = "txn_id",unique = true)
|
||||
private String txnId;
|
||||
@Column(name = "payment_ref_id",unique = true)
|
||||
private String paymentRefId;
|
||||
|
||||
@Column(name = "qr_string", columnDefinition = "TEXT")
|
||||
private String qrString;
|
||||
|
||||
@@ -30,6 +30,7 @@ import lombok.ToString;
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class TransactionMaster extends Auditable {
|
||||
|
||||
@Id
|
||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||
@Column(name = "id")
|
||||
@@ -48,8 +49,8 @@ public class TransactionMaster extends Auditable {
|
||||
@Column(name = "checker_txn_id")
|
||||
private String checkerTxnId;
|
||||
|
||||
@Column(name = "client_user_id")
|
||||
private String clientUserId;
|
||||
@Column(name = "user_id")
|
||||
private String userId;
|
||||
|
||||
@Column(name = "mid")
|
||||
private String mid;
|
||||
@@ -78,8 +79,8 @@ public class TransactionMaster extends Auditable {
|
||||
@Column(name = "user_type")
|
||||
private String userType;
|
||||
|
||||
@Column(name = "user_id")
|
||||
private String userId;
|
||||
@Column(name = "bank_user_id")
|
||||
private String bankUserId;
|
||||
|
||||
@Column(name = "txn_amount")
|
||||
private String txnAmount;
|
||||
@@ -163,7 +164,7 @@ public class TransactionMaster extends Auditable {
|
||||
|
||||
// ✅ Relation with PaymentStartRecord (one-to-one)
|
||||
@OneToOne(fetch = FetchType.LAZY)
|
||||
@JoinColumn(name = "payment_ref_id", referencedColumnName = "txn_id", insertable = false, updatable = false)
|
||||
@JoinColumn(name = "payment_ref_id", referencedColumnName = "payment_ref_id", insertable = false, updatable = false)
|
||||
@JsonIgnore
|
||||
private PaymentStartRecord paymentStartRecord;
|
||||
|
||||
|
||||
+7
-4
@@ -21,10 +21,13 @@ public interface TransactionMasterRepository extends JpaRepository<TransactionMa
|
||||
@Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid")
|
||||
Page<TransactionMaster> findByMid(@Param("mid") String mid, Pageable pageable);
|
||||
|
||||
@Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid AND t.clientUserId = :clientUserId")
|
||||
Page<TransactionMaster> findByMidOrClientUserId(@Param("mid") String mid,
|
||||
@Param("clientUserId") String clientUserId, Pageable pageable);
|
||||
|
||||
@Query("SELECT t FROM TransactionMaster t WHERE t.mid = :mid AND t.userId = :userId")
|
||||
Page<TransactionMaster> findByMidAndUserId(
|
||||
@Param("mid") String mid,
|
||||
@Param("userId") String userId,
|
||||
Pageable pageable
|
||||
);
|
||||
|
||||
@Query("SELECT t FROM TransactionMaster t WHERE t.txnId = :txnId")
|
||||
List<TransactionMaster> findByTxnId(@Param("txnId") String txnId);
|
||||
|
||||
|
||||
@@ -1,162 +0,0 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -117,9 +117,8 @@ public class PaymentServiceImpl implements PaymentService {
|
||||
preTransaction.setInitiatorTxnId(txnId);
|
||||
preTransaction.setReqId(requestId);
|
||||
preTransaction.setMid(billPaymentRequest.getMid());
|
||||
preTransaction.setClientUserId(billPaymentRequest.getClientUserId());
|
||||
preTransaction.setUserId(billPaymentRequest.getClientUserId());
|
||||
preTransaction.setClientReqId(billPaymentRequest.getClientReqId());
|
||||
preTransaction.setUserId(userId);
|
||||
preTransaction.setStatus("PENDING");
|
||||
EntityRelationshipUtil.linkTransactionMasterRelationships(preTransaction);
|
||||
// ensure txnId exists (no uniqueness suffixing)
|
||||
|
||||
@@ -67,7 +67,7 @@ public class TxnHistoryServiceImpl implements TxnHistoryService {
|
||||
} else {
|
||||
Pageable pageable = PageRequest.of(paymentHistoryRequest.getPageNo(),
|
||||
paymentHistoryRequest.getPageSize());
|
||||
dataResult = transactionMasterRepository.findByMidOrClientUserId(mid, clientUserId, pageable);
|
||||
dataResult = transactionMasterRepository.findByMidAndUserId(mid, clientUserId, pageable);
|
||||
|
||||
}
|
||||
// List<TransactionMaster> dataResult =
|
||||
|
||||
@@ -1034,15 +1034,15 @@ public class BillerMapperHelper {
|
||||
record.setOrderNo(orderNo);
|
||||
record.setRequestId(request.getReqId());
|
||||
record.setMid(request.getMid());
|
||||
record.setClientUserId(request.getClientUserId());
|
||||
record.setInternalTxnId(txnId);
|
||||
record.setUserId(request.getClientUserId());
|
||||
record.setTxnId(txnId);
|
||||
}
|
||||
|
||||
if (apiResponse != null && apiResponse.getResponseData() != null) {
|
||||
record.setAmount(Optional.ofNullable(
|
||||
apiResponse.getResponseData().getAmount()).orElse("0.00"));
|
||||
|
||||
record.setTxnId(Optional.ofNullable(
|
||||
record.setPaymentRefId(Optional.ofNullable(
|
||||
apiResponse.getResponseData().getTxnId()).orElse("DUMMY_TXN"));
|
||||
|
||||
Date createdOn = parseToDate(apiResponse.getResponseData().getCreatedOn());
|
||||
|
||||
@@ -238,7 +238,7 @@ public class Helper {
|
||||
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.getBankUserId()!= null && !src.getBankUserId().isBlank()) dest.setBankUserId(src.getBankUserId());
|
||||
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());
|
||||
|
||||
Reference in New Issue
Block a user