最近在做微信的过程中遇到不少问题,历经了不少坑,才发现网上的资料真的很少,大多都混乱零碎,而且做的时候遇到一些问题在两个微信群里问了,做完才发现他们的有些答案给了我陷阱。
加上最近身边很多童鞋也在做微信支付,而我上周刚好做完,现在整理一下,记录下来,希望也能帮到大家,注意这篇文章只是对API文档的一个解读。
首先还是先理清一下思路,做微信支付需要怎么入手,v3.3.7里面的demo只有php版本,里面是这样写的:
/** * JS_API支付demo * ==================================================== * 在微信浏览器里面打开H5网页中执行JS调起支付。接口输入输出数据格式为JSON。 * 成功调起支付需要三个步骤: * 步骤1:网页授权获取用户openid * 步骤2:使用统一支付接口,获取prepay_id * 步骤3:使用jsapi调起支付 */
看到这里心里就有个大概的思路了:
第一步应该不难,openid我们先跳过,关键在于获取prepay_id,有了prepay_id才能构建package,才能调用JSAPI进行下一步操作。
看文档发现获取需要调用API里面的统一支付接口,它返回的是预支付订单号。
首先列举一些必填的参数(其他非必填的我这里都忽略了),接口需要的请求参数为:
预支付请求参数: appid,body,notify_url,out_trade_no,mch_id,total_fee,spbill_create_ip,trade_type,nonce_str,openid,sign
具体的要求和说明参考文档,这里面的关键部分就是构建签名。
这里很清晰了,之前我的签名一直不对,而且感觉微信支付API怎么这么模糊,到底传哪些参数都不知道。后来仔细看文档才发现,需要对所有传入参数进行加工生成签名。
思路是:对预支付参数进行排序生成String1,在string1后面拼接上商户的支付密钥得到String2,然后对String2 进行MD5加密得到String3,将String3转换为大写得到sign。
具体的方法和参数为:
// 设置预支付参数 SortedMap<String, String> signParams = new TreeMap<String, String>(); signParams.put("appid", APP_ID); signParams.put("body", order.getInfo()); // 商品描述 signParams.put("notify_url", NOTIFY_URL); // 通知地址 signParams.put("out_trade_no", order.getOrderNo()); // 商户订单号 signParams.put("mch_id", PARTNER); // 设置商户号 signParams.put("total_fee", WxPayUtil.changeY2F(String.valueOf(order.getPaySum()))); // 商品总金额,以分为单位 signParams.put("spbill_create_ip", this.getClientIp()); // 订单生成机器IP,指用户浏览器端IP signParams.put("trade_type", "JSAPI"); signParams.put("nonce_str", noncestr); signParams.put("openid", openId); String sign = ""; try { sign = WxPayHelper.createSign(signParams, PAY_KEY); } catch (Exception e) { e.printStackTrace(); } // 增加非参与签名的额外参数 signParams.put("sign", sign); String prePayId = WxPayHelper.getPrePayId(reqPrePayUrl, signParams);// 预支付订单号
这里需要注意的是:生成签名的sign并不需要参与签名,但是请求参数里面是必填的参数。
/** * 创建sign签名 * * @param packageParams * @param paykey * @return */ public static String createSign(SortedMap<String, String> packageParams, String paykey) { StringBuffer sb = new StringBuffer(); Set es = packageParams.entrySet(); Iterator it = es.iterator(); while (it.hasNext()) { Map.Entry entry = (Map.Entry) it.next(); String k = (String) entry.getKey(); String v = (String) entry.getValue(); if (null != v && !"".equals(v) && !"sign".equals(k) && !"key".equals(k)) { sb.append(k + "=" + v + "&"); } } sb.append("key=" + paykey); String sign = MD5Util.MD5Encode(sb.toString(), "utf-8").toUpperCase(); return sign; }
此时就生成了sign签名,这里需要注意的是:openid,mch_id,total_fee,商户支付密钥。会走弯路的地方是:
1.total_fee的单位是分,不能带有小数点;
2.商户支付密钥会迷惑我们,我一开始以为是PARTNER_KEY,然后发起预支付的时候报错,又仔细检查了参数和文档发现:这里要的商户支付密钥是要安装证书后自己设置的,不是PARTNER_KEY。
剩下的就看请求的部分了,请求的时候我使用的是httpclient。
拼接xml字符串感觉很麻烦,我这里是先构建一个对象,然后将对象转换为XML,调预支付接口返回应答的XML。如果成功后就能得到返回结果里的prepay_id了。具体问题看返回的结果,注意请求和接收的数据格式都要是XML格式的。
然后就是调JSAPI 支付接口,这里我们看到了又需要一次签名得到paySign,方法和上面的一样,参数需要换掉,分别是:
appId,timeStamp,nonceStr,package,signType,paySign。同样的paySign不参与签名,具体的要求和说明参考文档注意,注意!
// 设置package参数 SortedMap<String, String> packageParams = new TreeMap<String, String>(); packageParams.put("appId", APP_ID); packageParams.put("timeStamp", timestamp); packageParams.put("nonceStr", noncestr); packageParams.put("package", "prepay_id=" + prePayId); packageParams.put("signType", "MD5"); String paySign = ""; try { paySign = WxPayUtil.createSign(packageParams, PAY_KEY); } catch (Exception e) { e.printStackTrace(); } packageParams.put("paySign", paySign);
Gson gs = new GsonBuilder().setPrettyPrinting().disableHtmlEscaping().create();// 特殊字符不被转义 String jsonStr = gs.toJson(packageParams);
需要注意的是:package的格式要求是:prepay_id=***,我这里对应的是
packageParams.put("package", "prepay_id=" + prePayId);(文档里都有详细的说明)
到这里就完成了JSAPI所需要的所有请求参数了,然后就得到了需要的JSON字符串,对应我这里的jsonStr。
最后就是调用WeixinJSBridge了。