|
|
@@ -0,0 +1,130 @@
|
|
|
+package com.ruoyi.app.omgpay;
|
|
|
+
|
|
|
+import com.ruoyi.app.omgpay.dto.OmgNotifyField;
|
|
|
+import com.ruoyi.app.omgpay.dto.OmgNotifyRequest;
|
|
|
+import com.ruoyi.system.omgpay.domain.OmgPaymentAttempt;
|
|
|
+import com.ruoyi.system.omgpay.service.IOmgPaymentAttemptService;
|
|
|
+import org.junit.jupiter.api.BeforeEach;
|
|
|
+import org.junit.jupiter.api.Test;
|
|
|
+
|
|
|
+import java.util.ArrayList;
|
|
|
+import java.util.LinkedHashMap;
|
|
|
+import java.util.List;
|
|
|
+import java.util.Map;
|
|
|
+
|
|
|
+import static org.junit.jupiter.api.Assertions.*;
|
|
|
+import static org.mockito.Mockito.mock;
|
|
|
+import static org.mockito.Mockito.when;
|
|
|
+
|
|
|
+class OmgPaymentClientReturnServiceTest {
|
|
|
+ private static final String HASH_KEY = "5294y06JbISpM5x9";
|
|
|
+ private static final String HASH_IV = "v77hoKGq4kWxNNIS";
|
|
|
+
|
|
|
+ private IOmgPaymentAttemptService attempts;
|
|
|
+ private OmgPaymentClientReturnService service;
|
|
|
+
|
|
|
+ @BeforeEach
|
|
|
+ void setUp() {
|
|
|
+ attempts = mock(IOmgPaymentAttemptService.class);
|
|
|
+ OmgPaymentProperties properties = new OmgPaymentProperties();
|
|
|
+ properties.setAppReturnUrl("https://link.waimai-paotui.com/payment/omg");
|
|
|
+ service = new OmgPaymentClientReturnService(attempts, new OmgCheckMacSigner(),
|
|
|
+ new OmgPaymentReturnPageRenderer(properties));
|
|
|
+ OmgPaymentAttempt attempt = new OmgPaymentAttempt();
|
|
|
+ attempt.setId(7L);
|
|
|
+ attempt.setDdId("DD-1&next=evil");
|
|
|
+ attempt.setMerchantId("1000031");
|
|
|
+ attempt.setMerchantTradeNo("OMG123");
|
|
|
+ attempt.setAmount(100);
|
|
|
+ attempt.setHashKeySnapshot(HASH_KEY);
|
|
|
+ attempt.setHashIvSnapshot(HASH_IV);
|
|
|
+ when(attempts.selectByMerchantTradeNo("OMG123")).thenReturn(attempt);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void verifiedPaymentResultReturnsNoStoreHtmlThatOnlyNavigatesToApp() {
|
|
|
+ LinkedHashMap<String, String> fields = paymentFields();
|
|
|
+ fields.put("FutureExtraField", "signed extra");
|
|
|
+ OmgPaymentReturnPage page = service.paymentResult(signedRequest(fields));
|
|
|
+
|
|
|
+ assertEquals(200, page.statusCode());
|
|
|
+ assertTrue(page.html().contains("https://link.waimai-paotui.com/payment/omg?orderId=DD-1%26next%3Devil&source=payment-result"));
|
|
|
+ assertTrue(page.html().contains("正在返回 App"));
|
|
|
+ assertFalse(page.html().contains(HASH_KEY));
|
|
|
+ assertFalse(page.html().contains("CheckMacValue"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void tamperingAnyExtraFieldRejectsTheWholeClientResult() {
|
|
|
+ LinkedHashMap<String, String> fields = paymentFields();
|
|
|
+ fields.put("FutureExtraField", "signed extra");
|
|
|
+ OmgNotifyRequest signed = signedRequest(fields);
|
|
|
+ List<OmgNotifyField> tamperedFields = new ArrayList<>(signed.getFields());
|
|
|
+ tamperedFields.replaceAll(field -> "FutureExtraField".equals(field.name())
|
|
|
+ ? new OmgNotifyField(field.name(), "tampered") : field);
|
|
|
+
|
|
|
+ OmgPaymentReturnPage page = service.paymentResult(
|
|
|
+ new OmgNotifyRequest("127.0.0.1", "tampered", tamperedFields, null));
|
|
|
+
|
|
|
+ assertEquals(400, page.statusCode());
|
|
|
+ assertFalse(page.html().contains("orderId="));
|
|
|
+ assertTrue(page.html().contains("無法驗證返回資料"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void verifiedPaymentInfoAndPlainBackUseDifferentNavigationSources() {
|
|
|
+ LinkedHashMap<String, String> info = commonFields("2", "ATM_TAISHIN");
|
|
|
+ info.put("BankCode", "812");
|
|
|
+ info.put("vAccount", "1234567890123456");
|
|
|
+ info.put("ExpireDate", "2026/08/14 23:59:59");
|
|
|
+
|
|
|
+ OmgPaymentReturnPage paymentInfo = service.paymentInfoResult(signedRequest(info));
|
|
|
+ OmgPaymentReturnPage back = service.back("OMG123");
|
|
|
+
|
|
|
+ assertEquals(200, paymentInfo.statusCode());
|
|
|
+ assertTrue(paymentInfo.html().contains("source=payment-info"));
|
|
|
+ assertTrue(back.html().contains("source=back"));
|
|
|
+ assertFalse(back.html().contains("付款成功"));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Test
|
|
|
+ void unknownBackReferenceDoesNotCreateAnAppLink() {
|
|
|
+ OmgPaymentReturnPage page = service.back("UNKNOWN");
|
|
|
+
|
|
|
+ assertEquals(404, page.statusCode());
|
|
|
+ assertFalse(page.html().contains("orderId="));
|
|
|
+ }
|
|
|
+
|
|
|
+ private OmgNotifyRequest signedRequest(LinkedHashMap<String, String> fields) {
|
|
|
+ fields.put("CheckMacValue", new OmgCheckMacSigner().sign(fields, HASH_KEY, HASH_IV));
|
|
|
+ List<OmgNotifyField> result = fields.entrySet().stream()
|
|
|
+ .map(entry -> new OmgNotifyField(entry.getKey(), entry.getValue())).toList();
|
|
|
+ return new OmgNotifyRequest("127.0.0.1", "fixture", result, null);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static LinkedHashMap<String, String> paymentFields() {
|
|
|
+ LinkedHashMap<String, String> fields = commonFields("1", "Credit_CreditCard");
|
|
|
+ fields.put("PaymentDate", "2026/08/13 16:00:00");
|
|
|
+ fields.put("PaymentTypeChargeFee", "0.00");
|
|
|
+ fields.put("SimulatePaid", "0");
|
|
|
+ return fields;
|
|
|
+ }
|
|
|
+
|
|
|
+ private static LinkedHashMap<String, String> commonFields(String rtnCode, String paymentType) {
|
|
|
+ LinkedHashMap<String, String> fields = new LinkedHashMap<>();
|
|
|
+ fields.put("MerchantID", "1000031");
|
|
|
+ fields.put("MerchantTradeNo", "OMG123");
|
|
|
+ fields.put("StoreID", "");
|
|
|
+ fields.put("RtnCode", rtnCode);
|
|
|
+ fields.put("RtnMsg", "Succeeded");
|
|
|
+ fields.put("TradeNo", "GW123");
|
|
|
+ fields.put("TradeAmt", "100");
|
|
|
+ fields.put("PaymentType", paymentType);
|
|
|
+ fields.put("TradeDate", "2026/08/13 15:59:00");
|
|
|
+ fields.put("CustomField1", "");
|
|
|
+ fields.put("CustomField2", "");
|
|
|
+ fields.put("CustomField3", "");
|
|
|
+ fields.put("CustomField4", "");
|
|
|
+ return fields;
|
|
|
+ }
|
|
|
+}
|