首先就是申请支付宝第三方接口,获取到APPID,应用发布前也可以用支付宝的沙箱环境,服务器要开启外网访问权限,因为支付宝需要返回异步通知(notify_url)和同步通知(return_url)
支付宝最近推出的支付宝接口2.0,网上有很多教程都是之前版本的,但支付宝给出了兼容方案
第三方支付原理
以上简要说明了支付过程,当然其中省略了一些步骤(比如购物车,订单等),我们重点来看支付流程。
2.1用户向商城网站发起确认订单的请求
2.2商城网站接收到请求保存订单数据到数据库或其他存储介质
2.3返回订单确认页面,页面上应该显示订单金额等信息
2.4用户确认支付,发起支付请求。注:支付请求是发送到支付网关(比如支付宝、网银在线)而不是发送到商城网站。
2.5显示支付页面
2.6用户填写认证信息(账号密码等)提交
2.7这里有两个步骤一个是扣款成功后页面跳转到支付结果页面(展示给用户),另一个是支付通知,这两步没有先后顺序可能同时执行,商城网站接收到支付通知后根据验证规则验证信息的有效性,并作出相应的更改操作(例:有效则更改订单为已付款状态,无效则记录非法请求信息)。
以支付宝为例:如果实现在网站中集成支付宝接口,首先要有一个支付宝账号,接下来向支付宝申请在线支付业务,签署协议。协议生效后有支付宝一方会给网站方一个合作伙伴ID,和安全校验码,有了这两样东西就可以按照支付宝接口文档开发支付宝接口了,在上图的几个步骤中只有4和7两个步骤在商城与支付网关之间有信息交互。在步骤4中指将数据发送到支付网关(支付宝),在步骤7中是的通知验证部分,验证网关请求网站某地址,网站按验证规则对信息进行验证记录并作出响应
(1).第一步要先能够跳转到支付宝的支付界面(可以本地测试)。
(2).第二部完成支付跳转回商家界面(必须线上测试,因为返回回来,支付宝找不到你的localhost:8080;所以必须要有域名(地址))。
支付宝开放平台下载demo和SDK(Java版本),然后根据自己所需要,拷贝进它的源代码,也可以全部拷入,但是不一定都用得到。
数据交互方式可以用jsp,也可以用控制器。支付宝给的demo是用的jsp实现。
首先导入支付宝的两个jar包:
但是感觉用控制器(Controller)更好做一些:alipay-sdk-java20170307171631.jar;commons-logging-1.1.1.jar
代码:
配置参数
package com.alipay.config;
public class AlipayConfig {// 商户appid这是从支付宝开放平台获取的APPIDpublic static String APPID = "";// 私钥 pkcs8格式的public static String RSA_PRIVATE_KEY = "";// 服务器异步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问public static String notify_url = "http://www.****.cn/PayTest/notify_url";// 页面跳转同步通知页面路径 需http://或者https://格式的完整路径,不能加?id=123这类自定义参数,必须外网可以正常访问 商户可以自定义同步跳转地址public static String return_url = "http://www.****.cn/PayTest/return_url";// 请求网关地址public static String URL = "https://openapi.alipay.com/gateway.do";// 编码public static String CHARSET = "UTF-8";// 返回格式public static String FORMAT = "json";// 支付宝公钥public static String ALIPAY_PUBLIC_KEY = "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAoUg6+XycCWdQRp2qpHN5b8mw1+juv4dbcYFA61dVivDHH2sqdcL2EFfFO1Msa66T9IeFqJFyod8bVmG8vF3iql12zpceY+eHEZHmkxCBqlivLTJIfChkDmiBlARlnsZ8vK8A01VvlNdgq3g/o6+OzY/frei5bVcMVyIFBjg0fdV9vzOzb4CrQn390JLJ80aA8o5TsvlCeUHg4zAMDUyOm2suAuvgiqekEVS/qvRO+I2O1GTp3dJjPNsdmRVxNsxoU8DEApMLNYmxHiWZpFhRxRdH54ugHWkNmFGyT5y/S1g7dq/nfVgriycZ3BoKtoG4eep7cpK1rnRk+aEpLm9FmQIDAQAB";// 日志记录目录public static String log_path = "/log";// RSA2public static String SIGNTYPE = "RSA2";
}
支付
if(request.getParameter("WIDout_trade_no")!=null){// 商户订单号,商户网站订单系统中唯一订单号,必填String out_trade_no = new String(request.getParameter("WIDout_trade_no").getBytes("ISO-8859-1"),"UTF-8");// 订单名称,必填//String subject = new String(request.getParameter("WIDsubject").getBytes("ISO-8859-1"),"UTF-8");String subject = request.getParameter("WIDsubject");//System.out.println(subject);// 付款金额,必填String total_amount=new String(request.getParameter("WIDtotal_amount").getBytes("ISO-8859-1"),"UTF-8");// 商品描述,可空String body = new String(request.getParameter("WIDbody").getBytes("ISO-8859-1"),"UTF-8");// 超时时间 可空String timeout_express="2m";// 销售产品码 必填String product_code="QUICK_WAP_PAY";/**********************/// SDK 公共请求类,包含公共请求参数,以及封装了签名与验签,开发者无需关注签名与验签 //调用RSA签名方式AlipayClient client = new DefaultAlipayClient(AlipayConfig.URL, AlipayConfig.APPID, AlipayConfig.RSA_PRIVATE_KEY, AlipayConfig.FORMAT, AlipayConfig.CHARSET, AlipayConfig.ALIPAY_PUBLIC_KEY,AlipayConfig.SIGNTYPE);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(AlipayConfig.notify_url);// 设置同步地址alipay_request.setReturnUrl(AlipayConfig.return_url); // form表单生产String form = "";try {// 调用SDK生成表单form = client.pageExecute(alipay_request).getBody();response.setContentType("text/html;charset=" + AlipayConfig.CHARSET); response.getWriter().write(form);//直接将完整的表单html输出到页面 response.getWriter().flush(); response.getWriter().close();} catch (AlipayApiException e) {// TODO Auto-generated catch blocke.printStackTrace();}
}
Controller
异步通知处理支付宝会以json格式返回相应参数
支付状态trade_status的值有四种
@RequestMapping(value="notify_url")
public String notify_url(HttpServletRequest request,HttpServletResponse response) throws AlipayApiException{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] + ",";}//乱码解决,这段代码在出现乱码时使用。如果mysign和sign不相等也可以使用这段代码转化//valueStr = new String(valueStr.getBytes("ISO-8859-1"), "gbk");params.put(name, valueStr);}//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表(以下仅供参考)////商户订单号String out_trade_no = request.getParameter("out_trade_no");//支付宝交易号String trade_no = request.getParameter("trade_no");//交易状态String trade_status = request.getParameter("trade_status");//获取支付宝的通知返回参数,可参考技术文档中页面跳转同步通知参数列表(以上仅供参考)////计算得出通知验证结果//boolean AlipaySignature.rsaCheckV1(Map<String, String> params, String publicKey, String charset, String sign_type)boolean verify_result = AlipaySignature.rsaCheckV1(params, AlipayConfig.ALIPAY_PUBLIC_KEY, AlipayConfig.CHARSET, AlipayConfig.SIGNTYPE);if(verify_result){//验证成功////请在这里加上商户的业务逻辑程序代码//——请根据您的业务逻辑来编写程序(以下代码仅作参考)——if(trade_status.equals("TRADE_FINISHED")){//判断该笔订单是否在商户网站中已经做过处理//注意://如果签约的是可退款协议,退款日期超过可退款期限后(如三个月可退款),支付宝系统发送该交易状态通知//如果没有签约可退款协议,那么付款完成后,支付宝系统发送该交易状态通知。} else if (trade_status.equals("TRADE_SUCCESS")){//判断该笔订单是否在商户网站中已经做过处理service.updatePayStatus(out_trade_no);//注意://如果签约的是可退款协议,那么付款完成后,支付宝系统发送该交易状态通知。}//——请根据您的业务逻辑来编写程序(以上代码仅作参考)——System.out.println("success"); //请不要修改或删除}else{//验证失败System.out.println("fail");}return trade_status;
}
理论上异步方法要快于同步方法,因为中间还有一个跳转流程。所以如果要将支付宝返回来的信息存入数据库,逻辑一般是写在异步方法里面,同步方法只作为页面跳转(支付完成后的跳转)一般不需要验签。如果把逻辑写在同步方法里面,客户在支付成功直接关闭窗口,活动会被关闭,就访问不到控制器了。
而异步方法访问的方法是要在浏览器上直接访问到的!但是一般我们网站都做了权限过滤的,要先去判断是否登录,没登录一般跳转登录界面。但是不论你是用的过滤器还是 shiro,还是其它的,总之你要暴露出这个方法要让支付宝能访问!