Prechádzať zdrojové kódy

!417 支付收银台,接入支付宝的 PC、Wap、二维码、条码、App 等支付方式
Merge pull request !417 from 芋道源码/feature/dev-yunai

芋道源码 2 rokov pred
rodič
commit
c419c1e107
97 zmenil súbory, kde vykonal 11673 pridanie a 11019 odobranie
  1. 0 33
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/AbstractPayCodeMapping.java
  2. 16 37
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayClient.java
  3. 0 57
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/PayCommonResult.java
  4. 3 3
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayNotifyDataDTO.java
  5. 2 8
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderNotifyRespDTO.java
  6. 2 7
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundNotifyDTO.java
  7. 12 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java
  8. 23 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedRespDTO.java
  9. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java
  10. 1 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java
  11. 51 25
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
  12. 3 5
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/PayClientFactoryImpl.java
  13. 42 83
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AbstractAlipayClient.java
  14. 57 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayAppPayClient.java
  15. 66 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayBarPayClient.java
  16. 0 28
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPayCodeMapping.java
  17. 61 30
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java
  18. 23 21
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayQrPayClient.java
  19. 27 42
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayWapPayClient.java
  20. 0 56
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXCodeMapping.java
  21. 36 45
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXLitePayClient.java
  22. 35 43
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXNativePayClient.java
  23. 36 43
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java
  24. 2 1
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java
  25. 29 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayDisplayModeEnum.java
  26. 4 2
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayFrameworkErrorCodeConstants.java
  27. 2 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayNotifyRefundStatusEnum.java
  28. 10 10
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/PayClientFactoryImplIntegrationTest.java
  29. 9 9
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java
  30. 1 8
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java
  31. 6 6
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java
  32. 9 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java
  33. 14 1
      yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java
  34. 7 2
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java
  35. 34 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java
  36. 12 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/PayDemoOrderRespVO.java
  37. 24 15
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java
  38. 31 17
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java
  39. 29 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java
  40. 20 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java
  41. 5 18
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java
  42. 2 14
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java
  43. 2 10
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java
  44. 0 47
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/refund/AppPayRefundController.java
  45. 4 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/refund/package-info.java
  46. 0 34
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/refund/vo/AppPayRefundReqVO.java
  47. 0 21
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/refund/vo/AppPayRefundRespVO.java
  48. 12 10
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java
  49. 0 13
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java
  50. 7 5
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java
  51. 0 4
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java
  52. 5 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java
  53. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogCoreMapper.java
  54. 1 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskCoreMapper.java
  55. 24 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java
  56. 169 3
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java
  57. 15 14
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java
  58. 15 10
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java
  59. 68 77
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
  60. 0 47
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/dto/PayOrderSubmitReqDTO.java
  61. 0 23
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/dto/PayOrderSubmitRespDTO.java
  62. 0 51
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/dto/PayRefundReqDTO.java
  63. 0 24
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/dto/PayRefundRespDTO.java
  64. 9 9
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java
  65. 51 41
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java
  66. 3 0
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/util/PaySeqUtils.java
  67. 5 5
      yudao-server/pom.xml
  68. 0 4
      yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/admin/package-info.java
  69. 0 73
      yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/app/AppShopOrderController.java
  70. 0 20
      yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/app/vo/AppShopOrderCreateRespVO.java
  71. 0 9
      yudao-server/src/main/java/cn/iocoder/yudao/module/shop/package-info.java
  72. 0 11
      yudao-server/src/main/java/cn/iocoder/yudao/server/framework/ui/core/AdminUiController.java
  73. 1 0
      yudao-server/src/main/resources/application-local.yaml
  74. 0 79
      yudao-server/src/main/resources/static/pay_alipay_qr.html
  75. 0 65
      yudao-server/src/main/resources/static/pay_alipay_wap.html
  76. 0 1
      yudao-server/src/main/resources/static/qrcode.min.js
  77. 0 38
      yudao-server/src/main/resources/static/social-login.html
  78. 0 87
      yudao-server/src/main/resources/static/social-login2.html
  79. 1 0
      yudao-ui-admin/package.json
  80. 35 0
      yudao-ui-admin/src/api/pay/demo.js
  81. 17 18
      yudao-ui-admin/src/api/pay/order.js
  82. 1 0
      yudao-ui-admin/src/assets/images/pay/icon/alipay_app.svg
  83. 2 0
      yudao-ui-admin/src/assets/images/pay/icon/alipay_bar.svg
  84. 1 0
      yudao-ui-admin/src/assets/images/pay/icon/alipay_pc.svg
  85. 2 0
      yudao-ui-admin/src/assets/images/pay/icon/alipay_qr.svg
  86. 1 0
      yudao-ui-admin/src/assets/images/pay/icon/alipay_wap.svg
  87. 1 0
      yudao-ui-admin/src/assets/images/pay/icon/mock.svg
  88. 2 0
      yudao-ui-admin/src/assets/images/pay/icon/wx_app.svg
  89. 1 0
      yudao-ui-admin/src/assets/images/pay/icon/wx_lite.svg
  90. 2 0
      yudao-ui-admin/src/assets/images/pay/icon/wx_pub.svg
  91. 15 0
      yudao-ui-admin/src/router/index.js
  92. 23 1
      yudao-ui-admin/src/utils/constants.js
  93. 12 5
      yudao-ui-admin/src/views/pay/app/index.vue
  94. 214 0
      yudao-ui-admin/src/views/pay/demo/index.vue
  95. 3 3
      yudao-ui-admin/src/views/pay/order/index.vue
  96. 397 0
      yudao-ui-admin/src/views/pay/order/submit.vue
  97. 9804 9482
      yudao-ui-admin/yarn.lock

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

@@ -1,33 +0,0 @@
-package cn.iocoder.yudao.framework.pay.core.client;
-
-import cn.iocoder.yudao.framework.common.exception.ErrorCode;
-import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
-import lombok.extern.slf4j.Slf4j;
-
-/**
- * 将 API 的错误码,转换为通用的错误码
- *
- * @see PayCommonResult
- * @see PayFrameworkErrorCodeConstants
- *
- * @author 芋道源码
- */
-@Slf4j
-public abstract class AbstractPayCodeMapping {
-
-    public final ErrorCode apply(String apiCode, String apiMsg) {
-        if (apiCode == null) {
-            log.error("[apply][API 错误码为空,请排查]");
-            return PayFrameworkErrorCodeConstants.EXCEPTION;
-        }
-        ErrorCode errorCode = this.apply0(apiCode, apiMsg);
-        if (errorCode == null) {
-            log.error("[apply][API 错误码({}) 错误提示({}) 无法匹配]", apiCode, apiMsg);
-            return PayFrameworkErrorCodeConstants.PAY_UNKNOWN;
-        }
-        return errorCode;
-    }
-
-    protected abstract ErrorCode apply0(String apiCode, String apiMsg);
-
-}

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

@@ -1,7 +1,12 @@
 package cn.iocoder.yudao.framework.pay.core.client;
 
-
-import cn.iocoder.yudao.framework.pay.core.client.dto.*;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
 
 /**
  * 支付客户端,用于对接各支付渠道的 SDK,实现发起支付、退款等功能
@@ -23,51 +28,25 @@ public interface PayClient {
      * @param reqDTO 下单信息
      * @return 各支付渠道的返回结果
      */
-    PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
-
-    /**
-     * 解析支付单的通知结果
-     *
-     * @param data 通知结果
-     * @return 解析结果
-     * @throws Exception 解析失败,抛出异常
-     */
-    PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws Exception;
+    PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO);
 
     /**
      * 调用支付渠道,进行退款
      * @param reqDTO  统一退款请求信息
      * @return 各支付渠道的统一返回结果
      */
-    PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
-
-    /**
-     * 解析支付退款通知数据
-     * @param notifyData  支付退款通知请求数据
-     * @return 支付退款通知的Notify DTO
-     */
-    PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData);
-
-    // TODO @芋艿:后续改成非 default,避免不知道去实现
-    /**
-     * 验证是否渠道通知
-     *
-     * @param notifyData 通知数据
-     * @return 默认是 true
-     */
-    default boolean verifyNotifyData(PayNotifyDataDTO notifyData) {
-        return true;
-    }
+    PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO);
 
-    // TODO @芋艿:后续改成非 default,避免不知道去实现
     /**
-     * 判断是否为退款通知
+     * 解析回调数据
      *
-     * @param notifyData  通知数据
-     * @return 默认是 false
+     * @param rawNotify 通知内容
+     * @return 回调对象
+     *         1. {@link PayRefundNotifyRespDTO} 退款通知
+     *         2. {@link PayOrderNotifyRespDTO} 支付通知
      */
-    default  boolean isRefundNotify(PayNotifyDataDTO notifyData){
-        return false;
+    default Object parseNotify(PayNotifyReqDTO rawNotify) {
+        throw new UnsupportedOperationException("未实现 parseNotify 方法!");
     }
 
 }

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

@@ -1,57 +0,0 @@
-package cn.iocoder.yudao.framework.pay.core.client;
-
-import cn.hutool.core.exceptions.ExceptionUtil;
-import cn.hutool.core.lang.Assert;
-import cn.iocoder.yudao.framework.common.exception.ErrorCode;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants;
-import lombok.Data;
-import lombok.EqualsAndHashCode;
-import lombok.ToString;
-
-/**
- * 支付的 CommonResult 拓展类
- *
- * 考虑到不同的平台,返回的 code 和 msg 是不同的,所以统一额外返回 {@link #apiCode} 和 {@link #apiMsg} 字段
- *
- * @author 芋道源码
- */
-@Data
-@EqualsAndHashCode(callSuper = true)
-@ToString(callSuper = true)
-public class PayCommonResult<T> extends CommonResult<T> {
-
-    /**
-     * API 返回错误码
-     *
-     * 由于第三方的错误码可能是字符串,所以使用 String 类型
-     */
-    private String apiCode;
-    /**
-     * API 返回提示
-     */
-    private String apiMsg;
-
-    private PayCommonResult() {
-    }
-
-    public static <T> PayCommonResult<T> build(String apiCode, String apiMsg, T data, AbstractPayCodeMapping codeMapping) {
-        Assert.notNull(codeMapping, "参数 codeMapping 不能为空");
-        PayCommonResult<T> result = new PayCommonResult<T>().setApiCode(apiCode).setApiMsg(apiMsg);
-        result.setData(data);
-        // 翻译错误码
-        if (codeMapping != null) {
-            ErrorCode errorCode = codeMapping.apply(apiCode, apiMsg);
-            result.setCode(errorCode.getCode()).setMsg(errorCode.getMsg());
-        }
-        return result;
-    }
-
-    public static <T> PayCommonResult<T> error(Throwable ex) {
-        PayCommonResult<T> result = new PayCommonResult<>();
-        result.setCode(PayFrameworkErrorCodeConstants.EXCEPTION.getCode());
-        result.setMsg(ExceptionUtil.getRootCauseMessage(ex));
-        return result;
-    }
-
-}

+ 3 - 3
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayNotifyDataDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.pay.core.client.dto;
+package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
 
 import lombok.Builder;
 import lombok.Data;
@@ -13,11 +13,11 @@ import java.util.Map;
 @Data
 @ToString
 @Builder
-public class PayNotifyDataDTO {
+public class PayNotifyReqDTO {
 
 
     /**
-     *  HTTP 回调接口的 request body
+     * HTTP 回调接口的 request body
      */
     private String body;
 

+ 2 - 8
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderNotifyRespDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.pay.core.client.dto;
+package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
 
 import lombok.AllArgsConstructor;
 import lombok.Builder;
@@ -36,14 +36,8 @@ public class PayOrderNotifyRespDTO {
     private LocalDateTime successTime;
 
     /**
-     * 通知的原始数据
-     *
-     * 主要用于持久化,方便后续修复数据,或者排错
-     */
-    private String data;
-
-    /**
      * TODO @jason 结合其他的渠道定义成枚举,
+     *
      * alipay
      * TRADE_CLOSED,未付款交易超时关闭,或支付完成后全额退款。
      * TRADE_SUCCESS, 交易支付成功

+ 2 - 7
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundNotifyDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.pay.core.client.dto;
+package cn.iocoder.yudao.framework.pay.core.client.dto.notify;
 
 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
 import lombok.Builder;
@@ -15,14 +15,13 @@ import java.time.LocalDateTime;
 @Data
 @ToString
 @Builder
-public class PayRefundNotifyDTO {
+public class PayRefundNotifyRespDTO {
 
     /**
      * 支付渠道编号
      */
     private String channelOrderNo;
 
-
     /**
      * 交易订单号,根据规则生成
      * 调用支付渠道时,使用该字段作为对接的订单号。
@@ -46,18 +45,14 @@ public class PayRefundNotifyDTO {
      */
     private String reqNo;
 
-
     /**
      * 退款是否成功
      */
     private PayNotifyRefundStatusEnum status;
 
-
-
     /**
      * 退款成功时间
      */
     private LocalDateTime refundSuccessTime;
 
-
 }

+ 12 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayOrderUnifiedReqDTO.java

@@ -1,5 +1,6 @@
-package cn.iocoder.yudao.framework.pay.core.client.dto;
+package cn.iocoder.yudao.framework.pay.core.client.dto.order;
 
+import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
 import lombok.Data;
 import org.hibernate.validator.constraints.Length;
 import org.hibernate.validator.constraints.URL;
@@ -7,6 +8,7 @@ import org.hibernate.validator.constraints.URL;
 import javax.validation.constraints.DecimalMin;
 import javax.validation.constraints.NotEmpty;
 import javax.validation.constraints.NotNull;
+import java.awt.*;
 import java.time.LocalDateTime;
 import java.util.Map;
 
@@ -78,4 +80,13 @@ public class PayOrderUnifiedReqDTO {
      */
     private Map<String, String> channelExtras;
 
+    /**
+     * 展示模式
+     *
+     * 如果不传递,则每个支付渠道使用默认的方式
+     *
+     * 枚举 {@link PayDisplayModeEnum}
+     */
+    private String displayMode;
+
 }

+ 23 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedRespDTO.java

@@ -0,0 +1,23 @@
+package cn.iocoder.yudao.framework.pay.core.client.dto.order;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+
+/**
+ * 统一下单 Response DTO
+ *
+ * @author 芋道源码
+ */
+@Data
+public class PayOrderUnifiedRespDTO {
+
+    /**
+     * 展示模式
+     */
+    private String displayMode;
+    /**
+     * 展示内容
+     */
+    private String displayContent;
+
+}

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedReqDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.pay.core.client.dto;
+package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
 
 import lombok.AllArgsConstructor;
 import lombok.Builder;

+ 1 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/PayRefundUnifiedRespDTO.java

@@ -1,4 +1,4 @@
-package cn.iocoder.yudao.framework.pay.core.client.dto;
+package cn.iocoder.yudao.framework.pay.core.client.dto.refund;
 
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelRefundRespEnum;
 import lombok.AllArgsConstructor;

+ 51 - 25
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java

@@ -1,17 +1,23 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl;
 
-import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
+import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
-import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
-import lombok.extern.slf4j.Slf4j;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
+import com.alipay.api.AlipayResponse;import lombok.extern.slf4j.Slf4j;
 
 import javax.validation.Validation;
+import java.time.LocalDateTime;
 
+import static cn.hutool.core.date.DatePattern.NORM_DATETIME_FORMATTER;
+import static cn.hutool.core.date.DatePattern.NORM_DATETIME_MS_FORMATTER;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
+import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.PAY_EXCEPTION;
 
 /**
  * 支付客户端的抽象类,提供模板方法,减少子类的冗余代码
@@ -30,18 +36,13 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
      */
     private final String channelCode;
     /**
-     * 错误码枚举类
-     */
-    protected AbstractPayCodeMapping codeMapping;
-    /**
      * 支付配置
      */
     protected Config config;
 
-    public AbstractPayClient(Long channelId, String channelCode, Config config, AbstractPayCodeMapping codeMapping) {
+    public AbstractPayClient(Long channelId, String channelCode, Config config) {
         this.channelId = channelId;
         this.channelCode = channelCode;
-        this.codeMapping = codeMapping;
         this.config = config;
     }
 
@@ -69,47 +70,72 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
         this.init();
     }
 
-    protected Double calculateAmount(Integer amount) {
-        return amount / 100.0;
-    }
-
     @Override
     public Long getId() {
         return channelId;
     }
 
     @Override
-    public final PayCommonResult<?> unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+    public final PayOrderUnifiedRespDTO unifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
         Validation.buildDefaultValidatorFactory().getValidator().validate(reqDTO);
         // 执行短信发送
-        PayCommonResult<?> result;
+        PayOrderUnifiedRespDTO result;
         try {
             result = doUnifiedOrder(reqDTO);
         } catch (Throwable ex) {
             // 打印异常日志
             log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), ex);
-            // 封装返回
-            return PayCommonResult.error(ex);
+            throw buildException(ex);
         }
         return result;
     }
 
-    protected abstract PayCommonResult<?> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
+    protected abstract PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO)
             throws Throwable;
 
     @Override
-    public PayCommonResult<PayRefundUnifiedRespDTO> unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
-        PayCommonResult<PayRefundUnifiedRespDTO> resp;
+    public PayRefundUnifiedRespDTO unifiedRefund(PayRefundUnifiedReqDTO reqDTO) {
+        PayRefundUnifiedRespDTO resp;
         try {
             resp = doUnifiedRefund(reqDTO);
         }  catch (Throwable ex) {
             // 记录异常日志
             log.error("[unifiedRefund][request({}) 发起退款失败]", toJsonString(reqDTO), ex);
-            resp = PayCommonResult.error(ex);
+            throw buildException(ex);
         }
         return resp;
     }
 
-    protected abstract PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
+    protected abstract PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable;
+
+    // ========== 各种工具方法 ==========
+
+    private RuntimeException buildException(Throwable ex) {
+        if (ex instanceof RuntimeException) {
+            return (RuntimeException) ex;
+        }
+        throw new RuntimeException(ex);
+    }
+
+    protected void validateSuccess(AlipayResponse response) {
+        if (response.isSuccess()) {
+            return;
+        }
+        throw exception0(PAY_EXCEPTION.getCode(), response.getSubMsg());
+    }
+
+    protected String formatAmount(Integer amount) {
+        return String.valueOf(amount / 100.0);
+    }
+
+    protected String formatTime(LocalDateTime time) {
+        // "yyyy-MM-dd HH:mm:ss"
+        return LocalDateTimeUtil.format(time, NORM_DATETIME_FORMATTER);
+    }
+
+    protected LocalDateTime parseTime(String str) {
+        // "yyyy-MM-dd HH:mm:ss"
+        return LocalDateTimeUtil.parse(str, NORM_DATETIME_FORMATTER);
+    }
 
 }

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

@@ -4,10 +4,7 @@ import cn.hutool.core.lang.Assert;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
-import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
-import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPcPayClient;
-import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
-import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
+import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.*;
 import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXLitePayClient;
 import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXNativePayClient;
 import cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXPayClientConfig;
@@ -69,8 +66,9 @@ public class PayClientFactoryImpl implements PayClientFactory {
             case WX_NATIVE: return (AbstractPayClient<Config>) new WXNativePayClient(channelId, (WXPayClientConfig) config);
             case ALIPAY_WAP: return (AbstractPayClient<Config>) new AlipayWapPayClient(channelId, (AlipayPayClientConfig) config);
             case ALIPAY_QR: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
-            case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayQrPayClient(channelId, (AlipayPayClientConfig) config);
+            case ALIPAY_APP: return (AbstractPayClient<Config>) new AlipayAppPayClient(channelId, (AlipayPayClientConfig) config);
             case ALIPAY_PC: return (AbstractPayClient<Config>) new AlipayPcPayClient(channelId, (AlipayPayClientConfig) config);
+            case ALIPAY_BAR: return (AbstractPayClient<Config>) new AlipayBarPayClient(channelId, (AlipayPayClientConfig) config);
         }
         // 创建失败,错误日志 + 抛出异常
         log.error("[createPayClient][配置({}) 找不到合适的客户端实现]", config);

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

@@ -1,10 +1,12 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 
 import cn.hutool.core.bean.BeanUtil;
-import cn.hutool.core.date.LocalDateTimeUtil;
-import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
-import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
-import cn.iocoder.yudao.framework.pay.core.client.dto.*;
+import cn.hutool.http.HttpUtil;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
 import com.alipay.api.AlipayApiException;
@@ -18,7 +20,6 @@ import lombok.SneakyThrows;
 import lombok.extern.slf4j.Slf4j;
 
 import java.nio.charset.StandardCharsets;
-import java.util.HashMap;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
@@ -33,9 +34,8 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
 
     protected DefaultAlipayClient client;
 
-    public AbstractAlipayClient(Long channelId, String channelCode,
-                                AlipayPayClientConfig config, AbstractPayCodeMapping codeMapping) {
-        super(channelId, channelCode, config, codeMapping);
+    public AbstractAlipayClient(Long channelId, String channelCode, AlipayPayClientConfig config) {
+        super(channelId, channelCode, config);
     }
 
     @Override
@@ -47,70 +47,24 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
     }
 
     /**
-     * 从支付宝通知返回参数中解析 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 = strToMap(data.getBody());
-
-        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(LocalDateTimeUtil.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 = strToMap(notifyData.getBody());
-        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(LocalDateTimeUtil.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 PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  {
+    protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  {
         AlipayTradeRefundModel model=new AlipayTradeRefundModel();
         model.setTradeNo(reqDTO.getChannelOrderNo());
         model.setOutTradeNo(reqDTO.getPayTradeNo());
+
         model.setOutRequestNo(reqDTO.getMerchantRefundId());
-        model.setRefundAmount(calculateAmount(reqDTO.getAmount()).toString());
+        model.setRefundAmount(formatAmount(reqDTO.getAmount()).toString());
         model.setRefundReason(reqDTO.getReason());
+
         AlipayTradeRefundRequest refundRequest = new AlipayTradeRefundRequest();
         refundRequest.setBizModel(model);
+        refundRequest.setNotifyUrl(reqDTO.getNotifyUrl());
+        refundRequest.setReturnUrl(reqDTO.getNotifyUrl());
         try {
             AlipayTradeRefundResponse response =  client.execute(refundRequest);
             log.info("[doUnifiedRefund][response({}) 发起退款 渠道返回", toJsonString(response));
@@ -119,38 +73,43 @@ public abstract class AbstractAlipayClient extends AbstractPayClient<AlipayPayCl
                 //支付宝不返回退款单号,设置为空
                 PayRefundUnifiedRespDTO respDTO = new PayRefundUnifiedRespDTO();
                 respDTO.setChannelRefundId("");
-                return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping);
+//                return PayCommonResult.build(response.getCode(), response.getMsg(), respDTO, codeMapping); TODO
+                return null;
             }
             // 失败。需要抛出异常
-            return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping);
+//            return PayCommonResult.build(response.getCode(), response.getMsg(), null, codeMapping); TODO
+            return null;
         } catch (AlipayApiException e) {
             // TODO 记录异常日志
             log.error("[doUnifiedRefund][request({}) 发起退款失败,网络读超时,退款状态未知]", toJsonString(reqDTO), e);
-            return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
+//            return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping); TODO
+            return null;
         }
     }
 
-
-
-    /**
-     * 支付宝统一回调参数  str 转 map
-     *
-     * @param s 支付宝支付通知回调参数
-     * @return map 支付宝集合
-     */
-    public static Map<String, String> strToMap(String s) {
-        // TODO @zxy:这个可以使用 hutool 的 HttpUtil decodeParams 方法么?
-        Map<String, String> stringStringMap = new HashMap<>();
-        // 调整时间格式
-        String s3 = s.replaceAll("%3A", ":");
-        // 获取 map
-        String s4 = s3.replace("+", " ");
-        String[] split = s4.split("&");
-        for (String s1 : split) {
-            String[] split1 = s1.split("=");
-            stringStringMap.put(split1[0], split1[1]);
+    @Override
+    @SneakyThrows
+    public Object parseNotify(PayNotifyReqDTO rawNotify) {
+        // 1. 校验回调数据
+        String body = rawNotify.getBody();
+        Map<String, String> params = rawNotify.getParams();
+        Map<String, String> bodyObj = HttpUtil.decodeParamMap(body, StandardCharsets.UTF_8);
+        AlipaySignature.rsaCheckV1(bodyObj, config.getAlipayPublicKey(),
+                StandardCharsets.UTF_8.name(), "RSA2");
+
+        // 2.1 退款的情况
+        if (bodyObj.containsKey("refund_fee")) {
+            return PayRefundNotifyRespDTO.builder().channelOrderNo(bodyObj.get("trade_no"))
+                    .tradeNo(bodyObj.get("out_trade_no")).reqNo(bodyObj.get("out_biz_no"))
+                    .status(PayNotifyRefundStatusEnum.SUCCESS)
+                    .refundSuccessTime(parseTime(params.get("gmt_refund")))
+                    .build();
         }
-        return stringStringMap;
+        // 2.2 支付的情况
+        return PayOrderNotifyRespDTO.builder().orderExtensionNo(bodyObj.get("out_trade_no"))
+                .channelOrderNo(bodyObj.get("trade_no")).channelUserId(bodyObj.get("seller_id"))
+                .tradeStatus(bodyObj.get("trade_status")).successTime(parseTime(params.get("notify_time")))
+                .build();
     }
 
 }

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

@@ -0,0 +1,57 @@
+package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
+
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.AlipayTradeAppPayModel;
+import com.alipay.api.request.AlipayTradeAppPayRequest;
+import com.alipay.api.response.AlipayTradeAppPayResponse;
+import lombok.extern.slf4j.Slf4j;
+
+/**
+ * 支付宝【App 支付】的 PayClient 实现类
+ *
+ * 文档:<a href="https://opendocs.alipay.com/open/02e7gq">App 支付</a>
+ *
+ * // TODO 芋艿:未详细测试,因为手头没 App
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class AlipayAppPayClient extends AbstractAlipayClient {
+
+    public AlipayAppPayClient(Long channelId, AlipayPayClientConfig config) {
+        super(channelId, PayChannelEnum.ALIPAY_APP.getCode(), config);
+    }
+
+    @Override
+    public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
+        // 1.1 构建 AlipayTradeAppPayModel 请求
+        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
+        // ① 通用的参数
+        model.setOutTradeNo(reqDTO.getMerchantOrderId());
+        model.setSubject(reqDTO.getSubject());
+        model.setBody(reqDTO.getBody());
+        model.setTotalAmount(formatAmount(reqDTO.getAmount()));
+        model.setProductCode(" QUICK_MSECURITY_PAY"); // 销售产品码:无线快捷支付产品
+        // ② 个性化的参数【无】
+        // ③ 支付宝扫码支付只有一种展示
+        String displayMode = PayDisplayModeEnum.APP.getMode();
+
+        // 1.2 构建 AlipayTradePrecreateRequest 请求
+        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
+        request.setBizModel(model);
+        request.setNotifyUrl(reqDTO.getNotifyUrl());
+        request.setReturnUrl(reqDTO.getReturnUrl());
+
+        // 2.1 执行请求
+        AlipayTradeAppPayResponse response = client.execute(request);
+        // 2.2 处理结果
+        validateSuccess(response);
+        return new PayOrderUnifiedRespDTO()
+                .setDisplayMode(displayMode).setDisplayContent("");
+    }
+
+}

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

@@ -0,0 +1,66 @@
+package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
+
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
+import com.alipay.api.AlipayApiException;
+import com.alipay.api.domain.AlipayTradePayModel;
+import com.alipay.api.request.AlipayTradePayRequest;
+import com.alipay.api.response.AlipayTradePayResponse;
+import lombok.extern.slf4j.Slf4j;
+
+import static cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants.BAD_REQUEST;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception0;
+
+/**
+ * 支付宝【条码支付】的 PayClient 实现类
+ *
+ * 文档:<a href="https://opendocs.alipay.com/open/194/105072">当面付</a>
+ *
+ * @author 芋道源码
+ */
+@Slf4j
+public class AlipayBarPayClient extends AbstractAlipayClient {
+
+    public AlipayBarPayClient(Long channelId, AlipayPayClientConfig config) {
+        super(channelId, PayChannelEnum.ALIPAY_BAR.getCode(), config);
+    }
+
+    @Override
+    public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
+        String authCode = MapUtil.getStr(reqDTO.getChannelExtras(), "auth_code");
+        if (StrUtil.isEmpty(authCode)) {
+            throw exception0(BAD_REQUEST.getCode(), "条形码不能为空");
+        }
+
+        // 1.1 构建 AlipayTradePayModel 请求
+        AlipayTradePayModel model = new AlipayTradePayModel();
+        // ① 通用的参数
+        model.setOutTradeNo(reqDTO.getMerchantOrderId());
+        model.setSubject(reqDTO.getSubject());
+        model.setBody(reqDTO.getBody());
+        model.setTotalAmount(formatAmount(reqDTO.getAmount()));
+        model.setScene("bar_code"); // 当面付条码支付场景
+        // ② 个性化的参数
+        model.setAuthCode(authCode);
+        // ③ 支付宝条码支付只有一种展示
+        String displayMode = PayDisplayModeEnum.BAR_CODE.getMode();
+
+        // 1.2 构建 AlipayTradePayRequest 请求
+        AlipayTradePayRequest request = new AlipayTradePayRequest();
+        request.setBizModel(model);
+        request.setNotifyUrl(reqDTO.getNotifyUrl());
+        request.setReturnUrl(reqDTO.getReturnUrl());
+
+        // 2.1 执行请求
+        AlipayTradePayResponse response = client.execute(request);
+        // 2.2 处理结果
+        validateSuccess(response);
+        return new PayOrderUnifiedRespDTO()
+                .setDisplayMode(displayMode).setDisplayContent("");
+    }
+}

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

@@ -1,28 +0,0 @@
-package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
-
-import cn.iocoder.yudao.framework.common.exception.ErrorCode;
-import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
-import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
-
-import java.util.Objects;
-
-/**
- * 支付宝的 PayCodeMapping 实现类
- *
- * @author 芋道源码
- */
-public class AlipayPayCodeMapping extends AbstractPayCodeMapping {
-
-    @Override
-    protected ErrorCode apply0(String apiCode, String apiMsg) {
-        if (Objects.equals(apiCode, "10000")) {
-            return GlobalErrorCodeConstants.SUCCESS;
-        }
-        // alipay wap  api code 返回为null, 暂时定为-9999
-        if (Objects.equals(apiCode, "-9999")) {
-            return GlobalErrorCodeConstants.SUCCESS;
-        }
-        return null;
-    }
-
-}

+ 61 - 30
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java

@@ -1,21 +1,24 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 
+import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
-import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
+import cn.hutool.http.Method;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
-import com.alibaba.fastjson.JSONObject;
+import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
 import com.alipay.api.AlipayApiException;
 import com.alipay.api.domain.AlipayTradePagePayModel;
 import com.alipay.api.request.AlipayTradePagePayRequest;
 import com.alipay.api.response.AlipayTradePagePayResponse;
 import lombok.extern.slf4j.Slf4j;
 
+import java.util.Objects;
 
 /**
- * 支付宝【PC网站支付】的 PayClient 实现类
- * 文档:https://opendocs.alipay.com/open/270/105898
+ * 支付宝【PC 网站】的 PayClient 实现类
+ *
+ * 文档:<a href="https://opendocs.alipay.com/open/270/105898">电脑网站支付</a>
  *
  * @author XGD
  */
@@ -23,38 +26,66 @@ import lombok.extern.slf4j.Slf4j;
 public class AlipayPcPayClient extends AbstractAlipayClient {
 
     public AlipayPcPayClient(Long channelId, AlipayPayClientConfig config) {
-        super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config, new AlipayPayCodeMapping());
+        super(channelId, PayChannelEnum.ALIPAY_PC.getCode(), config);
     }
 
     @Override
-    public PayCommonResult<AlipayTradePagePayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
-        // 构建 AlipayTradePagePayModel 请求
+    public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
+        // 1.1 构建 AlipayTradePagePayModel 请求
         AlipayTradePagePayModel model = new AlipayTradePagePayModel();
-        // 构建 AlipayTradePagePayRequest
+        // ① 通用的参数
+        model.setOutTradeNo(reqDTO.getMerchantOrderId());
+        model.setSubject(reqDTO.getSubject());
+        model.setBody(reqDTO.getBody());
+        model.setTotalAmount(formatAmount(reqDTO.getAmount()));
+        model.setTimeExpire(formatTime(reqDTO.getExpireTime()));
+        model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前 PC 支付场景下仅支持 FAST_INSTANT_TRADE_PAY
+        // ② 个性化的参数
+        // 参考 https://www.pingxx.com/api/支付渠道 extra 参数说明.html 的 alipay_pc_direct 部分
+        model.setQrPayMode(MapUtil.getStr(reqDTO.getChannelExtras(), "qr_pay_mode"));
+        model.setQrcodeWidth(MapUtil.getLong(reqDTO.getChannelExtras(), "qr_code_width"));
+        // ③ 支付宝 PC 支付有多种展示模式,因此这里需要计算
+        String displayMode = getDisplayMode(reqDTO.getDisplayMode(), model.getQrPayMode());
+
+        // 1.2 构建 AlipayTradePagePayRequest 请求
         AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
         request.setBizModel(model);
-        JSONObject bizContent = new JSONObject();
-        // 参数说明可查看: https://opendocs.alipay.com/open/028r8t?scene=22
-        bizContent.put("out_trade_no", reqDTO.getMerchantOrderId());
-        bizContent.put("total_amount", calculateAmount(reqDTO.getAmount()));
-        bizContent.put("subject", reqDTO.getSubject());
-        bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
-        // PC扫码支付的方式:支持前置模式和跳转模式。4: 订单码-可定义宽度的嵌入式二维码
-        bizContent.put("qr_pay_mode", "4");
-        // 自定义二维码宽度
-        bizContent.put("qrcode_width", "150");
-        request.setBizContent(bizContent.toJSONString());
         request.setNotifyUrl(reqDTO.getNotifyUrl());
-        request.setReturnUrl("");
-        // 执行请求
+        request.setReturnUrl(reqDTO.getReturnUrl());
+
+        // 2.1 执行请求
         AlipayTradePagePayResponse response;
-        try {
-            response = client.pageExecute(request);
-        } catch (AlipayApiException e) {
-            log.error("[unifiedOrder][request({}) 发起支付失败]", JsonUtils.toJsonString(reqDTO), e);
-            return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
+        if (Objects.equals(displayMode, PayDisplayModeEnum.FORM.getMode())) {
+            response = client.pageExecute(request, Method.POST.name()); // 需要特殊使用 POST 请求
+        } else {
+            response = client.pageExecute(request, Method.GET.name());
         }
-        // 响应为表单格式,前端可嵌入响应的页面或关闭当前支付窗口
-        return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000") ,response.getMsg(), response, codeMapping);
+
+        // 2.2 处理结果
+        validateSuccess(response);
+        return new PayOrderUnifiedRespDTO().setDisplayMode(displayMode)
+                .setDisplayContent(response.getBody());
     }
+
+    /**
+     * 获得最终的支付 UI 展示模式
+     *
+     * @param displayMode 前端传递的 UI 展示模式
+     * @param qrPayMode 前端传递的二维码模式
+     * @return 最终的支付 UI 展示模式
+     */
+    private String getDisplayMode(String displayMode, String qrPayMode) {
+        // 1.1 支付宝二维码的前置模式
+        if (StrUtil.equalsAny(qrPayMode, "0", "1", "3", "4")) {
+            return PayDisplayModeEnum.IFRAME.getMode();
+        }
+        // 1.2 支付宝二维码的跳转模式
+        if (StrUtil.equals(qrPayMode, "2")) {
+            return PayDisplayModeEnum.URL.getMode();
+        }
+        // 2. 前端传递了 UI 展示模式
+        return displayMode != null ? displayMode :
+                PayDisplayModeEnum.URL.getMode(); // 模式使用 URL 跳转
+    }
+
 }

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

@@ -1,19 +1,19 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 
-import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
 import com.alipay.api.AlipayApiException;
 import com.alipay.api.domain.AlipayTradePrecreateModel;
 import com.alipay.api.request.AlipayTradePrecreateRequest;
 import com.alipay.api.response.AlipayTradePrecreateResponse;
 import lombok.extern.slf4j.Slf4j;
 
-import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
-
 /**
  * 支付宝【扫码支付】的 PayClient 实现类
- * 文档:https://opendocs.alipay.com/apis/02890k
+ *
+ * 文档:<a href="https://opendocs.alipay.com/apis/02890k">扫码支付</a>
  *
  * @author 芋道源码
  */
@@ -21,32 +21,34 @@ import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString
 public class AlipayQrPayClient extends AbstractAlipayClient {
 
     public AlipayQrPayClient(Long channelId, AlipayPayClientConfig config) {
-        super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config, new AlipayPayCodeMapping());
+        super(channelId, PayChannelEnum.ALIPAY_QR.getCode(), config);
     }
 
     @Override
-    public PayCommonResult<AlipayTradePrecreateResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
-        // 构建 AlipayTradePrecreateModel 请求
+    public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
+        // 1.1 构建 AlipayTradePrecreateModel 请求
         AlipayTradePrecreateModel model = new AlipayTradePrecreateModel();
+        // ① 通用的参数
         model.setOutTradeNo(reqDTO.getMerchantOrderId());
         model.setSubject(reqDTO.getSubject());
         model.setBody(reqDTO.getBody());
-        model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString()); // 单位:元
-        // TODO 芋艿:userIp + expireTime
-        // 构建 AlipayTradePrecreateRequest
+        model.setTotalAmount(formatAmount(reqDTO.getAmount()));
+        model.setProductCode("FACE_TO_FACE_PAYMENT"); // 销售产品码. 目前扫码支付场景下仅支持 FACE_TO_FACE_PAYMENT
+        // ② 个性化的参数【无】
+        // ③ 支付宝扫码支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开
+        String displayMode = PayDisplayModeEnum.QR_CODE.getMode();
+
+        // 1.2 构建 AlipayTradePrecreateRequest 请求
         AlipayTradePrecreateRequest request = new AlipayTradePrecreateRequest();
         request.setBizModel(model);
         request.setNotifyUrl(reqDTO.getNotifyUrl());
         request.setReturnUrl(reqDTO.getReturnUrl());
-        // 执行请求
-        AlipayTradePrecreateResponse response;
-        try {
-            response = client.execute(request);
-        } catch (AlipayApiException e) {
-            log.error("[unifiedOrder][request({}) 发起支付失败]", toJsonString(reqDTO), e);
-            return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
-        }
-        // TODO 芋艿:sub Code 需要测试下各种失败的情况
-        return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
+
+        // 2.1 执行请求
+        AlipayTradePrecreateResponse response = client.execute(request);
+        // 2.2 处理结果
+        validateSuccess(response);
+        return new PayOrderUnifiedRespDTO()
+                .setDisplayMode(displayMode).setDisplayContent(response.getQrCode());
     }
 }

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

@@ -1,75 +1,60 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 
-import cn.hutool.core.date.DateUtil;
-import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
+import cn.hutool.core.util.ObjectUtil;
+import cn.hutool.http.Method;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
+import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
 import com.alipay.api.AlipayApiException;
 import com.alipay.api.domain.AlipayTradeWapPayModel;
 import com.alipay.api.request.AlipayTradeWapPayRequest;
 import com.alipay.api.response.AlipayTradeWapPayResponse;
 import lombok.extern.slf4j.Slf4j;
 
-import java.util.Objects;
-
 /**
- * 支付宝【手机网站】的 PayClient 实现类
- * 文档:https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
+ * 支付宝【Wap 网站】的 PayClient 实现类
+ *
+ * 文档:<a href="https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay">手机网站支付接口</a>
  *
  * @author 芋道源码
  */
 @Slf4j
 public class AlipayWapPayClient extends AbstractAlipayClient {
 
-
     public AlipayWapPayClient(Long channelId, AlipayPayClientConfig config) {
-        super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config, new AlipayPayCodeMapping());
+        super(channelId, PayChannelEnum.ALIPAY_WAP.getCode(), config);
     }
 
     @Override
-    public PayCommonResult<AlipayTradeWapPayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
-        // 构建 AlipayTradeWapPayModel 请求
+    public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) throws AlipayApiException {
+        // 1.1 构建 AlipayTradeWapPayModel 请求
         AlipayTradeWapPayModel model = new AlipayTradeWapPayModel();
+        // ① 通用的参数
         model.setOutTradeNo(reqDTO.getMerchantOrderId());
         model.setSubject(reqDTO.getSubject());
         model.setBody(reqDTO.getBody());
-        model.setTotalAmount(calculateAmount(reqDTO.getAmount()).toString());
-        model.setProductCode("QUICK_WAP_PAY"); // TODO 芋艿:这里咋整
-        //TODO 芋艿:这里咋整  jason @芋艿 可以去掉吧,
-        // TODO 芋艿 似乎这里不用传sellerId
-        // https://opendocs.alipay.com/apis/api_1/alipay.trade.wap.pay
-        //model.setSellerId("2088102147948060");
-        model.setTimeExpire(DateUtil.format(reqDTO.getExpireTime(),"yyyy-MM-dd HH:mm:ss"));
-        // TODO 芋艿:userIp
-        // 构建 AlipayTradeWapPayRequest
+        model.setTotalAmount(formatAmount(reqDTO.getAmount()));
+        model.setProductCode("QUICK_WAP_PAY"); // 销售产品码. 目前 Wap 支付场景下仅支持 QUICK_WAP_PAY
+        // ② 个性化的参数【无】
+        // ③ 支付宝 Wap 支付只有一种展示,考虑到前端可能希望二维码扫描后,手机打开
+        String displayMode = ObjectUtil.defaultIfNull(reqDTO.getDisplayMode(),
+                PayDisplayModeEnum.URL.getMode());
+
+        // 1.2 构建 AlipayTradeWapPayRequest 请求
         AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
         request.setBizModel(model);
         request.setNotifyUrl(reqDTO.getNotifyUrl());
         request.setReturnUrl(reqDTO.getReturnUrl());
+        model.setQuitUrl(reqDTO.getReturnUrl());
 
-        // 执行请求
-        AlipayTradeWapPayResponse response;
-        try {
-            response = client.pageExecute(request);
-        } catch (AlipayApiException e) {
-            return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
-        }
+        // 2.1 执行请求
+        AlipayTradeWapPayResponse response = client.pageExecute(request, Method.GET.name());
 
-        // TODO 芋艿:sub Code
-        if(response.isSuccess() && Objects.isNull(response.getCode()) && Objects.nonNull(response.getBody())){
-            //成功alipay wap 成功 code 为 null , body 为form 表单
-            return PayCommonResult.build("-9999", "Success", response, codeMapping);
-        }else {
-            return PayCommonResult.build(response.getCode(), response.getMsg(), response, codeMapping);
-        }
+        // 2.2 处理结果
+        validateSuccess(response);
+        return new PayOrderUnifiedRespDTO()
+                .setDisplayMode(displayMode).setDisplayContent(response.getBody());
     }
 
-
-
-
-
-
-
-
-
 }

+ 0 - 56
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXCodeMapping.java

@@ -1,56 +0,0 @@
-package cn.iocoder.yudao.framework.pay.core.client.impl.wx;
-
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.exception.ErrorCode;
-import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
-import cn.iocoder.yudao.framework.pay.core.client.AbstractPayCodeMapping;
-
-import java.util.Objects;
-
-import static cn.iocoder.yudao.framework.pay.core.enums.PayFrameworkErrorCodeConstants.*;
-
-/**
- * 微信支付 PayCodeMapping 实现类
- *
- * @author 芋道源码
- */
-public class WXCodeMapping extends AbstractPayCodeMapping {
-
-    /**
-     * 错误码 - 成功
-     * 由于 weixin-java-pay 封装的 Result 未返回 code,所以自己定义下
-     */
-    public static final String CODE_SUCCESS = "SUCCESS";
-    /**
-     * 错误提示 - 成功
-     */
-    public static final String MESSAGE_SUCCESS = "成功";
-
-    @Override
-    protected ErrorCode apply0(String apiCode, String apiMsg) {
-        if (Objects.equals(apiCode, CODE_SUCCESS)) {
-            return GlobalErrorCodeConstants.SUCCESS;
-        }
-        if (Objects.equals(apiCode, "FAIL")) {
-            if (Objects.equals(apiMsg, "AppID不存在,请检查后再试")) {
-                return PAY_CONFIG_APP_ID_ERROR;
-            }
-            if (Objects.equals(apiMsg, "签名错误,请检查后再试")
-                || Objects.equals(apiMsg, "签名错误")) {
-                return PAY_CONFIG_SIGN_ERROR;
-            }
-        }
-        if (Objects.equals(apiCode, "PARAM_ERROR")) {
-            if (Objects.equals(apiMsg, "无效的openid")) {
-                return PAY_OPENID_ERROR;
-            }
-        }
-        if (Objects.equals(apiCode, "CustomErrorCode")) {
-            if (StrUtil.contains(apiMsg, "必填字段")) {
-                return PAY_PARAM_MISSING;
-            }
-        }
-        return null;
-    }
-
-}

+ 36 - 45
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXLitePayClient.java

@@ -7,9 +7,13 @@ import cn.hutool.core.lang.Assert;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-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.notify.PayNotifyReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
@@ -30,11 +34,6 @@ import java.time.ZoneId;
 import java.util.Date;
 import java.util.Objects;
 
-import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
-import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
-import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
-
-
 /**
  * 微信小程序下支付
  *
@@ -46,7 +45,7 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
     private WxPayService client;
 
     public WXLitePayClient(Long channelId, WXPayClientConfig config) {
-        super(channelId, PayChannelEnum.WX_LITE.getCode(), config, new WXCodeMapping());
+        super(channelId, PayChannelEnum.WX_LITE.getCode(), config);
     }
 
     @Override
@@ -71,28 +70,29 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
     }
 
     @Override
-    public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
-        WxPayMpOrderResult response;
-        try {
-            switch (config.getApiVersion()) {
-                case WXPayClientConfig.API_VERSION_V2:
-                    response = this.unifiedOrderV2(reqDTO);
-                    break;
-                case WXPayClientConfig.API_VERSION_V3:
-                    WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
-                    // 将 V3 的结果,统一转换成 V2。返回的字段是一致的
-                    response = new WxPayMpOrderResult();
-                    BeanUtil.copyProperties(responseV3, response, true);
-                    break;
-                default:
-                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
-            }
-        } catch (WxPayException e) {
-            log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
-            return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
-                    ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
-        }
-        return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
+    public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+        throw new UnsupportedOperationException();
+//        WxPayMpOrderResult response;
+//        try {
+//            switch (config.getApiVersion()) {
+//                case WXPayClientConfig.API_VERSION_V2:
+//                    response = this.unifiedOrderV2(reqDTO);
+//                    break;
+//                case WXPayClientConfig.API_VERSION_V3:
+//                    WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
+//                    // 将 V3 的结果,统一转换成 V2。返回的字段是一致的
+//                    response = new WxPayMpOrderResult();
+//                    BeanUtil.copyProperties(responseV3, response, true);
+//                    break;
+//                default:
+//                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
+//            }
+//        } catch (WxPayException e) {
+//            log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
+//            return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
+//                    ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
+//        }
+//        return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
     }
 
     private WxPayMpOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
@@ -145,8 +145,8 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
      * @return 支付回调对象
      * @throws WxPayException 微信异常类
      */
-    @Override
-    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
+//    @Override
+    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
         log.info("[parseOrderNotify][微信支付回调data数据:{}]", data.getBody());
         // 微信支付 v2 回调结果处理
         switch (config.getApiVersion()) {
@@ -159,7 +159,7 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
         }
     }
 
-    private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
+    private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException {
         WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
         WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
         // 转换结果
@@ -172,11 +172,10 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
                 .channelOrderNo(result.getTransactionId())
                 .channelUserId(result.getPayer().getOpenid())
                 .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
-                .data(data.getBody())
                 .build();
     }
 
-    private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
+    private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
         WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
         Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
         // 转换结果
@@ -186,20 +185,12 @@ public class WXLitePayClient extends AbstractPayClient<WXPayClientConfig> {
                 .channelOrderNo(notifyResult.getTransactionId())
                 .channelUserId(notifyResult.getOpenid())
                 .successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
-                .data(data.getBody())
                 .build();
 
     }
 
     @Override
-    public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
-        //TODO 需要实现
-        throw new UnsupportedOperationException("需要实现");
-    }
-
-
-    @Override
-    protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
+    protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  {
         //TODO 需要实现
         throw new UnsupportedOperationException();
     }

+ 35 - 43
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXNativePayClient.java

@@ -6,9 +6,13 @@ import cn.hutool.core.date.LocalDateTimeUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-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.notify.PayNotifyReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
@@ -28,10 +32,6 @@ import java.time.ZoneId;
 import java.util.Date;
 import java.util.Objects;
 
-import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
-import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
-import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
-
 /**
  * 微信 App 支付
  *
@@ -43,7 +43,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
     private WxPayService client;
 
     public WXNativePayClient(Long channelId, WXPayClientConfig config) {
-        super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config, new WXCodeMapping());
+        super(channelId, PayChannelEnum.WX_NATIVE.getCode(), config);
     }
 
     @Override
@@ -68,27 +68,28 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
     }
 
     @Override
-    public PayCommonResult<String> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
-        // 这里原生的返回的是支付的 url 所以直接使用string接收
-        // "invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
-        String responseV3;
-        try {
-            switch (config.getApiVersion()) {
-                case WXPayClientConfig.API_VERSION_V2:
-                    responseV3 = unifiedOrderV2(reqDTO).getCodeUrl();
-                    break;
-                case WXPayClientConfig.API_VERSION_V3:
-                  responseV3 = this.unifiedOrderV3(reqDTO);
-                    break;
-                default:
-                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
-            }
-        } catch (WxPayException e) {
-            log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
-            return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
-                    ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
-        }
-        return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, responseV3, codeMapping);
+    public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+        throw new UnsupportedOperationException();
+//        // 这里原生的返回的是支付的 url 所以直接使用string接收
+//        // "invokeResponse": "weixin://wxpay/bizpayurl?pr=EGYAem7zz"
+//        String responseV3;
+//        try {
+//            switch (config.getApiVersion()) {
+//                case WXPayClientConfig.API_VERSION_V2:
+//                    responseV3 = unifiedOrderV2(reqDTO).getCodeUrl();
+//                    break;
+//                case WXPayClientConfig.API_VERSION_V3:
+//                  responseV3 = this.unifiedOrderV3(reqDTO);
+//                    break;
+//                default:
+//                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
+//            }
+//        } catch (WxPayException e) {
+//            log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
+//            return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
+//                    ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()), null, codeMapping);
+//        }
+//        return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, responseV3, codeMapping);
     }
 
     private WxPayNativeOrderResult unifiedOrderV2(PayOrderUnifiedReqDTO reqDTO) throws WxPayException {
@@ -129,8 +130,8 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
      * @return 支付回调对象
      * @throws WxPayException 微信异常类
      */
-    @Override
-    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
+//    @Override
+    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
         log.info("微信支付回调data数据:{}", data.getBody());
         // 微信支付 v2 回调结果处理
         switch (config.getApiVersion()) {
@@ -143,7 +144,7 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
         }
     }
 
-    private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
+    private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException {
         WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
         WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
         // 转换结果
@@ -154,11 +155,10 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
                 .orderExtensionNo(result.getOutTradeNo())
                 .channelOrderNo(result.getTradeState())
                 .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
-                .data(data.getBody())
                 .build();
     }
 
-    private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
+    private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
         WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
         Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
         // 转换结果
@@ -168,20 +168,12 @@ public class WXNativePayClient extends AbstractPayClient<WXPayClientConfig> {
                 .channelOrderNo(notifyResult.getTransactionId())
                 .channelUserId(notifyResult.getOpenid())
                 .successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
-                .data(data.getBody())
                 .build();
 
     }
 
     @Override
-    public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
-        // TODO 需要实现
-        throw new UnsupportedOperationException("需要实现");
-    }
-
-
-    @Override
-    protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
+    protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO)  {
         // TODO 需要实现
         throw new UnsupportedOperationException();
     }

+ 36 - 43
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/wx/WXPubPayClient.java

@@ -7,9 +7,12 @@ import cn.hutool.core.lang.Assert;
 import cn.hutool.core.map.MapUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.util.io.FileUtils;
-import cn.iocoder.yudao.framework.common.util.object.ObjectUtils;
-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.notify.PayNotifyReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedRespDTO;
 import cn.iocoder.yudao.framework.pay.core.client.impl.AbstractPayClient;
 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 import com.github.binarywang.wxpay.bean.notify.WxPayOrderNotifyResult;
@@ -30,10 +33,6 @@ import java.time.LocalDateTime;
 import java.time.ZoneId;
 import java.util.Objects;
 
-import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
-import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.CODE_SUCCESS;
-import static cn.iocoder.yudao.framework.pay.core.client.impl.wx.WXCodeMapping.MESSAGE_SUCCESS;
-
 /**
  * 微信支付(公众号)的 PayClient 实现类
  *
@@ -45,7 +44,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
     private WxPayService client;
 
     public WXPubPayClient(Long channelId, WXPayClientConfig config) {
-        super(channelId, PayChannelEnum.WX_PUB.getCode(), config, new WXCodeMapping());
+        super(channelId, PayChannelEnum.WX_PUB.getCode(), config);
     }
 
     @Override
@@ -70,28 +69,30 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
     }
 
     @Override
-    public PayCommonResult<WxPayMpOrderResult> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
-        WxPayMpOrderResult response;
-        try {
-            switch (config.getApiVersion()) {
-                case WXPayClientConfig.API_VERSION_V2:
-                    response = this.unifiedOrderV2(reqDTO);
-                    break;
-                case WXPayClientConfig.API_VERSION_V3:
-                    WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
-                    // 将 V3 的结果,统一转换成 V2。返回的字段是一致的
-                    response = new WxPayMpOrderResult();
-                    BeanUtil.copyProperties(responseV3, response, true);
-                    break;
-                default:
-                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
-            }
-        } catch (WxPayException e) {
-            log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
-            return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
-                    ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping);
-        }
-        return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
+    public PayOrderUnifiedRespDTO doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+        throw new UnsupportedOperationException();
+//
+//        WxPayMpOrderResult response;
+//        try {
+//            switch (config.getApiVersion()) {
+//                case WXPayClientConfig.API_VERSION_V2:
+//                    response = this.unifiedOrderV2(reqDTO);
+//                    break;
+//                case WXPayClientConfig.API_VERSION_V3:
+//                    WxPayUnifiedOrderV3Result.JsapiResult responseV3 = this.unifiedOrderV3(reqDTO);
+//                    // 将 V3 的结果,统一转换成 V2。返回的字段是一致的
+//                    response = new WxPayMpOrderResult();
+//                    BeanUtil.copyProperties(responseV3, response, true);
+//                    break;
+//                default:
+//                    throw new IllegalArgumentException(String.format("未知的 API 版本(%s)", config.getApiVersion()));
+//            }
+//        } catch (WxPayException e) {
+//            log.error("[unifiedOrder][request({}) 发起支付失败,原因({})]", toJsonString(reqDTO), e);
+//            return PayCommonResult.build(ObjectUtils.defaultIfNull(e.getErrCode(), e.getReturnCode(), "CustomErrorCode"),
+//                    ObjectUtils.defaultIfNull(e.getErrCodeDes(), e.getCustomErrorMsg()),null, codeMapping);
+//        }
+//        return PayCommonResult.build(CODE_SUCCESS, MESSAGE_SUCCESS, response, codeMapping);
     }
 
 
@@ -140,8 +141,8 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
      * @return 支付回调对象
      * @throws WxPayException 微信异常类
      */
-    @Override
-    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyDataDTO data) throws WxPayException {
+//    @Override
+    public PayOrderNotifyRespDTO parseOrderNotify(PayNotifyReqDTO data) throws WxPayException {
         log.info("[parseOrderNotify][微信支付回调data数据: {}]", data.getBody());
         // 微信支付 v2 回调结果处理
         switch (config.getApiVersion()) {
@@ -154,7 +155,7 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
         }
     }
 
-    private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyDataDTO data) throws WxPayException {
+    private PayOrderNotifyRespDTO parseOrderNotifyV3(PayNotifyReqDTO data) throws WxPayException {
         WxPayOrderNotifyV3Result wxPayOrderNotifyV3Result = client.parseOrderNotifyV3Result(data.getBody(), null);
         WxPayOrderNotifyV3Result.DecryptNotifyResult result = wxPayOrderNotifyV3Result.getResult();
         // 转换结果
@@ -165,11 +166,10 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
                 .orderExtensionNo(result.getOutTradeNo())
                 .channelOrderNo(result.getTradeState())
                 .successTime(LocalDateTimeUtil.parse(result.getSuccessTime(), "yyyy-MM-dd'T'HH:mm:ssXXX"))
-                .data(data.getBody())
                 .build();
     }
 
-    private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyDataDTO data) throws WxPayException {
+    private PayOrderNotifyRespDTO parseOrderNotifyV2(PayNotifyReqDTO data) throws WxPayException {
         WxPayOrderNotifyResult notifyResult = client.parseOrderNotifyResult(data.getBody());
         Assert.isTrue(Objects.equals(notifyResult.getResultCode(), "SUCCESS"), "支付结果非 SUCCESS");
         // 转换结果
@@ -179,19 +179,12 @@ public class WXPubPayClient extends AbstractPayClient<WXPayClientConfig> {
                 .channelOrderNo(notifyResult.getTransactionId())
                 .channelUserId(notifyResult.getOpenid())
                 .successTime(LocalDateTimeUtil.parse(notifyResult.getTimeEnd(), "yyyyMMddHHmmss"))
-                .data(data.getBody())
                 .build();
 
     }
 
     @Override
-    public PayRefundNotifyDTO parseRefundNotify(PayNotifyDataDTO notifyData) {
-        // TODO 需要实现
-        throw new UnsupportedOperationException("需要实现");
-    }
-
-    @Override
-    protected PayCommonResult<PayRefundUnifiedRespDTO> doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
+    protected PayRefundUnifiedRespDTO doUnifiedRefund(PayRefundUnifiedReqDTO reqDTO) throws Throwable {
         // TODO 需要实现
         throw new UnsupportedOperationException();
     }

+ 2 - 1
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayChannelEnum.java

@@ -25,7 +25,8 @@ public enum PayChannelEnum {
     ALIPAY_PC("alipay_pc", "支付宝 PC 网站支付", AlipayPayClientConfig.class),
     ALIPAY_WAP("alipay_wap", "支付宝 Wap 网站支付", AlipayPayClientConfig.class),
     ALIPAY_APP("alipay_app", "支付宝App 支付", AlipayPayClientConfig.class),
-    ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class);
+    ALIPAY_QR("alipay_qr", "支付宝扫码支付", AlipayPayClientConfig.class),
+    ALIPAY_BAR("alipay_bar", "支付宝条码支付", AlipayPayClientConfig.class);
 
     /**
      * 编码

+ 29 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayDisplayModeEnum.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.framework.pay.core.enums;
+
+import lombok.AllArgsConstructor;
+import lombok.Getter;
+
+/**
+ * 支付 UI 展示模式
+ *
+ * @author 芋道源码
+ */
+@Getter
+@AllArgsConstructor
+public enum PayDisplayModeEnum {
+
+    URL("url"), // Redirect 跳转链接的方式
+    IFRAME("iframe"), // IFrame 内嵌链接的方式
+    FORM("form"), // HTML 表单提交
+    QR_CODE("qr_code"), // 二维码的文字内容
+    QR_CODE_URL("qr_code_url"), // 二维码的图片链接
+    BAR_CODE("bar_code"), // 条形码
+    APP("app"), // 应用
+    ;
+
+    /**
+     * 展示模式
+     */
+    private final String mode;
+
+}

+ 4 - 2
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayFrameworkErrorCodeConstants.java

@@ -5,7 +5,7 @@ import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 /**
  * 支付框架的错误码枚举
  *
- * 短信框架,使用 2-002-000-000 段
+ * 支付框架,使用 2-002-000-000 段
  *
  * @author 芋道源码
  */
@@ -14,14 +14,16 @@ public interface PayFrameworkErrorCodeConstants {
     ErrorCode PAY_UNKNOWN = new ErrorCode(2002000000, "未知错误,需要解析");
 
     // ========== 配置相关相关 2002000100 ==========
+    // todo 芋艿:如下的错误码,怎么处理掉
     ErrorCode PAY_CONFIG_APP_ID_ERROR = new ErrorCode(2002000100, "支付渠道 AppId 不正确");
     ErrorCode PAY_CONFIG_SIGN_ERROR = new ErrorCode(2002000100, "签名错误"); // 例如说,微信支付,配置错了 mchId 或者 mchKey
 
 
     // ========== 其它相关 2002000900 开头 ==========
+    // todo 芋艿:如下的错误码,怎么处理掉
     ErrorCode PAY_OPENID_ERROR = new ErrorCode(2002000900, "无效的 openid"); // 例如说,微信 openid 未授权过
     ErrorCode PAY_PARAM_MISSING = new ErrorCode(2002000901, "请求参数缺失"); // 例如说,支付少传了金额
 
-    ErrorCode EXCEPTION = new ErrorCode(2002000999, "调用异常");
+    ErrorCode PAY_EXCEPTION = new ErrorCode(2002000999, "调用异常");
 
 }

+ 2 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayNotifyRefundStatusEnum.java

@@ -6,6 +6,7 @@ package cn.iocoder.yudao.framework.pay.core.enums;
  * @author  jason
  */
 public enum PayNotifyRefundStatusEnum {
+
     /**
      * 支付宝 中 全额退款 trade_status=TRADE_CLOSED, 部分退款 trade_status=TRADE_SUCCESS
      * 退款成功
@@ -17,4 +18,5 @@ public enum PayNotifyRefundStatusEnum {
      * 退款异常
      */
     ABNORMAL;
+
 }

+ 10 - 10
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/PayClientFactoryImplIntegrationTest.java

@@ -5,7 +5,7 @@ import cn.hutool.core.util.RandomUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayPayClientConfig;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayQrPayClient;
 import cn.iocoder.yudao.framework.pay.core.client.impl.alipay.AlipayWapPayClient;
@@ -46,8 +46,8 @@ public class PayClientFactoryImplIntegrationTest {
         PayClient client = payClientFactory.getPayClient(channelId);
         // 发起支付
         PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
-        CommonResult<?> result = client.unifiedOrder(reqDTO);
-        System.out.println(result);
+//        CommonResult<?> result = client.unifiedOrder(reqDTO);
+//        System.out.println(result);
     }
 
     /**
@@ -69,8 +69,8 @@ public class PayClientFactoryImplIntegrationTest {
         PayClient client = payClientFactory.getPayClient(channelId);
         // 发起支付
         PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
-        CommonResult<?> result = client.unifiedOrder(reqDTO);
-        System.out.println(result);
+//        CommonResult<?> result = client.unifiedOrder(reqDTO);
+//        System.out.println(result);
     }
 
     /**
@@ -93,9 +93,9 @@ public class PayClientFactoryImplIntegrationTest {
         // 发起支付
         PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
         reqDTO.setNotifyUrl("http://yunai.natapp1.cc/admin-api/pay/notify/callback/18"); // TODO @tina: 这里改成你的 natapp 回调地址
-        CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO);
-        System.out.println(JsonUtils.toJsonString(result));
-        System.out.println(result.getData().getQrCode());
+//        CommonResult<AlipayTradePrecreateResponse> result = (CommonResult<AlipayTradePrecreateResponse>) client.unifiedOrder(reqDTO);
+//        System.out.println(JsonUtils.toJsonString(result));
+//        System.out.println(result.getData().getQrCode());
     }
 
     /**
@@ -116,8 +116,8 @@ public class PayClientFactoryImplIntegrationTest {
         PayClient client = payClientFactory.getPayClient(channelId);
         // 发起支付
         PayOrderUnifiedReqDTO reqDTO = buildPayOrderUnifiedReqDTO();
-        CommonResult<?> result = client.unifiedOrder(reqDTO);
-        System.out.println(JsonUtils.toJsonString(result));
+//        CommonResult<?> result = client.unifiedOrder(reqDTO);
+//        System.out.println(JsonUtils.toJsonString(result));
     }
 
     private static PayOrderUnifiedReqDTO buildPayOrderUnifiedReqDTO() {

+ 9 - 9
yudao-framework/yudao-spring-boot-starter-biz-pay/src/test/java/cn.iocoder.yudao.framework.pay.core.client.impl/alipay/AlipayQrPayClientTest.java

@@ -1,8 +1,8 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 import cn.hutool.core.util.ReflectUtil;
 import cn.iocoder.yudao.framework.common.exception.enums.GlobalErrorCodeConstants;
-import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
 import cn.iocoder.yudao.framework.test.core.ut.BaseMockitoUnitTest;
 import com.alipay.api.AlipayApiException;
 import com.alipay.api.DefaultAlipayClient;
@@ -87,13 +87,13 @@ public class AlipayQrPayClientTest extends BaseMockitoUnitTest {
         }))).thenReturn(response);
 
 
-        PayCommonResult<AlipayTradePrecreateResponse> result = client.doUnifiedOrder(reqDTO);
-        // 断言
-        assertEquals(response.getCode(), result.getApiCode());
-        assertEquals(response.getMsg(), result.getApiMsg());
-        // TODO @tina:这个断言木有过?
-        assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
-        assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
+//        PayCommonResult<PayOrderUnifiedRespDTO> result = client.doUnifiedOrder(reqDTO);
+//        // 断言
+//        assertEquals(response.getCode(), result.getApiCode());
+//        assertEquals(response.getMsg(), result.getApiMsg());
+//        // TODO @tina:这个断言木有过?
+//        assertEquals(GlobalErrorCodeConstants.SUCCESS.getCode(), result.getCode());
+//        assertEquals(GlobalErrorCodeConstants.SUCCESS.getMsg(), result.getMsg());
 
     }
 }

+ 1 - 8
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/notify/dto/PayRefundNotifyReqDTO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pay.api.notify.dto;
 
+import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
 import lombok.Data;
@@ -31,12 +32,4 @@ public class PayRefundNotifyReqDTO {
     @NotNull(message = "支付退款编号不能为空")
     private Long payRefundId;
 
-    /**
-     * 退款状态
-     *
-     * (成功,失败) TODO 芋艿:枚举
-     */
-    @NotNull(message = "退款状态不能为空")
-    private Integer status;
-
 }

+ 6 - 6
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundCreateReqDTO.java

@@ -29,12 +29,6 @@ public class PayRefundCreateReqDTO {
     // ========== 商户相关字段 ==========
 
     /**
-     * 商户订单编号
-     */
-    @NotEmpty(message = "商户订单编号不能为空")
-    private String merchantOrderId;
-
-    /**
      * 退款描述
      */
     @NotEmpty(message = "退款描述不能为空")
@@ -44,6 +38,12 @@ public class PayRefundCreateReqDTO {
     // ========== 订单相关字段 ==========
 
     /**
+     * 支付单号
+     */
+    @NotNull(message = "支付单号不能为空")
+    private Long payOrderId;
+
+    /**
      * 退款金额,单位:分
      */
     @NotNull(message = "退款金额不能为空")

+ 9 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/api/refund/dto/PayRefundRespDTO.java

@@ -27,8 +27,16 @@ public class PayRefundRespDTO {
      * 枚举 {@link PayRefundStatusEnum}
      */
     private Integer status;
+    /**
+     * 退款金额,单位:分
+     */
+    private Integer refundAmount;
 
-    // ========== 渠道相关字段 ==========
+    // ========== 商户相关字段 ==========
+    /**
+     * 商户订单编号
+     */
+    private String merchantOrderId;
     /**
      * 退款成功时间
      */

+ 14 - 1
yudao-module-pay/yudao-module-pay-api/src/main/java/cn/iocoder/yudao/module/pay/enums/ErrorCodeConstants.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pay.enums;
 
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.exception.ErrorCode;
 
 /**
@@ -50,11 +51,23 @@ public interface ErrorCodeConstants {
     ErrorCode PAY_REFUND_SUCCEED = new ErrorCode(1007006003, "已经退款成功");
     ErrorCode PAY_REFUND_NOT_FOUND = new ErrorCode(1007006004, "支付退款单不存在");
 
-
     /**
      * ========== 支付商户信息 1-007-004-000 ==========
      */
     ErrorCode PAY_MERCHANT_NOT_EXISTS = new ErrorCode(1007004000, "支付商户信息不存在");
     ErrorCode PAY_MERCHANT_EXIST_APP_CANT_DELETE = new ErrorCode(1007004001, "支付商户存在支付应用,无法删除");
 
+    // ========== 示例订单 1-007-900-000 ==========
+    ErrorCode PAY_DEMO_ORDER_NOT_FOUND = new ErrorCode(100790000, "示例订单不存在");
+    ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID = new ErrorCode(100790001, "示例订单更新支付状态失败,订单不是【未支付】状态");
+    ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR = new ErrorCode(100790002, "示例订单更新支付状态失败,支付单编号不匹配");
+    ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS = new ErrorCode(100790003, "示例订单更新支付状态失败,支付单状态不是【支付成功】状态");
+    ErrorCode PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH = new ErrorCode(100790004, "示例订单更新支付状态失败,支付单金额不匹配");
+    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_NOT_PAID = new ErrorCode(100790005, "发起退款失败,示例订单未支付");
+    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUNDED = new ErrorCode(100790006, "发起退款失败,示例订单已退款");
+    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND = new ErrorCode(100790007, "发起退款失败,退款订单不存在");
+    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS = new ErrorCode(100790008, "发起退款失败,退款订单未退款成功");
+    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR = new ErrorCode(100790008, "发起退款失败,退款单编号不匹配");
+    ErrorCode PAY_DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH = new ErrorCode(100790004, "发起退款失败,退款单金额不匹配");
+
 }

+ 7 - 2
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/api/refund/PayRefundApiImpl.java

@@ -2,9 +2,12 @@ package cn.iocoder.yudao.module.pay.api.refund;
 
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
+import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
+import javax.annotation.Resource;
+
 /**
  * 退款单 API 实现类
  *
@@ -14,10 +17,12 @@ import org.springframework.validation.annotation.Validated;
 @Validated
 public class PayRefundApiImpl implements PayRefundApi {
 
+    @Resource
+    private PayRefundService payRefundService;
+
     @Override
     public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
-        // TODO 芋艿:暂未实现
-        return null;
+        return payRefundService.createPayRefund(reqDTO);
     }
 
     @Override

+ 34 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/PayDemoOrderController.java

@@ -3,20 +3,26 @@ package cn.iocoder.yudao.module.pay.controller.admin.demo;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
+import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderRespVO;
 import cn.iocoder.yudao.module.pay.convert.demo.PayDemoOrderConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
 import cn.iocoder.yudao.module.pay.service.demo.PayDemoOrderService;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
+import javax.annotation.security.PermitAll;
 import javax.validation.Valid;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
 import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
 
 @Tag(name = "管理后台 - 示例订单")
@@ -41,4 +47,32 @@ public class PayDemoOrderController {
         return success(PayDemoOrderConvert.INSTANCE.convertPage(pageResult));
     }
 
+    @PostMapping("/update-paid")
+    @Operation(summary = "更新示例订单为已支付") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
+    @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
+    @OperateLog(enable = false) // 禁用操作日志,因为没有操作人
+    public CommonResult<Boolean> updateDemoOrderPaid(@RequestBody PayOrderNotifyReqDTO notifyReqDTO) {
+        payDemoOrderService.updateDemoOrderPaid(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
+                notifyReqDTO.getPayOrderId());
+        return success(true);
+    }
+
+    @PutMapping("/refund")
+    @Operation(summary = "发起示例订单的退款")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    public CommonResult<Boolean> refundDemoOrder(@RequestParam("id") Long id) {
+        payDemoOrderService.refundDemoOrder(id, getClientIP());
+        return success(true);
+    }
+
+    @PostMapping("/update-refunded")
+    @Operation(summary = "更新示例订单为已退款") // 由 pay-module 支付服务,进行回调,可见 PayNotifyJob
+    @PermitAll // 无需登录,安全由 PayDemoOrderService 内部校验实现
+    @OperateLog(enable = false) // 禁用操作日志,因为没有操作人
+    public CommonResult<Boolean> updateDemoOrderRefunded(@RequestBody PayRefundNotifyReqDTO notifyReqDTO) {
+        payDemoOrderService.updateDemoOrderRefunded(Long.valueOf(notifyReqDTO.getMerchantOrderId()),
+                notifyReqDTO.getPayRefundId());
+        return success(true);
+    }
+
 }

+ 12 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/demo/vo/PayDemoOrderRespVO.java

@@ -12,6 +12,9 @@ import java.time.LocalDateTime;
 @Data
 public class PayDemoOrderRespVO {
 
+    @Schema(description = "订单编号", required = true, example = "1024")
+    private Long id;
+
     @Schema(description = "用户编号", required = true, example = "23199")
     private Long userId;
 
@@ -33,10 +36,19 @@ public class PayDemoOrderRespVO {
     @Schema(description = "订单支付时间")
     private LocalDateTime payTime;
 
+    @Schema(description = "支付渠道", example = "alipay_qr")
+    private String payChannelCode;
+
+    @Schema(description = "支付退款编号", example = "23366")
+    private Long payRefundId;
+
     @Schema(description = "退款金额,单位:分", required = true, example = "14039")
     private Integer refundPrice;
 
     @Schema(description = "退款时间")
     private LocalDateTime refundTime;
 
+    @Schema(description = "创建时间", required = true)
+    private LocalDateTime createTime;
+
 }

+ 24 - 15
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/notify/PayNotifyController.java

@@ -3,7 +3,11 @@ package cn.iocoder.yudao.module.pay.controller.admin.notify;
 import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
 import cn.iocoder.yudao.framework.pay.core.client.PayClient;
 import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
+import cn.iocoder.yudao.module.pay.service.merchant.PayChannelService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
 import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
 import io.swagger.v3.oas.annotations.Operation;
@@ -17,6 +21,7 @@ import javax.annotation.security.PermitAll;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND;
 
 @Tag(name = "管理后台 - 支付通知")
@@ -64,28 +69,32 @@ public class PayNotifyController {
     @PermitAll
     @OperateLog(enable = false) // 回调地址,无需记录操作日志
     public String notifyCallback(@PathVariable("channelId") Long channelId,
-                                 @RequestParam Map<String, String> params,
-                                 @RequestBody String body) throws Exception {
-        // 校验支付渠道是否存在
+                                 @RequestParam(required = false) Map<String, String> params,
+                                 @RequestBody(required = false) String body) {
+        log.info("[notifyCallback][channelId({}) 回调数据({}/{})]", channelId, params, body);
+        // 1. 校验支付渠道是否存在
         PayClient payClient = payClientFactory.getPayClient(channelId);
         if (payClient == null) {
             log.error("[notifyCallback][渠道编号({}) 找不到对应的支付客户端]", channelId);
             throw exception(PAY_CHANNEL_CLIENT_NOT_FOUND);
         }
-        // 校验通知数据是否合法
-        PayNotifyDataDTO notifyData = PayNotifyDataDTO.builder().params(params).body(body).build();
-        payClient.verifyNotifyData(notifyData);
 
-        // 情况一:如果是退款,则发起退款通知
-        if (payClient.isRefundNotify(notifyData)) {
-            refundService.notifyPayRefund(channelId, PayNotifyDataDTO.builder().params(params).body(body).build());
+        // 2. 解析通知数据
+        PayNotifyReqDTO rawNotify = PayNotifyReqDTO.builder().params(params).body(body).build();
+        Object notify = payClient.parseNotify(rawNotify);
+
+        // 3. 处理通知
+        // 3.1:退款通知
+        if (notify instanceof PayRefundNotifyRespDTO) {
+            refundService.notifyPayRefund(channelId, (PayRefundNotifyRespDTO) notify, rawNotify);
             return "success";
         }
-
-        // 情况二:如果非退款,则发起支付通知
-        orderService.notifyPayOrder(channelId, PayNotifyDataDTO.builder().params(params).body(body).build());
-        return "success";
+        // 3.2:支付通知
+        if (notify instanceof PayOrderNotifyRespDTO) {
+            orderService.notifyPayOrder(channelId, (PayOrderNotifyRespDTO) notify, rawNotify);
+            return "success";
+        }
+        throw new UnsupportedOperationException("未知通知:" + toJsonString(notify));
     }
 
-
 }

+ 31 - 17
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java

@@ -2,6 +2,12 @@ package cn.iocoder.yudao.module.pay.controller.admin.order;
 
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ObjectUtil;
+import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
+import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
+import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
 import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
@@ -12,21 +18,12 @@ import cn.iocoder.yudao.module.pay.service.merchant.PayAppService;
 import cn.iocoder.yudao.module.pay.service.merchant.PayMerchantService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
-import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
-import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.annotations.Parameter;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.Parameter;
+import io.swagger.v3.oas.annotations.tags.Tag;
 import org.springframework.security.access.prepost.PreAuthorize;
 import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.GetMapping;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RequestParam;
-import org.springframework.web.bind.annotation.RestController;
+import org.springframework.web.bind.annotation.*;
 
 import javax.annotation.Resource;
 import javax.servlet.http.HttpServletResponse;
@@ -37,6 +34,7 @@ import java.util.List;
 import java.util.Map;
 
 import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
+import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
 import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.EXPORT;
 
 @Tag(name = "管理后台 - 支付订单")
@@ -46,7 +44,7 @@ import static cn.iocoder.yudao.framework.operatelog.core.enums.OperateTypeEnum.E
 public class PayOrderController {
 
     @Resource
-    private PayOrderService orderService;
+    private PayOrderService payOrderService;
     @Resource
     private PayOrderExtensionService orderExtensionService;
     @Resource
@@ -58,8 +56,17 @@ public class PayOrderController {
     @Operation(summary = "获得支付订单")
     @Parameter(name = "id", description = "编号", required = true, example = "1024")
     @PreAuthorize("@ss.hasPermission('pay:order:query')")
-    public CommonResult<PayOrderDetailsRespVO> getOrder(@RequestParam("id") Long id) {
-        PayOrderDO order = orderService.getOrder(id);
+    public CommonResult<PayOrderRespVO> getOrder(@RequestParam("id") Long id) {
+        return success(PayOrderConvert.INSTANCE.convert(payOrderService.getOrder(id)));
+    }
+
+    // TODO 芋艿:看看怎么优化下;
+    @GetMapping("/get-detail")
+    @Operation(summary = "获得支付订单详情")
+    @Parameter(name = "id", description = "编号", required = true, example = "1024")
+    @PreAuthorize("@ss.hasPermission('pay:order:query')")
+    public CommonResult<PayOrderDetailsRespVO> getOrderDetail(@RequestParam("id") Long id) {
+        PayOrderDO order = payOrderService.getOrder(id);
         if (ObjectUtil.isNull(order)) {
             return success(new PayOrderDetailsRespVO());
         }
@@ -82,11 +89,18 @@ public class PayOrderController {
         return success(respVO);
     }
 
+    @PostMapping("/submit")
+    @Operation(summary = "提交支付订单")
+    public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
+        PayOrderSubmitRespVO respVO = payOrderService.submitPayOrder(reqVO, getClientIP());
+        return success(respVO);
+    }
+
     @GetMapping("/page")
     @Operation(summary = "获得支付订单分页")
     @PreAuthorize("@ss.hasPermission('pay:order:query')")
     public CommonResult<PageResult<PayOrderPageItemRespVO>> getOrderPage(@Valid PayOrderPageReqVO pageVO) {
-        PageResult<PayOrderDO> pageResult = orderService.getOrderPage(pageVO);
+        PageResult<PayOrderDO> pageResult = payOrderService.getOrderPage(pageVO);
         if (CollectionUtil.isEmpty(pageResult.getList())) {
             return success(new PageResult<>(pageResult.getTotal()));
         }
@@ -120,7 +134,7 @@ public class PayOrderController {
     public void exportOrderExcel(@Valid PayOrderExportReqVO exportReqVO,
             HttpServletResponse response) throws IOException {
 
-        List<PayOrderDO> list = orderService.getOrderList(exportReqVO);
+        List<PayOrderDO> list = payOrderService.getOrderList(exportReqVO);
         if (CollectionUtil.isEmpty(list)) {
             ExcelUtils.write(response, "支付订单.xls", "数据",
                     PayOrderExcelVO.class, new ArrayList<>());

+ 29 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java

@@ -0,0 +1,29 @@
+package cn.iocoder.yudao.module.pay.controller.admin.order.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.Data;
+import lombok.experimental.Accessors;
+
+import javax.validation.constraints.NotEmpty;
+import javax.validation.constraints.NotNull;
+import java.awt.*;
+import java.util.Map;
+
+@Schema(description = "管理后台 - 支付订单提交 Request VO")
+@Data
+public class PayOrderSubmitReqVO {
+
+    @Schema(description = "支付单编号", required = true, example = "1024")
+    @NotNull(message = "支付单编号不能为空")
+    private Long id;
+
+    @Schema(description = "支付渠道", required = true, example = "wx_pub")
+    @NotEmpty(message = "支付渠道不能为空")
+    private String channelCode;
+
+    @Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
+    private Map<String, String> channelExtras;
+
+    @Schema(description = "展示模式", example = "url") // 参见 {@link PayDisplayModeEnum} 枚举。如果不传递,则每个支付渠道使用默认的方式
+    private String displayMode;
+}

+ 20 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java

@@ -0,0 +1,20 @@
+package cn.iocoder.yudao.module.pay.controller.admin.order.vo;
+
+import io.swagger.v3.oas.annotations.media.Schema;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Data;
+import lombok.NoArgsConstructor;
+import lombok.experimental.Accessors;
+
+@Schema(description = "管理后台 - 支付订单提交 Response VO")
+@Data
+public class PayOrderSubmitRespVO {
+
+    @Schema(description = "展示模式", required = true, example = "url") // 参见 PayDisplayModeEnum 枚举
+    private String displayMode;
+
+    @Schema(description = "展示内容", required = true)
+    private String displayContent;
+
+}

+ 5 - 18
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java

@@ -1,15 +1,13 @@
 package cn.iocoder.yudao.module.pay.controller.app.order;
 
-import cn.hutool.core.bean.BeanUtil;
 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
 import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
 import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
-import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
-import io.swagger.v3.oas.annotations.tags.Tag;
 import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.validation.annotation.Validated;
 import org.springframework.web.bind.annotation.PostMapping;
@@ -34,20 +32,9 @@ public class AppPayOrderController {
 
     @PostMapping("/submit")
     @Operation(summary = "提交支付订单")
-//    @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好
     public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
-        // 获得订单
-        PayOrderDO payOrder = orderService.getOrder(reqVO.getId());
-
-        // 提交支付
-        PayOrderSubmitReqDTO reqDTO = new PayOrderSubmitReqDTO();
-        BeanUtil.copyProperties(reqVO, reqDTO, false);
-        reqDTO.setUserIp(getClientIP());
-        reqDTO.setAppId(payOrder.getAppId());
-        PayOrderSubmitRespDTO respDTO = orderService.submitPayOrder(reqDTO);
-
-        // 拼接返回
-        return success(AppPayOrderSubmitRespVO.builder().invokeResponse(respDTO.getInvokeResponse()).build());
+        PayOrderSubmitRespVO respVO = orderService.submitPayOrder(reqVO, getClientIP());
+        return success(PayOrderConvert.INSTANCE.convert3(respVO));
     }
 
 }

+ 2 - 14
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pay.controller.app.order.vo;
 
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.Data;
 import lombok.experimental.Accessors;
@@ -10,18 +11,5 @@ import java.util.Map;
 
 @Schema(description = "用户 APP - 支付订单提交 Request VO")
 @Data
-@Accessors(chain = true)
-public class AppPayOrderSubmitReqVO {
-
-    @Schema(description = "支付单编号", required = true, example = "1024")
-    @NotNull(message = "支付单编号不能为空")
-    private Long id;
-
-    @Schema(description = "支付渠道", required = true, example = "wx_pub")
-    @NotEmpty(message = "支付渠道不能为空")
-    private String channelCode;
-
-    @Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
-    private Map<String, String> channelExtras;
-
+public class AppPayOrderSubmitReqVO  extends PayOrderSubmitReqVO {
 }

+ 2 - 10
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java

@@ -1,5 +1,6 @@
 package cn.iocoder.yudao.module.pay.controller.app.order.vo;
 
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
 import io.swagger.v3.oas.annotations.media.Schema;
 import lombok.AllArgsConstructor;
 import lombok.Builder;
@@ -9,15 +10,6 @@ import lombok.experimental.Accessors;
 
 @Schema(description = "用户 APP - 支付订单提交 Response VO")
 @Data
-@Accessors(chain = true)
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class AppPayOrderSubmitRespVO {
-
-    /**
-     * 调用支付渠道的响应结果
-     */
-    private Object invokeResponse;
+public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO {
 
 }

+ 0 - 47
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/refund/AppPayRefundController.java

@@ -1,47 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.app.refund;
-
-import cn.hutool.core.util.StrUtil;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundReqVO;
-import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundRespVO;
-import cn.iocoder.yudao.module.pay.convert.refund.PayRefundConvert;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
-import cn.iocoder.yudao.module.pay.service.refund.PayRefundService;
-import cn.iocoder.yudao.module.pay.util.PaySeqUtils;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.annotations.Operation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
-
-@Tag(name = "用户 APP - 退款订单")
-@RestController
-@RequestMapping("/pay/refund")
-@Validated
-@Slf4j
-public class AppPayRefundController {
-
-    @Resource
-    private PayRefundService refundService;
-
-    @PostMapping("/refund")
-    @Operation(summary = "提交退款订单")
-    public CommonResult<AppPayRefundRespVO> submitRefundOrder(@RequestBody AppPayRefundReqVO reqVO){
-        PayRefundReqDTO req = PayRefundConvert.INSTANCE.convert(reqVO);
-        req.setUserIp(getClientIP());
-        // TODO 测试暂时模拟生成商户退款订单
-        if(StrUtil.isEmpty(reqVO.getMerchantRefundId())) {
-            req.setMerchantRefundId(PaySeqUtils.genMerchantRefundNo());
-        }
-        return success(PayRefundConvert.INSTANCE.convert(refundService.submitRefundOrder(req)));
-    }
-
-}

+ 4 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/refund/package-info.java

@@ -0,0 +1,4 @@
+/**
+ * TODO 芋艿:占个位置,没啥用
+ */
+package cn.iocoder.yudao.module.pay.controller.app.refund;

+ 0 - 34
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/refund/vo/AppPayRefundReqVO.java

@@ -1,34 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.app.refund.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.AllArgsConstructor;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-
-@Schema(description = "用户 APP - 退款订单 Req VO")
-@Data
-@NoArgsConstructor
-@AllArgsConstructor
-public class AppPayRefundReqVO {
-
-    @Schema(description = "支付订单编号自增", required = true, example = "10")
-    @NotNull(message = "支付订单编号自增")
-    private Long payOrderId;
-
-    @Schema(description = "退款金额", required = true, example = "1")
-    @NotNull(message = "退款金额")
-    private Long amount;
-
-    @Schema(description = "退款原因", required = true, example = "不喜欢")
-    @NotEmpty(message = "退款原因")
-    private String reason;
-
-    @Schema(description = "商户退款订单号", required = true, example = "MR202111180000000001")
-    //TODO 测试暂时模拟生成
-    //@NotEmpty(message = "商户退款订单号")
-    private String merchantRefundId;
-
-}

+ 0 - 21
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/refund/vo/AppPayRefundRespVO.java

@@ -1,21 +0,0 @@
-package cn.iocoder.yudao.module.pay.controller.app.refund.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-
-@Schema(description = "用户 APP - 提交退款订单 Response VO")
-@Data
-@Accessors(chain = true)
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class AppPayRefundRespVO {
-
-    @Schema(description = "退款订单编号", required = true, example = "10")
-    private Long refundId;
-
-}

+ 12 - 10
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java

@@ -1,16 +1,15 @@
 package cn.iocoder.yudao.module.pay.convert.order;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
-import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderDetailsRespVO;
-import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExcelVO;
-import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageItemRespVO;
-import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderRespVO;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
+import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
+import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.factory.Mappers;
@@ -31,6 +30,8 @@ public interface PayOrderConvert {
 
     PayOrderRespVO convert(PayOrderDO bean);
 
+    PayOrderRespDTO convert2(PayOrderDO order);
+
     PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean);
 
     PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean);
@@ -88,14 +89,15 @@ public interface PayOrderConvert {
         return payOrderExcelVO;
     }
 
-
     PayOrderDO convert(PayOrderCreateReqDTO bean);
 
     @Mapping(target = "id", ignore = true)
-    PayOrderExtensionDO convert(PayOrderSubmitReqDTO bean);
+    PayOrderExtensionDO convert(PayOrderSubmitReqVO bean, String userIp);
+
+    PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO);
 
-    PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqDTO bean);
+    PayOrderSubmitRespVO convert(PayOrderUnifiedRespDTO bean);
 
-    PayOrderRespDTO convert2(PayOrderDO bean);
+    AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean);
 
 }

+ 0 - 13
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/refund/PayRefundConvert.java

@@ -2,12 +2,8 @@ package cn.iocoder.yudao.module.pay.convert.refund;
 
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.*;
-import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundReqVO;
-import cn.iocoder.yudao.module.pay.controller.app.refund.vo.AppPayRefundRespVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundRespDTO;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.Mappings;
@@ -17,11 +13,6 @@ import java.math.BigDecimal;
 import java.math.RoundingMode;
 import java.util.List;
 
-/**
- * 退款订单 Convert
- *
- * @author aquan
- */
 @Mapper
 public interface PayRefundConvert {
 
@@ -102,8 +93,4 @@ public interface PayRefundConvert {
     })
     PayRefundDO convert(PayOrderDO orderDO);
 
-    PayRefundReqDTO convert(AppPayRefundReqVO bean);
-
-    AppPayRefundRespVO convert(PayRefundRespDTO bean);
-
 }

+ 7 - 5
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/demo/PayDemoOrderDO.java

@@ -73,14 +73,16 @@ public class PayDemoOrderDO extends BaseDO {
 
     // ========== 退款相关字段 ==========
     /**
-     * 退款金额
+     * 支付退款单号
+     */
+    private Long payRefundId;
+    /**
+     * 退款金额,单位:分
      */
     private Integer refundPrice;
     /**
-     * 退款时间
-     *
-     * 由于可以多次退款,记录最后一次退款的时间
+     * 退款完成时间
      */
-    private Date refundTime;
+    private LocalDateTime refundTime;
 
 }

+ 0 - 4
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/dataobject/refund/PayRefundDO.java

@@ -80,7 +80,6 @@ public class PayRefundDO extends BaseDO {
      */
     private String tradeNo;
 
-
     // ========== 商户相关字段 ==========
     /**
      * 商户订单编号
@@ -171,14 +170,12 @@ public class PayRefundDO extends BaseDO {
      */
     private String channelErrorMsg;
 
-
     /**
      * 支付渠道的额外参数
      * 参见 https://www.pingxx.com/api/Refunds%20退款概述.html
      */
     private String channelExtras;
 
-
     /**
      * TODO
      * 退款失效时间
@@ -193,5 +190,4 @@ public class PayRefundDO extends BaseDO {
      */
     private LocalDateTime notifyTime;
 
-
 }

+ 5 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/demo/PayDemoOrderMapper.java

@@ -20,4 +20,9 @@ public interface PayDemoOrderMapper extends BaseMapperX<PayDemoOrderDO> {
                 .orderByDesc(PayDemoOrderDO::getId));
     }
 
+    default int updateByIdAndPayed(Long id, boolean wherePayed, PayDemoOrderDO updateObj) {
+        return update(updateObj, new LambdaQueryWrapperX<PayDemoOrderDO>()
+                .eq(PayDemoOrderDO::getId, id).eq(PayDemoOrderDO::getPayed, wherePayed));
+    }
+
 }

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyLogCoreMapper.java

@@ -5,5 +5,5 @@ import cn.iocoder.yudao.framework.mybatis.core.mapper.BaseMapperX;
 import org.apache.ibatis.annotations.Mapper;
 
 @Mapper
-public interface PayNotifyLogCoreMapper extends BaseMapperX<PayNotifyLogDO> {
+public interface PayNotifyLogMapper extends BaseMapperX<PayNotifyLogDO> {
 }

+ 1 - 1
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/dal/mysql/notify/PayNotifyTaskCoreMapper.java

@@ -10,7 +10,7 @@ import java.time.LocalDateTime;
 import java.util.List;
 
 @Mapper
-public interface PayNotifyTaskCoreMapper extends BaseMapperX<PayNotifyTaskDO> {
+public interface PayNotifyTaskMapper extends BaseMapperX<PayNotifyTaskDO> {
 
     /**
      * 获得需要通知的 PayNotifyTaskDO 记录。需要满足如下条件:

+ 24 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderService.java

@@ -39,4 +39,28 @@ public interface PayDemoOrderService {
      */
     PageResult<PayDemoOrderDO> getDemoOrderPage(PageParam pageReqVO);
 
+    /**
+     * 更新示例订单为已支付
+     *
+     * @param id 编号
+     * @param payOrderId 支付订单号
+     */
+    void updateDemoOrderPaid(Long id, Long payOrderId);
+
+    /**
+     * 发起示例订单的退款
+     *
+     * @param id 编号
+     * @param userIp 用户编号
+     */
+    void refundDemoOrder(Long id, String userIp);
+
+    /**
+     * 更新示例订单为已退款
+     *
+     * @param id 编号
+     * @param payRefundId 退款订单号
+     */
+    void updateDemoOrderRefunded(Long id, Long payRefundId);
+
 }

+ 169 - 3
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/demo/PayDemoOrderServiceImpl.java

@@ -1,23 +1,38 @@
 package cn.iocoder.yudao.module.pay.service.demo;
 
 import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ObjectUtil;
 import cn.iocoder.yudao.framework.common.pojo.PageParam;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.module.pay.api.order.PayOrderApi;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
+import cn.iocoder.yudao.module.pay.api.refund.PayRefundApi;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundRespDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.demo.vo.PayDemoOrderCreateReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.demo.PayDemoOrderDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.demo.PayDemoOrderMapper;
+import cn.iocoder.yudao.module.pay.enums.order.PayOrderStatusEnum;
+import cn.iocoder.yudao.module.pay.enums.refund.PayRefundStatusEnum;
+import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.validation.annotation.Validated;
 
 import javax.annotation.Resource;
 import java.time.Duration;
+import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Objects;
 
+import static cn.hutool.core.util.ObjectUtil.*;
+import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
 import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.addTime;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.toJsonString;
 import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
+import static cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants.*;
 
 /**
  * 示例订单 Service 实现类
@@ -26,6 +41,7 @@ import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getCli
  */
 @Service
 @Validated
+@Slf4j
 public class PayDemoOrderServiceImpl implements PayDemoOrderService {
 
     /**
@@ -45,6 +61,8 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
 
     @Resource
     private PayOrderApi payOrderApi;
+    @Resource
+    private PayRefundApi payRefundApi;
 
     @Resource
     private PayDemoOrderMapper payDemoOrderMapper;
@@ -53,8 +71,8 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
         spuNames.put(1L, new Object[]{"华为手机", 1});
         spuNames.put(2L, new Object[]{"小米电视", 10});
         spuNames.put(3L, new Object[]{"苹果手表", 100});
-        spuNames.put(4L, new Object[]{"华硕笔记本", 200});
-        spuNames.put(5L, new Object[]{"蔚来汽车", 300});
+        spuNames.put(4L, new Object[]{"华硕笔记本", 1000});
+        spuNames.put(5L, new Object[]{"蔚来汽车", 200000});
     }
 
     @Override
@@ -67,7 +85,7 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
         // 1.2 插入 demo 订单
         PayDemoOrderDO demoOrder = new PayDemoOrderDO().setUserId(userId)
                 .setSpuId(createReqVO.getSpuId()).setSpuName(spuName)
-                .setPayed(false).setRefundPrice(0);
+                .setPrice(price).setPayed(false).setRefundPrice(0);
         payDemoOrderMapper.insert(demoOrder);
 
         // 2.1 创建支付单
@@ -99,4 +117,152 @@ public class PayDemoOrderServiceImpl implements PayDemoOrderService {
         return payDemoOrderMapper.selectPage(pageReqVO);
     }
 
+    @Override
+    public void updateDemoOrderPaid(Long id, Long payOrderId) {
+        // 校验并获得支付订单(可支付)
+        PayOrderRespDTO payOrder = validateDemoOrderCanPaid(id, payOrderId);
+
+        // 更新 PayDemoOrderDO 状态为已支付
+        int updateCount = payDemoOrderMapper.updateByIdAndPayed(id, false,
+                new PayDemoOrderDO().setPayed(true).setPayTime(LocalDateTime.now())
+                        .setPayChannelCode(payOrder.getChannelCode()));
+        if (updateCount == 0) {
+            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
+        }
+    }
+
+    /**
+     * 校验交易订单满足被支付的条件
+     *
+     * 1. 交易订单未支付
+     * 2. 支付单已支付
+     *
+     * @param id 交易订单编号
+     * @param payOrderId 支付订单编号
+     * @return 交易订单
+     */
+    private PayOrderRespDTO validateDemoOrderCanPaid(Long id, Long payOrderId) {
+        // 1.1 校验订单是否存在
+        PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
+        if (order == null) {
+            throw exception(PAY_DEMO_ORDER_NOT_FOUND);
+        }
+        // 1.2 校验订单未支付
+        if (order.getPayed()) {
+            log.error("[validateDemoOrderCanPaid][order({}) 不处于待支付状态,请进行处理!order 数据是:{}]",
+                    id, toJsonString(order));
+            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_STATUS_NOT_UNPAID);
+        }
+        // 1.3 校验支付订单匹配
+        if (notEqual(order.getPayOrderId(), payOrderId)) { // 支付单号
+            log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!order 数据是:{}]",
+                    id, payOrderId, toJsonString(order));
+            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
+        }
+
+        // 2.1 校验支付单是否存在
+        PayOrderRespDTO payOrder = payOrderApi.getOrder(payOrderId);
+        if (payOrder == null) {
+            log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 不存在,请进行处理!]", id, payOrderId);
+            throw exception(PAY_ORDER_NOT_FOUND);
+        }
+        // 2.2 校验支付单已支付
+        if (!PayOrderStatusEnum.isSuccess(payOrder.getStatus())) {
+            log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 未支付,请进行处理!payOrder 数据是:{}]",
+                    id, payOrderId, toJsonString(payOrder));
+            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_STATUS_NOT_SUCCESS);
+        }
+        // 2.3 校验支付金额一致
+        if (notEqual(payOrder.getAmount(), order.getPrice())) {
+            log.error("[validateDemoOrderCanPaid][order({}) payOrder({}) 支付金额不匹配,请进行处理!order 数据是:{},payOrder 数据是:{}]",
+                    id, payOrderId, toJsonString(order), toJsonString(payOrder));
+            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_PRICE_NOT_MATCH);
+        }
+        // 2.4 校验支付订单匹配(二次)
+        if (notEqual(payOrder.getMerchantOrderId(), id.toString())) {
+            log.error("[validateDemoOrderCanPaid][order({}) 支付单不匹配({}),请进行处理!payOrder 数据是:{}]",
+                    id, payOrderId, toJsonString(payOrder));
+            throw exception(PAY_DEMO_ORDER_UPDATE_PAID_FAIL_PAY_ORDER_ID_ERROR);
+        }
+        return payOrder;
+    }
+
+    @Override
+    public void refundDemoOrder(Long id, String userIp) {
+        // 1. 校验订单是否可以退款
+        PayDemoOrderDO order = validateDemoOrderCanRefund(id);
+
+        // 2.1 创建退款单
+        Long payRefundId = payRefundApi.createPayRefund(new PayRefundCreateReqDTO()
+                .setAppId(PAY_APP_ID).setUserIp(getClientIP()) // 支付应用
+                .setPayOrderId(order.getPayOrderId()) // 支付单号
+                .setReason("想退钱").setAmount(order.getPrice()));// 价格信息
+        // 2.2 更新退款单到 demo 订单
+        payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id)
+                .setPayRefundId(payRefundId).setRefundPrice(order.getPrice()));
+    }
+
+    private PayDemoOrderDO validateDemoOrderCanRefund(Long id) {
+        // 校验订单是否存在
+        PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
+        if (order == null) {
+            throw exception(PAY_DEMO_ORDER_NOT_FOUND);
+        }
+        // 校验订单是否支付
+        if (!order.getPayed()) {
+            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_NOT_PAID);
+        }
+        // 校验订单是否已退款
+        if (order.getPayRefundId() != null) {
+            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUNDED);
+        }
+        return order;
+    }
+
+    @Override
+    public void updateDemoOrderRefunded(Long id, Long payRefundId) {
+        // 1. 校验并获得退款订单(可退款)
+        PayRefundRespDTO payRefund = validateDemoOrderCanRefunded(id, payRefundId);
+        // 2.2 更新退款单到 demo 订单
+        payDemoOrderMapper.updateById(new PayDemoOrderDO().setId(id)
+                .setRefundTime(payRefund.getSuccessTime()));
+    }
+
+    private PayRefundRespDTO validateDemoOrderCanRefunded(Long id, Long payRefundId) {
+        // 1.1 校验示例订单
+        PayDemoOrderDO order = payDemoOrderMapper.selectById(id);
+        if (order == null) {
+            throw exception(PAY_DEMO_ORDER_NOT_FOUND);
+        }
+        // 1.2 校验退款订单匹配
+        if (Objects.equals(order.getPayOrderId(), payRefundId)) {
+            log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!order 数据是:{}]",
+                    id, payRefundId, toJsonString(order));
+            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
+        }
+
+        // 2.1 校验退款订单
+        PayRefundRespDTO payRefund = payRefundApi.getPayRefund(payRefundId);
+        if (payRefund == null) {
+            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_FOUND);
+        }
+        // 2.2
+        if (!PayRefundStatusEnum.isSuccess(payRefund.getStatus())) {
+            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_NOT_SUCCESS);
+        }
+        // 2.3 校验退款金额一致
+        if (notEqual(payRefund.getRefundAmount(), order.getPrice())) {
+            log.error("[validateDemoOrderCanRefunded][order({}) payRefund({}) 退款金额不匹配,请进行处理!order 数据是:{},payRefund 数据是:{}]",
+                    id, payRefundId, toJsonString(order), toJsonString(payRefund));
+            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_PRICE_NOT_MATCH);
+        }
+        // 2.4 校验退款订单匹配(二次)
+        if (notEqual(payRefund.getMerchantOrderId(), id.toString())) {
+            log.error("[validateDemoOrderCanRefunded][order({}) 退款单不匹配({}),请进行处理!payRefund 数据是:{}]",
+                    id, payRefundId, toJsonString(payRefund));
+            throw exception(PAY_DEMO_ORDER_REFUND_FAIL_REFUND_ORDER_ID_ERROR);
+        }
+        return payRefund;
+    }
+
 }

+ 15 - 14
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/notify/PayNotifyServiceImpl.java

@@ -13,8 +13,8 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyLogDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.notify.PayNotifyTaskDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
-import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyLogCoreMapper;
-import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskCoreMapper;
+import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyLogMapper;
+import cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper;
 import cn.iocoder.yudao.module.pay.dal.redis.notify.PayNotifyLockRedisDAO;
 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyStatusEnum;
 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
@@ -32,6 +32,7 @@ import org.springframework.transaction.annotation.Transactional;
 
 import javax.annotation.Resource;
 import javax.validation.Valid;
+import java.time.Duration;
 import java.time.LocalDateTime;
 import java.util.HashMap;
 import java.util.List;
@@ -40,6 +41,7 @@ import java.util.Objects;
 import java.util.concurrent.CountDownLatch;
 import java.util.concurrent.TimeUnit;
 
+import static cn.iocoder.yudao.framework.common.util.date.LocalDateTimeUtils.*;
 import static cn.iocoder.yudao.module.pay.framework.job.config.PayJobConfiguration.NOTIFY_THREAD_POOL_TASK_EXECUTOR;
 
 /**
@@ -69,9 +71,9 @@ public class PayNotifyServiceImpl implements PayNotifyService {
     private PayRefundService refundService;
 
     @Resource
-    private PayNotifyTaskCoreMapper payNotifyTaskCoreMapper;
+    private PayNotifyTaskMapper payNotifyTaskMapper;
     @Resource
-    private PayNotifyLogCoreMapper payNotifyLogCoreMapper;
+    private PayNotifyLogMapper payNotifyLogMapper;
 
     @Resource(name = NOTIFY_THREAD_POOL_TASK_EXECUTOR)
     private ThreadPoolTaskExecutor threadPoolTaskExecutor;
@@ -101,7 +103,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
         }
 
         // 执行插入
-        payNotifyTaskCoreMapper.insert(task);
+        payNotifyTaskMapper.insert(task);
 
         // 异步直接发起任务。虽然会有定时任务扫描,但是会导致延迟
         self.executeNotifyAsync(task);
@@ -110,7 +112,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
     @Override
     public int executeNotify() throws InterruptedException {
         // 获得需要通知的任务
-        List<PayNotifyTaskDO> tasks = payNotifyTaskCoreMapper.selectListByNotify();
+        List<PayNotifyTaskDO> tasks = payNotifyTaskMapper.selectListByNotify();
         if (CollUtil.isEmpty(tasks)) {
             return 0;
         }
@@ -168,8 +170,8 @@ public class PayNotifyServiceImpl implements PayNotifyService {
         payNotifyLockCoreRedisDAO.lock(task.getId(), NOTIFY_TIMEOUT_MILLIS, () -> {
             // 校验,当前任务是否已经被通知过
             // 虽然已经通过分布式加锁,但是可能同时满足通知的条件,然后都去获得锁。此时,第一个执行完后,第二个还是能拿到锁,然后会再执行一次。
-            PayNotifyTaskDO dbTask = payNotifyTaskCoreMapper.selectById(task.getId());
-            if (LocalDateTimeUtils.afterNow(dbTask.getNextNotifyTime())) {
+            PayNotifyTaskDO dbTask = payNotifyTaskMapper.selectById(task.getId());
+            if (afterNow(dbTask.getNextNotifyTime())) {
                 log.info("[executeNotifySync][dbTask({}) 任务被忽略,原因是未到达下次通知时间,可能是因为并发执行了]",
                         JsonUtils.toJsonString(dbTask));
                 return;
@@ -197,7 +199,7 @@ public class PayNotifyServiceImpl implements PayNotifyService {
         // 记录 PayNotifyLog 日志
         String response = invokeException != null ? ExceptionUtil.getRootCauseMessage(invokeException) :
                 JsonUtils.toJsonString(invokeResult);
-        payNotifyLogCoreMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
+        payNotifyLogMapper.insert(PayNotifyLogDO.builder().taskId(task.getId())
                 .notifyTimes(task.getNotifyTimes() + 1).status(newStatus).response(response).build());
     }
 
@@ -250,23 +252,22 @@ public class PayNotifyServiceImpl implements PayNotifyService {
         // 情况一:调用成功
         if (invokeResult != null && invokeResult.isSuccess()) {
             updateTask.setStatus(PayNotifyStatusEnum.SUCCESS.getStatus());
+            payNotifyTaskMapper.updateById(updateTask);
             return updateTask.getStatus();
         }
         // 情况二:调用失败、调用异常
         // 2.1 超过最大回调次数
         if (updateTask.getNotifyTimes() >= PayNotifyTaskDO.NOTIFY_FREQUENCY.length) {
             updateTask.setStatus(PayNotifyStatusEnum.FAILURE.getStatus());
+            payNotifyTaskMapper.updateById(updateTask);
             return updateTask.getStatus();
         }
         // 2.2 未超过最大回调次数
-        updateTask.setNextNotifyTime(LocalDateTime.now().plusSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()]));
+        updateTask.setNextNotifyTime(addTime(Duration.ofSeconds(PayNotifyTaskDO.NOTIFY_FREQUENCY[updateTask.getNotifyTimes()])));
         updateTask.setStatus(invokeException != null ? PayNotifyStatusEnum.REQUEST_FAILURE.getStatus()
                 : PayNotifyStatusEnum.REQUEST_SUCCESS.getStatus());
+        payNotifyTaskMapper.updateById(updateTask);
         return updateTask.getStatus();
     }
 
-    private void processNotifySuccess(PayNotifyTaskDO task, PayNotifyTaskDO updateTask) {
-        payNotifyTaskCoreMapper.updateById(updateTask);
-    }
-
 }

+ 15 - 10
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java

@@ -1,16 +1,18 @@
 package cn.iocoder.yudao.module.pay.service.order;
 
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
-import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
-import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
-import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
+import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
 
 import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -81,17 +83,20 @@ public interface PayOrderService {
      * 提交支付
      * 此时,会发起支付渠道的调用
      *
-     * @param reqDTO 提交请求
+     * @param reqVO 提交请求
+     * @param userIp 提交 IP
      * @return 提交结果
      */
-    PayOrderSubmitRespDTO submitPayOrder(@Valid PayOrderSubmitReqDTO reqDTO);
+    PayOrderSubmitRespVO submitPayOrder(@Valid PayOrderSubmitReqVO reqVO,
+                                        @NotEmpty(message = "提交 IP 不能为空") String userIp);
 
     /**
      * 通知支付单成功
      *
      * @param channelId 渠道编号
-     * @param notifyData 通知数据
+     * @param notify    通知
+     * @param rawNotify 通知数据
      */
-    void notifyPayOrder(Long channelId,  PayNotifyDataDTO notifyData) throws Exception;
+    void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
 
 }

+ 68 - 77
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java

@@ -3,19 +3,21 @@ package cn.iocoder.yudao.module.pay.service.order;
 import cn.hutool.core.date.DateUtil;
 import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.RandomUtil;
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
 import cn.iocoder.yudao.framework.pay.config.PayProperties;
 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 cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderNotifyRespDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
+import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
 import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
@@ -31,8 +33,6 @@ import cn.iocoder.yudao.module.pay.service.merchant.PayAppService;
 import cn.iocoder.yudao.module.pay.service.merchant.PayChannelService;
 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
 import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayOrderSubmitRespDTO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -42,9 +42,9 @@ import javax.annotation.Resource;
 import java.time.LocalDateTime;
 import java.util.Collection;
 import java.util.List;
-import java.util.Objects;
 
 import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
+import static cn.iocoder.yudao.framework.common.util.json.JsonUtils.*;
 
 /**
  * 支付订单 Service 实现类
@@ -105,7 +105,7 @@ public class PayOrderServiceImpl implements PayOrderService {
                 reqDTO.getAppId(), reqDTO.getMerchantOrderId());
         if (order != null) {
             log.warn("[createPayOrder][appId({}) merchantOrderId({}) 已经存在对应的支付单({})]", order.getAppId(),
-                    order.getMerchantOrderId(), JsonUtils.toJsonString(order)); // 理论来说,不会出现这个情况
+                    order.getMerchantOrderId(), toJsonString(order)); // 理论来说,不会出现这个情况
             return order.getId();
         }
 
@@ -127,51 +127,60 @@ public class PayOrderServiceImpl implements PayOrderService {
     }
 
     @Override
-    public PayOrderSubmitRespDTO submitPayOrder(PayOrderSubmitReqDTO reqDTO) {
-        // 校验 App
-        appService.validPayApp(reqDTO.getAppId());
-        // 校验支付渠道是否有效
-        PayChannelDO channel = channelService.validPayChannel(reqDTO.getAppId(), reqDTO.getChannelCode());
-        // 校验支付客户端是否正确初始化
+    public PayOrderSubmitRespVO submitPayOrder(PayOrderSubmitReqVO reqVO, String userIp) {
+        // 1. 获得 PayOrderDO ,并校验其是否存在
+        PayOrderDO order = validatePayOrderCanSubmit(reqVO.getId());
+        // 1.2 校验支付渠道是否有效
+        PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
         PayClient client = payClientFactory.getPayClient(channel.getId());
-        if (client == null) {
-            log.error("[submitPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
-            throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
-        }
-
-        // 获得 PayOrderDO ,并校验其是否存在
-        PayOrderDO order = orderMapper.selectById(reqDTO.getId());
-        if (order == null || !Objects.equals(order.getAppId(), reqDTO.getAppId())) { // 是否存在
-            throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
-        }
-        if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
-            throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
-        }
 
-        // 插入 PayOrderExtensionDO
-        PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqDTO)
+        // 2. 插入 PayOrderExtensionDO
+        PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
                 .setOrderId(order.getId()).setNo(generateOrderExtensionNo())
                 .setChannelId(channel.getId()).setChannelCode(channel.getCode())
                 .setStatus(PayOrderStatusEnum.WAITING.getStatus());
         orderExtensionMapper.insert(orderExtension);
 
-        // 调用三方接口
-        PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqDTO);
-        // 商户相关字段
-        //TODO jason @芋艿 是否加一个属性  如tradeNo 支付订单号, 用这个merchantOrderId让人迷糊
-        unifiedOrderReqDTO.setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
+        // 3. 调用三方接口
+        PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO)
+                // 商户相关的字段
+                .setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
                 .setSubject(order.getSubject()).setBody(order.getBody())
                 .setNotifyUrl(genChannelPayNotifyUrl(channel))
-                .setReturnUrl(genChannelReturnUrl(channel));
-        // 订单相关字段
-        unifiedOrderReqDTO.setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
-        CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO);
-        unifiedOrderResult.checkError();
+                .setReturnUrl(genChannelReturnUrl(channel))
+                // 订单相关字段
+                .setAmount(order.getAmount()).setExpireTime(order.getExpireTime());
+        PayOrderUnifiedRespDTO unifiedOrderRespDTO = client.unifiedOrder(unifiedOrderReqDTO);
 
         // TODO 轮询三方接口,是否已经支付的任务
         // 返回成功
-        return new PayOrderSubmitRespDTO().setExtensionId(orderExtension.getId())
-                .setInvokeResponse(unifiedOrderResult.getData());
+        return PayOrderConvert.INSTANCE.convert(unifiedOrderRespDTO);
+    }
+
+    private PayOrderDO validatePayOrderCanSubmit(Long id) {
+        PayOrderDO order = orderMapper.selectById(id);
+        if (order == null) { // 是否存在
+            throw exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
+        }
+        if (!PayOrderStatusEnum.WAITING.getStatus().equals(order.getStatus())) { // 校验状态,必须是待支付
+            throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);
+        }
+        return order;
+    }
+
+    private PayChannelDO validatePayChannelCanSubmit(Long appId, String channelCode) {
+        // 校验 App
+        appService.validPayApp(appId);
+
+        // 校验支付渠道是否有效
+        PayChannelDO channel = channelService.validPayChannel(appId, channelCode);
+        // 校验支付客户端是否正确初始化
+        PayClient client = payClientFactory.getPayClient(channel.getId());
+        if (client == null) {
+            log.error("[validatePayChannelCanSubmit][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
+            throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
+        }
+        return channel;
     }
 
     /**
@@ -213,49 +222,30 @@ public class PayOrderServiceImpl implements PayOrderService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void notifyPayOrder(Long channelId, PayNotifyDataDTO notifyData) {
-        // TODO 芋艿,记录回调日志
-        log.info("[notifyPayOrder][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
-
+    public void notifyPayOrder(Long channelId, PayOrderNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
         // 校验支付渠道是否有效
         PayChannelDO channel = channelService.validPayChannel(channelId);
         TenantUtils.execute(channel.getTenantId(), () -> {
-            try {
-                notifyPayOrder(channel, notifyData);
-            } catch (Exception e) {
-                throw new RuntimeException(e);
-            }
-        });
-    }
-
-    private void notifyPayOrder(PayChannelDO channel, PayNotifyDataDTO notifyData) throws Exception {
-        // 校验支付客户端是否正确初始化
-        PayClient client = payClientFactory.getPayClient(channel.getId());
-        if (client == null) {
-            log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
-            throw exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
-        }
+            // 1. 更新 PayOrderExtensionDO 支付成功
+            PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notify.getOrderExtensionNo(),
+                    rawNotify);
+            // 2. 更新 PayOrderDO 支付成功
+            PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notify);
 
-        // 0. 解析支付结果
-        PayOrderNotifyRespDTO notifyRespDTO = client.parseOrderNotify(notifyData);
-        // 1. 更新 PayOrderExtensionDO 支付成功
-        PayOrderExtensionDO orderExtension = updatePayOrderExtensionSuccess(notifyRespDTO.getOrderExtensionNo(), notifyData.getBody());
-        // 2. 更新 PayOrderDO 支付成功
-        PayOrderDO order = updatePayOrderSuccess(channel, orderExtension, notifyRespDTO);
-
-        // 3. 插入支付通知记录
-        notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
-                .type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getId()).build());
+            // 3. 插入支付通知记录
+            notifyService.createPayNotifyTask(PayNotifyTaskCreateReqDTO.builder()
+                    .type(PayNotifyTypeEnum.ORDER.getType()).dataId(order.getId()).build());
+        });
     }
 
     /**
      * 更新 PayOrderExtensionDO 支付成功
      *
      * @param no 支付订单号(支付模块)
-     * @param body 回调内容
+     * @param rawNotify 通知数据
      * @return PayOrderExtensionDO 对象
      */
-    private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, String body) {
+    private PayOrderExtensionDO updatePayOrderExtensionSuccess(String no, PayNotifyReqDTO rawNotify) {
         // 1.1 查询 PayOrderExtensionDO
         PayOrderExtensionDO orderExtension = orderExtensionMapper.selectByNo(no);
         if (orderExtension == null) {
@@ -267,7 +257,8 @@ public class PayOrderServiceImpl implements PayOrderService {
         // 1.2 更新 PayOrderExtensionDO
         int updateCounts = orderExtensionMapper.updateByIdAndStatus(orderExtension.getId(),
                 PayOrderStatusEnum.WAITING.getStatus(), PayOrderExtensionDO.builder().id(orderExtension.getId())
-                        .status(PayOrderStatusEnum.SUCCESS.getStatus()).channelNotifyData(body).build());
+                        .status(PayOrderStatusEnum.SUCCESS.getStatus())
+                        .channelNotifyData(toJsonString(rawNotify)).build());
         if (updateCounts == 0) { // 校验状态,必须是待支付
             throw exception(ErrorCodeConstants.PAY_ORDER_EXTENSION_STATUS_IS_NOT_WAITING);
         }
@@ -280,11 +271,11 @@ public class PayOrderServiceImpl implements PayOrderService {
      *
      * @param channel 支付渠道
      * @param orderExtension 支付拓展单
-     * @param notifyRespDTO 通知回调
+     * @param notify 通知回调
      * @return PayOrderDO 对象
      */
     private PayOrderDO updatePayOrderSuccess(PayChannelDO channel, PayOrderExtensionDO orderExtension,
-                                             PayOrderNotifyRespDTO notifyRespDTO) {
+                                             PayOrderNotifyRespDTO notify) {
         // 2.1 判断 PayOrderDO 是否处于待支付
         PayOrderDO order = orderMapper.selectById(orderExtension.getOrderId());
         if (order == null) {
@@ -297,8 +288,8 @@ public class PayOrderServiceImpl implements PayOrderService {
         int updateCounts = orderMapper.updateByIdAndStatus(order.getId(), PayOrderStatusEnum.WAITING.getStatus(),
                 PayOrderDO.builder().status(PayOrderStatusEnum.SUCCESS.getStatus())
                         .channelId(channel.getId()).channelCode(channel.getCode())
-                        .successTime(notifyRespDTO.getSuccessTime()).successExtensionId(orderExtension.getId())
-                        .channelOrderNo(notifyRespDTO.getChannelOrderNo()).channelUserId(notifyRespDTO.getChannelUserId())
+                        .successTime(notify.getSuccessTime()).successExtensionId(orderExtension.getId())
+                        .channelOrderNo(notify.getChannelOrderNo()).channelUserId(notify.getChannelUserId())
                         .notifyTime(LocalDateTime.now()).build());
         if (updateCounts == 0) { // 校验状态,必须是待支付
             throw exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_WAITING);

+ 0 - 47
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/dto/PayOrderSubmitReqDTO.java

@@ -1,47 +0,0 @@
-package cn.iocoder.yudao.module.pay.service.order.dto;
-
-import lombok.Data;
-import lombok.experimental.Accessors;
-
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-import java.io.Serializable;
-import java.util.Map;
-
-/**
- * 支付单提交 Request DTO
- */
-@Data
-@Accessors(chain = true)
-public class PayOrderSubmitReqDTO implements Serializable {
-
-    /**
-     * 应用编号
-     */
-    @NotNull(message = "应用编号不能为空")
-    private Long appId;
-
-    /**
-     * 支付单编号
-     */
-    @NotNull(message = "支付单编号不能为空")
-    private Long id;
-
-    /**
-     * 支付渠道
-     */
-    @NotEmpty(message = "支付渠道不能为空")
-    private String channelCode;
-
-    /**
-     * 用户 IP
-     */
-    @NotEmpty(message = "用户 IP 不能为空")
-    private String userIp;
-
-    /**
-     * 支付渠道的额外参数
-     */
-    private Map<String, String> channelExtras;
-
-}

+ 0 - 23
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/dto/PayOrderSubmitRespDTO.java

@@ -1,23 +0,0 @@
-package cn.iocoder.yudao.module.pay.service.order.dto;
-
-import lombok.Data;
-
-import java.io.Serializable;
-
-/**
- * 支付单提交 Response DTO
- */
-@Data
-public class PayOrderSubmitRespDTO implements Serializable {
-
-    /**
-     * 支付拓展单的编号
-     */
-    private Long extensionId;
-
-    /**
-     * 调用支付渠道的响应结果
-     */
-    private Object invokeResponse;
-
-}

+ 0 - 51
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/dto/PayRefundReqDTO.java

@@ -1,51 +0,0 @@
-package cn.iocoder.yudao.module.pay.service.order.dto;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-
-import javax.validation.constraints.DecimalMin;
-import javax.validation.constraints.NotEmpty;
-import javax.validation.constraints.NotNull;
-
-/**
- * 退款申请单 Request DTO
- */
-@Data
-@Accessors(chain = true)
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class PayRefundReqDTO {
-
-    /**
-     * 支付订单编号
-     */
-    @NotNull(message = "支付订单编号不能为空")
-    private Long payOrderId;
-
-    /**
-     * 退款金额
-     */
-    @NotNull(message = "退款金额不能为空")
-    @DecimalMin(value = "0", inclusive = false, message = "退款金额必须大于零")
-    private Integer amount;
-
-    /**
-     * 退款原因
-     */
-    private String reason;
-
-    /**
-     * 商户退款订单号
-     */
-    @NotEmpty(message = "商户退款订单号不能为空")
-    private String merchantRefundId;
-
-    /**
-     * 用户 IP
-     */
-    private String userIp;
-}

+ 0 - 24
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/dto/PayRefundRespDTO.java

@@ -1,24 +0,0 @@
-package cn.iocoder.yudao.module.pay.service.order.dto;
-
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-import lombok.NoArgsConstructor;
-import lombok.experimental.Accessors;
-
-/**
- * 退款申请单 Response DTO
- */
-@Data
-@Accessors(chain = true)
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
-public class PayRefundRespDTO {
-
-    /**
-     * 支付退款单编号,自增
-     */
-    private Long refundId;
-
-}

+ 9 - 9
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundService.java

@@ -1,12 +1,12 @@
 package cn.iocoder.yudao.module.pay.service.refund;
 
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
 import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundRespDTO;
 
 import java.util.List;
 
@@ -42,20 +42,20 @@ public interface PayRefundService {
     List<PayRefundDO> getRefundList(PayRefundExportReqVO exportReqVO);
 
     /**
-     * 提交退款申请
+     * 创建退款申请
      *
      * @param reqDTO 退款申请信息
-     * @return 退款申请返回信息
+     * @return 退款单号
      */
-    PayRefundRespDTO submitRefundOrder(PayRefundReqDTO reqDTO);
+    Long createPayRefund(PayRefundCreateReqDTO reqDTO);
 
     /**
      * 渠道的退款通知
      *
      * @param channelId  渠道编号
-     * @param notifyData  通知数据
-     * @throws Exception 退款通知异常
+     * @param notify     通知
+     * @param rawNotify  通知数据
      */
-    void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) throws Exception;
+    void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify);
 
 }

+ 51 - 41
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/refund/PayRefundServiceImpl.java

@@ -1,15 +1,17 @@
 package cn.iocoder.yudao.module.pay.service.refund;
 
+import cn.hutool.core.util.RandomUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil;
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.pay.config.PayProperties;
 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.PayCommonResult;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayNotifyDataDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundNotifyDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedReqDTO;
-import cn.iocoder.yudao.framework.pay.core.client.dto.PayRefundUnifiedRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyReqDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayRefundNotifyRespDTO;
+import cn.iocoder.yudao.framework.pay.core.client.dto.refund.PayRefundUnifiedReqDTO;
 import cn.iocoder.yudao.framework.pay.core.enums.PayNotifyRefundStatusEnum;
+import cn.iocoder.yudao.module.pay.api.refund.dto.PayRefundCreateReqDTO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundExportReqVO;
 import cn.iocoder.yudao.module.pay.controller.admin.refund.vo.PayRefundPageReqVO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
@@ -19,7 +21,6 @@ import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
 import cn.iocoder.yudao.module.pay.dal.dataobject.refund.PayRefundDO;
 import cn.iocoder.yudao.module.pay.dal.mysql.order.PayOrderMapper;
 import cn.iocoder.yudao.module.pay.dal.mysql.refund.PayRefundMapper;
-import cn.iocoder.yudao.framework.common.pojo.PageResult;
 import cn.iocoder.yudao.module.pay.enums.ErrorCodeConstants;
 import cn.iocoder.yudao.module.pay.enums.notify.PayNotifyTypeEnum;
 import cn.iocoder.yudao.module.pay.enums.order.PayOrderNotifyStatusEnum;
@@ -32,8 +33,6 @@ import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
 import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.dto.PayRefundRespDTO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -55,6 +54,9 @@ import java.util.Objects;
 public class PayRefundServiceImpl implements PayRefundService {
 
     @Resource
+    private PayProperties payProperties;
+
+    @Resource
     private PayClientFactory payClientFactory;
 
     @Resource
@@ -90,9 +92,9 @@ public class PayRefundServiceImpl implements PayRefundService {
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public PayRefundRespDTO submitRefundOrder(PayRefundReqDTO req) {
+    public Long createPayRefund(PayRefundCreateReqDTO reqDTO) {
         // 获得 PayOrderDO
-        PayOrderDO order = orderService.getOrder(req.getPayOrderId());
+        PayOrderDO order = orderService.getOrder(reqDTO.getPayOrderId());
         // 校验订单是否存在
         if (Objects.isNull(order) ) {
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_NOT_FOUND);
@@ -108,15 +110,19 @@ public class PayRefundServiceImpl implements PayRefundService {
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
         }
 
+        // TODO 芋艿:待实现
+        String merchantRefundId = RandomUtil.randomNumbers(16);
+
         // 校验退款的条件
-        validatePayRefund(req, order);
+        validatePayRefund(reqDTO, order);
         // 退款类型
         PayRefundTypeEnum refundType = PayRefundTypeEnum.SOME;
-        if (Objects.equals(req.getAmount(), order.getAmount())) {
+        if (Objects.equals(reqDTO.getAmount(), order.getAmount())) {
             refundType = PayRefundTypeEnum.ALL;
         }
         PayOrderExtensionDO orderExtensionDO = orderExtensionService.getOrderExtension(order.getSuccessExtensionId());
-        PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(), req.getMerchantRefundId());
+        PayRefundDO payRefundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(orderExtensionDO.getNo(),
+                merchantRefundId);  // TODO 芋艿:需要优化
         if(Objects.nonNull(payRefundDO)){
             // 退款订单已经提交过。
             //TODO 校验相同退款单的金额
@@ -137,15 +143,15 @@ public class PayRefundServiceImpl implements PayRefundService {
                     .channelId(order.getChannelId())
                     .merchantId(order.getMerchantId())
                     .orderId(order.getId())
-                    .merchantRefundNo(req.getMerchantRefundId())
+                    .merchantRefundNo(merchantRefundId) // TODO 芋艿:需要优化
                     .notifyUrl(app.getRefundNotifyUrl())
                     .payAmount(order.getAmount())
-                    .refundAmount(req.getAmount())
-                    .userIp(req.getUserIp())
+                    .refundAmount(reqDTO.getAmount())
+                    .userIp(reqDTO.getUserIp())
                     .merchantOrderId(order.getMerchantOrderId())
                     .tradeNo(orderExtensionDO.getNo())
                     .status(PayRefundStatusEnum.CREATE.getStatus())
-                    .reason(req.getReason())
+                    .reason(reqDTO.getReason())
                     .notifyStatus(PayOrderNotifyStatusEnum.NO.getStatus())
                     .type(refundType.getStatus())
                     .build();
@@ -153,47 +159,50 @@ public class PayRefundServiceImpl implements PayRefundService {
         }
         // TODO @jason:搞到 convert 里。一些额外的自动,手动 set 下;
         PayRefundUnifiedReqDTO unifiedReqDTO = new PayRefundUnifiedReqDTO();
-        unifiedReqDTO.setUserIp(req.getUserIp())
-                .setAmount(req.getAmount())
+        unifiedReqDTO.setUserIp(reqDTO.getUserIp())
+                .setAmount(reqDTO.getAmount())
                 .setChannelOrderNo(order.getChannelOrderNo())
                 .setPayTradeNo(orderExtensionDO.getNo())
-                .setMerchantRefundId(req.getMerchantRefundId())
-                .setReason(req.getReason());
+                .setMerchantRefundId(merchantRefundId)  // TODO 芋艿:需要优化
+                .setNotifyUrl(genChannelPayNotifyUrl(channel)) // TODO 芋艿:优化下 notifyUrl
+                .setReason(reqDTO.getReason());
         // 向渠道发起退款申请
-        PayCommonResult<PayRefundUnifiedRespDTO> refundUnifiedResult = client.unifiedRefund(unifiedReqDTO);
+        client.unifiedRefund(unifiedReqDTO);
         // 检查是否失败,失败抛出业务异常。
         // TODO 渠道的异常记录。
         // TODO @jason:可以先打个 warn log 哈;
-        refundUnifiedResult.checkError();
         // 成功在 退款回调中处理
-        return PayRefundRespDTO.builder().refundId(payRefundDO.getId()).build();
+        return payRefundDO.getId();
+    }
+
+    /**
+     * 根据支付渠道的编码,生成支付渠道的回调地址
+     *
+     * @param channel 支付渠道
+     * @return 支付渠道的回调地址  配置地址 + "/" + channel id
+     */
+    private String genChannelPayNotifyUrl(PayChannelDO channel) {
+        return payProperties.getCallbackUrl() + "/" + channel.getId();
     }
 
     @Override
     @Transactional(rollbackFor = Exception.class)
-    public void notifyPayRefund(Long channelId, PayNotifyDataDTO notifyData) {
-        log.info("[notifyPayRefund][channelId({}) 回调数据({})]", channelId, notifyData.getBody());
+    public void notifyPayRefund(Long channelId, PayRefundNotifyRespDTO notify, PayNotifyReqDTO rawNotify) {
         // 校验支付渠道是否有效
+        // TODO 芋艿:需要重构下这块的逻辑
         PayChannelDO channel = channelService.validPayChannel(channelId);
-        // 校验支付客户端是否正确初始化
-        PayClient client = payClientFactory.getPayClient(channel.getId());
-        if (client == null) {
-            log.error("[notifyPayOrder][渠道编号({}) 找不到对应的支付客户端]", channel.getId());
-            throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_CHANNEL_CLIENT_NOT_FOUND);
-        }
-        // 解析渠道退款通知数据, 统一处理
-        PayRefundNotifyDTO refundNotify = client.parseRefundNotify(notifyData);
-        if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS,refundNotify.getStatus())){
-            payRefundSuccess(refundNotify);
+        if (Objects.equals(PayNotifyRefundStatusEnum.SUCCESS, notify.getStatus())){
+            payRefundSuccess(notify);
         } else {
             //TODO 支付异常, 支付宝似乎没有支付异常的通知。
             // TODO @jason:那这里可以考虑打个 error logger @芋艿 微信是否存在支付异常通知
         }
     }
 
-    private void payRefundSuccess(PayRefundNotifyDTO refundNotify) {
+    private void payRefundSuccess(PayRefundNotifyRespDTO refundNotify) {
         // 校验退款单存在
-        PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(), refundNotify.getReqNo());
+        PayRefundDO refundDO = refundMapper.selectByTradeNoAndMerchantRefundNo(refundNotify.getTradeNo(),
+                refundNotify.getReqNo());
         if (refundDO == null) {
             log.error("[payRefundSuccess][不存在 seqNo 为{} 的支付退款单]", refundNotify.getReqNo());
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_NOT_FOUND);
@@ -235,10 +244,11 @@ public class PayRefundServiceImpl implements PayRefundService {
 
     /**
      * 校验是否进行退款
-     * @param req 退款申请信息
+     *
+     * @param reqDTO 退款申请信息
      * @param order 原始支付订单信息
      */
-    private void validatePayRefund(PayRefundReqDTO req, PayOrderDO order) {
+    private void validatePayRefund(PayRefundCreateReqDTO reqDTO, PayOrderDO order) {
         // 校验状态,必须是支付状态
         if (!PayOrderStatusEnum.SUCCESS.getStatus().equals(order.getStatus())) {
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_ORDER_STATUS_IS_NOT_SUCCESS);
@@ -248,7 +258,7 @@ public class PayRefundServiceImpl implements PayRefundService {
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_ALL_REFUNDED);
         }
         // 校验金额 退款金额不能大于 原定的金额
-        if (req.getAmount() + order.getRefundAmount() > order.getAmount()){
+        if (reqDTO.getAmount() + order.getRefundAmount() > order.getAmount()){
             throw ServiceExceptionUtil.exception(ErrorCodeConstants.PAY_REFUND_AMOUNT_EXCEED);
         }
         // 校验渠道订单号

+ 3 - 0
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/util/PaySeqUtils.java

@@ -18,6 +18,7 @@ public class PaySeqUtils {
 
     private static final AtomicLong MER_ORDER_NO_SEQ = new AtomicLong(0L);
 
+    // TODO 芋艿:需要看看
     /**
      * 生成商户退款单号,用于测试,应该由商户系统生成
      * @return 商户退款单
@@ -28,6 +29,8 @@ public class PaySeqUtils {
                 (int) MER_REFUND_NO_SEQ.getAndIncrement() % 10000);
     }
 
+    // TODO 芋艿:需要看看
+
     /**
      * 生成退款请求号
      * @return 退款请求号

+ 5 - 5
yudao-server/pom.xml

@@ -54,11 +54,11 @@
 <!--            <version>${revision}</version>-->
 <!--        </dependency>-->
         <!-- 支付服务 -->
-        <dependency>
-            <groupId>cn.iocoder.boot</groupId>
-            <artifactId>yudao-module-pay-biz</artifactId>
-            <version>${revision}</version>
-        </dependency>
+<!--        <dependency>-->
+<!--            <groupId>cn.iocoder.boot</groupId>-->
+<!--            <artifactId>yudao-module-pay-biz</artifactId>-->
+<!--            <version>${revision}</version>-->
+<!--        </dependency>-->
 
         <!-- 微信公众号模块。默认注释,保证编译速度 -->
 <!--        <dependency>-->

+ 0 - 4
yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/admin/package-info.java

@@ -1,4 +0,0 @@
-/**
- * 占位
- */
-package cn.iocoder.yudao.module.shop.controller.admin;

+ 0 - 73
yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/app/AppShopOrderController.java

@@ -1,73 +0,0 @@
-package cn.iocoder.yudao.module.shop.controller.app;
-
-import cn.iocoder.yudao.framework.common.pojo.CommonResult;
-import cn.iocoder.yudao.module.pay.api.notify.dto.PayOrderNotifyReqDTO;
-import cn.iocoder.yudao.module.pay.api.notify.dto.PayRefundNotifyReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
-import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
-import cn.iocoder.yudao.module.pay.util.PaySeqUtils;
-import cn.iocoder.yudao.module.shop.controller.app.vo.AppShopOrderCreateRespVO;
-import io.swagger.v3.oas.annotations.tags.Tag;
-import io.swagger.v3.oas.annotations.Operation;
-import lombok.extern.slf4j.Slf4j;
-import org.springframework.validation.annotation.Validated;
-import org.springframework.web.bind.annotation.PostMapping;
-import org.springframework.web.bind.annotation.RequestBody;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
-
-import javax.annotation.Resource;
-import javax.validation.Valid;
-import java.time.LocalDateTime;
-
-import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
-import static cn.iocoder.yudao.framework.common.util.servlet.ServletUtils.getClientIP;
-
-@Tag(name = "用户 APP - 商城订单")
-@RestController
-@RequestMapping("/shop/order")
-@Validated
-@Slf4j
-public class AppShopOrderController {
-
-    @Resource
-    private PayOrderService payOrderService;
-
-    @PostMapping("/create")
-    @Operation(summary = "创建商城订单")
-//    @PreAuthenticated // TODO 暂时不加登陆验证,前端暂时没做好
-    public CommonResult<AppShopOrderCreateRespVO> create() {
-        // 假装创建商城订单
-        Long shopOrderId = System.currentTimeMillis();
-
-        // 创建对应的支付订单
-        PayOrderCreateReqDTO reqDTO = new PayOrderCreateReqDTO();
-        reqDTO.setAppId(6L);
-        reqDTO.setUserIp(getClientIP());
-        reqDTO.setMerchantOrderId(PaySeqUtils.genMerchantOrderNo());
-        reqDTO.setSubject("标题:" + shopOrderId);
-        reqDTO.setBody("内容:" + shopOrderId);
-        reqDTO.setAmount(200); // 单位:分
-        reqDTO.setExpireTime(LocalDateTime.now().plusDays(1));
-        Long payOrderId = payOrderService.createPayOrder(reqDTO);
-
-        // 拼接返回
-        return success(AppShopOrderCreateRespVO.builder().id(shopOrderId)
-                .payOrderId(payOrderId).build());
-    }
-
-    @PostMapping("/pay-notify")
-    @Operation(summary = "支付回调")
-    public CommonResult<Boolean> payNotify(@RequestBody @Valid PayOrderNotifyReqDTO reqVO) {
-        log.info("[payNotify][回调成功]");
-        return success(true);
-    }
-
-    @PostMapping("/refund-notify")
-    @Operation(summary = "退款回调")
-    public CommonResult<Boolean> refundNotify(@RequestBody @Valid PayRefundNotifyReqDTO reqVO) {
-        log.info("[refundNotify][回调成功]");
-        return success(true);
-    }
-
-}

+ 0 - 20
yudao-server/src/main/java/cn/iocoder/yudao/module/shop/controller/app/vo/AppShopOrderCreateRespVO.java

@@ -1,20 +0,0 @@
-package cn.iocoder.yudao.module.shop.controller.app.vo;
-
-import io.swagger.v3.oas.annotations.media.Schema;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Data;
-
-@Schema(description = "用户 APP - 商城订单创建 Response VO")
-@Data
-@Builder
-@AllArgsConstructor
-public class AppShopOrderCreateRespVO {
-
-    @Schema(description = "商城订单编号", required = true, example = "1024")
-    private Long id;
-
-    @Schema(description = "支付订单编号", required = true, example = "2048")
-    private Long payOrderId;
-
-}

+ 0 - 9
yudao-server/src/main/java/cn/iocoder/yudao/module/shop/package-info.java

@@ -1,9 +0,0 @@
-/**
- * shop 包下,我们放商城业务
- * 例如说:商品、订单等等
- * 注意,目前仅仅作为 demo 演示,对接 pay 支付系统
- *
- * 缩写:shop
- */
-// TODO 芋艿:后续会迁移到 yudao-module-mall-trade 下
-package cn.iocoder.yudao.module.shop;

+ 0 - 11
yudao-server/src/main/java/cn/iocoder/yudao/server/framework/ui/core/AdminUiController.java

@@ -1,11 +0,0 @@
-package cn.iocoder.yudao.server.framework.ui.core;
-
-import org.springframework.boot.web.servlet.error.ErrorController;
-
-//@Controller
-//@RequestMapping("/admin-ui/")
-public class AdminUiController implements ErrorController {
-
-//    public String
-
-}

+ 1 - 0
yudao-server/src/main/resources/application-local.yaml

@@ -153,6 +153,7 @@ logging:
     cn.iocoder.yudao.module.infra.dal.mysql: debug
     cn.iocoder.yudao.module.infra.dal.mysql.job.JobLogMapper: INFO # 配置 JobLogMapper 的日志级别为 info
     cn.iocoder.yudao.module.pay.dal.mysql: debug
+    cn.iocoder.yudao.module.pay.dal.mysql.notify.PayNotifyTaskMapper: INFO # 配置 JobLogMapper 的日志级别为 info
     cn.iocoder.yudao.module.system.dal.mysql: debug
     cn.iocoder.yudao.module.tool.dal.mysql: debug
     cn.iocoder.yudao.module.member.dal.mysql: debug

+ 0 - 79
yudao-server/src/main/resources/static/pay_alipay_qr.html

@@ -1,79 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
-    <title>支付测试页</title>
-    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
-    <script src="qrcode.min.js" type="text/javascript"></script>
-
-</head>
-<body>
-<div>点击如下按钮,发起支付宝扫码支付的测试</div>
-<div>
-    <button id="alipay_wap">支付宝扫码支付</button>
-</div>
-<div  id="qrcode"></div>
-</body>
-<style>
-    #qrcode{
-        padding-left: 20px;
-        padding-top: 20px;
-    }
-</style>
-<script>
-    let shopOrderId = undefined;
-    let payOrderId = undefined;
-     let server = 'http://127.0.0.1:48080';
-    $(function() {
-        // 自动发起商城订单编号
-        $.ajax({
-            url: server + "/app-api/shop/order/create",
-            method: 'POST',
-            success: function( result ) {
-                if (result.code !== 0) {
-                    alert('创建商城订单失败,原因:' + result.msg)
-                    return;
-                }
-                shopOrderId = result.data.id;
-                payOrderId = result.data.payOrderId;
-                console.log("商城订单:" + shopOrderId)
-                console.log("支付订单:" + payOrderId)
-            }
-        })
-    });
-    // 支付宝扫码支付
-    $( "#alipay_wap").on( "click", function() {
-        // 提交支付
-        $.ajax({
-            url: server + "/app-api/pay/order/submit",
-            method: 'POST',
-            dataType: "json",
-            contentType: "application/json",
-            data: JSON.stringify({
-                "id": payOrderId,
-                "channelCode": 'alipay_qr'
-            }),
-            success: function( result ) {
-                if (result.code !== 0) {
-                    alert('提交支付订单失败,原因:' + result.msg)
-                    return;
-                }
-                //提交支付后返回的参数
-                let data = result.data.invokeResponse;
-                new QRCode($("#qrcode")[0],{
-                    text: data.qrCode, //内容
-                    width:98, //宽度
-                    height:98, //高度
-                    correctLevel: 3,//二维码纠错级别
-                    background: "#ffffff",//背景颜色
-                    foreground: "#000000"//二维码颜色
-                });
-
-                console.log("data.qrCode===",data.qrCode)
-
-            }
-        })
-    });
-</script>
-</html>

+ 0 - 65
yudao-server/src/main/resources/static/pay_alipay_wap.html

@@ -1,65 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
-    <title>支付测试页</title>
-    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
-</head>
-<body>
-<div>点击如下按钮,发起支付的测试</div>
-<div>
-    <button id="alipay_wap">支付宝手机网站支付</button>
-</div>
-<div id="dynamic_form"></div>
-</body>
-<script>
-
-    let shopOrderId = undefined;
-    let payOrderId = undefined;
-     let server = 'http://127.0.0.1:48080';
-    //let server = 'http://niubi.natapp1.cc';
-
-    $(function() {
-        // 自动发起商城订单编号
-        $.ajax({
-            url: server + "/app-api/shop/order/create",
-            method: 'POST',
-            success: function( result ) {
-                if (result.code !== 0) {
-                    alert('创建商城订单失败,原因:' + result.msg)
-                    return;
-                }
-                shopOrderId = result.data.id;
-                payOrderId = result.data.payOrderId;
-                console.log("商城订单:" + shopOrderId)
-                console.log("支付订单:" + payOrderId)
-            }
-        })
-    });
-
-    $( "#alipay_wap").on( "click", function() {
-        // 提交支付
-        $.ajax({
-            url: server + "/app-api/pay/order/submit",
-            method: 'POST',
-            dataType: "json",
-            contentType: "application/json",
-            data: JSON.stringify({
-                "id": payOrderId,
-                "channelCode": 'alipay_wap'
-            }),
-            success: function( result ) {
-                if (result.code !== 0) {
-                    alert('提交支付订单失败,原因:' + result.msg)
-                    return;
-                }
-                alert('点击确定,开始支付');
-                //支付宝 手机WAP 返回表单,自动跳到支付宝支付页面
-                let data = result.data.invokeResponse;
-                $("#dynamic_form").html(data.body);
-            }
-        })
-    });
-</script>
-</html>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 0 - 1
yudao-server/src/main/resources/static/qrcode.min.js


+ 0 - 38
yudao-server/src/main/resources/static/social-login.html

@@ -1,38 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
-    <title>社交登陆测试页</title>
-    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
-</head>
-<body>
-<div>点击如下按钮,发起登陆的测试</div>
-<div>
-    <button id="wx_pub">微信公众号</button>
-</div>
-</body>
-<script>
-    // let server = 'http://127.0.0.1:28080';
-    let server = 'http://192.168.1.2:48080';
-
-
-    // 微信公众号
-    $( "#wx_pub").on( "click", function() {
-        // 获得授权链接
-        $.ajax({
-            url: server + "/app-api/social-auth-redirect?type=31&redirectUri=" +
-                encodeURIComponent(server + '/static/social-login2.html'),  //重定向地址
-            method: 'GET',
-            success: function( result ) {
-                if (result.code !== 0) {
-                    alert('获得授权链接失败,原因:' + result.msg)
-                    return;
-                }
-                // 跳转重定向
-                document.location.href = result.data;
-            }
-        })
-    });
-</script>
-</html>

+ 0 - 87
yudao-server/src/main/resources/static/social-login2.html

@@ -1,87 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
-    <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no"/>
-    <title>社交登陆测试页</title>
-    <script src="https://cdn.bootcdn.net/ajax/libs/jquery/3.6.0/jquery.js"></script>
-</head>
-<body>
-<div>点击如下按钮,授权登录</div>
-<div>
-    手机号<input id="mobile" value="15601691300"><br>
-    手机验证码<input id="smsCode">
-    <button id="send_sms_code">发送手机验证码</button>
-    <br>
-    <button id="wx_pub">微信公众号授权登录</button>
-</div>
-</body>
-<script>
-    // let server = 'http://127.0.0.1:48080';
-    let server = 'http://192.168.1.2:48080';
-
-    let type = 31; //登录类型 微信公众号
-
-    // 微信公众号
-    $("#wx_pub").on("click", function () {
-        let code = getUrlParam("code"); // 访问授权连接后,会回调本页面地址,参数在本页面url后面
-        let state = getUrlParam("state");
-        console.log("获取code: " + code + ", state: " + state)
-
-        let data = {
-            'mobile': $('#mobile').val(),
-            'smsCode': $('#smsCode').val(),
-            'code': code,
-            'state': state,
-            'type': type
-        }
-
-        // 调用授权登录接口
-        $.ajax({
-            url: server + "/app-api/social-login2",
-            method: 'POST',
-            data: JSON.stringify(data),
-            contentType: "application/json;charset=utf-8",
-            dataType: "json",
-            success: function( result ) {
-                if (result.code !== 0) {
-                    alert('调用授权登录接口失败,原因:' + result.msg)
-                    return;
-                }
-                alert("授权登录成功, token: "+result.data.token)
-            }
-        })
-    });
-
-    // 发送手机验证码
-    $("#send_sms_code").on("click", function () {
-        let data = {
-            'mobile': $('#mobile').val(),
-            'scene': 1 // 手机号登陆 类型
-        }
-        $.ajax({
-            url: server + "/app-api/send-sms-code",
-            method: 'POST',
-            data: JSON.stringify(data),
-            contentType: "application/json;charset=utf-8",
-            dataType: "json",
-            success: function (result) {
-                if (result.code !== 0) {
-                    alert('发送手机验证码失败,原因:' + result.msg)
-                    return;
-                }
-                alert("发送成功, 请查看日志");
-            }
-        })
-    })
-
-
-    //获取url中的参数
-    function getUrlParam(name) {
-        var reg = new RegExp("(^|&)" + name + "=([^&]*)(&|$)"); //构造一个含有目标参数的正则表达式对象
-        var r = window.location.search.substr(1).match(reg);  //匹配目标参数
-        if (r != null) return unescape(r[2]);
-        return null; //返回参数值
-    }
-</script>
-</html>

+ 1 - 0
yudao-ui-admin/package.json

@@ -57,6 +57,7 @@
     "jsencrypt": "3.3.1",
     "min-dash": "3.5.2",
     "nprogress": "0.2.0",
+    "qrcode.vue": "^1.7.0",
     "quill": "1.3.7",
     "screenfull": "5.0.2",
     "sortablejs": "1.10.2",

+ 35 - 0
yudao-ui-admin/src/api/pay/demo.js

@@ -0,0 +1,35 @@
+import request from '@/utils/request'
+
+// 创建示例订单
+export function createDemoOrder(data) {
+  return request({
+    url: '/pay/demo-order/create',
+    method: 'post',
+    data: data
+  })
+}
+
+// 获得示例订单
+export function getDemoOrder(id) {
+  return request({
+    url: '/pay/demo-order/get?id=' + id,
+    method: 'get'
+  })
+}
+
+// 获得示例订单分页
+export function getDemoOrderPage(query) {
+  return request({
+    url: '/pay/demo-order/page',
+    method: 'get',
+    params: query
+  })
+}
+
+// 退款示例订单
+export function refundDemoOrder(id) {
+  return request({
+    url: '/pay/demo-order/refund?id=' + id,
+    method: 'put'
+  })
+}

+ 17 - 18
yudao-ui-admin/src/api/pay/order.js

@@ -1,23 +1,5 @@
 import request from '@/utils/request'
 
-// 创建支付订单
-export function createOrder(data) {
-  return request({
-    url: '/pay/order/create',
-    method: 'post',
-    data: data
-  })
-}
-
-// 更新支付订单
-export function updateOrder(data) {
-  return request({
-    url: '/pay/order/update',
-    method: 'put',
-    data: data
-  })
-}
-
 // 删除支付订单
 export function deleteOrder(id) {
   return request({
@@ -34,6 +16,23 @@ export function getOrder(id) {
   })
 }
 
+// 获得支付订单的明细
+export function getOrderDetail(id) {
+  return request({
+    url: '/pay/order/get-detail?id=' + id,
+    method: 'get'
+  })
+}
+
+// 提交支付订单
+export function submitOrder(data) {
+  return request({
+    url: '/pay/order/submit',
+    method: 'post',
+    data: data
+  })
+}
+
 // 获得支付订单分页
 export function getOrderPage(query) {
   return request({

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 0
yudao-ui-admin/src/assets/images/pay/icon/alipay_app.svg


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 2 - 0
yudao-ui-admin/src/assets/images/pay/icon/alipay_bar.svg


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 0
yudao-ui-admin/src/assets/images/pay/icon/alipay_pc.svg


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 2 - 0
yudao-ui-admin/src/assets/images/pay/icon/alipay_qr.svg


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 0
yudao-ui-admin/src/assets/images/pay/icon/alipay_wap.svg


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 0
yudao-ui-admin/src/assets/images/pay/icon/mock.svg


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 2 - 0
yudao-ui-admin/src/assets/images/pay/icon/wx_app.svg


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 1 - 0
yudao-ui-admin/src/assets/images/pay/icon/wx_lite.svg


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 2 - 0
yudao-ui-admin/src/assets/images/pay/icon/wx_pub.svg


+ 15 - 0
yudao-ui-admin/src/router/index.js

@@ -221,6 +221,21 @@ export const constantRoutes = [
         component: (resolve) => require(['@/views/mall/trade/order/detail'], resolve)
       }
     ]
+  },
+  {
+    path: '/pay',
+    component: Layout,
+    hidden: true,
+    children: [{
+      path: 'order/submit',
+      name: 'PayOrderSubmit',
+      hidden: true,
+      meta: {
+        title: '收银台',
+        noCache: true
+      },
+      component: (resolve) => require(['@/views/pay/order/submit'], resolve)
+    }]
   }
 ]
 

+ 23 - 1
yudao-ui-admin/src/utils/constants.js

@@ -148,6 +148,28 @@ export const PayChannelEnum = {
     "code": "alipay_qr",
     "name": "支付宝扫码支付"
   },
+  ALIPAY_BAR: {
+    "code": "alipay_bar",
+    "name": "支付宝条码支付"
+  },
+}
+
+/**
+ * 支付的展示模式每局
+ */
+export const PayDisplayModeEnum = {
+  URL: {
+    "mode": "url",
+  },
+  IFRAME: {
+    "mode": "iframe",
+  },
+  FORM: {
+    "mode": "form"
+  },
+  QR_CODE: {
+    "mode": "qr_code"
+  }
 }
 
 /**
@@ -172,7 +194,7 @@ export const PayOrderStatusEnum = {
   },
   CLOSED: {
     status: 20,
-    name: '支付'
+    name: '支付关闭'
   }
 }
 

+ 12 - 5
yudao-ui-admin/src/views/pay/app/index.vue

@@ -53,11 +53,6 @@
         </template>
       </el-table-column>
       <el-table-column label="商户名称" align="center" prop="payMerchant.name"/>
-      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
-        <template v-slot="scope">
-          <span>{{ parseTime(scope.row.createTime) }}</span>
-        </template>
-      </el-table-column>
       <el-table-column label="支付宝配置" align="center">
         <el-table-column :label="payChannelEnum.ALIPAY_APP.name" align="center">
           <template v-slot="scope">
@@ -107,6 +102,18 @@
             </el-button>
           </template>
         </el-table-column>
+        <el-table-column :label="payChannelEnum.ALIPAY_BAR.name" align="center">
+          <template v-slot="scope">
+            <el-button type="success" icon="el-icon-check" circle
+                       v-if="judgeChannelExist(scope.row.channelCodes,payChannelEnum.ALIPAY_BAR.code)"
+                       @click="handleUpdateChannel(scope.row,payChannelEnum.ALIPAY_BAR.code,payType.ALIPAY)">
+            </el-button>
+            <el-button v-else
+                       type="danger" icon="el-icon-close" circle
+                       @click="handleCreateChannel(scope.row,payChannelEnum.ALIPAY_BAR.code,payType.ALIPAY)">
+            </el-button>
+          </template>
+        </el-table-column>
       </el-table-column>
       <el-table-column label="微信配置" align="center">
         <el-table-column :label="payChannelEnum.WX_LITE.name" align="center">

+ 214 - 0
yudao-ui-admin/src/views/pay/demo/index.vue

@@ -0,0 +1,214 @@
+<template>
+  <div class="app-container">
+    <!-- 操作工具栏 -->
+    <el-row :gutter="10" class="mb8">
+      <el-col :span="1.5">
+        <el-button type="primary" plain icon="el-icon-plus" size="mini" @click="handleAdd">发起订单</el-button>
+      </el-col>
+      <right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
+    </el-row>
+
+    <!-- 列表 -->
+    <el-table v-loading="loading" :data="list">
+      <el-table-column label="订单编号" align="center" prop="id" />
+      <el-table-column label="用户编号" align="center" prop="userId" />
+      <el-table-column label="商品名字" align="center" prop="spuName" />
+      <el-table-column label="支付价格" align="center" prop="price">
+        <template v-slot="scope">
+          <span>¥{{ (scope.row.price / 100.0).toFixed(2) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="退款金额" align="center" prop="refundPrice">
+        <template v-slot="scope">
+          <span>¥{{ (scope.row.refundPrice / 100.0).toFixed(2) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="创建时间" align="center" prop="createTime" width="180">
+        <template v-slot="scope">
+          <span>{{ parseTime(scope.row.createTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="支付单号" align="center" prop="payOrderId" />
+      <el-table-column label="是否支付" align="center" prop="payed">
+        <template v-slot="scope">
+          <dict-tag :type="DICT_TYPE.INFRA_BOOLEAN_STRING" :value="scope.row.payed" />
+        </template>
+      </el-table-column>
+      <el-table-column label="支付时间" align="center" prop="payTime" width="180">
+        <template v-slot="scope">
+          <span>{{ parseTime(scope.row.payTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="退款时间" align="center" prop="refundTime" width="180">
+        <template v-slot="scope">
+          <span>{{ parseTime(scope.row.refundTime) }}</span>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
+        <template v-slot="scope">
+          <el-button size="mini" type="text" icon="el-icon-edit" @click="handlePay(scope.row)"
+                     v-if="!scope.row.payed">前往支付</el-button>
+          <el-button size="mini" type="text" icon="el-icon-delete" @click="handleRefund(scope.row)"
+                     v-if="scope.row.payed && !scope.row.payRefundId">发起退款</el-button>
+        </template>
+      </el-table-column>
+    </el-table>
+    <!-- 分页组件 -->
+    <pagination v-show="total > 0" :total="total" :page.sync="queryParams.pageNo" :limit.sync="queryParams.pageSize"
+                @pagination="getList"/>
+
+    <!-- 对话框(添加 / 修改) -->
+    <el-dialog :title="title" :visible.sync="open" width="500px" v-dialogDrag append-to-body>
+      <el-form ref="form" :model="form" :rules="rules" label-width="80px">
+        <el-form-item label="商品" prop="spuId">
+          <el-select v-model="form.spuId" placeholder="请输入下单商品" clearable size="small" style="width: 380px" >
+            <el-option v-for="item in spus" :key="item.id" :label="item.name" :value="item.id">
+              <span style="float: left">{{ item.name}}</span>
+              <span style="float: right; color: #8492a6; font-size: 13px">¥{{ (item.price / 100.0).toFixed(2) }}</span>
+            </el-option>
+          </el-select>
+        </el-form-item>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submitForm">确 定</el-button>
+        <el-button @click="cancel">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+
+<script>
+import {createDemoOrder, getDemoOrderPage, refundDemoOrder} from "@/api/pay/demo";
+import {deleteMerchant} from "@/api/pay/merchant";
+
+export default {
+  name: "PayDemoOrder",
+  components: {
+  },
+  data() {
+    return {
+      // 遮罩层
+      loading: true,
+      // 显示搜索条件
+      showSearch: true,
+      // 总条数
+      total: 0,
+      // 示例订单列表
+      list: [],
+      // 弹出层标题
+      title: "",
+      // 是否显示弹出层
+      open: false,
+      // 查询参数
+      queryParams: {
+        pageNo: 1,
+        pageSize: 10,
+      },
+      // 表单参数
+      form: {},
+      // 表单校验
+      rules: {
+        spuId: [{ required: true, message: "商品编号不能为空", trigger: "blur" }],
+      },
+      // 商品数组
+      spus: [{
+        id: 1,
+        name: '华为手机',
+        price: 1,
+      }, {
+        id: 2,
+        name: '小米电视',
+        price: 10,
+      }, {
+        id: 3,
+        name: '苹果手表',
+        price: 100,
+      }, {
+        id: 4,
+        name: '华硕笔记本',
+        price: 1000,
+      }, {
+        id: 5,
+        name: '蔚来汽车',
+        price: 200000,
+      }]
+    };
+  },
+  created() {
+    this.getList();
+  },
+  methods: {
+    /** 查询列表 */
+    getList() {
+      this.loading = true;
+      // 执行查询
+      getDemoOrderPage(this.queryParams).then(response => {
+        this.list = response.data.list;
+        this.total = response.data.total;
+        this.loading = false;
+      });
+    },
+    /** 取消按钮 */
+    cancel() {
+      this.open = false;
+      this.reset();
+    },
+    /** 表单重置 */
+    reset() {
+      this.form = {
+        spuId: undefined,
+      };
+      this.resetForm("form");
+    },
+    /** 搜索按钮操作 */
+    handleQuery() {
+      this.queryParams.pageNo = 1;
+      this.getList();
+    },
+    /** 重置按钮操作 */
+    resetQuery() {
+      this.resetForm("queryForm");
+      this.handleQuery();
+    },
+    /** 新增按钮操作 */
+    handleAdd() {
+      this.reset();
+      this.open = true;
+      this.title = "发起订单";
+    },
+    /** 提交按钮 */
+    submitForm() {
+      this.$refs["form"].validate(valid => {
+        if (!valid) {
+          return;
+        }
+        // 添加的提交
+        createDemoOrder(this.form).then(response => {
+          this.$modal.msgSuccess("新增成功");
+          this.open = false;
+          this.getList();
+        });
+      });
+    },
+    /** 支付按钮操作 */
+    handlePay(row) {
+      this.$router.push({
+          name: 'PayOrderSubmit',
+          query:{
+            id: row.payOrderId
+          }
+      })
+    },
+    /** 退款按钮操作 */
+    handleRefund(row) {
+      const id = row.id;
+      this.$modal.confirm('是否确认退款编号为"' + id + '"的示例订单?').then(function() {
+        return refundDemoOrder(id);
+      }).then(() => {
+        this.getList();
+        this.$modal.msgSuccess("发起退款成功!");
+      }).catch(() => {});
+    }
+  }
+};
+</script>

+ 3 - 3
yudao-ui-admin/src/views/pay/order/index.vue

@@ -215,7 +215,7 @@
 </template>
 
 <script>
-import {getOrder, getOrderPage, exportOrderExcel} from "@/api/pay/order";
+import { getOrderDetail, getOrderPage, exportOrderExcel} from "@/api/pay/order";
 import {getMerchantListByName} from "@/api/pay/merchant";
 import {getAppListByMerchantId} from "@/api/pay/app";
 import {DICT_TYPE, getDictDatas} from "@/utils/dict";
@@ -250,7 +250,7 @@ const defaultOrderDetail = {
 };
 
 export default {
-  name: "Order",
+  name: "PayOrder",
   components: {},
   data() {
     return {
@@ -364,7 +364,7 @@ export default {
      */
     handleQueryDetails(row) {
       this.orderDetail = JSON.parse(JSON.stringify(defaultOrderDetail));
-      getOrder(row.id).then(response => {
+      getOrderDetail(row.id).then(response => {
         this.orderDetail = response.data;
         if (response.data.payOrderExtension === null) {
           this.orderDetail.payOrderExtension = Object.assign(defaultOrderDetail.payOrderExtension, {});

+ 397 - 0
yudao-ui-admin/src/views/pay/order/submit.vue

@@ -0,0 +1,397 @@
+<template>
+  <div class="app-container">
+    <!-- 支付信息 -->
+    <el-card v-loading="loading">
+      <el-descriptions title="支付信息" :column="3" border>
+        <el-descriptions-item label="支付单号">{{ payOrder.id }}</el-descriptions-item>
+        <el-descriptions-item label="商品标题">{{ payOrder.subject }}</el-descriptions-item>
+        <el-descriptions-item label="商品内容">{{ payOrder.body }}</el-descriptions-item>
+        <el-descriptions-item label="支付金额">¥{{ (payOrder.amount / 100.0).toFixed(2) }}</el-descriptions-item>
+        <el-descriptions-item label="创建时间">{{ parseTime(payOrder.createTime) }}</el-descriptions-item>
+        <el-descriptions-item label="过期时间">{{ parseTime(payOrder.expireTime) }}</el-descriptions-item>
+      </el-descriptions>
+    </el-card>
+
+    <!-- 支付选择框 -->
+    <el-card style="margin-top: 10px" v-loading="submitLoading"  element-loading-text="提交支付中...">
+      <!-- 支付宝 -->
+      <el-descriptions title="选择支付宝支付">
+      </el-descriptions>
+      <div class="pay-channel-container">
+        <div class="box" v-for="channel in aliPayChannels" :key="channel.code" @click="submit(channel.code)">
+          <img :src="icons[channel.code]">
+          <div class="title">{{ channel.name }}</div>
+        </div>
+      </div>
+      <!-- 微信支付 -->
+      <el-descriptions title="选择微信支付" style="margin-top: 20px;" />
+      <div class="pay-channel-container">
+        <div class="box" v-for="channel in wxPayChannels" :key="channel.code">
+          <img :src="icons[channel.code]">
+          <div class="title">{{ channel.name }}</div>
+        </div>
+      </div>
+      <!-- 其它支付 -->
+      <el-descriptions title="选择其它支付" style="margin-top: 20px;" />
+      <div class="pay-channel-container">
+        <div class="box" v-for="channel in otherPayChannels" :key="channel.code">
+          <img :src="icons[channel.code]">
+          <div class="title">{{ channel.name }}</div>
+        </div>
+      </div>
+    </el-card>
+
+    <!-- 展示形式:二维码 URL -->
+    <el-dialog :title="qrCode.title" :visible.sync="qrCode.visible" width="350px" append-to-body
+               :close-on-press-escape="false">
+      <qrcode-vue :value="qrCode.url" size="310" level="L" />
+    </el-dialog>
+
+    <!-- 展示形式:IFrame -->
+    <el-dialog :title="iframe.title" :visible.sync="iframe.visible" width="800px" height="800px" append-to-body
+               :close-on-press-escape="false">
+      <iframe :src="iframe.url" width="100%" />
+    </el-dialog>
+
+    <!-- 展示形式:Form -->
+    <div ref="formRef" v-html="form.value" />
+
+    <!-- 展示形式:BarCode 条形码 -->
+    <el-dialog :title="barCode.title" :visible.sync="barCode.visible" width="500px" append-to-body
+               :close-on-press-escape="false">
+      <el-form ref="form" label-width="80px">
+        <el-row>
+          <el-col :span="24">
+            <el-form-item label="条形码" prop="name">
+              <el-input v-model="barCode.value" placeholder="请输入条形码" required />
+            </el-form-item>
+          </el-col>
+          <el-col :span="24">
+            <div style="text-align: right">
+              或使用
+              <el-link type="danger" target="_blank"
+                       href="https://baike.baidu.com/item/条码支付/10711903">(扫码枪/扫码盒)</el-link>
+              扫码
+            </div>
+          </el-col>
+        </el-row>
+      </el-form>
+      <div slot="footer" class="dialog-footer">
+        <el-button type="primary" @click="submit0(barCode.channelCode)"
+                   :disabled="barCode.value.length === 0">确认支付</el-button>
+        <el-button @click="barCode.visible = false">取 消</el-button>
+      </div>
+    </el-dialog>
+  </div>
+</template>
+<script>
+import QrcodeVue from 'qrcode.vue'
+import { DICT_TYPE, getDictDatas } from "@/utils/dict";
+import { getOrder, submitOrder } from '@/api/pay/order';
+import {PayChannelEnum, PayDisplayModeEnum, PayOrderStatusEnum} from "@/utils/constants";
+
+export default {
+  name: "PayOrderSubmit",
+  components: {
+    QrcodeVue,
+  },
+  data() {
+    return {
+      id: undefined, // 请假编号
+      loading: false, // 支付信息的 loading
+      payOrder: {}, // 支付信息
+      aliPayChannels: [], // 阿里支付的渠道
+      wxPayChannels: [], // 微信支付的渠道
+      otherPayChannels: [], // 其它的支付渠道
+      icons: {
+        alipay_qr: require("@/assets/images/pay/icon/alipay_qr.svg"),
+        alipay_app: require("@/assets/images/pay/icon/alipay_app.svg"),
+        alipay_wap: require("@/assets/images/pay/icon/alipay_wap.svg"),
+        alipay_pc: require("@/assets/images/pay/icon/alipay_pc.svg"),
+        alipay_bar: require("@/assets/images/pay/icon/alipay_bar.svg"),
+        wx_app: require("@/assets/images/pay/icon/wx_app.svg"),
+        wx_lite: require("@/assets/images/pay/icon/wx_lite.svg"),
+        wx_pub: require("@/assets/images/pay/icon/wx_pub.svg"),
+        mock: require("@/assets/images/pay/icon/mock.svg"),
+      },
+      submitLoading: false, // 提交支付的 loading
+      interval: undefined, // 定时任务,轮询是否完成支付
+      qrCode: { // 展示形式:二维码
+        url: '',
+        title: '',
+        visible: false,
+      },
+      iframe: { // 展示形式:iframe
+        url: '',
+        title: '',
+        visible: false
+      },
+      form: { // 展示形式:form
+        html: '',
+      },
+      barCode: { // 展示形式:条形码
+        channelCode: '',
+        value: '',
+        title: '',
+        visible: false,
+      },
+    };
+  },
+  created() {
+    this.id = this.$route.query.id;
+    this.getDetail();
+    this.initPayChannels();
+  },
+  methods: {
+    /** 初始化支付渠道 */
+    initPayChannels() {
+      // 微信支付
+      for (const dict of getDictDatas(DICT_TYPE.PAY_CHANNEL_CODE_TYPE)) {
+        const payChannel = {
+          name: dict.label,
+          code: dict.value
+        }
+        if (dict.value.indexOf('wx_') === 0) {
+          this.wxPayChannels.push(payChannel);
+        } else if (dict.value.indexOf('alipay_') === 0) {
+          this.aliPayChannels.push(payChannel);
+        } else {
+          this.otherPayChannels.push(payChannel);
+        }
+      }
+    },
+    /** 获得支付信息 */
+    getDetail() {
+      // 1.1 未传递订单编号
+      if (!this.id) {
+        this.$message.error('未传递支付单号,无法查看对应的支付信息');
+        this.goBackToList();
+        return;
+      }
+      getOrder(this.id).then(response => {
+        // 1.2 无法查询到支付信息
+        if (!response.data) {
+          this.$message.error('支付订单不存在,请检查!');
+          this.goBackToList();
+          return;
+        }
+        // 1.3 订单已支付
+        if (response.data.status !== PayOrderStatusEnum.WAITING.status) {
+          this.$message.error('支付订单不处于待支付状态,请检查!');
+          this.goBackToList();
+          return;
+        }
+
+        // 2. 可以展示
+        this.payOrder = response.data;
+      });
+    },
+    /** 提交支付 */
+    submit(channelCode) {
+      // 条形码支付,需要特殊处理
+      if (channelCode === PayChannelEnum.ALIPAY_BAR.code) {
+        this.barCode = {
+          channelCode: channelCode,
+          value: '',
+          title: '“支付宝”条码支付',
+          visible: true
+        }
+        return;
+      }
+
+      // 默认的提交处理
+      this.submit0(channelCode)
+    },
+    submit0(channelCode) {
+      this.submitLoading = true
+      submitOrder({
+        id: this.id,
+        channelCode: channelCode,
+        ...this.buildSubmitParam(channelCode)
+      }).then(response => {
+        const data = response.data
+        if (data.displayMode === PayDisplayModeEnum.IFRAME.mode) {
+          this.displayIFrame(channelCode, data)
+        } else if (data.displayMode === PayDisplayModeEnum.URL.mode) {
+          this.displayUrl(channelCode, data)
+        } else if (data.displayMode === PayDisplayModeEnum.FORM.mode) {
+          this.displayForm(channelCode, data)
+        } else if (data.displayMode === PayDisplayModeEnum.QR_CODE.mode) {
+          this.displayQrCode(channelCode, data)
+        }
+
+        // 打开轮询任务
+        this.createQueryInterval()
+      }).catch(() => {
+        this.submitLoading = false
+      });
+    },
+    /** 构建提交支付的额外参数 */
+    buildSubmitParam(channelCode) {
+      // ① 支付宝 PC 支付时,有多种展示形态
+      if (channelCode === PayChannelEnum.ALIPAY_PC.code) {
+        // 情况【前置模式】:将二维码前置到商户的订单确认页的模式。需要商户在自己的页面中以 iframe 方式请求支付宝页面。具体支持的枚举值有以下几种:
+        // 0:订单码-简约前置模式,对应 iframe 宽度不能小于 600px,高度不能小于 300px
+        // return {
+        //   "channelExtras": {
+        //     "qr_pay_mode": "0"
+        //   }
+        // }
+        // 1:订单码-前置模式,对应iframe 宽度不能小于 300px,高度不能小于 600px
+        // return {
+        //   "channelExtras": {
+        //     "qr_pay_mode": "1"
+        //   }
+        // }
+        // 3:订单码-迷你前置模式,对应 iframe 宽度不能小于 75px,高度不能小于 75px
+        // return {
+        //   "channelExtras": {
+        //     "qr_pay_mode": "3"
+        //   }
+        // }
+        // 4:订单码-可定义宽度的嵌入式二维码,商户可根据需要设定二维码的大小
+        // return {
+        //   "channelExtras": {
+        //     "qr_pay_mode": "4"
+        //   }
+        // }
+        // 情况【跳转模式】:跳转模式下,用户的扫码界面是由支付宝生成的,不在商户的域名下。支持传入的枚举值有
+        return {
+          "channelExtras": {
+            "qr_pay_mode": "2"
+          }
+        }
+        // 情况【表单模式】:直接提交当前页面到支付宝
+        // return {
+        //   displayMode: PayDisplayModeEnum.FORM.mode
+        // }
+      }
+
+      // ② 支付宝 Wap 支付时,引导手机扫码支付
+      if (channelCode === PayChannelEnum.ALIPAY_WAP.code) {
+        return {
+          displayMode: PayDisplayModeEnum.QR_CODE.mode
+        }
+      }
+
+      // ③ 支付宝 BarCode 支付时,需要传递 authCode 条形码
+      if (channelCode === PayChannelEnum.ALIPAY_BAR.code) {
+        return {
+          "channelExtras": {
+            "auth_code": this.barCode.value
+          }
+        }
+      }
+      return {}
+    },
+    /** 提交支付后,IFrame 内置 URL 的展示形式 */
+    displayIFrame(channelCode, data) {
+      // TODO 芋艿:目前有点奇怪,支付宝总是会显示“支付环境存在风险”
+      this.iframe = {
+        title: '支付窗口',
+        url: data.displayContent,
+        visible: true
+      }
+      this.submitLoading = false
+    },
+    /** 提交支付后,URL 的展示形式 */
+    displayUrl(channelCode, data) {
+      window.open(data.displayContent)
+      this.submitLoading = false
+    },
+    /** 提交支付后,Form 的展示形式 */
+    displayForm(channelCode, data) {
+      // 渲染支付页面
+      this.form = {
+        value: data.displayContent
+      }
+      // 防抖避免重复支付
+      this.$nextTick(() => {
+        // 提交支付表单
+        this.$refs.formRef.children[0].submit();
+        setTimeout(() => {
+          this.submitLoading = false
+        }, 1000);
+      });
+    },
+    /** 提交支付后(支付宝扫码支付) */
+    displayQrCode(channelCode, data) {
+      let title = '请使用手机浏览器“扫一扫”';
+      if (channelCode === PayChannelEnum.ALIPAY_WAP.code) {
+        // 考虑到 WAP 测试,所以引导手机浏览器搞
+      } else if (channelCode.indexOf('alipay_') === 0) {
+        title = '请使用支付宝“扫一扫”扫码支付';
+      } else if (channelCode.indexOf('wx_') === 0) {
+        title = '请使用微信“扫一扫”扫码支付';
+      }
+      this.qrCode = {
+        title: title,
+        url: data.displayContent,
+        visible: true
+      }
+      this.submitLoading = false
+    },
+    /** 轮询查询任务 */
+    createQueryInterval() {
+      if (this.interval) {
+        return
+      }
+      this.interval = setInterval(() => {
+        getOrder(this.id).then(response => {
+          // 已支付
+          if (response.data.status === PayOrderStatusEnum.SUCCESS.status) {
+            this.clearQueryInterval();
+            this.$message.success('支付成功!');
+            this.goBackToList();
+          }
+          // 已取消
+          if (response.data.status === PayOrderStatusEnum.CLOSED.status) {
+            this.clearQueryInterval();
+            this.$message.error('支付已关闭!');
+            this.goBackToList();
+          }
+        })
+      }, 1000 * 2)
+    },
+    /** 清空查询任务 */
+    clearQueryInterval() {
+      // 清空各种弹窗
+      this.qrCode = {
+        title: '',
+        url: '',
+        visible: false
+      }
+      // 清空任务
+      clearInterval(this.interval)
+      this.interval = undefined
+    },
+    /** 回到列表 **/
+    goBackToList() {
+      this.$tab.closePage();
+      this.$router.go(-1);
+    }
+  }
+};
+</script>
+<style lang="scss" scoped>
+.pay-channel-container {
+  display: flex;
+  margin-top: -10px;
+  .box {
+    width: 130px;
+    border: 1px solid #e6ebf5;
+    cursor: pointer;
+    text-align: center;
+    padding-top: 10px;
+    padding-bottom: 5px;
+    margin-right: 10px;
+    img {
+      width: 40px;
+      height: 40px;
+    }
+    .title {
+      padding-top: 5px
+    }
+  }
+}
+</style>

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 9804 - 9482
yudao-ui-admin/yarn.lock