├── app.js ├── app.json ├── app.wxss ├── lib ├── WxPayPubHelper │ ├── SDKRuntimeException.php │ ├── WxPay.pub.config.php │ └── WxPayPubHelper.php ├── demo.php ├── wechat.php ├── wxBizMsgCrypt.php └── wxapi.php ├── pages ├── index │ ├── index.js │ ├── index.json │ ├── index.wxml │ └── index.wxss └── logs │ ├── logs.js │ ├── logs.json │ ├── logs.wxml │ └── logs.wxss ├── utils └── util.js ├── wx.php └── wxmini.php /app.js: -------------------------------------------------------------------------------- 1 | //app.js 2 | App({ 3 | onLaunch: function() { 4 | //调用API从本地缓存中获取数据 5 | var logs = wx.getStorageSync('logs') || [] 6 | logs.unshift(Date.now()) 7 | wx.setStorageSync('logs', logs) 8 | }, 9 | 10 | getUserInfo: function(cb) { 11 | var that = this 12 | if (this.globalData.userInfo) { 13 | typeof cb == "function" && cb(this.globalData.userInfo) 14 | } else { 15 | //调用登录接口 16 | wx.getUserInfo({ 17 | withCredentials: false, 18 | success: function(res) { 19 | that.globalData.userInfo = res.userInfo 20 | typeof cb == "function" && cb(that.globalData.userInfo) 21 | } 22 | }) 23 | } 24 | }, 25 | globalData: { 26 | userInfo: null, 27 | url : 'https://www.example.com/' 28 | } 29 | }) 30 | -------------------------------------------------------------------------------- /app.json: -------------------------------------------------------------------------------- 1 | { 2 | "pages":[ 3 | "pages/index/index", 4 | "pages/logs/logs" 5 | ], 6 | "window":{ 7 | "backgroundTextStyle":"light", 8 | "navigationBarBackgroundColor": "#fff", 9 | "navigationBarTitleText": "小程序支付", 10 | "navigationBarTextStyle":"black" 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /app.wxss: -------------------------------------------------------------------------------- 1 | /**app.wxss**/ 2 | .container { 3 | height: 100%; 4 | display: flex; 5 | flex-direction: column; 6 | align-items: center; 7 | justify-content: space-between; 8 | padding: 180rpx 0; 9 | box-sizing: border-box; 10 | } 11 | -------------------------------------------------------------------------------- /lib/WxPayPubHelper/SDKRuntimeException.php: -------------------------------------------------------------------------------- 1 | getMessage(); 7 | } 8 | } 9 | 10 | ?> -------------------------------------------------------------------------------- /lib/WxPayPubHelper/WxPay.pub.config.php: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /lib/WxPayPubHelper/WxPayPubHelper.php: -------------------------------------------------------------------------------- 1 | $v) 76 | { 77 | if($urlencode) 78 | { 79 | $v = urlencode($v); 80 | } 81 | //$buff .= strtolower($k) . "=" . $v . "&"; 82 | $buff .= $k . "=" . $v . "&"; 83 | } 84 | $reqPar = ''; 85 | if (strlen($buff) > 0) 86 | { 87 | $reqPar = substr($buff, 0, strlen($buff)-1); 88 | } 89 | return $reqPar; 90 | } 91 | 92 | /** 93 | * 作用:生成签名 94 | */ 95 | public function getSign($Obj) 96 | { 97 | foreach ($Obj as $k => $v) 98 | { 99 | $Parameters[$k] = $v; 100 | } 101 | //签名步骤一:按字典序排序参数 102 | ksort($Parameters); 103 | $String = $this->formatBizQueryParaMap($Parameters, false); 104 | //echo '【string1】'.$String.'
'; 105 | //签名步骤二:在string后加入KEY 106 | $String = $String."&key=".WxPayConf_pub::KEY; 107 | //echo "【string2】".$String."
"; 108 | //签名步骤三:MD5加密 109 | $String = md5($String); 110 | //echo "【string3】 ".$String."
"; 111 | //签名步骤四:所有字符转为大写 112 | $result_ = strtoupper($String); 113 | //echo "【result】 ".$result_."
"; 114 | return $result_; 115 | } 116 | 117 | /** 118 | * 作用:array转xml 119 | */ 120 | function arrayToXml($arr) 121 | { 122 | $xml = ""; 123 | foreach ($arr as $key=>$val) 124 | { 125 | if (is_numeric($val)) 126 | { 127 | $xml.="<".$key.">".$val.""; 128 | 129 | } 130 | else 131 | $xml.="<".$key.">"; 132 | } 133 | $xml.=""; 134 | return $xml; 135 | } 136 | 137 | /** 138 | * 作用:将xml转为array 139 | */ 140 | public function xmlToArray($xml) 141 | { 142 | //将XML转为array 143 | $array_data = json_decode(json_encode(simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA)), true); 144 | return $array_data; 145 | } 146 | 147 | /** 148 | * 作用:以post方式提交xml到对应的接口url 149 | */ 150 | public function postXmlCurl($xml,$url,$second=30) 151 | { 152 | //初始化curl 153 | $ch = curl_init(); 154 | //设置超时 155 | curl_setopt($ch, CURLOPT_TIMEOUT, $second); 156 | //这里设置代理,如果有的话 157 | //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8'); 158 | //curl_setopt($ch,CURLOPT_PROXYPORT, 8080); 159 | curl_setopt($ch,CURLOPT_URL, $url); 160 | curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE); 161 | curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE); 162 | //设置header 163 | curl_setopt($ch, CURLOPT_HEADER, FALSE); 164 | //要求结果为字符串且输出到屏幕上 165 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 166 | //post提交方式 167 | curl_setopt($ch, CURLOPT_POST, TRUE); 168 | curl_setopt($ch, CURLOPT_POSTFIELDS, $xml); 169 | //运行curl 170 | $data = curl_exec($ch); 171 | curl_close($ch); 172 | file_put_contents('/tmp/wxmini', print_r($data, 1), FILE_APPEND); 173 | //返回结果 174 | if($data) 175 | { 176 | curl_close($ch); 177 | return $data; 178 | } 179 | else 180 | { 181 | $error = curl_errno($ch); 182 | echo "curl出错,错误码:$error"."
"; 183 | echo "错误原因查询
"; 184 | curl_close($ch); 185 | return false; 186 | } 187 | } 188 | 189 | /** 190 | * 作用:使用证书,以post方式提交xml到对应的接口url 191 | */ 192 | function postXmlSSLCurl($xml,$url,$second=30) 193 | { 194 | $ch = curl_init(); 195 | //超时时间 196 | curl_setopt($ch,CURLOPT_TIMEOUT,$second); 197 | //这里设置代理,如果有的话 198 | //curl_setopt($ch,CURLOPT_PROXY, '8.8.8.8'); 199 | //curl_setopt($ch,CURLOPT_PROXYPORT, 8080); 200 | curl_setopt($ch,CURLOPT_URL, $url); 201 | curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE); 202 | curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE); 203 | //设置header 204 | curl_setopt($ch,CURLOPT_HEADER,FALSE); 205 | //要求结果为字符串且输出到屏幕上 206 | curl_setopt($ch,CURLOPT_RETURNTRANSFER,TRUE); 207 | //设置证书 208 | //使用证书:cert 与 key 分别属于两个.pem文件 209 | //默认格式为PEM,可以注释 210 | curl_setopt($ch,CURLOPT_SSLCERTTYPE,'PEM'); 211 | curl_setopt($ch,CURLOPT_SSLCERT, WxPayConf_pub::SSLCERT_PATH); 212 | //默认格式为PEM,可以注释 213 | curl_setopt($ch,CURLOPT_SSLKEYTYPE,'PEM'); 214 | curl_setopt($ch,CURLOPT_SSLKEY, WxPayConf_pub::SSLKEY_PATH); 215 | //post提交方式 216 | curl_setopt($ch,CURLOPT_POST, true); 217 | curl_setopt($ch,CURLOPT_POSTFIELDS,$xml); 218 | $data = curl_exec($ch); 219 | //返回结果 220 | if($data){ 221 | curl_close($ch); 222 | return $data; 223 | } 224 | else { 225 | $error = curl_errno($ch); 226 | echo "curl出错,错误码:$error"."
"; 227 | echo "错误原因查询
"; 228 | curl_close($ch); 229 | return false; 230 | } 231 | } 232 | 233 | /** 234 | * 作用:打印数组 235 | */ 236 | function printErr($wording='',$err='') 237 | { 238 | print_r('
');
239 | 		echo $wording."
"; 240 | var_dump($err); 241 | print_r('
'); 242 | } 243 | } 244 | 245 | /** 246 | * 请求型接口的基类 247 | */ 248 | class Wxpay_client_pub extends Common_util_pub 249 | { 250 | var $parameters;//请求参数,类型为关联数组 251 | public $response;//微信返回的响应 252 | public $result;//返回参数,类型为关联数组 253 | var $url;//接口链接 254 | var $curl_timeout;//curl超时时间 255 | 256 | /** 257 | * 作用:设置请求参数 258 | */ 259 | function setParameter($parameter, $parameterValue) 260 | { 261 | $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue); 262 | } 263 | 264 | /** 265 | * 作用:设置标配的请求参数,生成签名,生成接口参数xml 266 | */ 267 | function createXml() 268 | { 269 | $this->parameters["appid"] = WxPayConf_pub::APPID;//公众账号ID 270 | $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号 271 | $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串 272 | $this->parameters["sign"] = $this->getSign($this->parameters);//签名 273 | return $this->arrayToXml($this->parameters); 274 | } 275 | 276 | /** 277 | * 作用:post请求xml 278 | */ 279 | function postXml() 280 | { 281 | $xml = $this->createXml(); 282 | $this->response = $this->postXmlCurl($xml,$this->url,$this->curl_timeout); 283 | return $this->response; 284 | } 285 | 286 | /** 287 | * 作用:使用证书post请求xml 288 | */ 289 | function postXmlSSL() 290 | { 291 | $xml = $this->createXml(); 292 | $this->response = $this->postXmlSSLCurl($xml,$this->url,$this->curl_timeout); 293 | return $this->response; 294 | } 295 | 296 | /** 297 | * 作用:获取结果,默认不使用证书 298 | */ 299 | function getResult() 300 | { 301 | $this->postXml(); 302 | $this->result = $this->xmlToArray($this->response); 303 | return $this->result; 304 | } 305 | } 306 | 307 | 308 | /** 309 | * 统一支付接口类 310 | */ 311 | class UnifiedOrder_pub extends Wxpay_client_pub 312 | { 313 | function __construct() 314 | { 315 | //设置接口链接 316 | $this->url = "https://api.mch.weixin.qq.com/pay/unifiedorder"; 317 | //设置curl超时时间 318 | $this->curl_timeout = WxPayConf_pub::CURL_TIMEOUT; 319 | } 320 | 321 | /** 322 | * 生成接口参数xml 323 | */ 324 | function createXml() 325 | { 326 | try 327 | { 328 | //检测必填参数 329 | if($this->parameters["out_trade_no"] == null) 330 | { 331 | throw new SDKRuntimeException("缺少统一支付接口必填参数out_trade_no!"."
"); 332 | }elseif($this->parameters["body"] == null){ 333 | throw new SDKRuntimeException("缺少统一支付接口必填参数body!"."
"); 334 | }elseif ($this->parameters["total_fee"] == null ) { 335 | throw new SDKRuntimeException("缺少统一支付接口必填参数total_fee!"."
"); 336 | }elseif ($this->parameters["notify_url"] == null) { 337 | throw new SDKRuntimeException("缺少统一支付接口必填参数notify_url!"."
"); 338 | }elseif ($this->parameters["trade_type"] == null) { 339 | throw new SDKRuntimeException("缺少统一支付接口必填参数trade_type!"."
"); 340 | }elseif ($this->parameters["trade_type"] == "JSAPI" && 341 | $this->parameters["openid"] == NULL){ 342 | throw new SDKRuntimeException("统一支付接口中,缺少必填参数openid!trade_type为JSAPI时,openid为必填参数!"."
"); 343 | } 344 | $this->parameters["appid"] = WxPayConf_pub::APPID;//公众账号ID 345 | $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号 346 | $this->parameters["spbill_create_ip"] = $_SERVER['REMOTE_ADDR'];//终端ip 347 | $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串 348 | $this->parameters["sign"] = $this->getSign($this->parameters);//签名 349 | return $this->arrayToXml($this->parameters); 350 | }catch (SDKRuntimeException $e) 351 | { 352 | die($e->errorMessage()); 353 | } 354 | } 355 | 356 | /** 357 | * 获取prepay_id 358 | */ 359 | function getPrepayId() 360 | { 361 | $this->postXml(); 362 | $this->result = $this->xmlToArray($this->response); 363 | $prepay_id = $this->result["prepay_id"]; 364 | return $prepay_id; 365 | } 366 | 367 | } 368 | 369 | /** 370 | * 订单查询接口 371 | */ 372 | class OrderQuery_pub extends Wxpay_client_pub 373 | { 374 | function __construct() 375 | { 376 | //设置接口链接 377 | $this->url = "https://api.mch.weixin.qq.com/pay/orderquery"; 378 | //设置curl超时时间 379 | $this->curl_timeout = WxPayConf_pub::CURL_TIMEOUT; 380 | } 381 | 382 | /** 383 | * 生成接口参数xml 384 | */ 385 | function createXml() 386 | { 387 | try 388 | { 389 | //检测必填参数 390 | if($this->parameters["out_trade_no"] == null && 391 | $this->parameters["transaction_id"] == null) 392 | { 393 | throw new SDKRuntimeException("订单查询接口中,out_trade_no、transaction_id至少填一个!"."
"); 394 | } 395 | $this->parameters["appid"] = WxPayConf_pub::APPID;//公众账号ID 396 | $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号 397 | $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串 398 | $this->parameters["sign"] = $this->getSign($this->parameters);//签名 399 | return $this->arrayToXml($this->parameters); 400 | }catch (SDKRuntimeException $e) 401 | { 402 | die($e->errorMessage()); 403 | } 404 | } 405 | 406 | } 407 | 408 | /** 409 | * 退款申请接口 410 | */ 411 | class Refund_pub extends Wxpay_client_pub 412 | { 413 | 414 | function __construct() { 415 | //设置接口链接 416 | $this->url = "https://api.mch.weixin.qq.com/secapi/pay/refund"; 417 | //设置curl超时时间 418 | $this->curl_timeout = WxPayConf_pub::CURL_TIMEOUT; 419 | } 420 | 421 | /** 422 | * 生成接口参数xml 423 | */ 424 | function createXml() 425 | { 426 | try 427 | { 428 | //检测必填参数 429 | if($this->parameters["out_trade_no"] == null && $this->parameters["transaction_id"] == null) { 430 | throw new SDKRuntimeException("退款申请接口中,out_trade_no、transaction_id至少填一个!"."
"); 431 | }elseif($this->parameters["out_refund_no"] == null){ 432 | throw new SDKRuntimeException("退款申请接口中,缺少必填参数out_refund_no!"."
"); 433 | }elseif($this->parameters["total_fee"] == null){ 434 | throw new SDKRuntimeException("退款申请接口中,缺少必填参数total_fee!"."
"); 435 | }elseif($this->parameters["refund_fee"] == null){ 436 | throw new SDKRuntimeException("退款申请接口中,缺少必填参数refund_fee!"."
"); 437 | }elseif($this->parameters["op_user_id"] == null){ 438 | throw new SDKRuntimeException("退款申请接口中,缺少必填参数op_user_id!"."
"); 439 | } 440 | $this->parameters["appid"] = WxPayConf_pub::APPID;//公众账号ID 441 | $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号 442 | $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串 443 | $this->parameters["sign"] = $this->getSign($this->parameters);//签名 444 | return $this->arrayToXml($this->parameters); 445 | }catch (SDKRuntimeException $e) 446 | { 447 | die($e->errorMessage()); 448 | } 449 | } 450 | /** 451 | * 作用:获取结果,使用证书通信 452 | */ 453 | function getResult() 454 | { 455 | $this->postXmlSSL(); 456 | $this->result = $this->xmlToArray($this->response); 457 | return $this->result; 458 | } 459 | 460 | } 461 | 462 | 463 | /** 464 | * 退款查询接口 465 | */ 466 | class RefundQuery_pub extends Wxpay_client_pub 467 | { 468 | 469 | function __construct() { 470 | //设置接口链接 471 | $this->url = "https://api.mch.weixin.qq.com/pay/refundquery"; 472 | //设置curl超时时间 473 | $this->curl_timeout = WxPayConf_pub::CURL_TIMEOUT; 474 | } 475 | 476 | /** 477 | * 生成接口参数xml 478 | */ 479 | function createXml() 480 | { 481 | try 482 | { 483 | if($this->parameters["out_refund_no"] == null && 484 | $this->parameters["out_trade_no"] == null && 485 | $this->parameters["transaction_id"] == null && 486 | $this->parameters["refund_id "] == null) 487 | { 488 | throw new SDKRuntimeException("退款查询接口中,out_refund_no、out_trade_no、transaction_id、refund_id四个参数必填一个!"."
"); 489 | } 490 | $this->parameters["appid"] = WxPayConf_pub::APPID;//公众账号ID 491 | $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号 492 | $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串 493 | $this->parameters["sign"] = $this->getSign($this->parameters);//签名 494 | return $this->arrayToXml($this->parameters); 495 | }catch (SDKRuntimeException $e) 496 | { 497 | die($e->errorMessage()); 498 | } 499 | } 500 | 501 | /** 502 | * 作用:获取结果,使用证书通信 503 | */ 504 | function getResult() 505 | { 506 | $this->postXmlSSL(); 507 | $this->result = $this->xmlToArray($this->response); 508 | return $this->result; 509 | } 510 | 511 | } 512 | 513 | /** 514 | * 对账单接口 515 | */ 516 | class DownloadBill_pub extends Wxpay_client_pub 517 | { 518 | 519 | function __construct() 520 | { 521 | //设置接口链接 522 | $this->url = "https://api.mch.weixin.qq.com/pay/downloadbill"; 523 | //设置curl超时时间 524 | $this->curl_timeout = WxPayConf_pub::CURL_TIMEOUT; 525 | } 526 | 527 | /** 528 | * 生成接口参数xml 529 | */ 530 | function createXml() 531 | { 532 | try 533 | { 534 | if($this->parameters["bill_date"] == null ) 535 | { 536 | throw new SDKRuntimeException("对账单接口中,缺少必填参数bill_date!"."
"); 537 | } 538 | $this->parameters["appid"] = WxPayConf_pub::APPID;//公众账号ID 539 | $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号 540 | $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串 541 | $this->parameters["sign"] = $this->getSign($this->parameters);//签名 542 | return $this->arrayToXml($this->parameters); 543 | }catch (SDKRuntimeException $e) 544 | { 545 | die($e->errorMessage()); 546 | } 547 | } 548 | 549 | /** 550 | * 作用:获取结果,默认不使用证书 551 | */ 552 | function getResult() 553 | { 554 | $this->postXml(); 555 | $this->result = $this->xmlToArray($this->result_xml); 556 | return $this->result; 557 | } 558 | 559 | 560 | 561 | } 562 | 563 | /** 564 | * 短链接转换接口 565 | */ 566 | class ShortUrl_pub extends Wxpay_client_pub 567 | { 568 | function __construct() 569 | { 570 | //设置接口链接 571 | $this->url = "https://api.mch.weixin.qq.com/tools/shorturl"; 572 | //设置curl超时时间 573 | $this->curl_timeout = WxPayConf_pub::CURL_TIMEOUT; 574 | } 575 | 576 | /** 577 | * 生成接口参数xml 578 | */ 579 | function createXml() 580 | { 581 | try 582 | { 583 | if($this->parameters["long_url"] == null ) 584 | { 585 | throw new SDKRuntimeException("短链接转换接口中,缺少必填参数long_url!"."
"); 586 | } 587 | $this->parameters["appid"] = WxPayConf_pub::APPID;//公众账号ID 588 | $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号 589 | $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串 590 | $this->parameters["sign"] = $this->getSign($this->parameters);//签名 591 | return $this->arrayToXml($this->parameters); 592 | }catch (SDKRuntimeException $e) 593 | { 594 | die($e->errorMessage()); 595 | } 596 | } 597 | 598 | /** 599 | * 获取prepay_id 600 | */ 601 | function getShortUrl() 602 | { 603 | $this->postXml(); 604 | $prepay_id = $this->result["short_url"]; 605 | return $prepay_id; 606 | } 607 | 608 | } 609 | 610 | /** 611 | * 响应型接口基类 612 | */ 613 | class Wxpay_server_pub extends Common_util_pub 614 | { 615 | public $data;//接收到的数据,类型为关联数组 616 | var $returnParameters;//返回参数,类型为关联数组 617 | 618 | /** 619 | * 将微信的请求xml转换成关联数组,以方便数据处理 620 | */ 621 | function saveData($xml) 622 | { 623 | $this->data = $this->xmlToArray($xml); 624 | } 625 | 626 | function checkSign() 627 | { 628 | $tmpData = $this->data; 629 | unset($tmpData['sign']); 630 | $sign = $this->getSign($tmpData);//本地签名 631 | if ($this->data['sign'] == $sign) { 632 | return TRUE; 633 | } 634 | return FALSE; 635 | } 636 | 637 | /** 638 | * 获取微信的请求数据 639 | */ 640 | function getData() 641 | { 642 | return $this->data; 643 | } 644 | 645 | /** 646 | * 设置返回微信的xml数据 647 | */ 648 | function setReturnParameter($parameter, $parameterValue) 649 | { 650 | $this->returnParameters[$this->trimString($parameter)] = $this->trimString($parameterValue); 651 | } 652 | 653 | /** 654 | * 生成接口参数xml 655 | */ 656 | function createXml() 657 | { 658 | return $this->arrayToXml($this->returnParameters); 659 | } 660 | 661 | /** 662 | * 将xml数据返回微信 663 | */ 664 | function returnXml() 665 | { 666 | $returnXml = $this->createXml(); 667 | return $returnXml; 668 | } 669 | } 670 | 671 | 672 | /** 673 | * 通用通知接口 674 | */ 675 | class Notify_pub extends Wxpay_server_pub 676 | { 677 | 678 | } 679 | 680 | 681 | 682 | 683 | /** 684 | * 请求商家获取商品信息接口 685 | */ 686 | class NativeCall_pub extends Wxpay_server_pub 687 | { 688 | /** 689 | * 生成接口参数xml 690 | */ 691 | function createXml() 692 | { 693 | if($this->returnParameters["return_code"] == "SUCCESS"){ 694 | $this->returnParameters["appid"] = WxPayConf_pub::APPID;//公众账号ID 695 | $this->returnParameters["mch_id"] = WxPayConf_pub::MCHID;//商户号 696 | $this->returnParameters["nonce_str"] = $this->createNoncestr();//随机字符串 697 | $this->returnParameters["sign"] = $this->getSign($this->returnParameters);//签名 698 | } 699 | return $this->arrayToXml($this->returnParameters); 700 | } 701 | 702 | /** 703 | * 获取product_id 704 | */ 705 | function getProductId() 706 | { 707 | $product_id = $this->data["product_id"]; 708 | return $product_id; 709 | } 710 | 711 | } 712 | 713 | /** 714 | * 静态链接二维码 715 | */ 716 | class NativeLink_pub extends Common_util_pub 717 | { 718 | var $parameters;//静态链接参数 719 | var $url;//静态链接 720 | 721 | function __construct() 722 | { 723 | } 724 | 725 | /** 726 | * 设置参数 727 | */ 728 | function setParameter($parameter, $parameterValue) 729 | { 730 | $this->parameters[$this->trimString($parameter)] = $this->trimString($parameterValue); 731 | } 732 | 733 | /** 734 | * 生成Native支付链接二维码 735 | */ 736 | function createLink() 737 | { 738 | try 739 | { 740 | if($this->parameters["product_id"] == null) 741 | { 742 | throw new SDKRuntimeException("缺少Native支付二维码链接必填参数product_id!"."
"); 743 | } 744 | $this->parameters["appid"] = WxPayConf_pub::APPID;//公众账号ID 745 | $this->parameters["mch_id"] = WxPayConf_pub::MCHID;//商户号 746 | $time_stamp = time(); 747 | $this->parameters["time_stamp"] = "$time_stamp";//时间戳 748 | $this->parameters["nonce_str"] = $this->createNoncestr();//随机字符串 749 | $this->parameters["sign"] = $this->getSign($this->parameters);//签名 750 | $bizString = $this->formatBizQueryParaMap($this->parameters, false); 751 | $this->url = "weixin://wxpay/bizpayurl?".$bizString; 752 | }catch (SDKRuntimeException $e) 753 | { 754 | die($e->errorMessage()); 755 | } 756 | } 757 | 758 | /** 759 | * 返回链接 760 | */ 761 | function getUrl() 762 | { 763 | $this->createLink(); 764 | return $this->url; 765 | } 766 | } 767 | 768 | /** 769 | * JSAPI支付——H5网页端调起支付接口 770 | */ 771 | class JsApi_pub extends Common_util_pub 772 | { 773 | var $code;//code码,用以获取openid 774 | var $openid;//用户的openid 775 | var $parameters;//jsapi参数,格式为json 776 | var $prepay_id;//使用统一支付接口得到的预支付id 777 | var $curl_timeout;//curl超时时间 778 | 779 | function __construct() 780 | { 781 | //设置curl超时时间 782 | $this->curl_timeout = WxPayConf_pub::CURL_TIMEOUT; 783 | } 784 | 785 | /** 786 | * 作用:生成可以获得code的url 787 | */ 788 | function createOauthUrlForCode($redirectUrl) 789 | { 790 | $urlObj["appid"] = WxPayConf_pub::APPID; 791 | $urlObj["redirect_uri"] = urlencode($redirectUrl); 792 | $urlObj["response_type"] = "code"; 793 | $urlObj["scope"] = "snsapi_base"; 794 | $urlObj["state"] = "STATE"."#wechat_redirect"; 795 | $bizString = $this->formatBizQueryParaMap($urlObj, false); 796 | return "https://open.weixin.qq.com/connect/oauth2/authorize?".$bizString; 797 | } 798 | 799 | /** 800 | * 作用:生成可以获得openid的url 801 | */ 802 | function createOauthUrlForOpenid() 803 | { 804 | $urlObj["appid"] = WxPayConf_pub::APPID; 805 | $urlObj["secret"] = WxPayConf_pub::APPSECRET; 806 | $urlObj["code"] = $this->code; 807 | $urlObj["grant_type"] = "authorization_code"; 808 | $bizString = $this->formatBizQueryParaMap($urlObj, false); 809 | return "https://api.weixin.qq.com/sns/oauth2/access_token?".$bizString; 810 | } 811 | 812 | 813 | /** 814 | * 作用:通过curl向微信提交code,以获取openid 815 | */ 816 | function getOpenid() 817 | { 818 | $url = $this->createOauthUrlForOpenid(); 819 | //初始化curl 820 | $ch = curl_init(); 821 | //设置超时 822 | curl_setopt($ch, CURLOPT_TIMEOUT, $this->curl_timeout); 823 | curl_setopt($ch, CURLOPT_URL, $url); 824 | curl_setopt($ch,CURLOPT_SSL_VERIFYPEER,FALSE); 825 | curl_setopt($ch,CURLOPT_SSL_VERIFYHOST,FALSE); 826 | curl_setopt($ch, CURLOPT_HEADER, FALSE); 827 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 828 | //运行curl,结果以jason形式返回 829 | $res = curl_exec($ch); 830 | curl_close($ch); 831 | //取出openid 832 | $data = json_decode($res,true); 833 | $this->openid = $data['openid']; 834 | return $this->openid; 835 | } 836 | 837 | /** 838 | * 作用:设置prepay_id 839 | */ 840 | function setPrepayId($prepayId) 841 | { 842 | $this->prepay_id = $prepayId; 843 | } 844 | 845 | /** 846 | * 作用:设置code 847 | */ 848 | function setCode($code_) 849 | { 850 | $this->code = $code_; 851 | } 852 | 853 | /** 854 | * 作用:设置jsapi的参数 855 | */ 856 | public function getParameters() 857 | { 858 | $jsApiObj["appId"] = WxPayConf_pub::APPID; 859 | $timeStamp = time(); 860 | $jsApiObj["timeStamp"] = "$timeStamp"; 861 | $jsApiObj["nonceStr"] = $this->createNoncestr(); 862 | $jsApiObj["package"] = "prepay_id=$this->prepay_id"; 863 | $jsApiObj["signType"] = "MD5"; 864 | $jsApiObj["paySign"] = $this->getSign($jsApiObj); 865 | $this->parameters = json_encode($jsApiObj); 866 | 867 | return $this->parameters; 868 | } 869 | } 870 | 871 | ?> 872 | -------------------------------------------------------------------------------- /lib/demo.php: -------------------------------------------------------------------------------- 1 | 1407743423"; 12 | 13 | 14 | $pc = new WXBizMsgCrypt($token, $encodingAesKey, $appId); 15 | $encryptMsg = ''; 16 | $errCode = $pc->encryptMsg($text, $timeStamp, $nonce, $encryptMsg); 17 | if ($errCode == 0) { 18 | print("加密后: " . $encryptMsg . "\n"); 19 | } else { 20 | print($errCode . "\n"); 21 | } 22 | 23 | $xml_tree = new DOMDocument(); 24 | $xml_tree->loadXML($encryptMsg); 25 | $array_e = $xml_tree->getElementsByTagName('Encrypt'); 26 | $array_s = $xml_tree->getElementsByTagName('MsgSignature'); 27 | $encrypt = $array_e->item(0)->nodeValue; 28 | $msg_sign = $array_s->item(0)->nodeValue; 29 | 30 | $format = ""; 31 | $from_xml = sprintf($format, $encrypt); 32 | 33 | // 第三方收到公众号平台发送的消息 34 | $msg = ''; 35 | $errCode = $pc->decryptMsg($msg_sign, $timeStamp, $nonce, $from_xml, $msg); 36 | if ($errCode == 0) { 37 | print("解密后: " . $msg . "\n"); 38 | } else { 39 | print($errCode . "\n"); 40 | } 41 | -------------------------------------------------------------------------------- /lib/wechat.php: -------------------------------------------------------------------------------- 1 | signature = $_GET["signature"]; 16 | $this->timestamp = $_GET["timestamp"]; 17 | $this->nonce = $_GET["nonce"]; 18 | $this->token = $token; 19 | $this->msg_signature = $_GET["msg_signature"]; 20 | $this->auth() || exit; 21 | if (strtolower($_SERVER['REQUEST_METHOD']) == 'get') { 22 | echo($_GET['echostr']); 23 | exit; 24 | }else { 25 | //可根据token参数获取相应的加密密钥、AppID等参数 26 | $encodingAESKey = 'D0o8aAIZNfxWJ2EeGmKheadc9Vpe8OZZ8YcbXtLqPUW'; //EncodingAESKey(消息加密密钥) 27 | $appId = 'wx22f0f5085f846137'; //AppID(微信公众号或者小程序ID) 28 | 29 | $this->msgcrypt = new WXBizMsgCrypt($this->token, $encodingAESKey, $appId); 30 | $xml = $GLOBALS["HTTP_RAW_POST_DATA"]; 31 | if(empty($xml)) { 32 | $xml = file_get_contents("php://input"); 33 | } 34 | if(isset($_GET['encrypt_type']) && strtolower($_GET['encrypt_type']) == 'aes' ){ 35 | //有加密信息 36 | $decryptMsg = $this->decryptMsg($xml); 37 | if($decryptMsg === false){ 38 | //TODO: 解密失败 39 | }else{ 40 | $xml = $decryptMsg; 41 | $this->hasAES = true; 42 | } 43 | } 44 | $this->data = (array)simplexml_load_string($xml, 'SimpleXMLElement', LIBXML_NOCDATA); 45 | } 46 | } 47 | 48 | /** 49 | * 作用:产生随机字符串,不长于32位 50 | */ 51 | public function createNoncestr( $length = 32 ) { 52 | $chars = "abcdefghijklmnopqrstuvwxyz0123456789"; 53 | $str =""; 54 | for ( $i = 0; $i < $length; $i++ ) { 55 | $str.= substr($chars, mt_rand(0, strlen($chars)-1), 1); 56 | } 57 | return $str; 58 | } 59 | 60 | /** 61 | * [encryptMsg 加密消息] 62 | * @param [type] $xml [待加密消息XML格式] 63 | * @param [type] $encodingAesKey [43位的encodingAesKey] 64 | * @param [type] $token [token] 65 | * @param [type] $appId [公众号APPID] 66 | * @return [type] [false标识解密失败,否则为加密后的字符串] 67 | */ 68 | private function encryptMsg($xml) { 69 | $timestamp = time(); 70 | $nonce = $this->createNoncestr(9); 71 | $encryptMsg = ''; 72 | $errCode = $this->msgcrypt->encryptMsg($xml, $timestamp, $nonce, $encryptMsg); 73 | if ($errCode == 0) { 74 | return $encryptMsg; 75 | } else { 76 | return false; 77 | } 78 | } 79 | /** 80 | * [decryptMsg 解密消息体] 81 | * @param [type] $encryptMsg [加密的消息体] 82 | * @return [type] [false =>解密失败,否则为解密后的消息] 83 | */ 84 | private function decryptMsg($encryptMsg) { 85 | $xml_tree = new DOMDocument(); 86 | $xml_tree->loadXML($encryptMsg); 87 | $array_e = $xml_tree->getElementsByTagName('Encrypt'); 88 | //$array_s = $xml_tree->getElementsByTagName('MsgSignature'); 89 | $encrypt = $array_e->item(0)->nodeValue; 90 | //$msg_sign = $array_s->item(0)->nodeValue; 91 | 92 | $format = ""; 93 | $from_xml = sprintf($format, $encrypt); 94 | 95 | // 第三方收到公众号平台发送的消息 96 | $msg = ''; 97 | $errCode = $this->msgcrypt->decryptMsg($this->msg_signature, $this->timestamp, $this->nonce, $from_xml, $msg); 98 | if ($errCode == 0) { 99 | return $msg; 100 | } else { 101 | return false; 102 | } 103 | } 104 | 105 | /** 106 | * 获取微信推送的数据 107 | * @return array 转换为数组后的数据 108 | */ 109 | public function request() { 110 | return $this->data; 111 | } 112 | /** 113 | * * 响应微信发送的信息(自动回复) 114 | * @param string $to 接收用户名 115 | * @param string $from 发送者用户名 116 | * @param array $content 回复信息,文本信息为string类型 117 | * @param string $type 消息类型 118 | * @param string $flag 是否新标刚接受到的信息 119 | * @return string XML字符串 120 | */ 121 | public function response($content, $type = 'text', $flag = 0) { 122 | $this->data = array('ToUserName' => $this->data['FromUserName'], 'FromUserName' => $this->data['ToUserName'], 'CreateTime' => time(), 'MsgType' => $type); 123 | /* 添加类型数据 */ 124 | $this->$type($content); 125 | /* 添加状态 */ 126 | $this->data['FuncFlag'] = $flag; 127 | /* 转换数据为XML */ 128 | $xml = new SimpleXMLElement(''); 129 | $this->data2xml($xml, $this->data); 130 | if($this->hasAES){ 131 | exit($this->encryptMsg($xml->asXML())); 132 | }else{ 133 | exit($xml->asXML()); 134 | } 135 | } 136 | /** 137 | * 回复文本信息 138 | * @param string $content 要回复的信息 139 | */ 140 | private function text($content) { 141 | $this->data['Content'] = $content; 142 | } 143 | /** 144 | * 回复音乐信息 145 | * @param string $content 要回复的音乐 146 | */ 147 | private function music($music) { 148 | list($music['Title'], $music['Description'], $music['MusicUrl'], $music['HQMusicUrl']) = $music; 149 | $this->data['Music'] = $music; 150 | } 151 | /** 152 | * 回复图文信息 153 | * @param string $news 要回复的图文内容 154 | */ 155 | private function news($news) { 156 | $articles = array(); 157 | foreach ($news as $key => $value) { 158 | list($articles[$key]['Title'], $articles[$key]['Description'], $articles[$key]['PicUrl'], $articles[$key]['Url']) = $value; 159 | if ($key >= 9) { 160 | break; 161 | }//最多只允许10调新闻 162 | } 163 | $this->data['ArticleCount'] = count($articles); 164 | $this->data['Articles'] = $articles; 165 | } 166 | private function transfer_customer_service($content) { 167 | $this->data['Content'] = ''; 168 | } 169 | private function data2xml($xml, $data, $item = 'item') { 170 | foreach ($data as $key => $value) { 171 | /* 指定默认的数字key */ 172 | is_numeric($key) && $key = $item; 173 | /* 添加子元素 */ 174 | if (is_array($value) || is_object($value)) { 175 | $child = $xml->addChild($key); 176 | $this->data2xml($child, $value, $item); 177 | }else { 178 | if (is_numeric($value)) { 179 | $child = $xml->addChild($key, $value); 180 | }else { 181 | $child = $xml->addChild($key); 182 | $node = dom_import_simplexml($child); 183 | $node->appendChild($node->ownerDocument->createCDATASection($value)); 184 | } 185 | } 186 | } 187 | } 188 | private function auth() { 189 | $tmpArr = array($this->token, $this->timestamp, $this->nonce); 190 | sort($tmpArr, SORT_STRING); 191 | $tmpStr = implode($tmpArr); 192 | $tmpStr = sha1($tmpStr); 193 | if (trim($tmpStr) == trim($this->signature)) { 194 | return true; 195 | }else { 196 | return false; 197 | } 198 | } 199 | } -------------------------------------------------------------------------------- /lib/wxBizMsgCrypt.php: -------------------------------------------------------------------------------- 1 | 5 | *
  • -40001: 签名验证错误
  • 6 | *
  • -40002: xml解析失败
  • 7 | *
  • -40003: sha加密生成签名失败
  • 8 | *
  • -40004: encodingAesKey 非法
  • 9 | *
  • -40005: appid 校验错误
  • 10 | *
  • -40006: aes 加密失败
  • 11 | *
  • -40007: aes 解密失败
  • 12 | *
  • -40008: 解密后得到的buffer非法
  • 13 | *
  • -40009: base64加密失败
  • 14 | *
  • -40010: base64解密失败
  • 15 | *
  • -40011: 生成xml失败
  • 16 | * 17 | */ 18 | class ErrorCode 19 | { 20 | public static $OK = 0; 21 | public static $ValidateSignatureError = -40001; 22 | public static $ParseXmlError = -40002; 23 | public static $ComputeSignatureError = -40003; 24 | public static $IllegalAesKey = -40004; 25 | public static $ValidateAppidError = -40005; 26 | public static $EncryptAESError = -40006; 27 | public static $DecryptAESError = -40007; 28 | public static $IllegalBuffer = -40008; 29 | public static $EncodeBase64Error = -40009; 30 | public static $DecodeBase64Error = -40010; 31 | public static $GenReturnXmlError = -40011; 32 | } 33 | 34 | /** 35 | * SHA1 class 36 | * 37 | * 计算公众平台的消息签名接口. 38 | */ 39 | class SHA1 40 | { 41 | /** 42 | * 用SHA1算法生成安全签名 43 | * @param string $token 票据 44 | * @param string $timestamp 时间戳 45 | * @param string $nonce 随机字符串 46 | * @param string $encrypt 密文消息 47 | */ 48 | public function getSHA1($token, $timestamp, $nonce, $encrypt_msg) 49 | { 50 | //排序 51 | try { 52 | $array = array($encrypt_msg, $token, $timestamp, $nonce); 53 | sort($array, SORT_STRING); 54 | $str = implode($array); 55 | return array(ErrorCode::$OK, sha1($str)); 56 | } catch (Exception $e) { 57 | //print $e . "\n"; 58 | return array(ErrorCode::$ComputeSignatureError, null); 59 | } 60 | } 61 | 62 | } 63 | 64 | /** 65 | * XMLParse class 66 | * 67 | * 提供提取消息格式中的密文及生成回复消息格式的接口. 68 | */ 69 | class XMLParse 70 | { 71 | 72 | /** 73 | * 提取出xml数据包中的加密消息 74 | * @param string $xmltext 待提取的xml字符串 75 | * @return string 提取出的加密消息字符串 76 | */ 77 | public function extract($xmltext) 78 | { 79 | try { 80 | $xml = new DOMDocument(); 81 | $xml->loadXML($xmltext); 82 | $array_e = $xml->getElementsByTagName('Encrypt'); 83 | $array_a = $xml->getElementsByTagName('ToUserName'); 84 | $encrypt = $array_e->item(0)->nodeValue; 85 | $tousername = $array_a->item(0)->nodeValue; 86 | return array(0, $encrypt, $tousername); 87 | } catch (Exception $e) { 88 | //print $e . "\n"; 89 | return array(ErrorCode::$ParseXmlError, null, null); 90 | } 91 | } 92 | 93 | /** 94 | * 生成xml消息 95 | * @param string $encrypt 加密后的消息密文 96 | * @param string $signature 安全签名 97 | * @param string $timestamp 时间戳 98 | * @param string $nonce 随机字符串 99 | */ 100 | public function generate($encrypt, $signature, $timestamp, $nonce) 101 | { 102 | $format = " 103 | 104 | 105 | %s 106 | 107 | "; 108 | return sprintf($format, $encrypt, $signature, $timestamp, $nonce); 109 | } 110 | 111 | } 112 | 113 | 114 | /** 115 | * PKCS7Encoder class 116 | * 117 | * 提供基于PKCS7算法的加解密接口. 118 | */ 119 | class PKCS7Encoder 120 | { 121 | public static $block_size = 32; 122 | 123 | /** 124 | * 对需要加密的明文进行填充补位 125 | * @param $text 需要进行填充补位操作的明文 126 | * @return 补齐明文字符串 127 | */ 128 | function encode($text) 129 | { 130 | $block_size = PKCS7Encoder::$block_size; 131 | $text_length = strlen($text); 132 | //计算需要填充的位数 133 | $amount_to_pad = PKCS7Encoder::$block_size - ($text_length % PKCS7Encoder::$block_size); 134 | if ($amount_to_pad == 0) { 135 | $amount_to_pad = PKCS7Encoder::block_size; 136 | } 137 | //获得补位所用的字符 138 | $pad_chr = chr($amount_to_pad); 139 | $tmp = ""; 140 | for ($index = 0; $index < $amount_to_pad; $index++) { 141 | $tmp .= $pad_chr; 142 | } 143 | return $text . $tmp; 144 | } 145 | 146 | /** 147 | * 对解密后的明文进行补位删除 148 | * @param decrypted 解密后的明文 149 | * @return 删除填充补位后的明文 150 | */ 151 | function decode($text) 152 | { 153 | 154 | $pad = ord(substr($text, -1)); 155 | if ($pad < 1 || $pad > 32) { 156 | $pad = 0; 157 | } 158 | return substr($text, 0, (strlen($text) - $pad)); 159 | } 160 | 161 | } 162 | 163 | /** 164 | * Prpcrypt class 165 | * 166 | * 提供接收和推送给公众平台消息的加解密接口. 167 | */ 168 | class Prpcrypt 169 | { 170 | public $key; 171 | 172 | function Prpcrypt($k) 173 | { 174 | $this->key = base64_decode($k . "="); 175 | } 176 | 177 | /** 178 | * 对明文进行加密 179 | * @param string $text 需要加密的明文 180 | * @return string 加密后的密文 181 | */ 182 | public function encrypt($text, $appid) 183 | { 184 | 185 | try { 186 | //获得16位随机字符串,填充到明文之前 187 | $random = $this->getRandomStr(); 188 | $text = $random . pack("N", strlen($text)) . $text . $appid; 189 | // 网络字节序 190 | $size = mcrypt_get_block_size(MCRYPT_RIJNDAEL_128, MCRYPT_MODE_CBC); 191 | $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); 192 | $iv = substr($this->key, 0, 16); 193 | //使用自定义的填充方式对明文进行补位填充 194 | $pkc_encoder = new PKCS7Encoder; 195 | $text = $pkc_encoder->encode($text); 196 | mcrypt_generic_init($module, $this->key, $iv); 197 | //加密 198 | $encrypted = mcrypt_generic($module, $text); 199 | mcrypt_generic_deinit($module); 200 | mcrypt_module_close($module); 201 | 202 | //print(base64_encode($encrypted)); 203 | //使用BASE64对加密后的字符串进行编码 204 | return array(ErrorCode::$OK, base64_encode($encrypted)); 205 | } catch (Exception $e) { 206 | //print $e; 207 | return array(ErrorCode::$EncryptAESError, null); 208 | } 209 | } 210 | 211 | /** 212 | * 对密文进行解密 213 | * @param string $encrypted 需要解密的密文 214 | * @return string 解密得到的明文 215 | */ 216 | public function decrypt($encrypted, $appid) 217 | { 218 | 219 | try { 220 | //使用BASE64对需要解密的字符串进行解码 221 | $ciphertext_dec = base64_decode($encrypted); 222 | $module = mcrypt_module_open(MCRYPT_RIJNDAEL_128, '', MCRYPT_MODE_CBC, ''); 223 | $iv = substr($this->key, 0, 16); 224 | mcrypt_generic_init($module, $this->key, $iv); 225 | 226 | //解密 227 | $decrypted = mdecrypt_generic($module, $ciphertext_dec); 228 | mcrypt_generic_deinit($module); 229 | mcrypt_module_close($module); 230 | } catch (Exception $e) { 231 | return array(ErrorCode::$DecryptAESError, null); 232 | } 233 | 234 | 235 | try { 236 | //去除补位字符 237 | $pkc_encoder = new PKCS7Encoder; 238 | $result = $pkc_encoder->decode($decrypted); 239 | //去除16位随机字符串,网络字节序和AppId 240 | if (strlen($result) < 16) 241 | return ""; 242 | $content = substr($result, 16, strlen($result)); 243 | $len_list = unpack("N", substr($content, 0, 4)); 244 | $xml_len = $len_list[1]; 245 | $xml_content = substr($content, 4, $xml_len); 246 | $from_appid = substr($content, $xml_len + 4); 247 | } catch (Exception $e) { 248 | //print $e; 249 | return array(ErrorCode::$IllegalBuffer, null); 250 | } 251 | if ($from_appid != $appid) 252 | return array(ErrorCode::$ValidateAppidError, null); 253 | return array(0, $xml_content); 254 | 255 | } 256 | 257 | 258 | /** 259 | * 随机生成16位字符串 260 | * @return string 生成的字符串 261 | */ 262 | function getRandomStr() 263 | { 264 | 265 | $str = ""; 266 | $str_pol = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz"; 267 | $max = strlen($str_pol) - 1; 268 | for ($i = 0; $i < 16; $i++) { 269 | $str .= $str_pol[mt_rand(0, $max)]; 270 | } 271 | return $str; 272 | } 273 | 274 | } 275 | 276 | /** 277 | * 1.第三方回复加密消息给公众平台; 278 | * 2.第三方收到公众平台发送的消息,验证消息的安全性,并对消息进行解密。 279 | */ 280 | class WXBizMsgCrypt 281 | { 282 | private $token; 283 | private $encodingAesKey; 284 | private $appId; 285 | 286 | /** 287 | * 构造函数 288 | * @param $token string 公众平台上,开发者设置的token 289 | * @param $encodingAesKey string 公众平台上,开发者设置的EncodingAESKey 290 | * @param $appId string 公众平台的appId 291 | */ 292 | public function WXBizMsgCrypt($token, $encodingAesKey, $appId) 293 | { 294 | $this->token = $token; 295 | $this->encodingAesKey = $encodingAesKey; 296 | $this->appId = $appId; 297 | } 298 | 299 | /** 300 | * 将公众平台回复用户的消息加密打包. 301 | *
      302 | *
    1. 对要发送的消息进行AES-CBC加密
    2. 303 | *
    3. 生成安全签名
    4. 304 | *
    5. 将消息密文和安全签名打包成xml格式
    6. 305 | *
    306 | * 307 | * @param $replyMsg string 公众平台待回复用户的消息,xml格式的字符串 308 | * @param $timeStamp string 时间戳,可以自己生成,也可以用URL参数的timestamp 309 | * @param $nonce string 随机串,可以自己生成,也可以用URL参数的nonce 310 | * @param &$encryptMsg string 加密后的可以直接回复用户的密文,包括msg_signature, timestamp, nonce, encrypt的xml格式的字符串, 311 | * 当return返回0时有效 312 | * 313 | * @return int 成功0,失败返回对应的错误码 314 | */ 315 | public function encryptMsg($replyMsg, $timeStamp, $nonce, &$encryptMsg) 316 | { 317 | $pc = new Prpcrypt($this->encodingAesKey); 318 | 319 | //加密 320 | $array = $pc->encrypt($replyMsg, $this->appId); 321 | $ret = $array[0]; 322 | if ($ret != 0) { 323 | return $ret; 324 | } 325 | 326 | if ($timeStamp == null) { 327 | $timeStamp = time(); 328 | } 329 | $encrypt = $array[1]; 330 | 331 | //生成安全签名 332 | $sha1 = new SHA1; 333 | $array = $sha1->getSHA1($this->token, $timeStamp, $nonce, $encrypt); 334 | $ret = $array[0]; 335 | if ($ret != 0) { 336 | return $ret; 337 | } 338 | $signature = $array[1]; 339 | 340 | //生成发送的xml 341 | $xmlparse = new XMLParse; 342 | $encryptMsg = $xmlparse->generate($encrypt, $signature, $timeStamp, $nonce); 343 | return ErrorCode::$OK; 344 | } 345 | 346 | 347 | /** 348 | * 检验消息的真实性,并且获取解密后的明文. 349 | *
      350 | *
    1. 利用收到的密文生成安全签名,进行签名验证
    2. 351 | *
    3. 若验证通过,则提取xml中的加密消息
    4. 352 | *
    5. 对消息进行解密
    6. 353 | *
    354 | * 355 | * @param $msgSignature string 签名串,对应URL参数的msg_signature 356 | * @param $timestamp string 时间戳 对应URL参数的timestamp 357 | * @param $nonce string 随机串,对应URL参数的nonce 358 | * @param $postData string 密文,对应POST请求的数据 359 | * @param &$msg string 解密后的原文,当return返回0时有效 360 | * 361 | * @return int 成功0,失败返回对应的错误码 362 | */ 363 | public function decryptMsg($msgSignature, $timestamp = null, $nonce, $postData, &$msg) 364 | { 365 | if (strlen($this->encodingAesKey) != 43) { 366 | return ErrorCode::$IllegalAesKey; 367 | } 368 | 369 | $pc = new Prpcrypt($this->encodingAesKey); 370 | 371 | //提取密文 372 | $xmlparse = new XMLParse; 373 | $array = $xmlparse->extract($postData); 374 | $ret = $array[0]; 375 | 376 | if ($ret != 0) { 377 | return $ret; 378 | } 379 | 380 | if ($timestamp == null) { 381 | $timestamp = time(); 382 | } 383 | 384 | $encrypt = $array[1]; 385 | $touser_name = $array[2]; 386 | 387 | //验证安全签名 388 | $sha1 = new SHA1; 389 | $array = $sha1->getSHA1($this->token, $timestamp, $nonce, $encrypt); 390 | $ret = $array[0]; 391 | 392 | if ($ret != 0) { 393 | return $ret; 394 | } 395 | 396 | $signature = $array[1]; 397 | if ($signature != $msgSignature) { 398 | return ErrorCode::$ValidateSignatureError; 399 | } 400 | 401 | $result = $pc->decrypt($encrypt, $this->appId); 402 | if ($result[0] != 0) { 403 | return $result[0]; 404 | } 405 | $msg = $result[1]; 406 | 407 | return ErrorCode::$OK; 408 | } 409 | 410 | } 411 | 412 | -------------------------------------------------------------------------------- /lib/wxapi.php: -------------------------------------------------------------------------------- 1 | token = trim($arg); 38 | } 39 | } 40 | file_put_contents('/tmp/wxmini', date('Y-m-d H:i:s') . print_r($rt, 1).PHP_EOL, FILE_APPEND); 41 | if (!class_exists('SimpleXMLElement')){ 42 | file_put_contents('/tmp/wxmini', date('Y-m-d H:i:s') . '-SimpleXMLElement class not exist'.PHP_EOL, FILE_APPEND); 43 | } 44 | if (!function_exists('dom_import_simplexml')){ 45 | file_put_contents('/tmp/wxmini', date('Y-m-d H:i:s') . '-dom_import_simplexml function not exist'.PHP_EOL, FILE_APPEND); 46 | } 47 | if(!preg_match("/^[0-9a-zA-Z]{3,42}$/", $this->token)){ 48 | file_put_contents('/tmp/wxmini', date('Y-m-d H:i:s') . '-error token, only support characters and number'.PHP_EOL, FILE_APPEND); 49 | } 50 | $this->weixin = new Wechat($this->token); 51 | $this->data = $this->weixin->request(); 52 | if ($this->data) { 53 | list($content, $type) = $this->reply($this->data); 54 | if($type){ 55 | $this->weixin->response($content, $type); 56 | } 57 | } 58 | } 59 | 60 | private function reply($data){ 61 | file_put_contents('/tmp/wechat', date('Y-m-d H:i:s').'-'.json_encode($data).PHP_EOL, FILE_APPEND); 62 | if (isset($data['Event'])) { 63 | $event = strtolower($data['Event']); 64 | switch ($event) { 65 | case 'subscribe': //不带参数/带参数的二维码 66 | break; 67 | case 'unsubscribe'://取消关注 68 | break; 69 | case 'scan'://带参数的二维码 70 | break; 71 | case 'location'://自动获取位置回复 72 | break; 73 | case 'templatesendjobfinish': 74 | break; 75 | case 'masssendjobfinish': //群发 76 | break; 77 | case 'click'://自定义菜单 78 | break; 79 | default: 80 | break; 81 | } 82 | }else if (isset($data['MsgType'])) { 83 | //消息转发到客服 84 | return array('turn on transfer_customer_service', 'transfer_customer_service'); 85 | } 86 | } 87 | } -------------------------------------------------------------------------------- /pages/index/index.js: -------------------------------------------------------------------------------- 1 | //index.js 2 | //获取应用实例 3 | var app = getApp() 4 | Page({ 5 | data: { 6 | motto: '微信小程序支付', 7 | userInfo: {}, 8 | }, 9 | onLoad: function () {//生命周期函数--监听页面加载 10 | var that = this 11 | //调用应用实例的方法获取全局数据 12 | app.getUserInfo(function (userInfo) { 13 | //更新数据 14 | that.setData({ 15 | userInfo: userInfo 16 | }) 17 | }); 18 | //登陆获取code 19 | var openid = wx.getStorageSync('openid') || ''; 20 | if (!openid) { 21 | wx.login({ 22 | success: function (res) { 23 | console.log(res); 24 | that.getOpenId(res.code); 25 | } 26 | }); 27 | } 28 | }, 29 | onShareAppMessage: function () { 30 | return { 31 | title: '微信小程序支付', 32 | path: 'pages/index/index', 33 | success: function (res) { 34 | // 分享成功 35 | }, 36 | fail: function (res) { 37 | // 分享失败 38 | } 39 | } 40 | }, 41 | //事件处理函数 42 | bindViewTap: function() { 43 | wx.navigateTo({ 44 | url: '../logs/logs' 45 | }) 46 | }, 47 | showInfo: function (msg) { 48 | wx.showModal({ 49 | title: '提示', 50 | showCancel : false, 51 | content: msg 52 | }); 53 | }, 54 | getOpenId: function (code) { 55 | var that = this; 56 | wx.request({ 57 | url: app.globalData.url + 'wxmini.php', 58 | data: { 59 | code: code, 60 | type: 'openid' 61 | }, 62 | header: { 63 | 'content-type': 'application/x-www-form-urlencoded' 64 | }, 65 | method: 'POST', 66 | success: function (res) { 67 | console.log(res); 68 | if (res.data.resultCode != 0) { 69 | that.showInfo(res.data.errMsg); 70 | return; 71 | } 72 | wx.setStorageSync('openid', res.data.openid); 73 | }, 74 | fail: function () { 75 | // fail 76 | }, 77 | complete: function (openid) { 78 | // complete 79 | } 80 | }); 81 | }, 82 | wxpay: function () { 83 | var openid = wx.getStorageSync('openid'); 84 | if(!openid){ 85 | that.showInfo('获取openid信息失败'); 86 | return; 87 | } 88 | this.generateOrder(openid); 89 | }, 90 | generateOrder: function (openid) { 91 | var that = this; 92 | wx.request({ 93 | url: app.globalData.url + 'wxmini.php', 94 | data: { 95 | type: 'pay', 96 | openid : openid 97 | }, 98 | header: { 99 | 'content-type': 'application/x-www-form-urlencoded' 100 | }, 101 | method: 'POST', 102 | success: function (res) { 103 | console.log(res); 104 | if (res.data.resultCode != 0) { 105 | that.showInfo(res.data.errMsg); 106 | return; 107 | } 108 | that.pay(res.data.params); 109 | }, 110 | fail: function () { 111 | // fail 112 | }, 113 | complete: function () { 114 | // complete 115 | } 116 | }) 117 | }, 118 | pay: function (param) { 119 | var that = this; 120 | wx.requestPayment({ 121 | 'timeStamp': param.timestamp, 122 | 'nonceStr': param.nonce_str, 123 | 'package': param.package, 124 | 'signType': param.sign_type, 125 | 'paySign': param.pay_sign, 126 | success: function (res) { 127 | // success 128 | console.log(res); 129 | that.showInfo('支付成功'); 130 | }, 131 | fail: function (res) { 132 | // fail 133 | console.log(res); 134 | var strMsg = res.errMsg; 135 | if (res.err_desc){ 136 | strMsg += ', ' + res.err_desc; 137 | } 138 | that.showInfo(strMsg); 139 | }, 140 | complete: function () { 141 | // complete 142 | console.log("pay complete"); 143 | } 144 | }); 145 | }, 146 | send_temp : function(e){ 147 | var that = this; 148 | var openid = wx.getStorageSync('openid'); 149 | if (!openid) { 150 | that.showInfo('获取openid信息失败'); 151 | return; 152 | } 153 | wx.request({ 154 | url: app.globalData.url + 'wxmini.php', 155 | data: { 156 | 'form_id': e.detail.formId, 157 | 'openid': openid, 158 | type: 'send', 159 | }, 160 | header: { 161 | 'content-type': 'application/x-www-form-urlencoded' 162 | }, 163 | method: 'POST', 164 | success: function(res){ 165 | // success 166 | console.log(res); 167 | if (res.data.resultCode != 0){ 168 | that.showInfo(res.data.errMsg); 169 | return; 170 | } 171 | that.showInfo('发送成功'); 172 | // console.log(e.detail.formId); 173 | }, 174 | fail: function(err) { 175 | // fail 176 | console.log('失败'); 177 | console.log(res); 178 | }, 179 | complete: function() { 180 | // complete 181 | } 182 | }); 183 | } 184 | }) -------------------------------------------------------------------------------- /pages/index/index.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /pages/index/index.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | {{userInfo.nickName}} 6 | 7 | 8 |
    价格:¥0.01元
    9 | 10 |
    11 | 12 | {{motto}} 13 | 14 |
    15 | -------------------------------------------------------------------------------- /pages/index/index.wxss: -------------------------------------------------------------------------------- 1 | /**index.wxss**/ 2 | .userinfo { 3 | display: flex; 4 | flex-direction: column; 5 | align-items: center; 6 | } 7 | 8 | .userinfo-avatar { 9 | width: 128rpx; 10 | height: 128rpx; 11 | margin: 20rpx; 12 | border-radius: 50%; 13 | } 14 | 15 | .demo { 16 | margin-top: 60px; 17 | width: 100px; 18 | } 19 | .demo .price{ 20 | border-bottom:1px dashed #C9C9C9; 21 | padding-bottom:10px; 22 | color:#666666; 23 | font-size:12px; 24 | } 25 | 26 | .demo .price strong{ 27 | font-weight:normal; 28 | color:#EE6209; 29 | font-size:16px; 30 | font-family:Helvetica; 31 | } 32 | 33 | .userinfo-nickname { 34 | color: #aaa; 35 | } 36 | 37 | .usermotto { 38 | margin-top: 120px; 39 | } -------------------------------------------------------------------------------- /pages/logs/logs.js: -------------------------------------------------------------------------------- 1 | //logs.js 2 | var util = require('../../utils/util.js') 3 | Page({ 4 | data: { 5 | logs: [] 6 | }, 7 | onLoad: function () { 8 | this.setData({ 9 | logs: (wx.getStorageSync('logs') || []).map(function (log) { 10 | return util.formatTime(new Date(log)) 11 | }) 12 | }) 13 | } 14 | }) 15 | -------------------------------------------------------------------------------- /pages/logs/logs.json: -------------------------------------------------------------------------------- 1 | { 2 | "navigationBarTitleText": "查看启动日志" 3 | } -------------------------------------------------------------------------------- /pages/logs/logs.wxml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {{index + 1}}. {{log}} 5 | 6 | 7 | -------------------------------------------------------------------------------- /pages/logs/logs.wxss: -------------------------------------------------------------------------------- 1 | .log-list { 2 | display: flex; 3 | flex-direction: column; 4 | padding: 40rpx; 5 | } 6 | .log-item { 7 | margin: 10rpx; 8 | } 9 | -------------------------------------------------------------------------------- /utils/util.js: -------------------------------------------------------------------------------- 1 | function formatTime(date) { 2 | var year = date.getFullYear() 3 | var month = date.getMonth() + 1 4 | var day = date.getDate() 5 | 6 | var hour = date.getHours() 7 | var minute = date.getMinutes() 8 | var second = date.getSeconds() 9 | 10 | 11 | return [year, month, day].map(formatNumber).join('/') + ' ' + [hour, minute, second].map(formatNumber).join(':') 12 | } 13 | 14 | function formatNumber(n) { 15 | n = n.toString() 16 | return n[1] ? n : '0' + n 17 | } 18 | 19 | module.exports = { 20 | formatTime: formatTime 21 | } 22 | -------------------------------------------------------------------------------- /wx.php: -------------------------------------------------------------------------------- 1 | index(); -------------------------------------------------------------------------------- /wxmini.php: -------------------------------------------------------------------------------- 1 | 1); 15 | $type = isset($_POST['type']) ? trim($_POST['type']) : ''; 16 | switch($type){ 17 | case 'openid': 18 | //小程序的appid和appsecret 19 | $appid = 'wx22f0f5085f846137'; 20 | $appsecret = '3e5f684e2948de6f74b487386692940a'; 21 | $code = isset($_POST['code']) ? trim($_POST['code']) : ''; 22 | if(empty($code)){ 23 | $ret['errMsg'] = '登录凭证code获取失败'; 24 | exit(json_encode($ret)); 25 | } 26 | $url = "https://api.weixin.qq.com/sns/jscode2session?appid=$appid&secret=$appsecret&js_code=$code&grant_type=authorization_code"; 27 | 28 | $json = json_decode(file_get_contents($url)); 29 | if(isset($json->errcode) && $json->errcode){ 30 | $ret['errMsg'] = $json->errcode.', '.$json->errmsg; 31 | exit(json_encode($ret)); 32 | } 33 | $openid = $json->openid; 34 | 35 | $ret['resultCode'] = 0; 36 | $ret['openid'] = $openid; 37 | exit(json_encode($ret)); 38 | break; 39 | case 'send': 40 | //小程序的appid和appsecret 41 | $appid = 'wx22f0f5085f846137'; 42 | $appsecret = '3e5f684e2948de6f74b487386692940a'; 43 | $access_token = ''; 44 | 45 | $openid = isset($_POST['openid']) ? trim($_POST['openid']) : ''; //小程序的openid 46 | if(empty($openid)){ 47 | $ret['errMsg'] = '却少参数openid'; 48 | exit(json_encode($ret)); 49 | } 50 | //表单提交场景下,为 submit 事件带上的 formId;支付场景下,为本次支付的 prepay_id 51 | $formid = isset($_POST['form_id']) ? trim($_POST['form_id']) : ''; 52 | if(empty($formid)){ 53 | $ret['errMsg'] = '却少参数form_id'; 54 | exit(json_encode($ret)); 55 | } 56 | 57 | //消息模板id 58 | $temp_id = 'eogEBS2i4VeS2rZfwda7kdkePLTcbmPm-wW1s7A0ky4'; 59 | //获取access_token, 做缓存,expires_in:7200 60 | generate_token($access_token, $appid, $appsecret); 61 | 62 | $data['touser'] = $openid; 63 | $data['template_id'] = $temp_id; 64 | $data['page'] = 'pages/index/index'; //该字段不填则模板无跳转 65 | $data['form_id'] = $formid; 66 | $data['data'] = array( 67 | 'keyword1' => array('value' => '小程序模板消息测试'), 68 | 'keyword2' => array('value' => '100元'), 69 | 'keyword3' => array('value' => '支付成功'), 70 | 'keyword4' => array('value' => time() * 1000), 71 | 'keyword5' => array('value' => '微信支付'), 72 | 'keyword6' => array('value' => date('Y-m-d H:i:s')), 73 | ); 74 | $data['emphasis_keyword'] = 'keyword5.DATA'; //模板需要放大的关键词,不填则默认无放大 75 | 76 | $send_url = 'https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send?access_token=' . $access_token; 77 | $str = request($send_url, 'post', $data); 78 | $json = json_decode(request($send_url, 'post', $data)); 79 | if(!$json){ 80 | $ret['errMsg'] = $str; 81 | exit(json_encode($ret)); 82 | }else if(isset($json->errcode) && $json->errcode){ 83 | $ret['errMsg'] = $json->errcode.', '.$json->errmsg; 84 | exit(json_encode($ret)); 85 | } 86 | $ret['resultCode'] = 0; 87 | exit(json_encode($ret)); 88 | break; 89 | case 'pay': 90 | include_once('lib/WxPayPubHelper/WxPayPubHelper.php'); 91 | 92 | $openid = isset($_POST['openid']) ? trim($_POST['openid']) : ''; 93 | if(empty($openid)){ 94 | $ret['errMsg'] = '缺少参数openid'; 95 | exit(json_encode($ret)); 96 | } 97 | 98 | $order = new UnifiedOrder_pub(); 99 | $order->setParameter("openid", $openid);//商品描述 100 | $order->setParameter('out_trade_no', 'phpdemo' . $timestamp); 101 | $order->setParameter('total_fee', 1); 102 | $order->setParameter('trade_type', 'JSAPI'); 103 | $order->setParameter('body', 'PHP微信小程序支付测试'); 104 | $order->setParameter('notify_url', 'https://www.example.cn/a.php'); 105 | 106 | $prepay_id = $order->getPrepayId(); 107 | $jsApi = new JsApi_pub(); 108 | $jsApi->setPrepayId($prepay_id); 109 | $jsApiParams = json_decode($jsApi->getParameters()); 110 | 111 | $ret['resultCode'] = 0; 112 | $ret['params'] = array( 113 | 'appid' => $jsApiParams->appId, 114 | 'timestamp' => $jsApiParams->timeStamp, 115 | 'nonce_str' => $jsApiParams->nonceStr, 116 | 'sign_type' => $jsApiParams->signType, 117 | 'package' => $jsApiParams->package, 118 | 'pay_sign' => $jsApiParams->paySign, 119 | ); 120 | exit(json_encode($ret)); 121 | break; 122 | default : 123 | $ret['errMsg'] = 'No this type : ' . $type; 124 | exit(json_encode($ret)); 125 | break; 126 | } 127 | 128 | function generate_token(&$access_token, $appid, $appsecret){ 129 | $token_file = '/tmp/token'; 130 | $general_token = true; 131 | if(file_exists($token_file) && ($info = json_decode(file_get_contents($token_file)))){ 132 | if(time() < $info->create_time + $info->expires_in - 200){ 133 | $general_token = false; 134 | $access_token = $info->access_token; 135 | } 136 | } 137 | if($general_token){ 138 | new_access_token($access_token, $token_file, $appid, $appsecret); 139 | } 140 | } 141 | 142 | function new_access_token(&$access_token, $token_file, $appid, $appsecret){ 143 | $token_url = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=$appid&secret=$appsecret"; 144 | $str = file_get_contents($token_url); 145 | $json = json_decode($str); 146 | if(isset($json->access_token)){ 147 | $access_token = $json->access_token; 148 | file_put_contents($token_file, json_encode(array('access_token' => $access_token, 'expires_in' => $json->expires_in, 'create_time' => time()))); 149 | }else{ 150 | file_put_contents('/tmp/error', date('Y-m-d H:i:s').'-Get Access Token Error: '.print_r($json, 1).PHP_EOL, FILE_APPEND); 151 | } 152 | } 153 | 154 | function request($url, $method, array $data, $timeout = 30) { 155 | try { 156 | $ch = curl_init(); 157 | /*支持SSL 不验证CA根验证*/ 158 | curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); 159 | curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, false); 160 | /*重定向跟随*/ 161 | if (ini_get('open_basedir') == '' && !ini_get('safe_mode')) { 162 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true); 163 | } 164 | curl_setopt($ch, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4); 165 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE); 166 | curl_setopt($ch, CURLOPT_TIMEOUT, $timeout); 167 | 168 | //设置 CURLINFO_HEADER_OUT 选项之后 curl_getinfo 函数返回的数组将包含 cURL 169 | //请求的 header 信息。而要看到回应的 header 信息可以在 curl_setopt 中设置 170 | //CURLOPT_HEADER 选项为 true 171 | curl_setopt($ch, CURLOPT_HEADER, false); 172 | curl_setopt($ch, CURLINFO_HEADER_OUT, false); 173 | 174 | //fail the request if the HTTP code returned is equal to or larger than 400 175 | //curl_setopt($ch, CURLOPT_FAILONERROR, true); 176 | $header = array("Content-Type:application/json;charset=utf-8;", "Connection: keep-alive;"); 177 | switch (strtolower($method)) { 178 | case "post": 179 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 180 | curl_setopt($ch, CURLOPT_POST, true); 181 | curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); 182 | curl_setopt($ch, CURLOPT_URL, $url); 183 | break; 184 | case "put": 185 | curl_setopt($ch, CURLOPT_HTTPHEADER, $header); 186 | curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data)); 187 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); 188 | curl_setopt($ch, CURLOPT_URL, $url); 189 | break; 190 | case "delete": 191 | curl_setopt($ch, CURLOPT_URL, $url.'?'.http_build_query($data)); 192 | curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); 193 | break; 194 | case "get": 195 | curl_setopt($ch, CURLOPT_URL, $url.'?'.http_build_query($data)); 196 | break; 197 | case "new_get": 198 | curl_setopt($ch, CURLOPT_URL, $url."?para=".urlencode(json_encode($data))); 199 | break; 200 | default: 201 | throw new Exception('不支持的HTTP方式'); 202 | break; 203 | } 204 | $result = curl_exec($ch); 205 | if (curl_errno($ch) > 0) { 206 | throw new Exception(curl_error($ch)); 207 | } 208 | curl_close($ch); 209 | return $result; 210 | } catch (Exception $e) { 211 | return "CURL EXCEPTION: ".$e->getMessage(); 212 | } 213 | } --------------------------------------------------------------------------------