[매입] mg_recv_svc → 전문수신 @Service (ACM-MG-004)

This commit is contained in:
forge-bot 2026-07-18 11:17:45 +00:00
parent dec970dbe9
commit f883ab754d

View file

@ -0,0 +1,158 @@
package com.klaro.acquirecore.acquiring.service;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.klaro.acquirecore.acquiring.dto.TxMessage;
import org.springframework.stereotype.Component;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.UUID;
/**
* Codec for TX message packing/unpacking.
* Handles binary protocol domain object conversion.
*/
@Component
public class TxMessageCodec {
private static final DateTimeFormatter DT_FORMAT = DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
private static final String ENCODING = "EUC-KR";
private final ObjectMapper objectMapper;
public TxMessageCodec(ObjectMapper objectMapper) {
this.objectMapper = objectMapper;
}
/**
* Unpack binary message to TxMessage domain object.
* Format: HEADER(20) + BODY(n)
*/
public TxMessage unpack(byte[] raw) {
if (raw == null || raw.length < 20) {
throw new IllegalArgumentException("Invalid message: too short");
}
ByteBuffer buf = ByteBuffer.wrap(raw);
// HEADER: 20 bytes
byte[] headerBytes = new byte[20];
buf.get(headerBytes);
String header = new String(headerBytes, StandardCharsets.ISO_8859_1).trim();
// BODY: remaining bytes
byte[] bodyBytes = new byte[raw.length - 20];
buf.get(bodyBytes);
TxMessage msg = new TxMessage();
msg.setHeader(header);
msg.setRawBody(bodyBytes);
// Parse body as JSON or delimited fields
parseBody(msg, bodyBytes);
return msg;
}
private void parseBody(TxMessage msg, byte[] body) {
String bodyStr = new String(body, StandardCharsets.ISO_8859_1);
try {
JsonNode node = objectMapper.readTree(bodyStr);
if (node.isObject()) {
ObjectNode obj = (ObjectNode) node;
obj.fields().forEachRemaining(e -> {
String key = e.getKey();
JsonNode val = e.getValue();
if (val.isTextual()) {
setField(msg, key, val.asText());
} else if (val.isNumber()) {
setNumericField(msg, key, val.asLong());
}
});
}
} catch (Exception e) {
// Fallback: delimited parse
parseDelimited(msg, bodyStr);
}
}
private void parseDelimited(TxMessage msg, String body) {
String[] fields = body.split("\\|");
for (int i = 0; i < fields.length; i++) {
String[] kv = fields[i].split("=", 2);
if (kv.length == 2) {
setField(msg, kv[0].trim(), kv[1].trim());
}
}
}
private void setField(TxMessage msg, String key, String value) {
switch (key.toUpperCase()) {
case "TXID": msg.setTxId(value); break;
case "MERCHANTID": msg.setMerchantId(value); break;
case "TERMINALID": msg.setTerminalId(value); break;
case "AMOUNT": msg.setAmount(value); break;
case "STATUS": msg.setStatus(value); break;
case "TRANDATE": msg.setTranDate(value); break;
case "TRANTIME": msg.setTranTime(value); break;
case "AUTHNO": msg.setAuthNo(value); break;
case "CARDNO": msg.setCardNo(value); break;
case "VANMSG": msg.setVanMsg(value); break;
default: msg.setExtra(key, value);
}
}
private void setNumericField(TxMessage msg, String key, long value) {
if ("AMOUNT".equalsIgnoreCase(key)) {
msg.setAmount(String.valueOf(value));
}
}
/**
* Pack TxMessage to binary format.
*/
public byte[] pack(TxMessage msg) {
byte[] body = packBody(msg);
byte[] header = buildHeader(msg, body.length);
ByteBuffer buf = ByteBuffer.allocate(header.length + body.length);
buf.put(header);
buf.put(body);
return buf.array();
}
private byte[] buildHeader(TxMessage msg, int bodyLen) {
String header = String.format("%-20s",
msg.getTxId() != null ? msg.getTxId() : UUID.randomUUID().toString().substring(0, 8));
return header.getBytes(StandardCharsets.ISO_8859_1);
}
private byte[] packBody(TxMessage msg) {
try {
ObjectNode node = objectMapper.createObjectNode();
if (msg.getTxId() != null) node.put("TXID", msg.getTxId());
if (msg.getMerchantId() != null) node.put("MERCHANTID", msg.getMerchantId());
if (msg.getTerminalId() != null) node.put("TERMINALID", msg.getTerminalId());
if (msg.getAmount() != null) node.put("AMOUNT", msg.getAmount());
if (msg.getStatus() != null) node.put("STATUS", msg.getStatus());
if (msg.getTranDate() != null) node.put("TRANDATE", msg.getTranDate());
if (msg.getTranTime() != null) node.put("TRANTIME", msg.getTranTime());
if (msg.getAuthNo() != null) node.put("AUTHNO", msg.getAuthNo());
if (msg.getCardNo() != null) node.put("CARDNO", maskCardNo(msg.getCardNo()));
if (msg.getVanMsg() != null) node.put("VANMSG", msg.getVanMsg());
msg.getExtra().forEach(node::put);
return objectMapper.writeValueAsString(node).getBytes(StandardCharsets.ISO_8859_1);
} catch (Exception e) {
throw new RuntimeException("Failed to pack message body", e);
}
}
private String maskCardNo(String cardNo) {
if (cardNo == null || cardNo.length() < 10) return cardNo;
return cardNo.substring(0, 6) + "****" + cardNo.substring(cardNo.length() - 4);
}
}