最近集成了沙箱环境下的支付宝支付功能,这里做一个总结和介绍。
配置
先去支付宝开发者文档配置沙箱环境,这里主要注意沙箱环境密钥的设置方法,具体步骤文档都写的很清楚了。
总结下,在开发时主要用到几个参数。
1、支付宝网关,具体参数见官方文档
2、应用ID,这个在开发者中心可以查看
3、签名方式,使用RSA2
4、应用私钥,PKCS8格式的RSA2私钥,密钥生成工具生成
5、支付宝公钥,密钥生成工具生成,生成之后可以在个人开发者中心查看
开始使用
首先需要引入两个概念,同步回调和异步回调。
同步回调:在用户完成支付之后回调,告诉用户已经支付成功,但是并不表示扣款成功,如果你把这个当成扣款成功的标识,那么你可亏惨了。
异步回调:扣款成功之后的回调。
配置好同步和异步回调地址
前端
用户点击支付按钮之后,进入支付页面,这里我主要传递了4个参数,具体根据自己需求
payment: function (orderNumber) {
var data = {};
data.out_trade_no = orderNumber;//订单号
data.total_amount = "5.00";//支付金额
data.subject = "图像隐写在线服务平台资源下载支付";//标题
data.body = "在您完成付款之后,平台会自动将生成的图片下载到您本地";//备注信息
$.ajax({
url: '/order/pay',
type: 'post',
data: JSON.stringify(data),
contentType: 'application/json',
success: function (result) {
location.href = "/orderView/href";
},
error: function () {
layer.msg('数据异常')
}
})
}
后端
Controller
//付款
@PostMapping("/pay")
@ResponseBody
public void payment(@RequestBody OrderPaymentRequest orderPaymentRequest) {
orderService.payment(orderPaymentRequest);
}
Service
public void payment(OrderPaymentRequest orderPaymentRequest) {
String result = null;
AlipayClient alipayClient = new DefaultAlipayClient(AlipayConstant.GATEWAR_URL, AlipayConstant.APP_ID, AlipayConstant.MECHART_PRIVATE_KEY,
"json", AlipayConstant.CHARSET, AlipayConstant.APLPAY_PUBLIC_KEY, AlipayConstant.SIGN_TYPE);
AlipayTradePagePayRequest alipayTradePagePayRequest = new AlipayTradePagePayRequest();
alipayTradePagePayRequest.setReturnUrl(AlipayConstant.RETURN_URL);
alipayTradePagePayRequest.setNotifyUrl(AlipayConstant.NOTIFY_URL);
alipayTradePagePayRequest.setBizContent("{\"out_trade_no\":\""+ orderPaymentRequest.getOutTradeNo() +"\","
+ "\"total_amount\":\""+ orderPaymentRequest.getTotalAmount() +"\","
+ "\"subject\":\""+ orderPaymentRequest.getSubject() +"\","
+ "\"body\":\""+ orderPaymentRequest.getBody() +"\","
+ "\"product_code\":\"FAST_INSTANT_TRADE_PAY\"}");
try {
result = alipayClient.pageExecute(alipayTradePagePayRequest).getBody();//返回一个字符串形式的表单
} catch (AlipayApiException e) {
e.printStackTrace();
}
buildPayIndexFormCache(result);//放入缓存中,具体实现根据自己需要定义
}
此时在缓存中已经存在字符串形式的支付页面,这边把result字段打印出来,如下图。再把表单渲染出来即可进入支付页面,具体实现如下。
对应的方法为:
@RequestMapping("/href")
public String payIndexForm(Model model) {
String payIndex = orderService.getPayIndexFormCache().getValue();//这里再从缓存中获取字符串形式的表单,使用模板引擎渲染即可
model.addAttribute("payIndex", payIndex);
return "/order/href";//跳转到href.html页面
}
href.html
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<title>跳转第三方支付平台中 ...</title>
</head>
<body th:utext="${payIndex}">
</body>
</html>
效果如下:接下来就是支付操作,支持扫码支付和登录账户后付款,扫码支付需要下载专门的支付宝app(目前只有Android版),账户付款时需要填写创建支付宝沙箱应用时支付宝提供的账号和密码。来看代码实现,这边我使用同步回调接口来做测试,异步回调接口在我内网上并不会得到调用,有人说是异步回调地址必须为公网地址...等等很多说法,这里留个疑问哈。
Controller
//支付宝同步回调
@RequestMapping(value = "/alipayReturnNotice", method = RequestMethod.GET)
public void alipayReturnNotice(HttpServletRequest request, HttpServletResponse response) throws IOException {
response.sendRedirect(orderService.payResult(request));
}
主要就是进入orderService.payResult(request)这个方法处理
Service
public String payResult(HttpServletRequest request) {
String trade_no = null;
String total_amount = null;
//获取支付宝GET过来反馈信息
Map<String,String> params = new HashMap<>();
Map<String,String[]> requestParams = request.getParameterMap();
//处理反馈信息
for (Iterator<String> iter = requestParams.keySet().iterator(); iter.hasNext();) {
String name = iter.next();
String[] values = requestParams.get(name);
String valueStr = "";
for (int i = 0; i < values.length; i++) {
valueStr = (i == values.length - 1) ? valueStr + values[i]
: valueStr + values[i] + ",";
}
try {
valueStr = new String(valueStr.getBytes("ISO-8859-1"), "utf-8");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
params.put(name, valueStr);
}
boolean signVerified = false;//验证签名
try {
signVerified = AlipaySignature.rsaCheckV1(params, AlipayConstant.APLPAY_PUBLIC_KEY,
AlipayConstant.CHARSET, AlipayConstant.SIGN_TYPE);
} catch (AlipayApiException e) {
e.printStackTrace();
}
if (signVerified) {
try {
trade_no = new String(request.getParameter("out_trade_no").
getBytes("ISO-8859-1"), "UTF-8");//订单号
total_amount = new String(request.getParameter("total_amount").
getBytes("ISO-8859-1"), "UTF-8");//支付金额
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
/*
业务可以在这里处理,比如修改DB付款状态等
*/
}
return "/userView/index";//支付成功页面跳转
}
总结
基础的沙箱支付就类似这样,首先获取到支付页面,支付完成之后在回调接口处理支付宝反馈回来的信息。真正的业务实现比这要复杂很多,包括退款、关闭交易、交易状态查询等等一堆操作。