Jelajahi Sumber

feat : 订单过期自动取消策略

bilingfeng 2 tahun lalu
induk
melakukan
d8c1c60005
19 mengubah file dengan 417 tambahan dan 54 penghapusan
  1. 1 1
      game-module/game-common/src/main/java/com/zanxiang/common/enums/ExpireTimeEnum.java
  2. 8 3
      game-module/game-common/src/main/java/com/zanxiang/common/enums/OrderStateEnum.java
  3. 1 1
      game-module/game-mybatis/src/main/java/com/zanxiang/mybatis/entity/Order.java
  4. 10 0
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/constant/RedisKeyConstant.java
  5. 0 11
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/constant/WxPayConstants.java
  6. 7 7
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/listener/OrderPaySuccessListener.java
  7. 1 1
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/IOrderService.java
  8. 7 0
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/IPayApplicationService.java
  9. 1 1
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/GameAppletServiceImpl.java
  10. 1 1
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/OrderPayServiceImpl.java
  11. 4 15
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/OrderServiceImpl.java
  12. 74 0
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/PayApplicationServiceImpl.java
  13. 1 1
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/PerformOrderServiceImpl.java
  14. 45 3
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/pay/AliPayService.java
  15. 14 1
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/pay/MiPayService.java
  16. 29 2
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/pay/PayBaseService.java
  17. 36 2
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/pay/WxPayService.java
  18. 99 0
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/task/OrderExpireTask.java
  19. 78 4
      game-module/game-sdk/src/main/java/com/zanxiang/sdk/util/RedisUtil.java

+ 1 - 1
game-module/game-common/src/main/java/com/zanxiang/common/enums/ExpireTimeEnum.java

@@ -25,7 +25,7 @@ public enum ExpireTimeEnum {
     /**
      * 5分钟
      */
-    FIVE_MIN(5 * 60, "1分钟"),
+    FIVE_MIN(5 * 60, "5分钟"),
 
     /**
      * 1小时

+ 8 - 3
game-module/game-common/src/main/java/com/zanxiang/common/enums/OrderStateEnum.java

@@ -12,20 +12,25 @@ import lombok.Getter;
 @AllArgsConstructor
 public enum OrderStateEnum {
 
+    /**
+     * 预下单
+     */
+    READY_PAY(0, "预下单"),
+
     /**
      * 待支付
      */
-    NO_PAY(1, "待支付"),
+    WAIT_PAY(1, "待支付"),
 
     /**
      * 支付成功
      */
-    SUCCESS(2, "支付成功"),
+    SUCCESS_PAY(2, "支付成功"),
 
     /**
      * 取消
      */
-    CANCEL(-1, "取消");
+    CANCEL_PAY(-1, "订单关闭");
 
     /**
      * 状态

+ 1 - 1
game-module/game-mybatis/src/main/java/com/zanxiang/mybatis/entity/Order.java

@@ -144,7 +144,7 @@ public class Order {
     private String merchantOrderNo;
 
     /**
-     * 支付状态,1待支付,2 支付成功,-1 已取消
+     * 支付状态,0 : 预下单, 1 : 待支付,2 : 支付成功,-1 : 已取消
      */
     private Integer status;
 

+ 10 - 0
game-module/game-sdk/src/main/java/com/zanxiang/sdk/constant/RedisKeyConstant.java

@@ -52,4 +52,14 @@ public class RedisKeyConstant {
      */
     public static final String MI_PAY_TOKEN = RedisKeyConstant.REDIS_PREFIX + "mi_pay_token_";
 
+    /**
+     * 订单过期缓存key
+     */
+    public static final String ORDER_EXPIRE = RedisKeyConstant.REDIS_PREFIX + "order_expire_";
+
+    /**
+     * 订单过期缓存锁
+     */
+    public static final String ORDER_EXPIRE_LOCK = RedisKeyConstant.REDIS_PREFIX + "order_expire_lock_";
+
 }

+ 0 - 11
game-module/game-sdk/src/main/java/com/zanxiang/sdk/constant/WxPayConstants.java

@@ -36,16 +36,5 @@ public class WxPayConstants {
      * 对账单接口(POST)
      */
     public final static String DOWNLOAD_BILL_URL = "https://api.mch.weixin.qq.com/pay/downloadbill";
-
-    /**
-     * 短链接转换接口(POST)
-     */
-    public final static String SHORT_URL = "https://api.mch.weixin.qq.com/tools/shorturl";
-
-    /**
-     * 接口调用上报接口(POST)
-     */
-    public final static String REPORT_URL = "https://api.mch.weixin.qq.com/payitil/report";
-
 }
 

+ 7 - 7
game-module/game-sdk/src/main/java/com/zanxiang/sdk/listener/OrderPaySuccessListener.java

@@ -43,21 +43,21 @@ public class OrderPaySuccessListener {
             return;
         }
         log.info("订单:{} 支付成功履约监听逻辑 ------start---------", event.getOrderId());
-        PlatformOrderDTO orderInfo = orderService.info(event.getOrderId());
-        if (Objects.isNull(orderInfo)) {
+        PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(event.getOrderId());
+        if (Objects.isNull(platformOrderDTO)) {
             log.error("订单:{} 获取详情失败", event.getOrderId());
             return;
         }
         //cp推送
-        performOrderService.pushCp(orderInfo);
+        performOrderService.pushCp(platformOrderDTO);
         //用户充值统计更新
-        performOrderService.userRechargeTotal(orderInfo);
+        performOrderService.userRechargeTotal(platformOrderDTO);
         //用户首冲统计
-        performOrderService.checkIsFirstRecharge(orderInfo);
+        performOrderService.checkIsFirstRecharge(platformOrderDTO);
         //商户号额度统计更新
-        performOrderService.payMerchantTotal(orderInfo);
+        performOrderService.payMerchantTotal(platformOrderDTO);
         //订单回传
-        callBackService.orderCallBack(orderInfo);
+        callBackService.orderCallBack(platformOrderDTO);
         log.info("订单:{} 支付成功履约监听逻辑 ------end---------", event.getOrderId());
     }
 }

+ 1 - 1
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/IOrderService.java

@@ -39,5 +39,5 @@ public interface IOrderService extends IService<Order> {
      * @param orderId 订单id
      * @return {@link PlatformOrderDTO}
      */
-    PlatformOrderDTO info(String orderId);
+    PlatformOrderDTO getByOrderId(String orderId);
 }

+ 7 - 0
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/IPayApplicationService.java

@@ -54,4 +54,11 @@ public interface IPayApplicationService extends IService<PayApplication> {
      * @throws IOException ioexception
      */
     String appletStoreNotify(HttpServletRequest request, HttpServletResponse response) throws IOException;
+
+    /**
+     * 小应用程序商店取消
+     *
+     * @param param 参数
+     */
+    void appletStoreCancel(String param);
 }

+ 1 - 1
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/GameAppletServiceImpl.java

@@ -104,7 +104,7 @@ public class GameAppletServiceImpl extends ServiceImpl<GameAppletMapper, GameApp
         //查询用户最新订单
         Order order = orderService.getOne(new LambdaQueryWrapper<Order>()
                 .eq(Order::getUserId, userDTO.getId())
-                .eq(Order::getStatus, OrderStateEnum.NO_PAY.getCode())
+                .eq(Order::getStatus, OrderStateEnum.WAIT_PAY.getCode())
                 .orderByDesc(Order::getCreateTime)
                 .last("limit 1"));
         if (order == null) {

+ 1 - 1
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/OrderPayServiceImpl.java

@@ -158,7 +158,7 @@ public class OrderPayServiceImpl implements IOrderPayService {
         if (order == null) {
             throw new BaseException("参数错误, 订单信息不存在");
         }
-        if (Objects.equals(order.getStatus(), OrderStateEnum.SUCCESS.getCode())) {
+        if (Objects.equals(order.getStatus(), OrderStateEnum.SUCCESS_PAY.getCode())) {
             return Boolean.TRUE;
         }
         return Boolean.FALSE;

+ 4 - 15
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/OrderServiceImpl.java

@@ -10,7 +10,6 @@ import com.zanxiang.mybatis.entity.*;
 import com.zanxiang.mybatis.mapper.OrderMapper;
 import com.zanxiang.sdk.domain.bo.PlatformOrderBO;
 import com.zanxiang.sdk.domain.dto.GamePayWayDTO;
-import com.zanxiang.sdk.domain.dto.PayMerchantDTO;
 import com.zanxiang.sdk.domain.dto.PlatformOrderDTO;
 import com.zanxiang.sdk.domain.params.ProductPayParam;
 import com.zanxiang.sdk.domain.params.UserData;
@@ -18,7 +17,6 @@ import com.zanxiang.sdk.listener.OrderPaySuccessEvent;
 import com.zanxiang.sdk.service.*;
 import lombok.extern.slf4j.Slf4j;
 import org.apache.commons.lang3.RandomStringUtils;
-import org.apache.logging.log4j.util.Strings;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.ApplicationContext;
 import org.springframework.stereotype.Service;
@@ -59,9 +57,6 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
     @Autowired
     private IGamePayWayService gamePayWayService;
 
-    @Autowired
-    private IPayMerchantService payMerchantService;
-
     @Override
     public Boolean createOrder(ProductPayParam payParam, UserData userData) {
         //用户id
@@ -94,11 +89,6 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
         }
         //游戏支付配置信息
         GamePayWayDTO gamePayWayDTO = gamePayWayService.getGamePayWay(gameId, payParam.getPayWay(), payParam.getPayDevice());
-        //商户信息
-        PayMerchantDTO payMerchantDTO = null;
-        if (Strings.isNotBlank(gamePayWayDTO.getMerchantNo())) {
-            payMerchantDTO = payMerchantService.getByMerchantNo(gamePayWayDTO.getMerchantNo());
-        }
         //生成订单id
         String orderNum = this.getOrderNum(userData.getUserId());
         //构造订单
@@ -117,6 +107,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
                 .amount(payParam.getAmount())
                 .productId(payParam.getProductId())
                 .productName(payParam.getProductName())
+                .status(OrderStateEnum.READY_PAY.getCode())
                 .gamePayWayId(gamePayWayDTO.getId())
                 .ext(payParam.getExtension())
                 .payDeviceId(gamePayWayDTO.getPayDeviceId())
@@ -125,8 +116,6 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
                 .username(user.getUsername())
                 .regTime(user.getCreateTime())
                 .deviceSystem(userData.getDeviceSystem())
-                .merchantNo(payMerchantDTO == null ? null : payMerchantDTO.getMerchantNo())
-                .merchantName(payMerchantDTO == null ? null : payMerchantDTO.getMerchantName())
                 .payWayId(gamePayWayDTO.getPayWayId())
                 .regGameId(user.getGameId())
                 .callBackStatus(CallBackEnum.UN_CALL_BACK.getCallBackStatus())
@@ -165,10 +154,10 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
         try {
             Order order = getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderId, platformOrderBO.getOrderId()));
             //已支付情况,直接返回成功
-            if (order.getStatus().equals(OrderStateEnum.SUCCESS.getCode())) {
+            if (order.getStatus().equals(OrderStateEnum.SUCCESS_PAY.getCode())) {
                 return true;
             }
-            if (!order.getStatus().equals(OrderStateEnum.NO_PAY.getCode())) {
+            if (!order.getStatus().equals(OrderStateEnum.WAIT_PAY.getCode())) {
                 throw new BaseException("订单状态非待支付");
             }
             platformOrderBO.setPayTime(LocalDateTime.now());
@@ -187,7 +176,7 @@ public class OrderServiceImpl extends ServiceImpl<OrderMapper, Order> implements
     }
 
     @Override
-    public PlatformOrderDTO info(String orderId) {
+    public PlatformOrderDTO getByOrderId(String orderId) {
         Order order = getOne(new LambdaQueryWrapper<Order>().eq(Order::getOrderId, orderId));
         return BeanUtils.copy(order, PlatformOrderDTO.class);
     }

+ 74 - 0
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/PayApplicationServiceImpl.java

@@ -5,6 +5,7 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
 import com.zanxiang.common.constant.Constants;
 import com.zanxiang.common.enums.HttpStatusEnum;
 import com.zanxiang.common.exception.BaseException;
+import com.zanxiang.module.util.DateUtil;
 import com.zanxiang.module.util.JsonUtil;
 import com.zanxiang.module.util.bean.BeanUtil;
 import com.zanxiang.module.util.pojo.ResultVO;
@@ -12,6 +13,7 @@ import com.zanxiang.module.web.util.IpUtil;
 import com.zanxiang.mybatis.entity.PayApplication;
 import com.zanxiang.mybatis.entity.PayMerchant;
 import com.zanxiang.mybatis.mapper.PayApplicationMapper;
+import com.zanxiang.sdk.constant.RedisKeyConstant;
 import com.zanxiang.sdk.constant.WxPayConstants;
 import com.zanxiang.sdk.domain.dto.PayApplicationDTO;
 import com.zanxiang.sdk.domain.dto.PayBoxDTO;
@@ -22,6 +24,7 @@ import com.zanxiang.sdk.service.IPayBoxService;
 import com.zanxiang.sdk.service.IPayMerchantService;
 import com.zanxiang.sdk.service.api.WxApiService;
 import com.zanxiang.sdk.util.HttpUtil;
+import com.zanxiang.sdk.util.RedisUtil;
 import com.zanxiang.sdk.util.WxPayUtil;
 import com.zanxiang.sdk.util.XmlUtil;
 import lombok.extern.slf4j.Slf4j;
@@ -62,6 +65,9 @@ public class PayApplicationServiceImpl extends ServiceImpl<PayApplicationMapper,
     @Autowired
     private IPayBoxService payBoxService;
 
+    @Autowired
+    private RedisUtil<String> redisUtil;
+
     private static final String WX_PAY_JSAPI = "JSAPI";
 
     private static final String SIGN_TYPE = "MD5";
@@ -129,6 +135,8 @@ public class PayApplicationServiceImpl extends ServiceImpl<PayApplicationMapper,
             Map<String, String> successMap = this.unifiedOrder(paramMap);
             //构造支付参数
             Map<Object, Object> payParamMap = this.payParamMap(orderId, payApplication.getAppId(), payConfigMap.get("apiKey"), successMap);
+            //添加过期缓存
+            this.orderExpire(orderId, payApplication.getAppId());
             //返回
             return ResultVO.ok(payParamMap);
         } catch (Exception e) {
@@ -137,6 +145,16 @@ public class PayApplicationServiceImpl extends ServiceImpl<PayApplicationMapper,
         }
     }
 
+    private void orderExpire(String orderId, String appId) {
+        //过期时间
+        double expire = (double) DateUtil.localDateTimeToMilli(LocalDateTime.now().plusMinutes(5));
+        //参数
+        String param = orderId + "_" + appId;
+        //设置缓存
+        redisUtil.addZSet(RedisKeyConstant.ORDER_EXPIRE, param, expire);
+        log.error("商城小程序订单添加过期缓存, orderId : {}, expire : {}", orderId, expire);
+    }
+
     @Override
     public String appletStoreNotify(HttpServletRequest request, HttpServletResponse response) throws IOException {
         // 读取参数
@@ -248,4 +266,60 @@ public class PayApplicationServiceImpl extends ServiceImpl<PayApplicationMapper,
             throw new BaseException("微信小程序支付下单异常");
         }
     }
+
+    @Override
+    public void appletStoreCancel(String param) {
+        //参数切割
+        String[] split = param.split("_");
+        //小程序appId
+        String appId = split[0];
+        String orderId = split[1];
+        //查询商城小程序信息
+        PayApplication payApplication = super.getOne(new LambdaQueryWrapper<PayApplication>()
+                .eq(PayApplication::getAppId, appId));
+        if (payApplication == null) {
+            return;
+        }
+        //查询商户信息
+        PayMerchantDTO payMerchantDTO = payMerchantService.getByMerchantNo(payApplication.getMerchantNo());
+        //商户支付配置
+        Map<String, String> payConfigMap = JsonUtil.toMap(payMerchantDTO.getPayConfig(), Map.class, String.class);
+        //关闭订单参数
+        Map<String, String> payParamMap = new HashMap<>(3);
+        payParamMap.put("appid", appId);
+        payParamMap.put("mch_id", payMerchantDTO.getMerchantNo());
+        payParamMap.put("out_trade_no", orderId);
+        payParamMap.put("apiKey", payConfigMap.get("apiKey"));
+        //关闭订单
+        this.closeOrder(payParamMap);
+    }
+
+    private void closeOrder(Map<String, String> paramMap) {
+        try {
+            Map<String, String> paramData = new HashMap<>(6);
+            paramData.put("appid", paramMap.get("appId"));
+            paramData.put("mch_id", paramMap.get("mchId"));
+            paramData.put("out_trade_no", paramMap.get("orderId"));
+            paramData.put("nonce_str", WxPayUtil.generateNonceStr());
+            paramData.put("sign_type", SIGN_TYPE);
+            //接口签名
+            String sign = WxPayUtil.generateSignature(paramData, paramMap.get("apiKey"));
+            paramData.put("sign", sign);
+            //取消订单, 获取结果
+            String result = HttpUtil.postData(WxPayConstants.CLOSE_ORDER_URL, XmlUtil.mapToXml(paramData));
+            Map<String, String> successMap = XmlUtil.xmlToMap(result);
+            // 结果状态码
+            String resultCode = successMap.get("result_code");
+            // 返回状态码
+            String returnCode = successMap.get("return_code");
+            //成功, 返回结果
+            if (Constants.SUCCESS.equalsIgnoreCase(returnCode) && returnCode.equals(resultCode)) {
+                log.error("微信支付关闭订单成功, out_trade_no:{}", paramMap.get("orderId"));
+                return;
+            }
+            log.error("微信支付关闭订单失败, paramData:{}, successMap : {}", JsonUtil.toString(paramData), JsonUtil.toString(successMap));
+        } catch (Exception e) {
+            log.error("微信支付关闭订单异常, 订单号:{}, e : {}", paramMap.get("orderId"), e.getMessage());
+        }
+    }
 }

+ 1 - 1
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/Impl/PerformOrderServiceImpl.java

@@ -150,7 +150,7 @@ public class PerformOrderServiceImpl implements IPerformOrderService {
         try {
             int count = orderService.count(new LambdaQueryWrapper<Order>()
                     .eq(Order::getUserId, platformOrderDTO.getUserId())
-                    .eq(Order::getStatus, OrderStateEnum.SUCCESS.getCode())
+                    .eq(Order::getStatus, OrderStateEnum.SUCCESS_PAY.getCode())
                     .ne(Order::getOrderId, platformOrderDTO.getOrderId()));
             if (count <= 0) {
                 orderService.update(new LambdaUpdateWrapper<Order>()

+ 45 - 3
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/pay/AliPayService.java

@@ -5,18 +5,23 @@ import com.alipay.easysdk.factory.Factory;
 import com.alipay.easysdk.kernel.Config;
 import com.alipay.easysdk.kernel.util.ResponseChecker;
 import com.alipay.easysdk.payment.app.models.AlipayTradeAppPayResponse;
+import com.alipay.easysdk.payment.common.models.AlipayTradeCloseResponse;
 import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
 import com.alipay.easysdk.payment.wap.models.AlipayTradeWapPayResponse;
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.zanxiang.common.constant.Constants;
 import com.zanxiang.common.enums.HttpStatusEnum;
+import com.zanxiang.common.enums.OrderStateEnum;
 import com.zanxiang.common.enums.OsEnum;
 import com.zanxiang.common.exception.BaseException;
 import com.zanxiang.module.util.JsonUtil;
 import com.zanxiang.module.util.pojo.ResultVO;
+import com.zanxiang.mybatis.entity.Order;
 import com.zanxiang.sdk.domain.bo.ProductPayParamBO;
 import com.zanxiang.sdk.domain.dto.GamePayWayDTO;
 import com.zanxiang.sdk.domain.dto.PayApplicationDTO;
 import com.zanxiang.sdk.domain.dto.PayMerchantDTO;
+import com.zanxiang.sdk.domain.dto.PlatformOrderDTO;
 import com.zanxiang.sdk.service.IPayApplicationService;
 import com.zanxiang.sdk.service.IPayMerchantService;
 import com.zanxiang.sdk.util.HttpUtil;
@@ -77,6 +82,11 @@ public class AliPayService extends PayBaseService {
      */
     private Config config;
 
+    /**
+     * 商户信息
+     */
+    private PayMerchantDTO payMerchant;
+
     @Autowired
     private IPayMerchantService payMerchantService;
 
@@ -93,16 +103,30 @@ public class AliPayService extends PayBaseService {
         //支付方式
         int payDevice = product.getPayDevice().intValue();
         //不同的支付途径
+        Map<String, Object> resultMap;
         switch (payDevice) {
             case 1:
-                return this.pcPay(product);
+                resultMap = this.pcPay(product);
+                break;
             case 2:
-                return this.h5Pay(product);
+                resultMap = this.h5Pay(product);
+                break;
             case 3:
-                return this.appPay(product);
+                resultMap = this.appPay(product);
+                break;
             default:
                 throw new RuntimeException("未知支付方式");
         }
+        //更新订单商户信息
+        orderService.update(new LambdaUpdateWrapper<Order>()
+                .set(Order::getMerchantNo, payMerchant.getMerchantNo())
+                .set(Order::getMerchantName, payMerchant.getMerchantName())
+                .set(Order::getStatus, OrderStateEnum.WAIT_PAY.getCode())
+                .eq(Order::getOrderId, product.getOutTradeNo()));
+        //商户已下单, 设置订单过期缓存
+        this.orderExpire(product.getOutTradeNo());
+        //返回结果
+        return resultMap;
     }
 
     @Override
@@ -176,6 +200,23 @@ public class AliPayService extends PayBaseService {
         }
     }
 
+    @Override
+    public void closeOrder(PlatformOrderDTO platformOrderDTO) {
+        //初始化配置
+        this.configInit(getPayConfig(platformOrderDTO.getGameId(), platformOrderDTO.getPayWayId(), platformOrderDTO.getPayDeviceId()));
+        //取消订单
+        AlipayTradeCloseResponse response;
+        try {
+            response = Factory.Payment.Common().close(platformOrderDTO.getOrderId());
+            if (ResponseChecker.success(response)) {
+                return;
+            }
+            log.error("支付宝取消订单失败, platformOrderDTO : {}, response : {}", JsonUtil.toString(platformOrderDTO), JsonUtil.toString(response));
+        } catch (Exception e) {
+            log.error("支付宝取消订单异常, platformOrderDTO : {}, e:{}", JsonUtil.toString(platformOrderDTO), e.getMessage());
+        }
+    }
+
     private Map<String, Object> h5Pay(ProductPayParamBO product) {
         AlipayTradeWapPayResponse response;
         try {
@@ -286,6 +327,7 @@ public class AliPayService extends PayBaseService {
     private void configInit(GamePayWayDTO gamePayWayDTO) {
         //商户信息
         PayMerchantDTO payMerchantDTO = payMerchantService.getByMerchantNo(gamePayWayDTO.getMerchantNo());
+        this.payMerchant = payMerchantDTO;
         //查询支付应用信息
         PayApplicationDTO payApplicationDTO = payApplicationService.getPayApplicationByAppId(gamePayWayDTO.getAppId());
         //支付参数

+ 14 - 1
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/pay/MiPayService.java

@@ -1,11 +1,14 @@
 package com.zanxiang.sdk.service.pay;
 
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.zanxiang.common.enums.HttpStatusEnum;
+import com.zanxiang.common.enums.OrderStateEnum;
 import com.zanxiang.common.exception.BaseException;
 import com.zanxiang.common.exception.CustomException;
 import com.zanxiang.common.utils.StringUtils;
 import com.zanxiang.module.util.JsonUtil;
 import com.zanxiang.module.util.pojo.ResultVO;
+import com.zanxiang.mybatis.entity.Order;
 import com.zanxiang.sdk.constant.MiPayConstants;
 import com.zanxiang.sdk.constant.RedisKeyConstant;
 import com.zanxiang.sdk.domain.bo.ProductPayParamBO;
@@ -66,7 +69,7 @@ public class MiPayService extends PayBaseService {
     @Override
     public Map<String, Object> create(ProductPayParamBO product) {
         //查询订单
-        PlatformOrderDTO orderInfo = orderService.info(product.getOutTradeNo());
+        PlatformOrderDTO orderInfo = orderService.getByOrderId(product.getOutTradeNo());
         if (Objects.isNull(orderInfo)) {
             throw new CustomException(HttpStatusEnum.ORDER_NO_FIND);
         }
@@ -99,6 +102,11 @@ public class MiPayService extends PayBaseService {
             payParamMap.put("balance", String.valueOf(balance));
             payParamMap.put("orderId", product.getOutTradeNo());
         }
+        //更新订单商户信息
+        orderService.update(new LambdaUpdateWrapper<Order>()
+                .set(Order::getMerchantNo, config.getAppId())
+                .set(Order::getStatus, OrderStateEnum.WAIT_PAY.getCode())
+                .eq(Order::getOrderId, product.getOutTradeNo()));
         return payParamMap;
     }
 
@@ -228,4 +236,9 @@ public class MiPayService extends PayBaseService {
     public ResultVO synNotify(HttpServletRequest request) {
         throw new BaseException("米大师支付不存在同步回调");
     }
+
+    @Override
+    public void closeOrder(PlatformOrderDTO platformOrderDTO) {
+        throw new BaseException("米大师支付不存在取消订单");
+    }
 }

+ 29 - 2
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/pay/PayBaseService.java

@@ -3,9 +3,11 @@ package com.zanxiang.sdk.service.pay;
 import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
 import com.zanxiang.common.enums.OrderStateEnum;
 import com.zanxiang.common.exception.BaseException;
+import com.zanxiang.module.util.DateUtil;
 import com.zanxiang.module.util.JsonUtil;
 import com.zanxiang.module.util.pojo.ResultVO;
 import com.zanxiang.mybatis.entity.Order;
+import com.zanxiang.sdk.constant.RedisKeyConstant;
 import com.zanxiang.sdk.domain.bo.PlatformOrderBO;
 import com.zanxiang.sdk.domain.bo.ProductPayAttachParamBO;
 import com.zanxiang.sdk.domain.bo.ProductPayParamBO;
@@ -13,6 +15,7 @@ import com.zanxiang.sdk.domain.dto.GamePayWayDTO;
 import com.zanxiang.sdk.domain.dto.PlatformOrderDTO;
 import com.zanxiang.sdk.service.IGamePayWayService;
 import com.zanxiang.sdk.service.IOrderService;
+import com.zanxiang.sdk.util.RedisUtil;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
 import org.springframework.context.annotation.Lazy;
@@ -40,6 +43,10 @@ public abstract class PayBaseService {
     @Autowired
     public IGamePayWayService gamePayWayService;
 
+    @Lazy
+    @Autowired
+    private RedisUtil<String> redisUtil;
+
     /**
      * 自定义参数
      */
@@ -53,7 +60,7 @@ public abstract class PayBaseService {
     public Map<String, Object> payCreate(ProductPayParamBO product) {
         try {
             //获取订单信息
-            PlatformOrderDTO platformOrderDTO = orderService.info(product.getOutTradeNo());
+            PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(product.getOutTradeNo());
             if (platformOrderDTO == null) {
                 throw new BaseException("参数错误, 订单信息不存在");
             }
@@ -98,7 +105,7 @@ public abstract class PayBaseService {
         bo.setOrderId(orderNo);
         bo.setRealAmount(new BigDecimal(realAmount));
         bo.setMerchantOrderNo(merchantOrderNo);
-        bo.setStatus(OrderStateEnum.SUCCESS.getCode());
+        bo.setStatus(OrderStateEnum.SUCCESS_PAY.getCode());
         bo.setGamePaywayId(gamePayWayId);
         bo.setPayTime(LocalDateTime.now());
         return orderService.pay(bo);
@@ -118,6 +125,19 @@ public abstract class PayBaseService {
         return gamePayWayDTO;
     }
 
+    /**
+     * 订单设置过期时间缓存
+     *
+     * @param orderId : 订单id
+     */
+    public void orderExpire(String orderId) {
+        //过期时间
+        double expire = (double) DateUtil.localDateTimeToMilli(LocalDateTime.now().plusMinutes(5));
+        //设置缓存
+        redisUtil.addZSet(RedisKeyConstant.ORDER_EXPIRE, orderId, expire);
+        log.error("订单添加过期缓存, orderId : {}, expire : {}", orderId, expire);
+    }
+
     /**
      * 支付调起
      *
@@ -143,4 +163,11 @@ public abstract class PayBaseService {
      * @return : 返回回调结果
      */
     public abstract ResultVO synNotify(HttpServletRequest request);
+
+    /**
+     * 关闭订单
+     *
+     * @param platformOrderDTO 平台订单dto
+     */
+    public abstract void closeOrder(PlatformOrderDTO platformOrderDTO);
 }

+ 36 - 2
game-module/game-sdk/src/main/java/com/zanxiang/sdk/service/pay/WxPayService.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
 import com.zanxiang.common.constant.Constants;
 import com.zanxiang.common.enums.DeleteEnum;
 import com.zanxiang.common.enums.HttpStatusEnum;
+import com.zanxiang.common.enums.OrderStateEnum;
 import com.zanxiang.common.enums.PayDeviceEnum;
 import com.zanxiang.common.exception.BaseException;
 import com.zanxiang.module.util.JsonUtil;
@@ -17,6 +18,7 @@ import com.zanxiang.sdk.domain.bo.WxPayConfigBO;
 import com.zanxiang.sdk.domain.dto.GamePayWayDTO;
 import com.zanxiang.sdk.domain.dto.PayApplicationDTO;
 import com.zanxiang.sdk.domain.dto.PayMerchantDTO;
+import com.zanxiang.sdk.domain.dto.PlatformOrderDTO;
 import com.zanxiang.sdk.service.*;
 import com.zanxiang.sdk.service.api.WxApiService;
 import com.zanxiang.sdk.util.HttpUtil;
@@ -129,7 +131,10 @@ public class WxPayService extends PayBaseService {
         orderService.update(new LambdaUpdateWrapper<Order>()
                 .set(Order::getMerchantNo, config.getMchId())
                 .set(Order::getMerchantName, config.getMachName())
+                .set(Order::getStatus, OrderStateEnum.WAIT_PAY.getCode())
                 .eq(Order::getOrderId, product.getOutTradeNo()));
+        //商户已下单, 设置订单过期缓存
+        this.orderExpire(product.getOutTradeNo());
         //返回支付参数
         return resultMap;
     }
@@ -270,6 +275,35 @@ public class WxPayService extends PayBaseService {
         }
     }
 
+    @Override
+    public void closeOrder(PlatformOrderDTO platformOrderDTO) {
+        //初始化配置
+        this.configInit(getPayConfig(platformOrderDTO.getGameId(), platformOrderDTO.getPayWayId(), platformOrderDTO.getPayDeviceId()));
+        try {
+            Map<String, String> paramData = new HashMap<>(6);
+            paramData.put("appid", config.getAppId());
+            paramData.put("mch_id", config.getMchId());
+            paramData.put("out_trade_no", platformOrderDTO.getOrderId());
+            paramData.put("nonce_str", WxPayUtil.generateNonceStr());
+            paramData.put("sign", WxPayUtil.generateSignature(paramData, config.getApiKey()));
+            paramData.put("sign_type", config.getSignType());
+            // 取消订单, 获取结果
+            String result = HttpUtil.postData(WxPayConstants.CLOSE_ORDER_URL, XmlUtil.mapToXml(paramData));
+            Map<String, String> successMap = XmlUtil.xmlToMap(result);
+            // 返回状态码
+            String returnCode = successMap.get("return_code");
+            // 结果状态码
+            String resultCode = successMap.get("result_code");
+            //成功, 返回结果
+            if (Constants.SUCCESS.equalsIgnoreCase(returnCode) && returnCode.equals(resultCode)) {
+                return;
+            }
+            log.error("微信支付关闭订单失败, paramData:{}, successMap : {}", JsonUtil.toString(paramData), JsonUtil.toString(successMap));
+        } catch (Exception e) {
+            log.error("微信支付关闭订单异常, 订单号:{}, e : {}", platformOrderDTO.getOrderId(), e.getMessage());
+        }
+    }
+
     private Map<String, String> unifiedOrder(ProductPayParamBO product, String tradeType, String openId) {
         try {
             Map<String, String> paramData = new HashMap<>(13);
@@ -304,10 +338,10 @@ public class WxPayService extends PayBaseService {
                 return successMap;
             }
             //下单失败
-            log.error("微信小程序支付通信异常, paramData:{}", JsonUtil.toString(paramData));
+            log.error("微信支付通信异常, paramData:{}, successMap : {}", JsonUtil.toString(paramData), JsonUtil.toString(successMap));
             throw new BaseException("微信小程序支付下单失败");
         } catch (Exception e) {
-            log.error("微信小程序支付通信异常, 订单号:{}, e : {}", product.getOutTradeNo(), e.getMessage());
+            log.error("微信支付通信异常, 订单号:{}, e : {}", product.getOutTradeNo(), e.getMessage());
             throw new BaseException("微信小程序支付下单异常");
         }
     }

+ 99 - 0
game-module/game-sdk/src/main/java/com/zanxiang/sdk/task/OrderExpireTask.java

@@ -0,0 +1,99 @@
+package com.zanxiang.sdk.task;
+
+import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
+import com.baomidou.mybatisplus.core.toolkit.CollectionUtils;
+import com.zanxiang.common.enums.ExpireTimeEnum;
+import com.zanxiang.common.enums.OrderStateEnum;
+import com.zanxiang.common.utils.SpringUtils;
+import com.zanxiang.mybatis.entity.Order;
+import com.zanxiang.sdk.constant.RedisKeyConstant;
+import com.zanxiang.sdk.domain.dto.PlatformOrderDTO;
+import com.zanxiang.sdk.enums.PayTypeEnum;
+import com.zanxiang.sdk.service.IOrderService;
+import com.zanxiang.sdk.service.IPayApplicationService;
+import com.zanxiang.sdk.service.pay.PayBaseService;
+import com.zanxiang.sdk.util.RedisUtil;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.cloud.context.config.annotation.RefreshScope;
+import org.springframework.scheduling.annotation.Scheduled;
+import org.springframework.stereotype.Component;
+
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * @author : lingfeng
+ * @time : 2023-01-17
+ * @description : 订单过期定时器
+ */
+@Slf4j
+@Component
+@RefreshScope
+public class OrderExpireTask {
+
+    @Autowired
+    private RedisUtil<String> redisUtil;
+
+    @Autowired
+    private IOrderService orderService;
+
+    @Autowired
+    private IPayApplicationService payApplicationService;
+
+    /**
+     * 订单过期每分钟检测
+     */
+    @Scheduled(cron = "0 0/1 * * * ?")
+    public void orderExpireCheck() {
+        double min = 0.0;
+        double max = System.currentTimeMillis();
+        //获取缓存订单
+        Set<String> paramList = redisUtil.getZSetByScore(RedisKeyConstant.ORDER_EXPIRE, min, max);
+        //不存在过期订单
+        if (CollectionUtils.isEmpty(paramList)) {
+            return;
+        }
+        //循环处理
+        paramList.forEach(param -> {
+            //缓存锁
+            String lock = RedisKeyConstant.ORDER_EXPIRE_LOCK + param;
+            //判断订单是否在执行, 设置执行锁 1 分钟
+            boolean ifAbsent = redisUtil.setIfAbsent(lock, param, ExpireTimeEnum.ONE_MIN.getTime());
+            if (!ifAbsent) {
+                return;
+            }
+            //小程序商城的订单取消
+            if (param.contains("_")) {
+                payApplicationService.appletStoreCancel(param);
+            } else {
+                this.orderExpire(param);
+            }
+            //删除订单列表缓存
+            redisUtil.delZSetValue(RedisKeyConstant.ORDER_EXPIRE, param);
+            //删除执行缓存锁
+            redisUtil.deleteCache(lock);
+        });
+    }
+
+    private void orderExpire(String orderId) {
+        //查询订单信息
+        PlatformOrderDTO platformOrderDTO = orderService.getByOrderId(orderId);
+        //订单不存在
+        if (platformOrderDTO == null) {
+            return;
+        }
+        //订单不是待支付状态, 不处理
+        if (Objects.equals(OrderStateEnum.WAIT_PAY.getCode(), platformOrderDTO.getStatus())) {
+            return;
+        }
+        //关闭商户订单
+        PayTypeEnum payTypeEnum = PayTypeEnum.getByPayType(platformOrderDTO.getPayWayId().intValue());
+        PayBaseService service = SpringUtils.getBean(payTypeEnum.getClazz());
+        service.closeOrder(platformOrderDTO);
+        //订单状态变更
+        orderService.update(new LambdaUpdateWrapper<Order>()
+                .set(Order::getStatus, OrderStateEnum.CANCEL_PAY.getCode())
+                .eq(Order::getOrderId, orderId));
+    }
+}

+ 78 - 4
game-module/game-sdk/src/main/java/com/zanxiang/sdk/util/RedisUtil.java

@@ -2,12 +2,10 @@ package com.zanxiang.sdk.util;
 
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.beans.factory.annotation.Autowired;
-import org.springframework.data.redis.core.ListOperations;
-import org.springframework.data.redis.core.RedisTemplate;
-import org.springframework.data.redis.core.SetOperations;
-import org.springframework.data.redis.core.ValueOperations;
+import org.springframework.data.redis.core.*;
 import org.springframework.stereotype.Component;
 
+import java.util.Set;
 import java.util.concurrent.TimeUnit;
 
 /**
@@ -195,4 +193,80 @@ public class RedisUtil<T> {
         }
         return null;
     }
+
+    /**
+     * 添加单个元素进入有序集合
+     *
+     * @param key   : 缓存key
+     * @param t     : 缓存值
+     * @param score : 分数
+     * @return : 返回是否添加成功
+     */
+    public boolean addZSet(String key, T t, double score) {
+        try {
+            ZSetOperations zSet = redisTemplate.opsForZSet();
+            Boolean add = zSet.add(key, t, score);
+            return add == null ? false : add;
+        } catch (Exception e) {
+            log.error("ZSet添加失败元素失败, key : {}, t : {}, score : {}, e : {}", key, t.toString(), score, e.getMessage());
+        }
+        return false;
+    }
+
+    /**
+     * 获取分数区间
+     *
+     * @param key : 缓存key
+     * @param min : 最小值
+     * @param max : 最大值
+     * @return {@link Set}<{@link T}>
+     */
+    public Set<T> getZSetByScore(String key, Double min, Double max) {
+        try {
+            ZSetOperations<String, T> zSet = redisTemplate.opsForZSet();
+            Set<T> set = zSet.rangeByScore(key, min, max);
+            return set;
+        } catch (Exception e) {
+            log.error("ZSet获取元素失败, key : {}, min : {}, max : {}, e : {}", key, min, max, e.getMessage());
+        }
+        return null;
+    }
+
+
+    /**
+     * 按照索引区间获取(按分数正序排序)
+     * (0, -1)表示获取全部
+     *
+     * @param key   : 缓存key
+     * @param start : 开始
+     * @param end   : 结束
+     * @return {@link Set}<{@link T}>
+     */
+    public Set<T> rangeZSet(String key, long start, long end) {
+        try {
+            ZSetOperations<String, T> zSet = redisTemplate.opsForZSet();
+            return zSet.range(key, start, end);
+        } catch (Exception e) {
+            log.error("ZSet获取元素失败, key : {}, start : {}, end : {}, e : {}", key, start, end, e.getMessage());
+        }
+        return null;
+    }
+
+    /**
+     * 按照缓存值删除
+     *
+     * @param key : 缓存key
+     * @param t   : 缓存值
+     * @return boolean : 删除结果
+     */
+    public boolean delZSetValue(String key, T t) {
+        try {
+            ZSetOperations<String, T> zSet = redisTemplate.opsForZSet();
+            Long remove = zSet.remove(key, t);
+            return remove != null && remove == 1;
+        } catch (Exception e) {
+            log.error("ZSet删除元素失败, key : {}, t : {}, , e : {}", key, t, e.getMessage());
+        }
+        return false;
+    }
 }