本文内容较多,包括微信接入、获取微信用户信息、微信支付、JSSDK配置参数获取等部分。如果读者对微信开发没有一个主观上的认识,那么建议读者先研读微信公众平台开发者文档,然后再阅读本文,效果更佳!
微信开发的完整例子已经整理在Github,欢迎查看: yii2-wechat-demo。【八宝粥的博客】
接入微信
Yii2后台配置
1.在app/config/params.php中配置token参数
1
2
3
4
5
6
|
return
[
//微信接入
'wechat'
=>[
'token'
=>
'your token'
,
],
];
|
2.在app/config/main.php中配置路由
因为接口模块使用的RESTful API,所以需要定义路由规则。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
'urlManager'
=> [
'enablePrettyUrl'
=> true,
'enableStrictParsing'
=> true,
'showScriptName'
=> false,
'rules'
=> [
[
'class'
=>
'yii\rest\UrlRule'
,
'controller'
=>
'wechat'
,
'extraPatterns'
=> [
'GET valid'
=>
'valid'
,
],
],
],
],
|
3.在app/controllers中新建WechatController
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
|
<?php
namespace
api\controllers;
use
Yii;
use
yii\rest\ActiveController;
class
WechatController
extends
ActiveController
{
public
$modelClass
=
''
;
public
function
actionValid()
{
$echoStr
=
$_GET
[
"echostr"
];
$signature
=
$_GET
[
"signature"
];
$timestamp
=
$_GET
[
"timestamp"
];
$nonce
=
$_GET
[
"nonce"
];
//valid signature , option
if
(
$this
->checkSignature(
$signature
,
$timestamp
,
$nonce
)){
echo
$echoStr
;
}
}
private
function
checkSignature(
$signature
,
$timestamp
,
$nonce
)
{
// you must define TOKEN by yourself
$token
= Yii::
$app
->params[
'wechat'
][
'token'
];
if
(!
$token
) {
echo
'TOKEN is not defined!'
;
}
else
{
$tmpArr
=
array
(
$token
,
$timestamp
,
$nonce
);
// use SORT_STRING rule
sort(
$tmpArr
, SORT_STRING);
$tmpStr
= implode(
$tmpArr
);
$tmpStr
= sha1(
$tmpStr
);
if
(
$tmpStr
==
$signature
){
return
true;
}
else
{
return
false;
}
}
}
}
|
微信公众号后台配置
在微信公众号后台配置URL和Token,然后提交验证即可。
1
2
|
URL:http:
//app.demo.com/wechats/valid
Token:your token
|
获取用户信息
用户表设计
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
CREATE TABLE `wechat_user` (
`id` int(11) NOT NULL,
`openid` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`nickname` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT
'微信昵称'
,
`sex` tinyint(4) NOT NULL COMMENT
'性别'
,
`headimgurl` varchar(255) COLLATE utf8_unicode_ci NOT NULL COMMENT
'头像'
,
`country` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT
'国家'
,
`province` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT
'省份'
,
`city` varchar(50) COLLATE utf8_unicode_ci NOT NULL COMMENT
'城市'
,
`access_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`refresh_token` varchar(255) COLLATE utf8_unicode_ci NOT NULL,
`created_at` timestamp NULL DEFAULT CURRENT_TIMESTAMP
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;
ALTER TABLE `wechat_user`
ADD PRIMARY KEY (`id`);
|
获取用户信息的相关接口
1.用户授权接口:获取access_token、openid等;获取并保存用户资料到数据库
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
|
public
function
actionAccesstoken()
{
$code
=
$_GET
[
"code"
];
$state
=
$_GET
[
"state"
];
$appid
= Yii::
$app
->params[
'wechat'
][
'appid'
];
$appsecret
= Yii::
$app
->params[
'wechat'
][
'appsecret'
];
$request_url
=
'https://api.weixin.qq.com/sns/oauth2/access_token?appid='
.
$appid
.
'&secret='
.
$appsecret
.
'&code='
.
$code
.
'&grant_type=authorization_code'
;
//初始化一个curl会话
$ch
= curl_init();
curl_setopt(
$ch
, CURLOPT_URL,
$request_url
);
curl_setopt(
$ch
, CURLOPT_RETURNTRANSFER, true);
$result
= curl_exec(
$ch
);
curl_close(
$ch
);
$result
=
$this
->response(
$result
);
//获取token和openid成功,数据解析
$access_token
=
$result
[
'access_token'
];
$refresh_token
=
$result
[
'refresh_token'
];
$openid
=
$result
[
'openid'
];
//请求微信接口,获取用户信息
$userInfo
=
$this
->getUserInfo(
$access_token
,
$openid
);
$user_check
= WechatUser::find()->where([
'openid'
=>
$openid
])->one();
if
(
$user_check
) {
//更新用户资料
}
else
{
//保存用户资料
}
//前端网页的重定向
if
(
$openid
) {
return
$this
->redirect(
$state
.
$openid
);
}
else
{
return
$this
->redirect(
$state
);
}
}
|
2.从微信获取用户资料
1
2
3
4
5
6
7
8
9
10
11
12
|
public
function
getUserInfo(
$access_token
,
$openid
)
{
$request_url
=
'https://api.weixin.qq.com/sns/userinfo?access_token='
.
$access_token
.
'&openid='
.
$openid
.
'&lang=zh_CN'
;
//初始化一个curl会话
$ch
= curl_init();
curl_setopt(
$ch
, CURLOPT_URL,
$request_url
);
curl_setopt(
$ch
, CURLOPT_RETURNTRANSFER, true);
$result
= curl_exec(
$ch
);
curl_close(
$ch
);
$result
=
$this
->response(
$result
);
return
$result
;
}
|
3.获取用户资料接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
|
public
function
actionUserinfo()
{
if
(isset(
$_REQUEST
[
"openid"
])){
$openid
=
$_REQUEST
[
"openid"
];
$user
= WechatUser::find()->where([
'openid'
=>
$openid
])->one();
if
(
$user
) {
$result
[
'error'
] = 0;
$result
[
'msg'
] =
'获取成功'
;
$result
[
'user'
] =
$user
;
}
else
{
$result
[
'error'
] = 1;
$result
[
'msg'
] =
'没有该用户'
;
}
}
else
{
$result
[
'error'
] = 1;
$result
[
'msg'
] =
'openid为空'
;
}
return
$result
;
}
|
微信支付
1.微信支付接口:打包支付数据
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
|
public
function
actionPay(){
if
(isset(
$_REQUEST
[
"uid"
])&&isset(
$_REQUEST
[
"oid"
])&&isset(
$_REQUEST
[
"totalFee"
])){
//uid、oid、totalFee
$uid
=
$_REQUEST
[
"uid"
];
$oid
=
$_REQUEST
[
"oid"
];
$totalFee
=
$_REQUEST
[
"totalFee"
];
$timestamp
= time();
//微信支付参数
$appid
= Yii::
$app
->params[
'wechat'
][
'appid'
];
$mchid
= Yii::
$app
->params[
'wechat'
][
'mchid'
];
$key
= Yii::
$app
->params[
'wechat'
][
'key'
];
$notifyUrl
= Yii::
$app
->params[
'wechat'
][
'notifyUrl'
];
//支付打包
$wx_pay
=
new
WechatPay(
$mchid
,
$appid
,
$key
);
$package
=
$wx_pay
->createJsBizPackage(
$uid
,
$totalFee
,
$oid
,
$notifyUrl
,
$timestamp
);
$result
[
'error'
] = 0;
$result
[
'msg'
] =
'支付打包成功'
;
$result
[
'package'
] =
$package
;
return
$result
;
}
else
{
$result
[
'error'
] = 1;
$result
[
'msg'
] =
'请求参数错误'
;
}
return
$result
;
}
|
2.接收微信发送的异步支付结果通知
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
|
public
function
actionNotify(){
$postStr
=
$GLOBALS
[
"HTTP_RAW_POST_DATA"
];
$postObj
= simplexml_load_string(
$postStr
,
'SimpleXMLElement'
, LIBXML_NOCDATA);
//
if
(
$postObj
=== false) {
die
(
'parse xml error'
);
}
if
(
$postObj
->return_code !=
'SUCCESS'
) {
die
(
$postObj
->return_msg);
}
if
(
$postObj
->result_code !=
'SUCCESS'
) {
die
(
$postObj
->err_code);
}
//微信支付参数
$appid
= Yii::
$app
->params[
'wechat'
][
'appid'
];
$mchid
= Yii::
$app
->params[
'wechat'
][
'mchid'
];
$key
= Yii::
$app
->params[
'wechat'
][
'key'
];
$wx_pay
=
new
WechatPay(
$mchid
,
$appid
,
$key
);
//验证签名
$arr
= (
array
)
$postObj
;
unset(
$arr
[
'sign'
]);
if
(
$wx_pay
->getSign(
$arr
,
$key
) !=
$postObj
->sign) {
die
(
"签名错误"
);
}
//支付处理正确-判断是否已处理过支付状态
$orders
= Order::find()->where([
'uid'
=>
$postObj
->openid,
'oid'
=>
$postObj
->out_trade_no,
'status'
=> 0])->all();
if
(
count
(
$orders
) > 0){
//更新订单状态
foreach
(
$orders
as
$order
) {
//更新订单
$order
[
'status'
] = 1;
$order
->update();
}
return
'<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'
;
}
else
{
//订单状态已更新,直接返回
return
'<xml><return_code><![CDATA[SUCCESS]]></return_code><return_msg><![CDATA[OK]]></return_msg></xml>'
;
}
}
|
3.微信支付类 WechatPay.php
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
|
<?php
namespace
api\sdk;
use
Yii;
class
WechatPay
{
protected
$mchid
;
protected
$appid
;
protected
$key
;
public
function
__construct(
$mchid
,
$appid
,
$key
){
$this
->mchid =
$mchid
;
$this
->appid =
$appid
;
$this
->key =
$key
;
}
public
function
createJsBizPackage(
$openid
,
$totalFee
,
$outTradeNo
,
$orderName
,
$notifyUrl
,
$timestamp
){
$config
=
array
(
'mch_id'
=>
$this
->mchid,
'appid'
=>
$this
->appid,
'key'
=>
$this
->key,
);
$unified
=
array
(
'appid'
=>
$config
[
'appid'
],
'attach'
=>
'支付'
,
'body'
=>
$orderName
,
'mch_id'
=>
$config
[
'mch_id'
],
'nonce_str'
=> self::createNonceStr(),
'notify_url'
=>
$notifyUrl
,
'openid'
=>
$openid
,
'out_trade_no'
=>
$outTradeNo
,
'spbill_create_ip'
=>
'127.0.0.1'
,
'total_fee'
=>
intval
(
$totalFee
* 100),
'trade_type'
=>
'JSAPI'
,
);
$unified
[
'sign'
] = self::getSign(
$unified
,
$config
[
'key'
]);
$responseXml
= self::curlPost(
'https://api.mch.weixin.qq.com/pay/unifiedorder'
, self::arrayToXml(
$unified
));
$unifiedOrder
= simplexml_load_string(
$responseXml
,
'SimpleXMLElement'
, LIBXML_NOCDATA);
if
(
$unifiedOrder
=== false) {
die
(
'parse xml error'
);
}
if
(
$unifiedOrder
->return_code !=
'SUCCESS'
) {
die
(
$unifiedOrder
->return_msg);
}
if
(
$unifiedOrder
->result_code !=
'SUCCESS'
) {
die
(
$unifiedOrder
->err_code);
}
$arr
=
array
(
"appId"
=>
$config
[
'appid'
],
"timeStamp"
=>
$timestamp
,
"nonceStr"
=> self::createNonceStr(),
"package"
=>
"prepay_id="
.
$unifiedOrder
->prepay_id,
"signType"
=>
'MD5'
,
);
$arr
[
'paySign'
] = self::getSign(
$arr
,
$config
[
'key'
]);
return
$arr
;
}
public
static
function
curlGet(
$url
=
''
,
$options
=
array
()){
$ch
= curl_init(
$url
);
curl_setopt(
$ch
, CURLOPT_RETURNTRANSFER, 1);
curl_setopt(
$ch
, CURLOPT_TIMEOUT, 30);
if
(!
empty
(
$options
)) {
curl_setopt_array(
$ch
,
$options
);
}
//https请求 不验证证书和host
curl_setopt(
$ch
, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt(
$ch
, CURLOPT_SSL_VERIFYHOST, false);
$data
= curl_exec(
$ch
);
curl_close(
$ch
);
return
$data
;
}
public
static
function
curlPost(
$url
=
''
,
$postData
=
''
,
$options
=
array
()){
if
(
is_array
(
$postData
)) {
$postData
= http_build_query(
$postData
);
}
$ch
= curl_init();
curl_setopt(
$ch
, CURLOPT_URL,
$url
);
curl_setopt(
$ch
, CURLOPT_RETURNTRANSFER, 1);
curl_setopt(
$ch
, CURLOPT_POST, 1);
curl_setopt(
$ch
, CURLOPT_POSTFIELDS,
$postData
);
curl_setopt(
$ch
, CURLOPT_TIMEOUT, 30);
//设置cURL允许执行的最长秒数
if
(!
empty
(
$options
)) {
curl_setopt_array(
$ch
,
$options
);
}
//https请求 不验证证书和host
curl_setopt(
$ch
, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt(
$ch
, CURLOPT_SSL_VERIFYHOST, false);
$data
= curl_exec(
$ch
);
curl_close(
$ch
);
return
$data
;
}
public
static
function
createNonceStr(
$length
= 16){
$chars
=
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
;
$str
=
''
;
for
(
$i
= 0;
$i
<
$length
;
$i
++){
$str
.=
substr
(
$chars
, mt_rand(0,
strlen
(
$chars
) - 1), 1);
}
return
$str
;
}
public
static
function
arrayToXml(
$arr
){
$xml
=
"<xml>"
;
foreach
(
$arr
as
$key
=>
$val
){
if
(
is_numeric
(
$val
)) {
$xml
.=
"<"
.
$key
.
">"
.
$val
.
"</"
.
$key
.
">"
;
}
else
{
$xml
.=
"<"
.
$key
.
"><![CDATA["
.
$val
.
"]]></"
.
$key
.
">"
;
}
}
$xml
.=
"</xml>"
;
return
$xml
;
}
public
static
function
getSign(
$params
,
$key
){
ksort(
$params
, SORT_STRING);
$unSignParaString
= self::formatQueryParaMap(
$params
, false);
$signStr
=
strtoupper
(md5(
$unSignParaString
.
"&key="
.
$key
));
return
$signStr
;
}
protected
static
function
formatQueryParaMap(
$paraMap
,
$urlEncode
= false){
$buff
=
""
;
ksort(
$paraMap
);
foreach
(
$paraMap
as
$k
=>
$v
){
if
(null !=
$v
&&
"null"
!=
$v
) {
if
(
$urlEncode
) {
$v
= urlencode(
$v
);
}
$buff
.=
$k
.
"="
.
$v
.
"&"
;
}
}
$reqPar
=
''
;
if
(
strlen
(
$buff
)>0) {
$reqPar
=
substr
(
$buff
, 0,
strlen
(
$buff
) - 1);
}
return
$reqPar
;
}
}
|
获取JS-SDK的config参数
根据微信公众平台开发者文档:
所有需要使用JS-SDK的页面必须先注入配置信息,否则将无法调用(同一个url仅需调用一次,对于变化url的SPA的web app可在每次url变化时进行调用,目前Android微信客户端不支持pushState的H5新特性,所以使用pushState来实现web app的页面会导致签名失败,此问题会在Android6.2中修复)。
即:
1
2
3
4
5
6
7
8
|
wx.config({
debug: true,
// 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
appId:
''
,
// 必填,公众号的唯一标识
timestamp: ,
// 必填,生成签名的时间戳
nonceStr:
''
,
// 必填,生成签名的随机串
signature:
''
,
// 必填,签名,见附录1
jsApiList: []
// 必填,需要使用的JS接口列表,所有JS接口列表见附录2
});
|
1.微信支付类 WechatPay.php
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
|
<?php
namespace
api\sdk;
use
Yii;
class
WechatPay
{
public
function
getSignPackage(
$url
) {
$jsapiTicket
= self::getJsApiTicket();
$timestamp
= time();
$nonceStr
= self::createNonceStr();
// 这里参数的顺序要按照 key 值 ASCII 码升序排序
$string
=
"jsapi_ticket="
.
$jsapiTicket
.
"&noncestr="
.
$nonceStr
.
"×tamp="
.
$timestamp
.
"&url="
.
$url
;
$signature
= sha1(
$string
);
$signPackage
=
array
(
"appId"
=>
$this
->appid,
"nonceStr"
=>
$nonceStr
,
"timestamp"
=>
$timestamp
,
"url"
=>
$url
,
"signature"
=>
$signature
,
"rawString"
=>
$string
);
return
$signPackage
;
}
public
static
function
getJsApiTicket() {
//使用Redis缓存 jsapi_ticket
$redis
= Yii::
$app
->redis;
$redis_ticket
=
$redis
->get(
'wechat:jsapi_ticket'
);
if
(
$redis_ticket
) {
$ticket
=
$redis_ticket
;
}
else
{
$accessToken
= self::getAccessToken();
$url
=
"https://api.weixin.qq.com/cgi-bin/ticket/getticket?type=jsapi&access_token="
.
$accessToken
;
$res
= json_decode(self::curlGet(
$url
));
$ticket
=
$res
->ticket;
if
(
$ticket
) {
$redis
->set(
'wechat:jsapi_ticket'
,
$ticket
);
$redis
->expire(
'wechat:jsapi_ticket'
, 7000);
}
}
return
$ticket
;
}
public
static
function
getAccessToken() {
//使用Redis缓存 access_token
$redis
= Yii::
$app
->redis;
$redis_token
=
$redis
->get(
'wechat:access_token'
);
if
(
$redis_token
) {
$access_token
=
$redis_token
;
}
else
{
$appid
= Yii::
$app
->params[
'wechat'
][
'appid'
];
$appsecret
= Yii::
$app
->params[
'wechat'
][
'appsecret'
];
$url
=
"https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid="
.
$appid
.
"&secret="
.
$appsecret
;
$res
= json_decode(self::curlGet(
$url
));
$access_token
=
$res
->access_token;
if
(
$access_token
) {
$redis
->set(
'wechat:access_token'
,
$access_token
);
$redis
->expire(
'wechat:access_token'
, 7000);
}
}
return
$access_token
;
}
public
static
function
curlGet(
$url
=
''
,
$options
=
array
()){
$ch
= curl_init(
$url
);
curl_setopt(
$ch
, CURLOPT_RETURNTRANSFER, 1);
curl_setopt(
$ch
, CURLOPT_TIMEOUT, 30);
if
(!
empty
(
$options
)) {
curl_setopt_array(
$ch
,
$options
);
}
//https请求 不验证证书和host
curl_setopt(
$ch
, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt(
$ch
, CURLOPT_SSL_VERIFYHOST, false);
$data
= curl_exec(
$ch
);
curl_close(
$ch
);
return
$data
;
}
public
static
function
curlPost(
$url
=
''
,
$postData
=
''
,
$options
=
array
()){
if
(
is_array
(
$postData
)) {
$postData
= http_build_query(
$postData
);
}
$ch
= curl_init();
curl_setopt(
$ch
, CURLOPT_URL,
$url
);
curl_setopt(
$ch
, CURLOPT_RETURNTRANSFER, 1);
curl_setopt(
$ch
, CURLOPT_POST, 1);
curl_setopt(
$ch
, CURLOPT_POSTFIELDS,
$postData
);
curl_setopt(
$ch
, CURLOPT_TIMEOUT, 30);
//设置cURL允许执行的最长秒数
if
(!
empty
(
$options
)) {
curl_setopt_array(
$ch
,
$options
);
}
//https请求 不验证证书和host
curl_setopt(
$ch
, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt(
$ch
, CURLOPT_SSL_VERIFYHOST, false);
$data
= curl_exec(
$ch
);
curl_close(
$ch
);
return
$data
;
}
public
static
function
createNonceStr(
$length
= 16){
$chars
=
'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'
;
$str
=
''
;
for
(
$i
= 0;
$i
<
$length
;
$i
++){
$str
.=
substr
(
$chars
, mt_rand(0,
strlen
(
$chars
) - 1), 1);
}
return
$str
;
}
}
|
2.获取config参数接口
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
public
function
actionConfig(){
if
(isset(
$_REQUEST
[
'url'
])) {
$url
=
$_REQUEST
[
'url'
];
//微信支付参数
$appid
= Yii::
$app
->params[
'wechat'
][
'appid'
];
$mchid
= Yii::
$app
->params[
'wechat'
][
'mchid'
];
$key
= Yii::
$app
->params[
'wechat'
][
'key'
];
$wx_pay
=
new
WechatPay(
$mchid
,
$appid
,
$key
);
$package
=
$wx_pay
->getSignPackage(
$url
);
$result
[
'error'
] = 0;
$result
[
'msg'
] =
'获取成功'
;
$result
[
'config'
] =
$package
;
}
else
{
$result
[
'error'
] = 1;
$result
[
'msg'
] =
'参数错误'
;
}
return
$result
;
}
|