用户选择商品发起购买请求,后端接收到请求后,先生成本地订单,以得到一个本地订单的商户订单号。同时从表中读取该用户的openID。
我是用一个对象pay来存统一下单需要传的参数,pay的字段如下(部分参数并非必填参数,可传可不传):
注:notify_url是必填参数,是微信支付成功后需要调用的回调方法,系统支付成功需要做的业务处理都是在这方法里面执行的。
进行统一下单之前,我们需要根据请求参数进行第一次签名,签名算法可以参考微信支付开发文档,这里提几点注意事项:
1、订单金额需要转换成以分为单位;
2、参数区分大小写,且值为空的参数不参与签名
3、参数需按ASCII码从小到大排序
签名方法举例:
/**
* 生成sign用于请求微信
* @param key 秘钥
* @param parameters 参数map
* @return
*/
publicstatic StringcreateSign(Stringkey,Map
StringBuffersb =new StringBuffer();
if (!(parametersinstanceof SortedMap,?>)) {
parameters = new TreeMap
}
Set>es =parameters.entrySet();//所有参与传参的参数按照accsii排序(升序),sign参数不参与签名
Iterator>it =es.iterator();
while (it.hasNext()) {
Map.Entryentry= (Map.Entry)it.next();
Stringk = (String)entry.getKey();
Objectv =entry.getValue();
if (null !=v && !"".equals(v) && !"sign".equals(k)
&&!"key".equals(k)) {
sb.append(k +"=" +v +"&");
}
}
sb.append("key=" +key);
Stringsign=md5Util.getMD5ofStr(sb.toString()).toUpperCase();
returnsign;
}
签名成功后,得到sign并赋值pay.setSign(sign) ;接下来就是将pay对象转换成xml,调用统一下单接口进行统一支付,并将统一支付返回的xml转换成bean,这里使用的是WechatPayResult对象,WechatPayResult的字段如下图;
统一下单成功会返回微信预支付订单号prepay_id,我们需要根据这个prepay_id进行二次签名,二次签名所用参数如下(不包括paySign):
其中,预支付订单号prepay_id存储在package字段中。
二次签名成功后,即可将参与签名参数及签名paySign一并传到小程序端,小程序端在接受到这些参数后,即可调用方法调起支付,调起代码如下:
wx.requestPayment({
'timeStamp': pay.timeStamp,
'nonceStr': pay.nonceStr,
'package': pay.package,
'signType': pay.signType,
'paySign': pay.paySign,
'success':function (res) {
console.log("支付成功")
callback();
},
'fail':function (res) {
}
});
支付成功后,微信服务器将会调用我们在调用统一下单时传过去的回调方法路径notify_url,在这个方法里是本地系统在支付成功后需要做的一些业务操作。
需要注意的是,商户系统对于支付结果通知的内容一定要做签名验证,并校验返回的订单金额是否与商户侧的订单金额一致,防止数据泄漏导致出现“假通知”,造成资金损失。其次就是在业务处理完成后,需要向微信服务器返回一个成功的信息,否则微信将按一定的策略定期重新发起通知,这样会频繁请求本地服务器的,所以本地系统必须能够正确处理重复的通知,并在处理完成后一定要向微信端发送成功的消息。
推荐的做法是,当收到通知进行处理时,首先检查对应业务数据的状态,判断该通知是否已经处理过,如果没有处理过再进行处理,如果处理过直接返回结果成功。在对业务数据进行状态检查和处理之前,要采用数据锁进行并发控制,以避免函数重入造成的数据混乱。
附:
对象转换成xml的方法如下:
publicstaticStringmyBeanToXML(Objectmodel,StringrootName){
StringBuildersb =new StringBuilder();
sb.append("<" +rootName +">");
if (model !=null) {
Field[]field=model.getClass().getDeclaredFields();//获取实体类的所有属性,返回Field数组
for (intj = 0;j <field.length;j++) {// 遍历所有属性
field[j].setAccessible(true);
Stringname=field[j].getName();//获取属性的名字
Objectvalue=null;
try {
value = field[j].get(model);
}catch(IllegalArgumentExceptione) {
e.printStackTrace();
}catch(IllegalAccessExceptione) {
e.printStackTrace();
}
if (value !=null) {
sb.append("<" +name +">");
if(!"total_fee".equals(name)){
sb.append("+value.toString()+"]]>");
}else{
sb.append(value.toString());
}
sb.append(""+name+">");
}
}
}
sb.append(""+rootName+">");
returnsb.toString();
}