Commit b34801f303773ec8dcdd9d516f606b8a5701b085

Authored by YunaiV
1 parent df702e8d

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

Showing 17 changed files with 262 additions and 158 deletions
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedReqDTO.java
1 1 package cn.iocoder.yudao.framework.pay.core.client.dto.order;
2 2  
  3 +import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
3 4 import lombok.Data;
4 5 import org.hibernate.validator.constraints.Length;
5 6 import org.hibernate.validator.constraints.URL;
... ... @@ -7,6 +8,7 @@ import org.hibernate.validator.constraints.URL;
7 8 import javax.validation.constraints.DecimalMin;
8 9 import javax.validation.constraints.NotEmpty;
9 10 import javax.validation.constraints.NotNull;
  11 +import java.awt.*;
10 12 import java.time.LocalDateTime;
11 13 import java.util.Map;
12 14  
... ... @@ -78,4 +80,13 @@ public class PayOrderUnifiedReqDTO {
78 80 */
79 81 private Map<String, String> channelExtras;
80 82  
  83 + /**
  84 + * 展示模式
  85 + *
  86 + * 如果不传递,则每个支付渠道使用默认的方式
  87 + *
  88 + * 枚举 {@link PayDisplayModeEnum}
  89 + */
  90 + private String displayMode;
  91 +
81 92 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/bo/PayOrderSubmitRespBO.java renamed to yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/dto/order/PayOrderUnifiedRespDTO.java
1   -package cn.iocoder.yudao.module.pay.service.order.bo;
  1 +package cn.iocoder.yudao.framework.pay.core.client.dto.order;
2 2  
  3 +import lombok.AllArgsConstructor;
3 4 import lombok.Data;
4 5  
5   -import java.io.Serializable;
6   -
7 6 /**
8   - * 支付单提交 Response BO
  7 + * 统一下单 Response DTO
  8 + *
  9 + * @author 芋道源码
9 10 */
10 11 @Data
11   -public class PayOrderSubmitRespBO implements Serializable {
  12 +public class PayOrderUnifiedRespDTO {
12 13  
13 14 /**
14   - * 支付拓展单的编号
  15 + * 展示模式
15 16 */
16   - private Long extensionId;
17   -
  17 + private String displayMode;
18 18 /**
19   - * 调用支付渠道的响应结果
  19 + * 展示内容
20 20 */
21   - private Object invokeResponse;
  21 + private String displayContent;
22 22  
23 23 }
... ...
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&lt;Config extends PayClientConfig&gt; implemen
69 69 this.init();
70 70 }
71 71  
  72 + // TODO 芋艿:后续抽取到工具类里
72 73 protected Double calculateAmount(Integer amount) {
73 74 return amount / 100.0;
74 75 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/client/impl/alipay/AlipayPcPayClient.java
1 1 package cn.iocoder.yudao.framework.pay.core.client.impl.alipay;
2 2  
  3 +import cn.hutool.core.lang.Pair;
  4 +import cn.hutool.core.map.MapUtil;
  5 +import cn.hutool.core.util.ObjectUtil;
3 6 import cn.hutool.core.util.StrUtil;
4 7 import cn.hutool.http.Method;
  8 +import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
5 9 import cn.iocoder.yudao.framework.common.util.json.JsonUtils;
6 10 import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
7 11 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
  12 +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
8 13 import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
9   -import com.alibaba.fastjson.JSONObject;
  14 +import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
10 15 import com.alipay.api.AlipayApiException;
11 16 import com.alipay.api.domain.AlipayTradePagePayModel;
12 17 import com.alipay.api.request.AlipayTradePagePayRequest;
13 18 import com.alipay.api.response.AlipayTradePagePayResponse;
14 19 import lombok.extern.slf4j.Slf4j;
15 20  
  21 +import java.util.HashMap;
  22 +import java.util.Objects;
  23 +
16 24  
17 25 /**
18 26 * 支付宝【PC网站支付】的 PayClient 实现类
... ... @@ -28,35 +36,72 @@ public class AlipayPcPayClient extends AbstractAlipayClient {
28 36 }
29 37  
30 38 @Override
31   - public PayCommonResult<AlipayTradePagePayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
32   - // 构建 AlipayTradePagePayModel 请求
  39 + public PayCommonResult<PayOrderUnifiedRespDTO> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
  40 + // 1.1 构建 AlipayTradePagePayModel 请求
33 41 AlipayTradePagePayModel model = new AlipayTradePagePayModel();
34   - // 构建 AlipayTradePagePayRequest
  42 + // ① 通用的参数
  43 + model.setOutTradeNo(reqDTO.getMerchantOrderId());
  44 + model.setSubject(reqDTO.getSubject());
  45 + model.setTotalAmount(String.valueOf(calculateAmount(reqDTO.getAmount())));
  46 + model.setProductCode("FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前电脑支付场景下仅支持 FAST_INSTANT_TRADE_PAY
  47 + // ② 个性化的参数
  48 + // 参考 https://www.pingxx.com/api/支付渠道 extra 参数说明.html 的 alipay_pc_direct 部分
  49 + model.setQrPayMode(MapUtil.getStr(reqDTO.getChannelExtras(), "qr_pay_mode"));
  50 + model.setQrcodeWidth(MapUtil.getLong(reqDTO.getChannelExtras(), "qr_code_width"));
  51 + // ③ 支付宝 PC 支付有多种展示模式,因此这里需要计算
  52 + String displayMode = getDisplayMode(reqDTO.getDisplayMode(), model.getQrPayMode());
  53 +
  54 + // 1.2 构建 AlipayTradePagePayRequest 请求
35 55 AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
36 56 request.setBizModel(model);
37   - JSONObject bizContent = new JSONObject();
38   - // 参数说明可查看: https://opendocs.alipay.com/open/028r8t?scene=22
39   - bizContent.put("out_trade_no", reqDTO.getMerchantOrderId());
40   - bizContent.put("subject", reqDTO.getSubject());
41   - bizContent.put("total_amount", calculateAmount(reqDTO.getAmount()));
42   - bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY"); // 销售产品码. 目前电脑支付场景下仅支持 FAST_INSTANT_TRADE_PAY
43   - // PC扫码支付的方式:支持前置模式和跳转模式。4: 订单码-可定义宽度的嵌入式二维码
44   -// bizContent.put("qr_pay_mode", "4");
45   - // 自定义二维码宽度
46   -// bizContent.put("qrcode_width", "150");
47   - request.setBizContent(bizContent.toJSONString());
48 57 request.setNotifyUrl(reqDTO.getNotifyUrl());
49   - request.setReturnUrl("");
50   - // 执行请求
  58 + request.setReturnUrl(""); // TODO 芋艿,待搞
  59 +
  60 + // 2.1 执行请求
51 61 AlipayTradePagePayResponse response;
52 62 try {
53   - response = client.pageExecute(request, Method.GET.name());
  63 + if (Objects.equals(displayMode, PayDisplayModeEnum.FORM.getMode())) {
  64 + response = client.pageExecute(request, Method.POST.name()); // 需要特殊使用 POST 请求
  65 + } else {
  66 + response = client.pageExecute(request, Method.GET.name());
  67 + }
54 68 } catch (AlipayApiException e) {
55 69 log.error("[unifiedOrder][request({}) 发起支付失败]", JsonUtils.toJsonString(reqDTO), e);
56 70 return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping);
57 71 }
  72 + // 1. form
  73 + // 2. url
  74 + // 3. code
  75 + // 4. code url
  76 +
  77 + // 2.2 处理结果
58 78 System.out.println(response.getBody());
  79 + PayOrderUnifiedRespDTO respDTO = new PayOrderUnifiedRespDTO()
  80 + .setDisplayMode(displayMode).setDisplayContent(response.getBody());
59 81 // 响应为表单格式,前端可嵌入响应的页面或关闭当前支付窗口
60   - return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000") ,response.getMsg(), response, codeMapping);
  82 + return PayCommonResult.build(StrUtil.blankToDefault(response.getCode(),"10000"),
  83 + response.getMsg(), respDTO, codeMapping);
61 84 }
  85 +
  86 + /**
  87 + * 获得最终的支付 UI 展示模式
  88 + *
  89 + * @param displayMode 前端传递的 UI 展示模式
  90 + * @param qrPayMode 前端传递的二维码模式
  91 + * @return 最终的支付 UI 展示模式
  92 + */
  93 + private String getDisplayMode(String displayMode, String qrPayMode) {
  94 + // 1.1 支付宝二维码的前置模式
  95 + if (StrUtil.equalsAny(qrPayMode, "0", "1", "3", "4")) {
  96 + return PayDisplayModeEnum.IFRAME.getMode();
  97 + }
  98 + // 1.2 支付宝二维码的跳转模式
  99 + if (StrUtil.equals(qrPayMode, "2")) {
  100 + return PayDisplayModeEnum.URL.getMode();
  101 + }
  102 + // 2. 前端传递了 UI 展示模式
  103 + return displayMode != null ? displayMode :
  104 + PayDisplayModeEnum.URL.getMode(); // 模式使用 URL 跳转
  105 + }
  106 +
62 107 }
... ...
yudao-framework/yudao-spring-boot-starter-biz-pay/src/main/java/cn/iocoder/yudao/framework/pay/core/enums/PayDisplayModeEnum.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.pay.core.enums;
  2 +
  3 +import lombok.AllArgsConstructor;
  4 +import lombok.Getter;
  5 +
  6 +/**
  7 + * 支付 UI 展示模式
  8 + *
  9 + * @author 芋道源码
  10 + */
  11 +@Getter
  12 +@AllArgsConstructor
  13 +public enum PayDisplayModeEnum {
  14 +
  15 + URL("url"), // Redirect 跳转链接的方式
  16 + IFRAME("iframe"), // IFrame 内嵌链接的方式
  17 + FORM("form"), // HTML 表单提交
  18 + QR_CODE("qr_code"), // 二维码的文字内容
  19 + QR_CODE_URL("qr_code_url"), // 二维码的图片链接
  20 + ;
  21 +
  22 + /**
  23 + * 展示模式
  24 + */
  25 + private final String mode;
  26 +
  27 +}
... ...
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;
2 2  
3 3 import cn.hutool.core.collection.CollectionUtil;
4 4 import cn.hutool.core.util.ObjectUtil;
  5 +import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  6 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  7 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  8 +import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
  9 +import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
  10 +import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
5 11 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
6   -import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
7   -import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
8 12 import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
9 13 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
10 14 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayMerchantDO;
... ... @@ -14,16 +18,9 @@ import cn.iocoder.yudao.module.pay.service.merchant.PayAppService;
14 18 import cn.iocoder.yudao.module.pay.service.merchant.PayMerchantService;
15 19 import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService;
16 20 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
17   -import cn.iocoder.yudao.framework.common.pojo.CommonResult;
18   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
19   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
20   -import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
21   -import cn.iocoder.yudao.framework.operatelog.core.annotations.OperateLog;
22   -import cn.iocoder.yudao.framework.pay.core.enums.PayChannelEnum;
23   -import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitRespBO;
24   -import io.swagger.v3.oas.annotations.tags.Tag;
25   -import io.swagger.v3.oas.annotations.Parameter;
26 21 import io.swagger.v3.oas.annotations.Operation;
  22 +import io.swagger.v3.oas.annotations.Parameter;
  23 +import io.swagger.v3.oas.annotations.tags.Tag;
27 24 import org.springframework.security.access.prepost.PreAuthorize;
28 25 import org.springframework.validation.annotation.Validated;
29 26 import org.springframework.web.bind.annotation.*;
... ... @@ -94,10 +91,9 @@ public class PayOrderController {
94 91  
95 92 @PostMapping("/submit")
96 93 @Operation(summary = "提交支付订单")
97   - public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
98   - PayOrderSubmitRespBO respDTO = payOrderService.submitPayOrder(
99   - PayOrderConvert.INSTANCE.convert(reqVO, getClientIP()));
100   - return success(new AppPayOrderSubmitRespVO(respDTO.getInvokeResponse()));
  94 + public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
  95 + PayOrderSubmitRespVO respVO = payOrderService.submitPayOrder(reqVO, getClientIP());
  96 + return success(respVO);
101 97 }
102 98  
103 99 @GetMapping("/page")
... ...
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;
6 6  
7 7 import javax.validation.constraints.NotEmpty;
8 8 import javax.validation.constraints.NotNull;
  9 +import java.awt.*;
9 10 import java.util.Map;
10 11  
11 12 @Schema(description = "管理后台 - 支付订单提交 Request VO")
12 13 @Data
13   -@Accessors(chain = true)
14 14 public class PayOrderSubmitReqVO {
15 15  
16 16 @Schema(description = "支付单编号", required = true, example = "1024")
... ... @@ -24,4 +24,6 @@ public class PayOrderSubmitReqVO {
24 24 @Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
25 25 private Map<String, String> channelExtras;
26 26  
  27 + @Schema(description = "展示模式", example = "url") // 参见 {@link PayDisplayModeEnum} 枚举。如果不传递,则每个支付渠道使用默认的方式
  28 + private String displayMode;
27 29 }
... ...
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;
9 9  
10 10 @Schema(description = "管理后台 - 支付订单提交 Response VO")
11 11 @Data
12   -@Accessors(chain = true)
13   -@Builder
14   -@NoArgsConstructor
15   -@AllArgsConstructor
16 12 public class PayOrderSubmitRespVO {
17 13  
18   - /**
19   - * 调用支付渠道的响应结果
20   - */
21   - private Object invokeResponse;
  14 + @Schema(description = "展示模式", required = true, example = "url") // 参见 PayDisplayModeEnum 枚举
  15 + private String displayMode;
  16 +
  17 + @Schema(description = "展示内容", required = true)
  18 + private String displayContent;
22 19  
23 20 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/AppPayOrderController.java
1 1 package cn.iocoder.yudao.module.pay.controller.app.order;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.CommonResult;
  4 +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
4 5 import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
5 6 import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
6 7 import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
7 8 import cn.iocoder.yudao.module.pay.service.order.PayOrderService;
8   -import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitRespBO;
9 9 import io.swagger.v3.oas.annotations.Operation;
10 10 import io.swagger.v3.oas.annotations.tags.Tag;
11 11 import lombok.extern.slf4j.Slf4j;
... ... @@ -33,9 +33,8 @@ public class AppPayOrderController {
33 33 @PostMapping("/submit")
34 34 @Operation(summary = "提交支付订单")
35 35 public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody AppPayOrderSubmitReqVO reqVO) {
36   - PayOrderSubmitRespBO respDTO = orderService.submitPayOrder(
37   - PayOrderConvert.INSTANCE.convert(reqVO, getClientIP()));
38   - return success(new AppPayOrderSubmitRespVO(respDTO.getInvokeResponse()));
  36 + PayOrderSubmitRespVO respVO = orderService.submitPayOrder(reqVO, getClientIP());
  37 + return success(PayOrderConvert.INSTANCE.convert3(respVO));
39 38 }
40 39  
41 40 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitReqVO.java
1 1 package cn.iocoder.yudao.module.pay.controller.app.order.vo;
2 2  
  3 +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
3 4 import io.swagger.v3.oas.annotations.media.Schema;
4 5 import lombok.Data;
5 6 import lombok.experimental.Accessors;
... ... @@ -10,18 +11,5 @@ import java.util.Map;
10 11  
11 12 @Schema(description = "用户 APP - 支付订单提交 Request VO")
12 13 @Data
13   -@Accessors(chain = true)
14   -public class AppPayOrderSubmitReqVO {
15   -
16   - @Schema(description = "支付单编号", required = true, example = "1024")
17   - @NotNull(message = "支付单编号不能为空")
18   - private Long id;
19   -
20   - @Schema(description = "支付渠道", required = true, example = "wx_pub")
21   - @NotEmpty(message = "支付渠道不能为空")
22   - private String channelCode;
23   -
24   - @Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
25   - private Map<String, String> channelExtras;
26   -
  14 +public class AppPayOrderSubmitReqVO extends PayOrderSubmitReqVO {
27 15 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/controller/app/order/vo/AppPayOrderSubmitRespVO.java
1 1 package cn.iocoder.yudao.module.pay.controller.app.order.vo;
2 2  
  3 +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
3 4 import io.swagger.v3.oas.annotations.media.Schema;
4 5 import lombok.AllArgsConstructor;
5 6 import lombok.Builder;
... ... @@ -9,15 +10,6 @@ import lombok.experimental.Accessors;
9 10  
10 11 @Schema(description = "用户 APP - 支付订单提交 Response VO")
11 12 @Data
12   -@Accessors(chain = true)
13   -@Builder
14   -@NoArgsConstructor
15   -@AllArgsConstructor
16   -public class AppPayOrderSubmitRespVO {
17   -
18   - /**
19   - * 调用支付渠道的响应结果
20   - */
21   - private Object invokeResponse;
  13 +public class AppPayOrderSubmitRespVO extends PayOrderSubmitRespVO {
22 14  
23 15 }
... ...
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;
2 2  
3 3 import cn.iocoder.yudao.framework.common.pojo.PageResult;
4 4 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
  5 +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
5 6 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
6 7 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO;
7 8 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.*;
8 9 import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO;
  10 +import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitRespVO;
9 11 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
10 12 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO;
11   -import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitReqBO;
12 13 import org.mapstruct.Mapper;
13 14 import org.mapstruct.Mapping;
14 15 import org.mapstruct.factory.Mappers;
... ... @@ -29,6 +30,8 @@ public interface PayOrderConvert {
29 30  
30 31 PayOrderRespVO convert(PayOrderDO bean);
31 32  
  33 + PayOrderRespDTO convert2(PayOrderDO order);
  34 +
32 35 PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean);
33 36  
34 37 PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean);
... ... @@ -86,18 +89,15 @@ public interface PayOrderConvert {
86 89 return payOrderExcelVO;
87 90 }
88 91  
89   -
90 92 PayOrderDO convert(PayOrderCreateReqDTO bean);
91 93  
92 94 @Mapping(target = "id", ignore = true)
93   - PayOrderExtensionDO convert(PayOrderSubmitReqBO bean);
94   -
95   - PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqBO bean);
  95 + PayOrderExtensionDO convert(PayOrderSubmitReqVO bean, String userIp);
96 96  
97   - PayOrderRespDTO convert2(PayOrderDO bean);
  97 + PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO);
98 98  
99   - PayOrderSubmitReqBO convert(AppPayOrderSubmitReqVO bean, String userIp);
  99 + PayOrderSubmitRespVO convert(PayOrderUnifiedRespDTO bean);
100 100  
101   - PayOrderSubmitReqBO convert(PayOrderSubmitReqVO bean, String userIp);
  101 + AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean);
102 102  
103 103 }
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/PayOrderService.java
1 1 package cn.iocoder.yudao.module.pay.service.order;
2 2  
  3 +import cn.iocoder.yudao.framework.common.pojo.PageResult;
  4 +import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
3 5 import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyDataDTO;
  6 +import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
4 7 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
5 8 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
6   -import cn.iocoder.yudao.framework.common.pojo.PageResult;
7   -import cn.iocoder.yudao.framework.common.util.collection.CollectionUtils;
  9 +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
  10 +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
8 11 import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderDO;
9   -import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
10   -import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitReqBO;
11   -import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitRespBO;
12 12  
13 13 import javax.validation.Valid;
  14 +import javax.validation.constraints.NotEmpty;
14 15 import java.util.Collection;
15 16 import java.util.List;
16 17 import java.util.Map;
... ... @@ -81,10 +82,12 @@ public interface PayOrderService {
81 82 * 提交支付
82 83 * 此时,会发起支付渠道的调用
83 84 *
84   - * @param reqDTO 提交请求
  85 + * @param reqVO 提交请求
  86 + * @param userIp 提交 IP
85 87 * @return 提交结果
86 88 */
87   - PayOrderSubmitRespBO submitPayOrder(@Valid PayOrderSubmitReqBO reqDTO);
  89 + PayOrderSubmitRespVO submitPayOrder(@Valid PayOrderSubmitReqVO reqVO,
  90 + @NotEmpty(message = "提交 IP 不能为空") String userIp);
88 91  
89 92 /**
90 93 * 通知支付单成功
... ...
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;
12 12 import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayNotifyDataDTO;
13 13 import cn.iocoder.yudao.framework.pay.core.client.dto.notify.PayOrderNotifyRespDTO;
14 14 import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedReqDTO;
  15 +import cn.iocoder.yudao.framework.pay.core.client.dto.order.PayOrderUnifiedRespDTO;
15 16 import cn.iocoder.yudao.framework.tenant.core.util.TenantUtils;
16 17 import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderCreateReqDTO;
17 18 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderExportReqVO;
18 19 import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderPageReqVO;
  20 +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitReqVO;
  21 +import cn.iocoder.yudao.module.pay.controller.admin.order.vo.PayOrderSubmitRespVO;
19 22 import cn.iocoder.yudao.module.pay.convert.order.PayOrderConvert;
20 23 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
21 24 import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayChannelDO;
... ... @@ -31,8 +34,6 @@ import cn.iocoder.yudao.module.pay.service.merchant.PayAppService;
31 34 import cn.iocoder.yudao.module.pay.service.merchant.PayChannelService;
32 35 import cn.iocoder.yudao.module.pay.service.notify.PayNotifyService;
33 36 import cn.iocoder.yudao.module.pay.service.notify.dto.PayNotifyTaskCreateReqDTO;
34   -import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitReqBO;
35   -import cn.iocoder.yudao.module.pay.service.order.bo.PayOrderSubmitRespBO;
36 37 import lombok.extern.slf4j.Slf4j;
37 38 import org.springframework.stereotype.Service;
38 39 import org.springframework.transaction.annotation.Transactional;
... ... @@ -126,22 +127,22 @@ public class PayOrderServiceImpl implements PayOrderService {
126 127 }
127 128  
128 129 @Override
129   - public PayOrderSubmitRespBO submitPayOrder(PayOrderSubmitReqBO reqBO) {
  130 + public PayOrderSubmitRespVO submitPayOrder(PayOrderSubmitReqVO reqVO, String userIp) {
130 131 // 1. 获得 PayOrderDO ,并校验其是否存在
131   - PayOrderDO order = validatePayOrderCanSubmit(reqBO.getId());
  132 + PayOrderDO order = validatePayOrderCanSubmit(reqVO.getId());
132 133 // 1.2 校验支付渠道是否有效
133   - PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqBO.getChannelCode());
  134 + PayChannelDO channel = validatePayChannelCanSubmit(order.getAppId(), reqVO.getChannelCode());
134 135 PayClient client = payClientFactory.getPayClient(channel.getId());
135 136  
136 137 // 2. 插入 PayOrderExtensionDO
137   - PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqBO)
  138 + PayOrderExtensionDO orderExtension = PayOrderConvert.INSTANCE.convert(reqVO, userIp)
138 139 .setOrderId(order.getId()).setNo(generateOrderExtensionNo())
139 140 .setChannelId(channel.getId()).setChannelCode(channel.getCode())
140 141 .setStatus(PayOrderStatusEnum.WAITING.getStatus());
141 142 orderExtensionMapper.insert(orderExtension);
142 143  
143 144 // 3. 调用三方接口
144   - PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqBO)
  145 + PayOrderUnifiedReqDTO unifiedOrderReqDTO = PayOrderConvert.INSTANCE.convert2(reqVO)
145 146 // 商户相关的字段
146 147 .setMerchantOrderId(orderExtension.getNo()) // 注意,此处使用的是 PayOrderExtensionDO.no 属性!
147 148 .setSubject(order.getSubject()).setBody(order.getBody())
... ... @@ -152,10 +153,11 @@ public class PayOrderServiceImpl implements PayOrderService {
152 153 CommonResult<?> unifiedOrderResult = client.unifiedOrder(unifiedOrderReqDTO);
153 154 unifiedOrderResult.checkError();
154 155  
  156 + PayOrderUnifiedRespDTO xx = (PayOrderUnifiedRespDTO) unifiedOrderResult.getData();
  157 +
155 158 // TODO 轮询三方接口,是否已经支付的任务
156 159 // 返回成功
157   - return new PayOrderSubmitRespBO().setExtensionId(orderExtension.getId())
158   - .setInvokeResponse(unifiedOrderResult.getData());
  160 + return PayOrderConvert.INSTANCE.convert(xx);
159 161 }
160 162  
161 163 private PayOrderDO validatePayOrderCanSubmit(Long id) {
... ...
yudao-module-pay/yudao-module-pay-biz/src/main/java/cn/iocoder/yudao/module/pay/service/order/bo/PayOrderSubmitReqBO.java deleted
1   -package cn.iocoder.yudao.module.pay.service.order.bo;
2   -
3   -import lombok.Data;
4   -import lombok.experimental.Accessors;
5   -
6   -import javax.validation.constraints.NotEmpty;
7   -import javax.validation.constraints.NotNull;
8   -import java.io.Serializable;
9   -import java.util.Map;
10   -
11   -/**
12   - * 支付单提交 Request BO
13   - */
14   -@Data
15   -@Accessors(chain = true)
16   -public class PayOrderSubmitReqBO implements Serializable {
17   -
18   - /**
19   - * 支付单编号
20   - */
21   - @NotNull(message = "支付单编号不能为空")
22   - private Long id;
23   -
24   - /**
25   - * 支付渠道
26   - */
27   - @NotEmpty(message = "支付渠道不能为空")
28   - private String channelCode;
29   -
30   - /**
31   - * 用户 IP
32   - */
33   - @NotEmpty(message = "用户 IP 不能为空")
34   - private String userIp;
35   -
36   - /**
37   - * 支付渠道的额外参数
38   - */
39   - private Map<String, String> channelExtras;
40   -
41   -}
yudao-ui-admin/src/utils/constants.js
... ... @@ -151,6 +151,18 @@ export const PayChannelEnum = {
151 151 }
152 152  
153 153 /**
  154 + * 支付的展示模式每局
  155 + */
  156 +export const PayDisplayModeEnum = {
  157 + URL: {
  158 + "mode": "url",
  159 + },
  160 + IFRAME: {
  161 + "mode": "iframe",
  162 + },
  163 +}
  164 +
  165 +/**
154 166 * 支付类型枚举
155 167 */
156 168 export const PayType = {
... ...
yudao-ui-admin/src/views/pay/order/submit.vue
... ... @@ -12,7 +12,6 @@
12 12 </el-descriptions>
13 13 </el-card>
14 14  
15   -
16 15 <!-- 支付选择框 -->
17 16 <el-card style="margin-top: 10px" v-loading="submitLoading" element-loading-text="提交支付中...">
18 17 <!-- 支付宝 -->
... ... @@ -42,12 +41,18 @@
42 41 </div>
43 42 </el-card>
44 43  
45   - <!-- 支付二维码 -->
  44 + <!-- 展示形式:二维码 -->
46 45 <el-dialog :title="qrCode.title" :visible.sync="qrCode.visible" width="350px" append-to-body
47 46 :close-on-press-escape="false">
48 47 <qrcode-vue :value="qrCode.url" size="310" level="H" />
49 48 </el-dialog>
50 49  
  50 + <!-- 展示形式:iframe -->
  51 + <el-dialog :title="iframe.title" :visible.sync="iframe.visible" width="800px" height="800px" append-to-body
  52 + :close-on-press-escape="false">
  53 + <iframe :src="iframe.url" width="100%" />
  54 + </el-dialog>
  55 +
51 56 <!-- 阿里支付 -->
52 57 <div ref="alipayWap" v-html="alipayHtml.value" />
53 58  
... ... @@ -57,7 +62,7 @@
57 62 import QrcodeVue from 'qrcode.vue'
58 63 import { DICT_TYPE, getDictDatas } from "@/utils/dict";
59 64 import { getOrder, submitOrder } from '@/api/pay/order';
60   -import { PayChannelEnum, PayOrderStatusEnum } from "@/utils/constants";
  65 +import {PayChannelEnum, PayDisplayModeEnum, PayOrderStatusEnum} from "@/utils/constants";
61 66  
62 67 export default {
63 68 name: "PayOrderSubmit",
... ... @@ -83,11 +88,16 @@ export default {
83 88 mock: require("@/assets/images/pay/icon/mock.svg"),
84 89 },
85 90 submitLoading: false, // 提交支付的 loading
86   - qrCode: { // 支付二维码
  91 + qrCode: { // 展示形式:二维码
87 92 url: '',
88 93 title: '',
89 94 visible: false,
90 95 },
  96 + iframe: { // 展示形式:iframe
  97 + url: '',
  98 + title: '',
  99 + visible: false
  100 + },
91 101 interval: undefined, // 定时任务,轮询是否完成支付
92 102 alipayHtml: '' // 阿里支付的 HTML
93 103 };
... ... @@ -146,21 +156,65 @@ export default {
146 156 this.submitLoading = true
147 157 submitOrder({
148 158 id: this.id,
149   - channelCode: channelCode
  159 + channelCode: channelCode,
  160 + ...this.buildSubmitParam(channelCode)
150 161 }).then(response => {
151   - const invokeResponse = response.data.invokeResponse
152   - // 不同的支付,调用不同的策略
153   - if (channelCode === PayChannelEnum.ALIPAY_QR.code) {
154   - this.submitAfterAlipayQr(invokeResponse)
155   - } else if (channelCode === PayChannelEnum.ALIPAY_PC.code
156   - || channelCode === PayChannelEnum.ALIPAY_WAP.code) {
157   - this.submitAfterAlipayPc(invokeResponse)
  162 + const data = response.data
  163 + if (data.displayMode === 'iframe') {
  164 + this.displayIFrame(channelCode, data)
  165 + } else if (data.displayMode === 'url') {
  166 + this.displayUrl(channelCode, data)
158 167 }
  168 + // 不同的支付,调用不同的策略
  169 + // if (channelCode === PayChannelEnum.ALIPAY_QR.code) {
  170 + // this.submitAfterAlipayQr(invokeResponse)
  171 + // } else if (channelCode === PayChannelEnum.ALIPAY_PC.code
  172 + // || channelCode === PayChannelEnum.ALIPAY_WAP.code) {
  173 + // this.submitAfterAlipayPc(invokeResponse)
  174 + // }
159 175  
160 176 // 打开轮询任务
161 177 this.createQueryInterval()
162 178 })
163 179 },
  180 + /** 构建提交支付的额外参数 */
  181 + buildSubmitParam(channelCode) {
  182 + // 支付宝网页支付时,有多种展示形态
  183 + if (channelCode === PayChannelEnum.ALIPAY_PC.code) {
  184 + // 情况【前置模式】:将二维码前置到商户的订单确认页的模式。需要商户在自己的页面中以 iframe 方式请求支付宝页面。具体支持的枚举值有以下几种:
  185 + // 0:订单码-简约前置模式,对应 iframe 宽度不能小于 600px,高度不能小于 300px
  186 + // return {
  187 + // "channelExtras": {
  188 + // "qr_pay_mode": "0"
  189 + // }
  190 + // }
  191 + // 1:订单码-前置模式,对应iframe 宽度不能小于 300px,高度不能小于 600px
  192 + // return {
  193 + // "channelExtras": {
  194 + // "qr_pay_mode": "1"
  195 + // }
  196 + // }
  197 + // 3:订单码-迷你前置模式,对应 iframe 宽度不能小于 75px,高度不能小于 75px
  198 + // return {
  199 + // "channelExtras": {
  200 + // "qr_pay_mode": "3"
  201 + // }
  202 + // }
  203 + // 4:订单码-可定义宽度的嵌入式二维码,商户可根据需要设定二维码的大小
  204 + // return {
  205 + // "channelExtras": {
  206 + // "qr_pay_mode": "2"
  207 + // }
  208 + // }
  209 + // 情况【跳转模式】:跳转模式下,用户的扫码界面是由支付宝生成的,不在商户的域名下。支持传入的枚举值有
  210 + return {
  211 + "channelExtras": {
  212 + "qr_pay_mode": "2"
  213 + }
  214 + }
  215 + }
  216 + return {}
  217 + },
164 218 /** 提交支付后(支付宝扫码支付) */
165 219 submitAfterAlipayQr(invokeResponse) {
166 220 this.qrCode = {
... ... @@ -170,6 +224,19 @@ export default {
170 224 }
171 225 this.submitLoading = false
172 226 },
  227 + displayIFrame(channelCode, data) {
  228 + // this.iframe = {
  229 + // title: '支付窗口',
  230 + // url: data.displayContent,
  231 + // visible: true
  232 + // }
  233 + window.open(data.displayContent)
  234 + },
  235 + /** 提交支付后,URL 的展示形式 */
  236 + displayUrl(channelCode, data) {
  237 + window.open(data.displayContent)
  238 + this.submitLoading = false
  239 + },
173 240 /** 提交支付后(支付宝 PC 网站支付) */
174 241 submitAfterAlipayPc(invokeResponse) {
175 242 // 渲染支付页面
... ... @@ -188,6 +255,9 @@ export default {
188 255 },
189 256 /** 轮询查询任务 */
190 257 createQueryInterval() {
  258 + if (!this.interval) {
  259 + return
  260 + }
191 261 this.interval = setInterval(() => {
192 262 getOrder(this.id).then(response => {
193 263 // 已支付
... ...