微信APP支付
最近公司在开发新项目,需要微信支付 经历了几天时间 终于写出来了 不过微信文档也不是很容易阅读 东西不全 很多东西全靠猜
注意:所有带加密的都需要带上微信商户秘钥(32位) 即key
这边主要是java服务端代码 :
1.1.首先是统一下单接口
一。
微信提供的下单接口 https://api.mch.weixin.qq.com/pay/unifiedorder
通过POST接口访问
Url是访问地址 data是微信转成xml格式的传输参数 必须是UTF-8格式的参数
微信要求的格式是这样 我们只需要填写必要参数就行了。
<xml> <appid>wx2421b1c4370ec43b</appid> <attach>支付测试</attach> <body>APP支付测试</body> <mch_id>10000100</mch_id> <nonce_str>1add1a30ac87aa2db72f57a2375d8fec</nonce_str> <notify_url>http://wxpay.wxutil.com/pub_v2/pay/notify.v2.php</notify_url> <out_trade_no>1415659990</out_trade_no> <spbill_create_ip>14.23.150.211</spbill_create_ip> <total_fee>1</total_fee> <trade_type>APP</trade_type> <sign>0CB01533B8C1EF103065174F50BCA001</sign> </xml>
==============Post 请求封装方法===================
public static String POST(String url, String data){ CloseableHttpClient client = HttpClients.createDefault(); CloseableHttpResponse response = null; try { HttpPost method = new HttpPost(url); method.addHeader(HTTP.CONTENT_TYPE, "application/xml"); StringEntity entity = new StringEntity(data,"UTF-8"); method.setEntity(entity); response = client.execute(method); String s = EntityUtils.toString(response.getEntity(), "UTF-8"); return s; } catch (IOException e) { e.printStackTrace(); }finally { try { response.close(); } catch (Exception e) { e.printStackTrace(); } } return null; }
//返回的数据也是xml格式的
https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=9_1 微信文档写的很清楚
1.2.生成随机字符串
这里我是通过UUID生成的32位随机字符串
/** * 生成32位随机数 * @return */ public static String getRandomNumberAlgorithm(){ String uuid = UUID.randomUUID().toString().toUpperCase(); String replace = uuid.replace("-", ""); return replace; }
1.3 将Map 转换成xml格式的数据字符串
/** * @author * @Description:将请求参数转换为xml格式的string 不包含CDATA标签 * @param parameters * 请求参数 * @return */ public static String getRequestXml2(SortedMap<String, Object> parameters) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set<?> es = parameters.entrySet(); Iterator<?> it = es.iterator(); while (it.hasNext()) { @SuppressWarnings("rawtypes") Map.Entry entry = (Map.Entry) it.next(); String k = entry.getKey().toString(); String v = entry.getValue().toString(); sb.append("<" + k + ">" + v + "</" + k + ">"); } sb.append("</xml>"); return sb.toString(); }
1.4 生成签名
public static String createSign(SortedMap<String, Object> packageParams, String apiKey) { StringBuffer sb = new StringBuffer(); Set<?> es = packageParams.entrySet(); Iterator<?> it = es.iterator(); while (it.hasNext()) { @SuppressWarnings("rawtypes") Map.Entry entry = (Map.Entry) it.next(); String k = entry.getKey().toString(); String v = entry.getValue().toString(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + apiKey); String sign = MD5.encoder(sb.toString()).toUpperCase(); return sign; }
这个时候你将下单返回的参数 传递过去 你以为就成功了? 相信我 幻觉 绝对是幻觉 这个时候浏览安卓支付起掉文档 OK 需要二次加密
ok 我们按照参数 依次写进去
//返回数据 CreateWeiXinPayOrderResponse createWeiXinPayOrderResponse = payResult(stringObjectSortedMap); //或者封装到map中将数据 SortedMap<String, String> secondaryEncryption=new TreeMap<>(); String appid = stringObjectSortedMap.get("appid").toString(); secondaryEncryption.put("appid",appid); String partnerid = request.getMch_id().toString(); secondaryEncryption.put("partnerid",partnerid); String prepay_id = stringObjectSortedMap.get("prepay_id").toString(); secondaryEncryption.put("prepayid",prepay_id); String nonce_str = stringObjectSortedMap.get("nonce_str").toString(); secondaryEncryption.put("noncestr",nonce_str); secondaryEncryption.put("package","Sign=WXPay"); secondaryEncryption.put("timestamp",String.valueOf(time/1000)); String sign1 = createSign("UTF-8", secondaryEncryption, request.getKey()); stringObjectSortedMap.put("sign",sign1); createWeiXinPayOrderResponse.setMap(stringObjectSortedMap);
这个时候 APP 前端有报错 -1 来我们看看微信对这个描述是怎么样的
坑爹的其他异常 搞了一天多 而后查看签名 规则 发现
也就是说 不管是签名几次 什么签名 都需要带上key 即商户密匙!!!
顺带贴上加密 代码:
public static String createSign(String characterEncoding, SortedMap<String, String> parameters,String key) { StringBuffer sb = new StringBuffer(); Set es = parameters.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); Object v = entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + key);//最后加密时添加商户密钥,由于key值放在最后,所以不用添加到SortMap里面去,单独处理,编码方式采用UTF-8 String sign = MD5Encode(sb.toString(), characterEncoding).toUpperCase(); return sign; } public static String MD5Encode(String origin, String charsetname) { String resultString = null; try { resultString = new String(origin); MessageDigest md = MessageDigest.getInstance("MD5"); if (charsetname == null || "".equals(charsetname)) resultString = byteArrayToHexString(md.digest(resultString .getBytes())); else resultString = byteArrayToHexString(md.digest(resultString .getBytes(charsetname))); } catch (Exception exception) { } return resultString; } private static String byteToHexString(byte b) { int n = b; if (n < 0) n += 256; int d1 = n / 16; int d2 = n % 16; return hexDigits[d1] + hexDigits[d2]; } private static final String hexDigits[] = { "0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f" }; private static String byteArrayToHexString(byte b[]) { StringBuffer resultSb = new StringBuffer(); for (int i = 0; i < b.length; i++) resultSb.append(byteToHexString(b[i])); return resultSb.toString(); }
OK 完事!!!
然后是最重要的回调
回调
该链接是通过【统一下单API】中提交的参数notify_url设置,如果链接无法访问,商户将无法接收到微信通知。
通知url必须为直接可访问的url,不能携带参数。示例:notify_url:“https://pay.weixin.qq.com/wxpay/pay.action”
不能带参数这个回调接口 需要自己提供 需要有证书的
这样的话我们就只能从二进制流中取得我们需要的数据了
将数据转换为xml格式的数据 后端进行验签
@Override public String weiXinNotify(HttpServletRequest request, HttpServletResponse response) { StringBuffer xmlStr = new StringBuffer(); try { BufferedReader reader = request.getReader(); String line = null; while ((line = reader.readLine()) != null) { xmlStr.append(line); } reader.close(); logger.info("微信支付回调返回参数:" + xmlStr + ", 即将调用支付模块回调接口"); //调用支付模块的回调接口,处理返回出具 NotifyRequest req = new NotifyRequest(); req.setXmlStr(xmlStr.toString().trim()); NotifyResponse resp = paymentClient.weiXinPayNotify(req); logger.info("微信支付回调返回给微信端的数据:" + resp.getResultXml()); BufferedOutputStream out = new BufferedOutputStream( response.getOutputStream()); out.write(resp.getResultXml().getBytes()); out.flush(); out.close(); } catch (Exception e) { logger.error("微信回调出错: " + e.getMessage()); throw new WebApiCommonException(WebApiCommonErrorCodeType.PayBackError); } return xmlStr.toString(); }
后端取 验签 开发时间紧 我就没封装了
public NotifyResponse weiXinPayNotify(String xmlStr) { NotifyResponse notifyResponse=new NotifyResponse(); // 获取微信配置信息并赋值 QueryAppliactionConfigRequest req = new QueryAppliactionConfigRequest(); req.setConfigType("weixinPay"); req.setConfigName("key"); QueryAppliactionConfigResponse resp = appliactionConfigClient.queryAppliactionConfig(req); if (resp.getData()==null||resp.getData().getList().size()<0){ throw new PayCommonException(PayCommonErrorCodeType.PAY_CONFIG_ERROR); } try { SortedMap<String, Object> map = xmlParse(xmlStr); logger.info(xmlStr); //返回结果 String return_code = map.get("return_code").toString(); if ("SUCCESS".equals(return_code)){ //签名 String sign1 = map.get("sign").toString().toUpperCase(); String sign = PayCommonUtil.createSign(map, resp.getData().getList().get(0).getConfigVal()); //回调验签 if(sign.equals(sign1)||runInTest){ //业务结果 String result_code = map.get("result_code").toString(); if ("SUCCESS".equals(return_code)&&"SUCCESS".equals(result_code)){ //商户订单号 String out_trade_no = map.get("out_trade_no").toString(); notifyResponse.setTradeNo(out_trade_no); //根据商户订单号获取订单信息 PayOrderInfo payOrderInfo = iPayOrderService.queryPayOrderInfoByTradeNo(out_trade_no); if ("5".equals(payOrderInfo.getCurrencyType())||"4".equals(payOrderInfo.getCurrencyType())){ notifyResponse.setResultXml(setXml("FAIL", "该订单已失败或超时")); return notifyResponse; } //根据商户订单号查到的状态判断是否进行下一步骤 payOrderInfo.setCurrencyType("1"); //交易金额 String total_fee = map.get("total_fee").toString(); //商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。 if (Integer.valueOf(total_fee)!= payOrderInfo.getTradeAmount()) { throw new PayCommonException(PayCommonErrorCodeType.PAY_INCORRECT_AMOUNT); } payOrderInfo.setTradeAmount(Integer.valueOf(total_fee)); //支付完成时间 String time_end = map.get("time_end").toString(); if (time_end!=null){ payOrderInfo.setTradeEndTime(new Date()); } //充值方式 payOrderInfo.setTradeChannel(1004); //微信订单号 String transaction_id = map.get("transaction_id").toString(); notifyResponse.setTradeNo(transaction_id); payOrderInfo.setTransactionId(transaction_id); //加入订单信息 if(time_end!=null){ //成功 payOrderInfo.setTradeStatus(3); } iPayOrderService.updatePayOrderByOrderNo(payOrderInfo); logger.info("支付成功!订单号为:"+payOrderInfo.getTradeNo()+"-----"+"第三方订单号:"+payOrderInfo.getTransactionId()); notifyResponse.setResultXml(setXml("SUCCESS", "OK")); return notifyResponse; }else { // 支付出错 notifyResponse.setResultXml(setXml("FAIL","支付订单校验出错")); } }else { //验签失败 notifyResponse.setResultXml(setXml("FAIL","支付验签失败")); } }else { notifyResponse.setResultXml(setXml("FAIL","支付不成功")); } } catch (Exception e) { logger.error("支付回调异常: " + e.getMessage()); notifyResponse.setResultXml(setXml("FAIL","支付回调异常")); return notifyResponse; } return null; }
搞定