调整渠道支付通知地址为统一的地址

This commit is contained in:
jason 2021-11-24 23:51:48 +08:00
parent 1c5544fc9d
commit f108d478a8
14 changed files with 332 additions and 232 deletions

View File

@ -24,9 +24,9 @@ public interface PayErrorCodeCoreConstants {
ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在");
ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相同的渠道");
ErrorCode CHANNEL_WECHAT_VERSION_2_MCH_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v2版本中商户密钥不可为空");
ErrorCode CHANNEL_WECHAT_VERSION_3_PRIVATE_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v3版本apiclient_key.pem不可为空");
ErrorCode CHANNEL_WECHAT_VERSION_3_CERT_KEY_IS_NULL = new ErrorCode(1007001006,"微信渠道v3版本中apiclient_cert.pem不可为空");
ErrorCode CHANNEL_WECHAT_VERSION_3_PRIVATE_KEY_IS_NULL = new ErrorCode(1007001007,"微信渠道v3版本apiclient_key.pem不可为空");
ErrorCode CHANNEL_WECHAT_VERSION_3_CERT_KEY_IS_NULL = new ErrorCode(1007001008,"微信渠道v3版本中apiclient_cert.pem不可为空");
ErrorCode PAY_CHANNEL_NOTIFY_VERIFY_FAILED = new ErrorCode(1007001009, "渠道通知校验失败");
/**
* ========== ORDER 模块 1-007-002-000 ==========
*/

View File

@ -0,0 +1,25 @@
package cn.iocoder.yudao.coreservice.modules.pay.service.order;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
/**
* 支付通用 Core Service
*
* @author jason
*/
public interface PayCommonCoreService {
/**
* 验证是否是渠道通知
* @param notifyData 通知数据
*/
void verifyNotifyData(Long channelId, PayNotifyDataDTO notifyData);
/**
* 支付宝的支付回调通知和退款回调通知 地址是同一个
* 是否是退款回调通知
* @param notifyData 通知数据
* @return
*/
boolean isRefundNotify(Long channelId, PayNotifyDataDTO notifyData);
}

View File

@ -43,10 +43,9 @@ public interface PayOrderCoreService {
* 通知支付单成功
*
* @param channelId 渠道编号
* @param channelCode 渠道编码
* @param notifyData 通知数据
*/
void notifyPayOrder(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception;
void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception;

View File

@ -23,11 +23,10 @@ public interface PayRefundCoreService {
/**
* 渠道的退款通知
* @param channelId 渠道编号
* @param channelCode 渠道编码
* @param notifyData 通知数据
* @throws Exception 退款通知异常
*/
void notifyPayRefund(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception;
void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) throws Exception;

View File

@ -0,0 +1,62 @@
package cn.iocoder.yudao.coreservice.modules.pay.service.order.impl;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.merchant.PayChannelDO;
import cn.iocoder.yudao.coreservice.modules.pay.service.merchant.PayChannelCoreService;
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayCommonCoreService;
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.dto.PayNotifyDataDTO;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_CHANNEL_CLIENT_NOT_FOUND;
import static cn.iocoder.yudao.coreservice.modules.pay.enums.PayErrorCodeCoreConstants.PAY_CHANNEL_NOTIFY_VERIFY_FAILED;
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
/**
* 支付通用 Core Service 实现类
*
* @author jason
*/
@Service
@Slf4j
public class PayCommonCoreServiceImpl implements PayCommonCoreService {
@Resource
private PayChannelCoreService payChannelCoreService;
@Resource
private PayClientFactory payClientFactory;
@Override
public void verifyNotifyData(Long channelId, PayNotifyDataDTO notifyData) {
// 校验支付渠道是否有效
PayChannelDO channel = payChannelCoreService.validPayChannel(channelId);
// 校验支付客户端是否正确初始化
PayClient client = payClientFactory.getPayClient(channel.getId());
if (client == null) {
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
}
boolean verifyResult = client.verifyNotifyData(notifyData);
if(!verifyResult){
//渠道通知验证失败
throw exception(PAY_CHANNEL_NOTIFY_VERIFY_FAILED);
}
}
@Override
public boolean isRefundNotify(Long channelId, PayNotifyDataDTO notifyData) {
// 校验支付渠道是否有效
PayChannelDO channel = payChannelCoreService.validPayChannel(channelId);
// 校验支付客户端是否正确初始化
PayClient client = payClientFactory.getPayClient(channel.getId());
if (client == null) {
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
}
return client.isRefundNotify(notifyData);
}
}

View File

@ -155,24 +155,22 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
/**
* 根据支付渠道的编码生成支付渠道的返回地址
* @param channel
* @return
* @param channel 支付渠道
* @return 支付成功返回的地址 配置地址 + "/" + channel id
*/
private String genChannelReturnUrl(PayChannelDO channel) {
return payProperties.getPayReturnUrl() + "/" + StrUtil.replace(channel.getCode(), "_", "-")
+ "/" + channel.getId();
return payProperties.getPayReturnUrl() + "/" + channel.getId();
}
/**
* 根据支付渠道的编码生成支付渠道的回调地址
*
* @param channel 支付渠道
* @return 支付渠道的回调地址
* @return 支付渠道的回调地址 配置地址 + "/" + channel id
*/
private String genChannelPayNotifyUrl(PayChannelDO channel) {
// _ 转化为 - 的原因是因为 URL 我们统一采用中划线的原则
return payProperties.getPayNotifyUrl() + "/" + StrUtil.replace(channel.getCode(), "_", "-")
+ "/" + channel.getId();
//去掉channel code, 似乎没啥用 用统一的回调地址
return payProperties.getPayNotifyUrl() + "/" + channel.getId();
}
private String generateOrderExtensionNo() {
@ -195,7 +193,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
@Override
@Transactional
public void notifyPayOrder(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception {
public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) throws Exception {
// TODO 芋艿记录回调日志
log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
@ -207,7 +205,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
}
//TODO @jason 校验 是否支付宝调用 使用 支付宝publickey 或者payclient 加一个校验方法
// 解析支付结果
PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData);
@ -222,7 +220,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
}
// 1.2 更新 PayOrderExtensionDO
//TODO @jason notifyRespDTO.getTradeStatus() 需要根据不同的状态更新成不同的值 PayOrderStatusEnum
//TODO 支付宝交易超时 TRADE_FINISHED 需要更新交易关闭
int updateCounts = payOrderExtensionCoreMapper.updateByIdAndStatus(orderExtension.getId(),
PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
.status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(notifyData.getBody()).build());
@ -241,7 +239,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService {
}
// 2.2 更新 PayOrderDO
updateCounts = payOrderCoreMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelId(channelId).channelCode(channelCode)
PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus()).channelId(channelId).channelCode(channel.getCode())
.successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId())
.channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId())
.notifyTime(new Date()).build());

View File

@ -40,7 +40,9 @@ public class PayRefundChannelQueryHandler extends PayRefundAbstractChannelPostHa
//更新退款单表
PayRefundDO updateRefundDO = new PayRefundDO();
updateRefundDO.setId(respBO.getRefundId())
.setStatus(refundStatus.getStatus());
.setStatus(refundStatus.getStatus())
.setChannelErrorCode(respBO.getChannelErrCode())
.setChannelErrorMsg(respBO.getChannelErrMsg());
updatePayRefund(updateRefundDO);
PayOrderDO updateOrderDO = new PayOrderDO();

View File

@ -182,7 +182,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService {
@Override
public void notifyPayRefund(Long channelId, String channelCode, PayNotifyDataDTO notifyData) {
public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) {
log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
// 校验支付渠道是否有效
PayChannelDO channel = payChannelCoreService.validPayChannel(channelId);

View File

@ -49,4 +49,22 @@ public interface PayClient {
*/
PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData);
/**
* 验证是否渠道通知
* @param notifyData 通知数据
* @return 默认是 true
*/
default boolean verifyNotifyData(PayNotifyDataDTO notifyData){
return true;
}
/**
* 是否退款通知
* @param notifyData 通知数据
* @return 默认是 false
*/
default boolean isRefundNotify(PayNotifyDataDTO notifyData){
return false;
}
}

View File

@ -0,0 +1,162 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.net.SocketTimeoutException;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/**
* 支付宝抽象类 实现支付宝统一的接口如退款
*
* @author jason
*/
@Slf4j
public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayClientConfig> {
protected DefaultAlipayClient client;
public AbstractAlipayClient(Long channelId, String channelCode,
AlipayPayClientConfig config, AbstractPayCodeMapping codeMapping) {
super(channelId, channelCode, config, codeMapping);
}
@Override
@SneakyThrows
protected void doInit() {
AlipayConfig alipayConfig = new AlipayConfig();
BeanUtil.copyProperties(config, alipayConfig, false);
this.client = new DefaultAlipayClient(alipayConfig);
}
/**
* 从支付宝通知返回参数中解析 PayOrderNotifyRespDTO, 通知具体参数参考
* //https://opendocs.alipay.com/open/203/105286
* @param data 通知结果
* @return 解析结果 PayOrderNotifyRespDTO
* @throws Exception 解析失败抛出异常
*/
@Override
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
Map<String, String> params = data.getParams();
return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no"))
.channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id"))
.tradeStatus(params.get("trade_status"))
.successTime(DateUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss"))
.data(data.getBody()).build();
}
@Override
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
Map<String, String> params = notifyData.getParams();
PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(params.get("trade_no"))
.tradeNo(params.get("out_trade_no"))
.reqNo(params.get("out_biz_no"))
.status(PayNotifyRefundStatusEnum.SUCCESS)
.refundSuccessTime(DateUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
.build();
return notifyDTO;
}
@Override
public boolean isRefundNotify(PayNotifyDataDTO notifyData) {
if (notifyData.getParams().containsKey("refund_fee")) {
return true;
} else {
return false;
}
}
@Override
public boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
boolean verifyResult = false;
try {
verifyResult = AlipaySignature.rsaCheckV1(notifyData.getParams(), config.getAlipayPublicKey(), StandardCharsets.UTF_8.name(), "RSA2");
} catch (AlipayApiException e) {
log.error("[AlipayClient verifyNotifyData][(notify param is :{}) 验证失败]", toJsonString(notifyData.getParams()), e);
}
return verifyResult;
}
/**
* 支付宝统一的退款接口 alipay.trade.refund
* @param reqDTO 退款请求 request DTO
* @return 退款请求 Response
*/
@Override
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
AlipayTradeRefundModel model=new AlipayTradeRefundModel();
model.setTradeNo(reqDTO.getChannelOrderNo());
model.setOutTradeNo(reqDTO.getPayTradeNo());
model.setOutRequestNo(reqDTO.getRefundReqNo());
model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString());
model.setRefundReason(reqDTO.getReason());
AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
refundRequest.setBizModel(model);
PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
try {
AlipayTradeRefundResponse response = client.execute(refundRequest);
log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
if (response.isSuccess()) {
//退款成功,更新为PROCESSING_NOTIFY 而不是 SYNC_SUCCESS 通过支付宝回调接口处理退款导致触发的异步通知
//退款导致触发的异步通知是发送到支付接口中设置的notify_url
//TODO 沙箱环境 返回 的tradeNo(渠道退款单号 订单的tradNo 是一个值是不是理解不对?
respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_NOTIFY);
}else{
//特殊处理 sub_code ACQ.SYSTEM_ERROR系统错误 需要调用重试任务
//沙箱环境返回的貌似是aop.ACQ.SYSTEM_ERROR 用contain
if (response.getSubCode().contains("ACQ.SYSTEM_ERROR")) {
respDTO.setRespEnum(PayChannelRespEnum.RETRY_FAILURE)
.setChannelErrMsg(response.getSubMsg())
.setChannelErrCode(response.getSubCode());
}else{
//交易已关闭需要查询确认退款是否已经完成
if("ACQ.TRADE_HAS_CLOSE".equals(response.getSubCode())){
respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_QUERY)
.setChannelErrMsg(response.getSubMsg())
.setChannelErrCode(response.getSubCode());
}else {
//其他当做不可以重试的错误
respDTO.setRespEnum(PayChannelRespEnum.CAN_NOT_RETRY_FAILURE)
.setChannelErrCode(response.getSubCode())
.setChannelErrMsg(response.getSubMsg());
}
}
}
return respDTO;
} catch (AlipayApiException e) {
//TODO 记录异常日志
log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
Throwable cause = e.getCause();
//网络 read time out 异常, 退款状态未知
if (cause instanceof SocketTimeoutException) {
respDTO.setExceptionMsg(e.getMessage())
.setRespEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION);
}else{
respDTO.setExceptionMsg(e.getMessage())
.setChannelErrCode(e.getErrCode())
.setChannelErrMsg(e.getErrMsg())
.setRespEnum(PayChannelRespEnum.CALL_EXCEPTION);
}
return respDTO;
}
}
}

View File

@ -1,22 +1,14 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradePrecreateModel;
import com.alipay.api.request.AlipayTradePrecreateRequest;
import com.alipay.api.response.AlipayTradePrecreateResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/**
@ -26,23 +18,12 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
* @author 芋道源码
*/
@Slf4j
public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> {
private DefaultAlipayClient client;
public class AlipayQrPayClient extends AbstractAlipayClient {
public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) {
super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config, new AlipayPayCodeMapping());
}
@Override
@SneakyThrows
protected void doInit() {
AlipayConfig alipayConfig = new AlipayConfig();
BeanUtil.copyProperties(config, alipayConfig, false);
// 真实客户端
this.client = new DefaultAlipayClient(alipayConfig);
}
@Override
public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
// 构建 AlipayTradePrecreateModel 请求
@ -56,7 +37,7 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig>
AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
request.setBizModel(model);
request.setNotifyUrl(reqDTO.getNotifyUrl());
request.setReturnUrl(reqDTO.getReturnUrl());
// 执行请求
AlipayTradePrecreateResponse response;
try {
@ -68,30 +49,4 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig>
// TODO 芋艿sub Code 需要测试下各种失败的情况
return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
}
@Override
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
//结果转换
Map<String, String> params = data.getParams();
return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no"))
.channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id"))
.tradeStatus(params.get("trade_status"))
.successTime(DateUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss"))
.data(data.getBody()).build();
}
@Override
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
//TODO 需要实现
throw new UnsupportedOperationException("需要实现");
}
@Override
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
//TODO 需要实现
throw new UnsupportedOperationException();
}
}

View File

@ -1,31 +1,17 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.date.DateUtil;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.*;
import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRespEnum;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayConfig;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeRefundModel;
import com.alipay.api.domain.AlipayTradeWapPayModel;
import com.alipay.api.request.AlipayTradeRefundRequest;
import com.alipay.api.request.AlipayTradeWapPayRequest;
import com.alipay.api.response.AlipayTradeRefundResponse;
import com.alipay.api.response.AlipayTradeWapPayResponse;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import java.net.SocketTimeoutException;
import java.util.Map;
import java.util.Objects;
import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
/**
* 支付宝手机网站 PayClient 实现类
* 文档https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
@ -33,22 +19,13 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
* @author 芋道源码
*/
@Slf4j
public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> {
public class AlipayWapPayClient extends AbstractAlipayClient {
private DefaultAlipayClient client;
public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) {
super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping());
}
@Override
@SneakyThrows
protected void doInit() {
AlipayConfig alipayConfig = new AlipayConfig();
BeanUtil.copyProperties(config, alipayConfig, false);
this.client = new DefaultAlipayClient(alipayConfig);
}
@Override
public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
// 构建 AlipayTradeWapPayModel 请求
@ -69,6 +46,7 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig>
request.setBizModel(model);
request.setNotifyUrl(reqDTO.getNotifyUrl());
request.setReturnUrl(reqDTO.getReturnUrl());
// 执行请求
AlipayTradeWapPayResponse response;
try {
@ -87,85 +65,11 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig>
}
/**
* 从支付宝通知返回参数中解析 PayOrderNotifyRespDTO, 通知具体参数参考
* //https://opendocs.alipay.com/open/203/105286
* @param data 通知结果
* @return 解析结果 PayOrderNotifyRespDTO
* @throws Exception 解析失败抛出异常
*/
@Override
public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception {
Map<String, String> params = data.getParams();
return PayOrderNotifyRespDTO.builder().orderExtensionNo(params.get("out_trade_no"))
.channelOrderNo(params.get("trade_no")).channelUserId(params.get("seller_id"))
.tradeStatus(params.get("trade_status"))
.successTime(DateUtil.parse(params.get("notify_time"), "yyyy-MM-dd HH:mm:ss"))
.data(data.getBody()).build();
}
@Override
protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
AlipayTradeRefundModel model=new AlipayTradeRefundModel();
model.setTradeNo(reqDTO.getChannelOrderNo());
model.setOutTradeNo(reqDTO.getPayTradeNo());
model.setOutRequestNo(reqDTO.getRefundReqNo());
model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString());
model.setRefundReason(reqDTO.getReason());
AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
refundRequest.setBizModel(model);
PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
try {
AlipayTradeRefundResponse response = client.execute(refundRequest);
log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
if (response.isSuccess()) {
//退款成功,更新为PROCESSING_NOTIFY 而不是 SYNC_SUCCESS 通过支付宝回调接口处理退款导致触发的异步通知
//退款导致触发的异步通知是发送到支付接口中设置的notify_url
//TODO 沙箱环境 返回 的tradeNo(渠道退款单号 订单的tradNo 是一个值是不是理解不对?
respDTO.setRespEnum(PayChannelRespEnum.PROCESSING_NOTIFY);
}else{
//特殊处理 sub_code ACQ.SYSTEM_ERROR系统错误 需要调用重试任务
//沙箱环境返回的貌似是aop.ACQ.SYSTEM_ERROR 用contain
if (response.getSubCode().contains("ACQ.SYSTEM_ERROR")) {
respDTO.setRespEnum(PayChannelRespEnum.RETRY_FAILURE)
.setChannelErrMsg(response.getSubMsg())
.setChannelErrCode(response.getSubCode());
}else{
//其他当做不可以重试的错误
respDTO.setRespEnum(PayChannelRespEnum.CAN_NOT_RETRY_FAILURE)
.setChannelErrCode(response.getSubCode())
.setChannelErrMsg(response.getSubMsg());
}
}
return respDTO;
} catch (AlipayApiException e) {
//TODO 记录异常日志
log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
Throwable cause = e.getCause();
//网络 read time out 异常, 退款状态未知
if (cause instanceof SocketTimeoutException) {
respDTO.setExceptionMsg(e.getMessage())
.setRespEnum(PayChannelRespEnum.READ_TIME_OUT_EXCEPTION);
}else{
respDTO.setExceptionMsg(e.getMessage())
.setChannelErrCode(e.getErrCode())
.setChannelErrMsg(e.getErrMsg())
.setRespEnum(PayChannelRespEnum.CALL_EXCEPTION);
}
return respDTO;
}
}
@Override
public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
Map<String, String> params = notifyData.getParams();
PayRefundNotifyDTO notifyDTO = PayRefundNotifyDTO.builder().channelOrderNo(params.get("trade_no"))
.tradeNo(params.get("out_trade_no"))
.reqNo(params.get("out_biz_no"))
.status(PayNotifyRefundStatusEnum.SUCCESS)
.refundSuccessTime(DateUtil.parse(params.get("gmt_refund"), "yyyy-MM-dd HH:mm:ss"))
.build();
return notifyDTO;
}
}

View File

@ -4,7 +4,8 @@ import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SuppressWarnings("SpringComponentScan") // 忽略 IDEA 无法识别 ${yudao.info.base-package} ${yudao.core-service.base-package}
@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}", "${yudao.core-service.base-package}"})public class UserServerApplication {
@SpringBootApplication(scanBasePackages = {"${yudao.info.base-package}", "${yudao.core-service.base-package}"})
public class UserServerApplication {
public static void main(String[] args) {
SpringApplication.run(UserServerApplication.class, args);

View File

@ -2,6 +2,7 @@ package cn.iocoder.yudao.userserver.modules.pay.controller.order;
import cn.hutool.core.bean.BeanUtil;
import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO;
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayCommonCoreService;
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayOrderCoreService;
import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService;
import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO;
@ -38,6 +39,9 @@ public class PayOrderController {
@Resource
private PayRefundCoreService payRefundCoreService;
@Resource PayCommonCoreService commonCoreService;
@PostMapping("/submit")
@ApiOperation("提交支付订单")
// @PreAuthenticated // TODO 暂时不加登陆验证前端暂时没做好
@ -57,82 +61,53 @@ public class PayOrderController {
}
// ========== 支付渠道的回调 ==========
//TODO 芋道源码 换成了统一的地址了 /notify/{channelId}测试通过可以删除
@PostMapping("/notify/wx-pub/{channelId}")
@ApiOperation("通知微信公众号支付的结果")
public String notifyWxPayOrder(@PathVariable("channelId") Long channelId,
@RequestBody String xmlData) throws Exception {
payOrderCoreService.notifyPayOrder(channelId, PayChannelEnum.WX_PUB.getCode(), PayNotifyDataDTO.builder().body(xmlData).build());
payOrderCoreService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().body(xmlData).build());
return "success";
}
@PostMapping("/notify/alipay-qr/{channelId}")
@ApiOperation("通知支付宝扫码支付的结果")
public String notifyAlipayQrPayOrder(@PathVariable("channelId") Long channelId,
@RequestParam Map<String, String> params,
@RequestBody String originData) throws Exception{
payOrderCoreService.notifyPayOrder(channelId, PayChannelEnum.ALIPAY_QR.getCode(),
PayNotifyDataDTO.builder().params(params).body(originData).build());
return "success";
}
@GetMapping(value = "/return/alipay-qr/{channelId}")
@ApiOperation("支付宝 wap 页面回跳")
public String returnAliPayQrPayOrder(@PathVariable("channelId") Long channelId){
//TODO @jason 校验 是否支付宝调用 支付宝publickey 可以根据 appId 跳转不同的页面
System.out.println("支付成功");
return "支付成功";
}
@PostMapping(value = "/notify/alipay-wap/{channelId}", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
@ApiOperation("支付宝 wap 页面回调")
public String notifyAliPayWapPayOrder(@PathVariable("channelId") Long channelId,
@RequestParam Map<String, String> params,
@RequestBody String originData) throws Exception {
//TODO 校验是否支付宝调用 payclient 中加一个校验方法
//支付宝退款交易也会触发支付回调接口
//参考 https://opensupport.alipay.com/support/helpcenter/193/201602484851
//判断是否为支付宝的退款交易
if(isAliPayRefund(params)) {
//退款通知
payRefundCoreService.notifyPayRefund(channelId,PayChannelEnum.ALIPAY_WAP.getCode(), PayNotifyDataDTO.builder().params(params).body(originData).build());
}else{
//支付通知
payOrderCoreService.notifyPayOrder(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), PayNotifyDataDTO.builder().params(params).body(originData).build());
}
return "success";
}
/**
* 统一的跳转页面 支付宝跳转参数说明
* https://opendocs.alipay.com/open/203/105285#%E5%89%8D%E5%8F%B0%E5%9B%9E%E8%B7%B3%E5%8F%82%E6%95%B0%E8%AF%B4%E6%98%8E
* @param channelId 渠道id
* @return 返回跳转页面
*/
@GetMapping(value = "/return/alipay-wap/{channelId}")
@ApiOperation("支付宝 wap 页面回跳")
public String returnAliPayWapPayOrder(@PathVariable("channelId") Long channelId){
//TODO 校验 是否支付宝调用 可以根据 appId 跳转不同的页面
return "支付成功";
@GetMapping(value = "/return/{channelId}")
@ApiOperation("渠道统一的支付成功返回地址")
public String returnAliPayOrder(@PathVariable("channelId") Long channelId, @RequestParam Map<String, String> params){
//TODO 可以根据渠道和 app_id 返回不同的页面
log.info("app_id is {}", params.get("app_id"));
return String.format("渠道[%s]支付成功", String.valueOf(channelId));
}
/**
* 是否是支付宝的退款交易
* @param params http content-type application/x-www-form-urlencoded 的参数
* @return
* 统一的渠道支付回调支付宝的退款回调
* @param channelId 渠道编号
* @param params form 参数
* @param originData http request body
* @return 成功返回 "success"
*/
private boolean isAliPayRefund(Map<String, String> params) {
if (params.containsKey("refund_fee")) {
return true;
} else {
return false;
@PostMapping(value = "/notify/{channelId}")
@ApiOperation("渠道统一的支付成功,或退款成功 通知url")
public String notifyChannelPay(@PathVariable("channelId") Long channelId,
@RequestParam Map<String, String> params,
@RequestBody String originData) throws Exception {
//校验是否是渠道回调
commonCoreService.verifyNotifyData(channelId, PayNotifyDataDTO.builder().params(params).body(originData).build());
//支付宝退款交易也会触发支付回调接口
//参考 https://opensupport.alipay.com/support/helpcenter/193/201602484851
//判断是否为退款通知
if(commonCoreService.isRefundNotify(channelId, PayNotifyDataDTO.builder().params(params).body(originData).build())) {
//退款通知
payRefundCoreService.notifyPayRefund(channelId,PayNotifyDataDTO.builder().params(params).body(originData).build());
}else{
//支付通知
payOrderCoreService.notifyPayOrder(channelId,PayNotifyDataDTO.builder().params(params).body(originData).build());
}
}
@RequestMapping("/notify/test")
@ApiOperation("通知的测试接口")
public String notifyTest() {
// System.out.println(data);
return "success";
}