diff --git a/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/MessageCodec.java b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/MessageCodec.java new file mode 100644 index 0000000..1b9f1f4 --- /dev/null +++ b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/MessageCodec.java @@ -0,0 +1,105 @@ +package com.klaro.acquirecore.acquiring.codec; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * 전문 언팩/팩 커덱 (codec) + * 바이너리 전문 파싱 및 조립 담당 + */ +@Component +public class MessageCodec { + + private static final Logger log = LoggerFactory.getLogger(MessageCodec.class); + + // 전문 헤더 길이 + private static final int HEADER_LENGTH = 20; + // 전문 트레일러 길이 + private static final int TRAILER_LENGTH = 4; + + /** + * 원시 전문 언팩 + * @param rawMessage 원시 바이트 배열 + * @return 파싱된 전문 객체 + */ + public ParsedMessage unpack(byte[] rawMessage) { + log.debug("전문 언팩 시작 - 길이: {}", rawMessage.length); + + if (rawMessage == null || rawMessage.length < HEADER_LENGTH + TRAILER_LENGTH) { + throw new IllegalArgumentException("전문 길이 오류"); + } + + ParsedMessage parsed = new ParsedMessage(); + + // 헤더 파싱 + parsed.setMessageType(decodeString(rawMessage, 0, 4)); + parsed.setTxId(decodeString(rawMessage, 4, 12)); + parsed.setTxCode(decodeString(rawMessage, 16, 4)); + + // 바디 파싱 (가변 길이) + int bodyLength = rawMessage.length - HEADER_LENGTH - TRAILER_LENGTH; + if (bodyLength > 0) { + parsed.setMerchantId(decodeString(rawMessage, HEADER_LENGTH, 15)); + parsed.setCardNumber(decodeString(rawMessage, HEADER_LENGTH + 15, 20)); + parsed.setAmount(decodeString(rawMessage, HEADER_LENGTH + 35, 12)); + parsed.setApprovalCode(decodeString(rawMessage, HEADER_LENGTH + 47, 10)); + } + + // 트레일러 검증 + byte[] trailer = new byte[TRAILER_LENGTH]; + System.arraycopy(rawMessage, rawMessage.length - TRAILER_LENGTH, trailer, 0, TRAILER_LENGTH); + parsed.setTrailer(new String(trailer)); + + log.debug("전문 언팩 완료 - TX_ID: {}, TX_CODE: {}", parsed.getTxId(), parsed.getTxCode()); + return parsed; + } + + /** + * 전문 팩 (응답 전문 생성) + * @param parsed 파싱된 전문 + * @return 원시 바이트 배열 + */ + public byte[] pack(ParsedMessage parsed) { + log.debug("전문 팩 시작 - TX_ID: {}", parsed.getTxId()); + + StringBuilder sb = new StringBuilder(); + sb.append(padRight(parsed.getMessageType(), 4)); + sb.append(padRight(parsed.getTxId(), 12)); + sb.append(padRight(parsed.getTxCode(), 4)); + sb.append(padRight(parsed.getMerchantId(), 15)); + sb.append(padRight(parsed.getCardNumber(), 20)); + sb.append(padRight(parsed.getAmount(), 12)); + sb.append(padRight(parsed.getApprovalCode(), 10)); + sb.append("ETX"); + + return sb.toString().getBytes(); + } + + /** + * 바이트 배열에서 문자열 추출 + */ + private String decodeString(byte[] data, int offset, int length) { + if (offset + length > data.length) { + return ""; + } + byte[] sub = new byte[length]; + System.arraycopy(data, offset, sub, 0, length); + return new String(sub).trim(); + } + + /** + * 문자열을 지정 길이만큼 패딩 + */ + private String padRight(String value, int length) { + if (value == null) value = ""; + if (value.length() >= length) { + return value.substring(0, length); + } + StringBuilder sb = new StringBuilder(value); + while (sb.length() < length) { + sb.append(' '); + } + return sb.toString(); + } +}