fix: vm支付对接googlecom去除
This commit is contained in:
parent
d284966664
commit
baac3fa842
|
|
@ -69,8 +69,8 @@ public class KadaPaymentJava {
|
||||||
map.put("pm", "QRPH");
|
map.put("pm", "QRPH");
|
||||||
map.put("ref", orderNo);
|
map.put("ref", orderNo);
|
||||||
map.put("payer", payer);
|
map.put("payer", payer);
|
||||||
map.put("redirect", "www.google.com/recharge");
|
map.put("redirect", "https://undressing.top/recharge");
|
||||||
map.put("callbackUrl", "www.google.com/api/pay/kada-callBack");
|
map.put("callbackUrl", "https://undressing.top/api/pay/kada-callBack");
|
||||||
|
|
||||||
return JSON.toJSONString(map);
|
return JSON.toJSONString(map);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,11 @@ import com.ruoyi.ai.service.IYuZhouService;
|
||||||
import com.ruoyi.ai.service.IVmService;
|
import com.ruoyi.ai.service.IVmService;
|
||||||
import com.ruoyi.common.annotation.Anonymous;
|
import com.ruoyi.common.annotation.Anonymous;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
import com.ruoyi.common.core.domain.AjaxResult;
|
||||||
|
import com.ruoyi.common.utils.ip.IpUtils;
|
||||||
import io.swagger.annotations.Api;
|
import io.swagger.annotations.Api;
|
||||||
import io.swagger.annotations.ApiOperation;
|
import io.swagger.annotations.ApiOperation;
|
||||||
import lombok.RequiredArgsConstructor;
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
|
@ -21,6 +23,7 @@ import java.util.Map;
|
||||||
@RequestMapping("/api/pay")
|
@RequestMapping("/api/pay")
|
||||||
@Api(tags = "支付接口")
|
@Api(tags = "支付接口")
|
||||||
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
@RequiredArgsConstructor(onConstructor_ = @Autowired)
|
||||||
|
@Slf4j
|
||||||
public class PayController {
|
public class PayController {
|
||||||
private final IJinShaService jinShaService;
|
private final IJinShaService jinShaService;
|
||||||
private final IKaDaService kaDaService;
|
private final IKaDaService kaDaService;
|
||||||
|
|
@ -92,17 +95,163 @@ public class PayController {
|
||||||
*/
|
*/
|
||||||
@PostMapping("/vm-pay")
|
@PostMapping("/vm-pay")
|
||||||
@ApiOperation("VM请求支付")
|
@ApiOperation("VM请求支付")
|
||||||
public AjaxResult vmPay(@RequestBody VmPayReq req) throws Exception {
|
public AjaxResult vmPay(@RequestBody VmPayReq req, HttpServletRequest request) throws Exception {
|
||||||
PayResVO payResVO = vmService.vmPay(req.getGearId(), req.getVmCardInfo());
|
// 打印入参
|
||||||
|
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);
|
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")
|
@PostMapping("/vm-callBack")
|
||||||
@ApiOperation("VM支付回调")
|
@ApiOperation("VM支付回调")
|
||||||
@Anonymous
|
@Anonymous
|
||||||
public AjaxResult vmCallBack(@RequestBody VmCallBackReq req) {
|
public String vmCallBack(HttpServletRequest request) {
|
||||||
AjaxResult ajaxResult = vmService.vmCallBack(req);
|
// 记录请求的Content-Type
|
||||||
return ajaxResult;
|
String contentType = request.getContentType();
|
||||||
|
|
||||||
|
// 从请求中解析表单参数
|
||||||
|
VmCallBackReq req = new VmCallBackReq();
|
||||||
|
|
||||||
|
// 记录所有请求参数用于调试
|
||||||
|
java.util.Enumeration<String> paramNames = request.getParameterNames();
|
||||||
|
java.util.Map<String, String> 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);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,22 +47,43 @@ public class VmCallBackReq {
|
||||||
private String currency;
|
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 channelOrderNo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渠道错误码
|
* 渠道错误码(可选)
|
||||||
*/
|
*/
|
||||||
private String errCode;
|
private String errCode;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 渠道错误描述
|
* 渠道错误描述(可选)
|
||||||
*/
|
*/
|
||||||
private String errMsg;
|
private String errMsg;
|
||||||
|
|
||||||
|
|
@ -72,9 +93,19 @@ public class VmCallBackReq {
|
||||||
private String extParam;
|
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 sign;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 签名类型
|
* 签名类型(文档回调示例无此字段,不参与签名)
|
||||||
*/
|
*/
|
||||||
private String signType;
|
private String signType;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.ruoyi.ai.domain;
|
package com.ruoyi.ai.domain;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -9,6 +10,7 @@ import lombok.Data;
|
||||||
* @date 2025-01-XX
|
* @date 2025-01-XX
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
|
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||||
public class VmUnifiedOrderRes {
|
public class VmUnifiedOrderRes {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -26,30 +28,72 @@ public class VmUnifiedOrderRes {
|
||||||
*/
|
*/
|
||||||
private VmUnifiedOrderData data;
|
private VmUnifiedOrderData data;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 响应签名(API 返回)
|
||||||
|
*/
|
||||||
|
private String sign;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class VmUnifiedOrderData {
|
public static class VmUnifiedOrderData {
|
||||||
/**
|
/**
|
||||||
* 支付链接
|
* 支付订单号(必填)
|
||||||
*/
|
*/
|
||||||
private String payUrl;
|
private String payOrderId;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单号
|
* 商户订单号(必填)
|
||||||
*/
|
|
||||||
private String orderId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 商户订单号
|
|
||||||
*/
|
*/
|
||||||
private String mchOrderNo;
|
private String mchOrderNo;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 订单状态
|
* 订单状态(必填)
|
||||||
|
* 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭
|
||||||
*/
|
*/
|
||||||
private Integer orderState;
|
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;
|
private String payParams;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,6 @@ package com.ruoyi.ai.service;
|
||||||
|
|
||||||
import com.ruoyi.ai.domain.PayResVO;
|
import com.ruoyi.ai.domain.PayResVO;
|
||||||
import com.ruoyi.ai.domain.VmCallBackReq;
|
import com.ruoyi.ai.domain.VmCallBackReq;
|
||||||
import com.ruoyi.common.core.domain.AjaxResult;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* VM支付服务接口
|
* VM支付服务接口
|
||||||
|
|
@ -17,16 +16,17 @@ public interface IVmService {
|
||||||
*
|
*
|
||||||
* @param gearId 档位ID
|
* @param gearId 档位ID
|
||||||
* @param vmCardInfo VM卡信息(可选,如果为null则需要在extParam中传入)
|
* @param vmCardInfo VM卡信息(可选,如果为null则需要在extParam中传入)
|
||||||
|
* @param clientIp 客户端IP地址
|
||||||
* @return 支付结果
|
* @return 支付结果
|
||||||
* @throws Exception 支付异常
|
* @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 回调请求
|
* @param req 回调请求
|
||||||
* @return 回调处理结果
|
* @return "success" 表示处理成功,其他字符串表示失败
|
||||||
*/
|
*/
|
||||||
AjaxResult vmCallBack(VmCallBackReq req);
|
String vmCallBack(VmCallBackReq req);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,10 @@ public class AiRechargeServiceImpl implements IAiRechargeService {
|
||||||
public void addRecharge(String orderNo) {
|
public void addRecharge(String orderNo) {
|
||||||
// 设置到账时间
|
// 设置到账时间
|
||||||
AiRecharge aiRecharge = this.getAiRechargeByOrderNum(orderNo);
|
AiRecharge aiRecharge = this.getAiRechargeByOrderNum(orderNo);
|
||||||
|
if (aiRecharge == null) {
|
||||||
|
log.error("支付回调订单不存在,orderNo: {}", orderNo);
|
||||||
|
return;
|
||||||
|
}
|
||||||
Date creditedTime = aiRecharge.getCreditedTime();
|
Date creditedTime = aiRecharge.getCreditedTime();
|
||||||
if (creditedTime != null) {
|
if (creditedTime != null) {
|
||||||
log.error("支付回调重复 {}", orderNo);
|
log.error("支付回调重复 {}", orderNo);
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,7 @@ public class VmService implements IVmService {
|
||||||
private IExchangeRateService exchangeRateService;
|
private IExchangeRateService exchangeRateService;
|
||||||
|
|
||||||
@Override
|
@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);
|
AiRechargeGiftGear aiRechargeGiftGear = aiRechargeGiftGearService.selectAiRechargeGiftGearById(gearId);
|
||||||
if (aiRechargeGiftGear == null) {
|
if (aiRechargeGiftGear == null) {
|
||||||
|
|
@ -117,6 +117,13 @@ public class VmService implements IVmService {
|
||||||
req.setReqTime(System.currentTimeMillis());
|
req.setReqTime(System.currentTimeMillis());
|
||||||
req.setVersion("1.0");
|
req.setVersion("1.0");
|
||||||
req.setSignType("MD5");
|
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);
|
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);
|
throw new ServiceException(msg != null ? msg : "支付请求失败", res.getCode() != null ? res.getCode() : -1);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查返回的payUrl
|
// 检查返回数据
|
||||||
String payUrl = null;
|
if (res.getData() == null) {
|
||||||
if (res.getData() != null) {
|
log.error("VM支付返回的data为空,订单号: {}", orderNo);
|
||||||
payUrl = res.getData().getPayUrl();
|
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()) {
|
if (payUrl == null || payUrl.trim().isEmpty()) {
|
||||||
log.error("VM支付返回的payUrl为空,订单号: {}", orderNo);
|
log.warn("VM支付返回的payUrl为空,订单状态为支付中,订单号: {}, payDataType: {}",
|
||||||
throw new ServiceException("支付返回的支付链接为空", -1);
|
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
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public AjaxResult vmCallBack(VmCallBackReq req) {
|
public String vmCallBack(VmCallBackReq req) {
|
||||||
log.info("VM支付回调,订单号: {}, 状态: {}", req.getMchOrderNo(), req.getOrderState());
|
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);
|
String sign = generateCallBackSign(req);
|
||||||
if (!sign.equals(req.getSign())) {
|
if (!sign.equals(req.getSign())) {
|
||||||
log.error("VM支付回调签名错误,订单号: {}, 期望签名: {}, 实际签名: {}",
|
log.error("VM支付回调签名错误,订单号: {}, 期望签名: {}, 实际签名: {}",
|
||||||
req.getMchOrderNo(), sign, req.getSign());
|
req.getMchOrderNo(), sign, req.getSign());
|
||||||
return AjaxResult.error("签名验证失败");
|
return "fail";
|
||||||
}
|
}
|
||||||
|
|
||||||
// 处理订单状态
|
// 处理订单状态(文档字段名:state)
|
||||||
// 订单状态:0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭
|
// 0-订单生成, 1-支付中, 2-支付成功, 3-支付失败, 4-已撤销, 5-已退款, 6-订单关闭
|
||||||
Integer orderState = req.getOrderState();
|
Integer state = req.getState();
|
||||||
if (orderState == null) {
|
if (state == null) {
|
||||||
log.error("VM支付回调状态为空,订单号: {}", req.getMchOrderNo());
|
log.warn("VM支付回调state为空,订单号: {},返回 success 等待后续回调", req.getMchOrderNo());
|
||||||
return AjaxResult.error("状态为空");
|
return "success";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (orderState == 2) {
|
if (state == 2) {
|
||||||
// 支付成功处理
|
|
||||||
log.info("VM支付成功,订单号: {}", req.getMchOrderNo());
|
log.info("VM支付成功,订单号: {}", req.getMchOrderNo());
|
||||||
aiRechargeService.addRecharge(req.getMchOrderNo());
|
aiRechargeService.addRecharge(req.getMchOrderNo());
|
||||||
return AjaxResult.success();
|
return "success";
|
||||||
} else if (orderState == 3) {
|
} else if (state == 3) {
|
||||||
// 支付失败
|
|
||||||
log.warn("VM支付失败,订单号: {}", req.getMchOrderNo());
|
log.warn("VM支付失败,订单号: {}", req.getMchOrderNo());
|
||||||
// 这里可以添加失败处理逻辑,比如更新订单状态为失败
|
return "success";
|
||||||
return AjaxResult.success();
|
|
||||||
} else {
|
} else {
|
||||||
// 其他状态(处理中、已撤销、已退款、订单关闭等)
|
log.info("VM支付状态: {}, 订单号: {}", state, req.getMchOrderNo());
|
||||||
log.info("VM支付状态: {}, 订单号: {}", orderState, req.getMchOrderNo());
|
return "success";
|
||||||
return AjaxResult.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) {
|
private String generateCallBackSign(VmCallBackReq req) {
|
||||||
TreeMap<String, Object> params = new TreeMap<>();
|
TreeMap<String, Object> params = new TreeMap<>();
|
||||||
|
|
||||||
// 添加所有非空参数(sign不参与签名)
|
|
||||||
if (req.getMchNo() != null && !req.getMchNo().isEmpty()) {
|
if (req.getMchNo() != null && !req.getMchNo().isEmpty()) {
|
||||||
params.put("mchNo", req.getMchNo());
|
params.put("mchNo", req.getMchNo());
|
||||||
}
|
}
|
||||||
|
|
@ -317,6 +406,9 @@ public class VmService implements IVmService {
|
||||||
if (req.getPayOrderId() != null && !req.getPayOrderId().isEmpty()) {
|
if (req.getPayOrderId() != null && !req.getPayOrderId().isEmpty()) {
|
||||||
params.put("payOrderId", req.getPayOrderId());
|
params.put("payOrderId", req.getPayOrderId());
|
||||||
}
|
}
|
||||||
|
if (req.getIfCode() != null && !req.getIfCode().isEmpty()) {
|
||||||
|
params.put("ifCode", req.getIfCode());
|
||||||
|
}
|
||||||
if (req.getWayCode() != null && !req.getWayCode().isEmpty()) {
|
if (req.getWayCode() != null && !req.getWayCode().isEmpty()) {
|
||||||
params.put("wayCode", req.getWayCode());
|
params.put("wayCode", req.getWayCode());
|
||||||
}
|
}
|
||||||
|
|
@ -326,8 +418,17 @@ public class VmService implements IVmService {
|
||||||
if (req.getCurrency() != null && !req.getCurrency().isEmpty()) {
|
if (req.getCurrency() != null && !req.getCurrency().isEmpty()) {
|
||||||
params.put("currency", req.getCurrency());
|
params.put("currency", req.getCurrency());
|
||||||
}
|
}
|
||||||
if (req.getOrderState() != null) {
|
if (req.getState() != null) {
|
||||||
params.put("orderState", req.getOrderState());
|
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()) {
|
if (req.getChannelOrderNo() != null && !req.getChannelOrderNo().isEmpty()) {
|
||||||
params.put("channelOrderNo", req.getChannelOrderNo());
|
params.put("channelOrderNo", req.getChannelOrderNo());
|
||||||
|
|
@ -341,14 +442,16 @@ public class VmService implements IVmService {
|
||||||
if (req.getExtParam() != null && !req.getExtParam().isEmpty()) {
|
if (req.getExtParam() != null && !req.getExtParam().isEmpty()) {
|
||||||
params.put("extParam", req.getExtParam());
|
params.put("extParam", req.getExtParam());
|
||||||
}
|
}
|
||||||
if (req.getNotifyTime() != null) {
|
if (req.getCreatedAt() != null) {
|
||||||
params.put("notifyTime", req.getNotifyTime());
|
params.put("createdAt", req.getCreatedAt());
|
||||||
}
|
}
|
||||||
if (req.getSignType() != null && !req.getSignType().isEmpty()) {
|
if (req.getSuccessTime() != null) {
|
||||||
params.put("signType", req.getSignType());
|
params.put("successTime", req.getSuccessTime());
|
||||||
|
}
|
||||||
|
if (req.getReqTime() != null) {
|
||||||
|
params.put("reqTime", req.getReqTime());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拼接参数
|
|
||||||
StringBuilder stringA = new StringBuilder();
|
StringBuilder stringA = new StringBuilder();
|
||||||
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
for (Map.Entry<String, Object> entry : params.entrySet()) {
|
||||||
if (stringA.length() > 0) {
|
if (stringA.length() > 0) {
|
||||||
|
|
@ -357,12 +460,11 @@ public class VmService implements IVmService {
|
||||||
stringA.append(entry.getKey()).append("=").append(entry.getValue());
|
stringA.append(entry.getKey()).append("=").append(entry.getValue());
|
||||||
}
|
}
|
||||||
|
|
||||||
// 拼接key
|
|
||||||
String stringSignTemp = stringA.toString() + "&key=" + secret;
|
String stringSignTemp = stringA.toString() + "&key=" + secret;
|
||||||
log.debug("VM回调签名源字符串: {}", stringSignTemp);
|
log.debug("VM回调签名源字符串: {}", stringSignTemp);
|
||||||
|
|
||||||
// MD5并转大写
|
|
||||||
String sign = Md5Utils.hash(stringSignTemp).toUpperCase();
|
String sign = Md5Utils.hash(stringSignTemp).toUpperCase();
|
||||||
|
log.debug("VM回调计算出的签名: {}", sign);
|
||||||
return sign;
|
return sign;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue