**写在前面:
本人一直“奉行授人以鱼不如授人以渔”,本文主要是起一个引导的作用,注意一些很坑的地方。
微信支付,本人菜鸟花了2天时间弄出来,也算是有点成就感,所以特此做个记录
**
分割线—————————————————————————
正文开始:
先来看看微信官方流程图:
总结起来就是四个步骤:
- 点击支付按钮
- 微信拉起支付,输入密码
- 完成支付
- 通知微信服务器
下面就是我做测试支付的图:
点击支付按钮(后台一大堆业务逻辑处理,后面会讲):
微信拉起支付(这个调用微信自动完成的):
完成支付,通知微信(这个需要我们自己完成业务逻辑,后面会讲到)
流程就是这样,其实总结出来,我们需要做的就是2步:
流程图中
4:统一下单
5:调用统一下单api
6:生成下单签名
(4 5 6)可以并为一步
11:告知微信通知处理结果
开始编码实现:
查阅文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
获取到统一下单接口:
统一下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder
文档中说明,统一下单必须获取openid,各位客官别急,待我带你们一起查阅官方文档:
https://mp.weixin.qq.com/wiki?action=doc&id=mp1421140842&t=0.003036260317902828#1
这里一大堆说的就是获取openid,文档讲的十分清楚。
先放下prepay_id,我们来说说如何获取open_id(因为没有open_id,我们也没法去获取prepay_id)
获取这个分为4步:
- 用户同意授权,获取code
https://open.weixin.qq.com/connect/oauth2/authorize?appid=APPID&redirect_uri=REDIRECT_URI&response_type=code&scope=SCOPE&state=STATE
我来解释下后面附带参数的意思:
appid: 用户唯一凭证,就是微信公众号的应用id
redirect_uri: 这个就是回调url,注意这个url 会附带着 code过去,就是第一步中的code。
scope: 有两种 snsapi_base 和 snsapi_userinfo 具体请参照文档
state: 随意填写
其他参数 照写 - 通过code换取网页授权access_token
请求以下链接获取access_token: https://api.weixin.qq.com/sns/oauth2/access_token?appid=APPID&secret=SECRET&code=CODE&grant_type=authorization_code
参数说明:
appid:这个参数使用都是一样的
secret: 这个参数在位置支付基本配置中可以查看
code: 第一步获取的code
grant_type:不变
请求这个就能返回如下json
{
"access_token":"ACCESS_TOKEN",
"expires_in":7200,
"refresh_token":"REFRESH_TOKEN",
"openid":"OPENID",
"scope":"SCOPE"
}
解析上述json字符就可以获取到openid了。各位客官,可以喝杯茶休息休息啦!
接着来:有了openid,开始获取prepay_id
继续文档:
查阅文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=7_3
获取到统一下单接口:
统一下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder
我们把那些必须的参数一一列出来,并给大家做出解释:
appid:公众号id
mch_id: 商户号
nonce_str: 随机字符串,长度32以内
sign :签名,最坑的地方,玩微信支付不遇到这个签名错误,都不好意思说自己开发过支付
body:商品描述
out_trade_no: 商户订单号 和随机字符串一样,长度32以内。
total_fee:总价,单位 分
spbill_create_ip : 终端IP
notify_url:回调url 这个就是我们总结出来2步中11的那个步骤,通知微信的处理结果的作用。
trade_type:交易类型,我们是公众号,传 JSAPI 就好了
openid 这里注意,网页支付一定要传这个,我当时就忘记传了,一直导致签名错误。
最后把这些东西组装成xml就好了:
给大家贴代码吧:
String openid = open_id;
String appid = APP_ID;
String mch_id = MCH_ID;
String nonce_str = RandomStringGenerator.getRandomStringByLength(32);
String bodyDesc = body;
String out_trade_no = RandomStringGenerator.getRandomNumber(18);
String total_fee = totalFee;
String spbill_create_ip = spbillCreateIp;
String notify_url = notifyUrl;
String trade_type = "JSAPI";
Map<String, Object> map = new HashMap<String, Object>();
map.put("openid", openid);
map.put("appid", appid);
map.put("mch_id", mch_id);
map.put("nonce_str", nonce_str);
map.put("body", bodyDesc);
map.put("out_trade_no", out_trade_no);
map.put("total_fee", total_fee);
map.put("spbill_create_ip", spbill_create_ip);
map.put("notify_url", notify_url);
map.put("trade_type", trade_type);
把需要的参数放到map中去,因为sign这个需要用到签名算法:微信文档中有,给出链接https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=4_3
还是贴代码:
public static String getSign(Map<String,Object> map){
ArrayList<String> list = new ArrayList<String>();
for(Map.Entry<String,Object> entry:map.entrySet()){
if(entry.getValue()!=""){
list.add(entry.getKey() + "=" + entry.getValue() + "&");
}
}
int size = list.size();
String [] arrayToSort = list.toArray(new String[size]);
Arrays.sort(arrayToSort, String.CASE_INSENSITIVE_ORDER);
StringBuilder sb = new StringBuilder();
for(int i = 0; i < size; i ++) {
sb.append(arrayToSort[i]);
}
String result = sb.toString();
result += "key=" + key;
Log.error(SignUtil.class, "getSign", result);
result = MD5.MD5Encode(result).toUpperCase();
return result;
}
注意这里用了一个key,这个key非常非常重要,key设置路径:微信商户平台(pay.weixin.qq.com)–>账户设置–>API安全–>密钥设置
这个key必须参与签名。
把上面参数封装成PrePay对象
public class XmlUtil {
public static String toXml(PrePay pay){
XStream stream = new XStream(new Xpp3Driver(new NoNameCoder()));
stream.alias("xml", pay.getClass());
return stream.toXML(pay);
}
最终得到的上传的参数是这样的:以下包含信息参数全用xxxxxx代替
<xml>
<openid>xxxxxxxx</openid>
<appid>xxxxxxxx</appid>
<mch_id>xxxxxxxx</mch_id>
<nonce_str>xxxxxxxx</nonce_str>
<body>testpay</body>
<out_trade_no>xxxxxxxx</out_trade_no>
<total_fee>1</total_fee>
<spbill_create_ip>xxxxxxx</spbill_create_ip>
<notify_url>xxxxxxxxxxx</notify_url>
<trade_type>JSAPI</trade_type>
<sign>xxxxxxxxxxx</sign>
</xml>
好了post提交这个参数到 统一下单接口:https://api.mch.weixin.qq.com/pay/unifiedorder
如果签名没有错误,服务器就会返回:
<xml><return_code><![CDATA[SUCCESS]]></return_code>
<return_msg><![CDATA[OK]]></return_msg>
<appid><![CDATA[xxxxxxxx]]></appid>
<mch_id><![CDATA[xxxxxxxxxx]]></mch_id>
<nonce_str><![CDATA[ClB4m3CRcfg9cS7v]]></nonce_str>
<sign><![CDATA[xxxxxxxxxxx]]></sign>
<result_code><![CDATA[SUCCESS]]></result_code>
<prepay_id><![CDATA[xxxxxxxx]]></prepay_id>
<trade_type><![CDATA[JSAPI]]></trade_type>
</xml>
大家擦亮亮眼睛,仔细看 prepay_id 好啦!!!解析xml获取prepay_id就算完事了,各位看官又到了喝茶的时间了,请各位前去喝茶。
好啦!差不多回来,开始最重要的一步,去前台调用支付:
以下就是微信需要我们传入的参数了:
appId :这个不解释了,全局都是同一个
timeStamp: 这个时间戳自己传过来就好了
nonceStr : 这个随机字符 自己传过来好了
package : 注意注意注意 这里 prepay_id= 一定要加这个
signType : MD5
paySign :这里把上面的参数按照获取prepay_id 一样的流程去签名就好了!
function onBridgeReady(){
WeixinJSBridge.invoke(
'getBrandWCPayRequest', {
"appId":"wx2421b1c4370ec43b", //公众号名称,由商户传入
"timeStamp":"1395712654", //时间戳,自1970年以来的秒数
"nonceStr":"e61463f8efa94090b1f366cccfbbb444", //随机串
"package":"prepay_id=u802345jgfjsdfgsdg888",
"signType":"MD5", //微信签名方式:
"paySign":"70EA570631E4BB79628FBCA90534C63FF7FADD89" //微信签名
},
function(res){
if(res.err_msg == "get_brand_wcpay_request:ok" ) {} // 使用以上方式判断前端返回,微信团队郑重提示:res.err_msg将在用户支付成功后返回 ok,但并不保证它绝对可靠。
}
);
}
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();
}
至此微信支付结束。。。
认为结束了???太年轻,微信还要我们对处理结果进行确认呢!!不然之前那个notify_url 不是很鸡肋,白传了么!!看文档:https://pay.weixin.qq.com/wiki/doc/api/jsapi.php?chapter=9_7
微信返回的是流对象,我们转成map就可以了:
public static Map<String,Object> requestToXml(HttpServletRequest request) throws DocumentException, IOException{
SAXReader reader = new SAXReader();
Document doc = reader.read(request.getInputStream());
Element root = doc.getRootElement();
Map<String,Object> map = new HashMap<String,Object>();
List<Element> elements = root.elements();
for(Element e:elements){
map.put(e.getName(), e.getData());
}
return map;
}
这里就获取到了微信传给我们的支付包含的所有数据:我这里把他放到map里面了。
(本人做法不推荐,只是用于测试:)
public static String replyWx(HttpServletRequest request) throws DocumentException,
IOException {
Map<String, Object> map = XmlUtil.requestToXml(request);
String xml = "";
if (map != null
&& "SUCCESS"
.equalsIgnoreCase(map.get("return_code").toString())) {
Map<String, Object> replyMap = new HashMap<String, Object>();
replyMap.put("return_code", "SUCCESS");
replyMap.put("return_msg", "OK");
xml = XmlUtil.mapToXml(replyMap);
} else {
Map<String, Object> replyMap = new HashMap<String, Object>();
replyMap.put("return_code", "FAIL");
replyMap.put("return_msg", "签名失败");
xml = XmlUtil.mapToXml(replyMap);
}
return xml;
}
这里我只是简单的返回成功和失败,大家做的时候一定一定要根据微信推荐的做法来,我推荐一个思路吧,先把sign取出来,然后置sign为空,让剩下的参数去签名,得到的签名和sign对比,如果一致,通过,如果不一致,那么sign被篡改,返回失败。
最后把这个xml返回给wx就好了!!
至此,支付结束,谢谢大家拜读!!!!