Bootstrap

沙箱支付宝alipay-easysdk java 支付能力对接

1.配置沙箱密钥并获取沙箱账号密码
1)打开支付宝开放平台主页https://open.alipay.com/platform/home.htm并登陆
2)打开https://opendocs.alipay.com/open/291/105971下载密钥生成工具
3)生成密钥(应用私钥就是下面alipay.properties中的merchantPrivateKey)
在这里插入图片描述
4)打开https://openhome.alipay.com/platform/appDaily.htm?tab=info设置支付宝公钥
下面是我已经设置好的
在这里插入图片描述

将应用公钥复制上去生成的支付宝公钥就是alipay.properties中的alipayPublicKey
5)记住沙箱账号密码
在这里插入图片描述
准备工作完成,接下来创建java工程
工程目录结构
在这里插入图片描述

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.wl.alipay</groupId>
    <artifactId>alipay</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <spring-boot-version>2.0.8.RELEASE</spring-boot-version>
        <slf4j-api-version>1.7.5</slf4j-api-version>
        <commons-lang-version>3.6</commons-lang-version>
        <MainClass>com.wl.alipay.PayApplication</MainClass>
    </properties>

    <dependencies>
        <!-- https://mvnrepository.com/artifact/com.alipay.sdk/alipay-easysdk -->
        <dependency>
            <groupId>com.alipay.sdk</groupId>
            <artifactId>alipay-easysdk</artifactId>
            <version>2.1.0</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <version>${spring-boot-version}</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>${slf4j-api-version}</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>${commons-lang-version}</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <version>${spring-boot-version}</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <version>${spring-boot-version}</version>
                <configuration>
                    <mainClass>${MainClass}</mainClass>
                    <layout>JAR</layout>
                </configuration>
                <!-- repackage  生成两个 jar.original -->
                <executions>
                    <execution>
                        <goals>
                            <goal>repackage</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
            <!-- 指定maven 打包java 版本 -->
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.1</version>
                <configuration>
                    <source>1.8</source>
                    <target>1.8</target>
                </configuration>
            </plugin>
        </plugins>
        <!-- maven  编译打包resource 和 java 目录下所有文件  maven默认资源路径是resources -->
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                    <include>*.*</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.*</include>
                    <include>*.*</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>

application.properties

server.port=80

alipay.properties
在这里插入图片描述
启动类

package com.wl.alipay;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.WebApplicationType;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceTransactionManagerAutoConfiguration;
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;

/**
 * Created by Administrator on 2020/12/9.
 */
@SpringBootApplication(exclude = {
        DataSourceAutoConfiguration.class,
        DataSourceTransactionManagerAutoConfiguration.class,
        HibernateJpaAutoConfiguration.class             //不使用数据库
},scanBasePackages = "com.wl")
public class PayApplication {

    public static void main(String[] args) {
        SpringApplication app = new SpringApplication(PayApplication.class);
        app.setWebApplicationType(WebApplicationType.SERVLET);
        app.run(args);
    }

}

config

package com.wl.alipay.config;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.config.PropertiesFactoryBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.util.Properties;

/**
 * Created by Administrator on 2020/12/5.
 */
@Configuration
public class AliPayConfig {

    private static final Logger logger = LoggerFactory.getLogger(AliPayConfig.class);

    @Bean
    public Properties aliPayProperties() throws IOException{
        PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
        propertiesFactoryBean.setLocation(new ClassPathResource("/alipay.properties"));
        propertiesFactoryBean.afterPropertiesSet();
        return propertiesFactoryBean.getObject();

    }
}

package com.wl.alipay.config;

/**
 * Created by Administrator on 2020/12/5.
 */
public class AliPayConstant {

    public static final String appId = "appId";

    public static final String alipayPublicKey = "alipayPublicKey";

    public static final String merchantCertPath = "merchantCertPath";

    public static final String alipayCertPath = "alipayCertPath";

    public static final String alipayRootCertPath = "alipayRootCertPath";

    public static final String gatewayHost = "gatewayHost";

    public static final String protocol = "protocol";

    public static final String signType = "signType";

    public static final String merchantPrivateKey = "merchantPrivateKey";

    public static final String notifyUrl = "notifyUrl";

    public static final String encryptKey = "encryptKey";

    public static final String timeoutExpress = "timeoutExpress";

    public static final String webReturnUrl = "webReturnUrl";


}

关键的服务类

package com.wl.alipay.service;

//import com.alipay.easysdk.payment.app.models.AlipayTradeAppPayResponse;

import com.alipay.easysdk.factory.Factory;
import com.alipay.easysdk.kernel.Config;
import com.alipay.easysdk.payment.app.models.AlipayTradeAppPayResponse;
import com.alipay.easysdk.payment.common.models.*;
import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.alipay.easysdk.payment.wap.models.AlipayTradeWapPayResponse;
import com.wl.alipay.config.AliPayConstant;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.Properties;

/**
 * Created by Administrator on 2019/8/2.
 */
@Service
public class AliPayService {

    private static final Logger logger = LoggerFactory.getLogger(AliPayService.class);

    private Properties aliPayProperties;

    @Autowired
    public AliPayService(Properties aliPayProperties){

        this.aliPayProperties =aliPayProperties;

        //设置Factory 全局参数
        {
            Config config = new Config();
            config.appId = aliPayProperties.getProperty(AliPayConstant.appId);

            if(StringUtils.isNotBlank(aliPayProperties.getProperty(AliPayConstant.alipayPublicKey))) {
                config.alipayPublicKey = aliPayProperties.getProperty(AliPayConstant.alipayPublicKey);
            }else{
                //如果采用非证书模式,则无需赋值下面的三个证书路径,改为赋值如上的支付宝公钥字符串即可
                String merchantCertPath = aliPayProperties.getProperty(AliPayConstant.merchantCertPath);
                String alipayCertPath = aliPayProperties.getProperty(AliPayConstant.alipayCertPath);
                String alipayRootCertPath = aliPayProperties.getProperty(AliPayConstant.alipayRootCertPath);

                if(StringUtils.isNotBlank(merchantCertPath) && StringUtils.isNotBlank(alipayCertPath)
                        && StringUtils.isNotBlank(alipayRootCertPath)){
                    config.merchantCertPath = merchantCertPath;
                    config.alipayCertPath = merchantCertPath;
                    config.alipayRootCertPath = alipayRootCertPath;
                }else {
                    logger.error("merchantCertPath:{},alipayCertPath:{},alipayRootCertPath:{}"
                            ,merchantCertPath,alipayCertPath,alipayRootCertPath);
                }
            }
            config.gatewayHost = aliPayProperties.getProperty(AliPayConstant.gatewayHost);
            config.protocol = aliPayProperties.getProperty(AliPayConstant.protocol);
            config.signType = aliPayProperties.getProperty(AliPayConstant.signType);
            config.merchantPrivateKey = aliPayProperties.getProperty(AliPayConstant.merchantPrivateKey);
            config.notifyUrl = aliPayProperties.getProperty(AliPayConstant.notifyUrl);
            //可设置AES密钥,调用AES加解密相关接口时需要(可选)
            config.encryptKey = aliPayProperties.getProperty(AliPayConstant.encryptKey);
            Factory.setOptions(config);
        }

    }

    /**
     * 对应 alipay.trade.app.pay 接口
     * 构造交易数据供商户app到支付宝下单
     */
    public AlipayTradeAppPayResponse createAppTradeForm(String subject ,String tradeNo,String totalAmount) throws Exception {

        return Factory.Payment.App()
                //单独设置超时时间 默认15分钟
                .optional("timeout_express",aliPayProperties.getProperty(AliPayConstant.timeoutExpress,"15m"))
                .pay(subject,tradeNo,totalAmount);
    }

    /**
     * 对应alipay.trade.page.pay 接口
     * 构造交易数据供pc端到支付宝下单
     */
    public AlipayTradePagePayResponse createWebTradeForm(String subject ,String tradeNo,String totalAmount,String returnUrl) throws Exception{
        return Factory.Payment.Page()
                //单独设置超时时间 默认15分钟
                .optional("timeout_express",aliPayProperties.getProperty(AliPayConstant.timeoutExpress,"15m"))
                .pay(subject,tradeNo,totalAmount, returnUrl);
    }

    /**
     *  alipay.trade.wap.pay
     *  构造交易数据供wap端到支付宝下单
     */
    public AlipayTradeWapPayResponse createWapTradeForm(String subject , String tradeNo, String totalAmount,String quitUrl, String returnUrl) throws Exception{
        return Factory.Payment.Wap()
                //单独设置超时时间 默认15分钟
                .optional("timeout_express",aliPayProperties.getProperty(AliPayConstant.timeoutExpress,"15m"))
                .pay(subject,tradeNo,totalAmount,quitUrl ,returnUrl);
    }

    /**
     * 对应alipay.trade.query(统一收单线下交易查询)
     */
    public AlipayTradeQueryResponse queryTrade(String tradeNo) throws Exception {

        return Factory.Payment.Common().query(tradeNo);
    }

    /**
     * alipay.trade.cancel
     */
    public AlipayTradeCancelResponse cancelTrade(String tradeNo) throws Exception{
        return Factory.Payment.Common().cancel(tradeNo);
    }

    /**
     * alipay.trade.close(统一收单交易关闭接口)
     */
    public AlipayTradeCloseResponse closeTrade(String tradeNo) throws Exception{
        return Factory.Payment.Common().close(tradeNo);
    }

    /**
     * alipay.trade.refund(统一收单交易退款接口)
     */
    public AlipayTradeRefundResponse refundTrade(String tradeNo,String refundAmount) throws Exception{
        return Factory.Payment.Common().refund(tradeNo,refundAmount);
    }

    /**
     * alipay.trade.fastpay.refund.query(统一收单交易退款查询)
     */
    public AlipayTradeFastpayRefundQueryResponse refundQuery(String tradeNo,String outRequestNo) throws Exception{
        return Factory.Payment.Common().queryRefund(tradeNo,outRequestNo);
    }


}

注意以上几个接口只带了几个必要的参数,如果想要设置更多的请求参数,可以使用对应Client的batchOptional或optional方法设置(在下面会讲如何设置复杂的参数)
下面仅以pc端支付测试(使用app端支付测试需要与app开发人员联调)
新建AliPayController

package com.wl.alipay.controller;

import com.alipay.easysdk.payment.page.models.AlipayTradePagePayResponse;
import com.wl.alipay.service.AliPayService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

/**
 * Created by Administrator on 2020/12/9.
 */
@RestController
@RequestMapping("/api/alipay/")
public class AliPayController {

    private AliPayService aliPayService;

    @Autowired
    public AliPayController(AliPayService aliPayService){
        this.aliPayService = aliPayService;
    }

    @RequestMapping("createWebTrade")
    public void createWebTrade(HttpServletResponse response) throws Exception{
        String tradeNo = "20150320011541010101";
        String subject = "iphone11 128G";
        String totalAmount = "0.1";
        String returnUrl =  "http://localhost/api/alipay/webReturnUrl";
        AlipayTradePagePayResponse pagePayResponse = aliPayService.createWebTradeForm(tradeNo,subject,totalAmount,returnUrl);
        response.setContentType("text/html;charset=utf-8");
        response.getWriter().write(pagePayResponse.getBody());// 直接将完整的表单html输出到页面
        response.getWriter().flush();
        response.getWriter().close();
    }

    @RequestMapping(value = "webReturnUrl",method = RequestMethod.GET)
    public Object webTradeReturnUrl(HttpServletRequest request){
        System.out.println(request.getParameterMap());
        return request.getParameterMap();
    }

}

访问http://localhost/api/alipay/createWebTrade即可跳到支付页面
支付成功后同步返回数据到http://localhost/api/alipay/webReturnUrl(支付宝推荐以异步回调通知为准)
启动项目
访问http://localhost/api/alipay/createWebTrade 如下
在这里插入图片描述
使用之前的沙箱帐户名和密码登陆
在这里插入图片描述
输入支付密码并确认付款
最后跳转到后台页面返回数据
在这里插入图片描述
测试成功
上面AliPayService中的参数都是alipay-easysdk中必须的参数,众所周知alipay的参数少则几个多则数十个,显然上面的接口是没法满足我们个性化需求的。下面介绍如何设置可选的参数
还是以alipay.trade.page.pay接口为例,参考alipay文档 我们将所有参数封装到对象里面去
参考alipay-sdk-java jar包中 com.alipay.api.domain包下的AlipayTradePagePayModel类
新建注解

package com.wl.alipay.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * Created by Administrator on 2020/12/5.
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.FIELD, ElementType.METHOD})
public @interface NameInMap {

    /**
     * 支付宝请求参数中可选map中的key 例如  totalAmount  对应  total_amount
     */
    String value();

    /**
     * 数据类型(默认String)
     */
    Class type() default String.class;

}

一个请求对象里面有十几个小的对象实在是多
在这里插入图片描述
这里只贴CreateWebTradeRequest

package com.wl.alipay.domain;


import com.wl.alipay.annotation.NameInMap;

import java.io.Serializable;
import java.util.List;

/**
 * Created by Administrator on 2020/12/8.
 */
public class CreateWebTradeRequest implements Serializable{

    /**
     * 支付成功后同步跳转的页面,是一个http/https开头的字符串(返回同步参数)
     *
     */
    private String returnUrl;

    /**
     * 商户订单号
     * 必填
     */
    private String tradeNo;

    /**
     * 销售产品码,与支付宝签约的产品码名称。注:目前电脑支付场景下仅支持FAST_INSTANT_TRADE_PAY
     * 必填
     */
    @NameInMap(value = "product_code")
    private String productCode;

    /**
     * 订单总金额,单位为元,精确到小数点后两位,取值范围为 [0.01,100000000]。金额不能为0
     * 必填
     */
    private String totalAmount;

    /**
     * 订单标题
     * 必填
     */
    private String subject;

    /**
     * 绝对超时时间,格式为 yyyy-MM-dd HH:mm:ss
     * 可选
     */
    @NameInMap(value = "time_expire")
    private String timeExpire;

    /**
     * 订单包含的商品列表信息,json数组格式,其它说明详见商品明细说明
     * 可选
     */
    @NameInMap(value = "goods_detail",type = List.class)
    private List<GoodsDetail> goodsDetail;

    /**
     * 公用回传参数,如果请求时传递了该参数,则返回给商户时会回传该参数。
     * 支付宝会在异步通知时将该参数原样返回。本参数必须进行 UrlEncode 之后才可以发送给支付宝。
     * 可选
     */
    @NameInMap(value = "passback_params")
    private String passbackParams;

    /**
     * 业务扩展参数 json对象
     * 可选
     */
    @NameInMap(value = "extend_params",type = ExtendParams.class)
    private ExtendParams extendParams;

    /**
     * 商品类型 0-虚拟类商品,1-实物类商品  注:虚拟类商品不支持使用花呗渠道
     * 可选
     */
    @NameInMap(value = "goods_type")
    private String goodsType;

    /**
     * 优惠参数   注:仅与支付宝协商后可用
     * 可选  maxLength 512
     */
    @NameInMap(value = "promo_params")
    private String promoParams;

    /**
     * 描述分账信息,json格式,详见分账参数说明
     */
    @NameInMap(value = "royalty_info",type = RoyaltyInfo.class)
    private RoyaltyInfo royaltyInfo;

    /**
     * 间连受理商户信息体,当前只对特殊银行机构特定场景下使用此字段
     */
    @NameInMap(value = "sub_merchant",type = SubMerchant.class)
    private SubMerchant subMerchant;

    /**
     * 商户原始订单号,最大长度限制32位
     */
    @NameInMap(value = "merchant_order_no")
    private String merchantOrderNo;

    /**
     * 可用渠道,用户只能在指定渠道范围内支付,多个渠道以逗号分割
     * 注,与disable_pay_channels互斥
     * 渠道列表:https://docs.open.alipay.com/common/wifww7
     */
    @NameInMap(value = "enable_pay_channels")
    private String enablePayChannels;

    /**
     * 商户门店编号
     */
    @NameInMap(value = "store_id")
    private String storeId;

    /**
     * 禁用渠道,用户不可用指定渠道支付,多个渠道以逗号分割
     * 注,与enable_pay_channels互斥
     * 渠道列表:https://docs.open.alipay.com/common/wifww7
     */
    @NameInMap(value = "disable_pay_channels")
    private String disablePayChannels;

    /**
     *PC扫码支付的方式,支持前置模式和
     * 跳转模式。
     * 前置模式是将二维码前置到商户
     * 的订单确认页的模式。需要商户在
     * 自己的页面中以 iframe 方式请求
     * 支付宝页面。具体分为以下几种:
     * 0:订单码-简约前置模式,对应 iframe 宽度不能小于600px,高度不能小于300px;
     * 1:订单码-前置模式,对应iframe 宽度不能小于 300px,高度不能小于600px;
     * 3:订单码-迷你前置模式,对应 iframe 宽度不能小于 75px,高度不能小于75px;
     * 4:订单码-可定义宽度的嵌入式二维码,商户可根据需要设定二维码的大小。
     *
     * 跳转模式下,用户的扫码界面是由支付宝生成的,不在商户的域名下。
     * 2:订单码-跳转模式
     */
    @NameInMap(value = "qr_pay_mode")
    private String qrPayMode;

    /**
     * 商户自定义二维码宽度
     * 注:qr_pay_mode=4时该参数生效
     */
    @NameInMap(value = "qrcode_width")
    private String qrcodeWidth;

    /**
     * 描述结算信息,json格式,详见结算参数说明
     */
    @NameInMap(value = "settle_info",type = SettleInfo.class)
    private SettleInfo settleInfo;

    /**
     * 开票信息
     */
    @NameInMap(value = "invoice_info",type = InvoiceInfo.class)
    private InvoiceInfo invoiceInfo;

    /**
     * 签约参数,支付后签约场景使用	与app的SingParams 参数大致相同
     */
    @NameInMap(value = "agreement_sign_params",type = AgreementSignParams.class)
    private AgreementSignParams agreementSignParams;

    /**
     * 请求后页面的集成方式。
     * 取值范围:
     * 1. ALIAPP:支付宝钱包内
     * 2. PCWEB:PC端访问
     * 默认值为PCWEB。
     */
    @NameInMap(value = "integration_type")
    private String integrationType;

    /**
     * 请求来源地址。如果使用ALIAPP的集成方式,用户中途取消支付会返回该地址。
     */
    @NameInMap(value = "request_from_url")
    private String requestFromUrl;

    /**
     * 商户传入业务信息,具体值要和支付宝约定,应用于安全,营销等参数直传场景,格式为json格式
     * https://opendocs.alipay.com/open/204/fx8ebu#%E9%9B%86%E6%88%90%E6%96%B9%E5%BC%8F
     */
    @NameInMap(value = "business_params")
    private String businessParams;

    /**
     * 外部指定买家
     * 可选
     */
    @NameInMap(value = "ext_user_info",type = ExtUserInfo.class)
    private ExtUserInfo extUserInfo;

    public String getReturnUrl() {
        return returnUrl;
    }

    public void setReturnUrl(String returnUrl) {
        this.returnUrl = returnUrl;
    }

    public String getTradeNo() {
        return tradeNo;
    }

    public void setTradeNo(String tradeNo) {
        this.tradeNo = tradeNo;
    }

    public String getProductCode() {
        return productCode;
    }

    public void setProductCode(String productCode) {
        this.productCode = productCode;
    }

    public String getTotalAmount() {
        return totalAmount;
    }

    public void setTotalAmount(String totalAmount) {
        this.totalAmount = totalAmount;
    }

    public String getSubject() {
        return subject;
    }

    public void setSubject(String subject) {
        this.subject = subject;
    }

    public String getTimeExpire() {
        return timeExpire;
    }

    public void setTimeExpire(String timeExpire) {
        this.timeExpire = timeExpire;
    }

    public List<GoodsDetail> getGoodsDetail() {
        return goodsDetail;
    }

    public void setGoodsDetail(List<GoodsDetail> goodsDetail) {
        this.goodsDetail = goodsDetail;
    }

    public String getPassbackParams() {
        return passbackParams;
    }

    public void setPassbackParams(String passbackParams) {
        this.passbackParams = passbackParams;
    }

    public ExtendParams getExtendParams() {
        return extendParams;
    }

    public void setExtendParams(ExtendParams extendParams) {
        this.extendParams = extendParams;
    }

    public String getGoodsType() {
        return goodsType;
    }

    public void setGoodsType(String goodsType) {
        this.goodsType = goodsType;
    }

    public String getPromoParams() {
        return promoParams;
    }

    public void setPromoParams(String promoParams) {
        this.promoParams = promoParams;
    }

    public RoyaltyInfo getRoyaltyInfo() {
        return royaltyInfo;
    }

    public void setRoyaltyInfo(RoyaltyInfo royaltyInfo) {
        this.royaltyInfo = royaltyInfo;
    }

    public SubMerchant getSubMerchant() {
        return subMerchant;
    }

    public void setSubMerchant(SubMerchant subMerchant) {
        this.subMerchant = subMerchant;
    }

    public String getMerchantOrderNo() {
        return merchantOrderNo;
    }

    public void setMerchantOrderNo(String merchantOrderNo) {
        this.merchantOrderNo = merchantOrderNo;
    }

    public String getEnablePayChannels() {
        return enablePayChannels;
    }

    public void setEnablePayChannels(String enablePayChannels) {
        this.enablePayChannels = enablePayChannels;
    }

    public String getStoreId() {
        return storeId;
    }

    public void setStoreId(String storeId) {
        this.storeId = storeId;
    }

    public String getDisablePayChannels() {
        return disablePayChannels;
    }

    public void setDisablePayChannels(String disablePayChannels) {
        this.disablePayChannels = disablePayChannels;
    }

    public String getQrPayMode() {
        return qrPayMode;
    }

    public void setQrPayMode(String qrPayMode) {
        this.qrPayMode = qrPayMode;
    }

    public String getQrcodeWidth() {
        return qrcodeWidth;
    }

    public void setQrcodeWidth(String qrcodeWidth) {
        this.qrcodeWidth = qrcodeWidth;
    }

    public SettleInfo getSettleInfo() {
        return settleInfo;
    }

    public void setSettleInfo(SettleInfo settleInfo) {
        this.settleInfo = settleInfo;
    }

    public InvoiceInfo getInvoiceInfo() {
        return invoiceInfo;
    }

    public void setInvoiceInfo(InvoiceInfo invoiceInfo) {
        this.invoiceInfo = invoiceInfo;
    }

    public AgreementSignParams getAgreementSignParams() {
        return agreementSignParams;
    }

    public void setAgreementSignParams(AgreementSignParams agreementSignParams) {
        this.agreementSignParams = agreementSignParams;
    }

    public String getIntegrationType() {
        return integrationType;
    }

    public void setIntegrationType(String integrationType) {
        this.integrationType = integrationType;
    }

    public String getRequestFromUrl() {
        return requestFromUrl;
    }

    public void setRequestFromUrl(String requestFromUrl) {
        this.requestFromUrl = requestFromUrl;
    }

    public String getBusinessParams() {
        return businessParams;
    }

    public void setBusinessParams(String businessParams) {
        this.businessParams = businessParams;
    }

    public ExtUserInfo getExtUserInfo() {
        return extUserInfo;
    }

    public void setExtUserInfo(ExtUserInfo extUserInfo) {
        this.extUserInfo = extUserInfo;
    }
}

在这里插入图片描述
NameInMap中的value就是对应文档的字段
在这里插入图片描述
如果是List集合 NameInMap中需要设置type=List.class

在这里插入图片描述
如果是复杂对象需要设置type=Class.class

将对象转换为Map<String,Object>的集合的工具类(Map中的key 就是上面NameInMap中value的值)

package com.wl.alipay.util;

import com.wl.alipay.annotation.NameInMap;

import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * Created by Administrator on 2020/12/5.
 */
public class AliPayUtil {

    public static Map<String,Object> genBatchOptionals(Object request) throws Exception{
        Map<String,Object> optionals = new HashMap<>();
        Class<?> clazz = request.getClass();
        convertMap(optionals,clazz,request);
        return optionals;
    }

    //只有特定类型的clazz 才能调用此方法,这里没有校验类型
    private static void convertMap(Map<String,Object> options,Class clazz,Object target) throws Exception{
        //todo   缓存field
        Field[] fields = clazz.getDeclaredFields();
        for(Field field : fields){
            NameInMap nameInMap = field.getAnnotation(NameInMap.class);
            if(nameInMap != null){
                field.setAccessible(true);
                String key = nameInMap.value();
                Object value = field.get(target);
                if(value != null) {
                    if (nameInMap.type() == String.class) {
                        options.put(key, value);
                    } else if (nameInMap.type() == List.class) {     //集合
                        List<Object> values = (List<Object>) value;   //
                        List<Object> list = new ArrayList<>();
                        options.put(key,list);
                        for(Object o : values){
                            if(isBasicType(o)){                       //基本数据类型或者String
                                list.add(o);
                            }else{
                                Map<String, Object> innerOptions = new HashMap<>();
                                list.add(innerOptions);
                                convertMap(innerOptions, o.getClass(), o);
                            }
                        }
                    } else {
                        //对象
                        Map<String, Object> innerOptions = new HashMap<>();
                        options.put(key, innerOptions);
                        convertMap(innerOptions, nameInMap.type(), value);
                    }
                }
            }
        }
    }

    //String 也算
    private static boolean isBasicType(Object o) {
        return o instanceof String || o instanceof Character || o instanceof Integer || o instanceof Long || o instanceof Byte
                || o instanceof Double || o instanceof Float || o instanceof Boolean;
    }

    
}

修改上面createWebTradeForm接口如下

/**
     * 对应alipay.trade.page.pay 接口
     * 构造交易数据供pc端到支付宝下单
     */
    public AlipayTradePagePayResponse createWebTradeForm(CreateWebTradeRequest request) throws Exception{
        return Factory.Payment.Page().batchOptional(AliPayUtil.genBatchOptionals(request))
                //单独设置超时时间 默认15分钟
                .optional("timeout_express",aliPayProperties.getProperty(AliPayConstant.timeoutExpress,"15m"))
                .pay(request.getSubject(),request.getTradeNo(),request.getTotalAmount(), 
                        "http://localhost/api/alipay/webReturnUrl");
    }

batchOptional就是批量设置请求可选参数的方法
测试genBatchOptionals方法
测试数据

package com.wl.alipay;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.wl.alipay.domain.*;
import com.wl.alipay.util.AliPayUtil;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 * Created by Administrator on 2020/12/10.
 */
public class Test {

    public static void main(String[] args) throws Exception{
        CreateWebTradeRequest request = new CreateWebTradeRequest();
        request.setTimeExpire("2016-12-31 10:05");
        {
            ExtendParams extendParams = new ExtendParams();
            extendParams.setSysServiceProviderId("2088511833207846");
            extendParams.setHbFqSellerPercent("100");
            extendParams.setHbFqNum("3");
            extendParams.setCardType("S0JP0000");
            request.setExtendParams(extendParams);
        }
        {
            SettleInfo settleInfo = new SettleInfo();
            settleInfo.setSettlePeriodTime("7d");
            List<SettleDetailInfo> list = new ArrayList<>();
            SettleDetailInfo detailInfo = new SettleDetailInfo();
            detailInfo.setAmount("0.1");
            detailInfo.setSettleEntityId("2088****;st_001");
            detailInfo.setTransInType("cardAliasNo");
            detailInfo.setTransIn("A0001");
            list.add(detailInfo);
            settleInfo.setSettleDetailInfos(list);
            request.setSettleInfo(settleInfo);
        }
        request.setSubject("iphone11");

        request.setMerchantOrderNo("20161008001");
        {
            InvoiceInfo invoiceInfo = new InvoiceInfo();
            invoiceInfo.setDetails("invoiceInfo");
            {
                InvoiceKeyInfo keyInfo = new InvoiceKeyInfo();
                keyInfo.setInvoiceMerchantName("merchantName");
                keyInfo.setTaxNum("10");
                keyInfo.setSupportInvoice(true);
                invoiceInfo.setKeyInfo(keyInfo);
            }
            request.setInvoiceInfo(invoiceInfo);
        }
        {
            RoyaltyInfo royaltyInfo = new RoyaltyInfo();
            royaltyInfo.setRoyaltyType("ROYALTY");
            {
                List<RoyaltyDetailInfos> list = new ArrayList<>();
                {
                    RoyaltyDetailInfos infos = new RoyaltyDetailInfos();
                    infos.setAmount("0.1");
                    infos.setSerialNo(1L);
                    infos.setTransIn("wwwwwww");
                    list.add(infos);
                    list.add(infos);
                }
                royaltyInfo.setRoyaltyDetailInfos(list);
            }
            request.setRoyaltyInfo(royaltyInfo);
        }
        {
            List<GoodsDetail> list = new ArrayList<>();
            GoodsDetail goodsDetail = new GoodsDetail();
            goodsDetail.setPrice("15.15");
            goodsDetail.setGoodsName("iphone11");
            list.add(goodsDetail);
            list.add(goodsDetail);
            request.setGoodsDetail(list);
        }
        Map<String,Object> map = AliPayUtil.genBatchOptionals(request);
        System.out.println(map);
        System.out.println(new ObjectMapper().writeValueAsString(map));
    }
}

输出数据(转换为json)

{
	"royalty_info": {
		"royalty_type": "ROYALTY",
		"royalty_detail_infos": [{
			"amount": "0.1",
			"trans_in": "wwwwwww",
			"serial_no": 1
		}, {
			"amount": "0.1",
			"trans_in": "wwwwwww",
			"serial_no": 1
		}]
	},
	"invoice_info": {
		"key_info": {
			"tax_num": "10",
			"is_support_invoice": true,
			"invoice_merchant_name": "merchantName"
		},
		"details": "invoiceInfo"
	},
	"time_expire": "2016-12-31 10:05",
	"extend_params": {
		"sys_service_provider_id": "2088511833207847",
		"hb_fq_seller_percent": "100",
		"hb_fq_num": "3",
		"card_type": "S0JP0000"
	},
	"settle_info": {
		"settle_period_time": "7d",
		"settle_detail_info": [{
			"amount": "0.1",
			"trans_in": "A0001",
			"settle_entity_id": "2088****;st_001",
			"trans_in_type": "cardAliasNo"
		}]
	},
	"goods_detail": [{
		"goods_name": "iphone11",
		"price": "15.15"
	}, {
		"goods_name": "iphone11",
		"price": "15.15"
	}],
	"merchant_order_no": "20161008001"
}

如果嫌使用对象麻烦,可以直接使用封装好的Map

悦读

道可道,非常道;名可名,非常名。 无名,天地之始,有名,万物之母。 故常无欲,以观其妙,常有欲,以观其徼。 此两者,同出而异名,同谓之玄,玄之又玄,众妙之门。

;