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

This commit is contained in:
YunaiV 2023-02-18 20:59:18 +08:00
parent df702e8d24
commit b34801f303
18 changed files with 276 additions and 172 deletions

View File

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

View File

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

View File

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

View File

@ -1,18 +1,26 @@
package cn.iocoder.yudao.framework.pay.core.client.impl.alipay; 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.core.util.StrUtil;
import cn.hutool.http.Method; 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.common.util.json.JsonUtils;
import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult; import cn.iocoder.yudao.framework.pay.core.client.PayCommonResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.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.PayChannelEnum;
import com.alibaba.fastjson.JSONObject; import cn.iocoder.yudao.framework.pay.core.enums.PayDisplayModeEnum;
import com.alipay.api.AlipayApiException; import com.alipay.api.AlipayApiException;
import com.alipay.api.domain.AlipayTradePagePayModel; import com.alipay.api.domain.AlipayTradePagePayModel;
import com.alipay.api.request.AlipayTradePagePayRequest; import com.alipay.api.request.AlipayTradePagePayRequest;
import com.alipay.api.response.AlipayTradePagePayResponse; import com.alipay.api.response.AlipayTradePagePayResponse;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import java.util.HashMap;
import java.util.Objects;
/** /**
* 支付宝PC网站支付 PayClient 实现类 * 支付宝PC网站支付 PayClient 实现类
@ -28,35 +36,72 @@ public class AlipayPcPayClient extends AbstractAlipayClient {
} }
@Override @Override
public PayCommonResult<AlipayTradePagePayResponse> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) { public PayCommonResult<PayOrderUnifiedRespDTO> doUnifiedOrder(PayOrderUnifiedReqDTO reqDTO) {
// 构建 AlipayTradePagePayModel 请求 // 1.1 构建 AlipayTradePagePayModel 请求
AlipayTradePagePayModel model = new 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(); AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
request.setBizModel(model); 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.setNotifyUrl(reqDTO.getNotifyUrl());
request.setReturnUrl(""); request.setReturnUrl(""); // TODO 芋艿待搞
// 执行请求
// 2.1 执行请求
AlipayTradePagePayResponse response; AlipayTradePagePayResponse response;
try { 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) { } catch (AlipayApiException e) {
log.error("[unifiedOrder][request({}) 发起支付失败]", JsonUtils.toJsonString(reqDTO), e); log.error("[unifiedOrder][request({}) 发起支付失败]", JsonUtils.toJsonString(reqDTO), e);
return PayCommonResult.build(e.getErrCode(), e.getErrMsg(), null, codeMapping); 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()); 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 跳转
}
} }

View File

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

View File

@ -2,9 +2,13 @@ package cn.iocoder.yudao.module.pay.controller.admin.order;
import cn.hutool.core.collection.CollectionUtil; import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.util.ObjectUtil; 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.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.convert.order.PayOrderConvert;
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO; import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayAppDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.merchant.PayMerchantDO; 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.merchant.PayMerchantService;
import cn.iocoder.yudao.module.pay.service.order.PayOrderExtensionService; 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.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.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.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@ -94,10 +91,9 @@ public class PayOrderController {
@PostMapping("/submit") @PostMapping("/submit")
@Operation(summary = "提交支付订单") @Operation(summary = "提交支付订单")
public CommonResult<AppPayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) { public CommonResult<PayOrderSubmitRespVO> submitPayOrder(@RequestBody PayOrderSubmitReqVO reqVO) {
PayOrderSubmitRespBO respDTO = payOrderService.submitPayOrder( PayOrderSubmitRespVO respVO = payOrderService.submitPayOrder(reqVO, getClientIP());
PayOrderConvert.INSTANCE.convert(reqVO, getClientIP())); return success(respVO);
return success(new AppPayOrderSubmitRespVO(respDTO.getInvokeResponse()));
} }
@GetMapping("/page") @GetMapping("/page")

View File

@ -6,11 +6,11 @@ import lombok.experimental.Accessors;
import javax.validation.constraints.NotEmpty; import javax.validation.constraints.NotEmpty;
import javax.validation.constraints.NotNull; import javax.validation.constraints.NotNull;
import java.awt.*;
import java.util.Map; import java.util.Map;
@Schema(description = "管理后台 - 支付订单提交 Request VO") @Schema(description = "管理后台 - 支付订单提交 Request VO")
@Data @Data
@Accessors(chain = true)
public class PayOrderSubmitReqVO { public class PayOrderSubmitReqVO {
@Schema(description = "支付单编号", required = true, example = "1024") @Schema(description = "支付单编号", required = true, example = "1024")
@ -24,4 +24,6 @@ public class PayOrderSubmitReqVO {
@Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数") @Schema(description = "支付渠道的额外参数,例如说,微信公众号需要传递 openid 参数")
private Map<String, String> channelExtras; private Map<String, String> channelExtras;
@Schema(description = "展示模式", example = "url") // 参见 {@link PayDisplayModeEnum} 枚举如果不传递则每个支付渠道使用默认的方式
private String displayMode;
} }

View File

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

View File

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

View File

@ -1,5 +1,6 @@
package cn.iocoder.yudao.module.pay.controller.app.order.vo; 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 io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data; import lombok.Data;
import lombok.experimental.Accessors; import lombok.experimental.Accessors;
@ -10,18 +11,5 @@ import java.util.Map;
@Schema(description = "用户 APP - 支付订单提交 Request VO") @Schema(description = "用户 APP - 支付订单提交 Request VO")
@Data @Data
@Accessors(chain = true) public class AppPayOrderSubmitReqVO extends PayOrderSubmitReqVO {
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;
} }

View File

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

View File

@ -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.common.pojo.PageResult;
import cn.iocoder.yudao.framework.pay.core.client.dto.order.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.PayOrderCreateReqDTO;
import cn.iocoder.yudao.module.pay.api.order.dto.PayOrderRespDTO; 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.admin.order.vo.*;
import cn.iocoder.yudao.module.pay.controller.app.order.vo.AppPayOrderSubmitReqVO; 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.PayOrderDO;
import cn.iocoder.yudao.module.pay.dal.dataobject.order.PayOrderExtensionDO; 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.Mapper;
import org.mapstruct.Mapping; import org.mapstruct.Mapping;
import org.mapstruct.factory.Mappers; import org.mapstruct.factory.Mappers;
@ -29,6 +30,8 @@ public interface PayOrderConvert {
PayOrderRespVO convert(PayOrderDO bean); PayOrderRespVO convert(PayOrderDO bean);
PayOrderRespDTO convert2(PayOrderDO order);
PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean); PayOrderDetailsRespVO orderDetailConvert(PayOrderDO bean);
PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean); PayOrderDetailsRespVO.PayOrderExtension orderDetailExtensionConvert(PayOrderExtensionDO bean);
@ -86,18 +89,15 @@ public interface PayOrderConvert {
return payOrderExcelVO; return payOrderExcelVO;
} }
PayOrderDO convert(PayOrderCreateReqDTO bean); PayOrderDO convert(PayOrderCreateReqDTO bean);
@Mapping(target = "id", ignore = true) @Mapping(target = "id", ignore = true)
PayOrderExtensionDO convert(PayOrderSubmitReqBO bean); PayOrderExtensionDO convert(PayOrderSubmitReqVO bean, String userIp);
PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqBO bean); PayOrderUnifiedReqDTO convert2(PayOrderSubmitReqVO reqVO);
PayOrderRespDTO convert2(PayOrderDO bean); PayOrderSubmitRespVO convert(PayOrderUnifiedRespDTO bean);
PayOrderSubmitReqBO convert(AppPayOrderSubmitReqVO bean, String userIp); AppPayOrderSubmitRespVO convert3(PayOrderSubmitRespVO bean);
PayOrderSubmitReqBO convert(PayOrderSubmitReqVO bean, String userIp);
} }

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -12,7 +12,6 @@
</el-descriptions> </el-descriptions>
</el-card> </el-card>
<!-- 支付选择框 --> <!-- 支付选择框 -->
<el-card style="margin-top: 10px" v-loading="submitLoading" element-loading-text="提交支付中..."> <el-card style="margin-top: 10px" v-loading="submitLoading" element-loading-text="提交支付中...">
<!-- 支付宝 --> <!-- 支付宝 -->
@ -42,12 +41,18 @@
</div> </div>
</el-card> </el-card>
<!-- 支付二维码 --> <!-- 展示形式二维码 -->
<el-dialog :title="qrCode.title" :visible.sync="qrCode.visible" width="350px" append-to-body <el-dialog :title="qrCode.title" :visible.sync="qrCode.visible" width="350px" append-to-body
:close-on-press-escape="false"> :close-on-press-escape="false">
<qrcode-vue :value="qrCode.url" size="310" level="H" /> <qrcode-vue :value="qrCode.url" size="310" level="H" />
</el-dialog> </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" /> <div ref="alipayWap" v-html="alipayHtml.value" />
@ -57,7 +62,7 @@
import QrcodeVue from 'qrcode.vue' import QrcodeVue from 'qrcode.vue'
import { DICT_TYPE, getDictDatas } from "@/utils/dict"; import { DICT_TYPE, getDictDatas } from "@/utils/dict";
import { getOrder, submitOrder } from '@/api/pay/order'; import { getOrder, submitOrder } from '@/api/pay/order';
import { PayChannelEnum, PayOrderStatusEnum } from "@/utils/constants"; import {PayChannelEnum, PayDisplayModeEnum, PayOrderStatusEnum} from "@/utils/constants";
export default { export default {
name: "PayOrderSubmit", name: "PayOrderSubmit",
@ -83,11 +88,16 @@ export default {
mock: require("@/assets/images/pay/icon/mock.svg"), mock: require("@/assets/images/pay/icon/mock.svg"),
}, },
submitLoading: false, // loading submitLoading: false, // loading
qrCode: { // qrCode: { //
url: '', url: '',
title: '', title: '',
visible: false, visible: false,
}, },
iframe: { // iframe
url: '',
title: '',
visible: false
},
interval: undefined, // interval: undefined, //
alipayHtml: '' // HTML alipayHtml: '' // HTML
}; };
@ -146,21 +156,65 @@ export default {
this.submitLoading = true this.submitLoading = true
submitOrder({ submitOrder({
id: this.id, id: this.id,
channelCode: channelCode channelCode: channelCode,
...this.buildSubmitParam(channelCode)
}).then(response => { }).then(response => {
const invokeResponse = response.data.invokeResponse const data = response.data
// if (data.displayMode === 'iframe') {
if (channelCode === PayChannelEnum.ALIPAY_QR.code) { this.displayIFrame(channelCode, data)
this.submitAfterAlipayQr(invokeResponse) } else if (data.displayMode === 'url') {
} else if (channelCode === PayChannelEnum.ALIPAY_PC.code this.displayUrl(channelCode, data)
|| channelCode === PayChannelEnum.ALIPAY_WAP.code) {
this.submitAfterAlipayPc(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)
// }
// //
this.createQueryInterval() 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) { submitAfterAlipayQr(invokeResponse) {
this.qrCode = { this.qrCode = {
@ -170,6 +224,19 @@ export default {
} }
this.submitLoading = false 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 网站支付) */ /** 提交支付后(支付宝 PC 网站支付) */
submitAfterAlipayPc(invokeResponse) { submitAfterAlipayPc(invokeResponse) {
// //
@ -188,6 +255,9 @@ export default {
}, },
/** 轮询查询任务 */ /** 轮询查询任务 */
createQueryInterval() { createQueryInterval() {
if (!this.interval) {
return
}
this.interval = setInterval(() => { this.interval = setInterval(() => {
getOrder(this.id).then(response => { getOrder(this.id).then(response => {
// //