文章目录
支付宝网站支付官方文档: https://docs.open.alipay.com/270
一、时序图
这里没有使用第7步,只使用的第8步
二、请求与响应参数
全部的参数:
这里只介绍一下使用到的必填的参数:
1. 过程1.1的请求参数与响应参数
公共请求参数:
- app_id:必填,支付宝分配给开发者的应用ID
- method:接口名称
- return_url:HTTP/HTTPS开头字符串,时序图中第六步回调的地址
- charset:请求使用的编码格式,如utf-8,gbk,gb2312等
- sign_type:商户生成签名字符串所使用的签名算法类型,目前支持RSA2和RSA,推荐使用RSA2,这里使用的是RSA2
- sign:商户请求参数的签名串
- timestamp:时间戳,发送请求的时间,格式"yyyy-MM-dd HH:mm:ss"
- version:调用的接口版本,固定为:1.0
- notify_url:支付宝服务器主动通知商户服务器里指定的页面http/https路径。要求可以在公网上访问的地址
业务请求参数:
- out_trade_no:商户订单号,64个字符以内、可包含字母、数字、下划线;需保证在商户端不重复
- product_code:仅支持"FAST_INSTANT_TRADE_PAY"
- total_amount:订单总金额,单位为元,精确到小数点后两位,取值范围[0.01,100000000]。
- subject:订单标题,
响应参数(过程6的同步返回请求参数):
- code:通信标识,10000表示通信成功
- out_trade_no:商户订单号
- total_amount:交易金额
2. 过程8中的请求参数与响应参数
公共请求参数同上,业务请求参数有:
- out_trade_no:订单支付时传入的商户订单号,和支付宝交易号不能同时为空。
- trade_no:支付宝交易号,和商户订单号不能同时为空
响应参数:
- code:通信标识,10000表示通信成功
- trade_status:交易状态,有四种情况:
- WAIT_BUYER_PAY(交易创建,等待买家付款)
- TRADE_CLOSED(未付款交易超时关闭,或支付完成后全额退款)
- TRADE_SUCCESS(交易支付成功)
- TRADE_FINISHED(交易结束,不可退款)
三、配置类
1. 引入依赖
<!--日志依赖-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
</dependency>
<!--支付宝开发软件包依赖-->
<dependency>
<groupId>com.alipay.sdk</groupId>
<artifactId>alipay-sdk-java</artifactId>
</dependency>
2. 配置一个配置类
配置类用来指定公共的请求参数
要改的参数:
- 私钥与公钥的生成方法:https://docs.open.alipay.com/291/105971
- appid:注册支付宝开放平台就有
- notify_url:异步通知页面路径,也可以不指定
- return_url:同步通知页面路径,也可以不指定,这里采用的方式是收到同步通知的请求后,调用支付宝的统一收单线下交易查询接口,确认订单是否完成
- gatewayUrl :支付宝网关,开发的接口需要在alipay后面加上dev,即
alipaydev
public class AlipayConfig {
//↓↓↓↓↓↓↓↓↓↓请在这里配置您的基本信息↓↓↓↓↓↓↓↓↓↓↓↓↓↓↓
// 应用ID,您的APPID,收款账号既是您的APPID对应支付宝账号
public static String app_id = "111111111";
// 商户私钥,您的PKCS8格式RSA2私钥
public static String merchant_private_key = "私钥";
// 支付宝公钥,查看地址:https://openhome.alipay.com/platform/keyManage.htm 对应APPID下的支付宝公钥。
public static String alipay_public_key = "公钥";
// 服务器异步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String notify_url = "http://localhost:9090/notify_url.jsp";
// 页面跳转同步通知页面路径 需http://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
public static String return_url = "http://localhost:9090/p2p/loan/back";
// 签名方式
public static String sign_type = "RSA2";
// 字符编码格式
public static String charset = "utf-8";
// 支付宝网关
public static String gatewayUrl = "https://openapi.alipaydev.com/gateway.do";
// 支付宝网关
public static String log_path = "C:\\";
//↑↑↑↑↑↑↑↑↑↑请在这里配置您的基本信息↑↑↑↑↑↑↑↑↑↑↑↑↑↑↑
/**
* 写日志,方便测试(看网站需求,也可以改成把记录存入数据库)
* @param sWord 要写入日志里的文本内容
*/
public static void logResult(String sWord) {
FileWriter writer = null;
try {
writer = new FileWriter(log_path + "alipay_log_" + System.currentTimeMillis()+".txt");
writer.write(sWord);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (writer != null) {
try {
writer.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
四、实现
由于支付这个功能是公共的,所以把这个功能单独提出来做为一个pay模块,而页面是在web模块,从web访问pay的两种方式:
- 直接在后台重定向到pay中的地址,缺点:只能发送get请求,在浏览器地址栏中中会看到参数一闪而过
- 先请求转发到一个web中的页面,该页面在加载完成后,自动访问pay模块中的地址(使用form表单发送post请求,隐藏域存放参数,js实现页面加载完毕后自动访问)
1. web模块
后台接收到充值请求后,访问pay工程的Alipay方法,Alipay方法中访问了支付宝的统一收单下单并支付页面接口
/**
* @param rechargeMoney:充值金额
* @return:跳转到一个页面
*/
@RequestMapping("/loan/toAlipayRecharge")
public String toAlipayRecharge(HttpServletRequest request,
Double rechargeMoney,
Model model) {
model.addAttribute("out_trade_no",rechargeNo);
model.addAttribute("total_amount", rechargeMoney);
model.addAttribute("subject", rechargeRecord.getRechargeDesc());
return "p2pToPay";
}
p2pToPay.html:在页面加载完成后,自动调用pay模块的方法
<form method="post" action="http://localhost:9094/pay/api/alipay">
<input type="hidden" name="out_trade_no" th:value="${out_trade_no}">
<input type="hidden" name="total_amount" th:value="${total_amount}">
<input type="hidden" name="subject" th:value="${subject}">
</form>
<script>document.forms[0].submit()</script>
2. pay模块
pay模块的alipay方法,该模块的负责向支付宝统一收单下单并支付页面接口发起请求,获取到一个类似上面 p2pToPay.html 页面的字符串,将该字符串放到一个页面中,即可跳转到支付宝的付款页面
@RequestMapping("/api/alipay")
public String alipay(Model model,
String out_trade_no,
String total_amount,
String subject) throws AlipayApiException {
//获得初始化的AlipayClient,从AlipayConfig中获取公共请求参数
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConfig.gatewayUrl, AlipayConfig.app_id, AlipayConfig.merchant_private_key, "json", AlipayConfig.charset, AlipayConfig.alipay_public_key, AlipayConfig.sign_type);
//设置同步响应请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(AlipayConfig.return_url);
//alipayRequest.setNotifyUrl(AlipayConfig.notify_url); 有需要的设,这里就不设置了
//拼接请求的参数
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
//请求
String result = alipayClient.pageExecute(alipayRequest).getBody();
//输出
model.addAttribute("result", result);
return "/payToAlipay";
}
这里的使用的是jsp页面,payToAlipay.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>Title</title>
</head>
<body>
${result}
</body>
</html>
3. 同步返回
@RequestMapping("/loan/back")
public String alipayBack(HttpServletRequest request,
Model model,
String out_trade_no) throws Exception {
//转换后的请求参数
Map<String,String> params = new HashMap<String,String>();
//请求参数
Map<String,String[]> requestParams = request.getParameterMap();
//这段代码将请求参数的String[]转为使用逗号隔开的String字符串
for (Iterator<String> 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);
}
//验证签名
boolean signVerified = AlipaySignature.rsaCheckV1(params, AlipayConfig.alipay_public_key, AlipayConfig.charset, AlipayConfig.sign_type); //调用SDK验证签名
if(signVerified){
Map<String ,Object > map = new HashMap<>();
map.put("out_trade_no", out_trade_no);
/*
在这里调用通过httpClint调用支付宝统一收单线下交易查询接口,查询交易是否成功
*/
// 创建httpClient对象
CloseableHttpClient httpClient = HttpClients.createDefault();
// 创建http对象
HttpPost httpPost = new HttpPost(url);
/**
* setConnectTimeout:设置连接超时时间,单位毫秒。
* setConnectionRequestTimeout:设置从connect Manager(连接池)获取Connection
* 超时时间,单位毫秒。这个属性是新加的属性,因为目前版本是可以共享连接池的。
* setSocketTimeout:请求获取数据的超时时间(即响应时间),单位毫秒。 如果访问一个接口,多少时间内无法返回数据,就直接放弃此次调用。
*/
RequestConfig requestConfig = RequestConfig.custom().setConnectTimeout(CONNECT_TIMEOUT).setSocketTimeout(SOCKET_TIMEOUT).build();
httpPost.setConfig(requestConfig);
// 封装请求参数
packageParam(params, map);
// 创建httpResponse对象
CloseableHttpResponse httpResponse = null;
String result = "";
try {
// 执行请求
httpResponse = httpClient.execute(httpPost);
// 获取返回结果
if (httpResponse != null && httpResponse.getStatusLine() != null) {
if (httpResponse.getEntity() != null) {
result = EntityUtils.toString(httpResponse.getEntity(), ENCODING);
}
}
} finally {
// 释放资源
release(httpResponse, httpClient);
}
//result为响应回来的json字符串,根据查询结果做出相应的业务处理
}
五、沙箱环境
官方文档:使用沙箱环境
使用沙箱环境可以在支付宝开发环境下完成一些主要功能和主要逻辑,比如使用沙箱环境进行支付,收款等