mirror of
				https://gitee.com/hhyykk/ipms-sjy.git
				synced 2025-11-04 12:18:42 +08:00 
			
		
		
		
	pay: 接入支付宝 PC 支付的跳转模式
This commit is contained in:
		@@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@@ -69,6 +69,7 @@ public abstract class AbstractPayClient<Config extends PayClientConfig> implemen
 | 
			
		||||
        this.init();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    // TODO 芋艿:后续抽取到工具类里
 | 
			
		||||
    protected Double calculateAmount(Integer amount) {
 | 
			
		||||
        return amount / 100.0;
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -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 跳转
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -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;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
		Reference in New Issue
	
	Block a user