feat:充值错误处理
This commit is contained in:
1 parent
b74b57b962
commit
abec54182d
8 files changed
+220
-9
No files matched your search
+5
@@ -54,6 +54,7 @@ public final class PjcloudSentinelFeign {
|
||||
return this;
|
||||
}
|
||||
|
||||
/** 为声明式与动态Feign客户端创建Sentinel调用处理器,显式声明的降级策略按原注解解析。 */
|
||||
@Override
|
||||
public Feign internalBuild() {
|
||||
super.invocationHandlerFactory(new InvocationHandlerFactory() {
|
||||
@@ -62,6 +63,10 @@ public final class PjcloudSentinelFeign {
|
||||
|
||||
// 查找 FeignClient 上的 降级策略
|
||||
FeignClient feignClient = AnnotationUtils.findAnnotation(target.type(), FeignClient.class);
|
||||
// FeignClientBuilder的动态通知契约没有@FeignClient,仍使用Sentinel处理器并保留请求拦截链。
|
||||
if (feignClient == null) {
|
||||
return new PjcloudSentinelInvocationHandler(target, dispatch);
|
||||
}
|
||||
Class<?> fallback = feignClient.fallback();
|
||||
Class<?> fallbackFactory = feignClient.fallbackFactory();
|
||||
|
||||
|
||||
+2
@@ -143,6 +143,8 @@ public class ThirdTransferOrderVo {
|
||||
private Integer attemptCount;
|
||||
/** 下一次执行时间。 */
|
||||
private LocalDateTime nextRunTime;
|
||||
/** 本阶段最近一次安全错误码,与单据最近查单错误分别展示。 */
|
||||
private String lastErrorCode;
|
||||
/** 最近一次安全错误摘要。 */
|
||||
private String lastErrorMessage;
|
||||
}
|
||||
|
||||
+7
-1
@@ -6,6 +6,7 @@ import com.bsd.pjcloud.thirdparty.api.feign.ThirdTransferResultReceiver;
|
||||
import com.bsd.pjcloud.thirdparty.dto.transfer.ThirdTransferResultNotifyDto;
|
||||
import com.bsd.pjcloud.thirdparty.entity.pay.ThirdTransferOrderEntity;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.cloud.openfeign.FeignClientBuilder;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -14,6 +15,7 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/** 通过服务端登记的内部服务发现目标发送结果,复用平台Feign上下文、租户及内部调用拦截器。 */
|
||||
@Service
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class ThirdTransferBusinessNotifier {
|
||||
/** 创建受平台管理的Feign客户端所需的应用上下文。 */
|
||||
@@ -33,8 +35,12 @@ public class ThirdTransferBusinessNotifier {
|
||||
notification.setErrorCode(order.getLastErrorCode());
|
||||
notification.setErrorMessage(order.getLastErrorMessage());
|
||||
R<Boolean> response = receiver.receive(notification);
|
||||
return response != null && response.isOk() && Boolean.TRUE.equals(response.getData());
|
||||
boolean acknowledged = response != null && response.isOk() && Boolean.TRUE.equals(response.getData());
|
||||
if (!acknowledged) log.warn("转账结果通知未获确认,转账号={},通知ID={},目标服务={},业务码={}", order.getTransferNo(), order.getNotifyId(), serviceId, response == null ? "EMPTY" : response.getCode());
|
||||
return acknowledged;
|
||||
} catch (Exception exception) {
|
||||
// 只记录定位字段和异常类别,收款人、原始报文及异常消息不进入业务日志。
|
||||
log.warn("转账结果通知调用失败,转账号={},通知ID={},目标服务={},类别={}", order.getTransferNo(), order.getNotifyId(), order.getNotifyServiceId(), exception.getClass().getSimpleName());
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
+9
-6
@@ -100,6 +100,9 @@ public class ThirdTransferOrderStore {
|
||||
if (ThirdTransferStatusEnum.isTerminal(order.getStatus())) {
|
||||
return order;
|
||||
}
|
||||
if (Boolean.TRUE.equals(order.getNeedsAttention())) {
|
||||
throw new IllegalStateException("原转账存在资金事实冲突,请先核对处理");
|
||||
}
|
||||
String previous = order.getStatus();
|
||||
order.setStatus(ThirdTransferStatusEnum.PROCESSING.name());
|
||||
if (order.getSubmitStartedTime() == null) {
|
||||
@@ -252,7 +255,7 @@ public class ThirdTransferOrderStore {
|
||||
event(order, "BUSINESS_RECHECK", null, order.getStatus(), "业务方恢复原指令并安排核对", null);
|
||||
}
|
||||
|
||||
/** 管理端恢复当前资金或当前结果通知对应的失败任务,操作与日志同事务提交。 */
|
||||
/** 管理端唤醒原单适用阶段,包括退避中的RETRY任务;正在执行的租约由Mapper保留,恢复与事件同事务提交。 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean retryOrder(Long id, ThirdTransferScope scope) {
|
||||
requireVisible(id, scope);
|
||||
@@ -260,7 +263,7 @@ public class ThirdTransferOrderStore {
|
||||
boolean changed = false;
|
||||
for (ThirdPaymentTaskEntity task : taskMapper.selectList(new LambdaQueryWrapper<ThirdPaymentTaskEntity>()
|
||||
.eq(ThirdPaymentTaskEntity::getTransferOrderId, id))) {
|
||||
if (taskCanResume(order, task) && taskMapper.manualRetry(task.getId(), LocalDateTime.now()) > 0) {
|
||||
if (taskCanResume(order, task) && taskMapper.rearmByTaskKey(order.getTenantId(), task.getTaskKey(), LocalDateTime.now()) > 0) {
|
||||
changed = true;
|
||||
}
|
||||
}
|
||||
@@ -270,7 +273,7 @@ public class ThirdTransferOrderStore {
|
||||
return changed;
|
||||
}
|
||||
|
||||
/** 既有通用任务重试入口复用转账归属与状态约束,避免从任务ID绕过授权。 */
|
||||
/** 通用任务入口同样恢复原任务键及退避等待,复用完整归属与资金状态校验,执行中租约保持有效。 */
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public boolean retryTask(Long taskId, ThirdTransferScope scope) {
|
||||
ThirdPaymentTaskEntity task = taskMapper.selectById(taskId);
|
||||
@@ -279,7 +282,7 @@ public class ThirdTransferOrderStore {
|
||||
}
|
||||
requireVisible(task.getTransferOrderId(), scope);
|
||||
ThirdTransferOrderEntity order = lock(task.getTransferOrderId());
|
||||
boolean changed = taskCanResume(order, task) && taskMapper.manualRetry(taskId, LocalDateTime.now()) > 0;
|
||||
boolean changed = taskCanResume(order, task) && taskMapper.rearmByTaskKey(order.getTenantId(), task.getTaskKey(), LocalDateTime.now()) > 0;
|
||||
if (changed) {
|
||||
event(order, "MANUAL_RETRY", null, order.getStatus(), "已恢复指定原转账任务",
|
||||
JSONUtil.createObj().set("taskId", String.valueOf(taskId)).set("taskType", task.getTaskType()).toString());
|
||||
@@ -358,7 +361,7 @@ public class ThirdTransferOrderStore {
|
||||
}
|
||||
return ThirdPaymentTaskTypeEnum.TRANSFER_QUERY.getCode().equals(task.getTaskType())
|
||||
|| ThirdPaymentTaskTypeEnum.TRANSFER_SUBMIT.getCode().equals(task.getTaskType())
|
||||
&& !ThirdTransferStatusEnum.isTerminal(order.getStatus());
|
||||
&& !ThirdTransferStatusEnum.isTerminal(order.getStatus()) && !Boolean.TRUE.equals(order.getNeedsAttention());
|
||||
}
|
||||
|
||||
/** 只保留诊断所需白名单字段,收款身份和SDK原始报文不进入持久事件。 */
|
||||
@@ -367,7 +370,7 @@ public class ThirdTransferOrderStore {
|
||||
.set("transferNo", fact.getTransferNo()).set("appId", fact.getAppId())
|
||||
.set("channelOrderNo", fact.getChannelOrderNo()).set("payFundOrderNo", fact.getPayFundOrderNo())
|
||||
.set("amountFen", fact.getAmountFen() == null ? null : fact.getAmountFen().toString())
|
||||
.set("paidTime", fact.getPaidTime()).set("errorCode", fact.getErrorCode())
|
||||
.set("paidTime", fact.getPaidTime()).set("notFound", fact.isNotFound()).set("errorCode", fact.getErrorCode())
|
||||
.set("errorMessage", fact.getErrorMessage()).toString();
|
||||
}
|
||||
|
||||
|
||||
+7
-2
@@ -84,6 +84,11 @@ public class ThirdAppPlusAliTransferServiceImpl implements ThirdTransferChannel
|
||||
Map.entry("MEMO_REQUIRED_IN_TRANSFER_ERROR", "大额付款须填写付款理由"),
|
||||
Map.entry("PAYER_PAYEE_CANNOT_SAME", "收付款账户不能相同"),
|
||||
Map.entry("REMARK_HAS_SENSITIVE_WORD", "付款理由未通过渠道文案校验"));
|
||||
/** 配置拒绝和查无原单只提供恢复步骤,资金状态保留待核实,不能据此创建新的付款指令。 */
|
||||
private static final Map<String, String> RECOVERY_ERRORS = Map.of(
|
||||
"isv.illegal-client-ip", "请求来源IP未通过支付宝应用校验,请核对应用IP白名单与服务出口IP,处理后恢复原转账任务",
|
||||
"isv.invalid-signature", "支付宝请求签名校验失败,请核对应用私钥与应用证书是否配套,处理后恢复原转账任务",
|
||||
"ORDER_NOT_EXIST", "支付宝当前未查到原转账单,请核对原提交任务错误;处理后恢复原任务,系统会先查单再按原号原参数重试");
|
||||
/** 平台登记应用配置,历史交易按固定AppID精确选择。 */
|
||||
private final AppPlusPayConfig applications;
|
||||
/** 既有双模式客户端及可信公钥快照。 */
|
||||
@@ -264,7 +269,7 @@ public class ThirdAppPlusAliTransferServiceImpl implements ThirdTransferChannel
|
||||
private ThirdTransferChannelResult rejected(String code, String subCode, boolean query) {
|
||||
String safe = safeCode(subCode);
|
||||
ThirdTransferChannelResult result = ThirdTransferChannelResult.unknown(safe, errorMessage(safe));
|
||||
result.setNotFound(query && "ORDER_NOT_EXIST".equals(subCode));
|
||||
result.setNotFound(query && "40004".equals(code) && "ORDER_NOT_EXIST".equals(subCode));
|
||||
if (!query && "40004".equals(code) && DEFINITE_ERRORS.containsKey(safe)) {
|
||||
result.setStatus(ThirdTransferStatusEnum.FAILED.name());
|
||||
}
|
||||
@@ -288,6 +293,6 @@ public class ThirdAppPlusAliTransferServiceImpl implements ThirdTransferChannel
|
||||
|
||||
/** 错误说明使用服务端白名单,避免渠道原始报错携带姓名、账号或请求密钥。 */
|
||||
private String errorMessage(String code) {
|
||||
return DEFINITE_ERRORS.getOrDefault(code, "渠道结果待核实,请根据错误码检查原单");
|
||||
return RECOVERY_ERRORS.getOrDefault(code, DEFINITE_ERRORS.getOrDefault(code, "渠道结果待核实,请根据错误码检查原单"));
|
||||
}
|
||||
}
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
package com.bsd.pjcloud.thirdparty.service;
|
||||
|
||||
import com.bsd.pjcloud.common.core.util.R;
|
||||
import com.bsd.pjcloud.common.sentinel.feign.PjcloudSentinelFeign;
|
||||
import com.bsd.pjcloud.thirdparty.api.feign.ThirdTransferResultReceiver;
|
||||
import com.bsd.pjcloud.thirdparty.dto.transfer.ThirdTransferResultNotifyDto;
|
||||
import feign.Client;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.openfeign.FeignClientFactory;
|
||||
import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Proxy;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
/** 使用项目真实Sentinel封装验证通知契约兼容性,所有渠道与HTTP调用均使用离线替身。 */
|
||||
class ThirdTransferBusinessNotifierTest {
|
||||
|
||||
/** 动态Feign契约没有@FeignClient时也能构造代理,避免通知发送前空指针并持续退避。 */
|
||||
@Test
|
||||
void dynamicReceiverBuildsWithSentinelWithoutFeignAnnotation() {
|
||||
Client transport = mock(Client.class);
|
||||
ThirdTransferResultReceiver receiver = PjcloudSentinelFeign.builder().contract(new SpringMvcContract())
|
||||
.client(transport).target(ThirdTransferResultReceiver.class, "http://offline-test");
|
||||
assertTrue(Proxy.isProxyClass(receiver.getClass()));
|
||||
verifyNoInteractions(transport);
|
||||
}
|
||||
|
||||
/** 已声明注解且没有显式降级的客户端继续构造原Sentinel代理。 */
|
||||
@Test
|
||||
void declarativeReceiverKeepsOriginalSentinelProxy() {
|
||||
Client transport = mock(Client.class);
|
||||
StaticReceiver receiver = PjcloudSentinelFeign.builder().contract(new SpringMvcContract())
|
||||
.client(transport).target(StaticReceiver.class, "http://offline-test");
|
||||
assertTrue(Proxy.isProxyClass(receiver.getClass()));
|
||||
verifyNoInteractions(transport);
|
||||
}
|
||||
|
||||
/** 原注解指定的降级实例仍由Feign上下文解析,运输失败后继续执行该降级。 */
|
||||
@Test
|
||||
void declarativeFallbackStillRunsOnTransportFailure() {
|
||||
ApplicationContext context = mock(ApplicationContext.class);
|
||||
FeignClientFactory factory = mock(FeignClientFactory.class);
|
||||
when(context.getBean(FeignClientFactory.class)).thenReturn(factory);
|
||||
when(factory.getInstance("test-result-fallback", ResultFallback.class)).thenReturn(new ResultFallback());
|
||||
PjcloudSentinelFeign.Builder builder = PjcloudSentinelFeign.builder();
|
||||
builder.setApplicationContext(context);
|
||||
FallbackReceiver receiver = builder.contract(new SpringMvcContract()).encoder((value, type, template) -> template.body("{}"))
|
||||
.client((request, options) -> { throw new IOException("offline transport failure"); })
|
||||
.target(FallbackReceiver.class, "http://offline-test");
|
||||
assertTrue(receiver.receive(new ThirdTransferResultNotifyDto()).getData());
|
||||
}
|
||||
|
||||
/** 模拟现有声明式客户端,名称仅用于本测试的Feign上下文。 */
|
||||
@FeignClient(name = "offline-test", contextId = "test-result-static")
|
||||
interface StaticReceiver extends ThirdTransferResultReceiver {
|
||||
}
|
||||
|
||||
/** 模拟现有配置了显式降级的声明式客户端。 */
|
||||
@FeignClient(name = "offline-test", contextId = "test-result-fallback", fallback = ResultFallback.class)
|
||||
interface FallbackReceiver extends ThirdTransferResultReceiver {
|
||||
}
|
||||
|
||||
/** 测试专用降级实现,通过明确响应验证原降级分支仍被调用。 */
|
||||
public static class ResultFallback implements FallbackReceiver {
|
||||
/** 测试降级接收返回明确确认,不创建任务或调用真实服务。 */
|
||||
@Override
|
||||
public R<Boolean> receive(ThirdTransferResultNotifyDto notification) {
|
||||
return R.ok(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
+94
@@ -4,10 +4,13 @@ import com.bsd.pjcloud.common.data.tenant.TenantContextHolder;
|
||||
import com.bsd.pjcloud.thirdparty.constant.pay.ThirdPaymentTaskTypeEnum;
|
||||
import com.bsd.pjcloud.thirdparty.entity.pay.ThirdTransferOrderEntity;
|
||||
import com.bsd.pjcloud.thirdparty.entity.pay.ThirdTransferEventEntity;
|
||||
import com.bsd.pjcloud.thirdparty.entity.pay.ThirdPaymentTaskEntity;
|
||||
import com.bsd.pjcloud.thirdparty.mapper.pay.ThirdPaymentTaskMapper;
|
||||
import com.bsd.pjcloud.thirdparty.mapper.pay.ThirdTransferEventMapper;
|
||||
import com.bsd.pjcloud.thirdparty.mapper.pay.ThirdTransferOrderMapper;
|
||||
import com.bsd.pjcloud.thirdparty.model.transfer.ThirdTransferChannelResult;
|
||||
import com.bsd.pjcloud.thirdparty.model.transfer.ThirdTransferScope;
|
||||
import com.bsd.pjcloud.thirdparty.vo.transfer.ThirdTransferOrderVo;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
@@ -15,6 +18,7 @@ import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import java.util.List;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
import static org.mockito.ArgumentMatchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
@@ -143,6 +147,96 @@ class ThirdTransferOrderStoreTest {
|
||||
verify(orderMapper, never()).update(any(), any());
|
||||
}
|
||||
|
||||
/** 原提交处于退避等待时同时唤醒提交与查单阶段,保持原单号、原任务和累计尝试次数。 */
|
||||
@Test
|
||||
void manualRecoveryWakesOriginalRetryingSubmitAndQuery() {
|
||||
ThirdTransferOrderEntity order = order("UNKNOWN");
|
||||
ThirdTransferScope scope = prepareRecovery(order, task("TRANSFER_SUBMIT", "RETRY"), task("TRANSFER_QUERY", "ALERT"));
|
||||
when(taskMapper.rearmByTaskKey(eq(9L), anyString(), any())).thenReturn(1);
|
||||
when(eventMapper.insert(any(ThirdTransferEventEntity.class))).thenReturn(1);
|
||||
|
||||
assertTrue(store.retryOrder(1L, scope));
|
||||
verify(taskMapper).rearmByTaskKey(eq(9L), eq("TRANSFER_SUBMIT:1"), any());
|
||||
verify(taskMapper).rearmByTaskKey(eq(9L), eq("TRANSFER_QUERY:1"), any());
|
||||
verify(taskMapper, never()).manualRetry(any(), any());
|
||||
verifyNoInteractions(tasks);
|
||||
assertEquals("TF-test", order.getTransferNo());
|
||||
assertEquals("UNKNOWN", order.getStatus());
|
||||
}
|
||||
|
||||
/** 成功及明确失败的资金终态均不能唤醒原提交,防止人工恢复变成新付款。 */
|
||||
@Test
|
||||
void terminalFundsDoNotRearmSubmit() {
|
||||
for (String status : List.of("SUCCESS", "FAILED")) {
|
||||
ThirdTransferOrderEntity order = order(status);
|
||||
ThirdTransferScope scope = prepareRecovery(order, task("TRANSFER_SUBMIT", "FAILED"));
|
||||
assertFalse(store.retryOrder(1L, scope));
|
||||
}
|
||||
verify(taskMapper, never()).rearmByTaskKey(any(), any(), any());
|
||||
verifyNoInteractions(tasks, eventMapper);
|
||||
}
|
||||
|
||||
/** 资金事实冲突时仅恢复核对任务,原提交在行锁内再次阻止执行。 */
|
||||
@Test
|
||||
void conflictingFundsCannotResumeOrBeginSubmit() {
|
||||
ThirdTransferOrderEntity order = order("UNKNOWN");
|
||||
order.setNeedsAttention(true);
|
||||
ThirdTransferScope scope = prepareRecovery(order, task("TRANSFER_SUBMIT", "ALERT"));
|
||||
assertFalse(store.retryOrder(1L, scope));
|
||||
assertThrows(IllegalStateException.class, () -> store.beginSubmit(1L));
|
||||
verify(taskMapper, never()).rearmByTaskKey(any(), any(), any());
|
||||
verify(orderMapper, never()).update(any(), any());
|
||||
verifyNoInteractions(tasks, eventMapper);
|
||||
}
|
||||
|
||||
/** 执行中的任务由条件更新保留活跃租约,界面收到false后提示核对当前执行状态。 */
|
||||
@Test
|
||||
void activeLeaseIsNotForcedIntoAnotherExecution() {
|
||||
ThirdTransferOrderEntity order = order("UNKNOWN");
|
||||
ThirdTransferScope scope = prepareRecovery(order, task("TRANSFER_SUBMIT", "PROCESSING"));
|
||||
when(taskMapper.rearmByTaskKey(eq(9L), eq("TRANSFER_SUBMIT:1"), any())).thenReturn(0);
|
||||
assertFalse(store.retryOrder(1L, scope));
|
||||
verify(taskMapper, never()).manualRetry(any(), any());
|
||||
verifyNoInteractions(tasks, eventMapper);
|
||||
}
|
||||
|
||||
/** 通用任务页按ID恢复时沿用同一权限与原任务键,退避中的提交也能被明确唤醒。 */
|
||||
@Test
|
||||
void singleTaskRecoveryWakesOriginalRetryingSubmit() {
|
||||
ThirdTransferOrderEntity order = order("UNKNOWN");
|
||||
ThirdPaymentTaskEntity task = task("TRANSFER_SUBMIT", "RETRY");
|
||||
ThirdTransferScope scope = new ThirdTransferScope(true, List.of(), null);
|
||||
when(taskMapper.selectById(10L)).thenReturn(task);
|
||||
when(orderMapper.scopeById(1L, scope)).thenReturn(new ThirdTransferOrderVo());
|
||||
when(orderMapper.selectForUpdate(1L)).thenReturn(order);
|
||||
when(taskMapper.rearmByTaskKey(eq(9L), eq("TRANSFER_SUBMIT:1"), any())).thenReturn(1);
|
||||
when(eventMapper.insert(any(ThirdTransferEventEntity.class))).thenReturn(1);
|
||||
assertTrue(store.retryTask(10L, scope));
|
||||
verify(taskMapper, never()).manualRetry(any(), any());
|
||||
verifyNoInteractions(tasks);
|
||||
}
|
||||
|
||||
/** 对同租户已通过完整数据权限的原单模拟锁和任务查询,恢复用例仍调用真实Store规则。 */
|
||||
private ThirdTransferScope prepareRecovery(ThirdTransferOrderEntity order, ThirdPaymentTaskEntity... originalTasks) {
|
||||
ThirdTransferScope scope = new ThirdTransferScope(true, List.of(), null);
|
||||
when(orderMapper.scopeById(1L, scope)).thenReturn(new ThirdTransferOrderVo());
|
||||
when(orderMapper.selectForUpdate(1L)).thenReturn(order);
|
||||
when(taskMapper.selectList(any())).thenReturn(List.of(originalTasks));
|
||||
return scope;
|
||||
}
|
||||
|
||||
/** 构造已持久化原任务,测试只恢复固定任务键。 */
|
||||
private ThirdPaymentTaskEntity task(String type, String status) {
|
||||
ThirdPaymentTaskEntity task = new ThirdPaymentTaskEntity();
|
||||
task.setTenantId(9L);
|
||||
task.setTransferOrderId(1L);
|
||||
task.setTaskType(type);
|
||||
task.setTaskKey(type + ":1");
|
||||
task.setStatus(status);
|
||||
task.setAttemptCount(7);
|
||||
return task;
|
||||
}
|
||||
|
||||
/** 配置可修改状态更新及事件落库成功,避免测试模拟数据库事务。 */
|
||||
private void prepareWrite(ThirdTransferOrderEntity order) {
|
||||
when(orderMapper.selectForUpdate(1L)).thenReturn(order);
|
||||
|
||||
+20
@@ -127,6 +127,26 @@ class ThirdAppPlusAliTransferServiceImplTest {
|
||||
assertFalse(service.query(order()).isNotFound());
|
||||
response.setSubCode("ORDER_NOT_EXIST");
|
||||
assertTrue(service.query(order()).isNotFound());
|
||||
assertTrue(service.query(order()).getErrorMessage().contains("恢复原任务"));
|
||||
response.setCode("40002");
|
||||
assertFalse(service.query(order()).isNotFound());
|
||||
}
|
||||
|
||||
/** IP与签名配置拒绝保留待核实并给出恢复步骤,禁止误判付款失败后生成新指令。 */
|
||||
@Test
|
||||
void gatewayRejectionsDescribeRecoveryWithoutInventingFundsFailure() throws Exception {
|
||||
prepareClient();
|
||||
AlipayFundTransUniTransferResponse response = new AlipayFundTransUniTransferResponse();
|
||||
response.setCode("40002");
|
||||
when(context.execute(any(AlipayFundTransUniTransferRequest.class))).thenReturn(response);
|
||||
for (String code : List.of("isv.illegal-client-ip", "isv.invalid-signature")) {
|
||||
response.setSubCode(code);
|
||||
var result = service.submit(order(), request());
|
||||
assertEquals("UNKNOWN", result.getStatus());
|
||||
assertFalse(result.isNotFound());
|
||||
assertEquals(code, result.getErrorCode());
|
||||
assertTrue(result.getErrorMessage().contains("恢复原转账任务"));
|
||||
}
|
||||
}
|
||||
|
||||
/** 成功响应超分精度或缺金额时继续核对,不能四舍五入。 */
|
||||
|
||||
Reference in new issue
Block a user