Browse Source

Merge pull request #287 from uctoo/master

增加个性化自定义菜单等
dodge 8 years ago
parent
commit
3a9d35879b
3 changed files with 484 additions and 1 deletions
  1. 218 0
      Thinkphp/JsSdkPay.class.php
  2. 108 0
      Thinkphp/Wxauth.class.php
  3. 158 1
      wechat.class.php

+ 218 - 0
Thinkphp/JsSdkPay.class.php

@@ -0,0 +1,218 @@
+<?php
+/**
+ * 官方文档:http://mp.weixin.qq.com/wiki/7/aaa137b55fb2e0456bf8dd9148dd613f.html
+ * 微信支付:http://pay.weixin.qq.com/wiki/doc/api/index.php?chapter=9_1#
+ * 官方示例:http://demo.open.weixin.qq.com/jssdk/sample.zip
+ * UCToo示例:http://git.oschina.net/uctoo/uctoo/blob/master/Addons/Jssdk/Controller/JssdkController.class.php
+ * 
+ * 微信JSSDK支付类,主要实现了微信JSSDK支付,参考官方提供的示例文档,
+ * @命名空间版本
+ * @author uctoo (www.uctoo.com)
+ * @date 2015-5-15 14:10
+ */
+namespace Com;
+
+class JsSdkPay {
+  private $appId;
+  private $appSecret;
+  public $debug = false;
+  public $weObj;      //微信类实例
+  public $parameters;//获取prepay_id时的请求参数
+  //受理商ID,身份标识
+  public $MCHID = '';
+  //商户支付密钥Key。审核通过后,在微信商户平台中查看 https://pay.weixin.qq.com
+  public $KEY = '';
+
+  //=======【JSAPI路径设置】===================================
+  //获取access_token过程中的跳转uri,通过跳转将code传入jsapi支付页面
+  public $JS_API_CALL_URL = '';
+
+  //=======【证书路径设置】=====================================
+  //证书路径,注意应该填写绝对路径
+  public $SSLCERT_PATH = '/xxx/xxx/xxxx/WxPayPubHelper/cacert/apiclient_cert.pem';
+  public $SSLKEY_PATH = '/xxx/xxx/xxxx/WxPayPubHelper/cacert/apiclient_key.pem';
+
+  //=======【异步通知url设置】===================================
+  //异步通知url,商户根据实际开发过程设定
+  //C('url')."admin.php/order/notify_url.html";
+  public $NOTIFY_URL = '';
+
+  //=======【curl超时设置】===================================
+  //本例程通过curl使用HTTP POST方法,此处可修改其超时时间,默认为30秒
+  public  $CURL_TIMEOUT = 30;
+
+  public  $prepay_id;
+
+  public function __construct($options) {
+    $this->appId = $options['appid'];
+    $this->appSecret = $options['appsecret'];
+    $this->weObj = new TPWechat($options);
+  }
+
+  //微信支付相关方法
+  /**
+   * 	作用:格式化参数,签名过程需要使用
+   */
+  function formatBizQueryParaMap($paraMap, $urlencode)
+  {
+    $buff = "";
+    ksort($paraMap);
+    foreach ($paraMap as $k => $v)
+    {
+      if($urlencode)
+      {
+        $v = urlencode($v);
+      }
+      //$buff .= strtolower($k) . "=" . $v . "&";
+      $buff .= $k . "=" . $v . "&";
+    }
+    $reqPar = "";
+    if (strlen($buff) > 0)
+    {
+      $reqPar = substr($buff, 0, strlen($buff)-1);
+    }
+    return $reqPar;
+  }
+  /**
+   * 	作用:设置jsapi的参数
+   */
+  public function getParameters()
+  {
+    $jsApiObj["appId"] = $this->appId;           //请求生成支付签名时需要,js调起支付参数中不需要
+    $timeStamp = time();
+    $jsApiObj["timeStamp"] = "$timeStamp";      //用大写的timeStamp参数请求生成支付签名
+    $jsParamObj["timestamp"] = $timeStamp;      //用小写的timestamp参数生成js支付参数,还要注意数据类型,坑!
+    $jsParamObj["nonceStr"] = $jsApiObj["nonceStr"] = $this->weObj->generateNonceStr();
+    $jsParamObj["package"] = $jsApiObj["package"] = "prepay_id=$this->prepay_id";
+    $jsParamObj["signType"] = $jsApiObj["signType"] = "MD5";
+    $jsParamObj["paySign"] = $jsApiObj["paySign"] = $this->getSign($jsApiObj);
+
+    $jsParam = json_encode($jsParamObj);
+
+    return $jsParam;
+  }
+
+  /**
+   * 获取prepay_id
+   */
+  function getPrepayId()
+  {
+    $result = $this->xmlToArray($this->postXml());
+    $prepay_id = $result["prepay_id"];
+    return $prepay_id;
+  }
+  /**
+   * 	作用:将xml转为array
+   */
+  public function xmlToArray($xml)
+  {
+    //将XML转为array
+    $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true);
+    return $array_data;
+  }
+  /**
+   * 	作用:post请求xml
+   */
+  function postXml()
+  {
+    $xml = $this->createXml();
+    return  $this->postXmlCurl($xml,"https://api.mch.weixin.qq.com/pay/unifiedorder",$this->CURL_TIMEOUT);
+
+  }
+  /**
+   * 	作用:以post方式提交xml到对应的接口url
+   */
+  public function postXmlCurl($xml,$url,$second=30)
+  {
+    //初始化curl
+    $ch = curl_init();
+    //设置超时
+    curl_setopt($ch,CURLOP_TIMEOUT, $this->CURL_TIMEOUT);
+    //这里设置代理,如果有的话
+    //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8');
+    //curl_setopt($ch,CURLOPT_PROXYPORT, 8080);
+    curl_setopt($ch,CURLOPT_URL, $url);
+    curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE);
+    curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE);
+    //设置header
+    curl_setopt($ch, CURLOPT_HEADER, FALSE);
+    //要求结果为字符串且输出到屏幕上
+    curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);
+    //post提交方式
+    curl_setopt($ch, CURLOPT_POST, TRUE);
+    curl_setopt($ch, CURLOPT_POSTFIELDS, $xml);
+    //运行curl
+    $data = curl_exec($ch);
+    curl_close($ch);
+    //返回结果
+    if($data)
+    {
+      curl_close($ch);
+      return $data;
+    }
+    else
+    {
+      $error = curl_errno($ch);
+      echo "curl出错,错误码:$error"."<br>";
+      echo "<a href='http://curl.haxx.se/libcurl/c/libcurl-errors.html'>错误原因查询</a></br>";
+      curl_close($ch);
+      return false;
+    }
+  }
+  /**
+   * 	作用:设置标配的请求参数,生成签名,生成接口参数xml
+   */
+  function createXml()
+  {
+    $this->parameters["appid"] = $this->appId;//公众账号ID
+    $this->parameters["mch_id"] = $this->MCHID;//商户号
+    $this->parameters["nonce_str"] = $this->weObj->generateNonceStr();//随机字符串
+    $this->parameters["sign"] = $this->getSign($this->parameters);//签名
+    return  $this->arrayToXml($this->parameters);
+  }
+   /**
+   * 	作用:array转xml
+   */
+  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 function getSign($Obj)
+  {
+    foreach ($Obj as $k => $v)
+    {
+      $Parameters[$k] = $v;
+    }
+    //签名步骤一:按字典序排序参数
+    ksort($Parameters);
+    $String = $this->formatBizQueryParaMap($Parameters, false);
+    //echo '【string1】'.$String.'</br>';
+    //签名步骤二:在string后加入KEY
+    $String = $String."&key=".$this->KEY;
+    //echo "【string2】".$String."</br>";
+    //签名步骤三:MD5加密
+    $String = md5($String);
+    //echo "【string3】 ".$String."</br>";
+    //签名步骤四:所有字符转为大写
+    $result_ = strtoupper($String);
+    //echo "【result】 ".$result_."</br>";
+    return $result_;
+  }
+	
+}
+

+ 108 - 0
Thinkphp/Wxauth.class.php

@@ -0,0 +1,108 @@
+<?php
+/**
+ * 微信oAuth认证示例
+ * 官方文档:http://mp.weixin.qq.com/wiki/17/c0f37d5704f0b64713d5d2c37b468d75.html
+ * UCToo示例:http://git.oschina.net/uctoo/uctoo/blob/master/Addons/Ucuser/UcuserAddon.class.php
+ *
+ * 微信oAuth认证类,适配Thinkphp框架,
+ * @命名空间版本
+ * @author uctoo (www.uctoo.com)
+ * @date 2015-5-15 14:10
+ */
+namespace Com;
+
+class Wxauth {
+	private $options;
+	public $open_id;
+	public $wxuser;
+	
+	public function __construct($options){
+		$this->options = $options;
+		$this->wxoauth();
+	}
+	
+	public function wxoauth(){
+		$scope = 'snsapi_base';
+		$code = isset($_GET['code'])?$_GET['code']:'';
+		$token_time = isset($_SESSION['token_time'])?$_SESSION['token_time']:0;
+		if(!$code && isset($_SESSION['open_id']) && isset($_SESSION['user_token']) && $token_time>time()-3600)
+		{
+			if (!$this->wxuser) {
+				$this->wxuser = $_SESSION['wxuser'];
+			}
+			$this->open_id = $_SESSION['open_id'];
+			return $this->open_id;
+		}
+		else
+		{
+
+			$options = array(
+					'token'=>$this->options["token"], //填写你设定的key
+                    'encodingaeskey'=>$this->options["encodingaeskey"], //填写加密用的EncodingAESKey
+					'appid'=>$this->options["appid"], //填写高级调用功能的app id
+					'appsecret'=>$this->options["appsecret"] //填写高级调用功能的密钥
+			);
+			$we_obj = new TPWechat($options);
+			if ($code) {
+				$json = $we_obj->getOauthAccessToken();
+				if (!$json) {
+					unset($_SESSION['wx_redirect']);
+					die('获取用户授权失败,请重新确认');
+				}
+				$_SESSION['open_id'] = $this->open_id = $json["openid"];
+				$access_token = $json['access_token'];
+				$_SESSION['user_token'] = $access_token;
+				$_SESSION['token_time'] = time();
+				$userinfo = $we_obj->getUserInfo($this->open_id);
+				if ($userinfo && !empty($userinfo['nickname'])) {
+					$this->wxuser = array(
+							'open_id'=>$this->open_id,
+							'nickname'=>$userinfo['nickname'],
+							'sex'=>intval($userinfo['sex']),
+							'location'=>$userinfo['province'].'-'.$userinfo['city'],
+							'avatar'=>$userinfo['headimgurl']
+					);
+				} elseif (strstr($json['scope'],'snsapi_userinfo')!==false) {
+					$userinfo = $we_obj->getOauthUserinfo($access_token,$this->open_id);
+					if ($userinfo && !empty($userinfo['nickname'])) {
+						$this->wxuser = array(
+								'open_id'=>$this->open_id,
+								'nickname'=>$userinfo['nickname'],
+								'sex'=>intval($userinfo['sex']),
+								'location'=>$userinfo['province'].'-'.$userinfo['city'],
+								'avatar'=>$userinfo['headimgurl']
+						);
+					} else {
+						return $this->open_id;
+					}
+				}
+				if ($this->wxuser) {
+					$_SESSION['wxuser'] = $this->wxuser;
+					$_SESSION['open_id'] =  $json["openid"];
+					unset($_SESSION['wx_redirect']);
+					return $this->open_id;
+				}
+				$scope = 'snsapi_userinfo';
+			}
+			if ($scope=='snsapi_base') {
+				$url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];
+				$_SESSION['wx_redirect'] = $url;
+			} else {
+				$url = $_SESSION['wx_redirect'];
+			}
+			if (!$url) {
+				unset($_SESSION['wx_redirect']);
+				die('获取用户授权失败');
+			}
+			$oauth_url = $we_obj->getOauthRedirect($url,"wxbase",$scope);
+			redirect ( $oauth_url );
+		}
+	}
+}
+//$options = array(
+//		'token'=>'uctoo', //填写你设定的key
+//		'appid'=>'wxdk1234567890', //填写高级调用功能的app id, 请在微信开发模式后台查询
+//		'appsecret'=>'xxxxxxxxxxxxxxxxxxx', //填写高级调用功能的密钥
+//);
+//$auth = new Wxauth($options);
+//var_dump($auth->wxuser);

+ 158 - 1
wechat.class.php

@@ -79,6 +79,9 @@ class Wechat
 	const MENU_CREATE_URL = '/menu/create?';
 	const MENU_GET_URL = '/menu/get?';
 	const MENU_DELETE_URL = '/menu/delete?';
+	const MENU_ADDCONDITIONAL_URL = '/menu/addconditional?';
+	const MENU_DELCONDITIONAL_URL = '/menu/delconditional?';
+	const MENU_TRYMATCH_URL = '/menu/trymatch?';
 	const GET_TICKET_URL = '/ticket/getticket?';
 	const CALLBACKSERVER_GET_URL = '/getcallbackip?';
 	const QRCODE_CREATE_URL='/qrcode/create?';
@@ -88,6 +91,7 @@ class Wechat
 	const SHORT_URL='/shorturl?';
 	const USER_GET_URL='/user/get?';
 	const USER_INFO_URL='/user/info?';
+	const USERS_INFO_URL='/user/info/batchget?';
 	const USER_UPDATEREMARK_URL='/user/info/updateremark?';
 	const GROUP_GET_URL='/groups/get?';
 	const USER_GROUP_URL='/groups/getid?';
@@ -144,7 +148,8 @@ class Wechat
 	const CARD_DELETE                     = '/card/delete?';
 	const CARD_UPDATE                     = '/card/update?';
 	const CARD_GET                        = '/card/get?';
-	const CARD_BATCHGET                   = '/card/batchget?';
+        const CARD_USER_GETCARDLIST         = '/card/user/getcardlist?';
+        const CARD_BATCHGET                   = '/card/batchget?';
 	const CARD_MODIFY_STOCK               = '/card/modifystock?';
 	const CARD_LOCATION_BATCHADD          = '/card/location/batchadd?';
 	const CARD_LOCATION_BATCHGET          = '/card/location/batchget?';
@@ -1310,6 +1315,37 @@ class Wechat
 	    return $signPackage;
 	}
 
+    /**
+     * 获取卡券签名cardSign
+     * @param string $card_type 卡券的类型,不可为空,官方jssdk文档说这个值可空,但签名验证工具又必填这个值,官方文档到处是坑,
+     * @param string $card_id 卡券的ID,可空
+     * @param string $location_id 卡券的适用门店ID,可空
+     * @param string $timestamp 当前时间戳 (为空则自动生成)
+     * @param string $noncestr 随机串 (为空则自动生成)
+     * @param string $appid 用于多个appid时使用,可空
+     * @return array|bool 返回签名字串
+     */
+    public function getCardSign($card_type='',$card_id='',$code='',$location_id='',$timestamp=0, $noncestr='', $appid=''){
+        if (!$this->api_ticket && !$this->getJsCardTicket($appid)) return false;
+        if (!$timestamp)
+            $timestamp = time();
+        if (!$noncestr)
+            $noncestr = $this->generateNonceStr();
+        $arrdata = array("api_ticket" => $this->api_ticket,"app_id" => $this->appid,"card_id" => $card_id,"code" => $code,"card_type" => $card_type,"location_id" => $location_id,"timestamp" => $timestamp, "noncestr" => $noncestr );
+        $sign = $this->getTicketSignature($arrdata);
+        if (!$sign)
+            return false;
+        $signPackage = array(
+            "cardType"     => $card_type,
+            "cardId"       => $card_id,
+            "shopId"       => $location_id,         //location_id就是shopId
+            "nonceStr"  => $noncestr,
+            "timestamp" => $timestamp,
+            "cardSign" => $sign
+        );
+        return $signPackage;
+    }
+
 	/**
 	 * 微信api不支持中文转义的json结构
 	 * @param array $arr
@@ -1577,6 +1613,72 @@ class Wechat
 	}
 
 	/**
+	 * 创建个性化菜单(认证后的订阅号可用)
+	 * @param array $data
+	 * @return bool
+	 *
+	 */
+	public function addconditionalMenu($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MENU_ADDCONDITIONAL_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * 删除个性化菜单(认证后的订阅号可用)
+	 * @param $data {"menuid":"208379533"}
+	 *
+	 * @return bool
+	 */
+	public function delconditionalMenu($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MENU_DELCONDITIONAL_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * 测试个性化菜单匹配结果(认证后的订阅号可用)
+	 * @param $data {"user_id":"weixin"} user_id可以是粉丝的OpenID,也可以是粉丝的微信号
+	 *
+	 * @return bool|array('button'=>array(....s))
+	 */
+	public function trymatchMenu($data){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::MENU_TRYMATCH_URL.'access_token='.$this->access_token,self::json_encode($data));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (!$json || !empty($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
+
+	/**
 	 * 上传临时素材,有效期为3天(认证后的订阅号可用)
 	 * 注意:上传大文件时可能需要先调用 set_time_limit(0) 避免超时
 	 * 注意:数组的键值任意,但文件名前必须加@,使用单引号以避免本地路径斜杠被转义
@@ -2209,6 +2311,27 @@ class Wechat
 		}
 		return false;
 	}
+	/**
+	 * 批量获取关注者详细信息
+	 * @param array $openids user_list{{'openid:xxxxxx'},{},{}}
+	 * @return array user_info_list{subscribe,openid,nickname,sex,city,province,country,language,headimgurl,subscribe_time,[unionid]}{}{}...
+	 * 注意:unionid字段 只有在用户将公众号绑定到微信开放平台账号后,才会出现。建议调用前用isset()检测一下
+	 */
+	public function getUsersInfo($openids){
+		if (!$this->access_token && !$this->checkAuth()) return false;
+		$result = $this->http_post(self::API_URL_PREFIX.self::USERS_INFO_URL.'access_token='.$this->access_token,json_encode($openids));
+		if ($result)
+		{
+			$json = json_decode($result,true);
+			if (isset($json['errcode'])) {
+				$this->errCode = $json['errcode'];
+				$this->errMsg = $json['errmsg'];
+				return false;
+			}
+			return $json;
+		}
+		return false;
+	}
 
 	/**
 	 * 设置用户备注名
@@ -3096,6 +3219,40 @@ class Wechat
     }
 
     /**
+     * 获取用户已领取卡券接口
+     * @param string $openid
+     * @param string $card_id
+     * @return boolean|array    返回数组信息比较复杂,请参看卡券接口文档
+     * 成功返回结果
+     *  {
+     * "errcode":0,
+     * "errmsg":"ok",
+     * "card_list": [
+     * {"code": "xxx1434079154", "card_id": "xxxxxxxxxx"},
+     * {"code": "xxx1434079155", "card_id": "xxxxxxxxxx"}
+     * ]
+     * }
+     */
+    public function getUserCardList($openid,$card_id) {
+        $data = array(
+            'openid' => $openid,
+            'card_id' => $card_id
+        );
+        if (!$this->access_token && !$this->checkAuth()) return false;
+        $result = $this->http_post(self::API_BASE_URL_PREFIX . self::CARD_USER_GETCARDLIST . 'access_token=' . $this->access_token, self::json_encode($data));
+        if ($result) {
+            $json = json_decode($result, true);
+            if (!$json || !empty($json['errcode'])) {
+                $this->errCode = $json['errcode'];
+                $this->errMsg  = $json['errmsg'];
+                return false;
+            }
+            return $json;
+        }
+        return false;
+    }
+
+    /**
      * 获取颜色列表
 	 * 获得卡券的最新颜色列表,用于创建卡券
 	 * @return boolean|array   返回数组请参看 微信卡券接口文档 的json格式