Преглед изворни кода

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

jason пре 3 година
родитељ
комит
f108d478a8
14 измењених фајлова са 332 додато и 232 уклоњено
  1. 3 3
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java
  2. 25 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayCommonCoreService.java
  3. 1 2
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java
  4. 1 2
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java
  5. 62 0
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayCommonCoreServiceImpl.java
  6. 10 12
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java
  7. 3 1
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelQueryHandler.java
  8. 1 1
      yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java
  9. 18 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
  10. 162 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java
  11. 3 48
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java
  12. 7 103
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java
  13. 2 1
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java
  14. 34 59
      yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java

+ 3 - 3
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/enums/PayErrorCodeCoreConstants.java

@@ -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 ==========
      */

+ 25 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayCommonCoreService.java

@@ -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);
+}

+ 1 - 2
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayOrderCoreService.java

@@ -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;
 
 
 

+ 1 - 2
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/PayRefundCoreService.java

@@ -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;
 
 
 

+ 62 - 0
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayCommonCoreServiceImpl.java

@@ -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);
+    }
+}

+ 10 - 12
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayOrderCoreServiceImpl.java

@@ -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());

+ 3 - 1
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundChannelQueryHandler.java

@@ -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();

+ 1 - 1
yudao-core-service/src/main/java/cn/iocoder/yudao/coreservice/modules/pay/service/order/impl/PayRefundCoreServiceImpl.java

@@ -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);

+ 18 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java

@@ -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;
+    }
 }

+ 162 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java

@@ -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;
+        }
+    }
+
+}

+ 3 - 48
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java

@@ -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();
-    }
 }

+ 7 - 103
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java

@@ -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;
-    }
+
+
+
+
 
 }

+ 2 - 1
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/UserServerApplication.java

@@ -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);

+ 34 - 59
yudao-user-server/src/main/java/cn/iocoder/yudao/userserver/modules/pay/controller/order/PayOrderController.java

@@ -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";
     }