Ver código fonte

pay: 接入支付宝 PC 支付的跳转模式

YunaiV 2 anos atrás
pai
commit
b34801f303
18 arquivos alterados com 275 adições e 171 exclusões
  1. 11 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java
  2. 23 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedRespDTO.java
  3. 1 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java
  4. 64 19
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java
  5. 27 0
      yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayDisplayModeEnum.java
  6. 11 15
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/PayOrderController.java
  7. 3 1
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitReqVO.java
  8. 5 8
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/admin/order/vo/PayOrderSubmitRespVO.java
  9. 3 4
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java
  10. 2 14
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java
  11. 2 10
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java
  12. 8 8
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/convert/order/PayOrderConvert.java
  13. 10 7
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java
  14. 11 9
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderServiceImpl.java
  15. 0 41
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/bo/PayOrderSubmitReqBO.java
  16. 0 23
      yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/bo/PayOrderSubmitRespBO.java
  17. 12 0
      yudao-ui-admin/src/utils/constants.js
  18. 82 12
      yudao-ui-admin/src/views/pay/order/submit.vue

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

@@ -1,5 +1,6 @@
 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 - 0
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/AbstractPayClient.java

@@ -69,6 +69,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
         this.init();
     }
 
+    // TODO 芋艿:后续抽取到工具类里
     protected Double calculateAmount(Integer amount) {
         return amount / 100.0;
     }

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

@@ -1,18 +1,26 @@
 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
 
+import cn.hutool.core.lang.Pair;
+import cn.hutool.core.map.MapUtil;
+import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.http.Method;
+import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
 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.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.HashMap;
+import java.util.Objects;
+
 
 /**
  * 支付宝【PC网站支付】的 PayClient 实现类
@@ -28,35 +36,72 @@ public class AlipayPcPayClient extends AbstractAlipayClient {
     }
 
     @Override
-    public PayCommonResult<AlipayTradePagePayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
-        // 构建 AlipayTradePagePayModel 请求
+    public PayCommonResult<PayOrderUnifiedRespDTO> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
+        // 1.1 构建 AlipayTradePagePayModel 请求
         AlipayTradePagePayModel model = new AlipayTradePagePayModel();
-        // 构建 AlipayTradePagePayRequest
+        // ① 通用的参数
+        model.setOutTradeNo(reqDTO.getMerchantOrderId());
+        model.setSubject(reqDTO.getSubject());
+        model.setTotalAmount(String.valueOf(calculateAmount(reqDTO.getAmount())));
+        model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前电脑支付场景下仅支持 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("subject", reqDTO.getSubject());
-        bizContent.put("total_amount", calculateAmount(reqDTO.getAmount()));
-        bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前电脑支付场景下仅支持 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(""); // TODO 芋艿,待搞
+
+        // 2.1 执行请求
         AlipayTradePagePayResponse response;
         try {
-            response = client.pageExecute(request, Method.GET.name());
+            if (Objects.equals(displayMode, PayDisplayModeEnum.FORM.getMode())) {
+                response = client.pageExecute(request, Method.POST.name()); // 需要特殊使用 POST 请求
+            } else {
+                response = client.pageExecute(request, Method.GET.name());
+            }
         } catch (AlipayApiException e) {
             log.error("[unifiedOrder][request({}) 发起支付失败]", JsonUtils.toJsonString(reqDTO), e);
             return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
         }
+        // 1. form
+        // 2. url
+        // 3. code
+        // 4. code url
+
+        // 2.2 处理结果
         System.out.println(response.getBody());
+        PayOrderUnifiedRespDTO respDTO = new PayOrderUnifiedRespDTO()
+                .setDisplayMode(displayMode).setDisplayContent(response.getBody());
         // 响应为表单格式,前端可嵌入响应的页面或关闭当前支付窗口
-        return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000") ,response.getMsg(), response, codeMapping);
+        return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000"),
+                response.getMsg(), respDTO, codeMapping);
     }
+
+    /**
+     * 获得最终的支付 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 跳转
+    }
+
 }

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

@@ -0,0 +1,27 @@
+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"), // 二维码的图片链接
+    ;
+
+    /**
+     * 展示模式
+     */
+    private final String mode;
+
+}

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

@@ -2,9 +2,13 @@ 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.controller.app.order.vo.AppPayOrderSubmitReqVO;
-import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
 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.PayMerchantDO;
@@ -14,16 +18,9 @@ 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 cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitRespBO;
-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.*;
@@ -94,10 +91,9 @@ public class PayOrderController {
 
     @PostMapping("/submit")
     @Operation(summary = "提交支付订单")
-    public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
-        PayOrderSubmitRespBO respDTO = payOrderService.submitPayOrder(
-                PayOrderConvert.INSTANCE.convert(reqVO, getClientIP()));
-        return success(new AppPayOrderSubmitRespVO(respDTO.getInvokeResponse()));
+    public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
+        PayOrderSubmitRespVO respVO = payOrderService.submitPayOrder(reqVO, getClientIP());
+        return success(respVO);
     }
 
     @GetMapping("/page")

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

@@ -6,11 +6,11 @@ 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
-@Accessors(chain = true)
 public class PayOrderSubmitReqVO {
 
     @Schema(description = "支付单编号", required = true, example = "1024")
@@ -24,4 +24,6 @@ public class PayOrderSubmitReqVO {
     @Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
     private Map<String, String> channelExtras;
 
+    @Schema(description = "展示模式", example = "url") // 参见 {@link PayDisplayModeEnum} 枚举。如果不传递,则每个支付渠道使用默认的方式
+    private String displayMode;
 }

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

@@ -9,15 +9,12 @@ import lombok.experimental.Accessors;
 
 @Schema(description = "管理后台 - 支付订单提交 Response VO")
 @Data
-@Accessors(chain = true)
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
 public class PayOrderSubmitRespVO {
 
-    /**
-     * 调用支付渠道的响应结果
-     */
-    private Object invokeResponse;
+    @Schema(description = "展示模式", required = true, example = "url") // 参见 PayDisplayModeEnum 枚举
+    private String displayMode;
+
+    @Schema(description = "展示内容", required = true)
+    private String displayContent;
 
 }

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

@@ -1,11 +1,11 @@
 package cn.iocoder.yudao.module.pay.controller.app.order;
 
 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.convert.order.PayOrderConvert;
 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
-import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitRespBO;
 import io.swagger.v3.oas.annotations.Operation;
 import io.swagger.v3.oas.annotations.tags.Tag;
 import lombok.extern.slf4j.Slf4j;
@@ -33,9 +33,8 @@ public class AppPayOrderController {
     @PostMapping("/submit")
     @Operation(summary = "提交支付订单")
     public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
-        PayOrderSubmitRespBO respDTO = orderService.submitPayOrder(
-                PayOrderConvert.INSTANCE.convert(reqVO, getClientIP()));
-        return success(new AppPayOrderSubmitRespVO(respDTO.getInvokeResponse()));
+        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 {
 
 }

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

@@ -2,13 +2,14 @@ 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.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.*;
 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.bo.PayOrderSubmitReqBO;
 import org.mapstruct.Mapper;
 import org.mapstruct.Mapping;
 import org.mapstruct.factory.Mappers;
@@ -29,6 +30,8 @@ public interface PayOrderConvert {
 
     PayOrderRespVO convert(PayOrderDO bean);
 
+    PayOrderRespDTO convert2(PayOrderDO order);
+
     PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean);
 
     PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean);
@@ -86,18 +89,15 @@ public interface PayOrderConvert {
         return payOrderExcelVO;
     }
 
-
     PayOrderDO convert(PayOrderCreateReqDTO bean);
 
     @Mapping(target = "id", ignore = true)
-    PayOrderExtensionDO convert(PayOrderSubmitReqBO bean);
-
-    PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqBO bean);
+    PayOrderExtensionDO convert(PayOrderSubmitReqVO bean, String userIp);
 
-    PayOrderRespDTO convert2(PayOrderDO bean);
+    PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO);
 
-    PayOrderSubmitReqBO convert(AppPayOrderSubmitReqVO bean, String userIp);
+    PayOrderSubmitRespVO convert(PayOrderUnifiedRespDTO bean);
 
-    PayOrderSubmitReqBO convert(PayOrderSubmitReqVO bean, String userIp);
+    AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean);
 
 }

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

@@ -1,16 +1,17 @@
 package cn.iocoder.yudao.module.pay.service.order;
 
+import cn.iocoder.yudao.framework.common.pojo.PageResult;
+import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
 import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyDataDTO;
+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.framework.common.pojo.PageResult;
-import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
+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 cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
-import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitReqBO;
-import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitRespBO;
 
 import javax.validation.Valid;
+import javax.validation.constraints.NotEmpty;
 import java.util.Collection;
 import java.util.List;
 import java.util.Map;
@@ -81,10 +82,12 @@ public interface PayOrderService {
      * 提交支付
      * 此时,会发起支付渠道的调用
      *
-     * @param reqDTO 提交请求
+     * @param reqVO 提交请求
+     * @param userIp 提交 IP
      * @return 提交结果
      */
-    PayOrderSubmitRespBO submitPayOrder(@Valid PayOrderSubmitReqBO reqDTO);
+    PayOrderSubmitRespVO submitPayOrder(@Valid PayOrderSubmitReqVO reqVO,
+                                        @NotEmpty(message = "提交 IP 不能为空") String userIp);
 
     /**
      * 通知支付单成功

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

@@ -12,10 +12,13 @@ import cn.iocoder.yudao.framework.pay.core.client.PayClientFactory;
 import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyDataDTO;
 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 +34,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.bo.PayOrderSubmitReqBO;
-import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitRespBO;
 import lombok.extern.slf4j.Slf4j;
 import org.springframework.stereotype.Service;
 import org.springframework.transaction.annotation.Transactional;
@@ -126,22 +127,22 @@ public class PayOrderServiceImpl implements PayOrderService {
     }
 
     @Override
-    public PayOrderSubmitRespBO submitPayOrder(PayOrderSubmitReqBO reqBO) {
+    public PayOrderSubmitRespVO submitPayOrder(PayOrderSubmitReqVO reqVO, String userIp) {
         // 1. 获得 PayOrderDO ,并校验其是否存在
-        PayOrderDO order = validatePayOrderCanSubmit(reqBO.getId());
+        PayOrderDO order = validatePayOrderCanSubmit(reqVO.getId());
         // 1.2 校验支付渠道是否有效
-        PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqBO.getChannelCode());
+        PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
         PayClient client = payClientFactory.getPayClient(channel.getId());
 
         // 2. 插入 PayOrderExtensionDO
-        PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqBO)
+        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);
 
         // 3. 调用三方接口
-        PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqBO)
+        PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO)
                 // 商户相关的字段
                 .setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
                 .setSubject(order.getSubject()).setBody(order.getBody())
@@ -152,10 +153,11 @@ public class PayOrderServiceImpl implements PayOrderService {
         CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO);
         unifiedOrderResult.checkError();
 
+        PayOrderUnifiedRespDTO xx = (PayOrderUnifiedRespDTO) unifiedOrderResult.getData();
+
         // TODO 轮询三方接口,是否已经支付的任务
         // 返回成功
-        return new PayOrderSubmitRespBO().setExtensionId(orderExtension.getId())
-                .setInvokeResponse(unifiedOrderResult.getData());
+        return PayOrderConvert.INSTANCE.convert(xx);
     }
 
     private PayOrderDO validatePayOrderCanSubmit(Long id) {

+ 0 - 41
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/bo/PayOrderSubmitReqBO.java

@@ -1,41 +0,0 @@
-package cn.iocoder.yudao.module.pay.service.order.bo;
-
-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 BO
- */
-@Data
-@Accessors(chain = true)
-public class PayOrderSubmitReqBO implements Serializable {
-
-    /**
-     * 支付单编号
-     */
-    @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/bo/PayOrderSubmitRespBO.java

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

+ 12 - 0
yudao-ui-admin/src/utils/constants.js

@@ -150,6 +150,18 @@ export const PayChannelEnum = {
   },
 }
 
+/**
+ * 支付的展示模式每局
+ */
+export const PayDisplayModeEnum = {
+  URL: {
+    "mode": "url",
+  },
+  IFRAME: {
+    "mode": "iframe",
+  },
+}
+
 /**
  * 支付类型枚举
  */

+ 82 - 12
yudao-ui-admin/src/views/pay/order/submit.vue

@@ -12,7 +12,6 @@
       </el-descriptions>
     </el-card>
 
-
     <!-- 支付选择框 -->
     <el-card style="margin-top: 10px" v-loading="submitLoading"  element-loading-text="提交支付中...">
       <!-- 支付宝 -->
@@ -42,12 +41,18 @@
       </div>
     </el-card>
 
-    <!-- 支付二维码 -->
+    <!-- 展示形式:二维码 -->
     <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="H" />
     </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>
+
     <!-- 阿里支付 -->
     <div ref="alipayWap" v-html="alipayHtml.value" />
 
@@ -57,7 +62,7 @@
 import QrcodeVue from 'qrcode.vue'
 import { DICT_TYPE, getDictDatas } from "@/utils/dict";
 import { getOrder, submitOrder } from '@/api/pay/order';
-import { PayChannelEnum, PayOrderStatusEnum } from "@/utils/constants";
+import {PayChannelEnum, PayDisplayModeEnum, PayOrderStatusEnum} from "@/utils/constants";
 
 export default {
   name: "PayOrderSubmit",
@@ -83,11 +88,16 @@ export default {
         mock: require("@/assets/images/pay/icon/mock.svg"),
       },
       submitLoading: false, // 提交支付的 loading
-      qrCode: { // 支付二维码
+      qrCode: { // 展示形式:二维码
         url: '',
         title: '',
         visible: false,
       },
+      iframe: { // 展示形式:iframe
+        url: '',
+        title: '',
+        visible: false
+      },
       interval: undefined, // 定时任务,轮询是否完成支付
       alipayHtml: '' // 阿里支付的 HTML
     };
@@ -146,21 +156,65 @@ export default {
       this.submitLoading = true
       submitOrder({
         id: this.id,
-        channelCode: channelCode
+        channelCode: channelCode,
+        ...this.buildSubmitParam(channelCode)
       }).then(response => {
-        const invokeResponse = response.data.invokeResponse
-        // 不同的支付,调用不同的策略
-        if (channelCode === PayChannelEnum.ALIPAY_QR.code) {
-          this.submitAfterAlipayQr(invokeResponse)
-        } else if (channelCode === PayChannelEnum.ALIPAY_PC.code
-          || channelCode === PayChannelEnum.ALIPAY_WAP.code) {
-          this.submitAfterAlipayPc(invokeResponse)
+        const data = response.data
+        if (data.displayMode === 'iframe') {
+          this.displayIFrame(channelCode, data)
+        } else if (data.displayMode === 'url') {
+          this.displayUrl(channelCode, data)
         }
+        // 不同的支付,调用不同的策略
+        // if (channelCode === PayChannelEnum.ALIPAY_QR.code) {
+        //   this.submitAfterAlipayQr(invokeResponse)
+        // } else if (channelCode === PayChannelEnum.ALIPAY_PC.code
+        //   || channelCode === PayChannelEnum.ALIPAY_WAP.code) {
+        //   this.submitAfterAlipayPc(invokeResponse)
+        // }
 
         // 打开轮询任务
         this.createQueryInterval()
       })
     },
+    /** 构建提交支付的额外参数 */
+    buildSubmitParam(channelCode) {
+      // 支付宝网页支付时,有多种展示形态
+      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": "2"
+        //   }
+        // }
+        // 情况【跳转模式】:跳转模式下,用户的扫码界面是由支付宝生成的,不在商户的域名下。支持传入的枚举值有
+        return {
+          "channelExtras": {
+            "qr_pay_mode": "2"
+          }
+        }
+      }
+      return {}
+    },
     /** 提交支付后(支付宝扫码支付) */
     submitAfterAlipayQr(invokeResponse) {
       this.qrCode = {
@@ -170,6 +224,19 @@ export default {
       }
       this.submitLoading = false
     },
+    displayIFrame(channelCode, data) {
+      // this.iframe = {
+      //   title: '支付窗口',
+      //   url: data.displayContent,
+      //   visible: true
+      // }
+      window.open(data.displayContent)
+    },
+    /** 提交支付后,URL 的展示形式 */
+    displayUrl(channelCode, data) {
+      window.open(data.displayContent)
+      this.submitLoading = false
+    },
     /** 提交支付后(支付宝 PC 网站支付) */
     submitAfterAlipayPc(invokeResponse) {
       // 渲染支付页面
@@ -188,6 +255,9 @@ export default {
     },
     /** 轮询查询任务 */
     createQueryInterval() {
+      if (!this.interval) {
+        return
+      }
       this.interval = setInterval(() => {
         getOrder(this.id).then(response => {
           // 已支付