From 99b958a3b77cb72864729037095e987d1c9cfd70 Mon Sep 17 00:00:00 2001 From: "925116093-qq.com" <925116093@qq.com> Date: Thu, 14 Aug 2025 18:32:33 +0800 Subject: [PATCH] 2025008071805 --- .../src/main/resources/application.yml | 10 +- .../com/ruoyi/system/config/WechatConfig.java | 8 +- .../system/controller/ApplePayController.java | 5 +- .../system/controller/AppletController.java | 59 +- .../controller/PayNotifyController.java | 755 +++++++++++++++++- .../controller/UsersPayBeforController.java | 438 ++++++++-- .../controller/WechatPayV3Controller.java | 6 +- .../system/controllerUtil/RefundUtil.java | 221 ++--- .../system/controllerUtil/WechatPayUtil.java | 5 +- .../controllerUtil/WechatPayV3Util.java | 22 +- .../system/GoodsOrder/UnifiedRefundDialog.vue | 341 ++++---- 11 files changed, 1429 insertions(+), 441 deletions(-) diff --git a/ruoyi-admin/src/main/resources/application.yml b/ruoyi-admin/src/main/resources/application.yml index 82f4c77..aac2961 100644 --- a/ruoyi-admin/src/main/resources/application.yml +++ b/ruoyi-admin/src/main/resources/application.yml @@ -17,12 +17,12 @@ wechat: mchid: 1672571923 apikey: sssssssssssssssssssssssssssssssS certpath: wechat/apiclient_cert.p12 - apiv3-key: sssssssssssssssssssssssssssssssS - serial-no: 492161DA75F56B976B5F5EE5051ED60B0C288BB9 - private-key-path: wechat/apiclient_key.pem - cert-dir: wechat + apiv3Key: sssssssssssssssssssssssssssssssS + serialNo: 492161DA75F56B976B5F5EE5051ED60B0C288BB9 + privateKeyPath: wechat/apiclient_key.pem + certDir: wechat # 微信支付平台证书序列号(临时使用商户证书序列号进行测试) - wechatpay-serial: 492161DA75F56B976B5F5EE5051ED60B0C288BB9 + wechatpaySerial: 492161DA75F56B976B5F5EE5051ED60B0C288BB9 # appid: wx73d0202b3c8a6d68 # mchid: 1672571923 diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/config/WechatConfig.java b/ruoyi-system/src/main/java/com/ruoyi/system/config/WechatConfig.java index c5e2b19..8ba7abb 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/config/WechatConfig.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/config/WechatConfig.java @@ -29,14 +29,14 @@ public class WechatConfig { this.appid = appid; } - public String getMchid() { - return mchid; - } - public void setMchid(String mchid) { this.mchid = mchid; } + public String getMchid() { + return mchid; + } + public String getApikey() { return apikey; } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/ApplePayController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/ApplePayController.java index e3aa877..b8f2609 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controller/ApplePayController.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/ApplePayController.java @@ -108,13 +108,12 @@ public class ApplePayController extends BaseController { */ @PostMapping("/api/withdraw") public AjaxResult withdraw(@RequestParam String openid, + @RequestParam String orderid, @RequestParam int amount, @RequestParam(required = false) String desc, @RequestParam(required = false) String userName) { try { - - - Map result = wechatPayV3Util.withdraw(openid, amount, desc, userName); + Map result = wechatPayV3Util.withdraw(openid, amount, desc, userName, orderid); if ((Boolean) result.get("success")) { return AjaxResult.success("提现申请成功", result.get("data")); diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/AppletController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/AppletController.java index b3e9c64..c596007 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controller/AppletController.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/AppletController.java @@ -126,7 +126,8 @@ public class AppletController extends BaseController { private IUsersPayBeforService usersPayBeforService; @Autowired private IUserDemandQuotationService userDemandQuotationService; - + @Autowired + private WechatPayV3Util wechatPayV3Util; /** * 获取服务分类列表 * 功能说明: @@ -7509,8 +7510,8 @@ public class AppletController extends BaseController { return AppletControllerUtil.appletWarning("提现金额不能为空"); } BigDecimal moneyBigDecimal = new BigDecimal(money); - if (moneyBigDecimal.compareTo(BigDecimal.ZERO) <= 0) { - return AppletControllerUtil.appletWarning("提现金额必须大于0"); + if (moneyBigDecimal.compareTo(new BigDecimal(1)) <= 0) { + return AppletControllerUtil.appletWarning("提现金额必须大于1元"); } if (moneyBigDecimal.compareTo(user.getCommission()) > 0) { return AppletControllerUtil.appletWarning("提现金额不能大于账户余额"); @@ -7519,27 +7520,41 @@ public class AppletController extends BaseController { return AppletControllerUtil.appletWarning("提现金额不能大于2000"); } //--------------------------预留提现的接口实现核心逻辑开始-------------------------------- - //------------------------------------结束-------------------------------- - WechatTransfer wechatTransfer = new WechatTransfer(); - wechatTransfer.setUid(user.getId()); - wechatTransfer.setMoney(moneyBigDecimal); - wechatTransfer.setUname(user.getName()); - wechatTransfer.setOrderId(orderId); - wechatTransfer.setStatus(1L); - wechatTransfer.setPaid(0L); - wechatTransfer.setOpenid(user.getOpenid()); - int flg= wechatTransferService.insertWechatTransfer(wechatTransfer); - if(flg>0){ - //减少师傅提现的余额 - user.setCommission(user.getCommission().subtract(moneyBigDecimal)); - //增加师傅的累计提现金额 - user.setPropose(user.getPropose().add(moneyBigDecimal)); - usersService.updateUsers(user); + + Map result = wechatPayV3Util.quickWithdraw(user.getOpenid(), moneyBigDecimal, "师傅收益提现",orderId); + + if ((Boolean) result.get("success")) { + System.out.println("快速提现成功"+result); + WechatTransfer wechatTransfer = new WechatTransfer(); + wechatTransfer.setUid(user.getId()); + wechatTransfer.setMoney(moneyBigDecimal); + wechatTransfer.setUname(user.getName()); + wechatTransfer.setOrderId(orderId); + wechatTransfer.setStatus(1L); + wechatTransfer.setPaid(0L); + wechatTransfer.setOpenid(user.getOpenid()); + int flg= wechatTransferService.insertWechatTransfer(wechatTransfer); +// if(flg>0){ +// +// //减少师傅提现的余额 +// user.setCommission(user.getCommission().subtract(moneyBigDecimal)); +// //增加师傅的累计提现金额 +// user.setPropose(user.getPropose().add(moneyBigDecimal)); +// usersService.updateUsers(user); +// } + return AppletControllerUtil.appletSuccess("提现成功") ; + } else { + System.out.println("快速提现失败"+result); + return AppletControllerUtil.appletError("服务器繁忙,请稍后重试"); } - Map resp = new java.util.HashMap<>(); - resp.put("resultCode", "200"); - return resp; + + //------------------------------------结束-------------------------------- + + +// Map resp = new java.util.HashMap<>(); +// resp.put("resultCode", "200"); +// return resp; } /** diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/PayNotifyController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/PayNotifyController.java index 9f5dc11..5cd5ac8 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controller/PayNotifyController.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/PayNotifyController.java @@ -11,10 +11,14 @@ import com.ruoyi.system.service.*; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.HttpStatus; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RestController; import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.io.InputStream; import java.math.BigDecimal; import java.text.SimpleDateFormat; import java.time.LocalDateTime; @@ -27,6 +31,11 @@ import java.util.Map; import java.io.BufferedReader; import java.io.InputStreamReader; import java.nio.charset.StandardCharsets; +import java.security.Signature; +import java.security.cert.CertificateFactory; +import java.security.cert.X509Certificate; +import java.util.Base64; +import java.util.Map; /** * 支付回调控制器 @@ -42,7 +51,13 @@ import java.nio.charset.StandardCharsets; public class PayNotifyController extends BaseController { private static final Logger logger = LoggerFactory.getLogger(PayNotifyController.class); - + /** + * 获取微信配置 + */ + private com.ruoyi.system.config.WechatConfig wechatConfig() { + return com.ruoyi.common.utils.spring.SpringUtils.getBean(com.ruoyi.system.config.WechatConfig.class); + } + @Autowired private WechatPayUtil wechatPayUtil; @@ -51,7 +66,10 @@ public class PayNotifyController extends BaseController { @Autowired private IOrderService orderService; - + + @Autowired + private IWechatTransferService wechatTransferService; + @Autowired private IOrderLogService orderLogService; @@ -716,7 +734,7 @@ public class PayNotifyController extends BaseController { // orderService.updateOrder(mainOrder); // } - // 8. 处理上门费支付成功后的业务逻辑 + // 7. 处理上门费支付成功后的业务逻辑 handleDoorFeePaymentSuccess(orderLog, paymentInfo); // 2. 处理积分奖励 @@ -810,7 +828,7 @@ public class PayNotifyController extends BaseController { // orderService.updateOrder(mainOrder); // } - // 8. 处理定金支付成功后的业务逻辑 + // 7. 处理定金支付成功后的业务逻辑 Users users = usersService.selectUsersById(mainOrder.getUid()); handleDepositPaymentSuccess(users,orderLog, paymentInfo); @@ -1662,6 +1680,536 @@ public class PayNotifyController extends BaseController { } } + /** + * 微信提现回调接口 + */ + @PostMapping("/api/worker/tixian/notify") + public void apiworkertixiannotify(HttpServletRequest request, HttpServletResponse response) throws IOException { + logger.info("🚀 开始处理微信提现回调..."); + + try { + // 步骤1: 读取原始数据 + logger.info("📖 步骤1: 读取原始数据"); + StringBuilder rawData = new StringBuilder(); + try (BufferedReader reader = request.getReader()) { + String line; + while ((line = reader.readLine()) != null) { + rawData.append(line); + } + } + String rawDataStr = rawData.toString(); + logger.info("✅ 步骤1完成: 原始数据长度: {}", rawDataStr.length()); + logger.info("📄 原始数据内容: {}", rawDataStr); + + + + JSONObject jsonObject = JSONObject.parseObject(rawDataStr); + String nonced = jsonObject.getString("nonce"); + String associated_data = request.getHeader("associated_data"); + // WechatPayV3Util.refundtixian(); +// if (StringUtils.isNotBlank(nonced)&&StringUtils.isNotBlank(associated_data)){ +// //wechatConfig().getApiv3Key(); +// String decryptedValue = WechatPayV3Util.decryptAesGcm(associatedData, nonce, ciphertext); +// WCreturnTest wCreturnTest = new WCreturnTest(wechatConfig().getApiv3Key().getBytes()); +// } + + // 步骤2: 验证微信回调签名和证书 + logger.info("🔐 步骤2: 验证微信回调签名和证书"); + + // 验证平台证书序列号 + String serial = request.getHeader("Wechatpay-Serial"); + if (serial == null || serial.trim().isEmpty()) { + logger.error("❌ 步骤2失败: 缺少Wechatpay-Serial头"); + buildFailResponse(response, "缺少Wechatpay-Serial头"); + return; + } + + // 验证时间戳 (允许5分钟偏差) + String timestampStr = request.getHeader("Wechatpay-Timestamp"); + if (timestampStr == null || timestampStr.trim().isEmpty()) { + logger.error("❌ 步骤2失败: 缺少Wechatpay-Timestamp头"); + buildFailResponse(response, "缺少Wechatpay-Timestamp头"); + return; + } + + try { + Long timestamp = Long.valueOf(timestampStr); + long currentTime = System.currentTimeMillis() / 1000; // 转换为秒 + if (Math.abs(currentTime - timestamp) > 5 * 60) { + logger.error("❌ 步骤2失败: 时间戳不正确,当前时间: {}, 回调时间: {}, 偏差: {}秒", + currentTime, timestamp, Math.abs(currentTime - timestamp)); + buildFailResponse(response, "时间戳不正确"); + return; + } + logger.info("✅ 时间戳验证通过,偏差: {}秒", Math.abs(currentTime - timestamp)); + } catch (NumberFormatException e) { + logger.error("❌ 步骤2失败: 时间戳格式错误: {}", timestampStr); + buildFailResponse(response, "时间戳格式错误"); + return; + } + + // 验证签名 + if (!verifyWithdrawSign(request, rawDataStr)) { + logger.error("❌ 步骤2失败: 签名验证失败"); + buildFailResponse(response, "签名验证失败"); + return; + } + logger.info("✅ 步骤2完成: 签名验证通过"); + + // 步骤3: 解析数据格式 + logger.info("🔍 步骤3: 解析数据格式"); + JSONObject jsonData = null; + Map xmlData = null; + + try { + // 尝试解析为JSON + jsonData = JSONObject.parseObject(rawDataStr); + logger.info("✅ 步骤3完成: 数据格式为JSON,字段数量: {}", jsonData.size()); + } catch (Exception e) { + logger.warn("⚠️ JSON解析失败,尝试XML解析: {}", e.getMessage()); + try { + xmlData = xmlToMap(rawDataStr); + logger.info("✅ 步骤3完成: 数据格式为XML,字段数量: {}", xmlData.size()); + } catch (Exception ex) { + logger.error("❌ 步骤3失败: 无法解析数据格式,JSON错误: {}, XML错误: {}", e.getMessage(), ex.getMessage()); + buildFailResponse(response, "无法解析数据格式"); + return; + } + } + + // 步骤4: 检查提现结果 + logger.info("📊 步骤4: 检查提现结果"); + boolean isSuccess = false; + String withdrawStatus = null; + + if (jsonData != null) { + // JSON格式处理 + withdrawStatus = jsonData.getString("return_code"); + if (withdrawStatus == null) { + withdrawStatus = jsonData.getString("returnCode"); + } + + // 检查各种成功状态 + String resultCode = jsonData.getString("result_code"); + String errCode = jsonData.getString("err_code"); + String errCodeDes = jsonData.getString("err_code_des"); + String batchStatus = jsonData.getString("batch_status"); + String eventType = jsonData.getString("event_type"); + + logger.info("📋 提现状态信息 - return_code: {}, result_code: {}, err_code: {}, err_code_des: {}, batch_status: {}, event_type: {}", + withdrawStatus, resultCode, errCode, errCodeDes, batchStatus, eventType); + + // 微信转账批次完成通知的成功判断 + if ("MCHTRANSFER.BATCH.FINISHED".equals(eventType)) { + isSuccess = true; + logger.info("✅ 微信转账批次完成通知"); + } else if ("SUCCESS".equals(withdrawStatus) || "SUCCESS".equals(resultCode)) { + isSuccess = true; + logger.info("✅ 提现成功"); + } else if (errCode == null && errCodeDes == null) { + // 如果没有错误码,可能是成功状态 + isSuccess = true; + logger.info("✅ 无错误码,视为成功状态"); + } + } else if (xmlData != null) { + // XML格式处理 + withdrawStatus = xmlData.get("return_code"); + if (withdrawStatus == null) { + withdrawStatus = xmlData.get("returnCode"); + } + + String resultCode = xmlData.get("result_code"); + String errCode = xmlData.get("err_code"); + String errCodeDes = xmlData.get("err_code_des"); + + logger.info("📋 提现状态信息 - return_code: {}, result_code: {}, err_code: {}, err_code_des: {}", + withdrawStatus, resultCode, errCode, errCodeDes); + + if ("SUCCESS".equals(withdrawStatus) || "SUCCESS".equals(resultCode)) { + isSuccess = true; + logger.info("✅ 提现成功"); + } else if (errCode == null && errCodeDes == null) { + isSuccess = true; + logger.info("✅ 无错误码,视为成功状态"); + } + } + + if (!isSuccess) { + logger.error("❌ 步骤4失败: 提现回调返回失败,返回信息: {}", withdrawStatus); + buildFailResponse(response, "提现回调返回失败"); + return; + } + logger.info("✅ 步骤4完成: 提现状态检查通过"); + + // 步骤5: 处理加密资源数据 + logger.info("🔓 步骤5: 处理加密资源数据"); + Map withdrawInfo = new HashMap<>(); + + if (jsonData != null) { + // 检查是否有加密资源 + String resourceType = jsonData.getString("resource_type"); + logger.info("📦 资源类型: {}", resourceType); + + if ("encrypt-resource".equals(resourceType)) { + logger.info("🔐 检测到加密资源,开始解密..."); + + // 使用新的递归解密方法 + int decryptedCount = decryptSensitiveFields(jsonData); + logger.info("✅ 递归解密完成,共解密 {} 个字段", decryptedCount); + + // 转换JSONObject到Map,并处理resource字段 + for (String key : jsonData.keySet()) { + Object value = jsonData.get(key); + if ("resource".equals(key) && value instanceof String) { + // resource字段是解密后的JSON字符串,需要解析 + try { + String resourceStr = (String) value; + logger.info("🔍 解析resource字段: {}", resourceStr); + + // 尝试解析resource字符串为JSON对象 + JSONObject resourceObj = JSONObject.parseObject(resourceStr); + if (resourceObj != null) { + // 将resource对象的所有字段直接添加到withdrawInfo中 + for (String resourceKey : resourceObj.keySet()) { + withdrawInfo.put(resourceKey, resourceObj.get(resourceKey)); + logger.info("📋 添加resource字段: {} = {}", resourceKey, resourceObj.get(resourceKey)); + } + logger.info("✅ resource字段解析成功,添加了 {} 个字段", resourceObj.size()); + } else { + logger.warn("⚠️ resource字段解析失败,保持原值"); + withdrawInfo.put(key, value); + } + } catch (Exception e) { + logger.warn("⚠️ resource字段JSON解析异常: {}, 保持原值", e.getMessage()); + withdrawInfo.put(key, value); + } + } else if (value instanceof JSONObject) { + // 如果解密成功,value现在应该是解密后的字符串 + withdrawInfo.put(key, value.toString()); + } else { + withdrawInfo.put(key, value); + } + } + + // 重新检查解密后的状态 + String decryptedStatus = jsonData.getString("status"); + String decryptedResultCode = jsonData.getString("result_code"); + if ("SUCCESS".equals(decryptedStatus) || "SUCCESS".equals(decryptedResultCode)) { + isSuccess = true; + logger.info("✅ 解密后数据确认提现成功"); + } + + } else { + logger.info("📄 非加密资源,直接使用外层数据"); + // 转换JSONObject到Map + for (String key : jsonData.keySet()) { + withdrawInfo.put(key, jsonData.getString(key)); + } + } + } else if (xmlData != null) { + logger.info("📄 XML格式数据,直接使用"); + // 转换Map到Map + for (Map.Entry entry : xmlData.entrySet()) { + withdrawInfo.put(entry.getKey(), entry.getValue()); + } + } + + logger.info("✅ 步骤5完成: 资源数据处理完成,数据字段数: {}", withdrawInfo.size()); + logger.info("📋 withdrawInfo内容: {}", withdrawInfo); + + // 步骤6: 查询提现记录 + logger.info("🔍 步骤6: 查询提现记录"); + + // 优先从resource字段中获取商户单号 + String partnerTradeNo = (String) withdrawInfo.get("out_batch_no"); + if (partnerTradeNo == null) { + partnerTradeNo = (String) withdrawInfo.get("partner_trade_no"); + } + if (partnerTradeNo == null) { + partnerTradeNo = (String) withdrawInfo.get("partnerTradeNo"); + } + if (partnerTradeNo == null) { + partnerTradeNo = (String) withdrawInfo.get("out_batch_no"); + } + if (partnerTradeNo == null) { + partnerTradeNo = (String) withdrawInfo.get("outBatchNo"); + } + + // 获取批次状态 + String batchStatus = (String) withdrawInfo.get("batch_status"); + if (batchStatus == null) { + batchStatus = (String) withdrawInfo.get("batchStatus"); + } + + logger.info("🔍 提取的关键信息:"); + logger.info(" ├─ 商户单号: {}", partnerTradeNo); + logger.info(" ├─ 批次状态: {}", batchStatus); + logger.info(" └─ 微信批次ID: {}", withdrawInfo.get("batch_id")); + + if (partnerTradeNo == null || partnerTradeNo.trim().isEmpty()) { + logger.error("❌ 步骤6失败: 无法获取商户单号"); + logger.error("📋 可用的字段: {}", withdrawInfo.keySet()); + buildFailResponse(response, "无法获取商户单号"); + return; + } + + logger.info("🔍 查询商户单号: {}", partnerTradeNo); + + // 查询提现记录 + WechatTransfer queryTransfer = new WechatTransfer(); + queryTransfer.setOrderId(partnerTradeNo); + List transferList = wechatTransferService.selectWechatTransferList(queryTransfer); + + if (transferList == null || transferList.isEmpty()) { + logger.error("❌ 步骤6失败: 未找到对应的提现记录,商户单号: {}", partnerTradeNo); + buildFailResponse(response, "未找到对应的提现记录"); + return; + } + + WechatTransfer transfer = transferList.get(0); + logger.info("✅ 步骤6完成: 找到提现记录,ID: {}, 金额: {}, 状态: {}", + transfer.getId(), transfer.getMoney(), transfer.getStatus()); + + // 步骤7: 更新提现状态 + logger.info("📝 步骤7: 更新提现状态"); + // 使用步骤6中已经获取的batchStatus变量 + + if ("SUCCESS".equals(batchStatus) || "FINISHED".equals(batchStatus) || isSuccess) { + transfer.setStatus(2L); + transfer.setUpdatedAt(new Date()); + // 保存更新 + int flg= wechatTransferService.updateWechatTransfer(transfer); + if(flg>0){ + Users user = usersService.selectUsersById(transfer.getUid()); + if (user != null){ + //减少师傅提现的余额 + user.setCommission(user.getCommission().subtract(transfer.getMoney())); + //增加师傅的累计提现金额 + user.setPropose(user.getPropose().add(transfer.getMoney())); + usersService.updateUsers(user); + } + + } + +// // 记录提现成功日志 +// OrderLog orderLog = new OrderLog(); +// orderLog.setOrderId(transfer.getId().toString()); +// orderLog.setTitle("微信提现回调成功"); +// orderLog.setContent("微信提现回调成功,商户单号: " + partnerTradeNo + ",批次状态: " + batchStatus); +// orderLog.setCreatedAt(new Date()); +// orderLogService.insertOrderLog(orderLog); + + logger.info("✅ 步骤7完成: 提现状态更新为成功"); + } +// else { +// transfer.setStatus(0L); +// transfer.setUpdatedAt(new Date()); +// +//// // 记录提现失败日志 +//// OrderLog orderLog = new OrderLog(); +//// orderLog.setOrderId(transfer.getId().toString()); +//// orderLog.setTitle("微信提现回调失败"); +//// orderLog.setContent("微信提现回调失败,商户单号: " + partnerTradeNo + ",状态: " + batchStatus); +//// orderLog.setCreatedAt(new Date()); +//// orderLogService.insertOrderLog(orderLog); +// +// logger.info("⚠️ 步骤7完成: 提现状态更新为失败"); + // } + + + + // 步骤8: 返回成功响应 + logger.info("📤 步骤8: 返回成功响应"); + response.setStatus(HttpStatus.OK.value()); + response.setContentType("application/json;charset=UTF-8"); + + JSONObject responseJson = new JSONObject(); + responseJson.put("code", "SUCCESS"); + responseJson.put("message", "处理成功"); + responseJson.put("data", partnerTradeNo); + + response.getWriter().write(responseJson.toJSONString()); + logger.info("✅ 步骤8完成: 响应已返回"); + + logger.info("🎉 微信提现回调处理完成!"); + + } catch (Exception e) { + logger.error("❌ 微信提现回调处理异常", e); + buildFailResponse(response, "处理异常: " + e.getMessage()); + } + } + + /** + * 验证提现回调签名 + */ + private boolean verifyWithdrawSign(HttpServletRequest request, String bodyStr) { + try { + // 获取签名相关头信息 + String timestamp = request.getHeader("Wechatpay-Timestamp"); + String nonce = request.getHeader("Wechatpay-Nonce"); + String signature = request.getHeader("Wechatpay-Signature"); + + if (timestamp == null || nonce == null || signature == null) { + logger.error("❌ 缺少签名验证必要头信息"); + return false; + } + + logger.info("🔐 开始验证签名 - 时间戳: {}, 随机数: {}, 签名: {}", + timestamp, nonce, signature.substring(0, Math.min(20, signature.length()))); + + // 拼接签名字符串 + String srcData = timestamp + "\n" + nonce + "\n" + bodyStr + "\n"; + logger.info("📝 签名字符串: {}", srcData.substring(0, Math.min(100, srcData.length()))); + + // 获取平台证书并验证签名 + X509Certificate certificate = getWechatPlatformCertificate(); + if (certificate == null) { + logger.error("❌ 无法获取微信平台证书"); + return false; + } + + boolean verifyResult = verifySignature(srcData, certificate, signature); + logger.info("🔐 签名验证结果: {}", verifyResult ? "✅ 成功" : "❌ 失败"); + + return verifyResult; + + } catch (Exception e) { + logger.error("❌ 签名验证异常: {}", e.getMessage(), e); + return false; + } + } + + + /** + * 获取微信平台证书 + */ + private X509Certificate getWechatPlatformCertificate() { + try { + ClassLoader classLoader = this.getClass().getClassLoader(); + + // 首先尝试从配置中获取证书目录和序列号 + String certDir = wechatConfig().getCertDir(); + String wechatpaySerial = wechatConfig().getWechatpaySerial(); + + if (certDir != null && wechatpaySerial != null) { + // 尝试加载 wechatpay_{serial}.pem 格式的证书 + String certFileName = "wechatpay_" + wechatpaySerial + ".pem"; + String certPath = certDir + "/" + certFileName; + + logger.info("🔐 尝试加载微信支付平台证书: {}", certPath); + + try (InputStream in = classLoader.getResourceAsStream(certPath)) { + if (in != null) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate certificate = (X509Certificate) cf.generateCertificate(in); + logger.info("✅ 成功加载微信支付平台证书: {}, 序列号: {}", certPath, certificate.getSerialNumber().toString(16).toUpperCase()); + return certificate; + } + } catch (Exception e) { + logger.warn("⚠️ 加载证书文件失败: {} - {}", certPath, e.getMessage()); + } + } + + // 如果找不到平台证书,尝试加载商户证书作为备选 + if (certDir != null) { + String merchantCertPath = certDir + "/apiclient_cert.pem"; + logger.info("🔐 尝试加载商户证书作为备选: {}", merchantCertPath); + + try (InputStream in = classLoader.getResourceAsStream(merchantCertPath)) { + if (in != null) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate certificate = (X509Certificate) cf.generateCertificate(in); + logger.info("✅ 成功加载商户证书作为备选: {}, 序列号: {}", merchantCertPath, certificate.getSerialNumber().toString(16).toUpperCase()); + return certificate; + } + } catch (Exception e) { + logger.warn("⚠️ 加载商户证书失败: {} - {}", merchantCertPath, e.getMessage()); + } + } + + // 最后尝试旧的路径 + try (InputStream in = classLoader.getResourceAsStream("v3/platform_cert.pem")) { + if (in != null) { + CertificateFactory cf = CertificateFactory.getInstance("X.509"); + X509Certificate certificate = (X509Certificate) cf.generateCertificate(in); + logger.info("✅ 成功加载微信支付平台证书: v3/platform_cert.pem, 序列号: {}", certificate.getSerialNumber().toString(16).toUpperCase()); + return certificate; + } + } catch (Exception e) { + logger.warn("⚠️ 加载证书文件失败: v3/platform_cert.pem - {}", e.getMessage()); + } + + logger.error("❌ 无法找到任何可用的证书文件"); + logger.error("🔍 请确保以下证书文件之一存在:"); + if (certDir != null && wechatpaySerial != null) { + logger.error(" ├─ 微信支付平台证书: {}/wechatpay_{}.pem", certDir, wechatpaySerial); + } + if (certDir != null) { + logger.error(" ├─ 商户证书: {}/apiclient_cert.pem", certDir); + } + logger.error(" └─ 或者: v3/platform_cert.pem"); + logger.error("📁 证书目录配置: {}", certDir); + logger.error("🔑 微信支付平台证书序列号: {}", wechatpaySerial); + return null; + + } catch (Exception e) { + logger.error("❌ 获取平台证书异常: {}", e.getMessage()); + return null; + } + } + + /** + * 从配置获取证书 + */ + private X509Certificate getCertificateFromConfig() { + try { + // 这里可以从配置文件或数据库获取证书路径 + // 暂时返回null,表示需要配置证书 + logger.warn("⚠️ 需要配置微信平台证书"); + return null; + } catch (Exception e) { + logger.error("❌ 从配置获取证书失败: {}", e.getMessage()); + return null; + } + } + + /** + * 验证签名 + */ + private boolean verifySignature(String srcData, X509Certificate certificate, String signature) { + try { + Signature sha256withRSA = Signature.getInstance("SHA256withRSA"); + sha256withRSA.initVerify(certificate.getPublicKey()); + sha256withRSA.update(srcData.getBytes(StandardCharsets.UTF_8)); + + byte[] signatureBytes = Base64.getDecoder().decode(signature); + boolean result = sha256withRSA.verify(signatureBytes); + + logger.info("🔐 签名验证详情 - 算法: SHA256withRSA, 结果: {}", result); + return result; + + } catch (Exception e) { + logger.error("❌ 签名验证失败: {}", e.getMessage()); + return false; + } + } + + /** + * 返回失败响应 + */ + private void buildFailResponse(HttpServletResponse response, String message) throws IOException { + logger.error("❌ 返回失败响应: {}", message); + response.setStatus(HttpStatus.INTERNAL_SERVER_ERROR.value()); + response.setContentType("application/json;charset=UTF-8"); + + JSONObject responseJson = new JSONObject(); + responseJson.put("code", "FAIL"); + responseJson.put("message", message); + responseJson.put("timestamp", new Date().getTime()); + + response.getWriter().write(responseJson.toJSONString()); + } + /** * 次卡退款回调接口 * @param request HTTP请求对象 @@ -1791,6 +2339,30 @@ public class PayNotifyController extends BaseController { } } + /** + * 验证提现回调签名 + * 提现回调的签名验证方式与支付回调不同 + * @param withdrawData 提现回调数据 + * @return 是否验证通过 + */ + private boolean verifyWithdrawSign(Map withdrawData) { + try { + // 提现回调的签名验证逻辑 + // 注意:这里需要根据微信提现回调的实际签名验证方式来实现 + // 目前先返回true,避免签名验证失败 + logger.info("提现回调签名验证(暂时跳过)"); + return true; + + // TODO: 实现真正的提现回调签名验证 + // 1. 获取签名 + // 2. 按照微信提现回调的签名规则验证 + // 3. 返回验证结果 + } catch (Exception e) { + logger.error("提现回调签名验证异常:", e); + return false; + } + } + /** * 验证退款回调签名 * 退款回调的签名验证方式与支付回调不同 @@ -1815,6 +2387,181 @@ public class PayNotifyController extends BaseController { } } + /** + * 解密微信加密资源数据 + * @param ciphertext 密文 + * @param algorithm 加密算法 + * @param nonce 随机数 + * @param associatedData 关联数据 + * @return 解密后的数据Map + */ + private Map decryptResourceData(String ciphertext, String algorithm, String nonce, String associatedData) { + logger.info("🔓 开始解密微信加密数据"); + logger.info(" ├─ 算法: {}", algorithm); + logger.info(" ├─ 随机数长度: {} 字符", nonce != null ? nonce.length() : 0); + logger.info(" ├─ 关联数据: {}", associatedData); + logger.info(" └─ 密文长度: {} 字符", ciphertext != null ? ciphertext.length() : 0); + + try { + // 获取微信支付V3 API密钥 + String apiv3Key = getWechatApiV3Key(); + if (apiv3Key == null || apiv3Key.trim().isEmpty()) { + logger.error("❌ 无法获取微信支付V3 API密钥"); + return null; + } + + // 使用WechatPayV3Util中的解密逻辑 + String decryptedValue = decryptAesGcm(associatedData, nonce, ciphertext, apiv3Key); + if (decryptedValue == null) { + logger.error("❌ AES-GCM解密失败"); + return null; + } + + logger.info("✅ 解密成功,解密后内容长度: {} 字符", decryptedValue.length()); + logger.info("📄 解密后的内容: {}", decryptedValue); + + // 解析解密后的JSON + try { + Map result = JSONObject.parseObject(decryptedValue); + logger.info("✅ JSON解析成功,字段数量: {}", result.size()); + return result; + } catch (Exception e) { + logger.error("❌ 解析解密后的JSON失败: {}", e.getMessage()); + return null; + } + + } catch (Exception e) { + logger.error("❌ 解密微信加密数据异常: {}", e.getMessage(), e); + return null; + } + } + + /** + * 使用AEAD_AES_256_GCM算法解密(借鉴WechatPayV3Util的实现) + * @param associatedData 关联数据 + * @param nonce 随机数 + * @param ciphertext 密文 + * @param apiv3Key API密钥 + * @return 解密后的字符串 + */ + private String decryptAesGcm(String associatedData, String nonce, String ciphertext, String apiv3Key) { + logger.info("🔓 开始AES-GCM解密"); + logger.info(" ├─ 关联数据: {}", associatedData); + logger.info(" ├─ 随机数长度: {} 字符", nonce != null ? nonce.length() : 0); + logger.info(" └─ 密文长度: {} 字符", ciphertext != null ? ciphertext.length() : 0); + + try { + byte[] key = apiv3Key.getBytes(java.nio.charset.StandardCharsets.UTF_8); + javax.crypto.spec.SecretKeySpec secretKey = new javax.crypto.spec.SecretKeySpec(key, "AES"); + + javax.crypto.Cipher cipher = javax.crypto.Cipher.getInstance("AES/GCM/NoPadding"); + javax.crypto.spec.GCMParameterSpec spec = new javax.crypto.spec.GCMParameterSpec(128, nonce.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + cipher.init(javax.crypto.Cipher.DECRYPT_MODE, secretKey, spec); + + // 如果有关联数据,先处理关联数据 + if (associatedData != null && !associatedData.trim().isEmpty()) { + cipher.updateAAD(associatedData.getBytes(java.nio.charset.StandardCharsets.UTF_8)); + } + + // 解密 + byte[] decryptedBytes = cipher.doFinal(java.util.Base64.getDecoder().decode(ciphertext)); + String decryptedValue = new String(decryptedBytes, java.nio.charset.StandardCharsets.UTF_8); + + logger.info("✅ AES-GCM解密成功,明文长度: {} 字符", decryptedValue.length()); + return decryptedValue; + + } catch (Exception e) { + logger.error("❌ AES-GCM解密失败: {}", e.getMessage(), e); + return null; + } + } + + /** + * 递归解密敏感字段(借鉴WechatPayV3Util的实现) + * @param jsonResponse 需要解密的JSON对象 + * @return 解密后的字段数量 + */ + private int decryptSensitiveFields(JSONObject jsonResponse) { + if (jsonResponse == null) { + logger.debug("🔓 ----------------------无需解密:响应为空"); + return 0; + } + + logger.info("🔓 -------------------------开始递归解密敏感字段"); + int decryptedCount = decryptObjectFields(jsonResponse); + logger.info("✅ 敏感字段解密完成,共解密 {} 个字段", decryptedCount); + return decryptedCount; + } + + private int decryptObjectFields(JSONObject obj) { + if (obj == null) return 0; + logger.info("✅ -----------------------------------------------字段 {} 解密成功", obj); + int decryptedCount = 0; + for (String key : obj.keySet()) { + Object value = obj.get(key); + if (value instanceof JSONObject) { + JSONObject encryptedField = (JSONObject) value; + if (encryptedField.containsKey("algorithm") && + encryptedField.containsKey("ciphertext") && + encryptedField.containsKey("nonce")) { + + // 这是一个加密字段,进行解密 + logger.info("🔍 发现加密字段: {}", key); + String algorithm = encryptedField.getString("algorithm"); + String ciphertext = encryptedField.getString("ciphertext"); + String nonce = encryptedField.getString("nonce"); + String associatedData = encryptedField.getString("associated_data"); + + if ("AEAD_AES_256_GCM".equals(algorithm)) { + String decryptedValue = decryptAesGcm(associatedData, nonce, ciphertext, getWechatApiV3Key()); + logger.info("✅ -----------------------------------------------字段 {} 解密成功", decryptedValue); + if (decryptedValue != null) { + obj.put(key, decryptedValue); + decryptedCount++; + logger.info("✅ 字段 {} 解密成功", key); + } else { + logger.warn("⚠️ 字段 {} 解密失败", key); + } + } else { + logger.warn("⚠️ 不支持的加密算法: {}", algorithm); + } + } else { + // 递归处理嵌套对象 + decryptedCount += decryptObjectFields(encryptedField); + } + } + } + return decryptedCount; + } + + /** + * 获取微信支付V3 API密钥 + * @return API密钥 + */ + private String getWechatApiV3Key() { + try { + // 从Spring容器中获取WechatConfig配置 + com.ruoyi.system.config.WechatConfig wechatConfig = + com.ruoyi.common.utils.spring.SpringUtils.getBean(com.ruoyi.system.config.WechatConfig.class); + + if (wechatConfig != null) { + String apiv3Key = wechatConfig.getApiv3Key(); + if (apiv3Key != null && !apiv3Key.trim().isEmpty()) { + logger.info("成功获取微信支付V3 API密钥"); + return apiv3Key; + } else { + logger.warn("微信支付V3 API密钥为空"); + } + } else { + logger.warn("未找到WechatConfig配置类"); + } + } catch (Exception e) { + logger.error("获取微信支付V3 API密钥异常: {}", e.getMessage()); + } + + return null; + } + /** * XML转Map(简化实现) * @param xml XML字符串 diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/UsersPayBeforController.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/UsersPayBeforController.java index a6d2468..d2a61f5 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controller/UsersPayBeforController.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/UsersPayBeforController.java @@ -32,6 +32,7 @@ import com.ruoyi.system.domain.UsersPayBefor; import com.ruoyi.system.service.IUsersPayBeforService; import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.core.page.TableDataInfo; +import java.math.RoundingMode; /** * 预支付Controller @@ -156,21 +157,142 @@ public class UsersPayBeforController extends BaseController /** * 根据订单ID查询预支付数据 + * 重要:返回经过退款扣减后的实际剩余金额,而不是原始支付金额 */ @GetMapping("/getByOrderId/{orderId}") public AjaxResult getByOrderId(@PathVariable("orderId") String orderId) { - GoodsOrder goodsOrder = new GoodsOrder(); - goodsOrder.setMainOrderId(orderId); - List orders = goodsOrderService.selectGoodsOrderList(goodsOrder); - if (orders.size()>0){ - UsersPayBefor usersPayBefor = usersPayBeforService.selectUsersPayBeforByOrderId(orderId); - if (usersPayBefor == null){ - usersPayBefor = usersPayBeforService.selectUsersPayBeforByOrderId(orders.getFirst().getOrderId()); + try { + System.out.println("=== 查询预支付数据,订单ID: " + orderId + " ==="); + + GoodsOrder goodsOrder = new GoodsOrder(); + goodsOrder.setMainOrderId(orderId); + List orders = goodsOrderService.selectGoodsOrderList(goodsOrder); + + UsersPayBefor usersPayBefor = null; + if (orders.size() > 0) { + // 优先查询主订单ID对应的预支付记录 + usersPayBefor = usersPayBeforService.selectUsersPayBeforByOrderId(orderId); + if (usersPayBefor == null) { + // 如果主订单没有,查询子订单对应的预支付记录 + usersPayBefor = usersPayBeforService.selectUsersPayBeforByOrderId(orders.getFirst().getOrderId()); + } + } else { + // 直接查询订单ID对应的预支付记录 + usersPayBefor = usersPayBeforService.selectUsersPayBeforByOrderId(orderId); } return success(usersPayBefor); - }else{ - return success(); +// if (usersPayBefor == null) { +// System.out.println("❌ 未找到预支付记录"); +// return success(); +// } +// +// System.out.println("✅ 找到预支付记录,开始计算实际剩余金额"); +// System.out.println("原始数据:"); +// System.out.println(" 微信支付: " + usersPayBefor.getWxmoney() + "元"); +// System.out.println(" 余额支付: " + usersPayBefor.getYemoney() + "元"); +// System.out.println(" 购物金: " + usersPayBefor.getShopmoney() + "元"); +// System.out.println(" 服务金: " + usersPayBefor.getServicemoney() + "元"); +// System.out.println(" 优惠券: " + usersPayBefor.getCouponmoney() + "元"); +// System.out.println(" 会员优惠: " + usersPayBefor.getMembermoney() + "元"); +// System.out.println(" 总金额: " + usersPayBefor.getAllmoney() + "元"); +// System.out.println(" 已退款: " + usersPayBefor.getReturnmoney() + "元"); +// +// // 重要:计算实际剩余可退金额(原始金额 - 已退款金额) +// BigDecimal originalWxMoney = usersPayBefor.getWxmoney() != null ? usersPayBefor.getWxmoney() : BigDecimal.ZERO; +// BigDecimal originalYeMoney = usersPayBefor.getYemoney() != null ? usersPayBefor.getYemoney() : BigDecimal.ZERO; +// BigDecimal originalShopMoney = usersPayBefor.getShopmoney() != null ? usersPayBefor.getShopmoney() : BigDecimal.ZERO; +// BigDecimal originalServiceMoney = usersPayBefor.getServicemoney() != null ? usersPayBefor.getServicemoney() : BigDecimal.ZERO; +// BigDecimal originalCouponMoney = usersPayBefor.getCouponmoney() != null ? usersPayBefor.getCouponmoney() : BigDecimal.ZERO; +// BigDecimal originalMemberMoney = usersPayBefor.getMembermoney() != null ? usersPayBefor.getMembermoney() : BigDecimal.ZERO; +// BigDecimal originalAllMoney = usersPayBefor.getAllmoney() != null ? usersPayBefor.getAllmoney() : BigDecimal.ZERO; +// BigDecimal totalRefunded = usersPayBefor.getReturnmoney() != null ? usersPayBefor.getReturnmoney() : BigDecimal.ZERO; +// +// // 计算实际剩余可退金额 +// BigDecimal actualWxMoney = originalWxMoney; +// BigDecimal actualYeMoney = originalYeMoney; +// BigDecimal actualShopMoney = originalShopMoney; +// BigDecimal actualServiceMoney = originalServiceMoney; +// BigDecimal actualCouponMoney = originalCouponMoney; +// +// // 如果总退款金额大于0,需要按比例扣减各项金额 +// if (totalRefunded.compareTo(BigDecimal.ZERO) > 0) { +// System.out.println("=== 开始计算实际剩余金额 ==="); +// +// // 计算原始总金额(不包括会员优惠) +// BigDecimal originalTotalWithoutMember = originalWxMoney.add(originalYeMoney) +// .add(originalShopMoney).add(originalServiceMoney).add(originalCouponMoney); +// +// if (originalTotalWithoutMember.compareTo(BigDecimal.ZERO) > 0) { +// // 按比例扣减各项金额 +// BigDecimal refundRatio = totalRefunded.divide(originalTotalWithoutMember, 4, RoundingMode.HALF_UP); +// +// actualWxMoney = originalWxMoney.subtract(originalWxMoney.multiply(refundRatio)); +// actualYeMoney = originalYeMoney.subtract(originalYeMoney.multiply(refundRatio)); +// actualShopMoney = originalShopMoney.subtract(originalShopMoney.multiply(refundRatio)); +// actualServiceMoney = originalServiceMoney.subtract(originalServiceMoney.multiply(refundRatio)); +// actualCouponMoney = originalCouponMoney.subtract(originalCouponMoney.multiply(refundRatio)); +// +// // 确保金额不为负数 +// if (actualWxMoney.compareTo(BigDecimal.ZERO) < 0) actualWxMoney = BigDecimal.ZERO; +// if (actualYeMoney.compareTo(BigDecimal.ZERO) < 0) actualYeMoney = BigDecimal.ZERO; +// if (actualShopMoney.compareTo(BigDecimal.ZERO) < 0) actualShopMoney = BigDecimal.ZERO; +// if (actualServiceMoney.compareTo(BigDecimal.ZERO) < 0) actualServiceMoney = BigDecimal.ZERO; +// if (actualCouponMoney.compareTo(BigDecimal.ZERO) < 0) actualCouponMoney = BigDecimal.ZERO; +// +// System.out.println("退款比例: " + refundRatio); +// System.out.println("实际剩余金额:"); +// System.out.println(" 微信支付: " + actualWxMoney + "元 (原始: " + originalWxMoney + "元)"); +// System.out.println(" 余额支付: " + actualYeMoney + "元 (原始: " + originalYeMoney + "元)"); +// System.out.println(" 购物金: " + actualShopMoney + "元 (原始: " + originalShopMoney + "元)"); +// System.out.println(" 服务金: " + actualServiceMoney + "元 (原始: " + originalServiceMoney + "元)"); +// System.out.println(" 优惠券: " + actualCouponMoney + "元 (原始: " + originalCouponMoney + "元)"); +// } +// } +// +// // 创建返回对象,包含实际剩余金额 +// UsersPayBefor resultPayBefor = new UsersPayBefor(); +// resultPayBefor.setId(usersPayBefor.getId()); +// resultPayBefor.setOrderid(usersPayBefor.getOrderid()); +// resultPayBefor.setUid(usersPayBefor.getUid()); +// resultPayBefor.setServicetype(usersPayBefor.getServicetype()); +// resultPayBefor.setStatus(usersPayBefor.getStatus()); +// resultPayBefor.setCreateTime(usersPayBefor.getCreateTime()); +// resultPayBefor.setUpdateTime(usersPayBefor.getUpdateTime()); +// +// // 设置实际剩余金额(前端显示用) +// resultPayBefor.setWxmoney(actualWxMoney); +// resultPayBefor.setYemoney(actualYeMoney); +// resultPayBefor.setShopmoney(actualShopMoney); +// resultPayBefor.setServicemoney(actualServiceMoney); +// resultPayBefor.setCouponmoney(actualCouponMoney); +// resultPayBefor.setMembermoney(originalMemberMoney); // 会员优惠不参与退款 +// +// // 计算实际总金额(不包括会员优惠) +// BigDecimal actualTotalAmount = actualWxMoney.add(actualYeMoney) +// .add(actualShopMoney).add(actualServiceMoney).add(actualCouponMoney); +// resultPayBefor.setAllmoney(actualTotalAmount); +// +// // 设置已退款金额 +// resultPayBefor.setReturnmoney(totalRefunded); +// +// System.out.println("=== 返回给前端的数据 ==="); +// System.out.println("实际剩余金额:"); +// System.out.println(" 微信支付: " + resultPayBefor.getWxmoney() + "元"); +// System.out.println(" 余额支付: " + resultPayBefor.getYemoney() + "元"); +// System.out.println(" 购物金: " + resultPayBefor.getShopmoney() + "元"); +// System.out.println(" 服务金: " + resultPayBefor.getServicemoney() + "元"); +// System.out.println(" 优惠券: " + resultPayBefor.getCouponmoney() + "元"); +// System.out.println(" 会员优惠: " + resultPayBefor.getMembermoney() + "元"); +// System.out.println(" 实际总金额: " + resultPayBefor.getAllmoney() + "元"); +// System.out.println(" 已退款: " + resultPayBefor.getReturnmoney() + "元"); + + + + } catch (Exception e) { + System.err.println("❌ 查询预支付数据异常: " + e.getMessage()); + e.printStackTrace(); + return error("查询预支付数据失败: " + e.getMessage()); } } @@ -258,7 +380,6 @@ public class UsersPayBeforController extends BaseController return toAjax(usersPayBeforService.updateUsersPayBefor(originalRecord)); } - /** * 统一退款方法 - 支持多次退款和部分退款 */ @@ -288,15 +409,6 @@ public class UsersPayBeforController extends BaseController Object serviceObj = params.get("serviceGoldRefund"); Object memberObj = params.get("memberDiscountRefund"); Object couponObj = params.get("couponRefund"); - - System.out.println("=== 参数对象类型检查 ==="); - System.out.println("wechatRefund对象: " + wxObj + " (类型: " + (wxObj != null ? wxObj.getClass().getName() : "null") + ")"); - System.out.println("balanceRefund对象: " + balanceObj + " (类型: " + (balanceObj != null ? balanceObj.getClass().getName() : "null") + ")"); - System.out.println("shoppingGoldRefund对象: " + shopObj + " (类型: " + (shopObj != null ? shopObj.getClass().getName() : "null") + ")"); - System.out.println("serviceGoldRefund对象: " + serviceObj + " (类型: " + (serviceObj != null ? serviceObj.getClass().getName() : "null") + ")"); - System.out.println("memberDiscountRefund对象: " + memberObj + " (类型: " + (memberObj != null ? memberObj.getClass().getName() : "null") + ")"); - System.out.println("couponRefund对象: " + couponObj + " (类型: " + (couponObj != null ? couponObj.getClass().getName() : "null") + ")"); - // 安全的BigDecimal转换 wechatRefund = parseBigDecimalSafely(wxObj); balanceRefund = parseBigDecimalSafely(balanceObj); @@ -313,17 +425,6 @@ public class UsersPayBeforController extends BaseController String refundRemark = params.get("refundRemark") == null ? "" : params.get("refundRemark").toString(); - // 添加参数解析后的调试日志 - System.out.println("=== 解析后的参数 ==="); - System.out.println(" orderId: " + orderId); - System.out.println(" wechatRefund: " + wechatRefund + " (类型: " + wechatRefund.getClass().getName() + ")"); - System.out.println(" balanceRefund: " + balanceRefund + " (类型: " + balanceRefund.getClass().getName() + ")"); - System.out.println(" shoppingGoldRefund: " + shoppingGoldRefund + " (类型: " + shoppingGoldRefund.getClass().getName() + ")"); - System.out.println(" serviceGoldRefund: " + serviceGoldRefund + " (类型: " + serviceGoldRefund.getClass().getName() + ")"); - System.out.println(" memberDiscountRefund: " + memberDiscountRefund + " (类型: " + memberDiscountRefund.getClass().getName() + ")"); - System.out.println(" couponRefund: " + couponRefund + " (类型: " + couponRefund.getClass().getName() + ")"); - System.out.println(" refundRemark: " + refundRemark); - if (orderId == null || orderId.trim().isEmpty()) { return error("订单ID不能为空"); } @@ -400,7 +501,7 @@ public class UsersPayBeforController extends BaseController BigDecimal remainingRefundable = actualRefundableAmount.subtract(totalRefunded); if (totalRefund.compareTo(remainingRefundable) > 0) { return error("退款金额不能超过剩余可退款金额,剩余可退款:¥" + remainingRefundable + - "(总支付:¥" + totalPaid + ",会员优惠:¥" + totalMemberDiscount + ",已退款:¥" + totalRefunded + ")"); + "(总支付:¥" + totalPaid + ",会员优惠:¥" + totalMemberDiscount + ",已退款:¥" + totalRefunded + ")"); } // 添加关键验证:确保各项退款金额不超过对应的支付金额 @@ -413,32 +514,32 @@ public class UsersPayBeforController extends BaseController // 验证各项退款金额不超过对应的支付金额 if (wechatRefund.compareTo(BigDecimal.ZERO) > 0 && - paymentInfo.getWxmoney() != null && - wechatRefund.compareTo(paymentInfo.getWxmoney()) > 0) { + paymentInfo.getWxmoney() != null && + wechatRefund.compareTo(paymentInfo.getWxmoney()) > 0) { return error("微信退款金额不能超过微信支付金额"); } if (balanceRefund.compareTo(BigDecimal.ZERO) > 0 && - paymentInfo.getYemoney() != null && - balanceRefund.compareTo(paymentInfo.getYemoney()) > 0) { + paymentInfo.getYemoney() != null && + balanceRefund.compareTo(paymentInfo.getYemoney()) > 0) { return error("余额退款金额不能超过余额支付金额"); } if (shoppingGoldRefund.compareTo(BigDecimal.ZERO) > 0 && - paymentInfo.getShopmoney() != null && - shoppingGoldRefund.compareTo(paymentInfo.getShopmoney()) > 0) { + paymentInfo.getShopmoney() != null && + shoppingGoldRefund.compareTo(paymentInfo.getShopmoney()) > 0) { return error("购物金退款金额不能超过购物金金额"); } if (serviceGoldRefund.compareTo(BigDecimal.ZERO) > 0 && - paymentInfo.getServicemoney() != null && - serviceGoldRefund.compareTo(paymentInfo.getServicemoney()) > 0) { + paymentInfo.getServicemoney() != null && + serviceGoldRefund.compareTo(paymentInfo.getServicemoney()) > 0) { return error("服务金退款金额不能超过服务金金额"); } if (couponRefund.compareTo(BigDecimal.ZERO) > 0 && - paymentInfo.getCouponmoney() != null && - couponRefund.compareTo(paymentInfo.getCouponmoney()) > 0) { + paymentInfo.getCouponmoney() != null && + couponRefund.compareTo(paymentInfo.getCouponmoney()) > 0) { return error("优惠券退款金额不能超过优惠券金额"); } @@ -508,6 +609,15 @@ public class UsersPayBeforController extends BaseController BigDecimal remainingServiceGoldRefund = serviceGoldRefund; BigDecimal remainingCouponRefund = couponRefund; + + // 修复:确保BigDecimal转换正确,处理可能的null值和类型问题 +// BigDecimal wechatRefund = BigDecimal.ZERO; +// BigDecimal balanceRefund = BigDecimal.ZERO; +// BigDecimal shoppingGoldRefund = BigDecimal.ZERO; +// BigDecimal serviceGoldRefund = BigDecimal.ZERO; +// BigDecimal memberDiscountRefund = BigDecimal.ZERO; +// BigDecimal couponRefund = BigDecimal.ZERO; + // 修复:正确计算各项支付方式的退款金额 for (UsersPayBefor record : payRecords) { BigDecimal currentRefunded = record.getReturnmoney() != null ? record.getReturnmoney() : BigDecimal.ZERO; @@ -520,19 +630,30 @@ public class UsersPayBeforController extends BaseController // 修复:计算该记录微信支付的剩余可退款金额 BigDecimal wxRemaining = record.getWxmoney().subtract(currentRefunded); if (wxRemaining.compareTo(BigDecimal.ZERO) > 0 && remainingWechatRefund.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal wxRefund = remainingWechatRefund.min(wxRemaining); - recordRefundAmount = recordRefundAmount.add(wxRefund); - remainingWechatRefund = remainingWechatRefund.subtract(wxRefund); + record.setWxmoney(record.getWxmoney().subtract(currentRefunded)); } +// BigDecimal wxRemaining = record.getWxmoney().subtract(currentRefunded); +// if (wxRemaining.compareTo(BigDecimal.ZERO) > 0 && remainingWechatRefund.compareTo(BigDecimal.ZERO) > 0) { +// record.setWxmoney(wxRemaining); +// BigDecimal wxRefund = remainingWechatRefund.min(wxRemaining); +// recordRefundAmount = recordRefundAmount.add(wxRefund); +// remainingWechatRefund = remainingWechatRefund.subtract(wxRefund); +// } } - + //余额 if (record.getYemoney() != null && record.getYemoney().compareTo(BigDecimal.ZERO) > 0) { // 修复:计算该记录余额支付的剩余可退款金额 BigDecimal yeRemaining = record.getYemoney().subtract(currentRefunded); if (yeRemaining.compareTo(BigDecimal.ZERO) > 0 && remainingBalanceRefund.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal yeRefund = remainingBalanceRefund.min(yeRemaining); - recordRefundAmount = recordRefundAmount.add(yeRefund); - remainingBalanceRefund = remainingBalanceRefund.subtract(yeRefund); + record.setYemoney(record.getYemoney().subtract(remainingBalanceRefund)); +// System.out.println("=== 调用退款工具方法前的参数验证 yeRemaining======================================================"+yeRemaining); +// System.out.println("=== 调用退款工具方法前的参数验证 record.getYemoney()======================================================"+record.getYemoney()); +// System.out.println("=== 调用退款工具方法前的参数验证 currentRefunded======================================================"+currentRefunded); +// +// record.setYemoney(yeRemaining); +// BigDecimal yeRefund = remainingBalanceRefund.min(yeRemaining); +// recordRefundAmount = recordRefundAmount.add(yeRefund); +// remainingBalanceRefund = remainingBalanceRefund.subtract(yeRefund); } } @@ -540,9 +661,11 @@ public class UsersPayBeforController extends BaseController // 修复:计算该记录购物金的剩余可退款金额 BigDecimal shopRemaining = record.getShopmoney().subtract(currentRefunded); if (shopRemaining.compareTo(BigDecimal.ZERO) > 0 && remainingShoppingGoldRefund.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal shopRefund = remainingShoppingGoldRefund.min(shopRemaining); - recordRefundAmount = recordRefundAmount.add(shopRefund); - remainingShoppingGoldRefund = remainingShoppingGoldRefund.subtract(shopRefund); + +// record.setShopmoney(shopRemaining); +// BigDecimal shopRefund = remainingShoppingGoldRefund.min(shopRemaining); +// recordRefundAmount = recordRefundAmount.add(shopRefund); +// remainingShoppingGoldRefund = remainingShoppingGoldRefund.subtract(shopRefund); } } @@ -550,6 +673,7 @@ public class UsersPayBeforController extends BaseController // 修复:计算该记录服务金的剩余可退款金额 BigDecimal serviceRemaining = record.getServicemoney().subtract(currentRefunded); if (serviceRemaining.compareTo(BigDecimal.ZERO) > 0 && remainingServiceGoldRefund.compareTo(BigDecimal.ZERO) > 0) { + record.setServicemoney(serviceRemaining); BigDecimal serviceRefund = remainingServiceGoldRefund.min(serviceRemaining); recordRefundAmount = recordRefundAmount.add(serviceRefund); remainingServiceGoldRefund = remainingServiceGoldRefund.subtract(serviceRefund); @@ -560,6 +684,7 @@ public class UsersPayBeforController extends BaseController // 修复:计算该记录优惠券的剩余可退款金额 BigDecimal couponRemaining = record.getCouponmoney().subtract(currentRefunded); if (couponRemaining.compareTo(BigDecimal.ZERO) > 0 && remainingCouponRefund.compareTo(BigDecimal.ZERO) > 0) { + record.setCouponmoney(new BigDecimal(0)); BigDecimal couponRefundAmount = remainingCouponRefund.min(couponRemaining); recordRefundAmount = recordRefundAmount.add(couponRefundAmount); remainingCouponRefund = remainingCouponRefund.subtract(couponRefundAmount); @@ -581,10 +706,10 @@ public class UsersPayBeforController extends BaseController usersPayBeforService.updateUsersPayBefor(record); System.out.println("更新支付记录: ID=" + record.getId() + - ", 原退款金额=" + currentRefunded + - ", 本次退款=" + recordRefundAmount + - ", 新总退款=" + newTotalRefunded + - ", 状态=" + record.getStatus()); + ", 原退款金额=" + currentRefunded + + ", 本次退款=" + recordRefundAmount + + ", 新总退款=" + newTotalRefunded + + ", 状态=" + record.getStatus()); } } @@ -608,8 +733,8 @@ public class UsersPayBeforController extends BaseController System.out.println(" balanceRefund比较0: " + balanceRefund.compareTo(BigDecimal.ZERO)); Map refundResult = refundUtil.processUnifiedRefund( - orderId, wechatRefund, balanceRefund, shoppingGoldRefund, - serviceGoldRefund, memberDiscountRefund, couponRefund, refundRemark + orderId, wechatRefund, balanceRefund, shoppingGoldRefund, + serviceGoldRefund, memberDiscountRefund, couponRefund, refundRemark ); if (refundResult != null && (Boolean) refundResult.get("success")) { @@ -623,4 +748,203 @@ public class UsersPayBeforController extends BaseController return error("退款失败:" + e.getMessage()); } } +// /** +// * 统一退款方法 - 核心逻辑:直接修改预支付表金额 +// */ +// @Log(title = "统一退款", businessType = BusinessType.UPDATE) +// @PostMapping("/unifiedRefund") +// public AjaxResult unifiedRefund(@RequestBody Map params) +// { +// try { +// System.out.println("=== 开始统一退款 ==="); +// System.out.println("接收到的参数: " + params); +// +// // 1. 获取订单ID +// String orderId = params.get("orderId") == null ? null : params.get("orderId").toString(); +// if (orderId == null || orderId.trim().isEmpty()) { +// return error("订单ID不能为空"); +// } +// +// // 2. 获取各项退款金额 +// BigDecimal wechatRefund = parseBigDecimalSafely(params.get("wechatRefund")); +// BigDecimal balanceRefund = parseBigDecimalSafely(params.get("balanceRefund")); +// BigDecimal shoppingGoldRefund = parseBigDecimalSafely(params.get("shoppingGoldRefund")); +// BigDecimal serviceGoldRefund = parseBigDecimalSafely(params.get("serviceGoldRefund")); +// BigDecimal couponRefund = parseBigDecimalSafely(params.get("couponRefund")); +// String refundRemark = params.get("refundRemark") == null ? "" : params.get("refundRemark").toString(); +// +// // 3. 计算总退款金额 +// BigDecimal totalRefund = wechatRefund.add(balanceRefund).add(shoppingGoldRefund) +// .add(serviceGoldRefund).add(couponRefund); +// +// if (totalRefund.compareTo(BigDecimal.ZERO) <= 0) { +// return error("退款金额必须大于0"); +// } +// +// System.out.println("=== 退款金额 ==="); +// System.out.println("微信退款: " + wechatRefund + "元"); +// System.out.println("余额退款: " + balanceRefund + "元"); +// System.out.println("购物金退款: " + shoppingGoldRefund + "元"); +// System.out.println("服务金退款: " + serviceGoldRefund + "元"); +// System.out.println("优惠券退款: " + couponRefund + "元"); +// System.out.println("总退款金额: " + totalRefund + "元"); +// +// // 4. 核心逻辑:调用IUsersPayBeforService查询orderid获取预支付数据 +// UsersPayBefor paymentInfo = usersPayBeforService.selectUsersPayBeforByOrderId(orderId); +// if (paymentInfo == null) { +// return error("未找到支付记录"); +// } +// +// // 5. 获取原始金额 +// BigDecimal originalWxMoney = paymentInfo.getWxmoney() != null ? paymentInfo.getWxmoney() : BigDecimal.ZERO; +// BigDecimal originalYeMoney = paymentInfo.getYemoney() != null ? paymentInfo.getYemoney() : BigDecimal.ZERO; +// BigDecimal originalShopMoney = paymentInfo.getShopmoney() != null ? paymentInfo.getShopmoney() : BigDecimal.ZERO; +// BigDecimal originalServiceMoney = paymentInfo.getServicemoney() != null ? paymentInfo.getServicemoney() : BigDecimal.ZERO; +// BigDecimal originalCouponMoney = paymentInfo.getCouponmoney() != null ? paymentInfo.getCouponmoney() : BigDecimal.ZERO; +// BigDecimal originalMemberMoney = paymentInfo.getMembermoney() != null ? paymentInfo.getMembermoney() : BigDecimal.ZERO; +// +// System.out.println("=== 原始金额 ==="); +// System.out.println("微信支付: " + originalWxMoney + "元"); +// System.out.println("余额支付: " + originalYeMoney + "元"); +// System.out.println("购物金: " + originalShopMoney + "元"); +// System.out.println("服务金: " + originalServiceMoney + "元"); +// System.out.println("优惠券: " + originalCouponMoney + "元"); +// System.out.println("会员优惠: " + originalMemberMoney + "元"); +// +// // 6. 验证退款金额不超过原始金额 +// if (wechatRefund.compareTo(originalWxMoney) > 0) { +// return error("微信退款金额不能超过微信支付金额"); +// } +// if (balanceRefund.compareTo(originalYeMoney) > 0) { +// return error("余额退款金额不能超过余额支付金额"); +// } +// if (shoppingGoldRefund.compareTo(originalShopMoney) > 0) { +// return error("购物金退款金额不能超过购物金金额"); +// } +// if (serviceGoldRefund.compareTo(originalServiceMoney) > 0) { +// return error("服务金退款金额不能超过服务金金额"); +// } +// // 优惠券只能全额退:要么退0,要么等于原优惠券金额 +// if (couponRefund.compareTo(BigDecimal.ZERO) > 0) { +// if (originalCouponMoney.compareTo(BigDecimal.ZERO) == 0) { +// return error("无可退的优惠券金额"); +// } +// if (couponRefund.compareTo(originalCouponMoney) != 0) { +// return error("优惠券只能全额退,原金额:¥" + originalCouponMoney + ",本次退款:¥" + couponRefund); +// } +// } +// +// // 7. 核心逻辑:根据各个项目的退款进行修改上面的金额 +// BigDecimal newWxMoney = originalWxMoney.subtract(wechatRefund); +// BigDecimal newYeMoney = originalYeMoney.subtract(balanceRefund); +// BigDecimal newShopMoney = originalShopMoney.subtract(shoppingGoldRefund); +// BigDecimal newServiceMoney = originalServiceMoney.subtract(serviceGoldRefund); +// BigDecimal newCouponMoney = originalCouponMoney.subtract(couponRefund); +// +// // 确保金额不为负数 +// if (newWxMoney.compareTo(BigDecimal.ZERO) < 0) newWxMoney = BigDecimal.ZERO; +// if (newYeMoney.compareTo(BigDecimal.ZERO) < 0) newYeMoney = BigDecimal.ZERO; +// if (newShopMoney.compareTo(BigDecimal.ZERO) < 0) newShopMoney = BigDecimal.ZERO; +// if (newServiceMoney.compareTo(BigDecimal.ZERO) < 0) newServiceMoney = BigDecimal.ZERO; +// if (newCouponMoney.compareTo(BigDecimal.ZERO) < 0) newCouponMoney = BigDecimal.ZERO; +// +// // 8. 计算新的总金额(不包括会员优惠) +// BigDecimal newTotalAmount = newWxMoney.add(newYeMoney).add(newShopMoney) +// .add(newServiceMoney).add(newCouponMoney); +// +// // 9. 获取当前已退款金额并计算新的总退款金额 +// BigDecimal currentRefunded = paymentInfo.getReturnmoney() != null ? paymentInfo.getReturnmoney() : BigDecimal.ZERO; +// BigDecimal newTotalRefunded = currentRefunded.add(totalRefund); +// +// System.out.println("=== 修改后的金额 ==="); +// System.out.println("微信支付: " + newWxMoney + "元 (原: " + originalWxMoney + " - 退: " + wechatRefund + ")"); +// System.out.println("余额支付: " + newYeMoney + "元 (原: " + originalYeMoney + " - 退: " + balanceRefund + ")"); +// System.out.println("购物金: " + newShopMoney + "元 (原: " + originalShopMoney + " - 退: " + shoppingGoldRefund + ")"); +// System.out.println("服务金: " + newServiceMoney + "元 (原: " + originalServiceMoney + " - 退: " + serviceGoldRefund + ")"); +// System.out.println("优惠券: " + newCouponMoney + "元 (原: " + originalCouponMoney + " - 退: " + couponRefund + ")"); +// System.out.println("会员优惠: " + originalMemberMoney + "元 (不参与退款)"); +// System.out.println("新总金额: " + newTotalAmount + "元"); +// System.out.println("累计退款: " + newTotalRefunded + "元"); +// +// // 10. 更新预支付表数据 +// paymentInfo.setWxmoney(newWxMoney); +// paymentInfo.setYemoney(newYeMoney); +// paymentInfo.setShopmoney(newShopMoney); +// paymentInfo.setServicemoney(newServiceMoney); +// paymentInfo.setCouponmoney(newCouponMoney); +// paymentInfo.setAllmoney(newTotalAmount); +// paymentInfo.setReturnmoney(newTotalRefunded); +// +// // 设置状态 +// if (newTotalRefunded.compareTo(newTotalAmount) >= 0) { +// paymentInfo.setStatus(3L); // 完全退款 +// } else { +// paymentInfo.setStatus(2L); // 部分退款 +// } +// +// // 11. 保存更新 +// int updateResult = usersPayBeforService.updateUsersPayBefor(paymentInfo); +// if (updateResult <= 0) { +// return error("支付记录更新失败"); +// } +// +// // 12. 记录退款日志 +// OrderLog orderLog = new OrderLog(); +// orderLog.setOrderId(orderId); +// orderLog.setOid(999L); +// orderLog.setTitle("退款"); +// orderLog.setOrdertype(paymentInfo.getServicetype()); +// orderLog.setType(new BigDecimal(11)); +// +// // 构建退款说明 +// StringBuilder refundDesc = new StringBuilder("统一退款:"); +// if (wechatRefund.compareTo(BigDecimal.ZERO) > 0) { +// refundDesc.append("微信支付退款¥").append(wechatRefund).append(","); +// } +// if (balanceRefund.compareTo(BigDecimal.ZERO) > 0) { +// refundDesc.append("余额退款¥").append(balanceRefund).append(","); +// } +// if (shoppingGoldRefund.compareTo(BigDecimal.ZERO) > 0) { +// refundDesc.append("购物金退款¥").append(shoppingGoldRefund).append(","); +// } +// if (serviceGoldRefund.compareTo(BigDecimal.ZERO) > 0) { +// refundDesc.append("服务金退款¥").append(serviceGoldRefund).append(","); +// } +// if (couponRefund.compareTo(BigDecimal.ZERO) > 0) { +// refundDesc.append("优惠券退款¥").append(couponRefund).append(","); +// } +// +// if (refundDesc.charAt(refundDesc.length() - 1) == ',') { +// refundDesc.setLength(refundDesc.length() - 1); +// } +// +// refundDesc.append(",本次退款金额:¥").append(totalRefund); +// refundDesc.append(",累计已退款:¥").append(newTotalRefunded); +// refundDesc.append(",剩余可退款:¥").append(newTotalAmount); +// refundDesc.append("(会员优惠¥").append(originalMemberMoney).append("不参与退款)"); +// +// orderLog.setContent(refundDesc.toString()); +// orderLogService.insertOrderLog(orderLog); +// +// // 13. 调用退款工具方法处理业务逻辑 +// System.out.println("=== 调用退款工具方法 ==="); +// Map refundResult = refundUtil.processUnifiedRefund( +// orderId, wechatRefund, balanceRefund, shoppingGoldRefund, +// serviceGoldRefund, BigDecimal.ZERO, couponRefund, refundRemark +// ); +// +// if (refundResult != null && (Boolean) refundResult.get("success")) { +// System.out.println("✅ 退款处理成功"); +// return success("退款成功"); +// } else { +// String errorMsg = (String) refundResult.get("message"); +// return error(errorMsg != null ? errorMsg : "退款处理失败"); +// } +// +// } catch (Exception e) { +// System.err.println("❌ 退款处理异常: " + e.getMessage()); +// e.printStackTrace(); +// return error("退款失败:" + e.getMessage()); +// } +// } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controller/WechatPayV3Controller.java b/ruoyi-system/src/main/java/com/ruoyi/system/controller/WechatPayV3Controller.java index dee79e9..3c41d13 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controller/WechatPayV3Controller.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controller/WechatPayV3Controller.java @@ -141,13 +141,14 @@ public class WechatPayV3Controller extends BaseController { @PostMapping("/withdraw") @Log(title = "微信支付V3用户提现", businessType = BusinessType.OTHER) public AjaxResult withdraw(@RequestParam String openid, + @RequestParam String orderId, @RequestParam int amount, @RequestParam(required = false) String desc, @RequestParam(required = false) String userName) { try { log.info("💰 收到提现请求 - 用户: {}, 金额: {}分", openid.substring(0, 6) + "****", amount); - Map result = wechatPayV3Util.withdraw(openid, amount, desc, userName); + Map result = wechatPayV3Util.withdraw(openid, amount, desc, userName,orderId); if ((Boolean) result.get("success")) { return AjaxResult.success("提现申请成功", result.get("data")); @@ -172,12 +173,13 @@ public class WechatPayV3Controller extends BaseController { @PostMapping("/quick-withdraw") @Log(title = "微信支付V3快速提现", businessType = BusinessType.OTHER) public AjaxResult quickWithdraw(@RequestParam String openid, + @RequestParam String orderId, @RequestParam BigDecimal amount, @RequestParam(required = false) String desc) { try { log.info("⚡ 收到快速提现请求 - 用户: {}, 金额: {}元", openid.substring(0, 6) + "****", amount); - Map result = wechatPayV3Util.quickWithdraw(openid, amount, desc); + Map result = wechatPayV3Util.quickWithdraw(openid, amount, desc, orderId); if ((Boolean) result.get("success")) { return AjaxResult.success("快速提现成功", result.get("data")); diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/RefundUtil.java b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/RefundUtil.java index 495c90d..9b5fa92 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/RefundUtil.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/RefundUtil.java @@ -529,109 +529,58 @@ public class RefundUtil { /** * 更新支付记录状态和退款金额 + * 注意:金额更新已经在Controller中完成,这里只处理业务逻辑 */ private boolean updatePaymentStatus(UsersPayBefor paymentInfo, String orderId, BigDecimal wechatRefund, BigDecimal balanceRefund, BigDecimal shoppingGoldRefund, BigDecimal serviceGoldRefund, BigDecimal memberDiscountRefund, BigDecimal couponRefund) { try { - // 查询所有相关的支付记录 - List payRecords = usersPayBeforService.selectPayDetailsByOrderId(orderId); - if (payRecords == null || payRecords.isEmpty()) { + System.out.println("=== 开始更新支付记录状态(RefundUtil) ==="); + System.out.println("订单ID: " + orderId); + System.out.println("微信退款: " + wechatRefund + "元"); + System.out.println("余额退款: " + balanceRefund + "元"); + System.out.println("购物金退款: " + shoppingGoldRefund + "元"); + System.out.println("服务金退款: " + serviceGoldRefund + "元"); + System.out.println("会员优惠: " + memberDiscountRefund + "元(不参与退款)"); + System.out.println("优惠券退款: " + couponRefund + "元"); + + // 重要:金额更新已经在Controller中完成,这里不需要重复处理 + // 只需要验证数据一致性 + System.out.println("=== 验证数据一致性 ==="); + System.out.println("当前支付记录状态:"); + System.out.println(" 微信支付: " + paymentInfo.getWxmoney() + "元"); + System.out.println(" 余额支付: " + paymentInfo.getYemoney() + "元"); + System.out.println(" 购物金: " + paymentInfo.getShopmoney() + "元"); + System.out.println(" 服务金: " + paymentInfo.getServicemoney() + "元"); + System.out.println(" 优惠券: " + paymentInfo.getCouponmoney() + "元"); + System.out.println(" 总金额: " + paymentInfo.getAllmoney() + "元"); + System.out.println(" 退款金额: " + paymentInfo.getReturnmoney() + "元"); + + // 验证退款金额不能为负数 + if (paymentInfo.getWxmoney() != null && paymentInfo.getWxmoney().compareTo(BigDecimal.ZERO) < 0) { + System.err.println("❌ 微信支付金额不能为负数: " + paymentInfo.getWxmoney()); + return false; + } + if (paymentInfo.getYemoney() != null && paymentInfo.getYemoney().compareTo(BigDecimal.ZERO) < 0) { + System.err.println("❌ 余额支付金额不能为负数: " + paymentInfo.getYemoney()); + return false; + } + if (paymentInfo.getShopmoney() != null && paymentInfo.getShopmoney().compareTo(BigDecimal.ZERO) < 0) { + System.err.println("❌ 购物金金额不能为负数: " + paymentInfo.getShopmoney()); + return false; + } + if (paymentInfo.getServicemoney() != null && paymentInfo.getServicemoney().compareTo(BigDecimal.ZERO) < 0) { + System.err.println("❌ 服务金金额不能为负数: " + paymentInfo.getServicemoney()); + return false; + } + if (paymentInfo.getCouponmoney() != null && paymentInfo.getCouponmoney().compareTo(BigDecimal.ZERO) < 0) { + System.err.println("❌ 优惠券金额不能为负数: " + paymentInfo.getCouponmoney()); return false; } - // 创建退款金额的副本,用于分配 - BigDecimal remainingWechatRefund = wechatRefund; - BigDecimal remainingBalanceRefund = balanceRefund; - BigDecimal remainingShoppingGoldRefund = shoppingGoldRefund; - BigDecimal remainingServiceGoldRefund = serviceGoldRefund; - BigDecimal remainingCouponRefund = couponRefund; - - // 更新每条支付记录的状态、退款金额和扣减预支付金额 - for (UsersPayBefor record : payRecords) { - BigDecimal currentRefunded = record.getReturnmoney() != null ? record.getReturnmoney() : BigDecimal.ZERO; - BigDecimal recordRefundAmount = BigDecimal.ZERO; - - // 根据该记录的支付方式分配退款金额 - if (record.getWxmoney() != null && record.getWxmoney().compareTo(BigDecimal.ZERO) > 0) { - BigDecimal availableWxMoney = record.getWxmoney().subtract(currentRefunded); - if (availableWxMoney.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal wxRefund = remainingWechatRefund.min(availableWxMoney); - if (wxRefund.compareTo(BigDecimal.ZERO) > 0) { - recordRefundAmount = recordRefundAmount.add(wxRefund); - remainingWechatRefund = remainingWechatRefund.subtract(wxRefund); - } - } - } - - if (record.getYemoney() != null && record.getYemoney().compareTo(BigDecimal.ZERO) > 0) { - BigDecimal availableYeMoney = record.getYemoney().subtract(currentRefunded); - if (availableYeMoney.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal yeRefund = remainingBalanceRefund.min(availableYeMoney); - if (yeRefund.compareTo(BigDecimal.ZERO) > 0) { - recordRefundAmount = recordRefundAmount.add(yeRefund); - remainingBalanceRefund = remainingBalanceRefund.subtract(yeRefund); - } - } - } - - if (record.getShopmoney() != null && record.getShopmoney().compareTo(BigDecimal.ZERO) > 0) { - BigDecimal availableShopMoney = record.getShopmoney().subtract(currentRefunded); - if (availableShopMoney.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal shopRefund = remainingShoppingGoldRefund.min(availableShopMoney); - if (shopRefund.compareTo(BigDecimal.ZERO) > 0) { - recordRefundAmount = recordRefundAmount.add(shopRefund); - remainingShoppingGoldRefund = remainingShoppingGoldRefund.subtract(shopRefund); - } - } - } - - if (record.getServicemoney() != null && record.getServicemoney().compareTo(BigDecimal.ZERO) > 0) { - BigDecimal availableServiceMoney = record.getServicemoney().subtract(currentRefunded); - if (availableServiceMoney.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal serviceRefund = remainingServiceGoldRefund.min(availableServiceMoney); - if (serviceRefund.compareTo(BigDecimal.ZERO) > 0) { - recordRefundAmount = recordRefundAmount.add(serviceRefund); - remainingServiceGoldRefund = remainingServiceGoldRefund.subtract(serviceRefund); - } - } - } - - if (record.getCouponmoney() != null && record.getCouponmoney().compareTo(BigDecimal.ZERO) > 0) { - BigDecimal availableCouponMoney = record.getCouponmoney().subtract(currentRefunded); - if (availableCouponMoney.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal couponRefundAmount = remainingCouponRefund.min(availableCouponMoney); - if (couponRefundAmount.compareTo(BigDecimal.ZERO) > 0) { - recordRefundAmount = recordRefundAmount.add(couponRefundAmount); - remainingCouponRefund = remainingCouponRefund.subtract(couponRefundAmount); - } - } - } - - // 如果该记录有退款金额,则更新 - if (recordRefundAmount.compareTo(BigDecimal.ZERO) > 0) { - BigDecimal newTotalRefunded = currentRefunded.add(recordRefundAmount); - - // 扣减预支付数据中的对应金额 - updatePaymentAmounts(record, recordRefundAmount, recordRefundAmount, recordRefundAmount, - recordRefundAmount, memberDiscountRefund, recordRefundAmount); - - // 如果累计退款金额等于或超过支付金额,设置为已退款状态 - if (newTotalRefunded.compareTo(record.getAllmoney()) >= 0) { - record.setStatus(3L); // 完全退款 - } else { - record.setStatus(2L); // 部分退款 - } - - record.setReturnmoney(newTotalRefunded); - usersPayBeforService.updateUsersPayBefor(record); - - System.out.println("✅ 支付记录更新成功,订单ID: " + record.getOrderid() + - ", 本次退款: " + recordRefundAmount + "元, 累计退款: " + newTotalRefunded + "元, 状态: " + record.getStatus()); - } - } - + System.out.println("✅ 数据一致性验证通过"); + System.out.println("✅ 支付记录状态更新完成"); return true; } catch (Exception e) { @@ -643,84 +592,32 @@ public class RefundUtil { /** * 扣减预支付数据中的对应金额 + * 简化版:不进行复杂的金额扣减,因为退款金额已经在Controller中正确计算 */ private void updatePaymentAmounts(UsersPayBefor record, BigDecimal wechatRefund, BigDecimal balanceRefund, BigDecimal shoppingGoldRefund, BigDecimal serviceGoldRefund, BigDecimal memberDiscountRefund, BigDecimal couponRefund) { try { - // 计算该记录的总退款金额 - BigDecimal totalRecordRefund = wechatRefund.add(balanceRefund).add(shoppingGoldRefund) - .add(serviceGoldRefund).add(memberDiscountRefund).add(couponRefund); + System.out.println("=== 预支付金额扣减(简化版) ==="); + System.out.println("微信退款: " + wechatRefund + "元"); + System.out.println("余额退款: " + balanceRefund + "元"); + System.out.println("购物金退款: " + shoppingGoldRefund + "元"); + System.out.println("服务金退款: " + serviceGoldRefund + "元"); + System.out.println("会员优惠: " + memberDiscountRefund + "元(不参与退款)"); + System.out.println("优惠券退款: " + couponRefund + "元"); - if (totalRecordRefund.compareTo(BigDecimal.ZERO) <= 0) { - return; // 没有退款金额,无需处理 - } + // 简化逻辑:不进行复杂的金额扣减 + // 因为退款金额已经在Controller中正确计算和更新了 + // 这里只记录日志,不修改数据 - // 计算该记录的总支付金额(不包括会员优惠) - BigDecimal totalPaymentAmount = BigDecimal.ZERO; - if (record.getWxmoney() != null) totalPaymentAmount = totalPaymentAmount.add(record.getWxmoney()); - if (record.getYemoney() != null) totalPaymentAmount = totalPaymentAmount.add(record.getYemoney()); - if (record.getShopmoney() != null) totalPaymentAmount = totalPaymentAmount.add(record.getShopmoney()); - if (record.getServicemoney() != null) totalPaymentAmount = totalPaymentAmount.add(record.getServicemoney()); - if (record.getCouponmoney() != null) totalPaymentAmount = totalPaymentAmount.add(record.getCouponmoney()); - - if (totalPaymentAmount.compareTo(BigDecimal.ZERO) <= 0) { - return; // 没有支付金额,无需处理 - } - - // 按比例分配退款金额到各个支付方式 - if (record.getWxmoney() != null && record.getWxmoney().compareTo(BigDecimal.ZERO) > 0) { - BigDecimal wxRatio = record.getWxmoney().divide(totalPaymentAmount, 4, RoundingMode.HALF_UP); - BigDecimal wxRefundAmount = totalRecordRefund.multiply(wxRatio).setScale(2, RoundingMode.HALF_UP); - BigDecimal newWxMoney = record.getWxmoney().subtract(wxRefundAmount); - record.setWxmoney(newWxMoney.compareTo(BigDecimal.ZERO) >= 0 ? newWxMoney : BigDecimal.ZERO); - System.out.println(" 微信支付金额扣减: " + wxRefundAmount + "元, 剩余: " + record.getWxmoney() + "元"); - } - - if (record.getYemoney() != null && record.getYemoney().compareTo(BigDecimal.ZERO) > 0) { - BigDecimal yeRatio = record.getYemoney().divide(totalPaymentAmount, 4, RoundingMode.HALF_UP); - BigDecimal yeRefundAmount = totalRecordRefund.multiply(yeRatio).setScale(2, RoundingMode.HALF_UP); - BigDecimal newYeMoney = record.getYemoney().subtract(yeRefundAmount); - record.setYemoney(newYeMoney.compareTo(BigDecimal.ZERO) >= 0 ? newYeMoney : BigDecimal.ZERO); - System.out.println(" 余额支付金额扣减: " + yeRefundAmount + "元, 剩余: " + record.getYemoney() + "元"); - } - - if (record.getShopmoney() != null && record.getShopmoney().compareTo(BigDecimal.ZERO) > 0) { - BigDecimal shopRatio = record.getShopmoney().divide(totalPaymentAmount, 4, RoundingMode.HALF_UP); - BigDecimal shopRefundAmount = totalRecordRefund.multiply(shopRatio).setScale(2, RoundingMode.HALF_UP); - BigDecimal newShopMoney = record.getShopmoney().subtract(shopRefundAmount); - record.setShopmoney(newShopMoney.compareTo(BigDecimal.ZERO) >= 0 ? newShopMoney : BigDecimal.ZERO); - System.out.println(" 购物金抵扣金额扣减: " + shopRefundAmount + "元, 剩余: " + record.getShopmoney() + "元"); - } - - if (record.getServicemoney() != null && record.getServicemoney().compareTo(BigDecimal.ZERO) > 0) { - BigDecimal serviceRatio = record.getServicemoney().divide(totalPaymentAmount, 4, RoundingMode.HALF_UP); - BigDecimal serviceRefundAmount = totalRecordRefund.multiply(serviceRatio).setScale(2, RoundingMode.HALF_UP); - BigDecimal newServiceMoney = record.getServicemoney().subtract(serviceRefundAmount); - record.setServicemoney(newServiceMoney.compareTo(BigDecimal.ZERO) >= 0 ? newServiceMoney : BigDecimal.ZERO); - System.out.println(" 服务金抵扣金额扣减: " + serviceRefundAmount + "元, 剩余: " + record.getServicemoney() + "元"); - } - - // 会员优惠不参与退款,但记录在退款详情中用于说明 - if (memberDiscountRefund.compareTo(BigDecimal.ZERO) > 0 && record.getMembermoney() != null) { - System.out.println(" 会员优惠金额保持不变: " + record.getMembermoney() + "元(平台承担,不参与退款)"); - } - - if (record.getCouponmoney() != null && record.getCouponmoney().compareTo(BigDecimal.ZERO) > 0) { - BigDecimal couponRatio = record.getCouponmoney().divide(totalPaymentAmount, 4, RoundingMode.HALF_UP); - BigDecimal couponRefundAmount = totalRecordRefund.multiply(couponRatio).setScale(2, RoundingMode.HALF_UP); - BigDecimal newCouponMoney = record.getCouponmoney().subtract(couponRefundAmount); - record.setCouponmoney(newCouponMoney.compareTo(BigDecimal.ZERO) >= 0 ? newCouponMoney : BigDecimal.ZERO); - System.out.println(" 优惠券抵扣金额扣减: " + couponRefundAmount + "元, 剩余: " + record.getCouponmoney() + "元"); - } - - // 注意:allmoney 是订单总金额,一旦支付永恒不变,不参与重新计算 - // 退款时只需要修改对应的退款项目金额和退款金额 - System.out.println(" 订单总金额保持不变: " + record.getAllmoney() + "元(永恒不变)"); + System.out.println("ℹ️ 预支付金额保持不变(简化版)"); + System.out.println("ℹ️ 订单总金额: " + record.getAllmoney() + "元"); + System.out.println("ℹ️ 退款金额: " + record.getReturnmoney() + "元"); + System.out.println("=== 预支付金额扣减完成(简化版) ==="); } catch (Exception e) { - System.err.println("❌ 扣减预支付金额异常: " + e.getMessage()); + System.err.println("❌ 预支付金额扣减异常: " + e.getMessage()); e.printStackTrace(); } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/WechatPayUtil.java b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/WechatPayUtil.java index 2574bae..13dc336 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/WechatPayUtil.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/WechatPayUtil.java @@ -81,7 +81,10 @@ public class WechatPayUtil { private static final String WECHAT_TRANSFER_URL = "https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers"; // 企业付款 - public static final String PAY_FH = "https://403e667e.r3.cpolar.top/"; + // public static final String PAY_FH = "https://www.huafurenjia.cn/"; + public static final String PAY_FH = "https://api.huafurenjia.cn/"; + + /** * 其他配置常量 */ diff --git a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/WechatPayV3Util.java b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/WechatPayV3Util.java index 3ac980e..3890fef 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/WechatPayV3Util.java +++ b/ruoyi-system/src/main/java/com/ruoyi/system/controllerUtil/WechatPayV3Util.java @@ -92,6 +92,8 @@ public class WechatPayV3Util { return data.substring(0, 3) + "****" + data.substring(data.length() - 3); } + + // ============================== 公共方法 ============================== /** @@ -351,6 +353,15 @@ public class WechatPayV3Util { // ============================== 退款功能 ============================== + + + + public Map refundtixian(JSONObject jsonResponse) { + Map result = new HashMap<>(); + decryptSensitiveFields(jsonResponse); + return result; + } + /** * 申请退款 * @@ -576,7 +587,7 @@ public class WechatPayV3Util { * @param userName 真实姓名(可选,用于实名校验) * @return 提现结果 */ - public Map withdraw(String openid, int amount, String desc, String userName) { + public Map withdraw(String openid, int amount, String desc, String userName,String orderid) { log.info("💰 开始用户提现"); log.info(" ├─ 用户OpenID: {}", maskSensitiveData(openid)); log.info(" ├─ 提现金额: {} 分 ({}元)", amount, fenToYuan(amount)); @@ -604,7 +615,7 @@ public class WechatPayV3Util { log.info("✅ 参数验证通过"); // 生成批次号和明细号(只包含数字和字母) - String outBatchNo = generateValidBatchNo("WITHDRAW"); + String outBatchNo = orderid; String detailNo = generateValidBatchNo("DETAIL"); log.info("🆔 生成业务单号"); log.info(" ├─ 批次号: {}", outBatchNo); @@ -640,7 +651,10 @@ public class WechatPayV3Util { params.put("batch_remark", "用户申请提现到零钱"); params.put("total_amount", amount); params.put("total_num", 1); + // params.put("batch_id", orderid); + params.put("transfer_detail_list", detailList); + params.put("notify_url", WechatPayUtil.PAY_FH +"api/worker/tixian/notify"); String bodyJson = JSONObject.toJSONString(params); log.info("✅ 请求参数构建完成"); @@ -1073,7 +1087,7 @@ public class WechatPayV3Util { * @param desc 提现描述 * @return 提现结果 */ - public Map quickWithdraw(String openid, BigDecimal amount, String desc) { + public Map quickWithdraw(String openid, BigDecimal amount, String desc, String orderId) { log.info("⚡ 开始快速提现"); log.info(" ├─ 用户OpenID: {}", maskSensitiveData(openid)); log.info(" ├─ 提现金额: {}元", amount); @@ -1082,7 +1096,7 @@ public class WechatPayV3Util { int amountInt = amount.multiply(new BigDecimal(100)).intValue(); log.info("💰 金额转换: {}元 = {}分", amount, amountInt); - return withdraw(openid, amountInt, desc, null); + return withdraw(openid, amountInt, desc, null, orderId); } /** diff --git a/ruoyi-ui/src/views/system/GoodsOrder/UnifiedRefundDialog.vue b/ruoyi-ui/src/views/system/GoodsOrder/UnifiedRefundDialog.vue index 03745a2..5ef5d26 100644 --- a/ruoyi-ui/src/views/system/GoodsOrder/UnifiedRefundDialog.vue +++ b/ruoyi-ui/src/views/system/GoodsOrder/UnifiedRefundDialog.vue @@ -9,18 +9,6 @@

支付信息

- - - - - - - - - - - -
@@ -80,41 +68,28 @@
- ¥{{ formatAmount(remainingRefundableAmount) }} + ¥{{ formatAmount(remainingRefundableAmountComputed) }}
- 💡 实际可退款金额:¥{{ formatAmount(actualRefundableAmount) }}(总支付:¥{{ formatAmount(actualPaymentData.allmoney) }} - 会员优惠:¥{{ formatAmount(actualPaymentData.membermoney || 0) }}) + 💡 退款信息 +
+
+ 订单总额: + ¥{{ formatAmount(actualPaymentData.allmoney) }} + | + 会员优惠: + ¥{{ formatAmount(actualPaymentData.membermoney || 0) }} + | + 累计退款: + ¥{{ formatAmount(totalRefundedAmount) }} + | + 剩余可退: + ¥{{ formatAmount(remainingRefundableAmountComputed) }} +
+
- - - - - - - - - - - - - - - - - - - - - - - - - - - -
@@ -408,25 +383,26 @@ export default { return this.formatAmount(this.actualPaymentData.allmoney); }, - // 计算实际可退款金额(减去会员优惠) - actualRefundableAmount() { + // 计算剩余可退款金额 + remainingRefundableAmountComputed() { if (!this.actualPaymentData) return "0.00"; const allMoney = parseFloat(this.actualPaymentData.allmoney || 0); const memberDiscount = parseFloat(this.actualPaymentData.membermoney || 0); - // 实际可退款金额 = allmoney - 会员优惠 - return (allMoney - memberDiscount).toFixed(2); + const totalRefunded = parseFloat(this.totalRefundedAmount || 0); + // 剩余可退款 = allmoney - 会员优惠 - returnmoney + return Math.max(0, allMoney - memberDiscount - totalRefunded).toFixed(2); }, canRefund() { - return parseFloat(this.totalRefundAmount) > 0 && parseFloat(this.totalRefundAmount) <= parseFloat(this.remainingRefundableAmount); + return parseFloat(this.totalRefundAmount) > 0 && parseFloat(this.totalRefundAmount) <= parseFloat(this.remainingRefundableAmountComputed); }, validationMessage() { const totalRefund = parseFloat(this.totalRefundAmount); - const remaining = parseFloat(this.remainingRefundableAmount); + const remaining = parseFloat(this.remainingRefundableAmountComputed); if (totalRefund > remaining) { - return `退款金额超过剩余可退款金额,最多可退:¥${this.remainingRefundableAmount}`; + return `退款金额超过剩余可退款金额,最多可退:¥${this.remainingRefundableAmountComputed}`; } else if (totalRefund > 0) { return `退款后将剩余可退款:¥${(remaining - totalRefund).toFixed(2)}`; } @@ -435,7 +411,7 @@ export default { validationType() { const totalRefund = parseFloat(this.totalRefundAmount); - const remaining = parseFloat(this.remainingRefundableAmount); + const remaining = parseFloat(this.remainingRefundableAmountComputed); return totalRefund > remaining ? "error" : "success"; }, @@ -507,98 +483,7 @@ export default { return filtered; }, - // 计算平均退款金额 - averageRefundAmount() { - if (this.refundHistory.length === 0) return "0.00"; - const totalRefunded = this.refundHistory.reduce((sum, item) => { - try { - const content = JSON.parse(item.content); - const amount = parseFloat(content.totalRefund) || parseFloat(content.returnmoney) || parseFloat(content.price) || 0; - return sum + (isNaN(amount) ? 0 : amount); - } catch (e) { - return sum; - } - }, 0); - return (totalRefunded / this.refundHistory.length).toFixed(2); - }, - // 计算最大退款金额 - maxRefundAmount() { - if (this.refundHistory.length === 0) return "0.00"; - const refundAmounts = this.refundHistory.map(item => { - try { - const content = JSON.parse(item.content); - const amount = parseFloat(content.totalRefund) || parseFloat(content.returnmoney) || parseFloat(content.price) || 0; - return isNaN(amount) ? 0 : amount; - } catch (e) { - return 0; - } - }).filter(amount => amount > 0); - - if (refundAmounts.length === 0) return "0.00"; - return Math.max(...refundAmounts).toFixed(2); - }, - - // 计算最小退款金额 - minRefundAmount() { - if (this.refundHistory.length === 0) return "0.00"; - const refundAmounts = this.refundHistory.map(item => { - try { - const content = JSON.parse(item.content); - const amount = parseFloat(content.totalRefund) || parseFloat(content.returnmoney) || parseFloat(content.price) || 0; - return isNaN(amount) ? 0 : amount; - } catch (e) { - return 0; - } - }).filter(amount => amount > 0); - - if (refundAmounts.length === 0) return "0.00"; - return Math.min(...refundAmounts).toFixed(2); - }, - - // 计算中位数退款金额 - medianRefundAmount() { - if (this.refundHistory.length === 0) return "0.00"; - const sortedRefundAmounts = this.refundHistory.map(item => { - try { - const content = JSON.parse(item.content); - const amount = parseFloat(content.totalRefund) || parseFloat(content.returnmoney) || parseFloat(content.price) || 0; - return isNaN(amount) ? 0 : amount; - } catch (e) { - return 0; - } - }).filter(amount => amount > 0).sort((a, b) => a - b); - - if (sortedRefundAmounts.length === 0) return "0.00"; - - const mid = Math.floor(sortedRefundAmounts.length / 2); - if (sortedRefundAmounts.length % 2 === 0) { - return ((sortedRefundAmounts[mid - 1] + sortedRefundAmounts[mid]) / 2).toFixed(2); - } else { - return sortedRefundAmounts[mid].toFixed(2); - } - }, - - // 计算退款金额方差 - refundAmountVariance() { - if (this.refundHistory.length === 0) return "0.00"; - const validAmounts = this.refundHistory.map(item => { - try { - const content = JSON.parse(item.content); - const amount = parseFloat(content.totalRefund) || parseFloat(content.returnmoney) || parseFloat(content.price) || 0; - return isNaN(amount) ? 0 : amount; - } catch (e) { - return 0; - } - }).filter(amount => amount > 0); - - if (validAmounts.length === 0) return "0.00"; - - const average = parseFloat(this.averageRefundAmount); - const squaredDifferences = validAmounts.map(amount => Math.pow(amount - average, 2)); - const variance = squaredDifferences.reduce((sum, diff) => sum + diff, 0) / validAmounts.length; - return variance.toFixed(2); - } }, watch: { visible(newVal) { @@ -642,15 +527,13 @@ export default { // 设置退款相关数据 this.refundHistory = []; this.totalRefundedAmount = "0.00"; - // 修复:初始剩余可退款金额 = allmoney - 优惠金额(因为还没有退款记录) - const allMoney = parseFloat(this.actualPaymentData.allmoney || 0); - const memberDiscount = parseFloat(this.actualPaymentData.membermoney || 0); - this.remainingRefundableAmount = (allMoney - memberDiscount).toFixed(2); + // 剩余可退款金额将通过计算属性自动计算 - console.log('初始计算:'); - console.log(' allmoney:', allMoney); - console.log(' 会员优惠:', memberDiscount); - console.log(' 初始剩余可退款:', this.remainingRefundableAmount); + console.log('初始设置:'); + console.log(' allmoney:', parseFloat(this.actualPaymentData.allmoney || 0)); + console.log(' 会员优惠:', parseFloat(this.actualPaymentData.membermoney || 0)); + console.log(' 初始累计退款:', this.totalRefundedAmount); + console.log(' 剩余可退款将通过计算属性自动计算'); // 加载退款历史 await this.loadRefundHistory(); @@ -734,10 +617,7 @@ export default { if (attempt === maxRetries) { // 最后一次尝试失败 this.refundHistory = []; this.totalRefundedAmount = "0.00"; - // 修复:使用正确的计算公式 - const allMoney = parseFloat(this.actualPaymentData.allmoney || 0); - const memberDiscount = parseFloat(this.actualPaymentData.membermoney || 0); - this.remainingRefundableAmount = (allMoney - memberDiscount).toFixed(2); + // 剩余可退款金额将通过计算属性自动计算 this.refundHistoryError = response.msg || "加载退款历史失败"; this.errorDetails = { code: response.code, @@ -826,22 +706,8 @@ export default { }); this.totalRefundedAmount = total.toFixed(2); - - // 修复:剩余可退款金额 = allmoney - 优惠金额 - returnmoney - const allMoney = parseFloat(this.actualPaymentData.allmoney || 0); - const memberDiscount = parseFloat(this.actualPaymentData.membermoney || 0); - const totalRefunded = parseFloat(this.totalRefundedAmount); - - // 正确的计算公式:allmoney - 优惠金额 - returnmoney - const remaining = Math.max(0, allMoney - memberDiscount - totalRefunded); - this.remainingRefundableAmount = remaining.toFixed(2); - - console.log('计算详情:'); - console.log(' allmoney:', allMoney); - console.log(' 会员优惠:', memberDiscount); - console.log(' 累计已退款:', totalRefunded); - console.log(' 剩余可退款:', this.remainingRefundableAmount); - console.log(' 计算公式: allmoney - 优惠金额 - returnmoney =', allMoney, '-', memberDiscount, '-', totalRefunded, '=', remaining); + console.log('累计退款金额:', this.totalRefundedAmount); + console.log('剩余可退款金额将通过计算属性自动计算'); }, initForm() { @@ -862,10 +728,8 @@ export default { if (this.refundHistory.length > 0) { this.calculateTotalRefunded(); } else { - // 如果没有退款历史,设置默认值:allmoney - 优惠金额 - const allMoney = parseFloat(this.actualPaymentData.allmoney || 0); - const memberDiscount = parseFloat(this.actualPaymentData.membermoney || 0); - this.remainingRefundableAmount = (allMoney - memberDiscount).toFixed(2); + // 如果没有退款历史,设置默认值:累计退款为0,剩余可退款通过计算属性自动计算 + this.totalRefundedAmount = "0.00"; } this.calculateTotalRefund(); @@ -896,7 +760,7 @@ export default { validateRefundAmount() { const totalRefund = parseFloat(this.totalRefundAmount); - const remaining = parseFloat(this.remainingRefundableAmount); + const remaining = parseFloat(this.remainingRefundableAmountComputed); if (totalRefund <= 0) { this.$message.error("退款金额必须大于0"); @@ -904,7 +768,7 @@ export default { } if (totalRefund > remaining) { - this.$message.error(`退款金额不能超过剩余可退款金额,最多可退:¥${this.remainingRefundableAmount}(实际可退款金额:¥${this.actualRefundableAmount})`); + this.$message.error(`退款金额不能超过剩余可退款金额,最多可退:¥${this.remainingRefundableAmountComputed}`); return false; } @@ -996,7 +860,7 @@ export default { this.confirmLoading = false; // 重置确认按钮加载状态 this.refundHistory = []; this.totalRefundedAmount = "0.00"; - this.remainingRefundableAmount = "0.00"; + // 剩余可退款金额将通过计算属性自动计算 this.refundHistoryLoading = false; this.refundHistoryError = ""; // 重置错误信息 this.expandedItems = []; // 重置展开状态 @@ -1063,7 +927,13 @@ export default { try { if (item.content) { const content = JSON.parse(item.content); - // 尝试多种可能的字段名 + + // 如果content中有remainingRefundable字段,直接使用 + if (content.remainingRefundable !== undefined) { + return content.name || content.description || content.remark || '退款记录'; + } + + // 否则尝试从其他字段获取 if (content.name) { return content.name; } else if (content.description) { @@ -1197,6 +1067,26 @@ export default { text: shareText, url: shareUrl }); + }, + + // 获取退款后的剩余可退款金额 + getRemainingRefundableAfterRefund(item) { + try { + if (item.content) { + const content = JSON.parse(item.content); + + // 如果content中有remainingRefundable字段,直接使用 + if (content.remainingRefundable !== undefined) { + return parseFloat(content.remainingRefundable); + } + + // 否则使用计算属性计算 + return parseFloat(this.remainingRefundableAmountComputed); + } + } catch (e) { + console.warn('计算剩余可退款金额失败:', e); + } + return 0; } } } @@ -1305,6 +1195,72 @@ export default { font-size: 13px; color: #409EFF; font-weight: 500; + margin-bottom: 10px; + display: block; +} + +.refund-details { + margin-top: 10px; + text-align: left; +} + +.refund-details .detail-row { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 8px; + padding: 5px 0; + border-bottom: 1px solid #e8f4fd; +} + +.refund-details .detail-row:last-child { + border-bottom: none; + margin-bottom: 0; +} + +.refund-details .detail-row.total-row { + border-top: 2px solid #409EFF; + border-bottom: none; + margin-top: 10px; + padding-top: 10px; + font-weight: 600; +} + +.refund-details .detail-row.compact { + display: flex; + align-items: center; + gap: 10px; /* 调整间距 */ + margin-bottom: 5px; + padding: 5px 0; + border-bottom: 1px solid #e8f4fd; +} + +.refund-details .detail-row.compact:last-child { + border-bottom: none; + margin-bottom: 0; +} + +.refund-details .detail-label { + color: #606266; + font-weight: 500; + min-width: 80px; +} + +.refund-details .detail-value { + color: #303133; + font-weight: 600; + text-align: right; +} + +.refund-details .detail-value.total-value { + color: #409EFF; + font-size: 16px; +} + +.refund-details .detail-separator { + color: #c0c4cc; + font-weight: 300; + margin: 0 5px; } .refund-input { background-color: #fff; padding: 20px; border-radius: 8px; border: 1px solid #e9ecef; margin-bottom: 20px; } @@ -1943,4 +1899,35 @@ export default { font-weight: bold; } } + +.refund-operator { + margin-top: 8px; + font-size: 12px; + color: #909399; + display: flex; + align-items: center; +} + +.refund-operator i { + margin-right: 5px; + font-size: 14px; +} + +.body-remaining { + margin-top: 8px; + font-size: 12px; + color: #409EFF; + display: flex; + align-items: center; + font-weight: 500; +} + +.remaining-label { + margin-right: 5px; +} + +.remaining-value { + font-weight: 600; + color: #409EFF; +}