From 78788230f945673b996d66c6e75e78c8c7c19ef5 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:09:35 +0000 Subject: [PATCH 1/9] forge: open work branch for ACM-MG-004-attempt-2-run-be8e7d8241de --- .forge/ACM-MG-004-attempt-2-run-be8e7d8241de.md | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .forge/ACM-MG-004-attempt-2-run-be8e7d8241de.md diff --git a/.forge/ACM-MG-004-attempt-2-run-be8e7d8241de.md b/.forge/ACM-MG-004-attempt-2-run-be8e7d8241de.md new file mode 100644 index 0000000..fb28116 --- /dev/null +++ b/.forge/ACM-MG-004-attempt-2-run-be8e7d8241de.md @@ -0,0 +1,3 @@ +# ACM-MG-004-attempt-2-run-be8e7d8241de + +Forge 이슈 작업 브랜치 `forge/ACM-MG-004-attempt-2-run-be8e7d8241de`. -- 2.49.1 From 1dd183a6db2311f8f7da6265c7d91580a2113e09 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:10:30 +0000 Subject: [PATCH 2/9] =?UTF-8?q?[=EB=A7=A4=EC=9E=85]=20mg=5Frecv=5Fsvc=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=84=EB=AC=B8=EC=88=98=EC=8B=A0=20@Service=20(?= =?UTF-8?q?ACM-MG-004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquiring/service/TxService.java | 88 +++++++++++++++++++ 1 file changed, 88 insertions(+) create mode 100644 boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/service/TxService.java diff --git a/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/service/TxService.java b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/service/TxService.java new file mode 100644 index 0000000..5a5011f --- /dev/null +++ b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/service/TxService.java @@ -0,0 +1,88 @@ +package com.klaro.acquirecore.acquiring.service; + +import com.klaro.acquirecore.acquiring.codec.MessageCodec; +import com.klaro.acquirecore.acquiring.codec.UnpackedMessage; +import com.klaro.acquirecore.acquiring.dto.TxRequest; +import com.klaro.acquirecore.acquiring.dto.TxResponse; +import com.klaro.acquirecore.acquiring.repository.TxRepository; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Service; + +/** + * TX_SERVICE: 전문수신 서비스 + * Legacy: mg_recv_svc.pgc + * Handles incoming acquiring transaction messages, unpacks, processes, and stores. + */ +@Service +public class TxService { + + private static final Logger log = LoggerFactory.getLogger(TxService.class); + + private final MessageCodec messageCodec; + private final TxRepository txRepository; + + public TxService(MessageCodec messageCodec, TxRepository txRepository) { + this.messageCodec = messageCodec; + this.txRepository = txRepository; + } + + /** + * 수신된 전문을 처리합니다. + * + * @param rawMessage 원본 바이트 전문 + * @return 처리 결과 응답 + */ + public TxResponse processMessage(byte[] rawMessage) { + log.info("Processing incoming message, length={}", rawMessage.length); + + try { + // 1. 전문 언팩 (디코딩) + UnpackedMessage unpacked = messageCodec.unpack(rawMessage); + log.debug("Message unpacked: txType={}, traceNo={}", + unpacked.getTxType(), unpacked.getTraceNo()); + + // 2. TX 요청 변환 + TxRequest request = toTxRequest(unpacked); + + // 3. 거래 처리 (DB 저장) + txRepository.save(request); + + // 4. 응답 생성 + return buildResponse(unpacked, "00", "Approved"); + + } catch (Exception e) { + log.error("Message processing failed", e); + return buildErrorResponse(e.getMessage()); + } + } + + private TxRequest toTxRequest(UnpackedMessage unpacked) { + TxRequest request = new TxRequest(); + request.setTraceNo(unpacked.getTraceNo()); + request.setTxType(unpacked.getTxType()); + request.setMerchantId(unpacked.getMerchantId()); + request.setAmount(unpacked.getAmount()); + request.setCardNo(unpacked.getCardNo()); + request.setExpiryDate(unpacked.getExpiryDate()); + request.setInstallments(unpacked.getInstallments()); + request.setMessageRaw(unpacked.getRawData()); + return request; + } + + private TxResponse buildResponse(UnpackedMessage unpacked, String respCode, String respMsg) { + TxResponse response = new TxResponse(); + response.setTraceNo(unpacked.getTraceNo()); + response.setRespCode(respCode); + response.setRespMsg(respMsg); + response.setTxType(unpacked.getTxType()); + return response; + } + + private TxResponse buildErrorResponse(String errorMessage) { + TxResponse response = new TxResponse(); + response.setRespCode("99"); + response.setRespMsg(errorMessage != null ? errorMessage : "Unknown error"); + return response; + } +} -- 2.49.1 From 3257739c883a621db4164a034be99d8a57931f03 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:10:31 +0000 Subject: [PATCH 3/9] =?UTF-8?q?[=EB=A7=A4=EC=9E=85]=20mg=5Frecv=5Fsvc=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=84=EB=AC=B8=EC=88=98=EC=8B=A0=20@Service=20(?= =?UTF-8?q?ACM-MG-004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquiring/codec/MessageCodec.java | 91 +++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/MessageCodec.java 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..8c94db8 --- /dev/null +++ b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/MessageCodec.java @@ -0,0 +1,91 @@ +package com.klaro.acquirecore.acquiring.codec; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.stereotype.Component; + +/** + * 전문 인코딩/디코딩 컴포넌트 + * Fixed-length 필드 기반 전문 파싱 + */ +@Component +public class MessageCodec { + + private static final Logger log = LoggerFactory.getLogger(MessageCodec.class); + + private static final int LEN_TX_TYPE = 4; + private static final int LEN_TRACE_NO = 12; + private static final int LEN_MERCHANT_ID = 15; + private static final int LEN_AMOUNT = 12; + private static final int LEN_CARD_NO = 20; + private static final int LEN_EXPIRY = 4; + private static final int LEN_INSTALLMENTS = 2; + private static final int LEN_FILLER = 99; + + public UnpackedMessage unpack(byte[] rawMessage) { + if (rawMessage == null || rawMessage.length == 0) { + throw new IllegalArgumentException("Empty message"); + } + + String data = new String(rawMessage); + int offset = 0; + + UnpackedMessage unpacked = new UnpackedMessage(); + unpacked.setRawData(data); + + unpacked.setTxType(extract(data, offset, LEN_TX_TYPE)); + offset += LEN_TX_TYPE; + + unpacked.setTraceNo(extract(data, offset, LEN_TRACE_NO)); + offset += LEN_TRACE_NO; + + unpacked.setMerchantId(extract(data, offset, LEN_MERCHANT_ID)); + offset += LEN_MERCHANT_ID; + + unpacked.setAmount(extract(data, offset, LEN_AMOUNT)); + offset += LEN_AMOUNT; + + unpacked.setCardNo(extract(data, offset, LEN_CARD_NO)); + offset += LEN_CARD_NO; + + unpacked.setExpiryDate(extract(data, offset, LEN_EXPIRY)); + offset += LEN_EXPIRY; + + unpacked.setInstallments(extract(data, offset, LEN_INSTALLMENTS)); + offset += LEN_INSTALLMENTS; + + if (data.length() > offset) { + unpacked.setFiller(extract(data, offset, Math.min(LEN_FILLER, data.length() - offset))); + } + + log.debug("Message unpacked: txType={}, traceNo={}", unpacked.getTxType(), unpacked.getTraceNo()); + return unpacked; + } + + public byte[] pack(UnpackedMessage unpacked) { + StringBuilder sb = new StringBuilder(); + sb.append(padRight(unpacked.getTxType(), LEN_TX_TYPE)); + sb.append(padRight(unpacked.getTraceNo(), LEN_TRACE_NO)); + sb.append(padRight(unpacked.getMerchantId(), LEN_MERCHANT_ID)); + sb.append(padRight(unpacked.getAmount(), LEN_AMOUNT)); + sb.append(padRight(unpacked.getCardNo(), LEN_CARD_NO)); + sb.append(padRight(unpacked.getExpiryDate(), LEN_EXPIRY)); + sb.append(padRight(unpacked.getInstallments(), LEN_INSTALLMENTS)); + sb.append(padRight(unpacked.getFiller(), LEN_FILLER)); + return sb.toString().getBytes(); + } + + private String extract(String data, int offset, int length) { + if (offset >= data.length()) return ""; + int end = Math.min(offset + length, data.length()); + return data.substring(offset, end).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(); + } +} -- 2.49.1 From 5fac88c29305cb35bb349d19a0f1db37394cfc2d Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:10:32 +0000 Subject: [PATCH 4/9] =?UTF-8?q?[=EB=A7=A4=EC=9E=85]=20mg=5Frecv=5Fsvc=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=84=EB=AC=B8=EC=88=98=EC=8B=A0=20@Service=20(?= =?UTF-8?q?ACM-MG-004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquiring/codec/UnpackedMessage.java | 36 +++++++++++++++++++ 1 file changed, 36 insertions(+) create mode 100644 boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/UnpackedMessage.java diff --git a/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/UnpackedMessage.java b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/UnpackedMessage.java new file mode 100644 index 0000000..2d6e168 --- /dev/null +++ b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/codec/UnpackedMessage.java @@ -0,0 +1,36 @@ +package com.klaro.acquirecore.acquiring.codec; + +/** + * 언팩된 전문 메시지 DTO + */ +public class UnpackedMessage { + + private String txType; + private String traceNo; + private String merchantId; + private String amount; + private String cardNo; + private String expiryDate; + private String installments; + private String filler; + private String rawData; + + public String getTxType() { return txType; } + public void setTxType(String txType) { this.txType = txType; } + public String getTraceNo() { return traceNo; } + public void setTraceNo(String traceNo) { this.traceNo = traceNo; } + public String getMerchantId() { return merchantId; } + public void setMerchantId(String merchantId) { this.merchantId = merchantId; } + public String getAmount() { return amount; } + public void setAmount(String amount) { this.amount = amount; } + public String getCardNo() { return cardNo; } + public void setCardNo(String cardNo) { this.cardNo = cardNo; } + public String getExpiryDate() { return expiryDate; } + public void setExpiryDate(String expiryDate) { this.expiryDate = expiryDate; } + public String getInstallments() { return installments; } + public void setInstallments(String installments) { this.installments = installments; } + public String getFiller() { return filler; } + public void setFiller(String filler) { this.filler = filler; } + public String getRawData() { return rawData; } + public void setRawData(String rawData) { this.rawData = rawData; } +} -- 2.49.1 From 8854396cae74dedf655442287f6553469f9deb6a Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:10:33 +0000 Subject: [PATCH 5/9] =?UTF-8?q?[=EB=A7=A4=EC=9E=85]=20mg=5Frecv=5Fsvc=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=84=EB=AC=B8=EC=88=98=EC=8B=A0=20@Service=20(?= =?UTF-8?q?ACM-MG-004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquirecore/acquiring/dto/TxRequest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/dto/TxRequest.java diff --git a/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/dto/TxRequest.java b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/dto/TxRequest.java new file mode 100644 index 0000000..0320b92 --- /dev/null +++ b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/dto/TxRequest.java @@ -0,0 +1,33 @@ +package com.klaro.acquirecore.acquiring.dto; + +/** + * 거래 요청 DTO + */ +public class TxRequest { + + private String traceNo; + private String txType; + private String merchantId; + private String amount; + private String cardNo; + private String expiryDate; + private String installments; + private String messageRaw; + + public String getTraceNo() { return traceNo; } + public void setTraceNo(String traceNo) { this.traceNo = traceNo; } + public String getTxType() { return txType; } + public void setTxType(String txType) { this.txType = txType; } + public String getMerchantId() { return merchantId; } + public void setMerchantId(String merchantId) { this.merchantId = merchantId; } + public String getAmount() { return amount; } + public void setAmount(String amount) { this.amount = amount; } + public String getCardNo() { return cardNo; } + public void setCardNo(String cardNo) { this.cardNo = cardNo; } + public String getExpiryDate() { return expiryDate; } + public void setExpiryDate(String expiryDate) { this.expiryDate = expiryDate; } + public String getInstallments() { return installments; } + public void setInstallments(String installments) { this.installments = installments; } + public String getMessageRaw() { return messageRaw; } + public void setMessageRaw(String messageRaw) { this.messageRaw = messageRaw; } +} -- 2.49.1 From e68e710f0af0ef5c68e1c82e8d9e2771b375fb5d Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:10:34 +0000 Subject: [PATCH 6/9] =?UTF-8?q?[=EB=A7=A4=EC=9E=85]=20mg=5Frecv=5Fsvc=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=84=EB=AC=B8=EC=88=98=EC=8B=A0=20@Service=20(?= =?UTF-8?q?ACM-MG-004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquirecore/acquiring/dto/TxResponse.java | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) create mode 100644 boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/dto/TxResponse.java diff --git a/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/dto/TxResponse.java b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/dto/TxResponse.java new file mode 100644 index 0000000..2b24489 --- /dev/null +++ b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/dto/TxResponse.java @@ -0,0 +1,21 @@ +package com.klaro.acquirecore.acquiring.dto; + +/** + * 거래 응답 DTO + */ +public class TxResponse { + + private String traceNo; + private String txType; + private String respCode; + private String respMsg; + + public String getTraceNo() { return traceNo; } + public void setTraceNo(String traceNo) { this.traceNo = traceNo; } + public String getTxType() { return txType; } + public void setTxType(String txType) { this.txType = txType; } + public String getRespCode() { return respCode; } + public void setRespCode(String respCode) { this.respCode = respCode; } + public String getRespMsg() { return respMsg; } + public void setRespMsg(String respMsg) { this.respMsg = respMsg; } +} -- 2.49.1 From b1d72570912fe883b31fc19723fef8a6c4ac8980 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:10:35 +0000 Subject: [PATCH 7/9] =?UTF-8?q?[=EB=A7=A4=EC=9E=85]=20mg=5Frecv=5Fsvc=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=84=EB=AC=B8=EC=88=98=EC=8B=A0=20@Service=20(?= =?UTF-8?q?ACM-MG-004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquiring/repository/TxRepository.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/repository/TxRepository.java diff --git a/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/repository/TxRepository.java b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/repository/TxRepository.java new file mode 100644 index 0000000..b52bd93 --- /dev/null +++ b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/repository/TxRepository.java @@ -0,0 +1,13 @@ +package com.klaro.acquirecore.acquiring.repository; + +import com.klaro.acquirecore.acquiring.dto.TxRequest; + +/** + * 거래 DB 접근 인터페이스 (스텁) + * Legacy: mg_recv_svc.pgc의 DB 처리 부분 + */ +public interface TxRepository { + + void save(TxRequest request); + TxRequest findByTraceNo(String traceNo); +} -- 2.49.1 From 7ff117469f21deddca542e989fa1fcb42823fb94 Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:10:37 +0000 Subject: [PATCH 8/9] =?UTF-8?q?[=EB=A7=A4=EC=9E=85]=20mg=5Frecv=5Fsvc=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=84=EB=AC=B8=EC=88=98=EC=8B=A0=20@Service=20(?= =?UTF-8?q?ACM-MG-004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../repository/TxRepositoryStub.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/repository/TxRepositoryStub.java diff --git a/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/repository/TxRepositoryStub.java b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/repository/TxRepositoryStub.java new file mode 100644 index 0000000..23be129 --- /dev/null +++ b/boot/modules/acquiring/src/main/java/com/klaro/acquirecore/acquiring/repository/TxRepositoryStub.java @@ -0,0 +1,28 @@ +package com.klaro.acquirecore.acquiring.repository; + +import com.klaro.acquirecore.acquiring.dto.TxRequest; +import org.springframework.stereotype.Repository; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +/** + * 거래 DB 접근 스텁 구현 (인메모리) + */ +@Repository +public class TxRepositoryStub implements TxRepository { + + private final Map store = new ConcurrentHashMap<>(); + + @Override + public void save(TxRequest request) { + if (request != null && request.getTraceNo() != null) { + store.put(request.getTraceNo(), request); + } + } + + @Override + public TxRequest findByTraceNo(String traceNo) { + return store.get(traceNo); + } +} -- 2.49.1 From ceec115bb76a59d8f2fa6787ed15551fac6ca0bd Mon Sep 17 00:00:00 2001 From: forge-bot Date: Sat, 18 Jul 2026 11:10:38 +0000 Subject: [PATCH 9/9] =?UTF-8?q?[=EB=A7=A4=EC=9E=85]=20mg=5Frecv=5Fsvc=20?= =?UTF-8?q?=E2=86=92=20=EC=A0=84=EB=AC=B8=EC=88=98=EC=8B=A0=20@Service=20(?= =?UTF-8?q?ACM-MG-004)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../acquiring/service/TxServiceTest.java | 94 +++++++++++++++++++ 1 file changed, 94 insertions(+) create mode 100644 boot/modules/acquiring/src/test/java/com/klaro/acquirecore/acquiring/service/TxServiceTest.java diff --git a/boot/modules/acquiring/src/test/java/com/klaro/acquirecore/acquiring/service/TxServiceTest.java b/boot/modules/acquiring/src/test/java/com/klaro/acquirecore/acquiring/service/TxServiceTest.java new file mode 100644 index 0000000..d472ca1 --- /dev/null +++ b/boot/modules/acquiring/src/test/java/com/klaro/acquirecore/acquiring/service/TxServiceTest.java @@ -0,0 +1,94 @@ +package com.klaro.acquirecore.acquiring.service; + +import com.klaro.acquirecore.acquiring.codec.MessageCodec; +import com.klaro.acquirecore.acquiring.codec.UnpackedMessage; +import com.klaro.acquirecore.acquiring.dto.TxRequest; +import com.klaro.acquirecore.acquiring.dto.TxResponse; +import com.klaro.acquirecore.acquiring.repository.TxRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.*; + +@ExtendWith(MockitoExtension.class) +class TxServiceTest { + + @Mock + private TxRepository txRepository; + + private MessageCodec messageCodec; + private TxService txService; + + @BeforeEach + void setUp() { + messageCodec = new MessageCodec(); + txService = new TxService(messageCodec, txRepository); + } + + @Test + void processMessage_validMessage_returnsApproved() { + String rawMessage = "0100TRX123456789MER00000012345 41111111111111112025/1230N "; + TxResponse response = txService.processMessage(rawMessage.getBytes()); + + assertNotNull(response); + assertEquals("00", response.getRespCode()); + assertEquals("Approved", response.getRespMsg()); + verify(txRepository, times(1)).save(any(TxRequest.class)); + } + + @Test + void processMessage_emptyMessage_returnsError() { + TxResponse response = txService.processMessage(new byte[0]); + + assertNotNull(response); + assertEquals("99", response.getRespCode()); + } + + @Test + void processMessage_nullMessage_returnsError() { + TxResponse response = txService.processMessage(null); + + assertNotNull(response); + assertEquals("99", response.getRespCode()); + } + + @Test + void codec_unpackValidMessage_parsesFields() { + String raw = "0100TRX123456789MER00000012345 41111111111111112025/1230N "; + UnpackedMessage unpacked = messageCodec.unpack(raw.getBytes()); + + assertEquals("0100", unpacked.getTxType()); + assertEquals("TRX123456789", unpacked.getTraceNo()); + assertEquals("MER00000012345", unpacked.getMerchantId()); + assertEquals("4111111111111111", unpacked.getCardNo()); + } + + @Test + void codec_unpackEmpty_throwsException() { + assertThrows(IllegalArgumentException.class, () -> messageCodec.unpack(new byte[0])); + } + + @Test + void codec_packUnpacked_roundTrips() { + UnpackedMessage original = new UnpackedMessage(); + original.setTxType("0100"); + original.setTraceNo("TRX123456789"); + original.setMerchantId("MER00000012345"); + original.setAmount("000000100000"); + original.setCardNo("4111111111111111"); + original.setExpiryDate("2025/1230"); + original.setInstallments("00"); + original.setFiller(""); + + byte[] packed = messageCodec.pack(original); + UnpackedMessage result = messageCodec.unpack(packed); + + assertEquals(original.getTxType(), result.getTxType()); + assertEquals(original.getTraceNo(), result.getTraceNo()); + } +} -- 2.49.1