mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2025-02-20 03:00:34 +08:00
mall + pay:
1. 拆分支付回调、退款回调的 URL 2. 修复微信支付回调的时间解析错误
This commit is contained in:
parent
66ed61c641
commit
68a4ef98ca
@ -13,14 +13,25 @@ import javax.validation.constraints.NotEmpty;
|
|||||||
public class PayProperties {
|
public class PayProperties {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 回调地址
|
* 支付回调地址
|
||||||
*
|
*
|
||||||
* 实际上,对应的 PayNotifyController 的 notifyCallback 方法的 URL
|
* 实际上,对应的 PayNotifyController 的 notifyOrder 方法的 URL
|
||||||
*
|
*
|
||||||
* 注意,支付渠道统一回调到 payNotifyUrl 地址,由支付模块统一处理;然后,自己的支付模块,在回调 PayAppDO.payNotifyUrl 地址
|
* 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 orderNotifyUrl 地址 => 业务的 PayAppDO.orderNotifyUrl 地址
|
||||||
*/
|
*/
|
||||||
@NotEmpty(message = "回调地址不能为空")
|
@NotEmpty(message = "支付回调地址不能为空")
|
||||||
@URL(message = "回调地址的格式必须是 URL")
|
@URL(message = "支付回调地址的格式必须是 URL")
|
||||||
private String callbackUrl;
|
private String orderNotifyUrl;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退款回调地址
|
||||||
|
*
|
||||||
|
* 实际上,对应的 PayNotifyController 的 notifyRefund 方法的 URL
|
||||||
|
*
|
||||||
|
* 回调顺序:支付渠道(支付宝支付、微信支付) => yudao-module-pay 的 refundNotifyUrl 地址 => 业务的 PayAppDO.notifyRefundUrl 地址
|
||||||
|
*/
|
||||||
|
@NotEmpty(message = "支付回调地址不能为空")
|
||||||
|
@URL(message = "支付回调地址的格式必须是 URL")
|
||||||
|
private String refundNotifyUrl;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -22,6 +22,8 @@ public interface PayClient {
|
|||||||
*/
|
*/
|
||||||
Long getId();
|
Long getId();
|
||||||
|
|
||||||
|
// ============ 支付相关 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用支付渠道,统一下单
|
* 调用支付渠道,统一下单
|
||||||
*
|
*
|
||||||
@ -30,6 +32,17 @@ public interface PayClient {
|
|||||||
*/
|
*/
|
||||||
PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
|
PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析 order 回调数据
|
||||||
|
*
|
||||||
|
* @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
|
||||||
|
* @param body HTTP 回调接口的 request body
|
||||||
|
* @return 支付订单信息
|
||||||
|
*/
|
||||||
|
PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body);
|
||||||
|
|
||||||
|
// ============ 退款相关 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 调用支付渠道,进行退款
|
* 调用支付渠道,进行退款
|
||||||
*
|
*
|
||||||
@ -39,16 +52,12 @@ public interface PayClient {
|
|||||||
PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
|
PayRefundRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析回调数据
|
* 解析 refund 回调数据
|
||||||
*
|
*
|
||||||
* @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
|
* @param params HTTP 回调接口 content type 为 application/x-www-form-urlencoded 的所有参数
|
||||||
* @param body HTTP 回调接口的 request body
|
* @param body HTTP 回调接口的 request body
|
||||||
* @return 回调对象
|
* @return 支付订单信息
|
||||||
* 1. {@link PayRefundRespDTO} 退款通知
|
|
||||||
* 2. {@link PayOrderRespDTO} 支付通知
|
|
||||||
*/
|
*/
|
||||||
default Object parseNotify(Map<String, String> params, String body) {
|
PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body);
|
||||||
throw new UnsupportedOperationException("未实现 parseNotify 方法!");
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -97,13 +97,7 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SneakyThrows
|
@SneakyThrows
|
||||||
public Object parseNotify(Map<String, String> params, String body) {
|
public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) {
|
||||||
// 补充说明:支付宝退款时,没有回调,这点和微信支付是不同的。并且,退款分成部分退款、和全部退款。
|
|
||||||
// ① 部分退款:是会有回调,但是它回调的是订单状态的同步回调,不是退款订单的回调
|
|
||||||
// ② 全部退款:Wap 支付有订单状态的同步回调,但是 PC/扫码又没有
|
|
||||||
// 所以,这里在解析时,即使是退款导致的订单状态同步,我们也忽略不做为“退款同步”,而是订单的回调。
|
|
||||||
// 实际上,支付宝退款只要发起成功,就可以认为退款成功,不需要等待回调。
|
|
||||||
|
|
||||||
// 1. 校验回调数据
|
// 1. 校验回调数据
|
||||||
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
|
||||||
AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
|
AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
|
||||||
@ -127,6 +121,16 @@ public abstract class AbstractAlipayPayClient extends AbstractPayClient<AlipayPa
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
|
||||||
|
// 补充说明:支付宝退款时,没有回调,这点和微信支付是不同的。并且,退款分成部分退款、和全部退款。
|
||||||
|
// ① 部分退款:是会有回调,但是它回调的是订单状态的同步回调,不是退款订单的回调
|
||||||
|
// ② 全部退款:Wap 支付有订单状态的同步回调,但是 PC/扫码又没有
|
||||||
|
// 所以,这里在解析时,即使是退款导致的订单状态同步,我们也忽略不做为“退款同步”,而是订单的回调。
|
||||||
|
// 实际上,支付宝退款只要发起成功,就可以认为退款成功,不需要等待回调。
|
||||||
|
throw new UnsupportedOperationException("支付宝无退款回调");
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 各种工具方法 ==========
|
// ========== 各种工具方法 ==========
|
||||||
|
|
||||||
protected String formatAmount(Integer amount) {
|
protected String formatAmount(Integer amount) {
|
||||||
|
@ -14,9 +14,11 @@ import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
|||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
|
import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.enums.order.PayOrderStatusRespEnum;
|
||||||
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
|
import cn.iocoder.yudao.framework.pay.core.enums.refund.PayRefundStatusRespEnum;
|
||||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
|
||||||
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
|
import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyV3Result;
|
||||||
|
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
|
||||||
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
|
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
|
||||||
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
|
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
|
||||||
import com.github.binarywang.wxpay.config.WxPayConfig;
|
import com.github.binarywang.wxpay.config.WxPayConfig;
|
||||||
@ -30,8 +32,7 @@ import java.time.ZoneId;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
|
||||||
import static cn.hutool.core.date.DatePattern.PURE_DATETIME_PATTERN;
|
import static cn.hutool.core.date.DatePattern.*;
|
||||||
import static cn.hutool.core.date.DatePattern.UTC_WITH_XXX_OFFSET_PATTERN;
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.invalidParamException;
|
||||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
||||||
@ -61,9 +62,6 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
|||||||
WxPayConfig payConfig = new WxPayConfig();
|
WxPayConfig payConfig = new WxPayConfig();
|
||||||
BeanUtil.copyProperties(config, payConfig, "keyContent");
|
BeanUtil.copyProperties(config, payConfig, "keyContent");
|
||||||
payConfig.setTradeType(tradeType);
|
payConfig.setTradeType(tradeType);
|
||||||
// if (WxPayClientConfig.API_VERSION_V2.equals(config.getApiVersion())) {
|
|
||||||
// payConfig.setSignType(WxPayConstants.SignType.MD5);
|
|
||||||
// }
|
|
||||||
// weixin-pay-java 无法设置内容,只允许读取文件,所以这里要创建临时文件来解决
|
// weixin-pay-java 无法设置内容,只允许读取文件,所以这里要创建临时文件来解决
|
||||||
if (Base64.isBase64(config.getKeyContent())) {
|
if (Base64.isBase64(config.getKeyContent())) {
|
||||||
payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath());
|
payConfig.setKeyPath(FileUtils.createTempFile(Base64.decode(config.getKeyContent())).getPath());
|
||||||
@ -162,8 +160,7 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Object parseNotify(Map<String, String> params, String body) {
|
public PayOrderRespDTO parseOrderNotify(Map<String, String> params, String body) {
|
||||||
log.info("[parseNotify][微信支付回调 data 数据: {}]", body);
|
|
||||||
try {
|
try {
|
||||||
// 微信支付 v2 回调结果处理
|
// 微信支付 v2 回调结果处理
|
||||||
switch (config.getApiVersion()) {
|
switch (config.getApiVersion()) {
|
||||||
@ -183,21 +180,24 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PayOrderRespDTO parseOrderNotifyV2(String body) throws WxPayException {
|
private PayOrderRespDTO parseOrderNotifyV2(String body) throws WxPayException {
|
||||||
WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(body);
|
// 1. 解析回调
|
||||||
Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
|
WxPayOrderNotifyResult response = client.parseOrderNotifyResult(body);
|
||||||
// 转换结果
|
// 2. 构建结果
|
||||||
return PayOrderRespDTO
|
return PayOrderRespDTO.builder()
|
||||||
.builder()
|
.outTradeNo(response.getOutTradeNo())
|
||||||
.outTradeNo(notifyResult.getOutTradeNo())
|
.channelOrderNo(response.getTransactionId())
|
||||||
.channelOrderNo(notifyResult.getTransactionId())
|
.channelUserId(response.getOpenid())
|
||||||
.channelUserId(notifyResult.getOpenid())
|
.status(Objects.equals(response.getResultCode(), "SUCCESS") ?
|
||||||
.successTime(parseDateV2(notifyResult.getTimeEnd()))
|
PayOrderStatusRespEnum.SUCCESS.getStatus() : PayOrderStatusRespEnum.CLOSED.getStatus())
|
||||||
|
.successTime(parseDateV2(response.getTimeEnd()))
|
||||||
|
.rawData(response)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private PayOrderRespDTO parseOrderNotifyV3(String body) throws WxPayException {
|
private PayOrderRespDTO parseOrderNotifyV3(String body) throws WxPayException {
|
||||||
WxPayOrderNotifyV3Result notifyResult = client.parseOrderNotifyV3Result(body, null);
|
WxPayOrderNotifyV3Result notifyResult = client.parseOrderNotifyV3Result(body, null);
|
||||||
WxPayOrderNotifyV3Result.DecryptNotifyResult result = notifyResult.getResult();
|
WxPayOrderNotifyV3Result.DecryptNotifyResult result = notifyResult.getResult();
|
||||||
|
// TODO 芋艿:翻译下 state
|
||||||
// 转换结果
|
// 转换结果
|
||||||
Assert.isTrue(Objects.equals(notifyResult.getResult().getTradeState(), "SUCCESS"),
|
Assert.isTrue(Objects.equals(notifyResult.getResult().getTradeState(), "SUCCESS"),
|
||||||
"支付结果非 SUCCESS");
|
"支付结果非 SUCCESS");
|
||||||
@ -209,6 +209,49 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public PayRefundRespDTO parseRefundNotify(Map<String, String> params, String body) {
|
||||||
|
try {
|
||||||
|
// 微信支付 v2 回调结果处理
|
||||||
|
switch (config.getApiVersion()) {
|
||||||
|
case API_VERSION_V2:
|
||||||
|
return parseRefundNotifyV2(body);
|
||||||
|
case WxPayClientConfig.API_VERSION_V3:
|
||||||
|
return parseRefundNotifyV3(body);
|
||||||
|
default:
|
||||||
|
throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
|
||||||
|
}
|
||||||
|
} catch (WxPayException e) {
|
||||||
|
log.error("[parseNotify][params({}) body({}) 解析失败]", params, body, e);
|
||||||
|
// throw buildPayException(e);
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
// TODO 芋艿:缺一个异常翻译
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private PayRefundRespDTO parseRefundNotifyV2(String body) throws WxPayException {
|
||||||
|
// 1. 解析回调
|
||||||
|
WxPayRefundNotifyResult response = client.parseRefundNotifyResult(body);
|
||||||
|
WxPayRefundNotifyResult.ReqInfo reqInfo = response.getReqInfo();
|
||||||
|
// 2. 构建结果
|
||||||
|
PayRefundRespDTO notify = new PayRefundRespDTO()
|
||||||
|
.setChannelRefundNo(reqInfo.getRefundId())
|
||||||
|
.setOutRefundNo(reqInfo.getOutRefundNo())
|
||||||
|
.setRawData(response);
|
||||||
|
if (Objects.equals("SUCCESS", reqInfo.getRefundStatus())) {
|
||||||
|
notify.setStatus(PayRefundStatusRespEnum.SUCCESS.getStatus())
|
||||||
|
.setSuccessTime(parseDateV2B(reqInfo.getSuccessTime()));
|
||||||
|
} else {
|
||||||
|
notify.setStatus(PayRefundStatusRespEnum.FAILURE.getStatus());
|
||||||
|
}
|
||||||
|
return notify;
|
||||||
|
}
|
||||||
|
|
||||||
|
private PayRefundRespDTO parseRefundNotifyV3(String body) throws WxPayException {
|
||||||
|
// TODO 芋艿:未实现
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
// ========== 各种工具方法 ==========
|
// ========== 各种工具方法 ==========
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -246,6 +289,10 @@ public abstract class AbstractWxPayClient extends AbstractPayClient<WxPayClientC
|
|||||||
return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN);
|
return LocalDateTimeUtil.parse(time, PURE_DATETIME_PATTERN);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static LocalDateTime parseDateV2B(String time) {
|
||||||
|
return LocalDateTimeUtil.parse(time, NORM_DATETIME_PATTERN);
|
||||||
|
}
|
||||||
|
|
||||||
static String formatDateV3(LocalDateTime time) {
|
static String formatDateV3(LocalDateTime time) {
|
||||||
return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), UTC_WITH_XXX_OFFSET_PATTERN);
|
return TemporalAccessorUtil.format(time.atZone(ZoneId.systemDefault()), UTC_WITH_XXX_OFFSET_PATTERN);
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,7 @@ package cn.iocoder.yudao.framework.pay.core.client.impl.weixin;
|
|||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
import cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils;
|
||||||
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
|
||||||
|
import com.github.binarywang.wxpay.bean.notify.WxPayRefundNotifyResult;
|
||||||
import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest;
|
import com.github.binarywang.wxpay.bean.request.WxPayMicropayRequest;
|
||||||
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
|
import com.github.binarywang.wxpay.bean.request.WxPayRefundRequest;
|
||||||
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
|
import com.github.binarywang.wxpay.bean.request.WxPayRefundV3Request;
|
||||||
@ -9,7 +10,6 @@ import com.github.binarywang.wxpay.bean.result.WxPayMicropayResult;
|
|||||||
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
|
import com.github.binarywang.wxpay.bean.result.WxPayRefundResult;
|
||||||
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
|
import com.github.binarywang.wxpay.bean.result.WxPayRefundV3Result;
|
||||||
import com.github.binarywang.wxpay.config.WxPayConfig;
|
import com.github.binarywang.wxpay.config.WxPayConfig;
|
||||||
import com.github.binarywang.wxpay.constant.WxPayConstants;
|
|
||||||
import com.github.binarywang.wxpay.exception.WxPayException;
|
import com.github.binarywang.wxpay.exception.WxPayException;
|
||||||
import com.github.binarywang.wxpay.service.WxPayService;
|
import com.github.binarywang.wxpay.service.WxPayService;
|
||||||
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
import com.github.binarywang.wxpay.service.impl.WxPayServiceImpl;
|
||||||
@ -53,6 +53,20 @@ public class WxBarPayClientIntegrationTest {
|
|||||||
System.out.println(JsonUtils.toJsonPrettyString(response));
|
System.out.println(JsonUtils.toJsonPrettyString(response));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void testParseRefundNotifyV2() throws WxPayException {
|
||||||
|
// 创建 config 配置
|
||||||
|
WxPayConfig config = buildWxPayConfigV2();
|
||||||
|
// 创建 WxPayService 客户端
|
||||||
|
WxPayService client = new WxPayServiceImpl();
|
||||||
|
client.setConfig(config);
|
||||||
|
|
||||||
|
// 执行解析
|
||||||
|
String xml = "<xml><return_code>SUCCESS</return_code><appid><![CDATA[wx62056c0d5e8db250]]></appid><mch_id><![CDATA[1545083881]]></mch_id><nonce_str><![CDATA[ed8f02c21d15635cede114a42d0525a0]]></nonce_str><req_info><![CDATA[bGp+wB9DAHjoOO9Nw1iSmmIFdN2zZDhsoRWZBYdf/8bcpjowr4T8i2qjLsbMtvKQeVC5kBZOL/Agal3be6UPwnoantil+L+ojZgvLch7dXFKs/AcoxIYcVYyGka+wmnRJfUmuFRBgzt++8HOFsmJz6e2brYv1EAz+93fP2AsJtRuw1FEzodcg8eXm52hbE0KhLNqC2OyNVkn8AbOOrwIxSYobg2jVbuJ4JllYbEGIQ/6kWzNbVmMKhGJGYBy/NbUGKoQsoe4QeTQqcqQqVp08muxaOfJGThaN3B9EEMFSrog/3yT7ykVV6WQ5+Ygt89LplOf5ucWa4Ird7VJhHWtzI92ZePj4Omy1XkT1TRlwtDegA0S5MeQpM4WZ1taMrhxgmNkTUJ0JXFncx5e2KLQvbvD/HOcccx48Xv1c16JBz6G3501k8E++LWXgZ2TeNXwGsk6FyRZb0ApLyQHIx5ZtPo/UET9z3AmJCPXkrUsZ4WK46fDtbzxVPU2r8nTOcGCPbO0LUsGT6wpsuQVC4CisXDJwoZmL6kKwHfKs6mmUL2YZYzNfgoB/KgpJYSpC96kcpQyFvw+xuwqK2SXGZbAl9lADT+a83z04feQHSSIG3PCrX4QEWzpCZZ4+ySEz1Y34aoU20X9GtX+1LSwUjmQgwHrMBSvFm3/B7+IFM8OUqDB+Uvkr9Uvy7P2/KDvfy3Ih7GFcGd0C5NXpSvVTTfu1IlK/T3/t6MR/8iq78pp/2ZTYvO6eNDRJWaXYU+x6sl2dTs9n+2Z4W4AfYTvEyuxlx+aI19SqCJh7WmaFcAxidFl/9iqDjWiplb9+C6ijZv2hJtVjSCuoptIWpGDYItH7RAqlKHrx6flJD+M/5BceMHBv2w4OWCD9vPRLo8gl9o06ip0iflzO1dixhOAgLFjsQmQHNGFtR3EvCID+iS4FUlilwK+hcKNxrr0wp9Btkl9W1R9aTo289CUiIxx45skfCYzHwb+7Hqj3uTiXnep6zhCKZBAnPsDOvISXfBgXKufcFsTNtts09jX8H5/uMc9wyJ179H1cp+At1mIK2duwfo4Q9asfEoffl6Zn1olGdtEruxHGeVU0NwJ8V7RflC/Cx5RXtJ3sPJ/sHmVnBlVyR0=]]></req_info></xml>";
|
||||||
|
WxPayRefundNotifyResult response = client.parseRefundNotifyResult(xml);
|
||||||
|
System.out.println(response.getReqInfo());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testRefundV2() throws WxPayException {
|
public void testRefundV2() throws WxPayException {
|
||||||
// 创建 config 配置
|
// 创建 config 配置
|
||||||
@ -101,7 +115,7 @@ public class WxBarPayClientIntegrationTest {
|
|||||||
config.setAppId("wx62056c0d5e8db250");
|
config.setAppId("wx62056c0d5e8db250");
|
||||||
config.setMchId("1545083881");
|
config.setMchId("1545083881");
|
||||||
config.setMchKey("dS1ngeN63JLr3NRbvPH9AJy3MyUxZdim");
|
config.setMchKey("dS1ngeN63JLr3NRbvPH9AJy3MyUxZdim");
|
||||||
config.setSignType(WxPayConstants.SignType.MD5);
|
// config.setSignType(WxPayConstants.SignType.MD5);
|
||||||
config.setKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.p12");
|
config.setKeyPath("/Users/yunai/Downloads/wx_pay/apiclient_cert.p12");
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
@ -3,8 +3,8 @@ package cn.iocoder.yudao.module.pay.controller.admin.notify;
|
|||||||
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
import cn.iocoder.yudao.framework.pay.core.client.PayClient;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
|
||||||
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderRespDTO;
|
||||||
|
import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundRespDTO;
|
||||||
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
|
||||||
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
@ -18,7 +18,6 @@ import javax.annotation.security.PermitAll;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
|
|
||||||
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND;
|
import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND;
|
||||||
|
|
||||||
@Tag(name = "管理后台 - 支付通知")
|
@Tag(name = "管理后台 - 支付通知")
|
||||||
@ -36,22 +35,14 @@ public class PayNotifyController {
|
|||||||
@Resource
|
@Resource
|
||||||
private PayClientFactory payClientFactory;
|
private PayClientFactory payClientFactory;
|
||||||
|
|
||||||
/**
|
@PostMapping(value = "/order/{channelId}")
|
||||||
* 统一的渠道支付回调,支付宝的退款回调
|
@Operation(summary = "支付渠道的统一【支付】回调")
|
||||||
*
|
|
||||||
* @param channelId 渠道编号
|
|
||||||
* @param params form 参数
|
|
||||||
* @param body request body
|
|
||||||
* @return 成功返回 "success"
|
|
||||||
*/
|
|
||||||
@PostMapping(value = "/callback/{channelId}")
|
|
||||||
@Operation(summary = "支付渠道的统一回调接口 - 包括支付回调,退款回调")
|
|
||||||
@PermitAll
|
@PermitAll
|
||||||
@OperateLog(enable = false) // 回调地址,无需记录操作日志
|
@OperateLog(enable = false) // 回调地址,无需记录操作日志
|
||||||
public String notifyCallback(@PathVariable("channelId") Long channelId,
|
public String notifyOrder(@PathVariable("channelId") Long channelId,
|
||||||
@RequestParam(required = false) Map<String, String> params,
|
@RequestParam(required = false) Map<String, String> params,
|
||||||
@RequestBody(required = false) String body) {
|
@RequestBody(required = false) String body) {
|
||||||
log.info("[notifyCallback][channelId({}) 回调数据({}/{})]", channelId, params, body);
|
log.info("[notifyOrder][channelId({}) 回调数据({}/{})]", channelId, params, body);
|
||||||
// 1. 校验支付渠道是否存在
|
// 1. 校验支付渠道是否存在
|
||||||
PayClient payClient = payClientFactory.getPayClient(channelId);
|
PayClient payClient = payClientFactory.getPayClient(channelId);
|
||||||
if (payClient == null) {
|
if (payClient == null) {
|
||||||
@ -60,20 +51,30 @@ public class PayNotifyController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 2. 解析通知数据
|
// 2. 解析通知数据
|
||||||
Object notify = payClient.parseNotify(params, body);
|
PayOrderRespDTO notify = payClient.parseOrderNotify(params, body);
|
||||||
|
orderService.notifyOrder(channelId, notify);
|
||||||
|
return "success";
|
||||||
|
}
|
||||||
|
|
||||||
// 3. 处理通知
|
@PostMapping(value = "/refund/{channelId}")
|
||||||
// 3.1:退款通知
|
@Operation(summary = "支付渠道的统一【退款】回调")
|
||||||
if (notify instanceof PayRefundRespDTO) {
|
@PermitAll
|
||||||
refundService.notifyRefund(channelId, (PayRefundRespDTO) notify);
|
@OperateLog(enable = false) // 回调地址,无需记录操作日志
|
||||||
return "success";
|
public String notifyRefund(@PathVariable("channelId") Long channelId,
|
||||||
|
@RequestParam(required = false) Map<String, String> params,
|
||||||
|
@RequestBody(required = false) String body) {
|
||||||
|
log.info("[notifyRefund][channelId({}) 回调数据({}/{})]", channelId, params, body);
|
||||||
|
// 1. 校验支付渠道是否存在
|
||||||
|
PayClient payClient = payClientFactory.getPayClient(channelId);
|
||||||
|
if (payClient == null) {
|
||||||
|
log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
|
||||||
|
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
|
||||||
}
|
}
|
||||||
// 3.2:支付通知
|
|
||||||
if (notify instanceof PayOrderRespDTO) {
|
// 2. 解析通知数据
|
||||||
orderService.notifyOrder(channelId, (PayOrderRespDTO) notify);
|
PayRefundRespDTO notify = payClient.parseRefundNotify(params, body);
|
||||||
return "success";
|
refundService.notifyRefund(channelId, notify);
|
||||||
}
|
return "success";
|
||||||
throw new UnsupportedOperationException("未知通知:" + toJsonString(notify));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -48,7 +48,7 @@ public class PayAppDO extends BaseDO {
|
|||||||
/**
|
/**
|
||||||
* 支付结果的回调地址
|
* 支付结果的回调地址
|
||||||
*/
|
*/
|
||||||
private String payNotifyUrl;
|
private String orderNotifyUrl;
|
||||||
/**
|
/**
|
||||||
* 退款结果的回调地址
|
* 退款结果的回调地址
|
||||||
*/
|
*/
|
||||||
|
@ -20,11 +20,11 @@ import javax.annotation.Resource;
|
|||||||
public class PayNotifyJob implements JobHandler {
|
public class PayNotifyJob implements JobHandler {
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private PayNotifyService payNotifyCoreService;
|
private PayNotifyService payNotifyService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String execute(String param) throws Exception {
|
public String execute(String param) throws Exception {
|
||||||
int notifyCount = payNotifyCoreService.executeNotify();
|
int notifyCount = payNotifyService.executeNotify();
|
||||||
return String.format("执行支付通知 %s 个", notifyCount);
|
return String.format("执行支付通知 %s 个", notifyCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
// 创建支付交易单
|
// 创建支付交易单
|
||||||
order = PayOrderConvert.INSTANCE.convert(reqDTO).setAppId(app.getId())
|
order = PayOrderConvert.INSTANCE.convert(reqDTO).setAppId(app.getId())
|
||||||
// 商户相关字段
|
// 商户相关字段
|
||||||
.setNotifyUrl(app.getPayNotifyUrl()).setNotifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
|
.setNotifyUrl(app.getOrderNotifyUrl()).setNotifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
|
||||||
// 订单相关字段
|
// 订单相关字段
|
||||||
.setStatus(PayOrderStatusEnum.WAITING.getStatus())
|
.setStatus(PayOrderStatusEnum.WAITING.getStatus())
|
||||||
// 退款相关字段
|
// 退款相关字段
|
||||||
@ -206,7 +206,7 @@ public class PayOrderServiceImpl implements PayOrderService {
|
|||||||
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
|
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
|
||||||
*/
|
*/
|
||||||
private String genChannelPayNotifyUrl(PayChannelDO channel) {
|
private String genChannelPayNotifyUrl(PayChannelDO channel) {
|
||||||
return payProperties.getCallbackUrl() + "/" + channel.getId();
|
return payProperties.getOrderNotifyUrl() + "/" + channel.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateOrderExtensionNo() {
|
private String generateOrderExtensionNo() {
|
||||||
|
@ -134,7 +134,7 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||||||
.setRefundPrice(reqDTO.getPrice())
|
.setRefundPrice(reqDTO.getPrice())
|
||||||
.setOutTradeNo(orderExtension.getNo())
|
.setOutTradeNo(orderExtension.getNo())
|
||||||
.setOutRefundNo(refund.getNo())
|
.setOutRefundNo(refund.getNo())
|
||||||
.setNotifyUrl(genChannelPayNotifyUrl(channel)) // TODO 芋艿:优化下 notifyUrl
|
.setNotifyUrl(genChannelPayNotifyUrl(channel))
|
||||||
.setReason(reqDTO.getReason());
|
.setReason(reqDTO.getReason());
|
||||||
PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO); // TODO 增加一个 channelErrorCode、channelErrorMsg 字段
|
PayRefundRespDTO refundRespDTO = client.unifiedRefund(unifiedReqDTO); // TODO 增加一个 channelErrorCode、channelErrorMsg 字段
|
||||||
// 2.3 处理退款返回
|
// 2.3 处理退款返回
|
||||||
@ -183,7 +183,7 @@ public class PayRefundServiceImpl implements PayRefundService {
|
|||||||
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
|
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
|
||||||
*/
|
*/
|
||||||
private String genChannelPayNotifyUrl(PayChannelDO channel) {
|
private String genChannelPayNotifyUrl(PayChannelDO channel) {
|
||||||
return payProperties.getCallbackUrl() + "/" + channel.getId();
|
return payProperties.getRefundNotifyUrl() + "/" + channel.getId();
|
||||||
}
|
}
|
||||||
|
|
||||||
private String generateRefundNo() {
|
private String generateRefundNo() {
|
||||||
|
@ -194,7 +194,8 @@ yudao:
|
|||||||
- ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
|
- ${spring.boot.admin.context-path}/** # 不处理 Spring Boot Admin 的请求
|
||||||
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
|
- ${management.endpoints.web.base-path}/** # 不处理 Actuator 的请求
|
||||||
pay:
|
pay:
|
||||||
callback-url: http://yunai.natapp1.cc/admin-api/pay/notify/callback
|
order-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/order # 支付渠道的【支付】回调地址
|
||||||
|
refund-notify-url: http://yunai.natapp1.cc/admin-api/pay/notify/refund # 支付渠道的【退款】回调地址
|
||||||
access-log: # 访问日志的配置项
|
access-log: # 访问日志的配置项
|
||||||
enable: false
|
enable: false
|
||||||
error-code: # 错误码相关配置项
|
error-code: # 错误码相关配置项
|
||||||
|
@ -142,7 +142,7 @@ yudao:
|
|||||||
- /admin-api/system/captcha/check # 校验图片验证码,和租户无关
|
- /admin-api/system/captcha/check # 校验图片验证码,和租户无关
|
||||||
- /admin-api/infra/file/*/get/** # 获取图片,和租户无关
|
- /admin-api/infra/file/*/get/** # 获取图片,和租户无关
|
||||||
- /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
|
- /admin-api/system/sms/callback/* # 短信回调接口,无法带上租户编号
|
||||||
- /admin-api/pay/notify/callback/* # 支付回调通知,不携带租户编号
|
- /admin-api/pay/notify/** # 支付回调通知,不携带租户编号
|
||||||
- /jmreport/* # 积木报表,无法携带租户编号
|
- /jmreport/* # 积木报表,无法携带租户编号
|
||||||
- /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,无法携带租户编号
|
- /admin-api/mp/open/** # 微信公众号开放平台,微信回调接口,无法携带租户编号
|
||||||
ignore-tables:
|
ignore-tables:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<template>
|
<template>
|
||||||
<div>
|
<div>
|
||||||
<el-dialog :visible.sync="transferParam.wechatOpen" :title="title" @close="close" append-to-body width="800px">
|
<el-dialog :visible.sync="transferParam.wechatOpen" :title="title" @close="close" append-to-body width="800px">
|
||||||
<el-form ref="wechatJsApiForm" :model="form" :rules="rules" size="medium" label-width="100px"
|
<el-form ref="wechatJsApiForm" :model="form" :rules="rules" size="medium" label-width="120px"
|
||||||
v-loading="transferParam.loading">
|
v-loading="transferParam.loading">
|
||||||
<el-form-item label-width="180px" label="渠道费率" prop="feeRate">
|
<el-form-item label-width="180px" label="渠道费率" prop="feeRate">
|
||||||
<el-input v-model="form.feeRate" placeholder="请输入渠道费率" clearable :style="{width: '100%'}">
|
<el-input v-model="form.feeRate" placeholder="请输入渠道费率" clearable :style="{width: '100%'}">
|
||||||
|
@ -331,6 +331,9 @@ export default {
|
|||||||
* ③ close:支付已关闭
|
* ③ close:支付已关闭
|
||||||
*/
|
*/
|
||||||
goReturnUrl(payResult) {
|
goReturnUrl(payResult) {
|
||||||
|
// 清理任务
|
||||||
|
this.clearQueryInterval();
|
||||||
|
|
||||||
// 未配置的情况下,只能关闭
|
// 未配置的情况下,只能关闭
|
||||||
if (!this.returnUrl) {
|
if (!this.returnUrl) {
|
||||||
this.$tab.closePage();
|
this.$tab.closePage();
|
||||||
|
Loading…
Reference in New Issue
Block a user