OmgPaymentRefundServiceTest.java 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. package com.ruoyi.app.omgpay;
  2. import org.junit.jupiter.api.Test;
  3. import org.springframework.jdbc.core.JdbcTemplate;
  4. import org.springframework.jdbc.core.RowMapper;
  5. import java.sql.ResultSet;
  6. import java.util.Map;
  7. import java.util.concurrent.atomic.AtomicInteger;
  8. import java.time.Clock;
  9. import java.time.Instant;
  10. import java.time.ZoneOffset;
  11. import static org.mockito.Mockito.mock;
  12. import static org.mockito.Mockito.verify;
  13. import static org.mockito.Mockito.verifyNoInteractions;
  14. import static org.mockito.Mockito.doAnswer;
  15. import static org.mockito.Mockito.when;
  16. import static org.mockito.Mockito.times;
  17. import static org.mockito.ArgumentMatchers.any;
  18. import static org.mockito.ArgumentMatchers.eq;
  19. import static org.mockito.ArgumentMatchers.startsWith;
  20. import static org.junit.jupiter.api.Assertions.assertEquals;
  21. import static org.junit.jupiter.api.Assertions.assertThrows;
  22. class OmgPaymentRefundServiceTest {
  23. private final OmgPaymentRefundService service = new OmgPaymentRefundService();
  24. @Test
  25. void testEnvironmentRejectsRefundBeforeAnyGatewayOrStateChangeCanExist() {
  26. OmgPaymentBusinessException error = assertThrows(OmgPaymentBusinessException.class,
  27. () -> service.refund(5L, "DD-1"));
  28. assertEquals(OmgPaymentErrorCode.PAYMENT_REFUND_UNAVAILABLE_IN_TEST_ENVIRONMENT,
  29. error.getCode());
  30. }
  31. @Test
  32. void validatesAuthenticationAndOrderIdBeforeEnvironmentResponse() {
  33. assertEquals(OmgPaymentErrorCode.AUTH_REQUIRED,
  34. assertThrows(OmgPaymentBusinessException.class,
  35. () -> service.refund(null, "DD-1")).getCode());
  36. assertEquals(OmgPaymentErrorCode.ORDER_REQUIRED,
  37. assertThrows(OmgPaymentBusinessException.class,
  38. () -> service.refund(5L, " ")).getCode());
  39. assertEquals(OmgPaymentErrorCode.ORDER_REQUIRED,
  40. assertThrows(OmgPaymentBusinessException.class,
  41. () -> service.refund(5L, "X".repeat(65))).getCode());
  42. }
  43. @Test
  44. void testEnvironmentRecordsExpectedFailureWithoutCallingProductionGateway() {
  45. JdbcTemplate jdbc = mock(JdbcTemplate.class);
  46. OmgRefundGateway gateway = mock(OmgRefundGateway.class);
  47. OmgPaymentRefundService refunds = new OmgPaymentRefundService(new OmgPaymentProperties(),
  48. null, jdbc, null, gateway, null);
  49. refunds.processOne(8L);
  50. verify(jdbc).update(org.mockito.ArgumentMatchers.contains("TEST_ENVIRONMENT"),
  51. org.mockito.ArgumentMatchers.eq(8L));
  52. verifyNoInteractions(gateway);
  53. }
  54. @Test
  55. void usesGatewayCreditStateToChooseOfficialAction() {
  56. assertEquals("N", OmgPaymentRefundService.nextAction("已授權"));
  57. assertEquals("E", OmgPaymentRefundService.nextAction("要關帳"));
  58. assertEquals("R", OmgPaymentRefundService.nextAction("已關帳"));
  59. assertEquals(null, OmgPaymentRefundService.nextAction("關帳中"));
  60. }
  61. @Test
  62. @SuppressWarnings({"unchecked", "rawtypes"})
  63. void productionQueriesCreditBeforeActionAndDoesNotReplayUnchangedAction() throws Exception {
  64. OmgPaymentProperties properties = new OmgPaymentProperties();
  65. properties.setEnvironment("PRODUCTION");
  66. properties.setCreditCheckCodes(Map.of("1000031", "123456"));
  67. JdbcTemplate jdbc = mock(JdbcTemplate.class);
  68. OmgRefundGateway gateway = mock(OmgRefundGateway.class);
  69. OmgPaymentRefundService refunds = new OmgPaymentRefundService(properties, null, jdbc,
  70. new OmgCheckMacSigner(), gateway, new OmgQueryResponseParser(),
  71. Clock.fixed(Instant.parse("2026-10-15T07:00:00Z"), ZoneOffset.UTC));
  72. when(jdbc.update(startsWith("UPDATE pos_order_omg_refund SET lease_until=DATE_ADD"), eq(8L)))
  73. .thenReturn(1);
  74. AtomicInteger reads = new AtomicInteger();
  75. doAnswer(call -> {
  76. RowMapper mapper = call.getArgument(1);
  77. ResultSet rs = mock(ResultSet.class);
  78. boolean first = reads.getAndIncrement() == 0;
  79. when(rs.getLong(1)).thenReturn(7L);
  80. when(rs.getString(2)).thenReturn(first ? "PENDING" : "ACTION_UNKNOWN");
  81. when(rs.getString(3)).thenReturn(first ? null : "N");
  82. when(rs.getString(4)).thenReturn(first ? null : "已授權");
  83. return mapper.mapRow(rs, 0);
  84. }).when(jdbc).queryForObject(startsWith("SELECT attempt_id"), any(RowMapper.class), eq(8L));
  85. doAnswer(call -> {
  86. RowMapper mapper = call.getArgument(1);
  87. ResultSet rs = mock(ResultSet.class);
  88. when(rs.getLong("id")).thenReturn(7L);
  89. when(rs.getString("dd_id")).thenReturn("DD-1");
  90. when(rs.getString("merchant_trade_no")).thenReturn("OMG123");
  91. when(rs.getString("merchant_id")).thenReturn("1000031");
  92. when(rs.getInt("amount")).thenReturn(100);
  93. when(rs.getString("hash_key_snapshot")).thenReturn("KEY");
  94. when(rs.getString("hash_iv_snapshot")).thenReturn("IV");
  95. when(rs.getString("trade_no")).thenReturn("GW123");
  96. when(rs.getString("credit_refund_id")).thenReturn("123");
  97. when(rs.getString("gateway_environment")).thenReturn("PRODUCTION");
  98. return mapper.mapRow(rs, 0);
  99. }).when(jdbc).queryForObject(startsWith("SELECT id, dd_id"), any(RowMapper.class), eq(7L));
  100. when(gateway.queryCredit(any())).thenReturn(
  101. "{\"RtnMsg\":\"\",\"RtnValue\":{\"TradeID\":\"123\",\"amount\":\"100\",\"status\":\"已授權\"}}",
  102. "{\"RtnMsg\":\"\",\"RtnValue\":{\"TradeID\":\"123\",\"amount\":\"100\",\"status\":\"已授權\"}}",
  103. "{\"RtnMsg\":\"\",\"RtnValue\":{\"TradeID\":\"123\",\"amount\":\"100\",\"status\":\"已關帳\",\"close_data\":[{\"status\":\"銀行已追回\",\"amount\":\"100\"}]}}");
  104. when(gateway.doAction(any())).thenReturn("MerchantID=1000031&MerchantTradeNo=OMG123&TradeNo=GW123&RtnCode=1");
  105. refunds.processOne(8L);
  106. refunds.processOne(8L);
  107. refunds.processOne(8L);
  108. verify(gateway, times(3)).queryCredit(any());
  109. verify(gateway, times(1)).doAction(org.mockito.ArgumentMatchers.argThat(fields ->
  110. "N".equals(((Map<?, ?>) fields).get("Action"))));
  111. verify(jdbc).update(startsWith("UPDATE pos_order_omg_refund SET status='REFUNDED'"), eq(8L));
  112. }
  113. }