|
|
@@ -9,10 +9,8 @@ import com.haha.common.enums.PayStatus;
|
|
|
import com.haha.common.enums.PaymentChannel;
|
|
|
import com.haha.entity.Order;
|
|
|
import com.haha.entity.User;
|
|
|
-import com.haha.entity.DoorRecord;
|
|
|
import com.haha.service.OrderService;
|
|
|
import com.haha.service.UserService;
|
|
|
-import com.haha.service.DoorRecordService;
|
|
|
import com.haha.service.payment.config.WxPayConfig;
|
|
|
import com.haha.service.payment.payscore.*;
|
|
|
import com.haha.service.payment.payscore.PayScoreCreateRequest.PostPayment;
|
|
|
@@ -49,9 +47,6 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
@Autowired
|
|
|
private UserService userService;
|
|
|
|
|
|
- @Autowired
|
|
|
- private DoorRecordService doorRecordService;
|
|
|
-
|
|
|
@Autowired(required = false)
|
|
|
private WxPayConfig wxPayConfig;
|
|
|
|
|
|
@@ -750,8 +745,8 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
return errorData;
|
|
|
}
|
|
|
|
|
|
- // 取消用户旧的进行中支付分订单(DB + Redis追踪的 + 暴力清理)
|
|
|
- cancelUserInProgressPayScoreOrders(userId, deviceId);
|
|
|
+ // 清理该用户旧的进行中支付分订单(DB中的 + 同设备Redis追踪的)
|
|
|
+ cancelStalePayScoreOrders(userId, deviceId);
|
|
|
|
|
|
// 如果没有传 openId,从用户表中获取
|
|
|
if (openId == null || openId.isEmpty()) {
|
|
|
@@ -765,20 +760,6 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
openId = user.getOpenid();
|
|
|
}
|
|
|
|
|
|
- // 尝试创建,失败时如果是"进行中订单过多"则暴力清理后重试
|
|
|
- Map<String, Object> result = doCreatePayScoreOrder(userId, deviceId, openId);
|
|
|
- if (!Boolean.TRUE.equals(result.get("success"))) {
|
|
|
- String errorMsg = (String) result.get("errorMsg");
|
|
|
- if (errorMsg != null && (errorMsg.contains("进行中") || errorMsg.contains("过多"))) {
|
|
|
- log.warn("[支付分服务] 首次创建失败(进行中订单过多),尝试暴力清理 - userId: {}", userId);
|
|
|
- bruteForceCancelRecentOrders(userId, deviceId);
|
|
|
- result = doCreatePayScoreOrder(userId, deviceId, openId);
|
|
|
- }
|
|
|
- }
|
|
|
- return result;
|
|
|
- }
|
|
|
-
|
|
|
- private Map<String, Object> doCreatePayScoreOrder(Long userId, String deviceId, String openId) {
|
|
|
String outOrderNo = generatePreAuthOutOrderNo(deviceId);
|
|
|
|
|
|
PayScoreCreateRequest request = new PayScoreCreateRequest();
|
|
|
@@ -795,7 +776,8 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
if (result.isSuccess()) {
|
|
|
trackPreAuthOrder(userId, outOrderNo);
|
|
|
storePreAuthToRedis(deviceId, userId, result);
|
|
|
- untrackPreAuthOrdersByDevice(userId, deviceId, outOrderNo);
|
|
|
+ // 取消同设备旧的追踪订单(已被本次新订单替代)
|
|
|
+ cancelOldDeviceOrders(userId, deviceId, outOrderNo);
|
|
|
|
|
|
Map<String, Object> data = new HashMap<>();
|
|
|
data.put("success", true);
|
|
|
@@ -871,10 +853,11 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 取消用户所有进行中的支付分订单(DB订单 + Redis追踪的预创建订单 + 暴力扫描)
|
|
|
+ * 清理用户旧的进行中支付分订单
|
|
|
+ * 生产环境下回调正常运转后,此方法只在用户多次快速扫码(测试/异常)时触发
|
|
|
*/
|
|
|
- private void cancelUserInProgressPayScoreOrders(Long userId, String deviceId) {
|
|
|
- // 1. 取消 DB 中的进行中支付分订单
|
|
|
+ private void cancelStalePayScoreOrders(Long userId, String deviceId) {
|
|
|
+ // 1. 取消 DB 中该用户进行中的支付分订单
|
|
|
try {
|
|
|
List<Order> inProgressOrders = orderService.lambdaQuery()
|
|
|
.eq(Order::getUserId, userId)
|
|
|
@@ -885,27 +868,25 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
.list();
|
|
|
|
|
|
if (inProgressOrders != null && !inProgressOrders.isEmpty()) {
|
|
|
- log.info("[支付分服务] 发现用户 {} 有 {} 笔DB中的进行中支付分订单,开始清理",
|
|
|
- userId, inProgressOrders.size());
|
|
|
+ log.info("[支付分服务] 清理用户 {} 的 {} 笔DB进行中订单", userId, inProgressOrders.size());
|
|
|
for (Order order : inProgressOrders) {
|
|
|
- cancelSingleByOutOrderNo(order.getPayScoreOrderId(), order.getId());
|
|
|
+ cancelPayScoreOrderByOutOrderNo(order.getPayScoreOrderId(), order.getId());
|
|
|
}
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
log.error("[支付分服务] 清理DB进行中订单失败 - userId: {}", userId, e);
|
|
|
}
|
|
|
|
|
|
- // 2. 取消 Redis 追踪 SET 中记录的预创建订单
|
|
|
+ // 2. 取消 Redis 追踪的预创建订单(之前未完成购物流程留下的)
|
|
|
if (stringRedisTemplate != null) {
|
|
|
try {
|
|
|
String trackingKey = REDIS_TRACKING_KEY + userId;
|
|
|
java.util.Set<String> trackedOrders = stringRedisTemplate.opsForSet().members(trackingKey);
|
|
|
if (trackedOrders != null && !trackedOrders.isEmpty()) {
|
|
|
- log.info("[支付分服务] 发现用户 {} 有 {} 笔Redis追踪的预创建订单,开始清理",
|
|
|
- userId, trackedOrders.size());
|
|
|
+ log.info("[支付分服务] 清理用户 {} 的 {} 笔追踪预创建订单", userId, trackedOrders.size());
|
|
|
for (String outOrderNo : trackedOrders) {
|
|
|
if (outOrderNo != null && !outOrderNo.isEmpty()) {
|
|
|
- cancelSingleByOutOrderNo(outOrderNo, null);
|
|
|
+ cancelPayScoreOrderByOutOrderNo(outOrderNo, null);
|
|
|
stringRedisTemplate.opsForSet().remove(trackingKey, outOrderNo);
|
|
|
}
|
|
|
}
|
|
|
@@ -914,22 +895,17 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
log.error("[支付分服务] 清理Redis追踪订单失败 - userId: {}", userId, e);
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
}
|
|
|
|
|
|
- /**
|
|
|
- * 通过 outOrderNo 调用微信API取消单个支付分订单
|
|
|
- */
|
|
|
- private void cancelSingleByOutOrderNo(String outOrderNo, Long localOrderId) {
|
|
|
+ private void cancelPayScoreOrderByOutOrderNo(String outOrderNo, Long localOrderId) {
|
|
|
try {
|
|
|
PayScoreCancelRequest cancelReq = new PayScoreCancelRequest();
|
|
|
cancelReq.setOutOrderNo(outOrderNo);
|
|
|
- cancelReq.setReason("自动取消:创建新订单前清理旧订单");
|
|
|
+ cancelReq.setReason("新订单创建前自动取消旧订单");
|
|
|
PayScoreResult cancelResult = payScoreStrategy.cancelServiceOrder(cancelReq);
|
|
|
|
|
|
if (cancelResult.isSuccess()) {
|
|
|
- log.info("[支付分服务] 取消成功 - outOrderNo: {}, localOrderId: {}", outOrderNo, localOrderId);
|
|
|
- // 如果有本地订单,同步更新状态
|
|
|
+ log.info("[支付分服务] 取消成功 - outOrderNo: {}", outOrderNo);
|
|
|
if (localOrderId != null) {
|
|
|
Order order = orderService.getById(localOrderId);
|
|
|
if (order != null) {
|
|
|
@@ -937,126 +913,14 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
orderService.updateById(order);
|
|
|
}
|
|
|
}
|
|
|
- } else {
|
|
|
- // ORDER_NOT_EXIST 表示微信侧已不存在,忽略
|
|
|
- if (cancelResult.getErrorMsg() != null && cancelResult.getErrorMsg().contains("ORDER_NOT_EXIST")) {
|
|
|
- log.debug("[支付分服务] 订单在微信侧已不存在 - outOrderNo: {}", outOrderNo);
|
|
|
- } else {
|
|
|
- log.debug("[支付分服务] 取消失败 - outOrderNo: {}, error: {}",
|
|
|
- outOrderNo, cancelResult.getErrorMsg());
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (Exception e) {
|
|
|
- log.debug("[支付分服务] 取消异常 - outOrderNo: {}, error: {}", outOrderNo, e.getMessage());
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- /**
|
|
|
- * 暴力扫描并取消最近一段时间内的预创建订单
|
|
|
- * 当微信返回"进行中订单过多"时,尝试用不同时间戳构造 outOrderNo 并取消
|
|
|
- * 扫描该用户所有历史设备 + 当前设备,时间窗口 4 小时
|
|
|
- */
|
|
|
- private void bruteForceCancelRecentOrders(Long userId, String deviceId) {
|
|
|
- if (payScoreStrategy == null) {
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 收集所有可能的设备ID后缀
|
|
|
- java.util.Set<String> suffixes = new java.util.HashSet<>();
|
|
|
- addSuffix(suffixes, deviceId);
|
|
|
-
|
|
|
- // 从 DB 中查找该用户关联过的所有设备
|
|
|
- try {
|
|
|
- java.util.Set<String> dbDevices = new java.util.HashSet<>();
|
|
|
- List<Order> orders = orderService.lambdaQuery()
|
|
|
- .eq(Order::getUserId, userId)
|
|
|
- .select(Order::getDeviceId)
|
|
|
- .isNotNull(Order::getDeviceId)
|
|
|
- .list();
|
|
|
- if (orders != null) {
|
|
|
- for (Order o : orders) {
|
|
|
- if (o.getDeviceId() != null) {
|
|
|
- dbDevices.add(o.getDeviceId());
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- List<DoorRecord> records = doorRecordService.lambdaQuery()
|
|
|
- .eq(DoorRecord::getUserId, userId)
|
|
|
- .select(DoorRecord::getDeviceId)
|
|
|
- .isNotNull(DoorRecord::getDeviceId)
|
|
|
- .list();
|
|
|
- if (records != null) {
|
|
|
- for (DoorRecord r : records) {
|
|
|
- if (r.getDeviceId() != null) {
|
|
|
- dbDevices.add(r.getDeviceId());
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- for (String devId : dbDevices) {
|
|
|
- addSuffix(suffixes, devId);
|
|
|
}
|
|
|
} catch (Exception e) {
|
|
|
- log.debug("[支付分服务] 查询用户设备列表异常: {}", e.getMessage());
|
|
|
- }
|
|
|
-
|
|
|
- log.info("[支付分服务] 暴力扫描 {} 个设备后缀", suffixes.size());
|
|
|
-
|
|
|
- long nowSec = System.currentTimeMillis() / 1000;
|
|
|
- int cancelledCount = 0;
|
|
|
- int totalAttempts = 0;
|
|
|
-
|
|
|
- // 高精度扫描最近5分钟(每1秒),覆盖密集重试场景
|
|
|
- // 之后每15秒扫描到1小时,覆盖较早的孤儿订单
|
|
|
- int[][] scanStages = {
|
|
|
- {0, 300, 1}, // 0-5min, 每1秒 (300次/设备)
|
|
|
- {300, 3600, 15}, // 5min-1h, 每15秒 (220次/设备)
|
|
|
- };
|
|
|
-
|
|
|
- for (int[] stage : scanStages) {
|
|
|
- int startSec = stage[0];
|
|
|
- int endSec = stage[1];
|
|
|
- int interval = stage[2];
|
|
|
-
|
|
|
- for (int s = startSec; s < endSec; s += interval) {
|
|
|
- long ts = nowSec - s;
|
|
|
- for (String suffix : suffixes) {
|
|
|
- totalAttempts++;
|
|
|
- String outOrderNo = "PS" + ts + suffix;
|
|
|
- try {
|
|
|
- PayScoreCancelRequest cancelReq = new PayScoreCancelRequest();
|
|
|
- cancelReq.setOutOrderNo(outOrderNo);
|
|
|
- cancelReq.setReason("自动清理孤儿订单");
|
|
|
- PayScoreResult cancelResult = payScoreStrategy.cancelServiceOrder(cancelReq);
|
|
|
- if (cancelResult.isSuccess()) {
|
|
|
- cancelledCount++;
|
|
|
- if (cancelledCount <= 5) {
|
|
|
- log.info("[支付分服务] 暴力清理成功 - outOrderNo: {}", outOrderNo);
|
|
|
- }
|
|
|
- }
|
|
|
- } catch (Exception ignored) {
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- if (cancelledCount > 0) {
|
|
|
- log.info("[支付分服务] 暴力清理完成 - 成功取消 {} 笔孤儿订单 (总尝试 {} 次)",
|
|
|
- cancelledCount, totalAttempts);
|
|
|
- } else {
|
|
|
- log.info("[支付分服务] 暴力清理完成 - 未找到孤儿订单 (总尝试 {} 次)", totalAttempts);
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void addSuffix(java.util.Set<String> suffixes, String deviceId) {
|
|
|
- if (deviceId == null || deviceId.isEmpty()) {
|
|
|
- return;
|
|
|
+ log.debug("[支付分服务] 取消订单异常(可能微信侧已不存在) - outOrderNo: {}", outOrderNo);
|
|
|
}
|
|
|
- String safe = deviceId.replaceAll("[^A-Za-z0-9]", "");
|
|
|
- String suffix = safe.length() > 10 ? safe.substring(safe.length() - 10) : safe;
|
|
|
- suffixes.add(suffix);
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 将预创建的 outOrderNo 记录到 Redis SET 中,防止成为孤儿订单
|
|
|
+ * 将预创建的 outOrderNo 记录到 Redis SET,回调正常关联后自动移除
|
|
|
*/
|
|
|
private void trackPreAuthOrder(Long userId, String outOrderNo) {
|
|
|
if (stringRedisTemplate == null) {
|
|
|
@@ -1072,9 +936,9 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
}
|
|
|
|
|
|
/**
|
|
|
- * 清除同一设备旧的追踪记录(同一设备的新订单会替代旧订单)
|
|
|
+ * 取消同设备旧的预创建订单(已被本次新订单替代)
|
|
|
*/
|
|
|
- private void untrackPreAuthOrdersByDevice(Long userId, String deviceId, String currentOutOrderNo) {
|
|
|
+ private void cancelOldDeviceOrders(Long userId, String deviceId, String currentOutOrderNo) {
|
|
|
if (stringRedisTemplate == null) {
|
|
|
return;
|
|
|
}
|
|
|
@@ -1086,7 +950,7 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
if (tracked != null) {
|
|
|
for (String outOrderNo : tracked) {
|
|
|
if (outOrderNo != null && outOrderNo.endsWith(suffix) && !outOrderNo.equals(currentOutOrderNo)) {
|
|
|
- cancelSingleByOutOrderNo(outOrderNo, null);
|
|
|
+ cancelPayScoreOrderByOutOrderNo(outOrderNo, null);
|
|
|
stringRedisTemplate.opsForSet().remove(trackingKey, outOrderNo);
|
|
|
}
|
|
|
}
|