├── .gitattributes ├── .gitignore ├── 1218891802_20140425185952.pfx ├── README.md ├── base └── HttpClient.class.php ├── pay.php ├── wechatAPISDK.php ├── wechat_notify.php ├── wechatconfig.php └── 【微信APP支付】接口文档V1.2_For_Android.pdf /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | 4 | # Custom for Visual Studio 5 | *.cs diff=csharp 6 | *.sln merge=union 7 | *.csproj merge=union 8 | *.vbproj merge=union 9 | *.fsproj merge=union 10 | *.dbproj merge=union 11 | 12 | # Standard to msysgit 13 | *.doc diff=astextplain 14 | *.DOC diff=astextplain 15 | *.docx diff=astextplain 16 | *.DOCX diff=astextplain 17 | *.dot diff=astextplain 18 | *.DOT diff=astextplain 19 | *.pdf diff=astextplain 20 | *.PDF diff=astextplain 21 | *.rtf diff=astextplain 22 | *.RTF diff=astextplain 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Windows image file caches 2 | Thumbs.db 3 | ehthumbs.db 4 | 5 | # Folder config file 6 | Desktop.ini 7 | 8 | # Recycle Bin used on file shares 9 | $RECYCLE.BIN/ 10 | 11 | # Windows Installer files 12 | *.cab 13 | *.msi 14 | *.msm 15 | *.msp 16 | 17 | # ========================= 18 | # Operating System Files 19 | # ========================= 20 | 21 | # OSX 22 | # ========================= 23 | 24 | .DS_Store 25 | .AppleDouble 26 | .LSOverride 27 | 28 | # Icon must ends with two \r. 29 | Icon 30 | 31 | # Thumbnails 32 | ._* 33 | 34 | # Files that might appear on external disk 35 | .Spotlight-V100 36 | .Trashes 37 | -------------------------------------------------------------------------------- /1218891802_20140425185952.pfx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofunbox/wechat_app_pay/HEAD/1218891802_20140425185952.pfx -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | 已废弃~~ 3 | 4 | wechat_app_pay 5 | ============== 6 | 7 | 最新微信APP支付PHP服务端SDK 8 | 9 | 懒得再写一遍了~ 10 | 11 | -------------------------------------------------------------------------------- /base/HttpClient.class.php: -------------------------------------------------------------------------------- 1 | cookies array ready for the next request 24 | // Note: This currently ignores the cookie path (and time) completely. Time is not important, 25 | // but path could possibly lead to security problems. 26 | var $persist_referers = true; // For each request, sends path of last request as referer 27 | var $debug = false; 28 | var $handle_redirects = true; // Auaomtically redirect if Location or URI header is found 29 | var $max_redirects = 5; 30 | var $headers_only = false; // If true, stops receiving once headers have been read. 31 | // Basic authorization variables 32 | var $username; 33 | var $password; 34 | // Response vars 35 | var $status; 36 | var $headers = array(); 37 | var $content = ''; 38 | var $errormsg; 39 | // Tracker variables 40 | var $redirect_count = 0; 41 | var $cookie_host = ''; 42 | function HttpClient($host, $port=80) { 43 | $this->host = $host; 44 | $this->port = $port; 45 | } 46 | function get($path, $data = false) { 47 | $this->path = $path; 48 | $this->method = 'GET'; 49 | if ($data) { 50 | $this->path .= '?'.$this->buildQueryString($data); 51 | } 52 | return $this->doRequest(); 53 | } 54 | function post($path, $data) { 55 | $this->path = $path; 56 | $this->method = 'POST'; 57 | $this->postdata = $this->buildQueryString($data); 58 | return $this->doRequest(); 59 | } 60 | function buildQueryString($data) { 61 | $querystring = ''; 62 | if (is_array($data)) { 63 | // Change data in to postable data 64 | foreach ($data as $key => $val) { 65 | if (is_array($val)) { 66 | foreach ($val as $val2) { 67 | $querystring .= urlencode($key).'='.urlencode($val2).'&'; 68 | } 69 | } else { 70 | $querystring .= urlencode($key).'='.urlencode($val).'&'; 71 | } 72 | } 73 | $querystring = substr($querystring, 0, -1); // Eliminate unnecessary & 74 | } else { 75 | $querystring = $data; 76 | } 77 | return $querystring; 78 | } 79 | function doRequest() { 80 | // Performs the actual HTTP request, returning true or false depending on outcome 81 | if (!$fp = @fsockopen($this->host, $this->port, $errno, $errstr, $this->timeout)) { 82 | // Set error message 83 | switch($errno) { 84 | case -3: 85 | $this->errormsg = 'Socket creation failed (-3)'; 86 | case -4: 87 | $this->errormsg = 'DNS lookup failure (-4)'; 88 | case -5: 89 | $this->errormsg = 'Connection refused or timed out (-5)'; 90 | default: 91 | $this->errormsg = 'Connection failed ('.$errno.')'; 92 | $this->errormsg .= ' '.$errstr; 93 | $this->debug($this->errormsg); 94 | } 95 | return false; 96 | } 97 | socket_set_timeout($fp, $this->timeout); 98 | $request = $this->buildRequest(); 99 | $this->debug('Request', $request); 100 | fwrite($fp, $request); 101 | // Reset all the variables that should not persist between requests 102 | $this->headers = array(); 103 | $this->content = ''; 104 | $this->errormsg = ''; 105 | // Set a couple of flags 106 | $inHeaders = true; 107 | $atStart = true; 108 | // Now start reading back the response 109 | while (!feof($fp)) { 110 | $line = fgets($fp, 4096); 111 | if ($atStart) { 112 | // Deal with first line of returned data 113 | $atStart = false; 114 | if (!preg_match('/HTTP\/(\\d\\.\\d)\\s*(\\d+)\\s*(.*)/', $line, $m)) { 115 | $this->errormsg = "Status code line invalid: ".htmlentities($line); 116 | $this->debug($this->errormsg); 117 | return false; 118 | } 119 | $http_version = $m[1]; // not used 120 | $this->status = $m[2]; 121 | $status_string = $m[3]; // not used 122 | $this->debug(trim($line)); 123 | continue; 124 | } 125 | if ($inHeaders) { 126 | if (trim($line) == '') { 127 | $inHeaders = false; 128 | $this->debug('Received Headers', $this->headers); 129 | if ($this->headers_only) { 130 | break; // Skip the rest of the input 131 | } 132 | continue; 133 | } 134 | if (!preg_match('/([^:]+):\\s*(.*)/', $line, $m)) { 135 | // Skip to the next header 136 | continue; 137 | } 138 | $key = strtolower(trim($m[1])); 139 | $val = trim($m[2]); 140 | // Deal with the possibility of multiple headers of same name 141 | if (isset($this->headers[$key])) { 142 | if (is_array($this->headers[$key])) { 143 | $this->headers[$key][] = $val; 144 | } else { 145 | $this->headers[$key] = array($this->headers[$key], $val); 146 | } 147 | } else { 148 | $this->headers[$key] = $val; 149 | } 150 | continue; 151 | } 152 | // We're not in the headers, so append the line to the contents 153 | $this->content .= $line; 154 | } 155 | fclose($fp); 156 | // If data is compressed, uncompress it 157 | if (isset($this->headers['content-encoding']) && $this->headers['content-encoding'] == 'gzip') { 158 | $this->debug('Content is gzip encoded, unzipping it'); 159 | $this->content = substr($this->content, 10); // See http://www.php.net/manual/en/function.gzencode.php 160 | $this->content = gzinflate($this->content); 161 | } 162 | // If $persist_cookies, deal with any cookies 163 | if ($this->persist_cookies && isset($this->headers['set-cookie']) && $this->host == $this->cookie_host) { 164 | $cookies = $this->headers['set-cookie']; 165 | if (!is_array($cookies)) { 166 | $cookies = array($cookies); 167 | } 168 | foreach ($cookies as $cookie) { 169 | if (preg_match('/([^=]+)=([^;]+);/', $cookie, $m)) { 170 | $this->cookies[$m[1]] = $m[2]; 171 | } 172 | } 173 | // Record domain of cookies for security reasons 174 | $this->cookie_host = $this->host; 175 | } 176 | // If $persist_referers, set the referer ready for the next request 177 | if ($this->persist_referers) { 178 | $this->debug('Persisting referer: '.$this->getRequestURL()); 179 | $this->referer = $this->getRequestURL(); 180 | } 181 | // Finally, if handle_redirects and a redirect is sent, do that 182 | if ($this->handle_redirects) { 183 | if (++$this->redirect_count >= $this->max_redirects) { 184 | $this->errormsg = 'Number of redirects exceeded maximum ('.$this->max_redirects.')'; 185 | $this->debug($this->errormsg); 186 | $this->redirect_count = 0; 187 | return false; 188 | } 189 | $location = isset($this->headers['location']) ? $this->headers['location'] : ''; 190 | $uri = isset($this->headers['uri']) ? $this->headers['uri'] : ''; 191 | if ($location || $uri) { 192 | $url = parse_url($location.$uri); 193 | // This will FAIL if redirect is to a different site 194 | return $this->get($url['path']); 195 | } 196 | } 197 | return true; 198 | } 199 | function buildRequest() { 200 | $headers = array(); 201 | $headers[] = "{$this->method} {$this->path} HTTP/1.0"; // Using 1.1 leads to all manner of problems, such as "chunked" encoding 202 | $headers[] = "Host: {$this->host}"; 203 | $headers[] = "User-Agent: {$this->user_agent}"; 204 | $headers[] = "Accept: {$this->accept}"; 205 | if ($this->use_gzip) { 206 | $headers[] = "Accept-encoding: {$this->accept_encoding}"; 207 | } 208 | $headers[] = "Accept-language: {$this->accept_language}"; 209 | if ($this->referer) { 210 | $headers[] = "Referer: {$this->referer}"; 211 | } 212 | // Cookies 213 | if ($this->cookies) { 214 | $cookie = 'Cookie: '; 215 | foreach ($this->cookies as $key => $value) { 216 | $cookie .= "$key=$value; "; 217 | } 218 | $headers[] = $cookie; 219 | } 220 | // Basic authentication 221 | if ($this->username && $this->password) { 222 | $headers[] = 'Authorization: BASIC '.base64_encode($this->username.':'.$this->password); 223 | } 224 | // If this is a POST, set the content type and length 225 | if ($this->postdata) { 226 | $headers[] = 'Content-Type: application/x-www-form-urlencoded'; 227 | $headers[] = 'Content-Length: '.strlen($this->postdata); 228 | } 229 | $request = implode("\r\n", $headers)."\r\n\r\n".$this->postdata; 230 | return $request; 231 | } 232 | function getStatus() { 233 | return $this->status; 234 | } 235 | function getContent() { 236 | return $this->content; 237 | } 238 | function getHeaders() { 239 | return $this->headers; 240 | } 241 | function getHeader($header) { 242 | $header = strtolower($header); 243 | if (isset($this->headers[$header])) { 244 | return $this->headers[$header]; 245 | } else { 246 | return false; 247 | } 248 | } 249 | function getError() { 250 | return $this->errormsg; 251 | } 252 | function getCookies() { 253 | return $this->cookies; 254 | } 255 | function getRequestURL() { 256 | $url = 'http://'.$this->host; 257 | if ($this->port != 80) { 258 | $url .= ':'.$this->port; 259 | } 260 | $url .= $this->path; 261 | return $url; 262 | } 263 | // Setter methods 264 | function setUserAgent($string) { 265 | $this->user_agent = $string; 266 | } 267 | function setAuthorization($username, $password) { 268 | $this->username = $username; 269 | $this->password = $password; 270 | } 271 | function setCookies($array) { 272 | $this->cookies = $array; 273 | } 274 | // Option setting methods 275 | function useGzip($boolean) { 276 | $this->use_gzip = $boolean; 277 | } 278 | function setPersistCookies($boolean) { 279 | $this->persist_cookies = $boolean; 280 | } 281 | function setPersistReferers($boolean) { 282 | $this->persist_referers = $boolean; 283 | } 284 | function setHandleRedirects($boolean) { 285 | $this->handle_redirects = $boolean; 286 | } 287 | function setMaxRedirects($num) { 288 | $this->max_redirects = $num; 289 | } 290 | function setHeadersOnly($boolean) { 291 | $this->headers_only = $boolean; 292 | } 293 | function setDebug($boolean) { 294 | $this->debug = $boolean; 295 | } 296 | // "Quick" static methods 297 | public static function quickGet($url , $cacert_url) { 298 | $curl = curl_init($url); 299 | curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头 300 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1);// 显示输出结果 301 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);//证书认证 302 | //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证 303 | //curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//证书地址 304 | $result = curl_exec($curl); 305 | curl_close($curl); 306 | return $result; 307 | 308 | } 309 | public static function quickPost($url, $data) { 310 | $curl = curl_init($url); 311 | curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);//SSL证书认证 312 | //curl_setopt($curl, CURLOPT_SSL_VERIFYHOST, 2);//严格认证 313 | //curl_setopt($curl, CURLOPT_CAINFO,$cacert_url);//证书地址 314 | curl_setopt($curl, CURLOPT_HEADER, 0 ); // 过滤HTTP头 315 | curl_setopt($curl,CURLOPT_RETURNTRANSFER, 1);// 显示输出结果 316 | curl_setopt($curl,CURLOPT_POST,true); // post传输数据 317 | curl_setopt($curl,CURLOPT_POSTFIELDS,$data);// post传输数据 318 | $responseText = curl_exec($curl); 319 | //var_dump( curl_error($curl) );//如果执行curl过程中出现异常,可打开此开关,以便查看异常内容 320 | curl_close($curl); 321 | 322 | return $responseText; 323 | } 324 | function debug($msg, $object = false) { 325 | if ($this->debug) { 326 | print '
HttpClient Debug: '.$msg; 327 | if ($object) { 328 | ob_start(); 329 | print_r($object); 330 | $content = htmlentities(ob_get_contents()); 331 | ob_end_clean(); 332 | print '
'.$content.'
'; 333 | } 334 | print '
'; 335 | } 336 | } 337 | } 338 | 339 | ?> -------------------------------------------------------------------------------- /pay.php: -------------------------------------------------------------------------------- 1 | getAccessToken(); 18 | $tran_result = $wechat->createOrder($access_token, $params); 19 | if ($tran_result["errmsg"] == 'Success') { 20 | $info['noncestr'] = $wechat->wechat_noncestr; 21 | $info['package'] = 'Sign=WXPay'; 22 | $info['partnerid'] = $wechat_config['partner_id']; 23 | $info['prepayid'] = $tran_result['prepayid']; 24 | $info['timestamp'] = $wechat->wechat_time; 25 | $info['appid'] = $wechat_config['app_id']; 26 | $info['sign'] = $wechat->buildSign($info); 27 | unset($info['appid']); 28 | unset($info['package']); 29 | $info['packageValue'] = 'Sign=WXPay'; 30 | 31 | $info = json_encode($info); 32 | $info = str_replace('null', '""', $info); 33 | header('Content-Type:application/json;charset=utf-8'); 34 | header("Access-Control-Allow-Origin:*"); 35 | exit($info); 36 | } 37 | ?> 38 | -------------------------------------------------------------------------------- /wechatAPISDK.php: -------------------------------------------------------------------------------- 1 | wechat_config = $wechat_config; 16 | } 17 | 18 | /** 19 | * Wechat::buildPackage() 20 | * 生成package 21 | * @param array $parameter 22 | * @return string 23 | */ 24 | public function buildPackage($parameter) { 25 | 26 | $filter = array('bank_type', 'body', 'partner', 'out_trade_no', 'total_fee', 'fee_type', 'notify_url', 'spbill_create_ip', 'input_charset'); 27 | $base = array( 28 | 'notify_url' => $this->wechat_config['notify_url'], 29 | 'bank_type' => 'WX', 30 | 'fee_type' => '1', 31 | 'input_charset' => 'UTF-8', 32 | 'partner' => $this->wechat_config['partner_id'], 33 | ); 34 | $parameter = array_merge($parameter, $base); 35 | $array = array(); 36 | foreach ($parameter as $k => $v) { 37 | if (in_array($k, $filter)) { 38 | $array[$k] = $v; 39 | } 40 | } 41 | ksort($array); 42 | reset($array); 43 | $signPars = ''; 44 | foreach ($array as $k => $v) { 45 | $signPars .= $k."=".$v."&"; 46 | } 47 | $sign = strtoupper(md5($signPars.'key='.$this->wechat_config['partner_key'])); 48 | $signPars = ''; 49 | foreach ($array as $k => $v) { 50 | $signPars .= strtolower($k) . "=" . urlencode($v) . "&"; 51 | } 52 | 53 | return $signPars . 'sign=' . $sign; 54 | } 55 | 56 | /** 57 | * Wechat::getXmlArray() 58 | * 从xml中获取数组 59 | * @return array 60 | */ 61 | public function getXmlArray() { 62 | $xmlData = file_get_contents("php://input"); 63 | if ($xmlData) { 64 | $postObj = simplexml_load_string($xmlData, 'SimpleXMLElement', LIBXML_NOCDATA); 65 | if (! is_object($postObj)) { 66 | return false; 67 | } 68 | $array = json_decode(json_encode($postObj), true); // xml对象转数组 69 | return array_change_key_case($array, CASE_LOWER); // 所有键小写 70 | } else { 71 | return false; 72 | } 73 | } 74 | 75 | /** 76 | * Wechat::verifyNotify() 77 | * 验证服务器通知 78 | * @param array $data 79 | * @return array 80 | */ 81 | public function verifyNotify($data) { 82 | $xml = $this->getXmlArray(); 83 | if (! $xml || ! $data) { 84 | return false; 85 | } 86 | $AppSignature = $xml['appsignature']; 87 | unset($xml['signmethod'], $xml['appsignature']); 88 | $sign = $this->buildSign($xml); 89 | if ($AppSignature != $sign) { 90 | return false; 91 | } elseif ($data['trade_state'] != 0) { 92 | return false; 93 | } 94 | 95 | return $xml; 96 | } 97 | 98 | /** 99 | * Wechat::buildSign() 100 | * 生成sign值 101 | * @param array $array 102 | * @return string 103 | */ 104 | public function buildSign($array) { 105 | $signPars = ""; 106 | $array['appkey'] = $this->wechat_config['pay_sign_key']; 107 | ksort($array); 108 | reset($array); 109 | foreach($array as $k => $v) { 110 | $signPars.=$k."=".$v."&"; 111 | } 112 | $signPars = rtrim($signPars, '&'); // 去除最后一个&符号 113 | $sign = sha1($signPars); 114 | return $sign; 115 | 116 | } 117 | 118 | /** 119 | * wechat::getAccessToken() 120 | * 获取access_token 121 | * @return string 122 | */ 123 | public function getAccessToken() { 124 | $request = HttpClient::quickGet('https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=' . $this->wechat_config['app_id'] . '&secret=' . $this->wechat_config['app_secret'] , $this->wechat_config['cacert_url']); 125 | $requestArray = json_decode($request, true); 126 | if (isset($requestArray['errcode'])) { 127 | return false; 128 | } 129 | $accessToken = $requestArray['access_token']; 130 | return $accessToken; 131 | } 132 | 133 | /** 134 | * Wechat::createorder() 135 | * 生成预支付订单 136 | * @param array $access_token 137 | * @param array $parameter 138 | * @return array 139 | */ 140 | public function createOrder($access_token , $parameter) { 141 | $url = 'https://api.weixin.qq.com/pay/genprepay?access_token='.$access_token; 142 | $params = array( 143 | 'appid' => $this->wechat_config['app_id'], 144 | 'traceid'=>'', 145 | 'noncestr' => uniqid(), 146 | 'package' => $this->buildPackage($parameter), 147 | 'timestamp' => time(), 148 | ); 149 | //用于之后的手机唤起 sign 150 | $this->wechat_noncestr = $params['noncestr']; 151 | $this->wechat_time = $params['timestamp']; 152 | 153 | $params['app_signature'] = $this->buildSign($params); 154 | $params['sign_method'] = 'sha1'; 155 | $result = HttpClient::quickPost($url, json_encode($params)); 156 | return json_decode($result, true); 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /wechat_notify.php: -------------------------------------------------------------------------------- 1 | verifyNotify($notify_info); // 验证通知 14 | if ($verify_info !== false) { 15 | echo 'success'; 16 | } else { 17 | echo 'fail'; 18 | } 19 | ?> 20 | -------------------------------------------------------------------------------- /wechatconfig.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /【微信APP支付】接口文档V1.2_For_Android.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/gofunbox/wechat_app_pay/HEAD/【微信APP支付】接口文档V1.2_For_Android.pdf --------------------------------------------------------------------------------