支付宝支付(1)之普通公钥方式

之前开发了支付宝支付,按照官方文档不难,最新新申请的app领导意外的配置成公钥证书方式,由于业务的特殊性踩了不少坑,感觉有必要记录一下整合过程,先记录一下普通公钥方式,也是最常用的,针对app支付,这里主要记录后台服务端java版本的实现。

一、签名

签名方式有两种(普通公钥方式、公钥证书方式),一般最常用的就是普通公钥方式,也相对比较简单,
申请步骤可以参照官方文档:
参考链接:https://docs.open.alipay.com/291/105971/
按照文档一步步就可以完成生成并上传公钥,在这里使用开放平台的SDK方式进行签名及验证,也是最简单最高效的方式,接下来主要实现服务端代码:

二、导入依赖

<dependency>
	<groupId>com.alipay.sdk</groupId>
	<artifactId>alipay-sdk-java</artifactId>
	<version>4.5.0.ALL</version>
</dependency>

三、生成APP支付订单信息

主要有三部分:实例化客户端、配置请求参数、发起请求。如果使用支付宝开放平台SDK,直接用以下代码逻辑调用即可:

//实例化客户端
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", APP_ID, APP_PRIVATE_KEY, "json", CHARSET, ALIPAY_PUBLIC_KEY, "RSA2");
//实例化具体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.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)
boolean flag = AlipaySignature.rsaCheckV1(params, alipaypublicKey, charset,"RSA2")

五、具体项目代码

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

项目整体结构

在这里插入图片描述
alipay文件夹是关于支付宝支付的配置及工具类,ResultStatusEnum是项目返回值状态码枚举类,ResultStatus是封装项目统一返回参数对象,PayRequest由于未掺杂业务逻辑暂时为空
仅供参考,这都不是重点

1、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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>2.1.4.RELEASE</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.zrk</groupId>
	<artifactId>pay-service</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>pay-service</name>
	<description>Demo project for Spring Boot</description>

	<properties>
		<java.version>1.8</java.version>
	</properties>

	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
			<optional>true</optional>
		</dependency>

		<dependency>
			<groupId>commons-lang</groupId>
			<artifactId>commons-lang</artifactId>
			<version>2.4</version>
		</dependency>

		<!--alipay-->
		<dependency>
			<groupId>com.alipay.sdk</groupId>
			<artifactId>alipay-sdk-java</artifactId>
			<version>4.5.0.ALL</version>
		</dependency>

		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>
</project>

2、ZrkAliPayConfig.java

package com.zrk.alipay;

/**
 * @Description: 支付宝支付配置类
 * @Author: zrk
 * @Date: 2019/9/10
 */
public class ZrkAliPayConfig {
    /**
     * 支付宝公钥
     */
    public static String ALIPAY_PUBLIC_KEY = "***";

    /**
     * 商户私钥
     */
    public static String APP_PRIVATE_KEY = "***";

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

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

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

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

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

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

3、ZrkAliPayUtil.java

package com.zrk.alipay;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Random;

/**
 * @Description: 支付宝支付工具类
 * @Author: zrk
 * @Date: 2019/9/10
 */
public class ZrkAliPayUtil {

    /**
     * 生成订单号
     * @return
     */
    public static String getOutTradeNo() {
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddhhmmss");
        return sdf.format(date) + getFixLengthString(5);
    }

    /**
     * 返回长度为【strLength】的随机数
     */
    public static String getFixLengthString(int strLength) {
        Random rm = new Random();
        // 获得随机数
        double pross = (1 + rm.nextDouble()) * Math.pow(10, strLength);
        // 将获得的获得随机数转化为字符串
        String fixLenthString = String.valueOf(pross);

        // 返回固定的长度的随机数
        return fixLenthString.substring(1, strLength + 1);
    }
}

4、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);
    }
}

5、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);
}

ThirdPayServiceImpl.java

package com.zrk.service.impl;

import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
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.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.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 {

    /**
     * 分隔符
     */
    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";//请不要修改或删除
    }
}

6、其他(可换用自己项目逻辑)

ResultStatus.java

package com.zrk.model;

import com.alipay.api.domain.PageInfo;
import com.zrk.enums.ResultStatusEnum;
import lombok.Data;
import org.apache.commons.lang.StringUtils;

import java.io.Serializable;

/**
 * 统一封装返回值对象
 * @Description: 返回对象包装类
 * @Author: zrk
 * @Date: 2019/9/10
 */
@Data
public class ResultStatus implements Serializable{

    private static final long serialVersionUID = -3276270216128997806L;

    /**状态码*/
    private Integer status = ResultStatusEnum.SUCCESS.getStatus();

    /**信息*/
    private String msg = "";

    /**数据*/
    private Object data;

    /**分页信息*/
    private PageInfo page;

    public ResultStatus(){
        setStatus(ResultStatusEnum.SUCCESS.getStatus());
    }


    public ResultStatus(Integer status) {
        setStatus(status);
        this.data = null;
        this.page = null;
    }

    public ResultStatus(Integer status, String msg) {
        setStatus(status);
        this.msg = msg;
        this.data = null;
        this.page = null;
    }

    private void setStatus(Integer status){
        this.status = status;
        try{
            if(StringUtils.isNotEmpty(ResultStatusEnum.getMsgByStatus(status))){
                this.msg = ResultStatusEnum.getMsgByStatus(status);
            }
        } catch (Exception e){
        }
    }
}

ResultStatusEnum.java

package com.zrk.enums;

/**
 * @Description: 返回值状态码
 * @Author: zrk
 * @Date: 2019/8/3
 */
public enum ResultStatusEnum {

    /**操作成功*/
    SUCCESS(0,"成功"),
    /**参数错误*/
    PARAM_INVALID(4001,"参数错误"),
    /**操作失败*/
    ERROR(5001,"操作失败"),
    /**结果为空*/
    RESULT_EMPTY(5002,"返回结果为空")
    ;

    private Integer status;
    private String msg;

    ResultStatusEnum(Integer status, String msg) {
        this.status = status;
        this.msg = msg;
    }

    public Integer getStatus() {
        return status;
    }

    public String getMsg(){
        return msg;
    }

    public static String getMsgByStatus(Integer status){
        if(status == null){
            return "";
        }
        for(ResultStatusEnum resultStatusEnum : values()){
            if(resultStatusEnum.getStatus().equals(status)){
                return resultStatusEnum.getMsg();
            }
        }
        return "";
    }

}

六、测试

支付宝下订单接口

使用postman访问 localhost:30040/thirdPay/aliPayUnifiedOrder,
返回结果:

{
    "status": 0,
    "msg": "成功",
    "data": "alipay_sdk=alipay-sdk-java-4.5.0.ALL&app_id=201904****237&biz_content=%7B%22body%22%22%E5%95%86%E5%93%81%E5%90%8D%E7%A7%B0%22%2C%22out_trade_no%22%3A%222019091012230100000%22%2C%22passback_params%22%3A%22123ZRKAPP111ZRKAPP222%22%2C%22product_code%22%3A%22QUICK_MSECURITY_PAY%22%2C%22subject%22%3A%22%E5%95%86%E5%93%81%E5%90%8D%E7%A7%B0%22%2C%22timeout_express%22%3A%2230m%22%2C%22total_amount%22%3A%220.01%22%7D&charset=utf-8&format=json&method=alipay.trade.app.pay&notify_url=http%3A%2F%2Fzrk.com%2FthirdPay%2FalipayNotify&sign=PHwxWegDutk6U41EvxatsgQF3Wyglxef1%2BxQr8X%2BkosNveCO%2By3URYwAE677HOxHyvaWTEmvQMcLvanFoXaNtOq65sdIG%2BOwtikV93FD02EZpeOrvR2ULYAOlFDUZkJnurJIMbT9TW4%2FJh4vL0sNIj1n%2BdqCGZUsFXFtYNarZmagKT1Qs7pJ3WjXzPfCn03lw%2Bp4b47YQcHnO%2B6PxNtuIrUebb23DQ9KtmQTbyGadkNwCxaakqsmhYqQCDEgfr4jYel%2BPZrdMzbiTiEQ7kjV58EPce6N8e7cz1GSU3iYacCJQ%2FOP7%2BlYKV1Q7REwSxPni4Uro94kVwDZ7yesj0qg%3D%3D&sign_type=RSA2&timestamp=2019-09-10+12%3A23%3A01&version=1.0",
    "page": null
}

其中data内容就是给客户端调起支付宝支付用的,出于保密对参数进行了部分处理

支付宝回调


在这里插入图片描述
参数是支付宝回调返回的,方便调试就将参数一一复制到postman进行模拟调试
官方文档有参数说明:
在这里插入图片描述

参考链接:

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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章