diff --git a/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/service/TxMessageReceiveService.java b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/service/TxMessageReceiveService.java new file mode 100644 index 0000000..03e38ee --- /dev/null +++ b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/service/TxMessageReceiveService.java @@ -0,0 +1,191 @@ +package com.klaro.acquirecore.acquiring.service; + +import com.klaro.acquirecore.acquiring.dto.TxMessage; +import com.klaro.acquirecore.acquiring.dto.TxResult; +import com.klaro.acquirecore.acquiring.repository.TxRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.UUID; + +/** + * TX_SERVICE: Main service for receiving and processing acquiring messages. + * Migrated from legacy/app/online/mg_recv_svc.pgc + */ +@Service +public class TxMessageReceiveService { + + private static final Logger log = LoggerFactory.getLogger(TxMessageReceiveService.class); + private static final DateTimeFormatter DT_FMT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + + private final TxMessageCodec codec; + private final TxRepository txRepository; + + public TxMessageReceiveService(TxMessageCodec codec, TxRepository txRepository) { + this.codec = codec; + this.txRepository = txRepository; + } + + /** + * Receive and process raw binary message. + * @param raw raw message bytes + * @return processing result + */ + public TxResult receive(byte[] raw) { + String traceId = UUID.randomUUID().toString().substring(0, 8); + log.info("[{}] Receiving message, size={}", traceId, raw != null ? raw.length : 0); + + try { + // 1. Unpack message + TxMessage msg = codec.unpack(raw); + log.debug("[{}] Unpacked: {}", traceId, msg); + + // 2. Validate required fields + TxResult validation = validate(msg, traceId); + if (validation != null) { + return validation; + } + + // 3. Process transaction + return processTransaction(msg, traceId); + + } catch (Exception e) { + log.error("[{}] Processing error: {}", traceId, e.getMessage(), e); + return TxResult.error(traceId, "9999", "Processing error: " + e.getMessage()); + } + } + + /** + * Validate message fields. + */ + private TxResult validate(TxMessage msg, String traceId) { + if (msg.getMerchantId() == null || msg.getMerchantId().isBlank()) { + log.warn("[{}] Missing merchantId", traceId); + return TxResult.error(traceId, "1001", "Missing merchantId"); + } + if (msg.getTerminalId() == null || msg.getTerminalId().isBlank()) { + log.warn("[{}] Missing terminalId", traceId); + return TxResult.error(traceId, "1002", "Missing terminalId"); + } + if (msg.getAmount() == null || msg.getAmount().isBlank()) { + log.warn("[{}] Missing amount", traceId); + return TxResult.error(traceId, "1003", "Missing amount"); + } + // Validate amount is numeric + try { + Long.parseLong(msg.getAmount()); + } catch (NumberFormatException e) { + log.warn("[{}] Invalid amount format: {}", traceId, msg.getAmount()); + return TxResult.error(traceId, "1004", "Invalid amount format"); + } + return null; + } + + /** + * Process the transaction. + */ + private TxResult processTransaction(TxMessage msg, String traceId) { + try { + // 1. Check merchant status via repository stub + if (!txRepository.isMerchantActive(msg.getMerchantId())) { + log.warn("[{}] Merchant not active: {}", traceId, msg.getMerchantId()); + return TxResult.error(traceId, "2001", "Merchant not active"); + } + + // 2. Check terminal status via repository stub + if (!txRepository.isTerminalActive(msg.getMerchantId(), msg.getTerminalId())) { + log.warn("[{}] Terminal not active: {}/{}", traceId, msg.getMerchantId(), msg.getTerminalId()); + return TxResult.error(traceId, "2002", "Terminal not active"); + } + + // 3. Generate transaction ID if not present + if (msg.getTxId() == null || msg.getTxId().isBlank()) { + msg.setTxId(generateTxId()); + } + + // 4. Set transaction timestamp + String now = LocalDateTime.now().format(DT_FMT); + msg.setTranDate(now.substring(0, 8)); + msg.setTranTime(now.substring(8)); + + // 5. Save transaction via repository stub + txRepository.saveTransaction(msg); + + // 6. Process based on status + String status = msg.getStatus() != null ? msg.getStatus().toUpperCase() : "REQ"; + switch (status) { + case "REQ": + case "APPROVE": + return handleApproval(msg, traceId); + case "CANCEL": + case "REFUND": + return handleCancellation(msg, traceId); + default: + log.info("[{}] Unknown status, defaulting to approval", traceId); + return handleApproval(msg, traceId); + } + + } catch (Exception e) { + log.error("[{}] Transaction processing error: {}", traceId, e.getMessage(), e); + return TxResult.error(traceId, "9999", "Transaction processing failed"); + } + } + + /** + * Handle approval transaction. + */ + private TxResult handleApproval(TxMessage msg, String traceId) { + log.info("[{}] Processing approval for merchant={}, amount={}", + traceId, msg.getMerchantId(), msg.getAmount()); + + // Generate auth number + String authNo = generateAuthNo(); + msg.setAuthNo(authNo); + msg.setStatus("APPROVED"); + + // Update via repository + txRepository.updateTransactionStatus(msg.getTxId(), "APPROVED"); + + log.info("[{}] Approval complete, authNo={}", traceId, authNo); + return TxResult.ok(msg.getTxId(), "Approval complete"); + } + + /** + * Handle cancellation/refund transaction. + */ + private TxResult handleCancellation(TxMessage msg, String traceId) { + log.info("[{}] Processing cancellation for txId={}", traceId, msg.getTxId()); + + // Check if original transaction exists + if (!txRepository.transactionExists(msg.getTxId())) { + log.warn("[{}] Original transaction not found: {}", traceId, msg.getTxId()); + return TxResult.error(traceId, "3001", "Original transaction not found"); + } + + // Check if already cancelled + String currentStatus = txRepository.getTransactionStatus(msg.getTxId()); + if ("CANCELLED".equals(currentStatus)) { + log.warn("[{}] Already cancelled: {}", traceId, msg.getTxId()); + return TxResult.error(traceId, "3002", "Already cancelled"); + } + + // Process cancellation + msg.setStatus("CANCELLED"); + txRepository.updateTransactionStatus(msg.getTxId(), "CANCELLED"); + + log.info("[{}] Cancellation complete", traceId); + return TxResult.ok(msg.getTxId(), "Cancellation complete"); + } + + private String generateTxId() { + return "TX" + LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyyMMddHHmmss")) + + String.format("%04d", (int)(Math.random() * 10000)); + } + + private String generateAuthNo() { + return String.format("%06d", (int)(Math.random() * 1000000)); + } +}