[매입] mg_recv_svc → 전문수신 @Service (ACM-MG-004)
This commit is contained in:
parent
dec970dbe9
commit
f883ab754d
1 changed files with 158 additions and 0 deletions
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in a new issue