微信公众号支付开发流程与避坑手册-Java篇

来源:互联网 发布:导航升级软件下载 编辑:程序博客网 时间:2024/06/09 06:43

最近完成了微信公众号内的未支付功能,当然开发的过程中难免遇到各种各样的问题,在这里把我开发的过程分享出来,给大家做个参考。
首先,在准备开发的时候需要进行必要的配置。

1.登录微信商户平台,在产品中心->开发配置中对支付授权目录进行配置
(注意:支付授权目录的配置规则是你使用微信支付控件页面的上一级目录,比如:你在www.xxx.cpm/wx/pay/pay.html中调用微信支付控件,那么你需要配置的目录是www.xxx.cpm/wx/pay/)
这里写图片描述

2.在账户中心->API安全中配置key值,此参数在生成签名时需要用到。
这里写图片描述

3.其他参数与配置:appid,开发者密码以及网页授权
这里写图片描述

在做完这些配置以后,我们就可以进行微信支付的开发了。在这里推荐参考微信平台提供的demo。https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=11_1

按照文档中统一接口的实现流程,我们需要先实现发起微信支付的请求。这里的请求链接是统一下单的链接,POST请求

  /**     * 发起微信支付请求      * @param requestUrl  请求链接     * @param method  请求方式     * @param param  请求参数     * @param connectTimeOut  连接超时时间     * @param readTimeOut  读取超时时间     * @return 请求结果     */public String requestOnce(final String requestUrl, final String method, String param, boolean useCert, int connectTimeOut, int readTimeOut) {        HttpURLConnection conn = null;        StringBuilder builder = new StringBuilder();        try {            URL url = new URL(requestUrl);            conn = (HttpURLConnection) url.openConnection();            conn.setDoOutput(true);            conn.setDoInput(true);            conn.setUseCaches(false);            conn.setRequestMethod(method);            conn.setConnectTimeout(connectTimeOut);            conn.setReadTimeout(readTimeOut);            OutputStream out = conn.getOutputStream();            out.write(param.getBytes());            out.flush();            out.close();            int responseCode = conn.getResponseCode();            if (responseCode == 200) {                InputStream inputStream = conn.getInputStream();                BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "utf-8"));                String line = null;                while ((line = reader.readLine()) != null) {                    builder.append(line);                }                reader.close();                inputStream.close();            }        } catch (MalformedURLException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }        return builder.toString();    }

在这里我们将微信支付所需要的参数进行填充。

 /**     * 填充微信支付参数     * @param data 需要填充的Hashmap     * @return 参数     */    public Map<String, String>  fillRequestData(Map<String, String> data) throws Exception {        data.put("appid", config.getAPPID());        data.put("mch_id", config.getMCHID());        data.put("nonce_str", WXPayUtil.generateNonceStr());        data.put("notify_url",config.getNOTIFY_URL());        data.put("spbill_create_ip", config.getSERVER_IP());        data.put("trade_type", config.getJSAPI());        if (WXPayConstants.SignType.MD5.equals(this.signType)) {            data.put("sign_type", WXPayConstants.MD5);        }        else if (WXPayConstants.SignType.HMACSHA256.equals(this.signType)) {            data.put("sign_type", WXPayConstants.HMACSHA256);        }        data.put("sign", WXPayUtil.generateSignature(data, config.getKEY(), this.signType));        return data;    }

在请求成功之后,微信服务器会返回xml形式的参数,我们可以将xml转换为Map(此方法在微信demo中有实现)。此外在结果返回成功以后,需要再次生成签名,此签名是由于前端页面调起微信支付控件所用

 /**     * 发起微信支付认证请求     * @param requestUrl 请求链接     * @param method 请求方式     * @param param 请求参数     * @param useCert 是否使用证书     * @return 认证结果     */    private Map<String, String> request(final String requestUrl, final String method, String param, boolean useCert) {        String strXml = wxPayRequest.request(requestUrl, method, param, useCert, config.getHttpConnectTimeoutMs(), config.getHttpReadTimeoutMs());        Map<String, String> data;        try {            data = WXPayUtil.xmlToMap(strXml);        } catch (Exception e) {            data = null;            e.printStackTrace();        }        return data;    }private Map<String, String> returnAuthParam(Map<String, String> temp, String key) {        Map<String, String> result = new HashMap<String, String>();        long timestamp = System.currentTimeMillis() / 1000;        result.put("appId", temp.get("appid"));        result.put("timeStamp", String.valueOf(timestamp));        result.put("nonceStr", temp.get("nonce_str"));        result.put("package", "prepay_id=" + temp.get("prepay_id"));        result.put("signType", WXPayConstants.MD5);        String sign = null;        try {            sign = WXPayUtil.generateSignature(result, key, WXPayConstants.SignType.MD5);        } catch (Exception e) {            e.printStackTrace();        }        result.put("paySign", sign);        result.put("return_code", temp.get("return_code"));        result.put("result_code", temp.get("result_code"));        return result;    }

签名生成方法:

/**     * 生成签名 MD5 HMACSHA256     * @param data 参数     * @param key API密钥     * @param signType 签名方式     * @return 签名     */    public static String generateSignature(final Map<String,String> data, String key, WXPayConstants.SignType signType) throws Exception {        Set<String> keySet = data.keySet();        String[] keyArray = keySet.toArray(new String[keySet.size()]);        Arrays.sort(keyArray);        StringBuilder sb = new StringBuilder();        for (String k : keyArray) {            if (k.equals(WXPayConstants.FIELD_SIGN)) {                continue;            }            if (data.get(k).trim().length() > 0) {                sb.append(k).append("=").append(data.get(k).trim()).append("&");            }        }        sb.append("key=").append(key);        if (WXPayConstants.SignType.MD5 == signType) {            return MD5(sb.toString()).toUpperCase();        }        throw new Exception(String.format("Invalid sign_type: %s", signType));    }

在做完这些操作以后,就可以在前端实现调起微信支付控件的逻辑了。在微信支付文档中给出了一种写法:

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(data);                    }function onBridgeReady(data){    orderPage.orderParam.orderId = data.orderId;    WeixinJSBridge.invoke(        'getBrandWCPayRequest', {        "appId": data.appId,     //公众号名称,由商户传入              "timeStamp": data.timeStamp, //时间戳,自1970年以来的秒数        "nonceStr": data.nonceStr, //随机串             "package": data.package,             "signType": data.signType,         //微信签名方式:             "paySign": data.paySign //微信签名         },        function(res){            alert(res.err_msg + " " + res.err_code + " " + res.err_desc);            if(res.err_msg == "get_brand_wcpay_request:ok"){                orderPage.orderParam.status = "WAITSEND";                orderPage.submitOrder();            }else if(res.err_msg == "get_brand_wcpay_request:fail"){                alert("调用微信支付失败");            }else if(res.err_msg == "get_brand_wcpay_request:cancel"){                alert("已取消支付");            }        }    ); }

在上面所使用到的参数都是服务端微信支付请求成功后获取的参数。完成这些之后,微信支付就可以使用了。
但是我们开发的过程中,如果没有相关的项目经验,难免会遇到很多坑。这里我就列举出我在开发中遇到的问题。
1.参数缺失:缺少openid,在微信公众号内嵌网页中调用微信支付需要添加用户的openid。
2.参数形式错误:在前端调起微信支付控件时,将package参数传错,package参数的正确格式为:“prepay_id=XXXXXXXXX”,而我忘了加入“prepay_id=”,这个错误着实让我找了好久(都是不仔细阅读文档的问题)。
3.签名失败:这个签名失败是在前端提示。造成的原因是我在微信支付请求成功之后,直接使用了服务端申请微信支付请求生成的签名,而正确的应该是在申请支付权限成功之后,需要将前端用到的参数拿过来重新生成一次签名才对。这个错误卡了我大半天才搞定,需要注意。
4.get_wcpay_branch_request:fail,这个错误的排查顺序是:首先查看返回参数中request_code,result_code的返回值,如果都为SUCCESS,那么说明服务端请求微信支付权限已经成功,错误就来自于前端,或者参数错误(参考3)。

微信支付的调试:
由于开发微信支付需要调用第三方接口,所以在调试的时候会遇到诸多不便。这里我们就需要使用一些工具来帮助我们完成。
1.微信web开发者工具,我们可以使用这个工具进行调试,直接输入链接地址,然后在这里可以断点调试也可以查看相关错误信息。
这里写图片描述
2。打印log。在前端我们可以打印出关键信息进行错误排查。比如说,在调用微信支付控件时,输出err_msg,err_code,err_desc,进行查看。
这里写图片描述

最后说两句
在做完微信支付以后,其实你会发现并没有太大难度,就是在开发的过程中我们需要善于发现问题。还有就是要仔细阅读文档,我遇到的一些错误有很多都是文档中所提到的,但是没有仔细看所以就花费了很多时间去排查错误,而这些都是可以避免的。

最后一张,微信支付的目录结构(仅供参考):
这里写图片描述

原创粉丝点击