微信 JS API 支付教程
最近一个项目中用到了微信开发,之前没有做过支付相关的东西,算是拿这个来练练手,刚开始接触支付时候很懵逼,加上微信支付开发文档本来就讲得不清楚,我是彻底蒙圈了,参考了很多代码之后,算是有一点思路了。
用户认证获取openId
如果你知识关注支付流程,这块可以跳过,因为我知道这些你已经做过了,在开始所有的流程之前,我觉得你应该把所有微信相关的配置放到一个properties文件中去,这样不仅显得更规范,而且会避免犯很多错误,真是一个完美的选择!
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
######## 配置文件######## 公众号开发配置中的token(自定义)wechat.token=######## 应用idwechat.appId=######## 密钥(同token查看地址)wechat.appSecret=######## 静默授权微信回调urlwechat.callBackSlientUrl=######## 商户Id(支付相关)wechat.MCHID=######## 微信下单地址wechat.wxorder=https://api.mch.weixin.qq.com/pay/unifiedorder######## 支付api密钥wechat.KEY=######## 支付结果回调地址wechat.NOTIFYURL= |
接着你可以考虑把这个properties注入到一个bean中,使用更方便,当然你还可以选择使用java来读取properties的配置,对比这两个方法,我更喜欢第一个,我就使用第一种方法来演示一下(这里使用spring boot框架,spring mvc类似)
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
|
/** * <p>Created on 2017/3/13.</p> * * @author StormMma * * @Description: 微信相关常量 */@Component@ConfigurationProperties(locations = {"classpath:config/wechat.properties"}, prefix = "wechat")public class WeChatConfigBean { /** * token */ private String token; /** * app id */ private String appId; /** * app secret */ private String appSecret; /** * 静默授权回调地址 */ private String callBackSlientUrl; /** * 商户id */ private String MCHID; /** * 异步回调地址 */ private String NOTIFYURL; /** * 微信统一下单地址 */ private String wxorder; /** * key */ private String KEY; public String getToken() { return token; } public void setToken(String token) { this.token = token; } public String getAppId() { return appId; } public void setAppId(String appId) { this.appId = appId; } public String getAppSecret() { return appSecret; } public void setAppSecret(String appSecret) { this.appSecret = appSecret; } public String getCallBackSlientUrl() { return callBackSlientUrl; } public void setCallBackSlientUrl(String callBackSlientUrl) { this.callBackSlientUrl = callBackSlientUrl; } public String getMCHID() { return MCHID; } public void setMCHID(String MCHID) { this.MCHID = MCHID; } public String getNOTIFYURL() { return NOTIFYURL; } public void setNOTIFYURL(String NOTIFYURL) { this.NOTIFYURL = NOTIFYURL; } public String getWxorder() { return wxorder; } public void setWxorder(String wxorder) { this.wxorder = wxorder; } public String getKEY() { return KEY; } public void setKEY(String KEY) { this.KEY = KEY; } |
封装请求工具(这次我选择使用HttpClient, 此处的json工具我选择了ali的fastjson)
RequestUtil.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
/** * 发送Get请求到url,获得response的json实体 * @param url * @return * @throws IOException */ private JSONObject doGetUrl(String url) throws WechatException, ServerSystemException { CloseableHttpClient httpclient = HttpClients.createDefault(); HttpGet httpGet = new HttpGet(url); CloseableHttpResponse response; String result; try { response = httpclient.execute(httpGet); HttpEntity entity = response.getEntity(); result = EntityUtils.toString(entity, "UTF-8"); httpclient.close(); } catch (IOException e) { logger.error("执行GET请求发生错误!", e); throw new ServerSystemException("执行GET请求发生错误!{}", e); } return JSONObject.parseObject(result); } /** * 发送post请求 * @param url * @param param * @return * @throws ServerSystemException */ private JSONObject doPostUrl(String url, String param) throws ServerSystemException { final String CONTENT_TYPE_TEXT_JSON = "application/json"; DefaultHttpClient httpClient = new DefaultHttpClient(new PoolingClientConnectionManager()); HttpPost httpPost = new HttpPost(url); HttpResponse response; String result; try { StringEntity stringEntity = new StringEntity(param); stringEntity.setContentType(CONTENT_TYPE_TEXT_JSON); stringEntity.setContentEncoding("UTF-8"); httpPost.setEntity(stringEntity); response = httpClient.execute(httpPost); HttpEntity entity = response.getEntity(); result = EntityUtils.toString(entity, "UTF-8"); httpClient.close(); } catch (IOException e) { logger.error("执行POST请求发生错误!", e); throw new ServerSystemException("执行POST请求发生错误!{}", e); } return JSONObject.parseObject(result); } |
获取code
在此之前,我想我们应该抽出一个微信工具类,专门来封装各种请求和RequestUtil来结合使用,是的,这是一个很好的选择。
WxRequestUtil.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
public calss WxRequestUtil { @AutoWired private WechatConfigBean config; /** * <p>获得静默授权的url</p> * @return */ public String getSlientUrl() { String url = "https://open.weixin.qq.com/connect/oauth2/authorize?appid=" + config.getAppId() + "&redirect_uri=" + URLEncoder.encode(config.getCallBackSlientUrl()) + "&response_type=code" + "&scope=snsapi_base" + "&state=STATE#wechat_redirect"; return url; }} |
接着我想我们应该参照开发文档来重定向到这个url,然后微信服务器会检查参数接着重定向到我们的回调地址,嗯嗯,你猜对了,就是参数带的那个redirect_uri,那么我们应该补充一下回调接口
获取openId
WechatController.java
|
1
2
3
4
5
6
7
8
9
10
11
|
/** * 获得openId,静默授权 * @param code * @param session * @param response */ @RequestMapping(value = "/slient/check") public RequestResult<String> callBackBase(@RequestParam(value = "code", required = false) String code, HttpServletResponse response) { String openId = wechatService.getOpenIdBySlientAuthy( return ResultUtil.success(openId); } |
我想我应该解释一下,控制器层我用的都是规范化的请求响应,不知道的可以参考我前面的博文。另外一点我需要说明的就是我们还需要一个service来处理获取openId的逻辑。
WechatService.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
/** * 静默授权获得openId * @param code * @return */ public String getOpenIdBySlientAuthy(String code) { String url = "https://api.weixin.qq.com/sns/oauth2/access_token?appid=" + config.getAppId() + "&secret=" + config.getAppSecret() + "&code=" + code + "&grant_type=authorization_code"; //为了代码简便,此处省略异常处理 JSONObject jsonObject = doGetUrl(url); return jsonObject.getString("openid"); } |
至此,我们获得了openId,那么接着我们回到支付的话题上
微信支付
首先,我需要说明的是,微信支付的一个流程,至于为什么呢,我的目的很明确就是要描述清楚微信支付。我做支付的时候看过很多资料,有一个很深的体会就是代码复制来复制去,一大片一大片的代码看着心碎。在这里,我就不贴微信官方的流程图了,我相信你看着流程图会吓一跳,所以我选择不残害你。回到正题,微信支付最重要的就是三个步骤。
- 统一下单,得到预支付id, 次数需要你提供商户的信息以及商品的信息,然后得到一个预支付id(请相信我,其他返回的数据并没有什么实际的意义)
- 组装调起支付参数(我不知道叫什么名字更贴切,索性就这么叫吧,这个步骤其实就是使用预支付id,和其他的配置信息签名生成请求数据,返回至前台调用)
- 调起支付(使用jssdk或者h5接口调起支付)
其他的步骤就不是那么重要了,比如支付接口通知接口,可以根据自己的需求进行改写,这里我就不多说了。
统一下单
PayService.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
|
/** * 获得统一下单参数 * @param openId * @param totalFee * @param ip * @param body * @return */public String getPayParam(String openId, String totalFee, String ip, String body) { Map<String, String> datas = new TreeMap<>(); datas.put("appid", weChatConfigBean.getAppId()); datas.put("mch_id", weChatConfigBean.getMCHID()); //设备 datas.put("device_info", "WEB"); //商品描述 datas.put("body", body); //支付类型,这里使用公众号支付,所以是JSAPI datas.put("trade_type", "JSAPI"); //随机字符串,32字符以内 datas.put("nonce_str", WXUtil.getNonceStr()); //支付结果通知地址 datas.put("notify_url", config.getNOTIFYURL()); //订单号,自己生成一个唯一的订单号就行 datas.put("out_trade_no", createOutTradeNO()); //支付金额,以分为单位 datas.put("total_fee", totalFee); //用户openId datas.put("openid", openId); //ip datas.put("spbill_create_ip", ip); String sign = SignatureUtils.signature(datas, config.getKEY()); datas.put("sign", sign); |
看到这里,你可能有点懵逼,我想我需要解释一下,开始之前我们用Map把所有的参数封装起来,至于为什么用TreeMapp,因为我们后面的签名要将Map的参数转换成一个字符串的形式(字段名=字段值&字段名=字段值)并且字段名字典序排序,这样,我们就只需要关注签名算法的实现,官方文档有解释签名算法,就像我前面说的,我们需要把Map转换成字符串的形式,并且后面要追加一个&key=#{key}(注意:#{key}是你的字段值)的参数,然后进行加密。我想此处我应该给出我的签名:
SignatureUtils.java
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
|
/** * 微信支付加密工具 * @param key * @param map */public static String signature(Map<String, String> map, String key) { Set<String> keySet = map.keySet(); String[] str = new String<script>jQuery(function($) {$("#google-maps-1").gMap({controls: false,scrollwheel: false,markers: [{address: "",icon: {image: "http://www.importnew.com/wp-content/themes/jobbolev4blog/_assets/img/_colors/red/pin.png",iconsize: [32, 32],iconanchor: [16,27],infowindowanchor: [16, 27]}}],address: "",zoom: 15,icon: {image: "http://www.importnew.com/wp-content/themes/jobbolev4blog/_assets/img/_colors/red/pin.png",iconsize: [32, 32],iconanchor: [16,27],infowindowanchor: [16, 27]}});});</script><div id="google-maps-1" class="google-maps" style="width: 100%; height: 200px;"></div>; StringBuilder tmp = new StringBuilder(); str = keySet.toArray(str); for (int i = 0; i < str.length; i++) { String t = str[i] + "=" + map.get(str[i]) + "&"; tmp.append(t); } if (StringUtils.isNotBlank(key)) { tmp.append("key=" + key); } String tosend = tmp.toString(); MessageDigest md = null; byte[] bytes = null; try { md = MessageDigest.getInstance("MD5"); bytes = md.digest(tosend.getBytes("utf-8")); } catch (Exception e) { e.printStackTrace(); } String singe = byteToStr(bytes); return singe.toUpperCase();}/** * 字节数组转换为字符串 * @param byteArray * @return */public static String byteToStr(byte[] byteArray) { String strDigest = ""; for (int i = 0; i < byteArray.length; i++) { strDigest += byteToHexStr(byteArray[i]); } return strDigest;}/** * 字节转换为字符串 * @param mByte * @return */public static String byteToHexStr(byte mByte) { char[] Digit = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' }; char[] tempArr = new char[2]; tempArr[0] = Digit[(mByte >>> 4) & 0X0F]; tempArr[1] = Digit[mByte & 0X0F]; String s = new String(tempArr); return s;} |
这个工具类,具体我就不多介绍了,可以查看一下官方文档,了解一下签名算法,然后回来看代码,我相信你可以看懂。
我想我应该说一声对不起,我忘了解释其实我们最终下单的参数是一个xml的String类型,所以我们还要把Map转换成xml,这个就很简单了。我们可以考虑把它加到PayService里面(其他地方用不着,你可以考虑私有,相信我,这样会更优雅)。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
|
/** * 得到统一下单参数的xml形式 * * @param parameters * @return */ public static String getRequestXml(Map<String, String> parameters) { StringBuffer sb = new StringBuffer(); sb.append("<xml>"); Set es = parameters.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 ("attach".equalsIgnoreCase(k) || "body".equalsIgnoreCase(k) || "sign".equalsIgnoreCase(k)) { sb.append("<" + k + ">" + "<![CDATA[" + v + "]]></" + k + ">"); } else { sb.append("<" + k + ">" + v + "</" + k + ">"); } } sb.append("</xml>"); return sb.toString(); } |
于是,我们得到了统一下单的参数,接下来就是去请求微信服务器了。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
|
/** * 支付接口 * @param body * @param totalFee * @param user * @param response * @return * @throws Exception */ @PostMapping(value = "/pay") public RequestResult<Map<String, String>> order(@RequestParam("body")String body, @RequestParam("totalFee")String totalFee, @SessionAttribute(name = "user", required = false)User user, HttpServletResponse response) throws Exception { //之前我们获得了openId,这里我使用假数据测试 String openId = "oxxjlv1dWSkielTGFfWQGNK-RHSc"; String ip = this.getIpAddress(); String requestParam = payService.getPayParam(openId, totalFee, ip, body); //stop here ,下面我会讲 Map<String, String> result = payService.requestWechatPayServer(requestParam); Map<String, String> datas = new TreeMap<>(); if (result.get("return_code").equals("SUCCESS")) { String prepayId = result.get("prepay_id"); datas.put("appId", weChatConfigBean.getAppId()); datas.put("package", "prepay_id=" + prepayId); datas.put("signType", "MD5"); datas.put("timeStamp", Long.toString(new Date().getTime()).substring(0, 10)); datas.put("nonceStr", WXUtil.getNonceStr()); String sign = SignatureUtils.signature(datas, weChatConfigBean.getKEY()); datas.put("paySign", sign); return ResultUtil.success(datas); } return ResultUtil.fail(); } |
组装调起支付参数
继续上面的控制器,我们已经得到了预支付id,那么我们离成功不远了 请相信我,我没有骗你。然后我们要封装调起支付参数,我们先看一下jssdk调起支付需要的参数。
|
1
2
3
4
5
6
7
8
9
10
|
wx.chooseWXPay({ timestamp: 0, // 支付签名时间戳,注意微信jssdk中的所有使用timestamp字段均为小写。但最新版的支付后台生成签名使用的timeStamp字段名需大写其中的S字符 nonceStr: '', // 支付签名随机串,不长于 32 位 package: '', // 统一支付接口返回的prepay_id参数值,提交格式如:prepay_id=***) signType: '', // 签名方式,默认为'SHA1',使用新版支付需传入'MD5' paySign: '', // 支付签名 success: function (res) { // 支付成功后的回调函数 }}); |
那么我们就根据这个参数列表来生成参数,不过我很好奇,为什么timeStamp一阵大写一阵小写的,我想估计脑子抽了吧。现在我们看看上面的控制器剩余的代码,其实就是组装这个参数到Map,我想这个应该没有疑惑的地方吧。说到这,微信开发基本结束了,剩下的就是js调起支付,输入密码,微信服务器判断,给你返回结果的过程,处理结果的接口我就不贴了,简单到不行。
结尾
在做微信支付的时候,我有时候真的很无奈,没有好的官方文档,更没有好的博文,这篇博客旨在能讲清楚微信支付的步骤,我知道在这么短的时间讲清楚显然不可能,希望各位多多指正,有问题的可以发邮件给我,StormMaybin@gmail.com。哦对了,最后别忘记配置支付目录,不然会显示url未注册。应部分人的要求,最后写了一个demo,附上链接:https://github.com/StormMaybin/wxpay.git
微信 JS API 支付教程的更多相关文章
- 微信支付开发(1) JS API支付
关键字:微信支付 微信支付v3 jsapi支付 统一支付 Native支付 prepay_id 作者:方倍工作室原文: http://www.cnblogs.com/txw1958/p/wxpayv3 ...
- 微信支付开发(3) JS API支付
由于微信支付接口更新,本文档已过期,请查看新版微信支付教程.地址 http://www.cnblogs.com/txw1958/category/624506.html 本文介绍如何使用JS API支 ...
- 微信支付开发(1) JS API支付V3版(转)
http://www.cnblogs.com/txw1958/p/wxpayv3-jsapi.html 本文介绍微信支付下的jsapi实现流程 前言 微信支付现在分为v2版和v3版,2014年9月10 ...
- 微信支付v2开发(3) JS API支付
本文介绍如何使用JS API支付接口完成微信支付. 一.JS API支付接口(getBrandWCPayRequest) 微信JS API只能在微信内置浏览器中使用,其他浏览器调用无效.微信提供get ...
- Android微信SDK API 调用教程1
最近一直在调用微信的API,却发现一直调用不成功,纠结了好久,各方面找教程,找官方,官方里的文档也只是写得很模糊,说是按三步走. 1.申请App_ID 2.填写包名3. 获取程序签名的md5值, 这三 ...
- Android微信SDK API 调用教程
最近一直在调用微信的API,却发现一直调用不成功,纠结了好久,各方面找教程,找官方,官方里的文档也只是写得很模糊,说是按三步走. 1.申请App_ID 2.填写包名3. 获取程序签名的md5值, 这 ...
- Android微信SDK API 调用教程【转】
原文:http://blog.csdn.net/worker90/article/details/8211451 最近一直在调用微信的API,却发现一直调用不成功,纠结了好久,各方面找教程,找官方,官 ...
- 微信支付配置信息,JSAPI接口,H5调用微信js接口支付,微信公众号支付
微信支付已经做完了,没接触过微信的我,经历了非常艰难的3天,才把微信支付给做出来,对于专业的人来说,估计就是一小时就搞定的事情了,虽然说做了很长时间,但是确实也学到东西了,也收获了不少,下面跟大家分享 ...
- 微信JS API PHP类
CURL操作类: <?php namespace app\common; class curl{ public static function wxcurl($getUrl){ $ch = cu ...
随机推荐
- c语言条件编译和预编译
转自http://www.cnblogs.com/rusty/archive/2011/03/27/1996806.html 一.C语言由源代码生成的各阶段如下: C源程序->编译预处理-> ...
- Struts2的配置文件中, <package>的作用,<action><result>重名?
问:Struts2的配置文件中, <package>的作用是什么? 答:防止action重名啊,例如前台和后台,总会有很多地方起名重复的! 问:可是访问的时候,不也是访问action吗,能 ...
- 什么是oauth2
对于目前大部分Web应用来说,用户认证基本上都由应用自身来完成.具体来说,Web应用利用自身存储的用户凭证(基本上是用户名/密码)与用户提供的凭证进行比较进而确认其真实身份.但是这种由Web应用全权负 ...
- codeforces.com/contest/251/problem/C
C. Number Transformation time limit per test 2 seconds memory limit per test 256 megabytes input sta ...
- Java基础-位运算符Bitwise Operators
Java基础-位运算符Bitwise Operators 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.位运算特点 位运算符分为按位与(&),按位或(|),按位异或(^ ...
- gitlab的备份与恢复与迁移
一.gitlab的备份1.1 创建备份目录,并授权 1 2 3 4 [root@linux-node1 ~]# mkdir /data/backups/gitlab -p [root@linux-no ...
- MVVM模式原则
1.MVVM简介 这个模式的核心是ViewModel,它是一种特殊的model类型,用于表示程序的UI状态.它包含描述每个UI控件的状态的属性.例如,文本输入域的当前文本,或者一个特定按钮是否可用.它 ...
- html基础知识汇总(二)之Emmet语法
div.imageBox+div.infoBox+input[type="button" class="starBtn"]*3 <div class=&q ...
- Shell记录-Shell命令(其他)
top命令是Linux下常用的性能分析工具,能够实时显示系统中各个进程的资源占用状况,类似于Windows的任务管理器. .命令格式 top [参数] Shell 2.命令功能 显示当前系统正在执行的 ...
- 【测试笔记】Redis学习笔记(十二)性能测试
http://blog.csdn.net/yangcs2009/article/details/50781530 Redis测试服务器一 redis_version:2.8.4 www@iZ23s8a ...