Explorar o código

fix(payment): 修复支付金额类型及泛型使用问题

- 将amount_fee方法返回类型由Integer改为String,解决金额精度问题
- 修改amount_fee内部实现,避免金额向下取整为整数
- 更新WFTOrderService中总金额设置逻辑,调用amount_fee获取正确金额字符串
- 修正PayUtill中Map泛型使用,移除参数化类型以匹配需求
- 调整返回结果中状态码的设置方式,统一使用字符串形式"200"
wzq hai 2 días
pai
achega
1c45c0ea07

+ 90 - 75
src/main/java/com/zsElectric/boot/business/controller/applet/AppletWFTOrderController.java

@@ -2,19 +2,25 @@ package com.zsElectric.boot.business.controller.applet;
 
 import com.zsElectric.boot.business.model.form.applet.AppLevelOrderForm;
 import com.zsElectric.boot.business.model.form.applet.AppUserPayForm;
+import com.zsElectric.boot.business.model.vo.applet.WFTCloseOrderVO;
+import com.zsElectric.boot.business.model.vo.applet.WFTOrderVO;
 import com.zsElectric.boot.business.service.WFTOrderService;
 import com.zsElectric.boot.common.annotation.Log;
 import com.zsElectric.boot.common.enums.LogModuleEnum;
 import com.zsElectric.boot.common.util.IPUtils;
 import com.zsElectric.boot.core.web.Result;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.servlet.ServletException;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.validation.Valid;
 import lombok.RequiredArgsConstructor;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.web.bind.annotation.*;
 
+import java.io.IOException;
+
 /**
  * 威富通订单控制器
  * 
@@ -46,81 +52,90 @@ public class AppletWFTOrderController {
         return Result.success(payForm);
     }
 
-//    /**
-//     * 威富通支付通知回调
-//     *
-//     * 重要说明:
-//     * 1. 此接口接收威富通支付成功后的异步通知
-//     * 2. 必须返回纯字符串"success"或"fail"
-//     * 3. 如果返回"fail"或超过5秒未响应,威富通会重试通知
-//     * 4. 重试策略:0/15/15/30/180/1800/1800/1800/1800/3600秒
-//     * 5. 必须做好幂等性处理,避免重复处理
-//     *
-//     * @param request HTTP请求对象
-//     * @return "success" 或 "fail"
-//     */
-//    @Operation(summary = "威富通支付通知回调")
-//    @PostMapping("/notify")
-//    public String payNotify(HttpServletRequest request) {
-//        log.info("========== 威富通支付通知回调开始 ==========");
-//        try {
-//            String result = wftOrderService.handlePayNotify(request);
-//            log.info("========== 威富通支付通知回调结束,返回:{} ==========", result);
-//            return result;
-//        } catch (Exception e) {
-//            log.error("威富通支付通知回调异常", e);
-//            return "fail";
-//        }
-//    }
-//
-//    /**
-//     * 订单查询
-//     *
-//     * 用于主动查询订单支付状态
-//     * 建议使用场景:
-//     * 1. 用户支付后前端轮询查询
-//     * 2. 后台定时任务轮询未支付订单
-//     * 3. 支付通知未收到时的补偿查询
-//     *
-//     * @param outTradeNo 商户订单号
-//     * @return 订单状态信息
-//     */
-//    @Operation(summary = "订单查询")
-//    @GetMapping("/query/{outTradeNo}")
-//    public Result<WFTOrderVO> queryOrder(
-//            @Parameter(description = "商户订单号") @PathVariable String outTradeNo) {
-//
-//        WFTOrderVO result = wftOrderService.queryOrderVO(outTradeNo);
-//
-//        return Result.success(result);
-//    }
-//
-//    /**
-//     * 关闭订单
-//     *
-//     * 业务功能:
-//     * 1. 商户订单支付失败需要生成新单号重新发起支付,要对原订单号调用关单,避免重复支付
-//     * 2. 系统下单后,用户支付超时,系统退出不再受理,避免用户继续,请调用关单接口
-//     *
-//     * 重要说明:
-//     * 1. result_code为0表示关单成功,此笔订单不能再发起支付
-//     * 2. result_code为非0或其它表示关单接口异常,可再次发起关单操作
-//     * 3. 已支付成功的订单无法关闭
-//     * 4. 关单接口具有幂等性,可重复调用
-//     *
-//     * @param outTradeNo 商户订单号
-//     * @return 关单结果
-//     */
-//    @Operation(summary = "关闭订单")
-//    @PostMapping("/close/{outTradeNo}")
-//    @Log(value = "关闭订单", module = LogModuleEnum.APP_ORDER)
-//    public Result<WFTCloseOrderVO> closeOrder(
-//            @Parameter(description = "商户订单号") @PathVariable String outTradeNo) {
-//
-//        WFTCloseOrderVO result = wftOrderService.closeOrderVO(outTradeNo);
-//
-//        return Result.success(result);
-//    }
+    /**
+     * 订单-支付
+     *
+     * @param orderId
+     * @return
+     */
+    @Operation(summary = "订单-支付")
+    @PutMapping("/payOrder/{orderId}")
+    @Log(value = "订单-支付", module = LogModuleEnum.APP_ORDER)
+    public Result<AppUserPayForm> payOrder(@PathVariable("orderId") String orderId,HttpServletRequest request){
+        String ip = IPUtils.getIpAddr(request);
+        return Result.success(wftOrderService.payOrder(orderId, ip));
+    }
+
+    /**
+     * 威富通支付通知回调
+     *
+     * 重要说明:
+     * 1. 此接口接收威富通支付成功后的异步通知
+     * 2. 必须返回纯字符串"success"或"fail"
+     * 3. 如果返回"fail"或超过5秒未响应,威富通会重试通知
+     * 4. 重试策略:0/15/15/30/180/1800/1800/1800/1800/3600秒
+     * 5. 必须做好幂等性处理,避免重复处理
+     * 6. 必须验证签名,确保通知来自威富通
+     * 7. 必须校验订单号和金额,防止金额篡改
+     *
+     * 通知数据格式:POST方式发送XML数据流
+     * 签名方式:支持RSA_1_256、RSA_1_1、MD5
+     *
+     * 定时轮询查单建议:
+     * - 方案一:以订单创建时间为基准,间隔5秒/30秒/1分钟/3分钟/5分钟/10分钟/30分钟查询
+     * - 方案二:定时任务每30秒启动,查询最近10分钟内未支付订单,最多查询10次
+     *
+     * @param request HTTP请求对象
+     * @return "success" 或 "fail"
+     */
+    @Operation(summary = "威富通支付通知回调")
+    @PostMapping("/notify")
+    public String payNotify(HttpServletRequest request) {
+        log.info("========== 威富通支付通知回调开始 ==========");
+        try {
+            String result = wftOrderService.handlePayNotify(request);
+            log.info("========== 威富通支付通知回调结束,返回:{} ==========", result);
+            return result;
+        } catch (Exception e) {
+            log.error("威富通支付通知回调异常", e);
+            return "fail";
+        }
+    }
+
+    /**
+     * 订单查询
+     *
+     * 用于主动查询订单支付状态
+     * 建议使用场景:
+     * 1. 用户支付后前端轮询查询
+     * 2. 后台定时任务轮询未支付订单
+     * 3. 支付通知未收到时的补偿查询
+     *
+     * @param orderNo 商户订单号
+     * @return 订单状态信息
+     */
+    @Operation(summary = "订单查询")
+    @GetMapping("/query/{orderNo}")
+    public Result<Boolean> queryOrder(
+            @Parameter(description = "商户订单号") @PathVariable String orderNo) {
+
+        Boolean result = wftOrderService.queryOrder(orderNo);
+
+        return Result.success(result);
+    }
+
+    /**
+     * 取消订单
+     *
+     * @param orderNo 商户订单号
+     * @return 关单结果
+     */
+    @Operation(summary = "取消订单")
+    @PutMapping("/closeOrder/{orderNo}")
+    @Log(value = "取消订单", module = LogModuleEnum.APP_ORDER)
+    public Result<Boolean> closeOrder(@Parameter(description = "商户订单号") @PathVariable String orderNo) {
+        return Result.success(wftOrderService.closeOrder(orderNo));
+    }
 //
 //    /**
 //     * 申请退款

+ 2 - 1
src/main/java/com/zsElectric/boot/business/model/form/applet/AppUserPayForm.java

@@ -7,6 +7,7 @@ import lombok.EqualsAndHashCode;
 import lombok.experimental.Accessors;
 
 import java.io.Serializable;
+import java.util.Map;
 
 @Data
 @Accessors(chain = true)
@@ -23,5 +24,5 @@ public class AppUserPayForm implements Serializable {
     private String orderNo;
 
     @Schema(description = "JsApi参数")
-    private WechatPayParamsVO params;
+    private Map<String, Object> params;
 }

+ 2 - 0
src/main/java/com/zsElectric/boot/business/model/vo/applet/WFTOrderVO.java

@@ -3,6 +3,7 @@ package com.zsElectric.boot.business.model.vo.applet;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 
+import java.io.Serial;
 import java.io.Serializable;
 
 /**
@@ -15,6 +16,7 @@ import java.io.Serializable;
 @Schema(description = "威富通订单查询响应")
 public class WFTOrderVO implements Serializable {
 
+    @Serial
     private static final long serialVersionUID = 1L;
 
     @Schema(description = "返回状态码(0表示成功)")

+ 534 - 7
src/main/java/com/zsElectric/boot/business/service/WFTOrderService.java

@@ -1,5 +1,7 @@
 package com.zsElectric.boot.business.service;
 
+import cn.hutool.core.util.ObjectUtil;
+import com.baomidou.mybatisplus.core.toolkit.Wrappers;
 import com.zsElectric.boot.business.mapper.RechargeLevelMapper;
 import com.zsElectric.boot.business.mapper.UserInfoMapper;
 import com.zsElectric.boot.business.mapper.UserOrderInfoMapper;
@@ -7,16 +9,26 @@ import com.zsElectric.boot.business.model.entity.RechargeLevel;
 import com.zsElectric.boot.business.model.entity.UserOrderInfo;
 import com.zsElectric.boot.business.model.form.applet.AppLevelOrderForm;
 import com.zsElectric.boot.business.model.form.applet.AppUserPayForm;
+import com.zsElectric.boot.business.model.vo.applet.WFTOrderVO;
 import com.zsElectric.boot.business.model.vo.applet.WechatPayParamsVO;
+import com.zsElectric.boot.common.constant.SystemConstants;
+import com.zsElectric.boot.core.exception.BusinessException;
 import com.zsElectric.boot.core.pay.WFT.WFTConstants;
 import com.zsElectric.boot.core.pay.swiftpass.config.SwiftpassConfig;
 import com.zsElectric.boot.core.pay.swiftpass.util.PayUtill;
+import com.zsElectric.boot.core.pay.swiftpass.util.SignUtil;
+import com.zsElectric.boot.core.pay.swiftpass.util.SignUtils;
+import com.zsElectric.boot.core.pay.swiftpass.util.XmlUtils;
 import com.zsElectric.boot.security.util.SecurityUtils;
 import jakarta.annotation.Resource;
+import jakarta.servlet.ServletException;
+import jakarta.servlet.http.HttpServletRequest;
 import jakarta.validation.Valid;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
 
+import java.io.IOException;
 import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.text.SimpleDateFormat;
@@ -112,7 +124,7 @@ public class WFTOrderService {
             log.debug("通知第三开始支付:");
             // 通知第三方支付----------------------------------------------------------
 
-            SortedMap<String,String> map = new TreeMap<String,String>();
+            SortedMap<String, String> map = new TreeMap<String, String>();
             // 订单编号
             map.put("out_trade_no", orderNo);
             // 商品描述
@@ -136,21 +148,536 @@ public class WFTOrderService {
                 // 是否小程序支付--值为1,表示小程序支付;不传或值不为1,表示公众账号内支付
                 map.put("is_minipg", "1");
                 map.put("sub_appid", "wx9894a01b9e92c368");
-            }else if(payWay.equals("2")){
+            } else if (payWay.equals("2")) {
                 map.put("is_minipg", "2");
             }
 
             // --------微信支付请求
-            Map<String, Object> wxMap = wx.pay(map, userOpenId,swiftpassConfig);
+            Map<String, Object> wxMap = wx.pay(map, userOpenId, swiftpassConfig);
+            log.info("威富通支付返回结果: {}", wxMap);
             if (wxMap.get("status").toString().equals("200")) {
-                payForm.setParams((WechatPayParamsVO) wxMap);
+                payForm.setParams(wxMap);
                 return payForm;
             } else {
-                throw new RuntimeException("请求支付失败。");
+                String errorMsg = wxMap.get("msg") != null ? wxMap.get("msg").toString() : "未知错误";
+                log.error("请求支付失败,返回结果: {}", wxMap);
+                throw new RuntimeException("请求支付失败:" + errorMsg);
             }
         } catch (Exception e) {
-            e.printStackTrace();
+            log.error("创建订单异常,用户ID: {}, 订单号: {}", userId, orderNo, e);
+            throw new RuntimeException("请求支付失败:" + e.getMessage(), e);
+        }
+    }
+
+    public AppUserPayForm payOrder(String orderId, String ip) {
+        UserOrderInfo orderInfo = userOrderInfoMapper.selectById(orderId);
+        //构建支付表单
+        AppUserPayForm payForm = new AppUserPayForm();
+        payForm.setOrderId(orderInfo.getId()).setOrderNo(orderInfo.getOrderNo());
+
+        //查询档位
+        RechargeLevel level = rechargeLevelMapper.selectById(orderInfo.getLevelId());
+
+        PayUtill wx = new PayUtill();
+
+        /**
+         * 小程序支付
+         */
+        String payWay = "1";
+
+        try {
+            log.debug("通知第三开始支付:");
+            // 通知第三方支付----------------------------------------------------------
+
+            SortedMap<String, String> map = new TreeMap<String, String>();
+            // 订单编号
+            map.put("out_trade_no", orderInfo.getOrderNo());
+            // 商品描述
+            map.put("body", "购买充电抵扣券");
+            // 附加信息
+            map.put("attach", "支付人" + orderInfo.getUserId());
+
+            // pifList.get(0).setHydOrderPayMoney(new BigDecimal("0.01"));
+            // 总金额(分)
+
+            map.put("total_fee", amount_fee(level.getMoney()));
+            // 终端ip
+            map.put("mch_create_ip", ip);
+            // 签名方式
+            map.put("sign_type", "RSA_1_256");
+            // 回调地址
+            map.put("notify_url", swiftpassConfig.getNotify_url());
+            // 公众账号或小程序ID
+            map.put("sub_appid", "wx9894a01b9e92c368");
+            if (payWay.equals("1")) {// 支付渠道(1 微信 2支付宝支付 4建行支付 6微信小程序支付)
+                // 是否小程序支付--值为1,表示小程序支付;不传或值不为1,表示公众账号内支付
+                map.put("is_minipg", "1");
+                map.put("sub_appid", "wx9894a01b9e92c368");
+            } else if (payWay.equals("2")) {
+                map.put("is_minipg", "2");
+            }
+
+            // --------微信支付请求
+            Map<String, Object> wxMap = wx.pay(map, orderInfo.getOpenid(), swiftpassConfig);
+            log.info("威富通支付返回结果: {}", wxMap);
+            if (wxMap.get("status").toString().equals("200")) {
+                payForm.setParams(wxMap);
+                return payForm;
+            } else {
+                String errorMsg = wxMap.get("msg") != null ? wxMap.get("msg").toString() : "未知错误";
+                log.error("支付订单失败,返回结果: {}", wxMap);
+                throw new RuntimeException("支付失败:" + errorMsg);
+            }
+        } catch (Exception e) {
+            log.error("支付订单异常,订单ID: {}", orderId, e);
+            throw new RuntimeException("支付失败:" + e.getMessage(), e);
+        }
+    }
+
+    /**
+     * 将威富通支付返回的Map转换为WechatPayParamsVO
+     *
+     * @param wxMap 威富通返回的支付参数Map
+     * @return WechatPayParamsVO
+     */
+    private WechatPayParamsVO convertMapToWechatPayParamsVO(Map<String, Object> wxMap) {
+        WechatPayParamsVO vo = new WechatPayParamsVO();
+        // 威富通返回的字段名可能不同,需要根据实际返回调整
+        if (wxMap.get("appId") != null ) {
+            vo.setAppId(wxMap.get("appId").toString());
+        }
+        if (wxMap.get("timeStamp") != null ) {
+            vo.setTimeStamp(wxMap.get("timeStamp").toString());
+        }
+        if (wxMap.get("nonceStr") != null ) {
+            vo.setNonceStr(wxMap.get("nonceStr").toString());
+        }
+        if (wxMap.get("package") != null ) {
+            vo.setPackageValue(wxMap.get("package").toString());
+        }
+        if (wxMap.get("paySign") != null ) {
+            vo.setPaySign(wxMap.get("paySign").toString());
+        }
+        if (wxMap.get("signType") != null ) {
+            vo.setSignType(wxMap.get("signType").toString());
+        }
+        if (wxMap.get("out_trade_no") != null) {
+            vo.setOutTradeNo(wxMap.get("out_trade_no").toString());
+        }
+        return vo;
+    }
+
+    public Boolean closeOrder(String orderNo) {
+        UserOrderInfo userOrderInfo = userOrderInfoMapper.selectOne(Wrappers.lambdaQuery(UserOrderInfo.class).eq(UserOrderInfo::getOrderNo, orderNo).last("limit 1"));
+        if(ObjectUtil.isNull(userOrderInfo)){
+            throw new BusinessException("订单不存在");
+        }
+        userOrderInfo.setOrderStatus(SystemConstants.STATUS_THREE);
+        int i = userOrderInfoMapper.updateById(userOrderInfo);
+        if(i > 0){
+            return Boolean.TRUE;
+        }
+        return Boolean.FALSE;
+    }
+
+    /**
+     * 处理威富通支付通知回调
+     *
+     * 重要说明:
+     * 1. 接收威富通POST的XML数据流
+     * 2. 验证签名(支持RSA_1_256、RSA_1_1、MD5)
+     * 3. 校验订单号和金额
+     * 4. 幂等性处理,防止重复通知
+     * 5. 使用数据库锁避免并发问题
+     * 6. 更新订单状态
+     * 7. 必须返回纯字符串"success"或"fail"
+     *
+     * 通知重试机制:0/15/15/30/180/1800/1800/1800/1800/3600秒
+     *
+     * @param request HTTP请求对象
+     * @return "success" 表示处理成功,"fail" 表示处理失败
+     */
+    public String handlePayNotify(HttpServletRequest request) {
+        log.info("========== 开始处理威富通支付通知 ==========");
+
+        try {
+            // 1. 读取POST的XML数据
+            String xmlData = XmlUtils.parseRequst(request);
+            log.info("接收到的XML通知数据: {}", xmlData);
+
+            if (xmlData == null || xmlData.trim().isEmpty()) {
+                log.error("通知数据为空");
+                return "fail";
+            }
+
+            // 2. 解析XML为Map
+            Map<String, String> notifyData = XmlUtils.toMap(xmlData.getBytes("UTF-8"), "UTF-8");
+            log.info("解析后的通知参数: {}", notifyData);
+
+            // 3. 验证基本参数
+            String status = notifyData.get("status");
+            String resultCode = notifyData.get("result_code");
+            String signType = notifyData.get("sign_type");
+            String sign = notifyData.get("sign");
+
+            if (status == null || resultCode == null || signType == null || sign == null) {
+                log.error("通知参数缺失: status={}, result_code={}, sign_type={}, sign={}",
+                        status, resultCode, signType, sign);
+                return "fail";
+            }
+
+            // 4. 验证签名
+            boolean signValid = SignUtil.verifySign(sign, signType, notifyData, swiftpassConfig);
+            if (!signValid) {
+                log.error("签名验证失败");
+                return "fail";
+            }
+            log.info("签名验证成功");
+
+            // 5. 检查通信状态和业务结果
+            if (!"0".equals(status)) {
+                log.error("通信状态失败: status={}, message={}", status, notifyData.get("message"));
+                return "fail";
+            }
+
+            if (!"0".equals(resultCode)) {
+                log.error("业务结果失败: result_code={}, err_code={}, err_msg={}",
+                        resultCode, notifyData.get("err_code"), notifyData.get("err_msg"));
+                return "fail";
+            }
+
+            // 6. 提取支付结果参数
+            String payResult = notifyData.get("pay_result");
+            if (!"0".equals(payResult)) {
+                log.error("支付结果失败: pay_result={}, pay_info={}",
+                        payResult, notifyData.get("pay_info"));
+                return "fail";
+            }
+
+            // 7. 提取订单相关信息
+            String outTradeNo = notifyData.get("out_trade_no");        // 商户订单号
+            String transactionId = notifyData.get("transaction_id");    // 平台订单号
+            String totalFeeStr = notifyData.get("total_fee");           // 总金额(分)
+            String timeEnd = notifyData.get("time_end");                // 支付完成时间
+            String openid = notifyData.get("openid");                   // 用户标识
+            String attach = notifyData.get("attach");                   // 附加信息
+
+            if (outTradeNo == null || transactionId == null || totalFeeStr == null) {
+                log.error("订单关键参数缺失: out_trade_no={}, transaction_id={}, total_fee={}",
+                        outTradeNo, transactionId, totalFeeStr);
+                return "fail";
+            }
+
+            log.info("订单信息: out_trade_no={}, transaction_id={}, total_fee={}, time_end={}",
+                    outTradeNo, transactionId, totalFeeStr, timeEnd);
+
+            // 8. 查询订单并加锁(防止并发)
+            UserOrderInfo orderInfo = userOrderInfoMapper.selectOne(
+                    Wrappers.lambdaQuery(UserOrderInfo.class)
+                            .eq(UserOrderInfo::getOrderNo, outTradeNo)
+                            .last("FOR UPDATE")  // 数据库行锁
+            );
+
+            if (orderInfo == null) {
+                log.error("订单不存在: out_trade_no={}", outTradeNo);
+                return "fail";
+            }
+
+            // 9. 幂等性检查 - 如果订单已支付,直接返回成功
+            if (SystemConstants.STATUS_TWO.equals(orderInfo.getOrderStatus())) {
+                log.info("订单已支付,幂等性处理: out_trade_no={}, transaction_id={}",
+                        outTradeNo, orderInfo.getTransactionId());
+                return "success";
+            }
+
+            // 10. 验证订单金额
+            RechargeLevel level = rechargeLevelMapper.selectById(orderInfo.getLevelId());
+            if (level == null) {
+                log.error("档位不存在: level_id={}", orderInfo.getLevelId());
+                return "fail";
+            }
+
+            String expectedFee = amount_fee(level.getMoney());
+            if (!expectedFee.equals(totalFeeStr)) {
+                log.error("订单金额不匹配: expected={}, actual={}", expectedFee, totalFeeStr);
+                return "fail";
+            }
+
+            // 11. 更新订单状态
+            orderInfo.setOrderStatus(SystemConstants.STATUS_TWO);  // 已支付
+            orderInfo.setTransactionId(transactionId);              // 平台订单号
+            orderInfo.setPayMoney(level.getMoney());                // 支付金额
+            orderInfo.setOutTradeNo(transactionId);                 // 第三方订单号
+            // 解析支付时间
+            if (timeEnd != null && timeEnd.length() == 14) {
+                try {
+                    java.time.format.DateTimeFormatter formatter =
+                            java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+                    orderInfo.setPayTime(java.time.LocalDateTime.parse(timeEnd, formatter));
+                } catch (Exception e) {
+                    log.warn("解析支付时间失败: time_end={}", timeEnd, e);
+                }
+            }
+
+            int updateCount = userOrderInfoMapper.updateById(orderInfo);
+            if (updateCount <= 0) {
+                log.error("更新订单状态失败: out_trade_no={}", outTradeNo);
+                return "fail";
+            }
+
+            log.info("订单支付成功: out_trade_no={}, transaction_id={}, total_fee={}",
+                    outTradeNo, transactionId, totalFeeStr);
+
+            // TODO: 这里可以添加业务逻辑,例如:
+            // - 发送支付成功通知
+            // - 更新用户余额/会员等级
+            // - 记录支付日志
+
+            return "success";
+
+        } catch (Exception e) {
+            log.error("处理支付通知异常", e);
+            return "fail";
+        } finally {
+            log.info("========== 威富通支付通知处理完成 ==========");
+        }
+    }
+
+    /**
+     * 订单是否支付成功查询
+     *
+     * 应用场景:
+     * 1. 用户支付后前端轮询查询订单状态
+     * 2. 后台定时任务查询未支付订单
+     * 3. 支付通知未收到时的补偿查询
+     *
+     * 查询策略建议:
+     * - 方案一:以订单创建时间为基准,间隔5秒/30秒/1分钟/3分钟/5分钟/10分钟/30分钟查询
+     * - 方案二:定时任务每30秒查询最近10分钟内未支付订单,最多查询10次
+     *
+     * @param orderNo 商户订单号
+     * @return true-已支付成功,false-未支付或查询失败
+     */
+    public Boolean queryOrder(String orderNo) {
+        log.info("========== 开始查询订单支付状态 ==========");
+        log.info("查询订单号: {}", orderNo);
+
+        try {
+            // 1. 查询本地订单信息
+            UserOrderInfo orderInfo = userOrderInfoMapper.selectOne(
+                    Wrappers.lambdaQuery(UserOrderInfo.class)
+                            .eq(UserOrderInfo::getOrderNo, orderNo)
+            );
+
+            if (orderInfo == null) {
+                log.error("订单不存在: orderNo={}", orderNo);
+                return false;
+            }
+
+            // 2. 如果订单已经是已支付状态,直接返回true
+            if (SystemConstants.STATUS_TWO.equals(orderInfo.getOrderStatus())) {
+                log.info("订单已支付: orderNo={}, transactionId={}", orderNo, orderInfo.getTransactionId());
+                return true;
+            }
+
+            // 3. 如果订单已取消,返回false
+            if (SystemConstants.STATUS_THREE.equals(orderInfo.getOrderStatus())) {
+                log.info("订单已取消: orderNo={}", orderNo);
+                return false;
+            }
+
+            // 4. 构建查询请求参数
+            SortedMap<String, String> queryParams = new TreeMap<>();
+            queryParams.put("out_trade_no", orderNo);  // 商户订单号
+            queryParams.put("sign_type", "RSA_1_256"); // 签名方式
+
+            // 5. 调用威富通订单查询API
+            Map<String, String> queryResult = queryOrderFromWFT(queryParams);
+
+            if (queryResult == null || !"200".equals(queryResult.get("status"))) {
+                log.error("查询订单失败: orderNo={}, result={}", orderNo, queryResult);
+                return false;
+            }
+
+            // 6. 解析查询结果
+            String tradeState = queryResult.get("trade_state");
+            String payResult = queryResult.get("pay_result");
+            
+            log.info("订单查询结果: orderNo={}, trade_state={}, pay_result={}", 
+                    orderNo, tradeState, payResult);
+
+            // 7. 判断支付状态
+            // trade_state: SUCCESS-支付成功, NOTPAY-未支付, CLOSED-已关闭, REFUND-转入退款
+            // pay_result: 0-成功
+            boolean isPaid = "SUCCESS".equals(tradeState) || "0".equals(payResult);
+
+            // 8. 如果支付成功但本地订单状态未更新,同步更新订单状态
+            if (isPaid && !SystemConstants.STATUS_TWO.equals(orderInfo.getOrderStatus())) {
+                log.info("同步更新订单支付状态: orderNo={}", orderNo);
+                syncOrderStatus(orderInfo, queryResult);
+            }
+
+            return isPaid;
+
+        } catch (Exception e) {
+            log.error("查询订单支付状态异常: orderNo={}", orderNo, e);
+            return false;
+        } finally {
+            log.info("========== 订单支付状态查询完成 ==========");
+        }
+    }
+
+    /**
+     * 调用威富通订单查询API
+     *
+     * @param queryParams 查询参数
+     * @return 查询结果
+     */
+    private Map<String, String> queryOrderFromWFT(SortedMap<String, String> queryParams) {
+        log.info("调用威富通订单查询API: params={}", queryParams);
+
+        try {
+            // 设置公共参数
+            queryParams.put("service", "unified.trade.query");
+            queryParams.put("version", "2.0");
+            queryParams.put("charset", "UTF-8");
+            queryParams.put("mch_id", swiftpassConfig.getMch_id());
+            queryParams.put("nonce_str", String.valueOf(new Date().getTime()));
+
+            // 生成签名
+            Map<String, String> params = SignUtils.paraFilter(queryParams);
+            StringBuilder buf = new StringBuilder((params.size() + 1) * 10);
+            SignUtils.buildPayParams(buf, params, false);
+            String preStr = buf.toString();
+            String signType = queryParams.get("sign_type");
+            String sign = SignUtil.getSign(signType, preStr, swiftpassConfig);
+            queryParams.put("sign", sign);
+
+            log.info("查询请求XML: {}", XmlUtils.toXml(queryParams));
+
+            // 发送HTTP请求
+            String reqUrl = swiftpassConfig.getReq_url();
+            org.apache.http.client.methods.HttpPost httpPost = 
+                    new org.apache.http.client.methods.HttpPost(reqUrl);
+            org.apache.http.entity.StringEntity entityParams = 
+                    new org.apache.http.entity.StringEntity(XmlUtils.parseXML(queryParams), "utf-8");
+            httpPost.setEntity(entityParams);
+            httpPost.setHeader("Content-Type", "text/xml;utf-8");
+
+            org.apache.http.impl.client.CloseableHttpClient client = 
+                    org.apache.http.impl.client.HttpClients.createDefault();
+            org.apache.http.client.methods.CloseableHttpResponse response = client.execute(httpPost);
+
+            try {
+                if (response != null && response.getEntity() != null) {
+                    // 解析响应
+                    Map<String, String> resultMap = XmlUtils.toMap(
+                            org.apache.http.util.EntityUtils.toByteArray(response.getEntity()), 
+                            "utf-8");
+                    
+                    log.info("查询响应结果: {}", resultMap);
+
+                    // 验证签名
+                    String reSign = resultMap.get("sign");
+                    String reSignType = resultMap.get("sign_type");
+                    
+                    if (resultMap.containsKey("sign")) {
+                        boolean signValid = SignUtil.verifySign(reSign, reSignType, resultMap, swiftpassConfig);
+                        if (!signValid) {
+                            log.error("查询结果签名验证失败");
+                            resultMap.put("status", "500");
+                            resultMap.put("msg", "签名验证失败");
+                            return resultMap;
+                        }
+                    }
+
+                    // 检查通信状态和业务结果
+                    if ("0".equals(resultMap.get("status")) && 
+                        "0".equals(resultMap.get("result_code"))) {
+                        resultMap.put("status", "200");
+                        return resultMap;
+                    } else {
+                        log.error("查询失败: status={}, result_code={}, err_code={}, err_msg={}",
+                                resultMap.get("status"), resultMap.get("result_code"),
+                                resultMap.get("err_code"), resultMap.get("err_msg"));
+                        resultMap.put("status", "500");
+                        return resultMap;
+                    }
+                } else {
+                    log.error("查询响应为空");
+                    Map<String, String> errorResult = new HashMap<>();
+                    errorResult.put("status", "500");
+                    errorResult.put("msg", "查询响应为空");
+                    return errorResult;
+                }
+            } finally {
+                response.close();
+                client.close();
+            }
+
+        } catch (Exception e) {
+            log.error("调用威富通查询API异常", e);
+            Map<String, String> errorResult = new HashMap<>();
+            errorResult.put("status", "500");
+            errorResult.put("msg", "系统异常: " + e.getMessage());
+            return errorResult;
+        }
+    }
+
+    /**
+     * 同步订单支付状态
+     * 当查询到订单已支付但本地状态未更新时,同步更新本地订单状态
+     *
+     * @param orderInfo 订单信息
+     * @param queryResult 查询结果
+     */
+    @Transactional(rollbackFor = Exception.class)
+    public void syncOrderStatus(UserOrderInfo orderInfo, Map<String, String> queryResult) {
+        log.info("同步订单状态: orderNo={}", orderInfo.getOrderNo());
+
+        try {
+            // 提取支付信息
+            String transactionId = queryResult.get("transaction_id");
+            String totalFeeStr = queryResult.get("total_fee");
+            String timeEnd = queryResult.get("time_end");
+
+            // 查询档位信息验证金额
+            RechargeLevel level = rechargeLevelMapper.selectById(orderInfo.getLevelId());
+            if (level != null) {
+                String expectedFee = amount_fee(level.getMoney());
+                if (expectedFee.equals(totalFeeStr)) {
+                    // 更新订单状态
+                    orderInfo.setOrderStatus(SystemConstants.STATUS_TWO);
+                    orderInfo.setTransactionId(transactionId);
+                    orderInfo.setPayMoney(level.getMoney());
+                    orderInfo.setOutTradeNo(transactionId);
+
+                    // 解析支付时间
+                    if (timeEnd != null && timeEnd.length() == 14) {
+                        try {
+                            java.time.format.DateTimeFormatter formatter =
+                                    java.time.format.DateTimeFormatter.ofPattern("yyyyMMddHHmmss");
+                            orderInfo.setPayTime(java.time.LocalDateTime.parse(timeEnd, formatter));
+                        } catch (Exception e) {
+                            log.warn("解析支付时间失败: time_end={}", timeEnd, e);
+                        }
+                    }
+
+                    int updateCount = userOrderInfoMapper.updateById(orderInfo);
+                    if (updateCount > 0) {
+                        log.info("订单状态同步成功: orderNo={}, transactionId={}", 
+                                orderInfo.getOrderNo(), transactionId);
+                    } else {
+                        log.error("订单状态同步失败: orderNo={}", orderInfo.getOrderNo());
+                    }
+                } else {
+                    log.error("订单金额不匹配,不同步状态: expected={}, actual={}", 
+                            expectedFee, totalFeeStr);
+                }
+            }
+        } catch (Exception e) {
+            log.error("同步订单状态异常: orderNo={}", orderInfo.getOrderNo(), e);
+            throw e;
         }
-        throw new RuntimeException("请求支付失败。");
     }
 }

+ 17 - 17
src/main/java/com/zsElectric/boot/business/service/impl/UserOrderInfoServiceImpl.java

@@ -190,7 +190,7 @@ public class UserOrderInfoServiceImpl extends ServiceImpl<UserOrderInfoMapper, U
         //查询档位
         RechargeLevel level = rechargeLevelMapper.selectById(appLevelOrderForm.getLevelId());
 
-        WechatPayParamsVO result = payment(userOpenId, orderNo, level.getMoney());
+        Map<String, Object> result = payment(userOpenId, orderNo, level.getMoney());
         payForm.setParams(result);
         return payForm;
     }
@@ -211,7 +211,7 @@ public class UserOrderInfoServiceImpl extends ServiceImpl<UserOrderInfoMapper, U
         //查询档位
         RechargeLevel level = rechargeLevelMapper.selectById(orderInfo.getLevelId());
 
-        WechatPayParamsVO result = payment(orderInfo.getOpenid(), orderInfo.getOrderNo(), level.getMoney());
+        Map<String,Object> result = payment(orderInfo.getOpenid(), orderInfo.getOrderNo(), level.getMoney());
         payForm.setParams(result);
         return payForm;
     }
@@ -673,7 +673,7 @@ public class UserOrderInfoServiceImpl extends ServiceImpl<UserOrderInfoMapper, U
      * @param amount
      * @return
      */
-    private WechatPayParamsVO payment(String openId, String orderNo, BigDecimal amount) {
+    private Map<String, Object> payment(String openId, String orderNo, BigDecimal amount) {
         //15分钟超时限制
         Calendar calendar = Calendar.getInstance();
         calendar.set(Calendar.MINUTE, calendar.get(Calendar.MINUTE) + 15);
@@ -715,7 +715,7 @@ public class UserOrderInfoServiceImpl extends ServiceImpl<UserOrderInfoMapper, U
      * @return
      * @throws ServiceException
      */
-    public WechatPayParamsVO wechatPay(JsonObject params) throws ServiceException {
+    public Map<String, Object> wechatPay(JsonObject params) throws ServiceException {
 
         //发起请求
         JsonObject res = wechatPayV3Utils.sendPost(WechatUrlConstants.PAY_V3_JSAPI, params);
@@ -726,19 +726,19 @@ public class UserOrderInfoServiceImpl extends ServiceImpl<UserOrderInfoMapper, U
 
         StringBuilder sb = new StringBuilder();
         //返回给小程序拉起微信支付的参数
-        WechatPayParamsVO result = new WechatPayParamsVO();
-        result.setAppId(WechatConstants.WECHAT_APPID); //小程序appid
-        sb.append(result.getAppId()).append("\n");
-        result.setTimeStamp((new Date().getTime() / 1000) + ""); //时间戳
-        sb.append(result.getTimeStamp()).append("\n");
-        result.setNonceStr(RandomStringUtils.randomAlphanumeric(32)); //32位随机字符串
-        sb.append(result.getNonceStr()).append("\n");
-        result.setPackageValue("prepay_id=" + res.get("prepay_id").getAsString()); //预支付id 格式为 prepay_id=xxx
-        sb.append(result.getPackageValue()).append("\n");
-        //签名
-        result.setPaySign(wechatPayV3Utils.signRSA(sb.toString()));
-        result.setSignType("RSA"); //加密方式 固定RSA
-        result.setOutTradeNo(params.get("out_trade_no").getAsString()); //商户订单号 此参数不是小程序拉起支付所需的参数 因此不参与签名
+        Map<String, Object> result = new HashMap();
+//        result.setAppId(WechatConstants.WECHAT_APPID); //小程序appid
+//        sb.append(result.getAppId()).append("\n");
+//        result.setTimeStamp((new Date().getTime() / 1000) + ""); //时间戳
+//        sb.append(result.getTimeStamp()).append("\n");
+//        result.setNonceStr(RandomStringUtils.randomAlphanumeric(32)); //32位随机字符串
+//        sb.append(result.getNonceStr()).append("\n");
+//        result.setPackageValue("prepay_id=" + res.get("prepay_id").getAsString()); //预支付id 格式为 prepay_id=xxx
+//        sb.append(result.getPackageValue()).append("\n");
+//        //签名
+//        result.setPaySign(wechatPayV3Utils.signRSA(sb.toString()));
+//        result.setSignType("RSA"); //加密方式 固定RSA
+//        result.setOutTradeNo(params.get("out_trade_no").getAsString()); //商户订单号 此参数不是小程序拉起支付所需的参数 因此不参与签名
 
         return result;
     }

+ 4 - 1
src/main/java/com/zsElectric/boot/config/MybatisConfig.java

@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.DbType;
 import com.baomidou.mybatisplus.core.config.GlobalConfig;
 import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.DataPermissionInterceptor;
+import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
 import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
 import com.zsElectric.boot.plugin.mybatis.MyDataPermissionHandler;
 import com.zsElectric.boot.plugin.mybatis.MyMetaObjectHandler;
@@ -22,13 +23,15 @@ import org.springframework.transaction.annotation.EnableTransactionManagement;
 public class MybatisConfig {
 
     /**
-     * 分页插件和数据权限插件
+     * 分页插件、数据权限插件和乐观锁插件
      */
     @Bean
     public MybatisPlusInterceptor mybatisPlusInterceptor() {
         MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
         //数据权限
         interceptor.addInnerInterceptor(new DataPermissionInterceptor(new MyDataPermissionHandler()));
+        //乐观锁
+        interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
         //分页插件
         interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));