[매입] mg_recv_svc → 전문수신 @Service #8
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