package com.ruoyi.app.utils.omg; import org.junit.jupiter.api.Test; import java.util.Comparator; import java.util.LinkedHashMap; import java.util.Map; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotEquals; import static org.junit.jupiter.api.Assertions.assertTrue; class OmgCheckMacValueTest { @Test void generatesOfficialEcpayLogisticsVector() { Map params = new LinkedHashMap<>(); params.put("MerchantID", "2000933"); params.put("MerchantTradeNo", "A20130312153023"); params.put("MerchantTradeDate", "2013/03/12 15:30:23"); params.put("LogisticsType", "CVS"); params.put("LogisticsSubType", "FAMIC2C"); params.put("GoodsAmount", "1000"); params.put("IsCollection", "N"); params.put("ServerReplyURL", "https://www.ecpay.com.tw/ServerReplyURL"); params.put("SenderName", "SteveJones"); params.put("ReceiverName", "MarkHarrison"); params.put("ReceiverStoreID", "001779"); assertEquals("754C5D1365035DA34D2CD91CC256F18C", OmgCheckMacValue.generate(params, "XBERn1YOvpM9nfZc", "h1ONHk4P4yqbl5LK", "MD5")); } @Test void verifiesSha256WithoutDependingOnHexCase() { Map params = new LinkedHashMap<>(); params.put("MerchantID", "2000132"); params.put("MerchantTradeNo", "OMG12345678901234567"); String signature = OmgCheckMacValue.generate(params, "5294y06JbISpM5x9", "v77hoKGq4kWxNNIS"); params.put("CheckMacValue", signature.toLowerCase()); assertTrue(OmgCheckMacValue.verify(params, "5294y06JbISpM5x9", "v77hoKGq4kWxNNIS")); } @Test void rejectsMalformedOrIncorrectSignature() { Map params = new LinkedHashMap<>(); params.put("MerchantID", "2000132"); params.put("CheckMacValue", "not-a-sha256"); assertFalse(OmgCheckMacValue.verify(params, "key", "iv")); params.put("CheckMacValue", "0".repeat(64)); assertFalse(OmgCheckMacValue.verify(params, "key", "iv")); } /** * 回归 bugB(2026-08-12):OMG QueryTradeInfo 响应字段大小写混合,服务端 CheckMacValue 按 * 大小写不敏感排序计算。旧实现用 Comparator.naturalOrder()(ASCII 大小写敏感)排序,混合字段下 * 与 OMG 分歧 → 验签失败(生产订单 991786433092835 实测 receivedCMV==ci_inc 坐实)。 * *

本用例构造同形响应(首字母大写的 AlipayID/CustomField1/MerchantID 与首字母小写的 * amount/auth_code/card4no/eci/gwsr/process_date/red_dan 并存),先用「大小写不敏感」生成 OMG 侧 * 签名,再断言 verify() 通过。修复前 verify 走 naturalOrder 会算出不同签名而失败。 */ @Test void verifiesQueryTradeInfoResponseWithCaseInsensitiveKeyOrder() { Map resp = new LinkedHashMap<>(); resp.put("MerchantID", "1000031"); resp.put("MerchantTradeNo", "OMG10C9AA21B1814EE29"); resp.put("TradeAmt", "97"); resp.put("TradeDate", "2026/08/11 17:06:42"); resp.put("TradeNo", "2608111706428239"); resp.put("TradeStatus", "10200095"); resp.put("ItemName", "order 991786433092835"); resp.put("HandlingCharge", "0"); resp.put("PaymentTypeChargeFee", "0"); // §2 额外回传(空值),首字母大小写混合 —— 触发排序分歧的关键 resp.put("AlipayID", ""); resp.put("CustomField1", ""); resp.put("amount", ""); resp.put("auth_code", ""); resp.put("card4no", ""); resp.put("eci", ""); resp.put("gwsr", ""); resp.put("process_date", ""); resp.put("red_dan", ""); String key = "5294y06JbISpM5x9"; String iv = "v77hoKGq4kWxNNIS"; // OMG 侧签名:大小写不敏感排序,保留空值字段 String omgCmv = OmgCheckMacValue.generate(resp, key, iv, String.CASE_INSENSITIVE_ORDER, false); // 前置断言:本字段集必须使两种排序产生不同签名,否则用例无法锁定该 bug String sensitiveCmv = OmgCheckMacValue.generate(resp, key, iv, Comparator.naturalOrder(), false); assertNotEquals(sensitiveCmv, omgCmv, "测试字段集必须使大小写敏感/不敏感排序产生不同签名,否则用例无效"); resp.put("CheckMacValue", omgCmv); assertTrue(OmgCheckMacValue.verify(resp, key, iv), "verify 必须按大小写不敏感排序以匹配 OMG 服务端算法"); } }