SpringBoot 接入支付宝SDK
最近有好奇网页/手机扫码付款支付宝如何实现的?然后去学习了一下, 发现还是很简单的~,记录一下吧(下次就直接拿来用了)
前言
我在学习的时候,发现与支付宝SDK对接,最麻烦的却是接入前的一些准备工作,但是好在官网其实很详细了
大家可以去 网页 & 移动接入 详细的进行接入前的准备工作(电脑网站支付 手机网站支付 网页支付)
具体就是几个步骤:(详细的配置这里就不说明了,都很清晰)
-
创建应用
在 创建应用 中创建应用并提交审核,审核通过后会生成应用 唯一标识 APPID,并且可以申请开通开放产品使用权限,通过 APPID 应用才能调用开放产品的接口能力
-
配置应用(根据自己的需求进行配置)
总结:
上述的准备工作,在下面的集成中,我们需要的东西为:
- APPID(创建应用得到的标识ID)
- 加密方式,根据加密方式的不同,所需内容有所不同
- 普通公钥模式加签:APP_PRIVATE_KEY(应用私钥) ALIPAY_PUBLIC_KEY (支付宝公钥)
- 公钥证书模式加签:APP_PRIVATE_KEY(应用私钥)app_cert_path(应用公钥证书文件本地路径)alipay_cert_path(支付宝公钥证书文件本地路径)alipay_root_cert_path(支付宝根证书文件本地路径)
APPID 创建应用就有了,其他的字段看下面的介绍。有上述的东西,我们就可以进行下面的集成了
接口内容加密方式介绍
以下内容参考 接口加签方式 进行介绍。常用的接口加签方式有两种:
- 公钥证书模式:企业开发者若涉及
alipay.fund.trans.uni.transfer
接口(即 现金红包、转账功能)接入,必须使用 公钥证书 方式。 - 公钥模式:个人开发者不涉及到
alipay.fund.trans.uni.transfer
接口,使用 公钥模式 进行加签。
首先通过 获取公钥 获取应用公私钥/公钥证书,待获取完成之后,在申请的具体应用详情页 > 设置 > 开发设置 > 接口加签方式,点击 设置,上传获取到的 CSR 文件,以此来获取 应用公钥证书 或 支付宝根证书、支付宝公钥证书 等信息(这里可以获取到应用私钥 即 APP_PRIVATE_KEY(应用私钥) )
- 公钥证书模式:下载上述图中的三个证书到本地,然后就知晓 app_cert_path(应用公钥证书文件本地路径)alipay_cert_path(支付宝公钥证书文件本地路径)alipay_root_cert_path(支付宝根证书文件本地路径)
- 公钥模式:上述图中的支付宝公钥即对应 ALIPAY_PUBLIC_KEY (支付宝公钥)
集成SDK
1、导包
集成首先导包:
-
<properties> <alipay.version>4.9.153.ALL</alipay.version> </properties> <!--支付宝依赖--> <dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>${alipay.version}</version> </dependency>
2、确定加签方式与签名算法类型
在上述接口内容加密方式介绍的加密方式,根据加密方式的不同,所需内容有所不同
- 普通公钥模式加签:APP_PRIVATE_KEY(应用私钥) ALIPAY_PUBLIC_KEY (支付宝公钥)
- 公钥证书模式加签:APP_PRIVATE_KEY(应用私钥)app_cert_path(应用公钥证书文件本地路径)alipay_cert_path(支付宝公钥证书文件本地路径)alipay_root_cert_path(支付宝根证书文件本地路径)
根据所需的内容与加签的方式,了解所需的参数
普通公钥模式关键参数说明
配置参数 | 示例值解释 | 获取方式/示例值 |
---|---|---|
URL | 支付宝网关(固定) | https://openapi.alipay.com/gateway.do |
APPID | APPID 即创建应用后生成 | 获取到的 APPID |
APP_PRIVATE_KEY | 开发者私钥,由开发者自己生成 | 获取到的 APP_PRIVATE_KEY |
FORMAT | 参数返回格式,只支持 JSON 格式 | json |
CHARSET | 编码集,支持 GBK/UTF-8 | 开发者根据实际工程编码配置 |
ALIPAY_PUBLIC_KEY | 支付宝公钥,由支付宝生成 | 获取到的 ALIPAY_PUBLIC_KEY |
SIGN_TYPE | 商户生成签名字符串所使用的签名算法类型,目前支持 RSA2 和 RSA,推荐使用 RSA2 | 根据加密所选的算法来决定 |
可根据上述字段相应的实体类:
这里用到了 lombok 与 swagger,不了解的可详细看:Swagger 介绍(Swagger 与 SpringBoot 集成) 、 lombok 的使用
package com.study.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
/**
* 支付宝配置类
*/
@Data
public class AlipayConfig implements Serializable {
@ApiModelProperty(value = "支付宝开放安全地址", hidden = true)
private String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
@NotBlank
@ApiModelProperty(value = "应用ID")
private String appId;
@NotBlank
@ApiModelProperty(value = "商户私钥")
private String privateKey;
@ApiModelProperty(value = "编码", hidden = true)
private String charset= "utf-8";
@ApiModelProperty(value = "类型")
private String format="JSON";
@NotBlank
@ApiModelProperty(value = "支付宝公钥")
private String publicKey;
@ApiModelProperty(value = "签名方式")
private String signType="RSA2";
}
公钥证书模式关键参数说明
配置参数 | 示例值解释 | 获取方式/示例值 |
---|---|---|
URL | 支付宝网关(固定) | https://openapi.alipay.com/gateway.do |
APPID | APPID 即创建应用后生成 | 获取到的 APPID |
APP_PRIVATE_KEY | 开发者私钥,由开发者自己生成 | 获取到的 APP_PRIVATE_KEY |
FORMAT | 参数返回格式,只支持 JSON(固定) | json |
CHARSET | 编码集,支持 GBK/UTF-8 | 开发者根据实际工程编码配置 |
SIGN_TYPE | 商户生成签名字符串所使用的签名算法类型,目前支持 RSA2 和 RSA,推荐使用 RSA2 | 根据加密所选的算法来决定 |
app_cert_path | 应用公钥证书文件本地路径 | 上面相应文件下载后的路径 |
alipay_cert_path | 支付宝公钥证书文件本地路径 | 上面相应文件下载后的路径 |
alipay_root_cert_path | 支付宝根证书文件本地路径 | 上面相应文件下载后的路径 |
根据配置同样可写相应的配置类:
package com.study.domain;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotBlank;
import java.io.Serializable;
/**
* 支付宝配置类
*/
@Data
public class AlipayConfig implements Serializable {
@ApiModelProperty(value = "支付宝开放安全地址", hidden = true)
private String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
@NotBlank
@ApiModelProperty(value = "应用ID")
private String appId;
@NotBlank
@ApiModelProperty(value = "商户私钥")
private String privateKey;
@ApiModelProperty(value = "编码", hidden = true)
private String charset= "utf-8";
@ApiModelProperty(value = "类型")
private String format="JSON";
@NotBlank
@ApiModelProperty(value = "应用公钥证书文件本地路径")
private String appCertPath;
@NotBlank
@ApiModelProperty(value = "支付宝公钥证书文件本地路径")
private String alipayCertPath;
@NotBlank
@ApiModelProperty(value = "支付宝根证书文件本地路径")
private String alipayRootCertPath;
@ApiModelProperty(value = "签名方式")
private String signType="RSA2";
}
上述的参数主要用于构造我们SDK集成所需的Client
3、请求参数与响应参数说明
不同的接口的请求参数与响应参数不完全一致,可查看具体调用的接口的参数说明 API列表
下面以 PC场景下单并支付 (接口名称为 alipay.trade.page.pay )的接口为例进行说明:
请求参数
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
app_id | String | 是 | 32 | 支付宝分配给开发者的应用ID | 2014072300007148 |
method | String | 是 | 128 | 接口名称 | alipay.trade.page.pay |
format | String | 否 | 40 | 仅支持JSON | JSON |
return_url | String | 否 | 256 | HTTP/HTTPS开头字符串 | https://m.alipay.com/Gk8NF23 |
charset | String | 是 | 10 | 请求使用的编码格式,如utf-8,gbk,gb2312等 | utf-8 |
sign_type | String | 是 | 10 | 商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2 | RSA2 |
sign | String | 是 | 344 | 商户请求参数的签名串,详见签名 | 详见示例 |
timestamp | String | 是 | 19 | 发送请求的时间,格式"yyyy-MM-dd HH:mm:ss" | 2014-07-24 03:07:50 |
version | String | 是 | 3 | 调用的接口版本,固定为:1.0 | 1.0 |
notify_url | String | 否 | 256 | 支付宝服务器主动通知商户服务器里指定的页面http/https路径。 | http://api.test.alipay.net/atinterface/receive_notify.htm |
app_auth_token | String | 否 | 40 | 详见应用授权概述 | |
biz_content | String | 是 | 请求参数的集合,最大长度不限,除公共参数外所有请求参数都必须放在这个参数中传递 |
其中 biz_content 的字段说明:
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
out_trade_no | String | 必选 | 64 | 商户订单号。 由商家自定义,64个字符以内,仅支持字母、数字、下划线且需保证在商户端不重复 | 20150320010101001 |
total_amount | Price | 必选 | 11 | 订单总金额,单位为元,精确到小数点后两位,取值范围为 [0.01,100000000],不能为0。 | 88.88 |
subject | String | 必选 | 256 | 订单标题。 注意:不可使用特殊字符,如 /,=,& 等。 | Iphone6 16G |
product_code | String | 必选 | 64 | 销售产品码,与支付宝签约的产品码名称。注:目前电脑支付场景下仅支持 FAST_INSTANT_TRADE_PAY | FAST_INSTANT_TRADE_PAY |
qr_pay_mode | String | 可选 | 2 | PC扫码支付的方式。 支持前置模式和跳转模式。 前置模式是将二维码前置到商户的订单确认页的模式。需要商户在自己的页面中以 iframe 方式请求支付宝页面 跳转模式是用户的扫码界面是由支付宝生成的,不在商户的域名下 具体支持的枚举值有以下几种: 0:订单码-简约前置模式,对应 iframe 宽度不能小于600px,高度不能小于300px; 1:订单码-前置模式,对应iframe 宽度不能小于 300px,高度不能小于600px; 2:订单码-跳转模式 3:订单码-迷你前置模式,对应 iframe 宽度不能小于 75px,高度不能小于75px; 4:订单码-可定义宽度的嵌入式二维码,商户可根据需要设定二维码的大小。 | 1 |
qrcode_width | Number | 可选 | 4 | 商户自定义二维码宽度。 注:qr_pay_mode=4时该参数有效 | 100 |
goods_detail | GoodsDetail[] | 可选 | 订单包含的商品列表信息,json格式。 | ||
time_expire | String | 可选 | 32 | 订单绝对超时时间。 格式为yyyy-MM-dd HH:mm:ss。 注:time_expire和timeout_express两者只需传入一个或者都不传,两者均传入时,优先使用time_expire。 | 2016-12-31 10:05:01 |
sub_merchant | SubMerchant | 可选 | 二级商户信息。 直付通模式和机构间连模式下必传,其它场景下不需要传入。 | ||
extend_params | ExtendParams | 可选 | 业务扩展参数 | ||
business_params | String | 可选 | 512 | 商户传入业务信息,具体值要和支付宝约定,应用于安全,营销等参数直传场景,格式为json格式 | {“data”:“123”} |
promo_params | String | 可选 | 512 | 优惠参数。为 JSON 格式。注:仅与支付宝协商后可用 | {“storeIdType”:“1”} |
integration_type | String | 可选 | 16 | 请求后页面的集成方式。 枚举值: ALIAPP:支付宝钱包内 PCWEB:PC端访问 默认值为PCWEB。 | PCWEB |
request_from_url | String | 可选 | 256 | 请求来源地址。如果使用ALIAPP的集成方式,用户中途取消支付会返回该地址。 | https:// |
store_id | String | 可选 | 32 | 商户门店编号。 指商户创建门店时输入的门店编号。 | NJ_001 |
merchant_order_no | String | 可选 | 32 | 商户原始订单号,最大长度限制 32 位 | 20161008001 |
invoice_info | InvoiceInfo | 可选 | 开票信息 |
响应参数
状态相关参数:
参数 | 类型 | 是否必填 | 描述 | 示例值 |
---|---|---|---|---|
code | String | 是 | 网关返回码,详见文档 | 40004 |
msg | String | 是 | 网关返回码描述,详见文档 | Business Failed |
sub_code | String | 否 | 业务返回码,参见具体的API接口文档 | ACQ.TRADE_HAS_SUCCESS |
sub_msg | String | 否 | 业务返回码描述,参见具体的API接口文档 | 交易已被支付 |
sign | String | 是 | 签名,详见文档 | DZXh8eeTuAHoYE3w1J+POiPhfDxOYBfU Nn1lkeT/V7P4zJdyojWEa6IZs6Hz0yDW5Cp/vi ufUb5I0/V5WENS3OYR8zRedqo6D+fUTdLHdc+EFyCkiQhBxI zgngPdPdfp1PIS7BdhhzrsZHbRqb7o4k3Dxc+AAnFauu4V6Zdwczo= |
数据相关参数:
参数 | 类型 | 是否必填 | 最大长度 | 描述 | 示例值 |
---|---|---|---|---|---|
trade_no | String | 必选 | 64 | 支付宝交易号 | 2013112011001004330000121536 |
out_trade_no | String | 必选 | 64 | 商户订单号 | 6823789339978248 |
seller_id | String | 必选 | 28 | 收款支付宝账号对应的支付宝唯一用户号。 以2088开头的纯16位数字 | 2088111111116894 |
total_amount | Price | 必选 | 11 | 交易金额 | 128.00 |
merchant_order_no | String | 必选 | 32 | 商户原始订单号,最大长度限制32位 | 20161008001 |
异常例子:
{
// 这个key 对应 请求参数的 method
"alipay_trade_query_response": {
"code": "20000",
"msg": "Service Currently Unavailable",
"sub_code": "isp.unknow-error",
"sub_msg": "系统繁忙"
},
"sign": "ERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE"
}
4、请求示例
一般直接使用 DefaultAlipayClient 就可完成我们的需求,三个步骤:
- 创建 Client 。 一般就创建 DefaultAlipayClient 即可
- 创建Reqeust 。根据不同的端/不同的加签方式会有所不同,常用的:
- 普通 公钥证书 模式加签 CertAlipayRequest
- 电脑网页端 AlipayTradePagePayRequest
- 手机网页版 AlipayTradeWapPayRequest
- 批量请求 BatchAlipayRequest
- 调用请求方法,发送请求
example:
@Override
public String toPayAsPc(AlipayConfig alipay, TradeVo trade) throws Exception {
String returnUrl = "https://m.alipay.com/Gk8NF23"; // 返回页面,也可放入 AlipayConfig 中
String notifyUrl = "http://api.test.alipay.net/atinterface/receive_notify.htm"; // 异步通知地址,也可放入 AlipayConfig 中
// 1、构建Client
AlipayClient alipayClient = new DefaultAlipayClient(alipay.getGatewayUrl(), alipay.getAppId(), alipay.getPrivateKey(), alipay.getFormat(), alipay.getCharset(), alipay.getPublicKey(), alipay.getSignType());
// 2、创建API对应的request(电脑网页版)
AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
// 创建API对应的request(手机网页版)
// AlipayTradeWapPayRequest request = new AlipayTradeWapPayRequest();
// 订单完成后返回的页面和异步通知地址
request.setReturnUrl(returnUrl);
request.setNotifyUrl(notifyUrl);
// 填充业务参数
request.setBizContent("{" +
" \"out_trade_no\":\""+trade.getOutTradeNo()+"\"," +
" \"product_code\":\"FAST_INSTANT_TRADE_PAY\"," +
" \"total_amount\":"+trade.getTotalAmount()+"," +
" \"subject\":\""+trade.getSubject()+"\"," +
" \"body\":\""+trade.getBody()+"\"," +
" \"extend_params\":{" +
" \"sys_service_provider_id\":\""+alipay.getSysServiceProviderId()+"\"" +
" }"+
" }");
// 3、调用SDK生成表单, 通过GET方式
return alipayClient.pageExecute(request, "GET").getBody();
}
说明:
第一步 创建 Client ,其提供了很多的重载构造方法供其构造,设置一些所有请求都包含的公共参数字段
- com.alipay.api.DefaultAlipayClient
package com.alipay.api;
import java.security.cert.X509Certificate;
import java.util.concurrent.ConcurrentHashMap;
public class DefaultAlipayClient extends AbstractAlipayClient {
private String privateKey;
private String encryptKey;
private String alipayPublicKey;
private Signer signer;
private SignChecker signChecker;
private Encryptor encryptor;
private Decryptor decryptor;
private X509Certificate cert;
private ConcurrentHashMap<String, X509Certificate> alipayPublicCertMap;
// 1
public DefaultAlipayClient(String serverUrl, String appId, String privateKey) {
....
}
// 2
public DefaultAlipayClient(String serverUrl, String appId, String privateKey, String format) {
.....
}
// 3
public DefaultAlipayClient(String serverUrl, String appId, String privateKey, String format,
String charset) {
.....
}
// 4
public DefaultAlipayClient(String serverUrl, String appId, String privateKey, String format,
String charset, String alipayPublicKey) {
....
}
//5 常用
public DefaultAlipayClient(String serverUrl, String appId, String privateKey, String format,
String charset, String alipayPublicKey, String signType) {
.....
}
// 6
public DefaultAlipayClient(String serverUrl, String appId, String privateKey, String format,
String charset, String alipayPublicKey, String signType,
String proxyHost, int proxyPort) {
.....
}
// 7
public DefaultAlipayClient(String serverUrl, String appId, String privateKey, String format,
String charset, String alipayPublicKey, String signType,
String encryptKey, String encryptType) {
.....
}
// 8
public DefaultAlipayClient(CertAlipayRequest certAlipayRequest) throws AlipayApiException {
....
}
}
第二步 创建 Request ,其实就是设置一些不同的端请求的一些私有参数的配置
- com.alipay.api.request.AlipayTradeWapPayRequest
public class AlipayTradeWapPayRequest implements AlipayRequest<AlipayTradeWapPayResponse> {
private AlipayHashMap udfParams; // add user-defined text parameters
private String apiVersion="1.0";
/**
* 手机网站支付接口2.0
*/
private String bizContent;
private String terminalType;
private String terminalInfo;
private String prodCode;
private String notifyUrl;
private String returnUrl;
private boolean needEncrypt=false;
private AlipayObject bizModel=null;
......
}
第三步 创建 请求调用执行 ,通过调用 DefaultAlipayClient 的父类 AlipayClient 的相应请求方法来执行请求,不同的request执行不同的方法
- com.alipay.api.AlipayClient
- 底层还是调用 java.net 包相应的HttpURLConnection构造连接,底层通过 socket通信 完成请求
public interface AlipayClient {
/**
* @param request 请求request
*/
<T extends AlipayResponse> T execute(AlipayRequest<T> request) throws AlipayApiException;
/**
* @param request 请求request
*/
<T extends AlipayResponse> T execute(AlipayRequest<T> request,
String authToken) throws AlipayApiException;
/**
* @param request
* @param accessToken
* @param appAuthToken
*/
<T extends AlipayResponse> T execute(AlipayRequest<T> request, String accessToken,
String appAuthToken) throws AlipayApiException;
<T extends AlipayResponse> T execute(AlipayRequest<T> request, String accessToken,
String appAuthToken, String targetAppId) throws AlipayApiException;
/**
* @param request
*/
<T extends AlipayResponse> T pageExecute(AlipayRequest<T> request) throws AlipayApiException;
/**
* SDK客户端调用生成sdk字符串
*
* @param <T>
* @param request
* @return
* @throws AlipayApiException
*/
<T extends AlipayResponse> T sdkExecute(AlipayRequest<T> request) throws AlipayApiException;
/**
* @param request
*/
<T extends AlipayResponse> T pageExecute(AlipayRequest<T> request,
String method) throws AlipayApiException;
/**
* 移动客户端同步结果返回解析的参考工具方法
* @param result 移动客户端SDK同步返回的结果map,一般包含resultStatus,result和memo三个key
* @param requsetClazz 接口请求request类,如App支付传入 AlipayTradeAppPayRequest.class
*/
<TR extends AlipayResponse, T extends AlipayRequest<TR>> TR parseAppSyncResult(Map<String, String> result,
Class<T> requsetClazz) throws AlipayApiException;
/**
* 批量调用
*
* @param request
*/
BatchAlipayResponse execute(BatchAlipayRequest request) throws AlipayApiException;
/**
* 证书类型调用
* @param request
*/
<T extends AlipayResponse> T certificateExecute(AlipayRequest<T> request) throws AlipayApiException;
/**
* @param request
*/
<T extends AlipayResponse> T certificateExecute(AlipayRequest<T> request,
String authToken) throws AlipayApiException;
/**
* @param request
* @param accessToken
* @param appAuthToken
*/
<T extends AlipayResponse> T certificateExecute(AlipayRequest<T> request, String accessToken,
String appAuthToken) throws AlipayApiException;
<T extends AlipayResponse> T certificateExecute(AlipayRequest<T> request, String accessToken,
String appAuthToken, String targetAppId) throws AlipayApiException;
}
总结
在学习的过程中,总的来说,官网说明的很详细了,也有社区来提问
在Java中集成支付宝支付其实很简单,涉及的代码就三步:(前期配置太多,但是配置好了就ok了)
- 创建 Client 。 一般就创建 DefaultAlipayClient 即可
- 创建Reqeust 。根据不同的端/不同的加签方式会有所不同,常用的:
- 普通 公钥证书 模式加签 CertAlipayRequest
- 电脑网页端 AlipayTradePagePayRequest
- 手机网页版 AlipayTradeWapPayRequest
- 批量请求 BatchAlipayRequest
- 调用请求方法,发送请求
在实际开发中,基本上会编写一个 AlipayConfig,用数据库的表 / properties 的方式来动态修改该Config,不同的端/请求方式,编写一个统一的Util就完事了
在学习时,也去看了一下底层实现,发现具体其底层还是 Socket通信,也不稀奇,只是其帮忙封装好了,我们直接傻瓜式调用即可