From baac3fa842d22c18bf06cd83efdd4725f13ecf6b Mon Sep 17 00:00:00 2001 From: old burden Date: Sat, 24 Jan 2026 13:56:02 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20vm=E6=94=AF=E4=BB=98=E5=AF=B9=E6=8E=A5go?= =?UTF-8?q?oglecom=E5=8E=BB=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/ruoyi/api/KadaPaymentJava.java | 4 +- .../java/com/ruoyi/api/PayController.java | 159 +++++++++++++++- .../com/ruoyi/ai/domain/VmCallBackReq.java | 47 ++++- .../ruoyi/ai/domain/VmUnifiedOrderRes.java | 64 ++++++- .../java/com/ruoyi/ai/service/IVmService.java | 10 +- .../service/impl/AiRechargeServiceImpl.java | 4 + .../com/ruoyi/ai/service/impl/VmService.java | 178 ++++++++++++++---- 7 files changed, 398 insertions(+), 68 deletions(-) diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/KadaPaymentJava.java b/ruoyi-admin/src/main/java/com/ruoyi/api/KadaPaymentJava.java index 93d2ae2..93dafb0 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/api/KadaPaymentJava.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/api/KadaPaymentJava.java @@ -69,8 +69,8 @@ public class KadaPaymentJava { map.put("pm", "QRPH"); map.put("ref", orderNo); map.put("payer", payer); - map.put("redirect", "www.google.com/recharge"); - map.put("callbackUrl", "www.google.com/api/pay/kada-callBack"); + map.put("redirect", "https://undressing.top/recharge"); + map.put("callbackUrl", "https://undressing.top/api/pay/kada-callBack"); return JSON.toJSONString(map); } diff --git a/ruoyi-admin/src/main/java/com/ruoyi/api/PayController.java b/ruoyi-admin/src/main/java/com/ruoyi/api/PayController.java index afeacf0..93244d5 100644 --- a/ruoyi-admin/src/main/java/com/ruoyi/api/PayController.java +++ b/ruoyi-admin/src/main/java/com/ruoyi/api/PayController.java @@ -8,9 +8,11 @@ import com.ruoyi.ai.service.IYuZhouService; import com.ruoyi.ai.service.IVmService; import com.ruoyi.common.annotation.Anonymous; import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.utils.ip.IpUtils; import io.swagger.annotations.Api; import io.swagger.annotations.ApiOperation; import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -21,6 +23,7 @@ import java.util.Map; @RequestMapping("/api/pay") @Api(tags = "支付接口") @RequiredArgsConstructor(onConstructor_ = @Autowired) +@Slf4j public class PayController { private final IJinShaService jinShaService; private final IKaDaService kaDaService; @@ -92,17 +95,163 @@ public class PayController { */ @PostMapping("/vm-pay") @ApiOperation("VM请求支付") - public AjaxResult vmPay(@RequestBody VmPayReq req) throws Exception { - PayResVO payResVO = vmService.vmPay(req.getGearId(), req.getVmCardInfo()); + public AjaxResult vmPay(@RequestBody VmPayReq req, HttpServletRequest request) throws Exception { + // 打印入参 + log.info("VM支付请求入参 - gearId: {}", req.getGearId()); + if (req.getVmCardInfo() != null) { + VmCardInfo cardInfo = req.getVmCardInfo(); + log.info("VM支付请求入参 - vmCardInfo: number={}, cvc={}, expYear={}, expMonth={}, email={}, firstName={}, lastName={}, country={}", + maskCardNumber(cardInfo.getNumber()), + maskCvc(cardInfo.getCvc()), + cardInfo.getExpYear(), + cardInfo.getExpMonth(), + cardInfo.getEmail(), + cardInfo.getFirstName(), + cardInfo.getLastName(), + cardInfo.getCountry()); + } else { + log.info("VM支付请求入参 - vmCardInfo: null"); + } + + // 获取用户真实IP地址 + String clientIp = IpUtils.getIpAddr(request); + log.info("VM支付请求入参 - clientIp: {}", clientIp); + + PayResVO payResVO = vmService.vmPay(req.getGearId(), req.getVmCardInfo(), clientIp); return AjaxResult.success(payResVO); } + + /** + * 脱敏卡号(只显示前4位和后4位) + */ + private String maskCardNumber(String cardNumber) { + if (cardNumber == null || cardNumber.length() < 8) { + return cardNumber; + } + return cardNumber.substring(0, 4) + "****" + cardNumber.substring(cardNumber.length() - 4); + } + + /** + * 脱敏CVC(全部隐藏) + */ + private String maskCvc(String cvc) { + if (cvc == null || cvc.isEmpty()) { + return cvc; + } + return "***"; + } @PostMapping("/vm-callBack") @ApiOperation("VM支付回调") @Anonymous - public AjaxResult vmCallBack(@RequestBody VmCallBackReq req) { - AjaxResult ajaxResult = vmService.vmCallBack(req); - return ajaxResult; + public String vmCallBack(HttpServletRequest request) { + // 记录请求的Content-Type + String contentType = request.getContentType(); + + // 从请求中解析表单参数 + VmCallBackReq req = new VmCallBackReq(); + + // 记录所有请求参数用于调试 + java.util.Enumeration paramNames = request.getParameterNames(); + java.util.Map allParams = new java.util.HashMap<>(); + while (paramNames.hasMoreElements()) { + String paramName = paramNames.nextElement(); + String paramValue = request.getParameter(paramName); + allParams.put(paramName, paramValue); + } + log.info("VM支付回调请求参数: {}, Content-Type: {}", allParams, contentType); + + req.setMchNo(request.getParameter("mchNo")); + req.setAppId(request.getParameter("appId")); + req.setMchOrderNo(request.getParameter("mchOrderNo")); + req.setPayOrderId(request.getParameter("payOrderId")); + req.setWayCode(request.getParameter("wayCode")); + + // 解析整数类型字段 + String amountStr = request.getParameter("amount"); + if (amountStr != null && !amountStr.isEmpty()) { + try { + req.setAmount(Integer.parseInt(amountStr)); + } catch (NumberFormatException e) { + log.warn("VM支付回调amount解析失败: {}", amountStr); + } + } + + req.setCurrency(request.getParameter("currency")); + req.setIfCode(request.getParameter("ifCode")); + req.setSubject(request.getParameter("subject")); + req.setBody(request.getParameter("body")); + req.setClientIp(request.getParameter("clientIp")); + + // 解析订单状态(文档字段名:state) + String stateStr = request.getParameter("state"); + if (stateStr != null && !stateStr.isEmpty()) { + try { + req.setState(Integer.parseInt(stateStr)); + } catch (NumberFormatException e) { + log.warn("VM支付回调state解析失败: {}", stateStr); + } + } + + req.setChannelOrderNo(request.getParameter("channelOrderNo")); + req.setErrCode(request.getParameter("errCode")); + req.setErrMsg(request.getParameter("errMsg")); + req.setExtParam(request.getParameter("extParam")); + + // 解析时间戳:createdAt、successTime、reqTime(文档字段名) + String createdAtStr = request.getParameter("createdAt"); + if (createdAtStr != null && !createdAtStr.isEmpty()) { + try { + req.setCreatedAt(Long.parseLong(createdAtStr)); + } catch (NumberFormatException e) { + log.warn("VM支付回调createdAt解析失败: {}", createdAtStr); + } + } + String successTimeStr = request.getParameter("successTime"); + if (successTimeStr != null && !successTimeStr.isEmpty()) { + try { + req.setSuccessTime(Long.parseLong(successTimeStr)); + } catch (NumberFormatException e) { + log.warn("VM支付回调successTime解析失败: {}", successTimeStr); + } + } + String reqTimeStr = request.getParameter("reqTime"); + if (reqTimeStr != null && !reqTimeStr.isEmpty()) { + try { + req.setReqTime(Long.parseLong(reqTimeStr)); + } catch (NumberFormatException e) { + log.warn("VM支付回调reqTime解析失败: {}", reqTimeStr); + } + } + + req.setSign(request.getParameter("sign")); + req.setSignType(request.getParameter("signType")); + + // 打印解析后的完整对象 + log.info("VM支付回调解析后的完整对象: mchNo={}, appId={}, mchOrderNo={}, payOrderId={}, ifCode={}, wayCode={}, amount={}, currency={}, state={}, subject={}, body={}, clientIp={}, channelOrderNo={}, errCode={}, errMsg={}, extParam={}, createdAt={}, successTime={}, reqTime={}, sign={}, signType={}", + req.getMchNo(), + req.getAppId(), + req.getMchOrderNo(), + req.getPayOrderId(), + req.getIfCode(), + req.getWayCode(), + req.getAmount(), + req.getCurrency(), + req.getState(), + req.getSubject(), + req.getBody(), + req.getClientIp(), + req.getChannelOrderNo(), + req.getErrCode(), + req.getErrMsg(), + req.getExtParam(), + req.getCreatedAt(), + req.getSuccessTime(), + req.getReqTime(), + req.getSign(), + req.getSignType()); + + return vmService.vmCallBack(req); } } diff --git a/ruoyi-system/src/main/java/com/ruoyi/ai/domain/VmCallBackReq.java b/ruoyi-system/src/main/java/com/ruoyi/ai/domain/VmCallBackReq.java index dfd1215..13b1628 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/ai/domain/VmCallBackReq.java +++ b/ruoyi-system/src/main/java/com/ruoyi/ai/domain/VmCallBackReq.java @@ -47,22 +47,43 @@ public class VmCallBackReq { private String currency; /** - * 订单状态:0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭 + * 支付接口编码 */ - private Integer orderState; + private String ifCode; /** - * 支付渠道订单号 + * 商品标题 + */ + private String subject; + + /** + * 商品描述 + */ + private String body; + + /** + * 订单状态:0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭 + * 文档字段名:state + */ + private Integer state; + + /** + * 客户端IP(可选) + */ + private String clientIp; + + /** + * 渠道订单号(可选) */ private String channelOrderNo; /** - * 渠道错误码 + * 渠道错误码(可选) */ private String errCode; /** - * 渠道错误描述 + * 渠道错误描述(可选) */ private String errMsg; @@ -72,9 +93,19 @@ public class VmCallBackReq { private String extParam; /** - * 回调时间,13位时间戳 + * 订单创建时间,13位时间戳 */ - private Long notifyTime; + private Long createdAt; + + /** + * 订单支付成功时间,13位时间戳(可选) + */ + private Long successTime; + + /** + * 通知请求时间,13位时间戳。文档字段名:reqTime + */ + private Long reqTime; /** * 签名值 @@ -82,7 +113,7 @@ public class VmCallBackReq { private String sign; /** - * 签名类型 + * 签名类型(文档回调示例无此字段,不参与签名) */ private String signType; } diff --git a/ruoyi-system/src/main/java/com/ruoyi/ai/domain/VmUnifiedOrderRes.java b/ruoyi-system/src/main/java/com/ruoyi/ai/domain/VmUnifiedOrderRes.java index 0bdeafd..f77b9f5 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/ai/domain/VmUnifiedOrderRes.java +++ b/ruoyi-system/src/main/java/com/ruoyi/ai/domain/VmUnifiedOrderRes.java @@ -1,5 +1,6 @@ package com.ruoyi.ai.domain; +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; import lombok.Data; /** @@ -9,6 +10,7 @@ import lombok.Data; * @date 2025-01-XX */ @Data +@JsonIgnoreProperties(ignoreUnknown = true) public class VmUnifiedOrderRes { /** @@ -26,30 +28,72 @@ public class VmUnifiedOrderRes { */ private VmUnifiedOrderData data; + /** + * 响应签名(API 返回) + */ + private String sign; + @Data public static class VmUnifiedOrderData { /** - * 支付链接 + * 支付订单号(必填) */ - private String payUrl; + private String payOrderId; /** - * 订单号 - */ - private String orderId; - - /** - * 商户订单号 + * 商户订单号(必填) */ private String mchOrderNo; /** - * 订单状态 + * 订单状态(必填) + * 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭 */ private Integer orderState; /** - * 支付参数(某些支付方式可能需要) + * 支付数据类型(必填) + * payUrl-跳转链接的方式 + * form-表单方式 + * wxapp-微信支付参数(微信公众号,小程序,app支付时) + * aliapp-支付宝app支付参数 + * ysfapp-云闪付app支付参数 + * codeUrl-二维码地址 + * codeImgUrl-二维码图片地址 + * none-空支付参数 + */ + private String payDataType; + + /** + * 支付数据(可选) + * 发起支付用到的支付参数,根据payDataType不同而不同 + * 如果payDataType为payUrl,则此字段为跳转链接 + */ + private String payData; + + /** + * 渠道错误码(可选) + */ + private String errCode; + + /** + * 渠道错误描述(可选) + */ + private String errMsg; + + // 以下字段为兼容旧版本保留 + /** + * 支付链接(兼容字段,优先使用payData) + */ + private String payUrl; + + /** + * 订单号(兼容字段) + */ + private String orderId; + + /** + * 支付参数(兼容字段) */ private String payParams; } diff --git a/ruoyi-system/src/main/java/com/ruoyi/ai/service/IVmService.java b/ruoyi-system/src/main/java/com/ruoyi/ai/service/IVmService.java index ad4f42c..baca39e 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/ai/service/IVmService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/ai/service/IVmService.java @@ -2,7 +2,6 @@ package com.ruoyi.ai.service; import com.ruoyi.ai.domain.PayResVO; import com.ruoyi.ai.domain.VmCallBackReq; -import com.ruoyi.common.core.domain.AjaxResult; /** * VM支付服务接口 @@ -17,16 +16,17 @@ public interface IVmService { * * @param gearId 档位ID * @param vmCardInfo VM卡信息(可选,如果为null则需要在extParam中传入) + * @param clientIp 客户端IP地址 * @return 支付结果 * @throws Exception 支付异常 */ - PayResVO vmPay(Long gearId, com.ruoyi.ai.domain.VmCardInfo vmCardInfo) throws Exception; + PayResVO vmPay(Long gearId, com.ruoyi.ai.domain.VmCardInfo vmCardInfo, String clientIp) throws Exception; /** - * VM支付回调 + * VM支付回调。文档要求返回字符串 "success" 表示成功,非 success 表示失败,支付中心将再次通知。 * * @param req 回调请求 - * @return 回调处理结果 + * @return "success" 表示处理成功,其他字符串表示失败 */ - AjaxResult vmCallBack(VmCallBackReq req); + String vmCallBack(VmCallBackReq req); } diff --git a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiRechargeServiceImpl.java b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiRechargeServiceImpl.java index 725c8b5..e5b3475 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiRechargeServiceImpl.java +++ b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/AiRechargeServiceImpl.java @@ -79,6 +79,10 @@ public class AiRechargeServiceImpl implements IAiRechargeService { public void addRecharge(String orderNo) { // 设置到账时间 AiRecharge aiRecharge = this.getAiRechargeByOrderNum(orderNo); + if (aiRecharge == null) { + log.error("支付回调订单不存在,orderNo: {}", orderNo); + return; + } Date creditedTime = aiRecharge.getCreditedTime(); if (creditedTime != null) { log.error("支付回调重复 {}", orderNo); diff --git a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/VmService.java b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/VmService.java index cf1f3e7..e134dbc 100644 --- a/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/VmService.java +++ b/ruoyi-system/src/main/java/com/ruoyi/ai/service/impl/VmService.java @@ -73,7 +73,7 @@ public class VmService implements IVmService { private IExchangeRateService exchangeRateService; @Override - public PayResVO vmPay(Long gearId, VmCardInfo vmCardInfo) throws Exception { + public PayResVO vmPay(Long gearId, VmCardInfo vmCardInfo, String clientIp) throws Exception { // 充值挡位查询 AiRechargeGiftGear aiRechargeGiftGear = aiRechargeGiftGearService.selectAiRechargeGiftGearById(gearId); if (aiRechargeGiftGear == null) { @@ -117,6 +117,13 @@ public class VmService implements IVmService { req.setReqTime(System.currentTimeMillis()); req.setVersion("1.0"); req.setSignType("MD5"); + // 设置客户端IP + if (clientIp != null && !clientIp.trim().isEmpty()) { + req.setClientIp(clientIp); + log.debug("VM支付设置客户端IP: {}", clientIp); + } else { + log.warn("VM支付客户端IP为空,订单号: {}", orderNo); + } // 生成签名 String sign = generateSign(req); @@ -154,14 +161,95 @@ public class VmService implements IVmService { throw new ServiceException(msg != null ? msg : "支付请求失败", res.getCode() != null ? res.getCode() : -1); } - // 检查返回的payUrl - String payUrl = null; - if (res.getData() != null) { - payUrl = res.getData().getPayUrl(); + // 检查返回数据 + if (res.getData() == null) { + log.error("VM支付返回的data为空,订单号: {}", orderNo); + throw new ServiceException("支付返回的数据为空", -1); } - if (payUrl == null || payUrl.trim().isEmpty()) { - log.error("VM支付返回的payUrl为空,订单号: {}", orderNo); - throw new ServiceException("支付返回的支付链接为空", -1); + + VmUnifiedOrderRes.VmUnifiedOrderData data = res.getData(); + + // 记录关键字段信息 + String payOrderId = data.getPayOrderId(); + String mchOrderNo = data.getMchOrderNo(); + log.info("VM支付返回数据,商户订单号: {}, 支付订单号: {}, 订单状态: {}, payDataType: {}", + mchOrderNo, payOrderId, data.getOrderState(), data.getPayDataType()); + + // 检查订单状态 + Integer orderState = data.getOrderState(); + if (orderState == null) { + log.error("VM支付返回的订单状态为空,订单号: {}", orderNo); + throw new ServiceException("支付返回的订单状态为空", -1); + } + + // 订单状态说明: + // 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭 + String payUrl = null; + String payDataType = data.getPayDataType(); + String payData = data.getPayData(); + + // 如果订单状态是支付失败(3),抛出异常 + if (orderState == 3) { + String errCode = data.getErrCode(); + String errMsg = data.getErrMsg(); + String errorMessage = "支付失败"; + if (errMsg != null && !errMsg.trim().isEmpty()) { + errorMessage = errMsg; + } else if (errCode != null && !errCode.trim().isEmpty()) { + errorMessage = String.format("支付失败,错误码: %s", errCode); + } + log.error("VM支付失败,订单号: {}, 订单状态: {}, 错误码: {}, 错误消息: {}", + orderNo, orderState, errCode, errMsg); + throw new ServiceException(errorMessage, -1); + } + + // 如果订单状态是支付中(1),需要获取支付URL让用户去支付 + // 如果订单状态是支付成功(2),不需要payUrl(用户说"订单状态只要是支付成功就不用管订单状态") + if (orderState == 1) { + // 根据payDataType获取支付URL + if (payDataType != null && payData != null && !payData.trim().isEmpty()) { + // payurl类型(注意:实际返回是小写payurl):跳转链接的方式 + if ("payurl".equalsIgnoreCase(payDataType) || "payUrl".equalsIgnoreCase(payDataType)) { + payUrl = payData; + log.info("VM支付使用payData作为支付URL(payDataType={}),订单号: {}", payDataType, orderNo); + } + // codeUrl类型:二维码地址 + else if ("codeUrl".equalsIgnoreCase(payDataType)) { + payUrl = payData; + log.info("VM支付使用payData作为支付URL(payDataType=codeUrl),订单号: {}", orderNo); + } + // codeImgUrl类型:二维码图片地址 + else if ("codeImgUrl".equalsIgnoreCase(payDataType)) { + payUrl = payData; + log.info("VM支付使用payData作为支付URL(payDataType=codeImgUrl),订单号: {}", orderNo); + } + // 其他类型(form, wxapp, aliapp, ysfapp, none)暂不处理payUrl + else if (!"none".equalsIgnoreCase(payDataType)) { + log.info("VM支付payDataType为: {},暂不支持作为支付URL,订单号: {}", payDataType, orderNo); + } + } + + // 兼容旧版本:如果payUrl字段有值,优先使用 + if ((payUrl == null || payUrl.trim().isEmpty()) && + data.getPayUrl() != null && !data.getPayUrl().trim().isEmpty()) { + payUrl = data.getPayUrl(); + log.info("VM支付使用payUrl字段,订单号: {}", orderNo); + } + + // 如果仍然没有payUrl,记录警告 + if (payUrl == null || payUrl.trim().isEmpty()) { + log.warn("VM支付返回的payUrl为空,订单状态为支付中,订单号: {}, payDataType: {}", + orderNo, payDataType); + payUrl = ""; + } + } else if (orderState == 2) { + // 支付成功,不需要payUrl + log.info("VM支付订单状态为支付成功,不需要返回payUrl,订单号: {}", orderNo); + payUrl = ""; + } else { + // 其他状态(0-订单生成, 4-已撤销, 5-已退款, 6-订单关闭) + log.info("VM支付订单状态: {},订单号: {}", orderState, orderNo); + payUrl = ""; } // 创建充值管理 @@ -186,39 +274,40 @@ public class VmService implements IVmService { @Override @Transactional - public AjaxResult vmCallBack(VmCallBackReq req) { - log.info("VM支付回调,订单号: {}, 状态: {}", req.getMchOrderNo(), req.getOrderState()); + public String vmCallBack(VmCallBackReq req) { + log.info("VM支付回调,订单号: {}, 状态: {}", req.getMchOrderNo(), req.getState()); + + log.debug("VM支付回调请求对象: mchNo={}, appId={}, mchOrderNo={}, payOrderId={}, ifCode={}, wayCode={}, amount={}, currency={}, state={}, subject={}, body={}, createdAt={}, reqTime={}, sign={}", + req.getMchNo(), req.getAppId(), req.getMchOrderNo(), req.getPayOrderId(), req.getIfCode(), req.getWayCode(), + req.getAmount(), req.getCurrency(), req.getState(), req.getSubject(), req.getBody(), + req.getCreatedAt(), req.getReqTime(), req.getSign()); // 验证签名 String sign = generateCallBackSign(req); if (!sign.equals(req.getSign())) { log.error("VM支付回调签名错误,订单号: {}, 期望签名: {}, 实际签名: {}", req.getMchOrderNo(), sign, req.getSign()); - return AjaxResult.error("签名验证失败"); + return "fail"; } - // 处理订单状态 - // 订单状态:0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭 - Integer orderState = req.getOrderState(); - if (orderState == null) { - log.error("VM支付回调状态为空,订单号: {}", req.getMchOrderNo()); - return AjaxResult.error("状态为空"); + // 处理订单状态(文档字段名:state) + // 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭 + Integer state = req.getState(); + if (state == null) { + log.warn("VM支付回调state为空,订单号: {},返回 success 等待后续回调", req.getMchOrderNo()); + return "success"; } - if (orderState == 2) { - // 支付成功处理 + if (state == 2) { log.info("VM支付成功,订单号: {}", req.getMchOrderNo()); aiRechargeService.addRecharge(req.getMchOrderNo()); - return AjaxResult.success(); - } else if (orderState == 3) { - // 支付失败 + return "success"; + } else if (state == 3) { log.warn("VM支付失败,订单号: {}", req.getMchOrderNo()); - // 这里可以添加失败处理逻辑,比如更新订单状态为失败 - return AjaxResult.success(); + return "success"; } else { - // 其他状态(处理中、已撤销、已退款、订单关闭等) - log.info("VM支付状态: {}, 订单号: {}", orderState, req.getMchOrderNo()); - return AjaxResult.success(); + log.info("VM支付状态: {}, 订单号: {}", state, req.getMchOrderNo()); + return "success"; } } @@ -299,12 +388,12 @@ public class VmService implements IVmService { } /** - * 生成回调签名 + * 生成回调签名。按文档字段名:state、reqTime;sign、signType 不参与签名。 + * 参与签名的非空参数按 key 字典序排序,key=value 用 & 拼接,最后 &key=secret,MD5 后转大写。 */ private String generateCallBackSign(VmCallBackReq req) { TreeMap params = new TreeMap<>(); - // 添加所有非空参数(sign不参与签名) if (req.getMchNo() != null && !req.getMchNo().isEmpty()) { params.put("mchNo", req.getMchNo()); } @@ -317,6 +406,9 @@ public class VmService implements IVmService { if (req.getPayOrderId() != null && !req.getPayOrderId().isEmpty()) { params.put("payOrderId", req.getPayOrderId()); } + if (req.getIfCode() != null && !req.getIfCode().isEmpty()) { + params.put("ifCode", req.getIfCode()); + } if (req.getWayCode() != null && !req.getWayCode().isEmpty()) { params.put("wayCode", req.getWayCode()); } @@ -326,8 +418,17 @@ public class VmService implements IVmService { if (req.getCurrency() != null && !req.getCurrency().isEmpty()) { params.put("currency", req.getCurrency()); } - if (req.getOrderState() != null) { - params.put("orderState", req.getOrderState()); + if (req.getState() != null) { + params.put("state", req.getState()); + } + if (req.getClientIp() != null && !req.getClientIp().isEmpty()) { + params.put("clientIp", req.getClientIp()); + } + if (req.getSubject() != null && !req.getSubject().isEmpty()) { + params.put("subject", req.getSubject()); + } + if (req.getBody() != null && !req.getBody().isEmpty()) { + params.put("body", req.getBody()); } if (req.getChannelOrderNo() != null && !req.getChannelOrderNo().isEmpty()) { params.put("channelOrderNo", req.getChannelOrderNo()); @@ -341,14 +442,16 @@ public class VmService implements IVmService { if (req.getExtParam() != null && !req.getExtParam().isEmpty()) { params.put("extParam", req.getExtParam()); } - if (req.getNotifyTime() != null) { - params.put("notifyTime", req.getNotifyTime()); + if (req.getCreatedAt() != null) { + params.put("createdAt", req.getCreatedAt()); } - if (req.getSignType() != null && !req.getSignType().isEmpty()) { - params.put("signType", req.getSignType()); + if (req.getSuccessTime() != null) { + params.put("successTime", req.getSuccessTime()); + } + if (req.getReqTime() != null) { + params.put("reqTime", req.getReqTime()); } - // 拼接参数 StringBuilder stringA = new StringBuilder(); for (Map.Entry entry : params.entrySet()) { if (stringA.length() > 0) { @@ -357,12 +460,11 @@ public class VmService implements IVmService { stringA.append(entry.getKey()).append("=").append(entry.getValue()); } - // 拼接key String stringSignTemp = stringA.toString() + "&key=" + secret; log.debug("VM回调签名源字符串: {}", stringSignTemp); - // MD5并转大写 String sign = Md5Utils.hash(stringSignTemp).toUpperCase(); + log.debug("VM回调计算出的签名: {}", sign); return sign; } }