PHP微信公众号后台开发(Yii2实现)

本文内容较多,包括微信接入、获取微信用户信息、微信支付、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 . "&timestamp=" . $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 ;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值