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

作者: 八宝粥BBZ | 来源:发表于2016-07-09 16:46 被阅读3638次

    本文内容较多,包括微信接入、获取微信用户信息、微信支付、JSSDK配置参数获取等部分。如果读者对微信开发没有一个主观上的认识,那么建议读者先研读微信公众平台开发者文档,然后再阅读本文,效果更佳!另外本文的分章节版本可以在八宝粥的博客找到。

    20160712-Update:微信开发的完整例子已经整理在Github,欢迎查看: yii2-wechat-demo

    接入微信

    Yii2后台配置

    1.在app/config/params.php中配置token参数

    return [
        //微信接入
        'wechat' =>[
            'token' => 'your token',
        ],
    ];
    

    2.在app/config/main.php中配置路由

    因为接口模块使用的RESTful API,所以需要定义路由规则。

    'urlManager' => [
        'enablePrettyUrl' => true,
        'enableStrictParsing' => true,
        'showScriptName' => false,
        'rules' => [
            [
                'class' => 'yii\rest\UrlRule',
                'controller' => 'wechat',
                'extraPatterns' => [
                    'GET valid' => 'valid',
                ],
            ],
        ],
    ],
    

    3.在app/controllers中新建WechatController

    <?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,然后提交验证即可。

    URL:http://app.demo.com/wechats/valid
    Token:your token
    

    获取用户信息

    用户表设计

    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等;获取并保存用户资料到数据库

    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.从微信获取用户资料

    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.获取用户资料接口

    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.微信支付接口:打包支付数据

    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.接收微信发送的异步支付结果通知

    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

    <?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中修复)。

    即:

    wx.config({
        debug: true, // 开启调试模式,调用的所有api的返回值会在客户端alert出来,若要查看传入的参数,可以在pc端打开,参数信息会通过log打出,仅在pc端时才会打印。
        appId: '', // 必填,公众号的唯一标识
        timestamp: , // 必填,生成签名的时间戳
        nonceStr: '', // 必填,生成签名的随机串
        signature: '',// 必填,签名,见附录1
        jsApiList: [] // 必填,需要使用的JS接口列表,所有JS接口列表见附录2
    });
    

    1.微信支付类 WechatPay.php

    <?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参数接口

    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;
    }
    

    相关文章

      网友评论

      • 7a4edf324e3b:粥哥,你知道yii框架里身份证验证怎么写吗😍
      • 酒醉疯子:膜拜大婶
      • 淋1枫: 完全没看懂 配置了 也没搞好 能加个qq吗 619656856
      • nivin:可以的
      • b89e7545e7c3:很不错,学习了,谢谢分享
      • linfree:不错。
      • linxb:正打算入手yii,刚好拿来学习,谢谢博主
        八宝粥BBZ:@linxb :smiley: 加油啊,也欢迎star、fork
      • 新亮笔记:赞~ PHP技术,学习一下。
        八宝粥BBZ:@壁虎 共同学习!
      • c57dddf3b833:求demo
        八宝粥BBZ:@c57dd 微信开发的完整例子已经整理在Github,欢迎查看: [yii2-wechat-demo](https://github.com/ybbz/yii2-wechat-demo)。
        c57dddf3b833:@八宝粥BBZ 谢谢
        八宝粥BBZ:@c57dd 等晚上有时间了,写一个demo放到github上去。 :smiley:

      本文标题:PHP微信公众号后台开发(Yii2实现)

      本文链接:https://www.haomeiwen.com/subject/ekorjttx.html