|
@@ -63,6 +63,8 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
private static final long REDIS_TTL_MINUTES = 30;
|
|
private static final long REDIS_TTL_MINUTES = 30;
|
|
|
private static final String REDIS_TRACKING_KEY = "payscore:preauth:tracking:";
|
|
private static final String REDIS_TRACKING_KEY = "payscore:preauth:tracking:";
|
|
|
private static final long REDIS_TRACKING_TTL_HOURS = 24;
|
|
private static final long REDIS_TRACKING_TTL_HOURS = 24;
|
|
|
|
|
+ private static final String REDIS_CALLBACK_CACHE_KEY = "payscore:callback:cache:";
|
|
|
|
|
+ private static final long REDIS_CALLBACK_CACHE_TTL_MINUTES = 10;
|
|
|
|
|
|
|
|
private static final BigDecimal DEFAULT_RISK_FUND_AMOUNT = new BigDecimal("99.00");
|
|
private static final BigDecimal DEFAULT_RISK_FUND_AMOUNT = new BigDecimal("99.00");
|
|
|
private static final String DEFAULT_RISK_FUND_NAME = "DEPOSIT";
|
|
private static final String DEFAULT_RISK_FUND_NAME = "DEPOSIT";
|
|
@@ -307,11 +309,13 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
.one();
|
|
.one();
|
|
|
|
|
|
|
|
if (order == null) {
|
|
if (order == null) {
|
|
|
- log.error("[支付分服务] 未找到对应订单 - payScoreOrderId: {}", outOrderNo);
|
|
|
|
|
|
|
+ log.warn("[支付分服务] 订单尚未创建,缓存回调信息等待订单创建后应用 - payScoreOrderId: {}, eventType: {}",
|
|
|
|
|
+ outOrderNo, eventType);
|
|
|
|
|
+ cacheCallbackForLater(outOrderNo, eventType, state);
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
- if ("PAYSCORE.USER_CONFIRMED".equals(eventType)) {
|
|
|
|
|
|
|
+ if ("PAYSCORE.USER_CONFIRM".equals(eventType)) {
|
|
|
order.setPayScoreState(PayScoreState.DOING.getCode());
|
|
order.setPayScoreState(PayScoreState.DOING.getCode());
|
|
|
log.info("[支付分服务] 用户确认使用服务 - orderId: {}", order.getId());
|
|
log.info("[支付分服务] 用户确认使用服务 - orderId: {}", order.getId());
|
|
|
} else if ("PAYSCORE.USER_PAID".equals(eventType)) {
|
|
} else if ("PAYSCORE.USER_PAID".equals(eventType)) {
|
|
@@ -369,6 +373,21 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
orderService.notifyHahaOrderStatusChanged(order.getOrderNo());
|
|
orderService.notifyHahaOrderStatusChanged(order.getOrderNo());
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
|
|
+ // 用户确认后,如果订单已有金额且未支付,说明 ORDER 回调先到达但 complete 失败(或尚未到达),
|
|
|
|
|
+ // 此时支付分订单状态已变为 DOING(可完结),立即重试扣款
|
|
|
|
|
+ if ("PAYSCORE.USER_CONFIRM".equals(eventType)
|
|
|
|
|
+ && order.getTotalAmount() != null
|
|
|
|
|
+ && order.getTotalAmount().compareTo(BigDecimal.ZERO) > 0
|
|
|
|
|
+ && PayStatus.UNPAID.getCode().equals(order.getPayStatus())) {
|
|
|
|
|
+ log.info("[支付分服务] 用户已确认,重试扣款 - orderId: {}, amount: {}元",
|
|
|
|
|
+ order.getId(), order.getTotalAmount());
|
|
|
|
|
+ try {
|
|
|
|
|
+ completePayScoreOrder(order.getId(), order.getTotalAmount(), null);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("[支付分服务] 重试扣款异常 - orderId: {}", order.getId(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
return result;
|
|
return result;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -982,6 +1001,16 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
String redisKey = REDIS_KEY_PREFIX + deviceId + ":" + userId;
|
|
String redisKey = REDIS_KEY_PREFIX + deviceId + ":" + userId;
|
|
|
|
|
+
|
|
|
|
|
+ // 防止覆盖已有的未消费预授权:如果同一设备+用户已有 preauth key,不覆盖
|
|
|
|
|
+ // 新的 outOrderNo 已在 tracking SET 中,后续可通过三层恢复机制找到
|
|
|
|
|
+ String existing = stringRedisTemplate.opsForValue().get(redisKey);
|
|
|
|
|
+ if (existing != null && !existing.isEmpty()) {
|
|
|
|
|
+ log.warn("[支付分服务] Redis中已有未消费的预授权信息,不覆盖 - key: {}, 已有: {}, 新: {}",
|
|
|
|
|
+ redisKey, existing, result.getOutOrderNo());
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
JSONObject json = new JSONObject();
|
|
JSONObject json = new JSONObject();
|
|
|
json.put("payScoreOrderId", result.getOutOrderNo());
|
|
json.put("payScoreOrderId", result.getOutOrderNo());
|
|
|
json.put("payScoreState", result.getState());
|
|
json.put("payScoreState", result.getState());
|
|
@@ -1010,12 +1039,16 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
stringRedisTemplate.delete(redisKey);
|
|
stringRedisTemplate.delete(redisKey);
|
|
|
stringRedisTemplate.opsForSet().remove(REDIS_TRACKING_KEY + order.getUserId(), order.getPayScoreOrderId());
|
|
stringRedisTemplate.opsForSet().remove(REDIS_TRACKING_KEY + order.getUserId(), order.getPayScoreOrderId());
|
|
|
log.debug("[支付分服务] 订单已有支付分信息,清理Redis - orderId: {}", order.getId());
|
|
log.debug("[支付分服务] 订单已有支付分信息,清理Redis - orderId: {}", order.getId());
|
|
|
|
|
+ // 检查是否有早到的微信回调缓存,应用后可能触发扣款
|
|
|
|
|
+ applyCachedCallback(order);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
|
String jsonStr = stringRedisTemplate.opsForValue().get(redisKey);
|
|
String jsonStr = stringRedisTemplate.opsForValue().get(redisKey);
|
|
|
if (jsonStr == null) {
|
|
if (jsonStr == null) {
|
|
|
log.warn("[支付分服务] Redis中未找到预授权信息 - key: {}", redisKey);
|
|
log.warn("[支付分服务] Redis中未找到预授权信息 - key: {}", redisKey);
|
|
|
|
|
+ // 即使 Redis 没命中,也检查是否有缓存的回调(兜底恢复场景)
|
|
|
|
|
+ applyCachedCallback(order);
|
|
|
return;
|
|
return;
|
|
|
}
|
|
}
|
|
|
|
|
|
|
@@ -1038,6 +1071,9 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
// 从追踪 SET 中移除,订单已正常关联不再需要清理
|
|
// 从追踪 SET 中移除,订单已正常关联不再需要清理
|
|
|
stringRedisTemplate.opsForSet().remove(REDIS_TRACKING_KEY + order.getUserId(), payScoreOrderId);
|
|
stringRedisTemplate.opsForSet().remove(REDIS_TRACKING_KEY + order.getUserId(), payScoreOrderId);
|
|
|
log.info("[支付分服务] 预授权信息已关联到订单 - orderId: {}, payScoreOrderId: {}", order.getId(), payScoreOrderId);
|
|
log.info("[支付分服务] 预授权信息已关联到订单 - orderId: {}, payScoreOrderId: {}", order.getId(), payScoreOrderId);
|
|
|
|
|
+
|
|
|
|
|
+ // 检查是否有早到的微信回调缓存,应用后可能触发扣款
|
|
|
|
|
+ applyCachedCallback(order);
|
|
|
} else {
|
|
} else {
|
|
|
log.warn("[支付分服务] 预授权JSON中payScoreOrderId为null - key: {}, json: {}", redisKey, jsonStr);
|
|
log.warn("[支付分服务] 预授权JSON中payScoreOrderId为null - key: {}, json: {}", redisKey, jsonStr);
|
|
|
}
|
|
}
|
|
@@ -1363,7 +1399,11 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
.eq(Order::getPayScoreOrderId, outOrderNo)
|
|
.eq(Order::getPayScoreOrderId, outOrderNo)
|
|
|
.one();
|
|
.one();
|
|
|
if (localOrder == null) {
|
|
if (localOrder == null) {
|
|
|
- // 没有关联的本地订单,可以安全取消(只是预创建的支付分记录)
|
|
|
|
|
|
|
+ // 没有关联的本地订单 — 但微信侧可能已被用户确认,需要查询微信状态
|
|
|
|
|
+ if (isPayScoreOrderConfirmedOnWechat(outOrderNo)) {
|
|
|
|
|
+ log.warn("[支付分服务] 支付分订单在微信侧已确认,不可取消 - outOrderNo: {}", outOrderNo);
|
|
|
|
|
+ return false;
|
|
|
|
|
+ }
|
|
|
return true;
|
|
return true;
|
|
|
}
|
|
}
|
|
|
String state = localOrder.getPayScoreState();
|
|
String state = localOrder.getPayScoreState();
|
|
@@ -1381,4 +1421,113 @@ public class PayScoreServiceImpl implements PayScoreService {
|
|
|
return false;
|
|
return false;
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 查询微信侧支付分订单状态,判断用户是否已确认
|
|
|
|
|
+ * 微信侧状态为 DOING/DONE/USER_PAYING 表示用户已确认,不应取消
|
|
|
|
|
+ *
|
|
|
|
|
+ * @return true 微信侧已确认(不可取消),false 可以安全取消
|
|
|
|
|
+ */
|
|
|
|
|
+ private boolean isPayScoreOrderConfirmedOnWechat(String outOrderNo) {
|
|
|
|
|
+ if (payScoreStrategy == null) {
|
|
|
|
|
+ log.warn("[支付分服务] 支付分策略未初始化,保守认为已确认");
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ PayScoreResult result = payScoreStrategy.queryServiceOrder(outOrderNo);
|
|
|
|
|
+ if (result.isSuccess()) {
|
|
|
|
|
+ String wxState = result.getState();
|
|
|
|
|
+ // CREATED 状态表示用户尚未确认,其他状态均表示已确认
|
|
|
|
|
+ boolean confirmed = wxState != null && !PayScoreState.CREATED.getCode().equals(wxState);
|
|
|
|
|
+ if (confirmed) {
|
|
|
|
|
+ log.info("[支付分服务] 微信侧订单已确认 - outOrderNo: {}, wxState: {}", outOrderNo, wxState);
|
|
|
|
|
+ }
|
|
|
|
|
+ return confirmed;
|
|
|
|
|
+ }
|
|
|
|
|
+ // 查询失败时保守处理:认为已确认,不取消
|
|
|
|
|
+ log.warn("[支付分服务] 查询微信订单状态失败,保守不取消 - outOrderNo: {}, error: {}",
|
|
|
|
|
+ outOrderNo, result.getErrorMsg());
|
|
|
|
|
+ return true;
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("[支付分服务] 查询微信订单状态异常,保守不取消 - outOrderNo: {}", outOrderNo, e);
|
|
|
|
|
+ return true;
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 缓存早到的微信回调信息,等订单创建后再应用
|
|
|
|
|
+ */
|
|
|
|
|
+ private void cacheCallbackForLater(String outOrderNo, String eventType, String state) {
|
|
|
|
|
+ if (stringRedisTemplate == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ try {
|
|
|
|
|
+ String key = REDIS_CALLBACK_CACHE_KEY + outOrderNo;
|
|
|
|
|
+ JSONObject json = new JSONObject();
|
|
|
|
|
+ json.put("eventType", eventType);
|
|
|
|
|
+ json.put("state", state);
|
|
|
|
|
+ stringRedisTemplate.opsForValue().set(key, json.toJSONString(),
|
|
|
|
|
+ REDIS_CALLBACK_CACHE_TTL_MINUTES, TimeUnit.MINUTES);
|
|
|
|
|
+ log.info("[支付分服务] 回调信息已缓存 - payScoreOrderId: {}, eventType: {}, ttl: {}min",
|
|
|
|
|
+ outOrderNo, eventType, REDIS_CALLBACK_CACHE_TTL_MINUTES);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("[支付分服务] 缓存回调信息失败 - outOrderNo: {}", outOrderNo, e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ /**
|
|
|
|
|
+ * 应用缓存的微信回调信息到订单
|
|
|
|
|
+ * 在订单创建后调用,处理微信回调先于 ORC_RESULT 到达的场景
|
|
|
|
|
+ */
|
|
|
|
|
+ private void applyCachedCallback(Order order) {
|
|
|
|
|
+ if (stringRedisTemplate == null || order == null || order.getPayScoreOrderId() == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ String key = REDIS_CALLBACK_CACHE_KEY + order.getPayScoreOrderId();
|
|
|
|
|
+ try {
|
|
|
|
|
+ String cached = stringRedisTemplate.opsForValue().get(key);
|
|
|
|
|
+ if (cached == null) {
|
|
|
|
|
+ return;
|
|
|
|
|
+ }
|
|
|
|
|
+ JSONObject json = JSON.parseObject(cached);
|
|
|
|
|
+ String eventType = json.getString("eventType");
|
|
|
|
|
+ String state = json.getString("state");
|
|
|
|
|
+
|
|
|
|
|
+ if ("PAYSCORE.USER_CONFIRM".equals(eventType)) {
|
|
|
|
|
+ // 用户已确认,更新订单状态为 DOING
|
|
|
|
|
+ order.setPayScoreState(PayScoreState.DOING.getCode());
|
|
|
|
|
+ order.setPayScoreFailReason(null);
|
|
|
|
|
+ orderService.updateById(order);
|
|
|
|
|
+ log.info("[支付分服务] 从缓存回调恢复用户确认状态 - orderId: {}, payScoreOrderId: {}",
|
|
|
|
|
+ order.getId(), order.getPayScoreOrderId());
|
|
|
|
|
+
|
|
|
|
|
+ // 如果订单已有金额且未支付,立即触发扣款
|
|
|
|
|
+ if (order.getTotalAmount() != null
|
|
|
|
|
+ && order.getTotalAmount().compareTo(BigDecimal.ZERO) > 0
|
|
|
|
|
+ && PayStatus.UNPAID.getCode().equals(order.getPayStatus())) {
|
|
|
|
|
+ log.info("[支付分服务] 缓存回调恢复后触发扣款 - orderId: {}, amount: {}元",
|
|
|
|
|
+ order.getId(), order.getTotalAmount());
|
|
|
|
|
+ try {
|
|
|
|
|
+ completePayScoreOrder(order.getId(), order.getTotalAmount(), null);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.error("[支付分服务] 缓存回调恢复后扣款异常 - orderId: {}", order.getId(), e);
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ } else {
|
|
|
|
|
+ log.info("[支付分服务] 缓存回调事件类型无需应用 - payScoreOrderId: {}, eventType: {}",
|
|
|
|
|
+ order.getPayScoreOrderId(), eventType);
|
|
|
|
|
+ }
|
|
|
|
|
+
|
|
|
|
|
+ // 删除缓存,防止重复应用
|
|
|
|
|
+ stringRedisTemplate.delete(key);
|
|
|
|
|
+ } catch (Exception e) {
|
|
|
|
|
+ log.warn("[支付分服务] 应用缓存回调失败 - payScoreOrderId: {}, orderId: {}",
|
|
|
|
|
+ order.getPayScoreOrderId(), order.getId(), e);
|
|
|
|
|
+ // 失败时也尝试删除缓存,避免反复失败
|
|
|
|
|
+ try {
|
|
|
|
|
+ stringRedisTemplate.delete(key);
|
|
|
|
|
+ } catch (Exception ignored) {
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
|
|
+ }
|
|
|
}
|
|
}
|