最近开发完了微信支付功能模块,下面就趁热打铁整理下微信支付(JSAPI支付)相关的知识。
1、JSAPI支付
JSAPI支付是用户在微信中打开商户的H5页面,商户在H5页面通过调用微信支付提供的JSAPI接口调起微信支付模块完成支付。应用场景有:
(1)用户在微信公众账号内进入商家公众号,打开某个主页面,完成支付
(2)用户的好友在朋友圈、聊天窗口等分享商家页面连接,用户点击链接打开商家页面,完成支付
(3) 将商户页面转换成二维码,用户扫描二维码后在微信浏览器中打开页面后完成支付
微信支付业务流程时序图:
2、获取微信支付的参数
支持微信支付,必须拥有两个账户:
(1)微信公众号认证的服务号,并且需要开通微信支付功能(只有企业才有资格申请)
公众号APPID:appi
APPSECEPT:appsecret
(2)微信商户平台账户
商户ID:mchid
API秘钥:paternerKey
从官网把公众号支付的sdk下载下来,如图所示:
主要是其中用到WXPayUtil工具类中的一些方法和WXPay中的实现方法。
3、微信统一下单接口调用
应用场景:
除付款码支付场景以外,商户系统先调用该接口在微信支付服务后台生成预支付交易单,返回正确的预支付交易会话标识后再按JSAPI的场景生成交易串调起支付。
前提条件:
微信统一下单接口需要用到openId,而获取openId的方法上两章扫码实现微信跳转和分享给好友实现微信跳转中已谈到了网页授权获取用户openId,有需要的小伙伴再重新看下。
如果需要其他获取openid方法可以看开发文档。(https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_4)
微信统一下单接口的调用:
/** * 微信统一下单接口 */ @POST @Path(value = "wechat-createInsurance") @Produces(MediaType.APPLICATION_JSON) public Response WechatCreateInsurance( @Context HttpServletRequest servletRequest) { Map<String, Object> result = new HashMap<>(); try{ Map<String, String> paramMap = new HashMap<>(); paramMap.put("appid", appid); //公众账号ID paramMap.put("mch_id", mchid); //商户号 paramMap.put("nonce_str", WXPayUtil.generateNonceStr()); //随机字符串 paramMap.put("body", shareNoInfo.getString("productName")); //商品描述或所支付的名称 paramMap.put("out_trade_no", outTradeNo); //自己后台提供的唯一的订单号 paramMap.put("total_fee", price); //支付金额,默认单位为分 paramMap.put("spbill_create_ip", servletRequest.getHeader("X-Real-IP")); //终端IP paramMap.put("notify_url", "http://XXXX.com/项目名称/wechat-notification");//微信支付回调地址(具体实现看第五章) paramMap.put("trade_type", "JSAPI"); //支付类型 paramMap.put("openid", openId); //支付人的微信公众号对应的唯一标识 //其他10个必须参数和API秘钥,用generateSignature方法获取sign String sign = WXPayUtil.generateSignature(paramMap, paternerKey); paramMap.put("sign", sign); //记得将sign放在map中凑齐11个参数 String requestXmlString = WXPayUtil.mapToXml(paramMap); //转为xml字符串 String unifiedorder = "https://api.mch.weixin.qq.com/pay/unifiedorder"; //微信统一下单接口 String xmlStr = HttpRequest.sendPost(unifiedorder_url, xml); if (xmlStr.indexOf("SUCCESS") <= 0) { throw new RuntimeException("调用微信统一下单接口报错!"); } Map<String, String> map = WXPayUtil.xmlToMap(xmlStr); String prepay_id = (String) map.get("prepay_id"); //预支付交易会话标识 Map<String, String> payMap = new HashMap<String, String>(); payMap.put("appId", appid); payMap.put("timeStamp", WXPayUtil.getCurrenttimeStamp()+""); payMap.put("nonceStr", WXPayUtil.generateNonceStr()); payMap.put("package", "prepay_id=" + prepay_id); //固定格式 payMap.put("signType", "MD5"); String paySign = WXPayUtil.generateSignature(payMap,paternerKey); payMap.put("paySign", paySign); //......下单成功后的业务流程...... result.put("code", SUCCESS); result.put("result", payMap); } catch(Exception e){ e.printStackTrace(); result.put("code", ERROR); result.put("msg", "系统异常"); } return Response.ok(result).build(); }
注意:
notify_url 回调地址:微信支付成功后,微信那边会带着一大堆参数(XML格式)请求这个地址多次,这个地址做我们业务处理如:修改订单状态,赠送积分等支付成功后的操作。地址要公网才可以访问。
HttpRequest类的结构具体如下:
public class HttpRequest { /** * 向指定URL发送GET方法的请求 * @param url 发送请求的URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return URL 所代表远程资源的响应结果 */ public static String sendGet(String url, String param) { String result = ""; BufferedReader in = null; try { String urlNameString = url + "?" + param; System.out.println(urlNameString); URL realUrl = new URL(urlNameString); // 打开和URL之间的连接 URLConnection connection = realUrl.openConnection(); // 设置通用的请求属性 connection.setRequestProperty("accept", "*/*"); connection.setRequestProperty("connection", "Keep-Alive"); connection.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 建立实际的连接 connection.connect(); // 获取所有响应头字段 Map<String, List<String>> map = connection.getHeaderFields(); // 遍历所有的响应头字段 for (String key : map.keySet()) { System.out.println(key + "--->" + map.get(key)); } // 定义 BufferedReader输入流来读取URL的响应 in = new BufferedReader(new InputStreamReader( connection.getInputStream())); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { System.out.println("发送GET请求出现异常!" + e); e.printStackTrace(); } // 使用finally块来关闭输入流 finally { try { if (in != null) { in.close(); } } catch (Exception e2) { e2.printStackTrace(); } } return result; } /** * 向指定 URL 发送POST方法的请求 * @param url 发送请求的 URL * @param param 请求参数,请求参数应该是 name1=value1&name2=value2 的形式。 * @return 所代表远程资源的响应结果 */ public static String sendPost(String url, String param) { PrintWriter out = null; BufferedReader in = null; String result = ""; try { URL realUrl = new URL(url); // 打开和URL之间的连接 URLConnection conn = realUrl.openConnection(); // 设置通用的请求属性 conn.setRequestProperty("accept", "*/*"); conn.setRequestProperty("connection", "Keep-Alive"); conn.setRequestProperty("user-agent", "Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1;SV1)"); // 发送POST请求必须设置如下两行 conn.setDoOutput(true); conn.setDoInput(true); // 获取URLConnection对象对应的输出流 out = new PrintWriter(conn.getOutputStream()); // 发送请求参数 out.print(param); // flush输出流的缓冲 out.flush(); // 定义BufferedReader输入流来读取URL的响应 in = new BufferedReader( new InputStreamReader(conn.getInputStream())); String line; while ((line = in.readLine()) != null) { result += line; } } catch (Exception e) { System.out.println("发送 POST 请求出现异常!"+e); e.printStackTrace(); } //使用finally块来关闭输出流、输入流 finally{ try{ if(out!=null){ out.close(); } if(in!=null){ in.close(); } } catch(IOException ex){ ex.printStackTrace(); } } return result; } }
4、微信支付前端页面调用
$('#btn').click(function() { var param = { //传入参数 ..... }; $.ajax({ type: "post", url: 'http://XXXX.com/项目名/wechat-createInsurance', contentType: 'application/json;charset=UTF-8', dataType: 'json', data: JSON.stringify(param), success: function(result) { if(result.code==SUCCESS){ function onBridgeReady() { WeixinJSBridge.invoke( 'getBrandWCPayRequest', { "appId": result.result.appId, // 公众账号ID "timeStamp": result.result.timeStamp, //时间戳,自1970年以来的秒 "nonceStr": result.result.nonceStr, //随机串 "package": result.result.package, "signType": result.result.signType, //微信签名方式: "paySign": result.result.paySign //微信签名 }, function(res) { if (res.err_msg == "get_brand_wcpay_request:ok") { // 使用以上方式判断前端返回,微信团队郑重提示: //res.err_msg将在用户支付成功后返回ok,但并不保证它绝对可靠。 window.location.href = 'sucess.html' ; //成功支付后跳转到下个页面 }else{ alert("微信支付失败"); } } ); } if (typeof WeixinJSBridge == "undefined") { if (document.addEventListener) { document.addEventListener('WeixinJSBridgeReady', onBridgeReady, false); } else if (document.attachEvent) { document.attachEvent('WeixinJSBridgeReady', onBridgeReady); document.attachEvent('onWeixinJSBridgeReady', onBridgeReady); } } else { onBridgeReady(); } }else{ alert(result.msg); } }, error: function(err) { alert("登陆失败"); console.log(err); } }); });
点击按钮,访问接口成功后,微信端h5页面自动弹出微信自带的支付页面,支付完成后,点击确定,微信会自动回调用户写微信支付回调接口。(微信支付回调接口具体实现看第五章)
5、微信支付回调接口
应用场景:
支付完成后,微信会把相关支付结果及用户信息通过数据流的形式发送给商户,商户需要接收处理,并按文档规范返回应答。
微信的通知参数格式:(具体查看微信支付结果通知)<https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7&index=8>
<xml> <appid><![CDATA[wx2421b1c4370ec43b]]></appid> <attach><![CDATA[支付测试]]></attach> <bank_type><![CDATA[CFT]]></bank_type> <fee_type><![CDATA[CNY]]></fee_type> <is_subscribe><![CDATA[Y]]></is_subscribe> <mch_id><![CDATA[10000100]]></mch_id> <nonce_str><![CDATA[5d2b6c2a8db53831f7eda20af46e531c]]></nonce_str> <openid><![CDATA[oUpF8uMEb4qRXf22hE3X68TekukE]]></openid> <out_trade_no><![CDATA[1409811653]]></out_trade_no> <result_code><![CDATA[SUCCESS]]></result_code> <return_code><![CDATA[SUCCESS]]></return_code> <sign><![CDATA[B552ED6B279343CB493C5DD0D78AB241]]></sign> <time_end><![CDATA[20140903131540]]></time_end> <total_fee>1</total_fee> <coupon_fee><![CDATA[10]]></coupon_fee> <coupon_count><![CDATA[1]]></coupon_count> <coupon_type><![CDATA[CASH]]></coupon_type> <coupon_id><![CDATA[10000]]></coupon_id> <coupon_fee><![CDATA[100]]></coupon_fee> <trade_type><![CDATA[JSAPI]]></trade_type> <transaction_id><![CDATA[1004400740201409030005092168]]></transaction_id> </xml>
微信支付回调接口的实现:
@POST @Path(value = "wechat-notification") public Response WeChatNotification(@Context HttpServletRequest request, @Context HttpServletResponse response) throws IOException, DocumentException { try { //微信发的xml格式的数据转为Map格式 Map<String, String> paramMap = new HashMap<String, String>(); InputStream inputStream = request.getInputStream(); SAXReader reader = new SAXReader(); Document document = reader.read(inputStream); Element root = document.getRootElement(); List<Element> elementList = root.elements(); for (Element e : elementList) { paramMap.put(e.getName(), e.getText()); } inputStream.close(); inputStream = null; if("FAIL".equals(paramMap.get("return_code"))){ throw new RuntimeException("return_code为fail, " + paramMap.get("return_msg")); } if("FAIL".equals(paramMap.get("result_code"))){ throw new RuntimeException("result_code为fail, " + paramMap.get("err_code_des")); } //...... 业务流程 ...... //响应给微信的一定是以流的方式 //告诉微信服务器收到信息了,不要在调用回调action了(回复微信服务器信息用流发送一个xml即可) return Response.ok("<xml><return_code><![CDATA[SUCCESS]]></return_code>" + " <return_msg><![CDATA[OK]]></return_msg></xml>").build(); } catch (Exception e) { e.printStackTrace(); return Response.ok("<xml><return_code><![CDATA[FAIL]]></return_code>" +"<return_msg><![CDATA["+e.getMessage() +"]]></return_msg></xml>").build(); } }
注意:
(1)该链接是通过【[统一下单】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。
(2)通知url必须为直接可访问的url,不能携带参数。
(3)商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。
(4)技术人员可登进微信商户后台扫描加入接口报警群,获取接口告警信息。
(5)同样的通知可能会多次发送给商户系统。商户系统必须能够正确处理重复的通知。
(6)后台通知交互时,如果微信收到商户的应答不符合规范或超时,微信会判定本次通知失败,重新发送通知,直到成功为止,但微信不保证通知最终一定能成功。
(7)在订单状态不明或者没有收到微信支付结果通知的情况下,建议商户主动调用微信支付【查询订单】确认订单状态。(可查看第六章,注意可以通过轮询查询支付订单来监控是否已支付)
6、查询订单接口
应用场景:
该接口提供所有微信支付订单的查询,商户可以通过查询订单接口主动查询订单状态,完成下一步的业务逻辑。
需要调用查询接口的情况:
(1)当商户后台、网络、服务器等出现异常,商户系统最终未接收到支付通知;
(2)调用支付接口后,返回系统错误或未知交易状态情况;
(3)调用付款码支付API,返回USERPAYING的状态;
(4)调用关单或撤销接口API之前,需确认支付状态;
查询订单接口实现:
@GET @Path(value = "wechat-orderquery") @Produces(MediaType.APPLICATION_JSON) public Response WechatOrderquery(@QueryParam("outTradeNo") String outTradeNo){ Map<String, Object> result = new HashMap<String, Object>(); try { HashMap<String, String> data = new HashMap<String, String>(); data.put("out_trade_no", outTradeNo);// 商户订单号 data.put("mch_id", mchid);// 商户号 data.put("nonce_str", WXPayUtil.generateNonceStr());// 随机字符串 data.put("appid", appid);// 公众账号ID String sign = WXPayUtil.generateSignature(data, paternerKey); data.put("sign", sign);// 签名 WXPay wxpay = new WXPay(MyConfig.getInstance()); //导入微信的下载的包 Map<String, String> resp = wxpay.orderQuery(data); if("FAIL".equals(resp.get("return_code"))){ throw new RuntimeException("return_code为fail, " + resp.get("return_msg")); } if("FAIL".equals(resp.get("result_code"))){ throw new RuntimeException("result_code为fail, " + resp.get("err_code_des")); } if(!"SUCCESS".equals(resp.get("trade_state"))){ throw new RuntimeException("trade_state为" + resp.get("trade_state")); } //.......业务流程...... result.put("paperno", paperno); result.put("code", SUCCESS); }catch(Exception e){ e.printStackTrace(); result.put("code", ERROR); result.put("msg", "订单查询异常"); } return Response.ok(result).build(); }