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

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();
}
// TODO 芋艿:后续抽取到工具类里
protected Double calculateAmount(Integer amount) {
return amount / 100.0;
}

View File

@ -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 跳转
}
}

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