最近项目APP需要接入微信、支付宝支付功能,在分配开发任务时,听说微信支付接口比支付宝支付接口要难实现,由于我开发经验不是那么丰富(现工作经验1年半)且未接触过支付接口开发,组里刚好又有支付接口的老司机,所以很自然把简单的支付宝接口开发任务交给了我,看来开发组的组长还是很好人的嘛.....,废话就不多说了,我们开始吧!
实现支付宝接口详细过程
1.去支付宝官网申请公司企业账号并开通一个应用,在应用里签约APP支付功能
具体的申请截图步骤,在这里我就不详细说了,因为这不是文章的重点,可参考支付宝官网。
经过这一步,我们可以得过开发中需要用到的几个参数
①商户appid ②商户公钥、私钥 ③支付宝公钥 ④支付宝网关地址
解释一下这几个参数:
1.商户appid是识别商户的唯一ID,是让支付宝识别,我们到底是哪一个商户,这样支付宝就能识别商户对应的账号、用户号、收款账号...等等一系列信息。
2.商户公钥、私钥以及支付宝公钥这3个参数是对商户系统与支付宝进行信息交互的数字签名用的,相信各位大学里也有学过关于数字签名的一些知识,在这里,我就简单说一下我理解的过程:首先是商户系统需要给支付宝发送信息(支付、查询等等....),涉及钱方面,咱们当前要谨慎一点对吧,所以我们需要对发送之前的信息加把锁(用商户私钥进行签名),然后再发送给支付宝。支付宝收到商户发送的信息之后,发现上了把锁,那肯定得要一把钥匙(商户公钥)来解锁对吧,所以商户在跟支付宝签约APP支付功能的时候,就得把这把钥匙上传给支付宝了,支付宝就可以用商户的公钥进行解锁了。反过来也是一样,支付宝需要发送信息给商户信息,先用支付宝的私钥进行签名,再发送给商户系统,商户系统收到支付宝反馈过来的信息后,再用支付宝的公钥进行解密。在这里我们并没有用到支付宝的私钥,所以我们并不需要得到支付宝的私钥。这里放一个生成私钥公钥的支付宝官方工具
3.支付宝网关地址,是用来配置发送给支付宝的网关地址的。
2.将支付宝的SDK集成到项目系统里
支付宝的SDK指的就是支付宝提供的工具Jar包给我们开发者,SDK封装了大量的基础功能,使我们可以快速开发支付宝接口。这也是我在前面说的比微信支付接口更容易实现的原因。获取支付宝SDK地址:支付宝SDK下载地址,这里我选择JAVA的SDK。下载解压后的SDK得到:
alipay-sdk-java20180122110032.jar、commons-logging-1.1.1.jar是我们需要导入到项目里的,因为项目后台的大致的架构是maven+springBoot+jpa,所以我们需要从maven里导入jar包,首先是导入commons-logging-1.1.1.jar,不用多说,咱直接在pom.xml里加上:
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1.1</version>
</dependency>
然后是alipay-sdk-java20180122110032.jar,如果你也像上面一样直接加入到pom.xml,会发现,咦,怎么一直下载不下来。当然alipy的包在线上的maven仓库并没有,所以我们需要导入到本地的maven仓库。前提是配置好maven的环境变量,将包放在G:\alipay\sdk下,然后打开dos窗口,cd进入到G:\alipay\sdk下,执行maven如下命令:
mvn install:install-file -DgroupId=com.alipay -DartifactId=sdk-java -Dversion=20180122110032 -Dpackaging=jar -Dfile=alipay-sdk-java20180122110032.jar
导入成功后,在项目的pom.xml里继续添加
<!-- 支付宝SDK -->
<dependency>
<groupId>com.alipay</groupId>
<artifactId>sdk-java</artifactId>
<version>20180122110032</version>
</dependency>
到此,我们就顺利把支付宝SDK集成到项目里,是不是有点小兴奋,我们很快可以开发了!
3.看支付宝提供的API和网上各位牛人总结的经验,后台使用支付宝的SDK与支付宝进行交互
先看一下支付宝支付流程的图:
首先,我们来理一理开发的思路,按照我当前项目的需求,关于支付这一块大概操作流程是:用户在APP上选好要购买的商品,点击“立即购买”,跳转到订单详细页面。选择支付方式,点击“确定支付”跳转到支付宝APP,付款完成后,跳转回APP,完成支付。这个过程,当用户点击“确定支付”时,APP需要调用商户后台接口。
这时候就是我们所需要做的事情:先是生成商户系统一笔未支付的订单,获得商户订单ID(商户系统生成)和订单的一些其他信息,然后再调用支付宝的SDK提供的数字签名方法,将需要传给支付宝的信息进行加签,然后把加签后的字符串返回给APP。APP拉起支付宝APP,再把这个加签的字符串传给支付宝,完成支付。APP接收到同步通知后,还需要再次调用商户后台的接口(虽然同步通知也有付款情况,但需要以后台通知为准),校验订单最终的付款情况。按照支付宝API上所说,当完成支付后,支付宝会做2个操作,一个是同步返回信息给APP,一个是异步通知商户后台返回支付状态等信息,并且最终的支付结果是以异步通知为准。所以我们还需要考虑到一点,就是当用户支付成功之后,商户系统暂时没有接收到支付宝的异步通知时。我们需要拿着这个商户订单ID主动调用SDK支付宝的查询接口,去获取该订单的支付情况,并最终返回给APP。这个查询的接口应该是给APP收到同步通知后,请求商户系统后台进行校验的时候调用的。
根据我们上面思考所得,后台只需要对外提供3个接口即可
1.用户点击“立即购买”时调用商户后台接口,后台返回加签后的订单信息字符串
2.在支付完成之后,支付宝异步通知商户后台订单的付款情况
3.在支付完成之后,跳转回APP时,APP调用商户后台进行最终付款校验
想通想明白之后,终于接一下我们要敲代码了,哈哈哈哈
首先,我们来准备一下需要传给支付宝SDK的公共基本参数,我把参数放到一个单独的类里,你也可以放到数据库里,代码如下:
-
public
class AlipayConfig {
-
// 1.商户appid
-
//public static String APPID = "2017...";
-
-
//2.私钥 pkcs8格式的
-
public
static String RSA_PRIVATE_KEY =
"MIIEwAIBADANBg.....";
-
-
// 3.支付宝公钥
-
public
static String ALIPAY_PUBLIC_KEY =
"MIIBIjANBgkq.....";
-
-
// 4.服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问
-
public
static String notify_url =
"http://www.xxx.com/alipay/notify_url.do";
-
-
//5.页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 商户可以自定义同步跳转地址
-
public
static String return_url =
"http://www.xxx.com/alipay/return_url.do";
-
-
// 6.请求支付宝的网关地址
-
public
static String URL =
"https://openapi.alipay.com/gateway.do";
-
-
// 7.编码
-
public
static String CHARSET =
"UTF-8";
-
-
// 8.返回格式
-
public
static String FORMAT =
"json";
-
-
// 9.加密类型
-
public
static String SIGNTYPE =
"RSA2";
-
-
}
1.实现第一个接口:用户点击“立即购买”时调用商户后台接口,后台返回加签后的订单信息字符串。我把主要的处理逻辑写在Service层了,Controller层直接调用就可以,这里就不放Controller层的代码了
生成商户订单的代码,我就不放了,这个根据各自的业务需求来做,生成后订单之后,把订单信息传进来该方法进行处理,返回加签后的字符串,直接返回给APP即可,代码如下:
-
/**
-
* 获取支付宝加签后台的订单信息字符串
-
*
-
* @param request
-
* @return
-
*/
-
@Override
-
@Transactional(propagation = Propagation.REQUIRED)
-
public String getAliPayOrderStr(OrderTest orderTest) {
-
-
//最终返回加签之后的,app需要传给支付宝app的订单信息字符串
-
String orderString =
"";
-
logger.info(
"==================支付宝下单,商户订单号为:"+orderTest.getOutTradeNo());
-
-
//创建商户支付宝订单(因为需要记录每次支付宝支付的记录信息,单独存一个表跟商户订单表关联,以便以后查证)
-
AlipaymentOrder alipaymentOrder=
new AlipaymentOrder();
-
alipaymentOrder.setClubOrderId(orderTest.getId().toString());
//商家订单主键
-
alipaymentOrder.setOutTradeNo(orderTest.getOutTradeNo());
//商户订单号
-
alipaymentOrder.setTradeStatus((
byte)
0);
//交易状态
-
alipaymentOrder.setTotalAmount(Double.parseDouble(orderTest.getTotalAmount()));
//订单金额
-
alipaymentOrder.setReceiptAmount(
0.00);
//实收金额
-
alipaymentOrder.setInvoiceAmount(
0.00);
//开票金额
-
alipaymentOrder.setBuyerPayAmount(
0.00);
//付款金额
-
alipaymentOrder.setRefundFee(
0.00);
//总退款金额
-
-
try{
-
//实例化客户端(参数:网关地址、商户appid、商户私钥、格式、编码、支付宝公钥、加密类型),为了取得预付订单信息
-
AlipayClient alipayClient =
new DefaultAlipayClient(AlipayConfig.URL, AlipayConfig.APPID,
-
AlipayConfig.RSA_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET,
-
AlipayConfig.ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE);
-
-
//实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay
-
AlipayTradeAppPayRequest ali_request =
new AlipayTradeAppPayRequest();
-
-
//SDK已经封装掉了公共参数,这里只需要传入业务参数。以下方法为sdk的model入参方式
-
AlipayTradeAppPayModel model =
new AlipayTradeAppPayModel();
-
-
//业务参数传入,可以传很多,参考API
-
//model.setPassbackParams(URLEncoder.encode(request.getBody().toString())); //公用参数(附加数据)
-
model.setBody(orderTest.getBody());
//对一笔交易的具体描述信息。如果是多种商品,请将商品描述字符串累加传给body。
-
model.setSubject(orderTest.getSubjecy());
//商品名称
-
model.setOutTradeNo(orderTest.getOutTradeNo());
//商户订单号(自动生成)
-
// model.setTimeoutExpress("30m"); //交易超时时间
-
model.setTotalAmount(orderTest.getTotalAmount());
//支付金额
-
model.setProductCode(
"QUICK_MSECURITY_PAY");
//销售产品码(固定值)
-
ali_request.setBizModel(model);
-
logger.info(
"====================异步通知的地址为:"+alipayment.getNotifyUrl());
-
ali_request.setNotifyUrl(AlipayConfig.notify_url);
//异步回调地址(后台)
-
ali_request.setReturnUrl(AlipayConfig.return_url);
//同步回调地址(APP)
-
-
// 这里和普通的接口调用不同,使用的是sdkExecute
-
AlipayTradeAppPayResponse alipayTradeAppPayResponse = alipayClient.sdkExecute(ali_request);
//返回支付宝订单信息(预处理)
-
orderString=alipayTradeAppPayResponse.getBody();
//就是orderString 可以直接给APP请求,无需再做处理。
-
this.createAlipayMentOrder(alipaymentOrder);
//创建新的商户支付宝订单
-
-
}
catch (AlipayApiException e) {
-
e.printStackTrace();
-
logger.info(
"与支付宝交互出错,未能生成订单,请检查代码!");
-
}
-
-
return orderString;
-
}
2.实现第二个接口:在支付完成之后,支付宝异步通知商户后台订单的付款情况,这个是支付宝每隔一段时间来访问一次的接口,直到你返回success,才会停止访问,这里我分了2个地方进行调用
-
/**
-
* 支付宝支付成功后.异步请求该接口
-
* @param request
-
* @return
-
* @throws IOException
-
*/
-
@RequestMapping(value=
"/notify_url",method=RequestMethod.POST)
-
@ResponseBody
-
public String notify(HttpServletRequest request,HttpServletResponse response) throws IOException {
-
logger.info(
"==================支付宝异步返回支付结果开始");
-
//1.从支付宝回调的request域中取值
-
//获取支付宝返回的参数集合
-
Map<String, String[]> aliParams = request.getParameterMap();
-
//用以存放转化后的参数集合
-
Map<String, String> conversionParams =
new HashMap<String, String>();
-
for (Iterator<String> iter = aliParams.keySet().iterator(); iter.hasNext();) {
-
String key = iter.next();
-
String[] values = aliParams.get(key);
-
String valueStr =
"";
-
for (
int i =
0; i < values.length; i++) {
-
valueStr = (i == values.length -
1) ? valueStr + values[i] : valueStr + values[i] +
",";
-
}
-
// 乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化
-
// valueStr = new String(valueStr.getBytes("ISO-8859-1"), "uft-8");
-
conversionParams.put(key, valueStr);
-
}
-
logger.info(
"==================返回参数集合:"+conversionParams);
-
String status=alipayMentOrderService.notify(conversionParams);
-
return status;
-
}
-
/**
-
* 支付宝异步请求逻辑处理
-
* @param request
-
* @return
-
* @throws IOException
-
*/
-
public String notify(Map<String, String> conversionParams){
-
-
logger.info(
"==================支付宝异步请求逻辑处理");
-
-
//签名验证(对支付宝返回的数据验证,确定是支付宝返回的)
-
boolean signVerified =
false;
-
-
try {
-
//调用SDK验证签名
-
signVerified = AlipaySignature.rsaCheckV1(conversionParams, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE);
-
-
}
catch (AlipayApiException e) {
-
logger.info(
"==================验签失败 !");
-
e.printStackTrace();
-
}
-
-
//对验签进行处理
-
if (signVerified) {
-
//验签通过
-
//获取需要保存的数据
-
String appId=conversionParams.get(
"app_id");
//支付宝分配给开发者的应用Id
-
String notifyTime=conversionParams.get(
"notify_time");
//通知时间:yyyy-MM-dd HH:mm:ss
-
String gmtCreate=conversionParams.get(
"gmt_create");
//交易创建时间:yyyy-MM-dd HH:mm:ss
-
String gmtPayment=conversionParams.get(
"gmt_payment");
//交易付款时间
-
String gmtRefund=conversionParams.get(
"gmt_refund");
//交易退款时间
-
String gmtClose=conversionParams.get(
"gmt_close");
//交易结束时间
-
String tradeNo=conversionParams.get(
"trade_no");
//支付宝的交易号
-
String outTradeNo = conversionParams.get(
"out_trade_no");
//获取商户之前传给支付宝的订单号(商户系统的唯一订单号)
-
String outBizNo=conversionParams.get(
"out_biz_no");
//商户业务号(商户业务ID,主要是退款通知中返回退款申请的流水号)
-
String buyerLogonId=conversionParams.get(
"buyer_logon_id");
//买家支付宝账号
-
String sellerId=conversionParams.get(
"seller_id");
//卖家支付宝用户号
-
String sellerEmail=conversionParams.get(
"seller_email");
//卖家支付宝账号
-
String totalAmount=conversionParams.get(
"total_amount");
//订单金额:本次交易支付的订单金额,单位为人民币(元)
-
String receiptAmount=conversionParams.get(
"receipt_amount");
//实收金额:商家在交易中实际收到的款项,单位为元
-
String invoiceAmount=conversionParams.get(
"invoice_amount");
//开票金额:用户在交易中支付的可开发票的金额
-
String buyerPayAmount=conversionParams.get(
"buyer_pay_amount");
//付款金额:用户在交易中支付的金额
-
String tradeStatus = conversionParams.get(
"trade_status");
// 获取交易状态
-
-
//支付宝官方建议校验的值(out_trade_no、total_amount、sellerId、app_id)
-
AlipaymentOrder alipaymentOrder=
this.selectByOutTradeNo(outTradeNo);
-
-
if(alipaymentOrder!=
null&&totalAmount.equals(alipaymentOrder.getTotalAmount().toString())&&AlipayConfig.APPID.equals(appId)){
-
//修改数据库支付宝订单表(因为要保存每次支付宝返回的信息到数据库里,以便以后查证)
-
alipaymentOrder.setNotifyTime(dateFormat(notifyTime));
-
alipaymentOrder.setGmtCreate(dateFormat(gmtCreate));
-
alipaymentOrder.setGmtPayment(dateFormat(gmtPayment));
-
alipaymentOrder.setGmtRefund(dateFormat(gmtRefund));
-
alipaymentOrder.setGmtClose(dateFormat(gmtClose));
-
alipaymentOrder.setTradeNo(tradeNo);
-
alipaymentOrder.setOutBizNo(outBizNo);
-
alipaymentOrder.setBuyerLogonId(buyerLogonId);
-
alipaymentOrder.setSellerId(sellerId);
-
alipaymentOrder.setSellerEmail(sellerEmail);
-
alipaymentOrder.setTotalAmount(Double.parseDouble(totalAmount));
-
alipaymentOrder.setReceiptAmount(Double.parseDouble(receiptAmount));
-
alipaymentOrder.setInvoiceAmount(Double.parseDouble(invoiceAmount));
-
alipaymentOrder.setBuyerPayAmount(Double.parseDouble(buyerPayAmount));
-
switch (tradeStatus)
// 判断交易结果
-
{
-
case
"TRADE_FINISHED":
// 交易结束并不可退款
-
alipaymentOrder.setTradeStatus((
byte)
3);
-
break;
-
case
"TRADE_SUCCESS":
// 交易支付成功
-
alipaymentOrder.setTradeStatus((
byte)
2);
-
break;
-
case
"TRADE_CLOSED":
// 未付款交易超时关闭或支付完成后全额退款
-
alipaymentOrder.setTradeStatus((
byte)
1);
-
break;
-
case
"WAIT_BUYER_PAY":
// 交易创建并等待买家付款
-
alipaymentOrder.setTradeStatus((
byte)
0);
-
break;
-
default:
-
break;
-
}
-
int returnResult=
this.updateByPrimaryKey(alipaymentOrder);
//更新交易表中状态
-
-
if(tradeStatus.equals(
"TRADE_SUCCESS")) {
//只处理支付成功的订单: 修改交易表状态,支付成功
-
-
if(returnResult>
0){
-
return
"success";
-
}
else{
-
return
"fail";
-
}
-
}
else{
-
return
"fail";
-
}
-
-
}
else{
-
logger.info(
"==================支付宝官方建议校验的值(out_trade_no、total_amount、sellerId、app_id),不一致!返回fail");
-
return
"fail";
-
}
-
-
}
else {
//验签不通过
-
logger.info(
"==================验签不通过 !");
-
return
"fail";
-
}
-
-
}
3.实现第三个接口:在支付完成之后,跳转回APP时,APP调用商户后台进行最终付款校验。我把主要的处理逻辑写在Service层了,Controller层直接调用就可以,这里就不放Controller层的代码了。
-
/**
-
* 向支付宝发起订单查询请求
-
* @param request
-
* @return
-
* @throws IOException
-
*/
-
@Override
-
public Byte checkAlipay(String outTradeNo) {
-
logger.info(
"==================向支付宝发起查询,查询商户订单号为:"+outTradeNo);
-
-
try {
-
//实例化客户端(参数:网关地址、商户appid、商户私钥、格式、编码、支付宝公钥、加密类型)
-
AlipayClient alipayClient =
new DefaultAlipayClient(AlipayConfig.URL, AlipayConfig.APPID,
-
AlipayConfig.RSA_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET,
-
AlipayConfig.ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE);
-
AlipayTradeQueryRequest alipayTradeQueryRequest =
new AlipayTradeQueryRequest();
-
alipayTradeQueryRequest.setBizContent(
"{" +
-
"\"out_trade_no\":\""+outTradeNo+
"\"" +
-
"}");
-
AlipayTradeQueryResponse alipayTradeQueryResponse = alipayClient.execute(alipayTradeQueryRequest);
-
if(alipayTradeQueryResponse.isSuccess()){
-
-
AlipaymentOrder alipaymentOrder=
this.selectByOutTradeNo(outTradeNo);
-
//修改数据库支付宝订单表
-
alipaymentOrder.setTradeNo(alipayTradeQueryResponse.getTradeNo());
-
alipaymentOrder.setBuyerLogonId(alipayTradeQueryResponse.getBuyerLogonId());
-
alipaymentOrder.setTotalAmount(Double.parseDouble(alipayTradeQueryResponse.getTotalAmount()));
-
alipaymentOrder.setReceiptAmount(Double.parseDouble(alipayTradeQueryResponse.getReceiptAmount()));
-
alipaymentOrder.setInvoiceAmount(Double.parseDouble(alipayTradeQueryResponse.getInvoiceAmount()));
-
alipaymentOrder.setBuyerPayAmount(Double.parseDouble(alipayTradeQueryResponse.getBuyerPayAmount()));
-
switch (alipayTradeQueryResponse.getTradeStatus())
// 判断交易结果
-
{
-
case
"TRADE_FINISHED":
// 交易结束并不可退款
-
alipaymentOrder.setTradeStatus((
byte)
3);
-
break;
-
case
"TRADE_SUCCESS":
// 交易支付成功
-
alipaymentOrder.setTradeStatus((
byte)
2);
-
break;
-
case
"TRADE_CLOSED":
// 未付款交易超时关闭或支付完成后全额退款
-
alipaymentOrder.setTradeStatus((
byte)
1);
-
break;
-
case
"WAIT_BUYER_PAY":
// 交易创建并等待买家付款
-
alipaymentOrder.setTradeStatus((
byte)
0);
-
break;
-
default:
-
break;
-
}
-
this.updateByPrimaryKey(alipaymentOrder);
//更新表记录
-
return alipaymentOrder.getTradeStatus();
-
}
else {
-
logger.info(
"==================调用支付宝查询接口失败!");
-
}
-
}
catch (AlipayApiException e) {
-
// TODO Auto-generated catch block
-
e.printStackTrace();
-
}
-
return
0;
-
}
至此,代码已经上完了,里面可能涉及部分业务代码,如果各位需要拿代码,需要把业务代码换成自己所需要的。
建议:可以边看API边进行开发,主要是看我们需要给支付传什么参数,支付宝可以给我们传什么参数,不然没看清除,你会多很多坑要踩的,亲试过。
感觉太少图片了,这里给几张API的图片。。你们也可以自己去看 一些支付宝API
4.关于测试的一些事
支付宝有提供沙箱环境进行测试所使用,见支付宝沙箱调试指南。
但是楼主比较有米,直接用真实环境进行测试,其实测一次0.01元也是挺贵的吧,前提是你的电脑必须访问外网和能够被外网所访问,建议可以找个内网穿透的工具。