支付宝支付(2)之公钥证书方式

上一篇介绍了最常用的普通公钥方式整合app支付宝支付,此篇主要介绍公钥证书方式,实际项目中因为业务的特殊性两种支付方式统一用同一套接口,根据不同参数对差异部分进行区分,这里为更清楚的介绍整合方法便提炼出另一套接口,大同小异,可以根据实际情况处理:
支付宝支付(1)之普通公钥方式

一、签名

参考链接:https://docs.open.alipay.com/291/105971/
跟上一篇不同的是这次选择公钥证书方式:
在这里插入图片描述
按照文档一步步就可以生成公钥证书及私钥等相关文件
在这里插入图片描述
需要将这三个文件上传到服务器,使用中都是使用的文件根路径
依赖跟工具类等其他文件跟普通公钥一样,这里无需再重复,直接上重点

二、生成APP支付订单信息

//构造client
CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
//设置网关地址
certAlipayRequest.setServerUrl("https://openapi.alipay.com/gateway.do");
//设置应用Id
certAlipayRequest.setAppId(app_id);
//设置应用私钥
certAlipayRequest.setPrivateKey(privateKey);
//设置请求格式,固定值json
certAlipayRequest.setFormat("json");
//设置字符集
certAlipayRequest.setCharset(charset);
//设置签名类型
certAlipayRequest.setSignType(sign_type);
//设置应用公钥证书路径
certAlipayRequest.setCertPath(app_cert_path);
//设置支付宝公钥证书路径
certAlipayRequest.setAlipayPublicCertPath(alipay_cert_path);
//设置支付宝根证书路径
certAlipayRequest.setRootCertPath(alipay_root_cert_path);
//构造client
AlipayClient alipayClient = new DefaultAlipayClient(certAlipayRequest);

//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
model.setBody("我是测试数据");
model.setSubject("App支付测试Java");
model.setOutTradeNo(outtradeno);
model.setTimeoutExpress("30m");
model.setTotalAmount("0.01");
model.setProductCode("QUICK_MSECURITY_PAY");
request.setBizModel(model);
request.setNotifyUrl("商户外网可以访问的异步地址");
try {
        //这里和普通的接口调用不同,使用的是sdkExecute
        AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
        System.out.println(response.getBody());//就是orderString 可以直接给客户端请求,无需再做处理。
    } catch (AlipayApiException e) {
        e.printStackTrace();
}

调用此逻辑,返回orderString 字符串,此字符串直接发给客户端调起支付宝支付即可,无需做其他处理。
其中需要配置回调地址,就是下面要说的处理支付宝回调的接口地址。

三、处理支付宝异步回调

//获取支付宝POST过来反馈信息
Map<String,String> params = new HashMap<String,String>();
Map requestParams = request.getParameterMap();
for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
    String name = (String) iter.next();
    String[] values = (String[]) requestParams.get(name);
    String valueStr = "";
    for (int i = 0; i < values.length; i++) {
        valueStr = (i == values.length - 1) ? valueStr + values[i]
                    : valueStr + values[i] + ",";
    }
    //乱码解决,这段代码在出现乱码时使用。
  //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
  params.put(name, valueStr);
}
//切记alipaypublickey是支付宝的公钥,请去open.alipay.com对应应用下查看。
//boolean AlipaySignature.rsaCertCheckV1(Map<String, String> params, String publicKeyCertPath, String charset,String signType)
boolean flag = AlipaySignature.rsaCertCheckV1(params, publicKeyCertPath, charset,"RSA2")

四、具体项目代码

(代码使用了springboot工程,用到lombok等,这些都可以根据实际情况自己选择使用,这都不是重点)
这里附上整合支付宝app支付的整体后台服务代码,相关参数跟业务逻辑需要根据自己业务场景跟需求进行调整或者补充,这里只做与支付宝对接的部分:

项目整体结构

在这里插入图片描述
整体结构与上一篇大致相同,其中加了配置类跟证书文件

1、ZrkAliPayCertConfig.java

package com.zrk.alipay;

/**
 * @Description: 支付宝支付配置类(公钥证书)
 * @Author: zrk
 * @Date: 2019/9/10
 */
public class ZrkAliPayCertConfig {
    /**
     * 商户私钥
     */
    public static String APP_PRIVATE_KEY = "***";

    /**
     * 支付宝APPID
     */
    public static String APPID = "***";

    /**
     * 应用公钥证书路径
     */
    public static String app_cert_path = "CRT/appCertPublicKey.crt";

    /**
     * 支付宝公钥证书文件路径
     */
    public static String alipay_cert_path = "CRT/alipayCertPublicKey_RSA2.crt";

    /**
     * 支付宝CA根证书文件路径
     */
    public static String alipay_root_cert_path = "CRT/alipayRootCert.crt";

    /**
     * 请求网关
     */
    public static String SERVERURL = "https://openapi.alipay.com/gateway.do";

    /**
     * 回调地址
     */
    public static String ALIPAY_NOTIFY_URL = "http://zrk.com/thirdPay/alipayCertNotify";

    /**
     * 字符集
     */
    public static String CHARSET = "utf-8";

    /**
     * 签名类型
     */
    public static String SIGN_TYPE = "RSA2";

    /**
     * 格式
     */
    public static String FORMAT = "json";
}

注:其中三个路径是文件名,使用时需要拼接上根路径

2、ThirdPayController.java

package com.zrk.controller;

import com.zrk.model.ResultStatus;
import com.zrk.request.PayRequest;
import com.zrk.service.ThirdPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;

/**
 * @Description: 第三方支付接口
 * @Author: zrk
 * @Date: 2019/9/10
 */
@Slf4j
@RestController
@RequestMapping("thirdPay")
public class ThirdPayController {

    @Resource
    private ThirdPayService thirdPayService;

    /**
     * 支付宝下订单接口
     *      参数可根据自己的业务需求传相应参数
     * @param request
     * @return
     */
    @GetMapping(value = "aliPayUnifiedOrder")
    public ResultStatus aliPayUnifiedOrder(PayRequest request){

        return thirdPayService.aliPayUnifiedOrder(request);
    }

    /**
     * 支付宝回调接口
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "aliPayNotify")
    @ResponseBody
    public String aliPayNotify(HttpServletRequest request, HttpServletResponse response) throws Exception{
        //获取支付宝POST过来反馈信息
        Map requestParams = request.getParameterMap();
        return thirdPayService.aliPayNotify(requestParams);
    }

    /**
     * 支付宝下订单接口(公钥证书方式)
     *      参数可根据自己的业务需求传相应参数
     * @param request
     * @return
     */
    @GetMapping(value = "aliPayCertUnifiedOrder")
    public ResultStatus aliPayCertUnifiedOrder(PayRequest request){

        return thirdPayService.aliPayCertUnifiedOrder(request);
    }

    /**
     * 支付宝回调接口(公钥证书方式)
     * @param request
     * @param response
     * @return
     * @throws Exception
     */
    @RequestMapping(value = "aliPayCertNotify")
    @ResponseBody
    public String aliPayCertNotify(HttpServletRequest request, HttpServletResponse response) throws Exception{
        //获取支付宝POST过来反馈信息
        Map requestParams = request.getParameterMap();
        return thirdPayService.aliPayCertNotify(requestParams);
    }
}

3、service

ThirdPayService.java

package com.zrk.service;

import com.zrk.model.ResultStatus;
import com.zrk.request.PayRequest;

import java.util.Map;

/**
 * @Description:
 * @Author: zrk
 * @Date: 2019/9/10
 */
public interface ThirdPayService {

    /**
     * 支付宝支付统一下单接口
     * @param request
     * @return
     */
    ResultStatus aliPayUnifiedOrder(PayRequest request);

    /**
     * 支付宝回调
     * @param requestParams
     * @return
     */
    String aliPayNotify(Map requestParams);

    /**
     * 支付宝支付统一下单接口(公钥证书方式)
     * @param request
     * @return
     */
    ResultStatus aliPayCertUnifiedOrder(PayRequest request);

    /**
     * 支付宝回调(公钥证书方式)
     * @param requestParams
     * @return
     */
    String aliPayCertNotify(Map requestParams);
}

ThirdPayServiceImpl.java

package com.zrk.service.impl;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.CertAlipayRequest;
import com.alipay.api.DefaultAlipayClient;
import com.alipay.api.domain.AlipayTradeAppPayModel;
import com.alipay.api.internal.util.AlipaySignature;
import com.alipay.api.request.AlipayTradeAppPayRequest;
import com.alipay.api.response.AlipayTradeAppPayResponse;
import com.zrk.alipay.ZrkAliPayCertConfig;
import com.zrk.alipay.ZrkAliPayConfig;
import com.zrk.alipay.ZrkAliPayUtil;
import com.zrk.enums.ResultStatusEnum;
import com.zrk.model.ResultStatus;
import com.zrk.request.PayRequest;
import com.zrk.service.ThirdPayService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.net.URLEncoder;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

/**
 * @Description:
 * @Author: zrk
 * @Date: 2019/9/10
 */
@Slf4j
@Service
public class ThirdPayServiceImpl implements ThirdPayService {

    /**
     * 文件跟路径
     */
    @Value("${alipay_cert_file_path}")
    private String AliPay_CERT_FILE_PATH;

    /**
     * 分隔符
     */
    private final static  String attachRegex = "ZRKAPP";

    @Override
    public ResultStatus aliPayUnifiedOrder(PayRequest payRequest) {

        /**
         * 定义变量,可以根据实际需求获取并生成相应变量,变量值仅供参考
         */
        String body = "商品名称";
        String outTradeNo = ZrkAliPayUtil.getOutTradeNo();
        Double totalFee = 0.01;
        /**
         * 拼接自己的业务参数
         *  例如 用户id + 商品id + 订单id
         *  用指定分隔符进行拼接,以便后续做业务处理
         */
        String passBackParams = "123" + attachRegex + "111" + attachRegex + "222";

        /**
         * 以下部分可以共用,复制即可
         * *********************************************************
         */
        //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
        //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setBody(body);
        model.setSubject(body);
        model.setOutTradeNo(outTradeNo);
        model.setTimeoutExpress("30m");
        model.setTotalAmount(totalFee + "");
        model.setProductCode("QUICK_MSECURITY_PAY");
        model.setPassbackParams(URLEncoder.encode(passBackParams));
        request.setBizModel(model);
        request.setNotifyUrl(ZrkAliPayConfig.ALIPAY_NOTIFY_URL);
        log.info(">>>>支付宝统一下单接口请求参数:" + model.getBody() + "," + model.getOutTradeNo() + "," + model.getTotalAmount());

        /**实例化客户端*/
        AlipayClient alipayClient = new DefaultAlipayClient(
                ZrkAliPayConfig.SERVERURL,
                ZrkAliPayConfig.APPID,
                ZrkAliPayConfig.APP_PRIVATE_KEY,
                ZrkAliPayConfig.FORMAT,
                ZrkAliPayConfig.CHARSET,
                ZrkAliPayConfig.ALIPAY_PUBLIC_KEY,
                ZrkAliPayConfig.SIGN_TYPE);
        try {
            //这里和普通的接口调用不同,使用的是sdkExecute
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
            //就是orderString 可以直接给客户端请求,无需再做处理。
            log.info(">>>生成调用支付宝参数" + response.getBody());
            /**************************************************************/
            ResultStatus resultStatus = new ResultStatus(ResultStatusEnum.SUCCESS.getStatus());
            resultStatus.setData(response.getBody());
            return resultStatus;
        } catch (AlipayApiException e) {
            log.error(e.getMessage(), e);
        }
        return new ResultStatus(ResultStatusEnum.ERROR.getStatus(),"支付宝下订单失败");
    }

    @Override
    public String aliPayNotify(Map requestParams) {
        log.info(">>>支付宝回调参数:" + requestParams);
        Map<String,String> params = new HashMap<>();
        for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            //乱码解决,这段代码在出现乱码时使用。
            //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }
        log.info(">>>支付宝回调参数解析:" + params);
        try {
            //切记alipaypublickey是支付宝的公钥,请去open.alipay.com对应应用下查看。
            boolean flag = AlipaySignature.rsaCheckV1(
                    params,
                    ZrkAliPayConfig.ALIPAY_PUBLIC_KEY,
                    ZrkAliPayConfig.CHARSET, ZrkAliPayConfig.SIGN_TYPE);
            if(flag) {
                log.info(">>>支付宝回调签名认证成功");
                //商户订单号
                String out_trade_no = params.get("out_trade_no");
                //交易状态
                String trade_status = params.get("trade_status");
                //交易金额
                String amount = params.get("total_amount");
                //商户app_id
                String app_id = params.get("app_id");

                if ("TRADE_SUCCESS".equals(trade_status) || "TRADE_FINISHED".equals(trade_status)) {
                    /**
                     * 自己的业务处理
                     */
                } else {
                    log.error("没有处理支付宝回调业务,支付宝交易状态:{},params:{}",trade_status,params);
                }
            } else {
                log.info("支付宝回调签名认证失败,signVerified=false, params:{}", params);
                return "failure";
            }
        } catch (Exception e){
            log.error(e.getMessage(), e);
            log.info("支付宝回调签名认证失败,signVerified=false, params:{}", params);
            return "failure";
        }
        return "success";//请不要修改或删除
    }

    @Override
    public ResultStatus aliPayCertUnifiedOrder(PayRequest payRequest) {
        /**
         * 定义变量,可以根据实际需求获取并生成相应变量,变量值仅供参考
         */
        String body = "商品名称";
        String outTradeNo = ZrkAliPayUtil.getOutTradeNo();
        Double totalFee = 0.01;
        /**
         * 拼接自己的业务参数
         *  例如 用户id + 商品id + 订单id
         *  用指定分隔符进行拼接,以便后续做业务处理
         */
        String passBackParams = "123" + attachRegex + "111" + attachRegex + "222";

        /**
         * 以下部分可以共用,复制即可
         * *********************************************************
         */
        //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
        AlipayTradeAppPayRequest request = new AlipayTradeAppPayRequest();
        //SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式(model和biz_content同时存在的情况下取biz_content)。
        AlipayTradeAppPayModel model = new AlipayTradeAppPayModel();
        model.setBody(body);
        model.setSubject(body);
        model.setOutTradeNo(outTradeNo);
        model.setTimeoutExpress("30m");
        model.setTotalAmount(totalFee + "");
        model.setProductCode("QUICK_MSECURITY_PAY");
        model.setPassbackParams(URLEncoder.encode(passBackParams));
        request.setBizModel(model);
        request.setNotifyUrl(ZrkAliPayConfig.ALIPAY_NOTIFY_URL);
        log.info(">>>>支付宝统一下单接口请求参数:" + model.getBody() + "," + model.getOutTradeNo() + "," + model.getTotalAmount());

        /**实例化客户端*/
        CertAlipayRequest certAlipayRequest = new CertAlipayRequest();
        certAlipayRequest.setServerUrl(ZrkAliPayCertConfig.SERVERURL);
        certAlipayRequest.setAppId(ZrkAliPayCertConfig.APPID);
        certAlipayRequest.setPrivateKey(ZrkAliPayCertConfig.APP_PRIVATE_KEY);
        certAlipayRequest.setFormat("json");
        certAlipayRequest.setCharset(ZrkAliPayCertConfig.CHARSET);
        certAlipayRequest.setSignType(ZrkAliPayCertConfig.SIGN_TYPE);
        certAlipayRequest.setCertPath(AliPay_CERT_FILE_PATH + ZrkAliPayCertConfig.app_cert_path);
        certAlipayRequest.setAlipayPublicCertPath(AliPay_CERT_FILE_PATH + ZrkAliPayCertConfig.alipay_cert_path);
        certAlipayRequest.setRootCertPath(AliPay_CERT_FILE_PATH + ZrkAliPayCertConfig.alipay_root_cert_path);
        try {
            //构造client
            AlipayClient alipayClient = new DefaultAlipayClient(certAlipayRequest);
            AlipayTradeAppPayResponse response = alipayClient.sdkExecute(request);
            //就是orderString 可以直接给客户端请求,无需再做处理。
            log.info(">>>生成调用支付宝参数" + response.getBody());
            /**************************************************************/
            ResultStatus resultStatus = new ResultStatus(ResultStatusEnum.SUCCESS.getStatus());
            resultStatus.setData(response.getBody());
            return resultStatus;
        } catch (AlipayApiException e) {
            log.error(e.getMessage(), e);
        }
        return new ResultStatus(ResultStatusEnum.ERROR.getStatus(),"支付宝下订单失败");
    }

    @Override
    public String aliPayCertNotify(Map requestParams) {
        log.info(">>>支付宝回调参数:" + requestParams);
        Map<String,String> params = new HashMap<>();
        for (Iterator iter = requestParams.keySet().iterator(); iter.hasNext();) {
            String name = (String) iter.next();
            String[] values = (String[]) requestParams.get(name);
            String valueStr = "";
            for (int i = 0; i < values.length; i++) {
                valueStr = (i == values.length - 1) ? valueStr + values[i]
                        : valueStr + values[i] + ",";
            }
            //乱码解决,这段代码在出现乱码时使用。
            //valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
            params.put(name, valueStr);
        }
        log.info(">>>支付宝回调参数解析:" + params);
        try {
            //切记alipaypublickey是支付宝的公钥,请去open.alipay.com对应应用下查看。
            boolean flag = AlipaySignature.rsaCertCheckV1(
                    params,
                    AliPay_CERT_FILE_PATH+ZrkAliPayCertConfig.alipay_cert_path,
                    ZrkAliPayCertConfig.CHARSET,
                    ZrkAliPayCertConfig.SIGN_TYPE);
            if(flag) {
                log.info(">>>支付宝回调签名认证成功");
                //商户订单号
                String out_trade_no = params.get("out_trade_no");
                //交易状态
                String trade_status = params.get("trade_status");
                //交易金额
                String amount = params.get("total_amount");
                //商户app_id
                String app_id = params.get("app_id");

                if ("TRADE_SUCCESS".equals(trade_status) || "TRADE_FINISHED".equals(trade_status)) {
                    /**
                     * 自己的业务处理
                     */
                } else {
                    log.error("没有处理支付宝回调业务,支付宝交易状态:{},params:{}",trade_status,params);
                }
            } else {
                log.info("支付宝回调签名认证失败,signVerified=false, params:{}", params);
                return "failure";
            }
        } catch (Exception e){
            log.error(e.getMessage(), e);
            log.info("支付宝回调签名认证失败,signVerified=false, params:{}", params);
            return "failure";
        }
        return "success";//请不要修改或删除
    }
}

4、application-dev.properties

##文件跟路径
#本地
alipay_cert_file_path=D:/microservice/pay-service/src/main/resources/static/
#测试
#alipay_cert_file_path=/home/zrk/microservice/pay-service/src/main/resources/static/

五、测试

使用postman访问 localhost:30040/thirdPay/aliPayCertUnifiedOrder,
返回结果:
在这里插入图片描述
其中data内容就是给客户端调起支付宝支付用的,出于保密对参数进行了部分处理

参考链接:

1、App支付服务端 DEMO & SDK :https://docs.open.alipay.com/54/106370/
2、签名文档:https://docs.open.alipay.com/291/105974/
3、参数说明:https://docs.open.alipay.com/204/105301/