mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-10-31 10:18:42 +08:00 
			
		
		
		
	调整渠道支付通知地址为统一的地址
This commit is contained in:
		| @@ -24,9 +24,9 @@ public interface PayErrorCodeCoreConstants { | |||||||
|     ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在"); |     ErrorCode CHANNEL_NOT_EXISTS = new ErrorCode(1007001003, "支付渠道不存在"); | ||||||
|     ErrorCode CHANNEL_EXIST_SAME_CHANNEL_ERROR = new ErrorCode(1007001005, "已存在相同的渠道"); |     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_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_PRIVATE_KEY_IS_NULL = new ErrorCode(1007001007,"微信渠道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_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 ========== |      * ========== ORDER 模块 1-007-002-000 ========== | ||||||
|      */ |      */ | ||||||
|   | |||||||
| @@ -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); | ||||||
|  | } | ||||||
| @@ -43,10 +43,9 @@ public interface PayOrderCoreService { | |||||||
|     * 通知支付单成功 |     * 通知支付单成功 | ||||||
|     * |     * | ||||||
|     * @param channelId 渠道编号 |     * @param channelId 渠道编号 | ||||||
|     * @param channelCode 渠道编码 |  | ||||||
|     * @param notifyData 通知数据 |     * @param notifyData 通知数据 | ||||||
|     */ |     */ | ||||||
|    void notifyPayOrder(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception; |    void notifyPayOrder(Long channelId,  PayNotifyDataDTO notifyData) throws Exception; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -23,11 +23,10 @@ public interface PayRefundCoreService { | |||||||
|     /** |     /** | ||||||
|      * 渠道的退款通知 |      * 渠道的退款通知 | ||||||
|      * @param channelId  渠道编号 |      * @param channelId  渠道编号 | ||||||
|      * @param channelCode 渠道编码 |  | ||||||
|      * @param notifyData  通知数据 |      * @param notifyData  通知数据 | ||||||
|      * @throws Exception 退款通知异常 |      * @throws Exception 退款通知异常 | ||||||
|      */ |      */ | ||||||
|     void notifyPayRefund(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception; |     void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) throws Exception; | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -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); | ||||||
|  |     } | ||||||
|  | } | ||||||
| @@ -155,24 +155,22 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | |||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 根据支付渠道的编码,生成支付渠道的返回地址 |      * 根据支付渠道的编码,生成支付渠道的返回地址 | ||||||
|      * @param channel |      * @param channel 支付渠道 | ||||||
|      * @return |      * @return 支付成功返回的地址。 配置地址 + "/" + channel id | ||||||
|      */ |      */ | ||||||
|     private String genChannelReturnUrl(PayChannelDO channel) { |     private String genChannelReturnUrl(PayChannelDO channel) { | ||||||
|         return payProperties.getPayReturnUrl() + "/" + StrUtil.replace(channel.getCode(), "_", "-") |         return payProperties.getPayReturnUrl() + "/" + channel.getId(); | ||||||
|                 + "/" + channel.getId(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /** |     /** | ||||||
|      * 根据支付渠道的编码,生成支付渠道的回调地址 |      * 根据支付渠道的编码,生成支付渠道的回调地址 | ||||||
|      * |      * | ||||||
|      * @param channel 支付渠道 |      * @param channel 支付渠道 | ||||||
|      * @return 支付渠道的回调地址 |      * @return 支付渠道的回调地址  配置地址 + "/" + channel id | ||||||
|      */ |      */ | ||||||
|     private String genChannelPayNotifyUrl(PayChannelDO channel) { |     private String genChannelPayNotifyUrl(PayChannelDO channel) { | ||||||
|         // _ 转化为 - 的原因,是因为 URL 我们统一采用中划线的原则 |         //去掉channel code, 似乎没啥用, 用统一的回调地址 | ||||||
|         return payProperties.getPayNotifyUrl() + "/" + StrUtil.replace(channel.getCode(), "_", "-") |         return payProperties.getPayNotifyUrl() + "/" + channel.getId(); | ||||||
|                 + "/" + channel.getId(); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     private String generateOrderExtensionNo() { |     private String generateOrderExtensionNo() { | ||||||
| @@ -195,7 +193,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | |||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     @Transactional |     @Transactional | ||||||
|     public void notifyPayOrder(Long channelId, String channelCode, PayNotifyDataDTO notifyData) throws Exception { |     public void notifyPayOrder(Long channelId,  PayNotifyDataDTO notifyData) throws Exception { | ||||||
|         // TODO 芋艿,记录回调日志 |         // TODO 芋艿,记录回调日志 | ||||||
|         log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody()); |         log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody()); | ||||||
|  |  | ||||||
| @@ -207,7 +205,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | |||||||
|             log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); |             log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId()); | ||||||
|             throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); |             throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND); | ||||||
|         } |         } | ||||||
|         //TODO @jason 校验 是否支付宝调用。 使用 支付宝publickey 或者payclient 加一个校验方法 |  | ||||||
|         // 解析支付结果 |         // 解析支付结果 | ||||||
|         PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData); |         PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData); | ||||||
|  |  | ||||||
| @@ -222,7 +220,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | |||||||
|             throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); |             throw exception(PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING); | ||||||
|         } |         } | ||||||
|         // 1.2 更新 PayOrderExtensionDO |         // 1.2 更新 PayOrderExtensionDO | ||||||
|         //TODO @jason notifyRespDTO.getTradeStatus() 需要根据不同的状态更新成不同的值 PayOrderStatusEnum |         //TODO 支付宝交易超时 TRADE_FINISHED 需要更新交易关闭 | ||||||
|         int updateCounts = payOrderExtensionCoreMapper.updateByIdAndStatus(orderExtension.getId(), |         int updateCounts = payOrderExtensionCoreMapper.updateByIdAndStatus(orderExtension.getId(), | ||||||
|                 PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId()) |                 PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId()) | ||||||
|                         .status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(notifyData.getBody()).build()); |                         .status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(notifyData.getBody()).build()); | ||||||
| @@ -241,7 +239,7 @@ public class PayOrderCoreServiceImpl implements PayOrderCoreService { | |||||||
|         } |         } | ||||||
|         // 2.2 更新 PayOrderDO |         // 2.2 更新 PayOrderDO | ||||||
|         updateCounts = payOrderCoreMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(), |         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()) |                         .successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId()) | ||||||
|                         .channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId()) |                         .channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId()) | ||||||
|                         .notifyTime(new Date()).build()); |                         .notifyTime(new Date()).build()); | ||||||
|   | |||||||
| @@ -40,7 +40,9 @@ public class PayRefundChannelQueryHandler extends PayRefundAbstractChannelPostHa | |||||||
|         //更新退款单表 |         //更新退款单表 | ||||||
|         PayRefundDO updateRefundDO = new PayRefundDO(); |         PayRefundDO updateRefundDO = new PayRefundDO(); | ||||||
|         updateRefundDO.setId(respBO.getRefundId()) |         updateRefundDO.setId(respBO.getRefundId()) | ||||||
|                 .setStatus(refundStatus.getStatus()); |                 .setStatus(refundStatus.getStatus()) | ||||||
|  |                 .setChannelErrorCode(respBO.getChannelErrCode()) | ||||||
|  |                 .setChannelErrorMsg(respBO.getChannelErrMsg()); | ||||||
|         updatePayRefund(updateRefundDO); |         updatePayRefund(updateRefundDO); | ||||||
|  |  | ||||||
|         PayOrderDO updateOrderDO = new PayOrderDO(); |         PayOrderDO updateOrderDO = new PayOrderDO(); | ||||||
|   | |||||||
| @@ -182,7 +182,7 @@ public class PayRefundCoreServiceImpl implements PayRefundCoreService { | |||||||
|  |  | ||||||
|  |  | ||||||
|     @Override |     @Override | ||||||
|     public void notifyPayRefund(Long channelId, String channelCode, PayNotifyDataDTO notifyData) { |     public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) { | ||||||
|         log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody()); |         log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody()); | ||||||
|         // 校验支付渠道是否有效 |         // 校验支付渠道是否有效 | ||||||
|         PayChannelDO channel = payChannelCoreService.validPayChannel(channelId); |         PayChannelDO channel = payChannelCoreService.validPayChannel(channelId); | ||||||
|   | |||||||
| @@ -49,4 +49,22 @@ public interface PayClient { | |||||||
|      */ |      */ | ||||||
|     PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData); |     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; | ||||||
|  |     } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -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; | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  |  | ||||||
|  | } | ||||||
| @@ -1,22 +1,14 @@ | |||||||
| package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | 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.PayCommonResult; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.dto.*; | import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | ||||||
| import com.alipay.api.AlipayApiException; | 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.domain.AlipayTradePrecreateModel; | ||||||
| import com.alipay.api.request.AlipayTradePrecreateRequest; | import com.alipay.api.request.AlipayTradePrecreateRequest; | ||||||
| import com.alipay.api.response.AlipayTradePrecreateResponse; | import com.alipay.api.response.AlipayTradePrecreateResponse; | ||||||
| import lombok.SneakyThrows; |  | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
| import java.util.Map; |  | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; | 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 芋道源码 |  * @author 芋道源码 | ||||||
|  */ |  */ | ||||||
| @Slf4j | @Slf4j | ||||||
| public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> { | public class AlipayQrPayClient extends AbstractAlipayClient { | ||||||
|  |  | ||||||
|     private DefaultAlipayClient client; |  | ||||||
|  |  | ||||||
|     public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { |     public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) { | ||||||
|         super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config, new AlipayPayCodeMapping()); |         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 |     @Override | ||||||
|     public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { |     public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { | ||||||
|         // 构建 AlipayTradePrecreateModel 请求 |         // 构建 AlipayTradePrecreateModel 请求 | ||||||
| @@ -56,7 +37,7 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> | |||||||
|         AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); |         AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest(); | ||||||
|         request.setBizModel(model); |         request.setBizModel(model); | ||||||
|         request.setNotifyUrl(reqDTO.getNotifyUrl()); |         request.setNotifyUrl(reqDTO.getNotifyUrl()); | ||||||
|  |         request.setReturnUrl(reqDTO.getReturnUrl()); | ||||||
|         // 执行请求 |         // 执行请求 | ||||||
|         AlipayTradePrecreateResponse response; |         AlipayTradePrecreateResponse response; | ||||||
|         try { |         try { | ||||||
| @@ -68,30 +49,4 @@ public class AlipayQrPayClient extends AbstractPayClient<AlipayPayClientConfig> | |||||||
|         // TODO 芋艿:sub Code 需要测试下各种失败的情况 |         // TODO 芋艿:sub Code 需要测试下各种失败的情况 | ||||||
|         return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping); |         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(); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,31 +1,17 @@ | |||||||
| package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; | ||||||
|  |  | ||||||
| import cn.hutool.core.bean.BeanUtil; |  | ||||||
| import cn.hutool.core.date.DateUtil; | import cn.hutool.core.date.DateUtil; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; | 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.dto.PayOrderUnifiedReqDTO; | ||||||
| import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient; |  | ||||||
| import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum; | 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.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.domain.AlipayTradeWapPayModel; | ||||||
| import com.alipay.api.request.AlipayTradeRefundRequest; |  | ||||||
| import com.alipay.api.request.AlipayTradeWapPayRequest; | import com.alipay.api.request.AlipayTradeWapPayRequest; | ||||||
| import com.alipay.api.response.AlipayTradeRefundResponse; |  | ||||||
| import com.alipay.api.response.AlipayTradeWapPayResponse; | import com.alipay.api.response.AlipayTradeWapPayResponse; | ||||||
| import lombok.SneakyThrows; |  | ||||||
| import lombok.extern.slf4j.Slf4j; | import lombok.extern.slf4j.Slf4j; | ||||||
|  |  | ||||||
| import java.net.SocketTimeoutException; |  | ||||||
| import java.util.Map; |  | ||||||
| import java.util.Objects; | import java.util.Objects; | ||||||
|  |  | ||||||
| import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString; |  | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * 支付宝【手机网站】的 PayClient 实现类 |  * 支付宝【手机网站】的 PayClient 实现类 | ||||||
|  * 文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay |  * 文档: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 芋道源码 |  * @author 芋道源码 | ||||||
|  */ |  */ | ||||||
| @Slf4j | @Slf4j | ||||||
| public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> { | public class AlipayWapPayClient extends AbstractAlipayClient { | ||||||
|  |  | ||||||
|     private DefaultAlipayClient client; |  | ||||||
|  |  | ||||||
|     public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { |     public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) { | ||||||
|         super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping()); |         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 |     @Override | ||||||
|     public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { |     public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { | ||||||
|         // 构建 AlipayTradeWapPayModel 请求 |         // 构建 AlipayTradeWapPayModel 请求 | ||||||
| @@ -69,6 +46,7 @@ public class AlipayWapPayClient extends AbstractPayClient<AlipayPayClientConfig> | |||||||
|         request.setBizModel(model); |         request.setBizModel(model); | ||||||
|         request.setNotifyUrl(reqDTO.getNotifyUrl()); |         request.setNotifyUrl(reqDTO.getNotifyUrl()); | ||||||
|         request.setReturnUrl(reqDTO.getReturnUrl()); |         request.setReturnUrl(reqDTO.getReturnUrl()); | ||||||
|  |  | ||||||
|         // 执行请求 |         // 执行请求 | ||||||
|         AlipayTradeWapPayResponse response; |         AlipayTradeWapPayResponse response; | ||||||
|         try { |         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; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -4,7 +4,8 @@ import org.springframework.boot.SpringApplication; | |||||||
| import org.springframework.boot.autoconfigure.SpringBootApplication; | import org.springframework.boot.autoconfigure.SpringBootApplication; | ||||||
|  |  | ||||||
| @SuppressWarnings("SpringComponentScan") // 忽略 IDEA 无法识别 ${yudao.info.base-package} 和 ${yudao.core-service.base-package} | @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) { |     public static void main(String[] args) { | ||||||
|         SpringApplication.run(UserServerApplication.class, args); |         SpringApplication.run(UserServerApplication.class, args); | ||||||
|   | |||||||
| @@ -2,6 +2,7 @@ package cn.iocoder.yudao.userserver.modules.pay.controller.order; | |||||||
|  |  | ||||||
| import cn.hutool.core.bean.BeanUtil; | import cn.hutool.core.bean.BeanUtil; | ||||||
| import cn.iocoder.yudao.coreservice.modules.pay.dal.dataobject.order.PayOrderDO; | 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.PayOrderCoreService; | ||||||
| import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService; | import cn.iocoder.yudao.coreservice.modules.pay.service.order.PayRefundCoreService; | ||||||
| import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO; | import cn.iocoder.yudao.coreservice.modules.pay.service.order.dto.PayOrderSubmitReqDTO; | ||||||
| @@ -38,6 +39,9 @@ public class PayOrderController { | |||||||
|     @Resource |     @Resource | ||||||
|     private PayRefundCoreService payRefundCoreService; |     private PayRefundCoreService payRefundCoreService; | ||||||
|  |  | ||||||
|  |     @Resource PayCommonCoreService commonCoreService; | ||||||
|  |  | ||||||
|  |  | ||||||
|     @PostMapping("/submit") |     @PostMapping("/submit") | ||||||
|     @ApiOperation("提交支付订单") |     @ApiOperation("提交支付订单") | ||||||
| //    @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好 | //    @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好 | ||||||
| @@ -57,82 +61,53 @@ public class PayOrderController { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // ========== 支付渠道的回调 ========== |     // ========== 支付渠道的回调 ========== | ||||||
|  |     //TODO 芋道源码 换成了统一的地址了 /notify/{channelId},测试通过可以删除 | ||||||
|     @PostMapping("/notify/wx-pub/{channelId}") |     @PostMapping("/notify/wx-pub/{channelId}") | ||||||
|     @ApiOperation("通知微信公众号支付的结果") |     @ApiOperation("通知微信公众号支付的结果") | ||||||
|     public String notifyWxPayOrder(@PathVariable("channelId") Long channelId, |     public String notifyWxPayOrder(@PathVariable("channelId") Long channelId, | ||||||
|                                    @RequestBody String xmlData) throws Exception { |                                    @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"; |         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 |      * 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 |      * @param channelId 渠道id | ||||||
|      * @return 返回跳转页面 |      * @return 返回跳转页面 | ||||||
|      */ |      */ | ||||||
|     @GetMapping(value = "/return/alipay-wap/{channelId}") |     @GetMapping(value = "/return/{channelId}") | ||||||
|     @ApiOperation("支付宝 wap 页面回跳") |     @ApiOperation("渠道统一的支付成功返回地址") | ||||||
|     public String returnAliPayWapPayOrder(@PathVariable("channelId") Long channelId){ |     public String returnAliPayOrder(@PathVariable("channelId") Long channelId, @RequestParam Map<String, String> params){ | ||||||
|         //TODO 校验 是否支付宝调用。 可以根据 appId 跳转不同的页面 |         //TODO 可以根据渠道和 app_id 返回不同的页面 | ||||||
|         return "支付成功"; |         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 的参数 |      * @param channelId 渠道编号 | ||||||
|      * @return |      * @param params form 参数 | ||||||
|  |      * @param originData http request body | ||||||
|  |      * @return 成功返回 "success" | ||||||
|      */ |      */ | ||||||
|     private boolean  isAliPayRefund(Map<String, String> params) { |     @PostMapping(value = "/notify/{channelId}") | ||||||
|         if (params.containsKey("refund_fee")) { |     @ApiOperation("渠道统一的支付成功,或退款成功 通知url") | ||||||
|             return true; |     public String notifyChannelPay(@PathVariable("channelId") Long channelId, | ||||||
|         } else { |                                @RequestParam Map<String, String> params, | ||||||
|             return false; |                                @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"; |         return "success"; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user
	 jason
					jason