作的是微信H5支付,微信APP支付已经有了,H5支付尚未,须要注意的是:H5支付须要在微信支付商家后台单独开通。php
微信H5支付开发流程图:java
红色的就是咱们服务端须要作的,简单来讲的业务流程就是:node
到这里微信H5支付的业务流程基本上算完成了。web
具体的代码是:算法
// 须要的jar等,这里为了方便就把一些工具类的方法写在一块儿了,须要的jar在这里,没有的在下面具体的类上的注释也有标出来 import java.io.IOException; import java.nio.charset.Charset; import java.util.HashMap; import java.util.Map; import org.apache.hc.client5.http.fluent.Request; import org.apache.hc.core5.http.entity.ContentType; import org.dom4j.DocumentException; import org.springframework.stereotype.Service; import com.alibaba.fastjson.JSON; /*建立微信的H5订单信息*/ @Override public OrderCreateVO getWechatH5PayOrderInfo(int fee, String callback, Long uid, String out_trade_no) { // 请求和响应参数参考:https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=9_20&index=1 HashMap<String, String> params = new HashMap<String, String>(); // 公众帐号ID:微信分配的公众帐号ID(企业号corpid即为此appId) params.put("appid", WX_APP_ID); // 商户号:微信支付分配的商户号 params.put("mch_id",WX_MCH_ID); // 设备号:终端设备号(门店号或收银设备ID),注意:PC网页或公众号内支付请传"WEB" params.put("device_info", "WEB"); // 随机字符串:随机字符串,不长于32位。推荐随机数生成算法(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_3) params.put("nonce_str", PayUtil.getRandomHex(32)); // 签名类型:签名类型,目前支持HMAC-SHA256和MD5,默认为MD5 params.put("sign_type", "MD5"); // 商品描述:商品简单描述,该字段须严格按照规范传递,具体请见参数规定(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_2) params.put("body", "XXX-支付"); // 商户订单号:商户系统内部的订单号,32个字符内、可包含字母, 其余说明见商户订单号(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_2) params.put("out_trade_no", out_trade_no); // 总金额:订单总金额,单位为分,详见支付金额(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_2) params.put("total_fee", fee + ""); // 终端IP:必须传正确的用户端IP,详见获取用户ip指引(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=15_5) params.put("spbill_create_ip", PayUtil.getHostIp()); // 通知地址:接收微信支付异步通知回调地址,通知url必须为直接可访问的url,不能携带参数。 // 这里的callback就是微信检测到付款成功之后访问你的系统后台方法,就是你要改票的状态或者推送通知什么的业务,就写到这个连接的方法里面去,方法是本身系统的 params.put("notify_url", callback); // 交易类型:H5支付的交易类型为MWEB params.put("trade_type", "MWEB"); // 签名:签名,详见签名生成算法(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_3) params.put("sign", PayUtil.getWeChatSign(params, WX_API_KEY)); // 参数XML String body = PayUtil.mapToXmlString(params); // 返回响应XML String response; try { response = Request.Post(WX_UNIFIED_ORDER_URL).bodyString(body, APPLICATION_XML).execute().returnContent() .asString(Charset.forName("UTF-8")); } catch (IOException e) { e.printStackTrace(); return null; } //解析相应信息 Map<String, String> xmlResponse; try { xmlResponse = PayUtil.xmlStringToMap(response); } catch (DocumentException e) { e.printStackTrace(); return null; } // 取得解析过的微信响应回执,这个就至关于微信用你的订单,在他系统里下了一个单,返回给你(预支付订单),你再把订单给前端H5页面,页面把这些参数发给微信。其中公众号内支付是访问接口发参数,H5支付是访问mweb_url连接 if ("SUCCESS".equals(xmlResponse.get("return_code")) && "SUCCESS".equals(xmlResponse.get("result_code"))) { // 封装H5页面调用参数,参考文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_7&index=6 Map<String, String> resultMap = new HashMap<String, String>(); // 回调地址:当微信支付成功后,微信会访问回调地址,访问咱们系统的方法,修改票的状态等 String redirectUrl = "&redirect_url=" + callback; // 支付跳转连接:mweb_url为拉起微信支付收银台的中间页面,可经过访问该url来拉起微信客户端,完成支付,mweb_url的有效期为5分钟。 resultMap.put("mwebUrl", xmlResponse.get("mweb_url") + redirectUrl); // 公众帐号ID:调用接口提交的公众帐号ID resultMap.put("appId", xmlResponse.get("appid")); // 时间戳:自1970年以来的秒数 resultMap.put("timeStamp", System.currentTimeMillis() / 1000 + ""); // 随机字符串:微信返回的随机字符串 resultMap.put("nonceStr", xmlResponse.get("nonce_str")); // 订单详情扩展字符串:统一下单接口返回的prepay_id参数值,提交格式如:prepay_id=*** resultMap.put("prepayIdPackage", "prepay_id=" + xmlResponse.get("prepay_id")); // 签名方式:签名类型,默认为MD5,支持HMAC-SHA256和MD5。注意此处需与统一下单的签名类型一致 resultMap.put("signType", "MD5"); // 签名:微信返回的签名,详见签名算法(https://pay.weixin.qq.com/wiki/doc/api/H5.php?chapter=4_3) // 这里是给前端H5封装的请求参数,不能用刚才的请求参数了,须要用此次的请求Map再次签名 resultMap.put("paySign", PayUtil.getWeChatSign(resultMap, WX_API_KEY)); OrderCreateVO createVO = new OrderCreateVO(); createVO.setOrderId(out_trade_no); createVO.setOrderInfo(resultMap); return createVO; } else { System.out.println("微信下单失败..." + xmlResponse.get("return_code") + "; 返回信息: "+xmlResponse.get("return_msg")); return null; } } /** * nonce_str生成算法 * * @param len * @return */ public static String getRandomHex(int len) { if (len == 0) return ""; Random random = new Random(); char[] str = "0123456789abcdefABCDEF".toCharArray(); StringBuffer sb = new StringBuffer(); for (int i = 0; i < len; i++) { sb.append(str[random.nextInt(str.length)]); } return sb.toString(); } /** * spbill_create_ip:获得本地机器的IP * @return */ public static String getHostIp(){ String ip = ""; try{ ip = InetAddress.getLocalHost().getHostAddress(); }catch(UnknownHostException e){ e.printStackTrace(); } return ip; } /** * 获取微信支付的参数签名 * 参考文档:https://pay.weixin.qq.com/wiki/doc/api/app/app.php?chapter=4_3 * * @param map * @param apiKey * @return */ public static String getWeChatSign(Map<String, String> map, String apiKey) { // 先对参数Map进行ASCII字典排序 String authInfo = mapToSortString(map); // 在排序后的参数字符串后拼接API KEY String stringSignTemp = authInfo + "&key=" + apiKey; // 使用org.apache.commons.codec.digest.DigestUtils.md5Hex()进行MD5加密并大写 String sign = DigestUtils.md5Hex(stringSignTemp).toUpperCase(); return sign; } /** * 对Map进行ASCII字典排序 * * @param map * @return */ public static String mapToSortString(Map<String, String> map) { Collection<String> keyset= map.keySet(); List<String> list=new ArrayList<String>(keyset); Collections.sort(list); //排序 StringBuffer sb = new StringBuffer(); for (String key : list) { sb.append(buildKeyValue(key, map.get(key), false)); sb.append("&"); } removeLastChar(sb); return sb.toString(); } /** * Map转XML * * @param map * @return */ public static String mapToXmlString(Map<String, String> map) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.append("<xml>"); for (Map.Entry<String, String> entry : map.entrySet()) { stringBuilder.append(String.format("<%s>%s</%s>", entry.getKey(), entry.getValue(), entry.getKey())); } stringBuilder.append("</xml>"); return stringBuilder.toString(); } /** * XML转Map * * @param map * @return */ public static Map<String, String> xmlStringToMap(String xml) throws DocumentException { Map<String, String> stringMap = new HashMap<String, String>(); // org.dom4j.DocumentHelper Element element = DocumentHelper.parseText(xml).getRootElement(); @SuppressWarnings("unchecked") Iterator<Element> iterator = element.elementIterator(); while (iterator.hasNext()) { Element node = iterator.next(); stringMap.put(node.getName(), node.getTextTrim()); } return stringMap; }
而后前端H5页面支付的问题,由于是测试环境,而微信H5支付须要域名和“商家后台-产品中心-开发配置”里的域名一致,而测试环境域名不一致,再因为短期不能上线,之后上线了再加上。spring