最近项目中用到了微信支付,于是找到了这篇资料,为了防止遗忘转载一下,先谢过作者。原文地址:http://www.360us.net/article/22.html
最近把支付宝、银联和微信支付全都做了一遍,目前做的都还只涉及到消费的功能。
做下来感觉就是各个平台的支付流程都是大同小异,签名方式也是一样的。
这里主要总结一下微信支付公众号支付的一些东西。
微信公众号支付的主要流程如下:
1、生成我们自己系统的订单。
2、调用微信支付的统一下单接口把订单信息推给微信。
3、在第二部会返回一个预支付会话标识,然后凭这个标识用JS去调用支付操作。
关于支付页面的url问题,微信要求是最后必须要有“/
”,我看到很多文章说不适合MVC结构的程序,我的情况是否定的,MVC结构一样可以。
比如url是这个:http://www.example.com/payment/wechatpay/
,url里面payment
是controller
,wechatpay
是action
,这有问题吗?
一样可以访问,可以支付,是不是一个真正的目录,在微信看来就是,实际上其实不是。
好,下面进入正题。
微信支付配置如下:
1
2
3
4
5
|
$config
= [
'mch_id'
=>
'1234455666'
,
//商户号
'signType'
=>
'MD5'
,
//签名方式,目前只有MD5
'key'
=>
'sdsfdhgjh34343krn3453tnelt'
,
//api密钥
];
|
Weixinpay代码清单如下:
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
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
|
<?php
namespace
weixin\components;
//这个是命名空间,可以根据需要修改
class
WeixinPay
{
//支付配置
public
$config
;
//支付参数
public
$params
;
//统一下单url
const
POST_ORDER_URL =
'https://api.mch.weixin.qq.com/pay/unifiedorder'
;
//订单查询url
const
ORDER_QUERY_URL =
'https://api.mch.weixin.qq.com/pay/orderquery'
;
/**
* 创建微信js发起支付参数
* @return array
*/
public
function
createJsPayData()
{
$this
->params[
'nonce_str'
] =
$this
->getRandomStr();
$this
->params[
'sign'
] =
$this
->sign();
$xmlStr
=
$this
->arrayToXml();
$res
=
$this
->postUrl(self::POST_ORDER_URL,
$xmlStr
);
$res
=
$this
->xmlToArray(
$res
);
if
(
$res
[
'return_code'
] ==
'SUCCESS'
&&
$res
[
'result_code'
] ==
'SUCCESS'
&&
$this
->verifySignResponse(
$res
) ) {
$params
= [
'appId'
=>
$this
->params[
'appid'
],
'timeStamp'
=> (string)time(),
'nonceStr'
=>
$this
->getRandomStr(),
'package'
=>
'prepay_id='
.
$res
[
'prepay_id'
],
'signType'
=>
'MD5'
];
$this
->params =
$params
;
$this
->params[
'paySign'
] =
$this
->sign();
return
$this
->params;
}
if
(
$res
[
'return_code'
] ==
'FAIL'
) {
throw
new
\Exception(
"提交预支付交易单失败:{$res['return_msg']}"
);
}
throw
new
\Exception(
"提交预支付交易单失败,{$res['err_code']}:{$res['err_code']}"
);
}
/**
* 验证异步通知
* @return boolean
*/
public
function
verifyNotify()
{
$this
->params =
$this
->xmlToArray(
$this
->params);
if
(
empty
(
$this
->params[
'sign'
]) ) {
return
false;
}
$sign
=
$this
->sign();
return
$this
->params[
'sign'
] ==
$sign
;
}
/**
* 取成功响应
* @return string
*/
public
function
getSucessXml()
{
$xml
=
'<xml>'
;
$xml
.=
'<return_code><![CDATA[SUCCESS]]></return_code>'
;
$xml
.=
'<return_msg><![CDATA[OK]]></return_msg>'
;
$xml
.=
'</xml>'
;
return
$xml
;
}
public
function
getFailXml()
{
$xml
=
'<xml>'
;
$xml
.=
'<return_code><![CDATA[FAIL]]></return_code>'
;
$xml
.=
'<return_msg><![CDATA[OK]]></return_msg>'
;
$xml
.=
'</xml>'
;
return
$xml
;
}
/**
* 数组转成xml字符串
*
* @return string
*/
protected
function
arrayToXml()
{
$xml
=
'<xml>'
;
foreach
(
$this
->params
as
$key
=>
$value
) {
$xml
.=
"<{$key}>"
;
$xml
.=
"<![CDATA[{$value}]]>"
;
$xml
.=
"</{$key}>"
;
}
$xml
.=
'</xml>'
;
return
$xml
;
}
/**
* xml 转换成数组
* @param string $xml
* @return array
*/
protected
function
xmlToArray(
$xml
)
{
$xmlObj
= simplexml_load_string(
$xml
,
'SimpleXMLIterator'
,
//可迭代对象
LIBXML_NOCDATA
);
$arr
= [];
$xmlObj
->
rewind
();
//指针指向第一个元素
while
(1) {
if
( !
is_object
(
$xmlObj
->current()) )
{
break
;
}
$arr
[
$xmlObj
->key()] =
$xmlObj
->current()->__toString();
$xmlObj
->next();
//指向下一个元素
}
return
$arr
;
}
//验证统一下单接口响应
protected
function
verifySignResponse(
$arr
)
{
$tmpArr
=
$arr
;
unset(
$tmpArr
[
'sign'
]);
ksort(
$tmpArr
);
$str
=
''
;
foreach
(
$tmpArr
as
$key
=>
$value
) {
$str
.=
"$key=$value&"
;
}
$str
.=
'key='
.
$this
->config[
'key'
];
if
(
$arr
[
'sign'
] ==
$this
->signMd5(
$str
)) {
return
true;
}
return
false;
}
/**
* 签名
* 规则:
* 先按照参数名字典排序
* 用&符号拼接成字符串
* 最后拼接上API秘钥,str&key=密钥
* md5运算,全部转换为大写
*
* @return string
*/
protected
function
sign()
{
ksort(
$this
->params);
$signStr
=
$this
->arrayToString();
$signStr
.=
'&key='
.
$this
->config[
'key'
];
if
(
$this
->config[
'signType'
] ==
'MD5'
) {
return
$this
->signMd5(
$signStr
);
}
throw
new
\InvalidArgumentException(
'Unsupported sign method'
);
}
/**
* 数组转成字符串
* @return string
*/
protected
function
arrayToString()
{
$params
=
$this
->filter(
$this
->params);
$str
=
''
;
foreach
(
$params
as
$key
=>
$value
) {
$str
.=
"{$key}={$value}&"
;
}
return
substr
(
$str
, 0,
strlen
(
$str
)-1);
}
/*
* 过滤待签名数据,sign和空值不参加签名
*
* @return array
*/
protected
function
filter(
$params
)
{
$tmpParams
= [];
foreach
(
$params
as
$key
=>
$value
) {
if
(
$key
!=
'sign'
&& !
empty
(
$value
) ) {
$tmpParams
[
$key
] =
$value
;
}
}
return
$tmpParams
;
}
/**
* MD5签名
*
* @param string $str 待签名字符串
* @return string 生成的签名,最终数据转换成大写
*/
protected
function
signMd5(
$str
)
{
$sign
= md5(
$str
);
return
strtoupper
(
$sign
);
}
/**
* 获取随机字符串
* @return string 不长于32位
*/
protected
function
getRandomStr()
{
return
substr
( rand(10, 999).
strrev
(uniqid()), 0, 15 );
}
/**
* 通过POST方法请求URL
* @param string $url
* @param array|string $data post的数据
*
* @return mixed
*/
protected
function
postUrl(
$url
,
$data
) {
$curl
= curl_init(
$url
);
curl_setopt(
$curl
, CURLOPT_SSL_VERIFYPEER, false);
//忽略证书验证
curl_setopt(
$curl
, CURLOPT_POST, true);
curl_setopt(
$curl
, CURLOPT_RETURNTRANSFER, true);
curl_setopt(
$curl
, CURLOPT_POSTFIELDS,
$data
);
$result
= curl_exec(
$curl
);
return
$result
;
}
}
|
拿发起支付参数:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
|
try
{
$weixinPay
=
new
WeixinPay();
$weixinPay
->config =
$config
;
$weixinPay
->params = [
'appid'
=>
'sdfdgf1234345'
,
//APP ID
'mch_id'
=>
$config
[
'mch_id'
],
//商户号
'body'
=>
'test'
,
//商品描述
'out_trade_no'
=>
'esdfrdgegtr234365546'
,
//订单号
'total_fee'
=> 100,
//总金额,单位分
'spbill_create_ip'
=>
'192.168.100.100'
,
//终端IP
'notify_url'
=>
'http://www.example.com/paynofify'
, //异步通知地址
'trade_type'
=>
'JSAPI'
,
//交易类型
'openid'
=>
'xxxxdfdfdgdfxcvcvgfg'
,
//用户标识
];
$return
=
$weixinPay
->createJsPayData();
}
catch
(\Exception
$e
) {
Yii::error(
'微信支付错误:'
.
$e
->getMessage());
return
[
'code'
=> 0,
'errmsg'
=>
'创建支付参数失败'
,
];
}
|
变量$return
的内容如下,就是网页调起支付api的参数:
1
2
3
4
5
6
7
|
[
'appId'
=>
'dfgfg'
,
//APP ID
'timeStamp'
=> (string)time(),
//时间戳
'nonceStr'
=>
'dfdsfdgfgdsg'
,
//随机字符串
'package'
=>
'prepay_id=sdsfgdhgfh4565756'
,
//预支付会话标识
'signType'
=>
'MD5'
];
|
这里有个提示,timeStamp
参数必须是字符串类型,不能是整数类型,否则在iPhone上面会报缺少timeStamp参数的错误。
我们这里可以直接响应json格式的数据。
然后拿到这个数据之后直接放进微信jsapi的参数里面就行。
js发起支付请求如下:
1
2
3
4
5
6
7
8
9
10
|
WeixinJSBridge.invoke(
"getBrandWCPayRequest"
,
params,
//这个就是上面$return变量的json格式
//下面是支付完成后的回调,可以直接提示成功
function
(res) {
if
(res.err_msg ==
"get_brand_wcpay_request:ok"
) {
//.......
}
}
);
|
如果支付成功之后,微信会发起主动调用,通知商户支付成功,业务处理可以放在那里进行。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
|
$weixinPay
=
new
WeixinPay();
$weixinPay
->config =
$config
;
$weixinPay
->params =
'xxxxx'
;
//微信通知提交过来的xml
if
(
empty
(
$weixinPay
->params) || !
$weixinPay
->verifyNotify()) {
return
$weixinPay
->getFailXml();
}
if
(
$weixinPay
->params[
'return_code'
] ==
'SUCCESS'
&&
$weixinPay
->params[
'result_code'
] ==
'SUCCESS'
) {
//处理业务....
//.....
return
$weixinPay
->getSucessXml();
}
return
$weixinPay
->getFailXml();
|
至此微信支付的整个过程就结束了。
需要注意的一点是微信5.0以下版本不支持微信支付功能。
还有就是在支付url后面加上showwxpaytitle=1
字符串,会有“微信安全支付”的文字提示,最终的url就变成了http://www.example.com/payment/wechatpay/?showwxpaytitle=1
。