Просмотр исходного кода

修复OMG支付尝试终态后重新付款提示无可查询付款信息

旧尝试被自动补偿任务关闭为FAILED/SUPERSEDED并释放active指针后,
retry内部query查不到记录直接抛PAYMENT_QUERY_NOT_AVAILABLE形成死路;
现捕获该错误回落create新建尝试(create自带订单锁与已付校验)。

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
qmj 1 день назад
Родитель
Сommit
c24ca960ee

+ 13 - 1
ruoyi-admin/src/main/java/com/ruoyi/app/omgpay/OmgPaymentRetryService.java

@@ -5,6 +5,7 @@ import org.springframework.stereotype.Service;
 
 import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.ORDER_ALREADY_PAID;
 import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.PAYMENT_METHOD_INVALID;
+import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.PAYMENT_QUERY_NOT_AVAILABLE;
 import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.PAYMENT_RETRY_NOT_AVAILABLE;
 
 /** Verifies the gateway state before replacing an abandoned payment attempt. */
@@ -23,7 +24,18 @@ public class OmgPaymentRetryService {
         if (paymentMethod == null) {
             throw business(PAYMENT_METHOD_INVALID);
         }
-        OmgQueryPaymentResponse query = queryService.query(userId, orderId);
+        OmgQueryPaymentResponse query;
+        try {
+            query = queryService.query(userId, orderId);
+        } catch (OmgPaymentBusinessException exception) {
+            if (exception.getCode() != PAYMENT_QUERY_NOT_AVAILABLE) {
+                throw exception;
+            }
+            // A terminal attempt (FAILED/SUPERSEDED by auto-compensation) has released its
+            // active pointer, so query has nothing to reconcile; a still-unpaid order is then
+            // equivalent to a first-time payment, and create re-validates order and concurrency.
+            return createService.create(userId, orderId, paymentMethod);
+        }
         if (query == null || query.status() == null) {
             throw business(PAYMENT_RETRY_NOT_AVAILABLE);
         }

+ 29 - 0
ruoyi-admin/src/test/java/com/ruoyi/app/omgpay/OmgPaymentRetryServiceTest.java

@@ -8,7 +8,9 @@ import org.junit.jupiter.api.Test;
 import java.util.Map;
 
 import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.ORDER_ALREADY_PAID;
+import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.ORDER_NOT_AVAILABLE;
 import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.PAYMENT_METHOD_INVALID;
+import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.PAYMENT_QUERY_NOT_AVAILABLE;
 import static com.ruoyi.app.omgpay.OmgPaymentErrorCode.PAYMENT_RETRY_NOT_AVAILABLE;
 import static org.junit.jupiter.api.Assertions.assertEquals;
 import static org.junit.jupiter.api.Assertions.assertSame;
@@ -90,6 +92,33 @@ class OmgPaymentRetryServiceTest {
         verifyNoInteractions(createService);
     }
 
+    @Test
+    void createsFreshAttemptWhenNoActiveAttemptRemains() {
+        // 旧尝试被自动补偿任务关闭为 FAILED/SUPERSEDED 后 active 指针已释放,
+        // query 找不到可查记录时,重新付款必须回落到新建而不是死路。
+        OmgPaymentCreateOutcome expected = outcome();
+        when(queryService.query(5L, "DD-1"))
+                .thenThrow(new OmgPaymentBusinessException(PAYMENT_QUERY_NOT_AVAILABLE, 77L));
+        when(createService.create(5L, "DD-1", OmgPaymentMethod.CREDIT)).thenReturn(expected);
+
+        assertSame(expected, service.retry(5L, "DD-1", OmgPaymentMethod.CREDIT));
+
+        verify(createService).create(5L, "DD-1", OmgPaymentMethod.CREDIT);
+        verify(createService, never()).replaceActiveForRetry(5L, "DD-1", "OMGOLD", OmgPaymentMethod.CREDIT);
+    }
+
+    @Test
+    void propagatesUnrelatedQueryErrorsWithoutCreating() {
+        when(queryService.query(5L, "DD-1"))
+                .thenThrow(new OmgPaymentBusinessException(ORDER_NOT_AVAILABLE));
+
+        OmgPaymentBusinessException error = assertThrows(OmgPaymentBusinessException.class,
+                () -> service.retry(5L, "DD-1", OmgPaymentMethod.CREDIT));
+
+        assertEquals(ORDER_NOT_AVAILABLE, error.getCode());
+        verifyNoInteractions(createService);
+    }
+
     @Test
     void rejectsNullPaymentMethodBeforeQuery() {
         OmgPaymentBusinessException error = assertThrows(OmgPaymentBusinessException.class,

+ 1 - 0
specs/020-omg-payment-rebuild/omg-app-integration.md

@@ -534,6 +534,7 @@ token: <用户登录 token>
 |---|---|
 | `PAID` | 不创建新支付,返回 `ORDER_ALREADY_PAID` |
 | `FAILED` | 旧尝试已经结束,生成新的 `MerchantTradeNo` 和新表单 |
+| 本地无进行中的尝试(旧尝试已被自动补偿关闭为终态 FAILED/SUPERSEDED,`active` 指针已释放) | 不再查询旧尝试,直接生成新的 `MerchantTradeNo` 和新表单(等价首次 create;订单已付会被 create 的校验以 `ORDER_ALREADY_PAID` 拒绝) |
 | `UNPAID` 且 `paymentType` 为空 | 原子地把旧尝试改为 `SUPERSEDED`,生成新的 `MerchantTradeNo` 和新表单 |
 | `UNPAID` 且 `paymentType` 非空 | 为避免覆盖可能正在授权的交易,返回 `PAYMENT_RETRY_NOT_AVAILABLE` |
 | `UNKNOWN`、查询失败或并发状态已改变 | 不修改旧尝试,返回对应错误 |