最近在做微信的过程中遇到不少问题,历经了不少坑,才发现网上的资料真的很少,大多都混乱零碎,而且做的时候遇到一些问题在两个微信群里问了,做完才发现他们的有些答案给了我陷阱。
加上最近身边很多童鞋也在做微信支付,而我上周刚好做完,现在整理一下,记录下来,希望也能帮到大家,注意这篇文章只是对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了。