IWxPayService.java 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558
  1. package beilv.wx.pay.service;
  2. import beilv.carinformation.mapper.CarInformationMapper;
  3. import beilv.common.core.domain.AjaxResult;
  4. import beilv.competition.mapper.CompetitionMapper;
  5. import beilv.order.domain.StoreOrder;
  6. import beilv.order.domain.StoreRefund;
  7. import beilv.order.mapper.StoreOrderMapper;
  8. import beilv.order.mapper.StoreRefundMapper;
  9. import beilv.system.domain.SysMember;
  10. import beilv.system.service.ISysMemberService;
  11. import beilv.vipCardLog.domain.VipCardLog;
  12. import beilv.vipCardLog.service.IVipCardLogService;
  13. import beilv.wx.pay.config.WxPayProperties;
  14. import beilv.wx.pay.domain.vo.AppPayParam;
  15. import beilv.wx.pay.domain.vo.AppRefundParam;
  16. import com.github.binarywang.wxpay.bean.notify.SignatureHeader;
  17. import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Response;
  18. import com.github.binarywang.wxpay.bean.notify.WxPayNotifyV3Result;
  19. import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyV3Result;
  20. import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryRequest;
  21. import com.github.binarywang.wxpay.bean.request.WxPayOrderQueryV3Request;
  22. import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
  23. import com.github.binarywang.wxpay.bean.request.WxPayUnifiedOrderV3Request;
  24. import com.github.binarywang.wxpay.bean.result.BaseWxPayResult;
  25. import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryResult;
  26. import com.github.binarywang.wxpay.bean.result.WxPayOrderQueryV3Result;
  27. import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
  28. import com.github.binarywang.wxpay.bean.result.WxPayUnifiedOrderV3Result.JsapiResult;
  29. import com.github.binarywang.wxpay.bean.result.enums.TradeTypeEnum;
  30. import com.github.binarywang.wxpay.constant.WxPayConstants;
  31. import com.github.binarywang.wxpay.exception.WxPayException;
  32. import com.github.binarywang.wxpay.service.WxPayService;
  33. import lombok.AllArgsConstructor;
  34. import lombok.extern.slf4j.Slf4j;
  35. import org.apache.commons.lang3.ObjectUtils;
  36. import org.apache.commons.lang3.StringUtils;
  37. import org.springframework.beans.factory.annotation.Autowired;
  38. import org.springframework.data.redis.core.StringRedisTemplate;
  39. import org.springframework.http.ResponseEntity;
  40. import org.springframework.stereotype.Service;
  41. import javax.servlet.http.HttpServletRequest;
  42. import java.math.BigDecimal;
  43. import java.math.RoundingMode;
  44. import java.text.SimpleDateFormat;
  45. import java.util.Date;
  46. import java.util.Set;
  47. import java.util.UUID;
  48. @Slf4j
  49. @Service
  50. @AllArgsConstructor
  51. public class IWxPayService {
  52. @Autowired
  53. private CarInformationMapper carInformationMapper;
  54. @Autowired
  55. private StoreOrderMapper storeOrderMapper;
  56. @Autowired
  57. private CompetitionMapper competitionMapper;
  58. @Autowired
  59. private WxPayProperties wxPayProperties;
  60. @Autowired
  61. private ISysMemberService sysMemberService;
  62. @Autowired
  63. private StoreRefundMapper storeRefundMapper;
  64. @Autowired
  65. private StringRedisTemplate redisTemplate;
  66. @Autowired
  67. private IVipCardLogService vipCardLogService;
  68. private WxPayService wxService;
  69. // Redis中订单延迟队列的key前缀
  70. private static final String ORDER_DELAY_QUEUE_PREFIX = "order:delay:queue:";
  71. // 订单超时时间(5分钟)
  72. private static final long ORDER_TIMEOUT_SECONDS = 5 * 60;
  73. /**
  74. * 统一下单接口
  75. *
  76. * @return 预支付交易会话标识
  77. */
  78. public JsapiResult unifiedOrder(AppPayParam param) throws Exception {
  79. log.info("开始处理统一下单请求,订单号: {}", param.getOrderId());
  80. // 幂等性校验 - 检查订单是否已存在且已支付
  81. StoreOrder existingOrder = storeOrderMapper.selectStoreOrderByOrderId(param.getOrderId());
  82. if (existingOrder != null && existingOrder.getPaid() != null && existingOrder.getPaid() == 1) {
  83. log.warn("订单 {} 已支付,不能重复下单", param.getOrderId());
  84. throw new Exception("订单已支付,不能重复下单");
  85. }
  86. SysMember user = getSysMember(param.getUserId());
  87. param.setSysMember(user);
  88. //微信支付需要的参数
  89. WxPayUnifiedOrderV3Request request = new WxPayUnifiedOrderV3Request();
  90. request.setAppid(wxPayProperties.getAppId());
  91. request.setMchid(wxPayProperties.getMchId());
  92. request.setDescription(param.getDescription());
  93. request.setOutTradeNo(param.getOrderId());
  94. request.setNotifyUrl(wxPayProperties.getNotifyUrl());
  95. WxPayUnifiedOrderV3Request.Amount amount = new WxPayUnifiedOrderV3Request.Amount();
  96. amount.setTotal(yuanToFen(param.getPayPrice()));
  97. amount.setCurrency("CNY");
  98. request.setAmount(amount);
  99. request.setPayer(new WxPayUnifiedOrderV3Request.Payer().setOpenid(user.getOpenId()));
  100. log.info("调用微信统一下单接口,订单号: {}", param.getOrderId());
  101. JsapiResult jsapiResult = this.wxService.createOrderV3(TradeTypeEnum.JSAPI, request);
  102. if (StringUtils.isNotEmpty(jsapiResult.getPackageValue())) {
  103. log.info("微信统一下单成功,订单号: {},开始创建本地订单", param.getOrderId());
  104. //成功之后创建订单
  105. generateOrder(param, jsapiResult.getPackageValue());
  106. //将订单添加到延迟队列中
  107. addOrderToDelayQueue(param.getOrderId());
  108. log.info("订单创建完成并加入延迟队列,订单号: {}", param.getOrderId());
  109. } else {
  110. log.warn("微信统一下单失败,订单号: {}", param.getOrderId());
  111. }
  112. return jsapiResult;
  113. }
  114. //获取当前用户信息
  115. private SysMember getSysMember(Long userId) {
  116. log.debug("获取用户信息,用户ID: {}", userId);
  117. return sysMemberService.selectSysMemberById(userId);
  118. }
  119. /**
  120. * 元转分(不四舍五入,直接截取)
  121. *
  122. * @param amount 金额(元)
  123. * @return 分(整数)
  124. */
  125. public static int yuanToFen(BigDecimal amount) {
  126. if (amount == null) return 0;
  127. return amount.multiply(BigDecimal.valueOf(100))
  128. .setScale(0, RoundingMode.DOWN) // 不四舍五入
  129. .intValueExact();
  130. }
  131. /**
  132. * 统一下单接口
  133. *
  134. * @return 预支付交易会话标识
  135. */
  136. private void generateOrder(AppPayParam param, String payId) {
  137. log.info("开始生成订单,订单号: {}", param.getOrderId());
  138. //查看是否已经生成订单,但是未支付
  139. if (!isOrderInDelayQueue(param.getOrderId())) {
  140. // 订单不存在,创建新订单
  141. log.info("订单不存在,创建新订单,订单号: {}", param.getOrderId());
  142. StoreOrder storeOrder = new StoreOrder();
  143. storeOrder.setOrderId(param.getOrderId());
  144. storeOrder.setUid(param.getSysMember().getId());
  145. storeOrder.setRealName(param.getSysMember().getUsername());
  146. storeOrder.setUserPhone(param.getSysMember().getMobile());
  147. storeOrder.setUserAddress(param.getSysMember().getAddress());
  148. storeOrder.setTotalNum(1);
  149. storeOrder.setTotalPrice(param.getTotalPrice());
  150. storeOrder.setPayPrice(param.getPayPrice());
  151. storeOrder.setPayTime(new Date());
  152. storeOrder.setPayId(payId);
  153. storeOrder.setCreateTime(new Date());
  154. storeOrderMapper.insertStoreOrder(storeOrder);
  155. log.info("新订单创建成功,订单号: {}", param.getOrderId());
  156. } else {
  157. StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(param.getOrderId());
  158. // 订单已存在,更新支付相关信息
  159. log.info("订单已存在,更新支付信息,订单号: {}", param.getOrderId());
  160. order.setPayTime(new Date());
  161. order.setPayId(payId);
  162. storeOrderMapper.updateStoreOrder(order);
  163. log.info("订单支付信息更新完成,订单号: {}", param.getOrderId());
  164. }
  165. }
  166. /**
  167. * 将订单添加到延迟队列
  168. *
  169. * @param orderId 订单ID
  170. */
  171. private void addOrderToDelayQueue(String orderId) {
  172. log.info("将订单添加到延迟队列,订单号: {}", orderId);
  173. // 计算订单超时时间戳(当前时间+5分钟)
  174. long expireTime = System.currentTimeMillis() + (ORDER_TIMEOUT_SECONDS * 1000);
  175. // 将订单添加到Redis有序集合中,以超时时间戳作为score
  176. redisTemplate.opsForZSet().add(ORDER_DELAY_QUEUE_PREFIX + "orders", orderId, expireTime);
  177. log.info("订单 {} 已添加到延迟队列,超时时间: {}", orderId, new Date(expireTime));
  178. }
  179. /**
  180. * 从延迟队列中移除订单(支付成功时调用)
  181. *
  182. * @param orderId 订单ID
  183. */
  184. private void removeOrderFromDelayQueue(String orderId) {
  185. log.info("从延迟队列中移除订单,订单号: {}", orderId);
  186. // 从Redis有序集合中移除订单
  187. redisTemplate.opsForZSet().remove(ORDER_DELAY_QUEUE_PREFIX + "orders", orderId);
  188. log.info("订单 {} 已从延迟队列中移除", orderId);
  189. }
  190. //生成退款订单号
  191. public String generateOutTradeNo() {
  192. String time = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
  193. String random = String.valueOf((int) ((Math.random() * 9 + 1) * 100000)); // 6位随机
  194. return "REF" + time + random;
  195. }
  196. /**
  197. * 处理支付结果通知
  198. *
  199. * @return 处理结果
  200. */
  201. public AjaxResult handleNotify(String notifyData, HttpServletRequest request) {
  202. log.info("收到微信支付回调通知");
  203. SignatureHeader header = getRequestHeader(request);
  204. try {
  205. WxPayNotifyV3Result res = this.wxService.parseOrderNotifyV3Result(notifyData, header);
  206. WxPayNotifyV3Result.DecryptNotifyResult decryptRes = res.getResult();
  207. log.info("解析微信支付回调结果,订单号: {}, 交易状态: {}", decryptRes.getOutTradeNo(), decryptRes.getTradeState());
  208. if (WxPayConstants.WxpayTradeStatus.SUCCESS.equals(decryptRes.getTradeState())) {
  209. log.info("支付成功,开始处理订单,订单号: {}", decryptRes.getOutTradeNo());
  210. // 成功处理订单
  211. processOrderPayment(decryptRes.getOutTradeNo());
  212. //调用充值记录服务,更新充值记录状态
  213. VipCardLog vipCardLog = new VipCardLog();
  214. vipCardLog.setId(decryptRes.getOutTradeNo());
  215. vipCardLog.setPaymentStatus("payment_status_have_paid");
  216. vipCardLogService.updateVipCardLogByOrderId(vipCardLog);
  217. // 成功返回200/204,body无需有内容
  218. log.info("订单处理完成,订单号: {}", decryptRes.getOutTradeNo());
  219. return AjaxResult.success();
  220. } else {
  221. log.warn("支付失败,订单号: {},状态: {}", decryptRes.getOutTradeNo(), decryptRes.getTradeState());
  222. // 失败返回4xx或5xx,且需要构造body信息
  223. return AjaxResult.error("订单支付失败");
  224. }
  225. } catch (WxPayException e) {
  226. log.error("处理微信支付回调失败", e);
  227. // 失败返回4xx或5xx,且需要构造body信息
  228. return AjaxResult.error("微信支付回调失败");
  229. }
  230. }
  231. /**
  232. * 处理订单支付完成后的业务逻辑
  233. *
  234. * @param orderId 订单号
  235. */
  236. private void processOrderPayment(String orderId) {
  237. log.info("开始处理订单支付完成逻辑,订单号: {}", orderId);
  238. // 使用Redis分布式锁防止并发处理
  239. String lockKey = "payment:lock:" + orderId;
  240. Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", java.time.Duration.ofSeconds(30));
  241. if (Boolean.FALSE.equals(lockAcquired)) {
  242. log.warn("订单正在被其他线程处理,订单号: {}", orderId);
  243. return;
  244. }
  245. try {
  246. StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(orderId);
  247. if (order != null) {
  248. // 检查订单是否已被取消
  249. if (order.getStatus() != null && order.getStatus() == 3) {
  250. log.warn("订单已超时取消,支付回调到达,订单号: {}", orderId);
  251. // TODO: 这里应该调用退款接口,将钱退回给用户
  252. return;
  253. }
  254. // 检查订单是否已经处理过
  255. if (order.getPaid() == null || order.getPaid() != 1) {
  256. log.info("更新订单状态为已支付,订单号: {}", orderId);
  257. // 更新订单状态
  258. order.setPaid(1); // 设置为已支付
  259. order.setStatus(1); // 设置订单状态为已支付
  260. order.setPayTime(new Date()); // 设置支付时间
  261. storeOrderMapper.updateStoreOrder(order);
  262. // 从延迟队列中移除订单
  263. removeOrderFromDelayQueue(orderId);
  264. log.info("订单状态更新完成,订单号: {}", orderId);
  265. } else {
  266. log.info("订单已处理过,无需重复处理,订单号: {}", orderId);
  267. }
  268. } else {
  269. log.warn("订单不存在,订单号: {}", orderId);
  270. }
  271. } finally {
  272. // 释放锁
  273. redisTemplate.delete(lockKey);
  274. }
  275. }
  276. /**
  277. * 微信支付-申请退款
  278. *
  279. */
  280. public WxPayRefundV3Result refund(AppRefundParam param) throws WxPayException {
  281. log.info("开始处理退款请求,订单号: {}", param.getOrderId());
  282. // 检查订单是否存在
  283. StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(param.getOrderId());
  284. if (order == null) {
  285. log.warn("退款失败,订单不存在,订单号: {}", param.getOrderId());
  286. throw new WxPayException("订单不存在");
  287. }
  288. // 检查订单是否已支付
  289. if (order.getPaid() == null || order.getPaid() != 1) {
  290. log.warn("退款失败,订单未支付,订单号: {}", param.getOrderId());
  291. throw new WxPayException("订单未支付,无法退款");
  292. }
  293. // 检查订单是否已退款
  294. if (order.getRefundStatus() != null && order.getRefundStatus() == 2) {
  295. log.warn("退款失败,订单已退款,订单号: {}", param.getOrderId());
  296. throw new WxPayException("订单已退款");
  297. }
  298. // 检查退款金额是否合法
  299. if (param.getRefundAmount().compareTo(order.getPayPrice()) > 0) {
  300. log.warn("退款失败,退款金额大于支付金额,订单号: {}", param.getOrderId());
  301. throw new WxPayException("退款金额不能大于支付金额");
  302. }
  303. WxPayRefundV3Request request = new WxPayRefundV3Request();
  304. //商户退款单号
  305. request.setOutRefundNo(param.getOrderId());
  306. //原支付商户订单号
  307. request.setOutTradeNo(param.getOrderId());
  308. //退款原因说明
  309. request.setReason(param.getReason());
  310. WxPayRefundV3Request.Amount amount = new WxPayRefundV3Request.Amount();
  311. //原订单实际支付金额(单位:分)
  312. amount.setTotal(yuanToFen(param.getTotalPrice()));
  313. //退款金额(单位:分)
  314. amount.setRefund(yuanToFen(param.getRefundAmount()));
  315. //货币类型,默认 "CNY"
  316. amount.setCurrency("CNY");
  317. request.setAmount(amount);
  318. request.setNotifyUrl(wxPayProperties.getRefundNotifyUrl());
  319. param.setSysMember(getSysMember(param.getUserId()));
  320. // 检查是否已存在退款记录
  321. StoreRefund existingRefund = storeRefundMapper.selectStoreRefundByOrderId(param.getOrderId());
  322. if (existingRefund != null && "2".equals(existingRefund.getRefundStatus())) {
  323. log.warn("退款失败,退款记录已存在且已完成,订单号: {}", param.getOrderId());
  324. throw new WxPayException("该订单已退款");
  325. }
  326. // 先调用微信退款接口
  327. log.info("调用微信退款接口,订单号: {}", param.getOrderId());
  328. WxPayRefundV3Result result = this.wxService.refundV3(request);
  329. log.info("微信退款接口调用成功,订单号: {}", param.getOrderId());
  330. // 微信退款成功后,再创建本地退款记录
  331. if (existingRefund == null) {
  332. log.info("微信退款成功,创建本地退款记录,订单号: {}", param.getOrderId());
  333. refundOrder(param);
  334. }
  335. return result;
  336. }
  337. /**
  338. * 统一退款接口
  339. *
  340. * @return 预支付交易会话标识
  341. */
  342. private void refundOrder(AppRefundParam param) {
  343. log.info("生成退款订单记录,订单号: {}", param.getOrderId());
  344. StoreRefund storeRefund = new StoreRefund();
  345. storeRefund.setUid(param.getSysMember().getId());
  346. storeRefund.setRefundNo(generateOutTradeNo());
  347. storeRefund.setOrderId(param.getOrderId());
  348. storeRefund.setRefundAmount(param.getRefundAmount());
  349. storeRefund.setRefundReason(param.getReason());
  350. storeRefund.setCreateTime(new Date());
  351. storeRefundMapper.insertStoreRefund(storeRefund);
  352. log.info("退款订单记录创建完成,订单号: {}", param.getOrderId());
  353. }
  354. /**
  355. * 退款结果通知
  356. *
  357. * @return 处理结果
  358. */
  359. public AjaxResult parseRefundNotify(String notifyData, HttpServletRequest request) {
  360. log.info("收到微信退款回调通知");
  361. SignatureHeader header = getRequestHeader(request);
  362. try {
  363. WxPayRefundNotifyV3Result res = this.wxService.parseRefundNotifyV3Result(notifyData, header);
  364. WxPayRefundNotifyV3Result.DecryptNotifyResult decryptRes = res.getResult();
  365. log.info("解析微信退款回调结果,订单号: {}, 退款状态: {}", decryptRes.getOutTradeNo(), decryptRes.getRefundStatus());
  366. // TODO 根据自己业务场景需要构造返回对象
  367. if (WxPayConstants.RefundStatus.SUCCESS.equals(decryptRes.getRefundStatus())) {
  368. log.info("退款成功,开始处理订单,订单号: {}", decryptRes.getOutTradeNo());
  369. // 成功处理订单
  370. processOrderRefund(decryptRes.getOutTradeNo());
  371. //成功返回200/204,body无需有内容
  372. log.info("退款处理完成,订单号: {}", decryptRes.getOutTradeNo());
  373. return AjaxResult.success();
  374. } else {
  375. log.warn("退款失败,订单号: {},状态: {}", decryptRes.getOutTradeNo(), decryptRes.getRefundStatus());
  376. //失败返回4xx或5xx,且需要构造body信息
  377. return AjaxResult.error();
  378. }
  379. } catch (WxPayException e) {
  380. log.error("处理微信退款回调失败", e);
  381. //失败返回4xx或5xx,且需要构造body信息
  382. return AjaxResult.error("微信退款回调失败");
  383. }
  384. }
  385. /**
  386. * 退款完成后的业务逻辑
  387. *
  388. * @param orderId 订单号
  389. */
  390. private void processOrderRefund(String orderId) {
  391. log.info("开始处理订单退款完成逻辑,订单号: {}", orderId);
  392. StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(orderId);
  393. if (order != null) {
  394. // 检查订单是否已经处理过
  395. if (order.getRefundStatus() != null && order.getPaid() == 1) {
  396. log.info("更新订单状态为已退款,订单号: {}", orderId);
  397. //更新支付订单状态为退款
  398. order.setStatus(2); // 3表示已取消
  399. order.setUpdateTime(new Date());
  400. storeOrderMapper.updateStoreOrder(order);
  401. log.info("订单状态更新完成,订单号: {}", orderId);
  402. } else {
  403. log.info("订单退款状态无需更新,订单号: {}", orderId);
  404. }
  405. } else {
  406. log.warn("订单不存在,订单号: {}", orderId);
  407. }
  408. StoreRefund refund = storeRefundMapper.selectStoreRefundByOrderId(orderId);
  409. if (refund != null) {
  410. // 检查订单是否已经处理过
  411. if (refund.getRefundStatus() != null && refund.getRefundStatus().equals("1")) {
  412. log.info("更新退款记录状态为已退款,退款单号: {}", refund.getRefundNo());
  413. // 更新订单状态
  414. refund.setRefundStatus("2"); // 设置订单状态为已退款
  415. refund.setSuccessTime(new Date());
  416. storeRefundMapper.updateStoreRefund(refund);
  417. log.info("退款记录状态更新完成,退款单号: {}", refund.getRefundNo());
  418. } else {
  419. log.info("退款记录状态无需更新,退款单号: {}", refund.getRefundNo());
  420. }
  421. } else {
  422. log.warn("退款记录不存在,订单号: {}", orderId);
  423. }
  424. }
  425. /**
  426. * 组装请求头重的前面信息
  427. *
  428. * @param request
  429. * @return
  430. */
  431. private SignatureHeader getRequestHeader(HttpServletRequest request) {
  432. log.debug("解析微信回调请求头信息");
  433. // 获取通知签名
  434. String signature = request.getHeader("Wechatpay-Signature");
  435. String nonce = request.getHeader("Wechatpay-Nonce");
  436. String serial = request.getHeader("Wechatpay-Serial");
  437. String timestamp = request.getHeader("Wechatpay-Timestamp");
  438. SignatureHeader signatureHeader = new SignatureHeader();
  439. signatureHeader.setSignature(signature);
  440. signatureHeader.setNonce(nonce);
  441. signatureHeader.setSerial(serial);
  442. signatureHeader.setTimeStamp(timestamp);
  443. return signatureHeader;
  444. }
  445. /**
  446. * 处理超时订单
  447. * 定时任务调用此方法检查并处理超时订单
  448. */
  449. public void processTimeoutOrders() {
  450. // log.info("开始处理超时订单");
  451. // 获取当前时间戳
  452. long currentTime = System.currentTimeMillis();
  453. // 从Redis有序集合中获取所有已超时的订单(score小于当前时间的元素)
  454. redisTemplate.opsForZSet().rangeByScore(ORDER_DELAY_QUEUE_PREFIX + "orders", 0, currentTime)
  455. .forEach(orderId -> {
  456. // 使用分布式锁防止与支付回调冲突
  457. String lockKey = "payment:lock:" + orderId;
  458. Boolean lockAcquired = redisTemplate.opsForValue().setIfAbsent(lockKey, "1", java.time.Duration.ofSeconds(30));
  459. if (Boolean.FALSE.equals(lockAcquired)) {
  460. log.warn("订单正在被处理,跳过超时处理,订单号: {}", orderId);
  461. return;
  462. }
  463. try {
  464. log.info("处理超时订单: {}", orderId);
  465. // 查询订单信息
  466. StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(orderId);
  467. if (order != null && (order.getPaid() == null || order.getPaid() != 1)) {
  468. // 订单未支付,执行删除操作或更新状态
  469. // 这里我们更新订单状态为已取消
  470. order.setStatus(3); // 3表示已取消
  471. order.setUpdateTime(new Date());
  472. storeOrderMapper.updateStoreOrder(order);
  473. log.info("订单 {} 已超时,状态已更新为已取消", orderId);
  474. } else if (order != null) {
  475. log.info("订单 {} 已支付,无需处理", orderId);
  476. } else {
  477. log.warn("订单 {} 不存在", orderId);
  478. }
  479. // 从延迟队列中移除订单
  480. redisTemplate.opsForZSet().remove(ORDER_DELAY_QUEUE_PREFIX + "orders", orderId);
  481. log.info("超时订单 {} 处理完成", orderId);
  482. } catch (Exception e) {
  483. log.error("处理超时订单 {} 时发生错误", orderId, e);
  484. } finally {
  485. // 释放锁
  486. redisTemplate.delete(lockKey);
  487. }
  488. });
  489. // log.info("超时订单处理完成");
  490. }
  491. /**
  492. * 取消订单
  493. *
  494. * @param orderId 订单ID
  495. * @return 处理结果
  496. */
  497. public int cancelOrder(String orderId) {
  498. StoreOrder order = storeOrderMapper.selectStoreOrderByOrderId(orderId);
  499. order.setStatus(3); // 3表示已取消
  500. order.setUpdateTime(new Date());
  501. return storeOrderMapper.updateStoreOrder(order);
  502. }
  503. /**
  504. * 检查订单是否在延迟队列中
  505. *
  506. * @param orderId 订单ID
  507. * @return 如果订单在延迟队列中返回true,否则返回false
  508. */
  509. public boolean isOrderInDelayQueue(String orderId) {
  510. // 使用rank命令检查订单是否在有序集合中
  511. // 如果返回null,说明订单不在集合中
  512. Long rank = redisTemplate.opsForZSet().rank(ORDER_DELAY_QUEUE_PREFIX + "orders", orderId);
  513. return rank != null;
  514. }
  515. }