微信支付开发日志


曾经有一项需求,是从移动端的H5页面请求微信支付功能。微信的文档写地较为混乱,在这里写个防坑指南。

支付的坑

1.微信支付的文档不止一个。

  • 官方的文档里,有一段简单的关于微信支付的说明。调用wx.chooseWXPay方法启用。但是在这个文档之下,有另一个开发文档微信支付开发文档

  • 这个文档里也有一个支付调用的方法weixinJSBridge,但是只能在微信浏览器里使用,非常不方便。本人测试从未成功过,所以建议放弃。

2.wx.chooseWXPay方法的传入参数和签名

  • 该方法发起请求时是没有appId参数的,但是在生成paySign签名的时候,需要有appId和它的值。这里的I是大写的i。

  • 生成paySign的时候的timeStamp是大写的,和传入的参数不一样,传入的是小写。

3.服务端的工作?!

  • 由于微信文档里没有说明哪些是前端工作哪些是后端,所以在一开始开发时我做了很多服务端做的事。比如生成signature, paySign.两者不一样。

  • 生成签名的工作前端也是可以做的,但是要发起很多异步请求,处理非常多的回调,简直是回调地狱,而且不安全。最后服务端的伙伴处理了第一个签名。

  • 注意两个access_token的不同。

微信公众号的准备工作

1.微信公众号开通微信支付功能。

  • 微信公众平台 微信支付-开发配置里配置支付授权目录。

    • 正式授权目录可以添加多个,测试时只能添加一个。
    • 不允许添加端口。只支持默认80端口。
    • 目录即为调用微信支付空间的页面所在的目录。
    • 将个人的微信号加到支付的白名单里,只有白名单里的用户才可以支付,但没想到付的是真钱。
  • 同一个页面里,选择开发者配置,找到接口权限表。

    • 网页授权用户基本信息。
    • 这个url是用户回调的url,即开启微信支付控件页面的url,这个值即是redirect_url的值。
    • 要记得encodeUrl转码。
  • 进入公众号设置,功能设置

    • js安全域名做设置。
    • 这个值只能设置一个且似乎改动次数有限制。所以要小心改动。
    • 上线前记得切换。
  • 开发者中心里的配置项。

    • 获取appId, secret等相关信息。
    • 还有mch_idkey
    • 这些数据都不推荐写在前端页面里。

使用步骤

  1. 打开微信页面,从服务端的回调url里获取access_token, openId. 须服务端通过code验证等,然后返回。

  2. 获取wx.config配置需要的signature, 传递随机数nonceStr,时间戳timestamp等参数给服务端生成签名。

  3. wx.config接口注入权限验证配置——通过signature等验证后。

  4. 通过openId获取prepay_id

  5. 前端生成另一个签名paySign, 然后调用wx.readywx.chooseWXPay的方法调起支付。

Show Your the Code

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
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
// ES6、jquery结合的写法.

class WxpayCtrl {
constructor() {
this.access_token = this.getUrlParams().access_token;
this.openId = this.getUrlParams().openId;
this.appId = 'skidfoislkdrehjkgrg';
this.key = 'sdkjgfgkgkhjishiduh';
}

/**
* [getUrlParams 获取url的参数 querystring]
* @return {Object} [一个url参数键值配对的对象]
*/
getUrlParams() {
const url = window.location.search;
const queryString = new Object();
if (url.indexOf('?') !== -1) {
let str = url.substr(1);
let strs = str.split('&');
for (let i = 0; i < strs.length; i++) {
queryString[strs[i].split('=')[0]] = decodeURI(strs[i].split('=')[1]);
}
}
return queryString;
}

/**
* [createTimestamp 时间戳]
* @return {String} [description]
*/
createTimestamp () {
return parseInt(new Date().getTime() / 1000) + '';
}

/**
* [createNonceStr 随机字符串]
* @return {String} [description]
*/
createNonceStr() {
return Math.random().toString(36).substr(2, 15);
}

/**
* [getSignature 获取授权签名]
* @return {String} [description]
*/
getSignature() {
const promise = new Promise((resolve, reject) => {
const data = {
nonceStr: this.createNonceStr(),
timestamp: this.createTimestamp(),
url: window.location.href.split('#')[0],
access_token: this.accessToken
};
$.ajax({
url: `${url}/wechat/getSignature`,
type: 'POST',
data: JSON.stringify(data)
})
.done((res) => {
resolve(res);
})
.fail((err) => {
reject(err.message);
});
});
return promise;
}

/**
* [getPrepayId 用openId获取prepay_id, 和服务端协调后决定queryString]
* @param {Number} cash [消费金额]
* @return {String} [description]
*/
getPrepayId (cash=100) {
const promise = new Promise((resolve, reject) => {
const data = {
amount: cash,
openId: this.openId,
type: 'JSAPI',
nonceStr: this.createNonceStr()
};
$.ajax({
url: `${url}/wechat/getPrepayId`,
type: 'POST',
data: JSON.stringify(data)
})
.done((res) => {
resolve(res.content);
})
.fail((err) => {
reject(err.message);
});
});
return promise;
}

/**
* [weiPay 微信支付,验证加最终支付]
* @param {String} prepay_id [description]
*/
weiPay(prepay_id) {

// 随机生成随机数,在不同地方调用的时候是不一样的,尤其注意!!!
const nonceStr = this.createNonceStr();
const stringA = `appId=${this.appId}&nonceStr=${nonceStr}&package=prepay_id=${prepay_id}&signType=MD5&timeStamp=${this.createTimestamp()}`;
const stringSignTemp = `${stringA}&key=${this.key}`;
const sign = md5(stringSignTemp).toUpperCase();
wx.chooseWXPay({
timestamp: this.createTimestamp(),
nonceStr: nonceStr,
package: `prepay_id=${prepay_id}`,
paySign: sign,
signType: 'MD5',
success: (res) => {
if (res.errMsg === 'chooseWXPay:ok'){
window.location.href = 'success.html';
}
},
fail: () => {
window.alert('支付失败!');
window.location.href = 'fail.html';
},
cancel: () => {
window.location.href = 'cancel.html';
}
});
}

/**
* [weixinPay 调起微信支付]
* @return {[type]} [description]
*/
weixinPay(cash) {
this.getSignature()
.then((signature) => {

/**
* [appId 微信支付的相关配置,通过config接口注入权限验证配置]
*/
wx.config({

// debug: true, // 该选项仅用于调试
appId: this.appId,
timestamp: this.createTimestamp(),
nonceStr: this.createNonceStr(),
signature: signature,
jsApiList: ['chooseWXPay']
});
wx.ready(() => {
this.getPrepayId(cash)
.then((prepay_id) => {
this.weiPay(prepay_id);
})
.catch((err) => {
window.alert(err);
});
});
})
.catch((err) => {
window.alert(err);
});
}
}
  • 虽然这里有很多回调,代码也略凌乱,但它work了!
  • 当然有更好的方案,但紧急需求要求下这是较快的实现方式了。再次对接可让生成签名的工作移交服务端!
  • PS:经过这次对接微信支付文档后,对微信的开发失去了信心。然后对微信推出的小程序也坚持保守态度。
参考: * [接入微信支付流程清晰版](http://www.cnblogs.com/sunshq/p/5035163.html)
前端入坑周年记——野蛮生长 ES6改写cookies.js及分析

评论

Your browser is out-of-date!

Update your browser to view this website correctly. Update my browser now

×