支付宝支付、查询订单、退款
1. 前言
对接了好长一段时间的支付,期间涉及到支付宝相关工作,这里将支付宝相关部分整理一下。
环境配置,主要是在pom文件中添加如下依赖:
<dependency> <groupId>com.alipay.sdk</groupId> <artifactId>alipay-sdk-java</artifactId> <version>3.1.0</version> </dependency>
支付宝的支付api文档比较完整,对接起来也比较顺利,下面就开始吧‘(*>﹏<*)′
2. 电脑网站支付
其实是统一收单下单并支付页面接口(alipay.trade.page.pay),PC场景下单并支付,接口调用成功后会返回一个form表单,直接提交就可以了,跳过去的页面是二维码,扫码支付,成功后会回跳到指定的页面(这里是一个重定向,重定向地址是在发起调用时以接口参数的形式指定的)。
接口发起示例代码,详见统一收单下单并支付页面接口。
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do", app_id, private_key, "json", "UTF-8", alipay_public_key, "RSA2");
//设置请求参数
AlipayTradePagePayRequest alipayRequest = new AlipayTradePagePayRequest();
alipayRequest.setReturnUrl(returnUrl); // 支付成功后回跳地址
alipayRequest.setNotifyUrl(notifyUrl); // 支付后的异步通知地址
//商户订单号,商户网站订单系统中唯一订单号,必填
String out_trade_no = "";
//付款金额,必填
String total_amount = "";
//订单名称,必填
String subject = "";
//商品描述,可空
String body = "";
// 销售产品码 必填
String product_code="FAST_INSTANT_TRADE_PAY";
alipayRequest.setBizContent("{\"out_trade_no\":\""+ out_trade_no +"\","
+ "\"total_amount\":\""+ total_amount +"\","
+ "\"subject\":\""+ subject +"\","
+ "\"body\":\""+ body +"\","
+ "\"timeout_express\":\"10m\","
+ "\"product_code\":\""+product_code+"\"}");
//请求
String form = "";
try{
form = alipayClient.pageExecute(alipayRequest).getBody();//调用SDK生成表单
}catch (AlipayApiException e){
// TODO Auto-generated catch block
e.printStackTrace();
}
return form ;
这里拿到的是一个字符串,其实是一个form表单,前端收到这个之后直接跳过去就可以了,剩下的就交给支付宝了,厉害吧!示例如下:
-<!DOCTYPE html> <html lang="zh-CN"> <head> <title>Document</title> </head> <body> <form ...> // 这里就是返回的form字符串 </form> <script>document.forms[0].submit();</script> </body> <script> </script> </html>
关于异步回调,后台需要提供一个外网可访问的接口,供支付宝回调,示例代码:
//获取支付宝POST过来反馈信息 logger.debug("----------------------------------->pay notify start--------------^_^"); 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] + ","; } params.put(name, valueStr); } // 验签 boolean result = AlipaySignature.rsaCheckV1(params, aliPayPublicKey, "utf-8" ,"RSA2"); if (result) { // 验签成功 if (trade_status.equals("TRADE_FINISHED")) { response.getWriter().print("success"); } else if (trade_status.equals("TRADE_SUCCESS")) { // 支付成功 // 业务逻辑 } } else {//验证失败 response.getWriter().print("failure"); } }
3. 手机网站支付
这个是用于手机端,在手机浏览器上发起的,接口名称(alipay.trade.wap.pay),这个接口和电脑网站支付接口返回的结果类似,也是一个form表单,相关操作类似,跳转过去之后会检测手机上是否安装支付宝客户端,如果有则打开客户端支付,没有则在网页上登陆进行支付,示例代码,详见手机网站支付接口2.0:
String out_trade_no = ""; // 商户订单号,商户网站订单系统中唯一订单号,必填 String subject = ""; // 订单名称,必填 String total_amount=""; // 付款金额,必填 String body = ""; // 商品描述,可空 String timeout_express="30m"; // 超时时间 可空 // SDK 公共请求类,包含公共请求参数,以及封装了签名与验签,开发者无需关注签名与验签 //调用RSA签名方式 AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do",appId, privateKey, "json", "UTF-8", aliPayPublicKey, "RSA2"); // 移动端封装请求支付信息 String product_code="QUICK_WAP_PAY"; //销售产品码 必填 AlipayTradeWapPayRequest alipay_request=new AlipayTradeWapPayRequest(); AlipayTradeWapPayModel model=new AlipayTradeWapPayModel(); model.setOutTradeNo(out_trade_no); model.setSubject(subject); model.setTotalAmount(total_amount); model.setBody(body); model.setTimeoutExpress(timeout_express); model.setProductCode(product_code); alipay_request.setBizModel(model); alipay_request.setNotifyUrl(notifyUrl);// 设置异步通知地址,支付成功后支付宝回掉通知地址 alipay_request.setReturnUrl(returnUrl);// 设置同步地址,支付后页面跳转的地址 // form表单生产 String form = ""; try { // 调用SDK生成表单 form = alipayClient.pageExecute(alipay_request).getBody(); } catch (AlipayApiException e) { } return form;
4. 交易查询
通过调用alipay.trade.query(统一收单线下交易查询)接口来实现,该接口提供所有支付宝支付订单的查询,商户可以通过该接口主动查询订单状态,完成下一步的业务逻辑。
需要调用查询接口的情况:
- 当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
- 调用支付接口后,返回系统错误或未知交易状态情况;
- 调用alipay.trade.pay,返回INPROCESS的状态;
- 调用alipay.trade.cancel之前,需确认支付状态;
示例代码,详见支付宝文档:
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do","app_id","your private_key","json","utf8","alipay_public_key","RSA2"); AlipayTradeQueryRequest request = new AlipayTradeQueryRequest(); request.setBizContent("{" + "\"out_trade_no\":\"20150320010101001\"," + "\"trade_no\":\"2014112611001004680073956707\"," + "\"org_pid\":\"2088101117952222\"" + " }"); AlipayTradeQueryResponse response = alipayClient.execute(request); if(response.isSuccess()){ System.out.println("调用成功");
// 业务逻辑
} else {
System.out.println("调用失败");
}
5. 退款
通过调用alipay.trade.refund(统一收单交易退款接口)接口实现,当交易发生之后一段时间内,由于买家或者卖家的原因需要退款时,卖家可以通过退款接口将支付款退还给买家,支付宝将在收到退款请求并且验证成功之后,按照退款规则将支付款按原路退到买家帐号上。 交易超过约定时间(签约时设置的可退款时间)的订单无法进行退款 支付宝退款支持单笔交易分多次退款,多次退款需要提交原支付订单的商户订单号和设置不同的退款单号。一笔退款失败后重新提交,要采用原来的退款单号。总退款金额不能超过用户实际支付金额。
关于退款的异步通知,这和微信是不一样的,微信的异步通知地址是在发起退款接口时指定的,而支付宝呢,比较厉害,分两种情况:
- 全额退款,交易状态变为交易关闭(TRADE_CLOSED),具体是否会触发异步通知根据接口中的通知触发条件判断,我的测试是没有回调通知;
- 部分退款都会触发异步通知,异步通知的地址是支付时指定的异步回调地址;
那既然退款的回调地址和支付时的回调地址是一致的,我如何区分一次回调是支付还是退款呢?退款的异步通知参数中一定会返回out_biz_no(商户业务号)、refund_fee(总退款金额)、gmt_refund(交易退款时间)。
关于退款期限,支付宝一般是3个月,这个可以在开通的服务签约信息中查看。
示例代码,详见退款文档:
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do","app_id","your private_key","json","GBK","alipay_public_key","RSA2"); AlipayTradeRefundRequest request = new AlipayTradeRefundRequest(); request.setBizContent("{" + "\"out_trade_no\":\"20150320010101001\"," + "\"trade_no\":\"2014112611001004680073956707\"," + "\"refund_amount\":200.12," + "\"refund_currency\":\"USD\"," + "\"refund_reason\":\"正常退款\"," + "\"out_request_no\":\"HZ01RF001\"," + "\"operator_id\":\"OP001\"," + "\"store_id\":\"NJ_S_001\"," + "\"terminal_id\":\"NJ_T_001\"," + " \"goods_detail\":[{" + " \"goods_id\":\"apple-01\"," + "\"alipay_goods_id\":\"20010001\"," + "\"goods_name\":\"ipad\"," + "\"quantity\":1," + "\"price\":2000," + "\"goods_category\":\"34543238\"," + "\"categories_tree\":\"124868003|126232002|126252004\"," + "\"body\":\"特价手机\"," + "\"show_url\":\"http://www.alipay.com/xxx.jpg\"" + " }]," + " \"refund_royalty_parameters\":[{" + " \"royalty_type\":\"transfer\"," + "\"trans_out\":\"2088101126765726\"," + "\"trans_out_type\":\"userId\"," + "\"trans_in_type\":\"userId\"," + "\"trans_in\":\"2088101126708402\"," + "\"amount\":0.1," + "\"amount_percentage\":100," + "\"desc\":\"分账给2088101126708402\"" + " }]," + "\"org_pid\":\"2088101117952222\"" + " }"); AlipayTradeRefundResponse response = alipayClient.execute(request); if(response.isSuccess()){ System.out.println("调用成功");
// 业务逻辑 } else { System.out.println("调用失败"); }
最后退款成功后可以在手机支付宝--朋友--服务提醒中查看,如下所示:
6. 退款查询
通过调用alipay.trade.fastpay.refund.query接口完成退款查询功能,该接口的返回码10000,仅代表本次查询操作成功,不代表退款成功。如果该接口返回了查询数据,则代表退款成功,如果没有查询到则代表未退款成功,可以调用退款接口进行重试。
关于如何确定退款是否成功,可以通过主动调用查询接口来确定,也可通过如上的回调接口等待支付宝的异步通知,但是如果未收到支付宝的异步通知并且确定不是参数以及回调地址的问题,这时应该主动调用查询接口来同步退款状态。
示例代码,详细见统一收单交易退款查询。
AlipayClient alipayClient = new DefaultAlipayClient("https://openapi.alipay.com/gateway.do","app_id","your private_key","json","GBK","alipay_public_key","RSA2"); AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest(); request.setBizContent("{" + "\"trade_no\":\"20150320010101001\"," + "\"out_trade_no\":\"2014112611001004680073956707\"," + "\"out_request_no\":\"2014112611001004680073956707\"," + "\"org_pid\":\"2088101117952222\"" + " }"); AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request); if(response.isSuccess()){ System.out.println("调用成功"); } else { System.out.println("调用失败"); }
7. 遇到的问题
1. 订单查询时报:com.alipay.api.AlipayApiException: sign check fail: check Sign and Data Fail
原因:报这个错误是因为支付宝公钥(alipay_public_key)使用错误导致的!很多开发者容易把自己生成的应用公钥和支付宝公钥搞混淆,从而配置错误导致这个错误,自己生成的是应用公钥和应用私钥!
- RSA支付宝公钥对于所有商户都是唯一的相同值
- RSA2对于所有商户都是单独一对一的,并且只支持开发平台密钥管理和沙箱RSA2支付宝公钥,只能您的appid下面商户公钥上传才会显示,并且获取只能从这个位置获取, 所有商户一个账号下的RSA2支付宝公钥是相同的。
具体查看可参考下图,蚂蚁金服开放平台登陆--开发者中心--网页&移动应用--应用列表--查看详情--应用信息。
如何设置公私钥,可参考:上传应用公钥并获取支付宝公钥
解决办法:
确认使用的支付宝公钥是否正确,不同的环境使用的支付宝公钥不同,如沙箱环境、线上openapi网关和mapi网关对应的支付宝公钥是不一样的。查看地址如下:
查看到正确的公钥后,更换正确的支付宝公钥后即可成功。
参考文献: