diff --git a/.gitignore b/.gitignore index 016b7e8..e487a21 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,3 @@ /.idea/php.xml +vendor/ +composer.lock diff --git a/composer.json b/composer.json index d193ced..2da4093 100644 --- a/composer.json +++ b/composer.json @@ -12,11 +12,15 @@ } ], "autoload": { + "ext-json": false, "psr-4": { "wchat\\": "wx" } }, "require": { "php": ">= 7.0" + }, + "require-dev": { + "swoole/ide-helper": "@dev" } } diff --git a/test.php b/test.php index 9425b29..541402a 100644 --- a/test.php +++ b/test.php @@ -8,8 +8,22 @@ spl_autoload_register(function ($className) { - include __DIR__ . '/wx/' . str_replace('wchat\\', '', $className) . '.php'; + include __DIR__ . '/wx/' . str_replace('wchat\\', '', $className) . '.php'; }); -$data = \wchat\Recharge::call( __DIR__.'/config.php'); -var_dump($data); \ No newline at end of file + +$config = new \wchat\Config(); +$config->setAppid(''); +$config->setAppsecret(''); +$config->setMchId(''); +$config->setKey(''); +$config->setRemoteAddr(''); +$config-> + + +$instance = \wchat\Wx::getMiniProGaRamPage(); +$instance->setConfig($config); + +$recharge = $instance->getRecharge(); +$recharge->cashWithdrawal(1, 'xxx', 'ooo'); + diff --git a/wx/Account.php b/wx/Account.php index c56c563..3513e1a 100644 --- a/wx/Account.php +++ b/wx/Account.php @@ -7,28 +7,127 @@ */ namespace wchat; -class Account extends Base +class Account extends Miniprogarampage { + private $wxaqr = 'https://api.weixin.qq.com/cgi-bin/wxaapp/createwxaqrcode?access_token='; + private $getwxacode = 'https://api.weixin.qq.com/wxa/getwxacode?access_token='; + private $getwxacodeunlimit = 'https://api.weixin.qq.com/wxa/getwxacodeunlimit?access_token='; + + private $savePath = __DIR__ . '/../../../'; + + /** + * @param $path + */ + public function setSavePath($path) + { + $this->savePath = $path; + } + /** * @param $code * @return Result */ public function login($code) { - $param = [ - 'sns/jscode2session', - [ - 'appid' => $this->appid, - 'secret' => $this->appsecret, - 'js_code' => $code, - 'grant_type' => 'authorization_code' - ], - null, - ['Content-Type' => 'text/xml'] - ]; - return WxClient::get(...$param); + $param['appid'] = $this->config->getAppid(); + $param['secret'] = $this->config->getAppsecret(); + $param['js_code'] = $code; + $param['grant_type'] = 'authorization_code'; + + $this->request->setMethod(WxClient::GET); + $this->request->addHeader('Content-Type', 'text/xml'); + + return $this->request->get('/sns/jscode2session', $param); } + /** + * @param $path + * @param $width + * @return array|mixed|Result + * @throws \Exception + */ + public function createwxaqrcode($path, $width) + { + $url = $this->wxaqr . $this->getAccessToken(); + + $sendBody['path'] = $path; + $sendBody['width'] = $width; + + $this->request->setMethod(WxClient::POST); + $this->request->setCallback([$this, 'saveByPath']); + + return $this->request->post($url, $sendBody); + } + + + /** + * @param $path + * @param $width + * @param bool $is_hyaline + * @param bool $auto_color + * @param string $line_color + * @return array|mixed|Result + * @throws \Exception + */ + public function getwxacode($path, $width, $is_hyaline = false, $auto_color = false, $line_color = '') + { + $sendBody['path'] = $path; + $sendBody['width'] = $width; + $sendBody['auto_color'] = $auto_color; + $sendBody['is_hyaline'] = $is_hyaline; + if ($auto_color) { + $sendBody['line_color'] = $line_color; + } + + $url = $this->getwxacode . $this->getAccessToken(); + + $this->request->setMethod(WxClient::POST); + $this->request->setCallback([$this, 'saveByPath']); + return $this->request->post($url, $sendBody); + } + + + /** + * @param $path + * @param $width + * @param bool $is_hyaline + * @param bool $auto_color + * @param string $line_color + * @return array|mixed|Result + * @throws \Exception + */ + public function getwxacodeunlimit($path, $width, $is_hyaline = false, $auto_color = false, $line_color = '') + { + $sendBody['path'] = $path; + $sendBody['width'] = $width; + $sendBody['auto_color'] = $auto_color; + $sendBody['is_hyaline'] = $is_hyaline; + if ($auto_color) { + $sendBody['line_color'] = $line_color; + } + + $url = $this->getwxacodeunlimit . $this->getAccessToken(); + + $this->request->setMethod(WxClient::POST); + $this->request->setCallback([$this, 'saveByPath']); + return $this->request->post($url, $sendBody); + } + + /** + * @param mixed $body + * @return string + * @throws \Exception + */ + public function saveByPath($body) + { + if (!is_null($json = json_decode($body))) { + throw new \Exception($json['errmsg'], $json['errcode']); + } + + $push = md5_file($body) . '.png'; + file_put_contents($this->savePath . $push, $this->savePath); + return $this->savePath . $push; + } } diff --git a/wx/Base.php b/wx/Base.php deleted file mode 100644 index e3339e1..0000000 --- a/wx/Base.php +++ /dev/null @@ -1,261 +0,0 @@ -loadConfig($configPath); - return $class; - } - - - /** - * - */ - public function loadConfig($config) - { - if (empty($config)) { - return; - } - if (is_string($config)) { - $config = require_once $config; - } - foreach ($config as $key => $val) { - if (!property_exists($this, $key)) { - continue; - } - $this->$key = $val; - } - } - - /** - * @param $url - * @param array $data - * @param callable|null $callback - * @return Result - * @throws - */ - public function push($url, $data = [], callable $callback = NULL) - { - return WxClient::post($url, $data, $callback); - } - - /** - * @param int $length - * @return string - * - * 随机字符串 - */ - public function random($length = 20) - { - $res = []; - $str = 'abcdefghijklmnopqrstuvwxyz'; - $str .= strtoupper($str) . '1234567890'; - for ($i = 0; $i < $length; $i++) { - $rand = substr($str, rand(0, strlen($str) - 2), 1); - if (empty($rand)) { - $rand = substr($str, strlen($str) - 3, 1); - } - array_push($res, $rand); - } - - return $this->nonce_str = implode($res); - } - - - /** - * @return bool|mixed|string - * @throws \Exception - */ - protected function getAccessToken() - { - $data = WxClient::get('https://api.weixin.qq.com/cgi-bin/token', [ - 'grant_type' => 'client_credential', - 'appid' => $this->appid, - 'secret' => $this->appsecret, - ]); - - if (!$data->isResultsOK()) { - throw new \Exception($data->getMessage()); - } - return $data->getData('access_token'); - } - - /** - * @param $data - * @return mixed - * @throws \Exception - */ - protected function buildResult($data, $body = NULL) - { - $data = $this->checkSign($data); - if (!$data) { - $return['code'] = -1; - $return['message'] = '签名错误.'; - } else { - if (isset($data['return_code'])) { - if ($data['return_code'] == 'FAIL') { - $return['code'] = -1; - $return['message'] = $data['return_msg']; - } else { - $return['code'] = 0; - $return['data'] = $data; - $return['data']['postBody'] = $body; - } - } else { - if ($data['errcode'] == 'FAIL') { - $return['code'] = -1; - $return['message'] = $data['errmsg']; - } else { - $return['code'] = 0; - $return['data'] = $data; - $return['data']['postBody'] = $body; - } - } - } - return $return; - } - - /** - * @param $result - * @return mixed - * @throws \Exception - */ - protected function checkSign($result) - { - $data = Help::toArray($result); - - if (!isset($data['sign'])) { - return $data; - } - - $sign = $data['sign']; - - unset($data['sign']); - - $_sign = Help::sign($data, $this->key, $this->sign_type); - if ($sign != $_sign) { - return FALSE; - } - return $data; - } - -} diff --git a/wx/Config.php b/wx/Config.php new file mode 100644 index 0000000..5d143d7 --- /dev/null +++ b/wx/Config.php @@ -0,0 +1,382 @@ +remote_addr = $remote_addr; + } + + /** + * @param string $appid + */ + public function setAppid(string $appid) + { + $this->appid = $appid; + } + + /** + * @param string $mch_id + */ + public function setMchId(string $mch_id) + { + $this->mch_id = $mch_id; + } + + /** + * @param string $device_info + */ + public function setDeviceInfo(string $device_info) + { + $this->device_info = $device_info; + } + + /** + * @param string $nonce_str + */ + public function setNonceStr(string $nonce_str) + { + $this->nonce_str = $nonce_str; + } + + /** + * @param string $body + */ + public function setBody(string $body) + { + $this->body = $body; + } + + /** + * @param string $out_trade_no + */ + public function setOutTradeNo(string $out_trade_no) + { + $this->out_trade_no = $out_trade_no; + } + + /** + * @param int $total_fee + */ + public function setTotalFee(int $total_fee) + { + $this->total_fee = $total_fee; + } + + /** + * @param string $spbill_create_ip + */ + public function setSpbillCreateIp(string $spbill_create_ip) + { + $this->spbill_create_ip = $spbill_create_ip; + } + + /** + * @param string $notify_url + */ + public function setNotifyUrl(string $notify_url) + { + $this->notify_url = $notify_url; + } + + /** + * @param string $trade_type + */ + public function setTradeType(string $trade_type) + { + $this->trade_type = $trade_type; + } + + /** + * @param string $sign_type + */ + public function setSignType(string $sign_type) + { + $this->sign_type = $sign_type; + } + + /** + * @param string $mch_host + */ + public function setMchHost(string $mch_host) + { + $this->mch_host = $mch_host; + } + + /** + * @param string $appsecret + */ + public function setAppsecret(string $appsecret) + { + $this->appsecret = $appsecret; + } + + /** + * @param string $ssl_cert + */ + public function setSslCert(string $ssl_cert) + { + $this->ssl_cert = $ssl_cert; + } + + /** + * @param string $ssl_key + */ + public function setSslKey(string $ssl_key) + { + $this->ssl_key = $ssl_key; + } + + /** + * @param string $key + */ + public function setKey(string $key) + { + $this->key = $key; + } + + /** + * @return string + */ + public function getAppid(): string + { + return $this->appid; + } + + /** + * @return string + */ + public function getMchId(): string + { + return $this->mch_id; + } + + /** + * @return string + */ + public function getDeviceInfo(): string + { + return $this->device_info; + } + + /** + * @return string + */ + public function getNonceStr(): string + { + return $this->nonce_str; + } + + /** + * @return string + */ + public function getBody(): string + { + return $this->body; + } + + /** + * @return string + */ + public function getOutTradeNo(): string + { + return $this->out_trade_no; + } + + /** + * @return int + */ + public function getTotalFee(): int + { + return $this->total_fee; + } + + /** + * @return string + */ + public function getSpbillCreateIp(): string + { + return $this->spbill_create_ip; + } + + /** + * @return string + */ + public function getNotifyUrl(): string + { + return $this->notify_url; + } + + /** + * @return string + */ + public function getTradeType(): string + { + return $this->trade_type; + } + + /** + * @return string + */ + public function getSignType(): string + { + return $this->sign_type; + } + + /** + * @return string + */ + public function getMchHost(): string + { + return $this->mch_host; + } + + /** + * @return string + */ + public function getAppsecret(): string + { + return $this->appsecret; + } + + /** + * @return string + */ + public function getRemoteAddr(): string + { + return $this->remote_addr; + } + + /** + * @return string + */ + public function getSslCert(): string + { + return $this->ssl_cert; + } + + /** + * @return string + */ + public function getSslKey(): string + { + return $this->ssl_key; + } + + /** + * @return string + */ + public function getKey(): string + { + return $this->key; + } + +} diff --git a/wx/Help.php b/wx/Help.php index 2bf97b5..7290e76 100644 --- a/wx/Help.php +++ b/wx/Help.php @@ -4,7 +4,7 @@ namespace wchat; -class Help extends Base +class Help extends Miniprogarampage { public static $OK = 0; public static $IllegalAesKey = -41001; @@ -12,52 +12,43 @@ class Help extends Base public static $IllegalBuffer = -41003; public static $DecodeBase64Error = -41004; - public static function d($code) - { - $messages = [ - static::$OK => '', - static::$IllegalAesKey => '', - static::$IllegalIv => '', - static::$IllegalBuffer => '', - static::$DecodeBase64Error => '', - ]; - return $messages[$code] ?? static::$DecodeBase64Error; - } - /** * @param $encryptedData * @param $iv * @param $sessionKey - * @param $data - * @param null $appId * @return int + * @throws + * + * *
  • -41001: encodingAesKey 非法
  • + *
  • -41003: aes 解密失败
  • + *
  • -41004: 解密后得到的buffer非法
  • + *
  • -41005: base64加密失败
  • + *
  • -41016: base64解密失败
  • */ - public static function decode($encryptedData, $iv, $sessionKey, &$data, $appId = null) + public static function decode($encryptedData, $iv, $sessionKey) { + $config = Wx::getMiniProGaRamPage()->getConfig(); if (strlen($sessionKey) != 24) { - return self::$IllegalAesKey; + throw new \Exception('encodingAesKey 非法', self::$IllegalAesKey); } - flush(); $aesKey = base64_decode($sessionKey); if (strlen($iv) != 24) { - return self::$IllegalIv; + throw new \Exception('base64解密失败', self::$IllegalIv); } $aesIV = base64_decode($iv); $aesCipher = base64_decode($encryptedData); - $result = openssl_decrypt($aesCipher, "AES-128-CBC", $aesKey, OPENSSL_RAW_DATA, $aesIV); if ($result === false) { - return self::$IllegalBuffer; + throw new \Exception('aes 解密失败', self::$IllegalBuffer); } $dataObj = json_decode($result); - if ($dataObj->watermark->appid != $appId) { - return self::$IllegalBuffer; + if ($dataObj->watermark->appid != $config->getAppid()) { + throw new \Exception('aes 解密失败', self::$IllegalBuffer); } - $data = $dataObj; - return self::$OK; + return $dataObj; } @@ -97,6 +88,28 @@ class Help extends Base } + /** + * @param int $length + * @return string + * + * 随机字符串 + */ + public static function random($length = 20) + { + $res = []; + $str = 'abcdefghijklmnopqrstuvwxyz'; + $str .= strtoupper($str) . '1234567890'; + for ($i = 0; $i < $length; $i++) { + $rand = substr($str, rand(0, strlen($str) - 2), 1); + if (empty($rand)) { + $rand = substr($str, strlen($str) - 3, 1); + } + array_push($res, $rand); + } + + return implode($res); + } + /** * @param array $array * @param $key diff --git a/wx/Message.php b/wx/Message.php new file mode 100644 index 0000000..05e73f5 --- /dev/null +++ b/wx/Message.php @@ -0,0 +1,289 @@ +type = $type; + } + + /** + * @param string $openid + */ + public function setOpenid(string $openid) + { + $this->openid = $openid; + $this->msgData['touser'] = $openid; + } + + /** + * @param string $token + */ + public function setToken(string $token) + { + $this->token = $token; + } + + + /** + * @param string $content + * @return Result + * @throws \Exception + */ + public function sendTextNews(string $content) + { + $this->msgData['msgtype'] = 'text'; + $this->msgData['text[content]'] = $content; + + return $this->sendKefuMsg(); + } + + /** + * @param $media_id + * @return Result + * @throws \Exception + */ + public function sendImageNews(string $media_id) + { + $this->msgData['msgtype'] = 'image'; + $this->msgData['image[media_id]'] = $media_id; + + return $this->sendKefuMsg(); + } + + + /** + * @param $media_id + * @return Result + * @throws \Exception + */ + public function sendVoiceNews(string $media_id) + { + $this->msgData['msgtype'] = 'voice'; + $this->msgData['voice[media_id]'] = $media_id; + + return $this->sendKefuMsg(); + } + + /** + * @param $media_id + * @return Result + * @throws \Exception + */ + public function sendMpNewsNews(string $media_id) + { + $this->msgData['msgtype'] = 'mpnews'; + $this->msgData['mpnews[media_id]'] = $media_id; + + return $this->sendKefuMsg(); + } + + + /** + * @param string $title + * @param string $description + * @param string $url + * @param string $picurl + * @return Result + * @throws \Exception + */ + public function sendNewsNews(string $title, string $description, string $url, string $picurl) + { + $this->msgData['msgtype'] = 'news'; + $this->msgData['news[articles][0][title]'] = $title; + $this->msgData['news[articles][0][description]'] = $description; + $this->msgData['news[articles][0][url]'] = $url; + $this->msgData['news[articles][0][picurl]'] = $picurl; + + return $this->sendKefuMsg(); + } + + + /** + * @param string $title + * @return Result + * @throws \Exception + */ + public function sendCardNews(string $title) + { + $this->msgData['msgtype'] = 'wxcard'; + $this->msgData['wxcard[card_id]'] = $title; + + return $this->sendKefuMsg(); + } + + + /** + * @param string $head_content + * @param string $tail_content + * @param array $menus + * @return Result + * @throws \Exception + */ + public function sendMenuNews(string $head_content, string $tail_content, array $menus = []) + { + $this->msgData['msgtype'] = 'msgmenu'; + $this->msgData['msgmenu[head_content]'] = $head_content; + $this->msgData['msgmenu[tail_content]'] = $tail_content; + + if (empty($menus) || !is_array($menus) || count($menus) < 2) { + throw new \Exception('菜单选项必须有2个'); + } + + foreach ($menus as $key => $val) { + $this->addNewsMenu($val['id'], $val['name']); + } + + return $this->sendKefuMsg(); + } + + private $index = 0; + + /** + * @param $id + * @param $menuName + * @return $this + */ + public function addNewsMenu($id, $menuName) + { + $this->msgData['msgmenu[list][' . $this->index . '][id]'] = $id; + $this->msgData['msgmenu[list][' . $this->index . '][content]'] = $menuName; + + ++$this->index; + + return $this; + } + + /** + * @param $title + * @param $appid + * @param $pagepath + * @param $thumb_media_id + * @return Result + * @throws \Exception + */ + public function sendMiniprogrampageNews(string $title, string $appid, string $pagepath, string $thumb_media_id) + { + $this->msgData['msgtype'] = 'msgmenu'; + $this->msgData['miniprogrampage[title]'] = $title; + $this->msgData['miniprogrampage[appid]'] = $appid; + $this->msgData['miniprogrampage[pagepath]'] = $pagepath; + $this->msgData['miniprogrampage[thumb_media_id]'] = $thumb_media_id; + + return $this->sendKefuMsg(); + } + + /** + * @param string $filePath + * @param string $type + * @param bool $isPermanent + * @param string $title + * @param string $introduction + * @return mixed + * @throws \Exception + */ + public function upload(string $filePath, string $type, $isPermanent = false, string $title = '', string $introduction = '') + { + if (!file_exists($filePath)) { + throw new \Exception('文件不存在'); + } + + if (!in_array($type, ['image', 'voice', 'video', 'thumb'])) { + throw new \Exception('暂不支持的文件类型'); + } + + if ($isPermanent) { + $url = "https://api.weixin.qq.com/cgi-bin/material/add_material?access_token={$this->token}&type={$type}"; + } else { + $url = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token={$this->token}&type={$type}"; + } + + $mime = mime_content_type($filePath); + + $real_path = new \CURLFile(realpath($filePath)); + + $data = array("media" => $real_path, 'form-data[filename]' => $filePath, 'form-data[content-type]' => $mime); + if ($isPermanent && $mime == 'video/mp3') { + $data = ['media' => $real_path, 'description[title]' => $title, 'description[introduction]' => $introduction]; + } + + $this->request->setMethod(WxClient::POST); + + /** @var Result $body */ + $data = $this->request->post($url, $data); + if (!$data->isResultsOK()) { + throw new \Exception($data->getMessage()); + } + + return $data->getData(); + } + + /** + * @param $mime + * @throws \Exception + */ + private function checkExtinfo($mime) + { + switch (strtolower($mime)) { + case 'image/bmp': + case 'image/png': + case 'image/jpeg': + case 'image/jpg': + case 'image/gif': + break; + case 'mp3/wma/wav/amr': + break; + case 'mp4'; + break; + case 'jpg'; + break; + default: + throw new \Exception('不支持的文件格式'); + } + } + + /** + * @param $data + * @return Result + * @throws \Exception + */ + private function sendKefuMsg() + { + $data = json_encode($this->msgData, JSON_UNESCAPED_UNICODE); + + $url = '/cgi-bin/message/custom/send?access_token=' . $this->token; + $this->request->setMethod(WxClient::POST); + + /** @var Result $body */ + $body = $this->request->post($url, $data); + + if (!$body->isResultsOK()) { + throw new \Exception($body->getMessage()); + } + return $body; + } +} diff --git a/wx/Miniprogarampage.php b/wx/Miniprogarampage.php new file mode 100644 index 0000000..9ac5ec1 --- /dev/null +++ b/wx/Miniprogarampage.php @@ -0,0 +1,129 @@ +request instanceof WxClient)) { + $this->request = new WxClient(); + } + $this->request->setIsSSL(true); + } + + /** + * @param Config $config + * @return mixed + */ + public static function getInstance(Config $config) + { + if (static::$instance === null) { + static::$instance = new static(); + } + static::$instance->config = $config; + return static::$instance; + } + + + /** + * @return bool|mixed|string + * @throws \Exception + */ + protected function getAccessToken() + { + $this->request->setMethod(WxClient::GET); + $data = $this->request->get('/cgi-bin/token', [ + 'grant_type' => 'client_credential', + 'appid' => $this->config->getAppid(), + 'secret' => $this->config->getAppsecret(), + ]); + if (!$data->isResultsOK()) { + throw new \Exception($data->getMessage()); + } + return $data->getData('access_token'); + } + + /** + * @param $data + * @param $body + * @return mixed + * @throws \Exception + */ + protected function buildResult($data, $body = NULL) + { + $data = $this->checkSign($data); + if (!$data) { + $return['code'] = -1; + $return['message'] = '签名错误.'; + } else { + if (isset($data['return_code'])) { + if ($data['return_code'] == 'FAIL') { + $return['code'] = -1; + $return['message'] = $data['return_msg']; + } else { + $return['code'] = 0; + $return['data'] = $data; + $return['data']['postBody'] = $body; + } + } else { + if ($data['errcode'] == 'FAIL') { + $return['code'] = -1; + $return['message'] = $data['errmsg']; + } else { + $return['code'] = 0; + $return['data'] = $data; + $return['data']['postBody'] = $body; + } + } + } + return $return; + } + + /** + * @param $result + * @return mixed + * @throws \Exception + */ + protected function checkSign($result) + { + $data = Help::toArray($result); + + if (!isset($data['sign'])) { + return $data; + } + + $sign = $data['sign']; + + unset($data['sign']); + + $key = $this->config->getKey(); + $sign_type = $this->config->getSignType(); + + $_sign = Help::sign($data, $key, $sign_type); + if ($sign != $_sign) { + return FALSE; + } + return $data; + } + +} diff --git a/wx/Recharge.php b/wx/Recharge.php index 9605985..4084dd8 100644 --- a/wx/Recharge.php +++ b/wx/Recharge.php @@ -8,24 +8,25 @@ namespace wchat; -class Recharge extends Base +class Recharge extends Miniprogarampage { - - /** @var Recharge */ - private static $recharge; - private $money = 0; private $orderNo; private $data = []; + private $transfers = 'https://api.mch.weixin.qq.com/mmpaymkttransfers/promotion/transfers'; + private $unifiedorder = 'https://api.mch.weixin.qq.com/pay/unifiedorder'; + /** * @param int $money * @param string $orderNo - * @return bool|Result + * @param string $openId + * @return array|mixed|Result + * @throws */ - public function payment(int $money, string $orderNo, $openId = NULL) + public function recharge(int $money, string $orderNo, $openId = '') { if ($money < 0) { return new Result(['code' => 500, 'message' => '充值金额不能小于0.']); @@ -34,14 +35,8 @@ class Recharge extends Base $this->orderNo = $orderNo; $this->data['openid'] = $openId; - $params = [ - $this->mch_host . '/pay/unifiedorder', - $this->builder(), - [$this, 'payCallback'], - ['Content-Type' => 'text/xml'] - ]; - - return WxClient::post(...$params); + $this->request->setCallback([$this, 'payCallback']); + return $this->send($this->unifiedorder, $this->builder()); } @@ -56,20 +51,19 @@ class Recharge extends Base if (isset($data['sign'])) { $sign = $data['sign']; unset($data['sign']); - $_sign = Help::sign($data, $this->key, $this->sign_type); } $return = []; + $_sign = Help::sign($data, $this->config->getKey(), $this->config->getSignType()); if (!isset($sign) || $sign != $_sign) { $return['code'] = -1; $return['message'] = $data['return_msg'] ?? '返回数据签名验证失败'; } else { + $return['code'] = 0; + $return['data'] = $data; + $return['data']['postBody'] = $body; if ($data['return_code'] == 'FAIL') { $return['code'] = -1; $return['message'] = $data['return_msg']; - } else { - $return['code'] = 0; - $return['data'] = $data; - $return['data']['postBody'] = $body; } } return $return; @@ -82,22 +76,24 @@ class Recharge extends Base protected function builder() { $data = [ - 'appid' => $this->appid, - 'mch_id' => $this->mch_id, - 'nonce_str' => $this->random(32), - 'body' => $this->body, + 'appid' => $this->config->getAppid(), + 'mch_id' => $this->config->getMchId(), + 'nonce_str' => Help::random(32), + 'body' => $this->config->getBody(), 'out_trade_no' => $this->orderNo, 'total_fee' => $this->money, - 'sign_type' => $this->sign_type, + 'sign_type' => $this->config->getSignType(), 'spbill_create_ip' => $_SERVER['REMOTE_ADDR'], - 'notify_url' => $this->notify_url, - 'trade_type' => $this->trade_type, + 'notify_url' => $this->config->getNotifyUrl(), + 'trade_type' => $this->config->getTradeType(), ]; $data = array_merge($data, $this->data); - $data['sign'] = Help::sign($data, $this->key, $this->sign_type); + $key = $this->config->getKey(); + $sign_type = $this->config->getSignType(); + $data['sign'] = Help::sign($data, $key, $sign_type); return Help::toXml($data); } @@ -105,38 +101,47 @@ class Recharge extends Base * @param $money * @param $openid * @param $order - * @param $REMOTE_ADDR + * @param $desc * @return Result * @throws * * 提现 */ - public function tx($money, $openid, $order, $REMOTE_ADDR, $desc = NULL) + public function cashWithdrawal($money, $openid, $order, $desc = '零钱提现') { $array = [ - 'nonce_str' => $this->random(32), + 'nonce_str' => Help::random(32), 'partner_trade_no' => $order, - 'mchid' => $this->mch_id, - 'mch_appid' => $this->appid, + 'mchid' => $this->config->getMchId(), + 'mch_appid' => $this->config->getAppid(), 'openid' => $openid, 'check_name' => 'NO_CHECK', 'amount' => $money * 100, - 'spbill_create_ip' => $REMOTE_ADDR, - 'desc' => $desc ?? '有大佬给你发红包啦 . ', + 'spbill_create_ip' => $this->config->getRemoteAddr(), + 'desc' => $desc, ]; - $array['sign'] = Help::sign($array, $this->key, $this->sign_type); - $prams = [ - $this->mch_host . '/mmpaymkttransfers/promotion/transfers', - Help::toXml($array), - [$this, 'txCallback'], - NULL, - [$this->ssl_cert, $this->ssl_key] - ]; + $key = $this->config->getKey(); + $sign_type = $this->config->getSignType(); + $array['sign'] = Help::sign($array, $key, $sign_type); - return WxClient::post(...$prams); + $this->request->setCallback([$this, 'txCallback']); + return $this->send($this->transfers, Help::toXml($array)); } + /** + * @param $url + * @param $data + * @return array|mixed|Result + * @throws \Exception + */ + private function send($url, $data) + { + $this->request->setIsSSL(true); + $this->request->setMethod(WxClient::POST); + $this->request->addHeader('Content-Type', 'text/xml'); + return $this->request->send($url, $data); + } /** * @param $data diff --git a/wx/Template.php b/wx/Template.php index 9011c40..5872687 100644 --- a/wx/Template.php +++ b/wx/Template.php @@ -8,7 +8,7 @@ namespace wchat; -class Template extends Base +class Template extends Miniprogarampage { private $keywords = []; @@ -21,23 +21,6 @@ class Template extends Base private $sendUrl = 'https://api.weixin.qq.com/cgi-bin/message/wxopen/template/send'; - /** @var Template $instance */ - private static $instance = null; - - private function __construct() - { - } - - /** - * @return Template - */ - public static function getInstance() - { - if (static::$instance === null) { - static::$instance = new Template(); - } - return static::$instance; - } /** * @param array $keywords @@ -150,8 +133,12 @@ class Template extends Base $params['emphasis_keyword'] = $this->emphasis_keyword; } - $header = ['content-type' => 'application/json']; - return WxClient::post($url, $params, NULL, $header) - ->append('postBody', $params); + $this->request->setIsSSL(true); + $this->request->addHeader('content-type', 'application/json'); + + $result = $this->request->post($url, $params); + $result->append('postBody', $params); + + return $result; } } diff --git a/wx/Wx.php b/wx/Wx.php new file mode 100644 index 0000000..a416761 --- /dev/null +++ b/wx/Wx.php @@ -0,0 +1,77 @@ +config; + } + + + /** + * @param Config $config + * @return $this + */ + public function setConfig(Config $config) + { + $this->config = $config; + return $this; + } + + /** + * @return Template + */ + public function getTemplate() + { + return Template::getInstance($this->config); + } + + /** + * @return Account + */ + public function getAccount() + { + return Account::getInstance($this->config); + } + + /** + * @return Message + */ + public function getMessage() + { + return Message::getInstance($this->config); + } + + /** + * @return Recharge + */ + public function getRecharge() + { + return Recharge::getInstance($this->config); + } +} diff --git a/wx/WxClient.php b/wx/WxClient.php index 504bf77..52a53e2 100644 --- a/wx/WxClient.php +++ b/wx/WxClient.php @@ -6,137 +6,230 @@ use Swoole\Coroutine\Http\Client; class WxClient { - private $url = 'api.weixin.qq.com'; + private $host = 'api.weixin.qq.com'; private $header = []; + private $callback = null; + private $method = 'get'; + + private $url = ''; + private $isSSL = false; + + const POST = 'post'; + const GET = 'get'; + const PUT = 'put'; + const DELETE = 'delete'; + const OPTIONS = 'option'; + + /** + * @param string $host + */ + public function setHost(string $host) + { + $this->host = $host; + } + + /** + * @param array $header + */ + public function setHeader(array $header) + { + $this->header = $header; + } + + /** + * @param $key + * @param $value + */ + public function addHeader($key, $value) + { + $this->header[$key] = $value; + } + + /** + * @param null $callback + */ + public function setCallback($callback) + { + $this->callback = $callback; + } + + /** + * @param string $method + */ + public function setMethod(string $method) + { + $this->method = $method; + } + + /** + * @param string $url + */ + public function setUrl(string $url) + { + $this->url = $url; + } + + /** + * @param bool $isSSL + */ + public function setIsSSL(bool $isSSL) + { + $this->isSSL = $isSSL; + if ($this->isSSL) { + $ssl = Wx::getMiniProGaRamPage()->getConfig(); + $this->header['ssl_cert_file'] = $ssl->getSslCert(); + $this->header['ssl_key_file'] = $ssl->getSslKey(); + } + } + + /** * @param $url - * @param string $pushType * @param array $data - * @param callable|NULL $callback - * @param bool $isSSL * @return array|mixed|Result * @throws \Exception */ - private function request($url, $pushType = 'get', $data = [], callable $callback = NULL, $isSSL = FALSE) + private function request($url, $data = []) { if ( strpos($url, 'http://') === 0 || strpos($url, 'https://') === 0 ) { - return $this->curl($url, $pushType, $data, $callback, $isSSL); + return $this->curl($url, $data); } if (function_exists('getIsCli') && getIsCli()) { - return $this->coroutine($url, $pushType, $data, $callback, $isSSL); + return $this->coroutine($url, $data); } - $url = 'https://' . $this->url . '/' . $url; - - return $this->curl($url, $pushType, $data, $callback, $isSSL); + if ($this->isSSL) { + return $this->curl('https://' . $this->host . '/' . $url, $data); + } else { + return $this->curl('http://' . $this->host . '/' . $url, $data); + } } /** * @param $url - * @param string $type * @param array $data - * @param callable|NULL $callback - * @param bool $isSSL * @return array|mixed|Result * @throws \Exception * * 使用swoole协程方式请求 */ - private function coroutine($url, $type = 'get', $data = [], callable $callback = NULL, $isSSL = FALSE) + private function coroutine($url, $data = []) { $_data = $this->paramEncode($data); - if ($type == 'get' && is_array($_data)) { + if ($this->method == 'get' && is_array($_data)) { $url .= '?' . http_build_query($_data); } - $host = \Co::getAddrInfo($this->url); - - $clientInfo = [array_shift($host), 443, TRUE]; - - if ($isSSL && is_array($isSSL)) { - $this->header['ssl_cert_file'] = $isSSL[0]; - $this->header['ssl_key_file'] = $isSSL[1]; - } - - $cli = new Client(...$clientInfo); - if (!empty($this->header)) { - $cli->setHeaders($this->header); - } - strtolower($type) == 'get' ? $cli->get($url) : $cli->post($url, $data); - - if ($cli->statusCode < 0) { + $client = $this->getClient($this->getHostPort(), $url, $data); + if ($client->statusCode < 0) { throw new \Exception('连接错误!'); } - $body = $cli->body; - $cli->close(); - return $this->build($body, $callback, $_data); + $body = $client->body; + $client->close(); + + return $this->structure($body, $_data); + } + + /** + * @return mixed + */ + private function getHostIp() + { + return array_shift(\Co::getAddrInfo($this->host)); + } + + /** + * @return int + */ + private function getHostPort() + { + $port = 80; + if ($this->isSSL) $port = 443; + return $port; + } + + /** + * @param $host + * @param $port + * @param $url + * @param $data + * @return Client + */ + private function getClient($port, $url, $data) + { + $host = $this->getHostIp(); + + $client = new Client($host, $port, $this->isSSL); + if (!empty($this->header)) { + $client->setHeaders($this->header); + } + + switch (strtolower($this->method)) { + case self::POST: + $client->post($url, $data); + break; + default: + $client->get($url); + } + return $client; } /** * @param $url - * @param string $type * @param array $data - * @param callable|NULL $callback - * @param bool $isSSL * @return array|mixed|Result */ - private function curl($url, $type = 'get', $data = [], callable $callback = NULL, $isSSL = FALSE) + private function curl($url, $data = []) { - $_data = $this->paramEncode($data); - if (is_array($_data)) $_data = http_build_query($_data); + $data = $this->paramEncode($data, self::POST); + $ch = $this->structureCurlRequest($url, $data); - if ($type == 'get') $url .= '?' . $_data; - - $ch = $this->buildCurl($url, $isSSL); - switch (strtolower($type)) { - case 'post': - curl_setopt($ch, CURLOPT_POST, 1); - curl_setopt($ch, CURLOPT_POSTFIELDS, $_data); - break; - case 'delete': - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'DELETE'); - curl_setopt($ch, CURLOPT_POSTFIELDS, $_data); - break; - case 'put': - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'PUT'); - curl_setopt($ch, CURLOPT_POSTFIELDS, $_data); - break; - default: - curl_setopt($ch, CURLOPT_CUSTOMREQUEST, 'GET'); + if ($this->method != self::GET) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $data); + } else if ($this->method == self::POST) { + curl_setopt($ch, CURLOPT_POST, 1); } + + curl_setopt($ch, CURLOPT_CUSTOMREQUEST, strtoupper($this->method)); $output = curl_exec($ch); if ($output === FALSE) { return new Result(['code' => 500, 'message' => curl_error($ch)]); } curl_close($ch); - return $this->build($output, $callback, $_data); + return $this->structure($output, $data); } /** * @param $url - * @param $isSSL + * @param $_data * @return resource */ - private function buildCurl($url, $isSSL) + private function structureCurlRequest($url, $_data) { $ch = curl_init(); + if ($this->method == self::GET) { + $url = $url . '?' . $_data; + } + curl_setopt($ch, CURLOPT_URL, $url); curl_setopt($ch, CURLOPT_TIMEOUT, 5);// 超时设置 curl_setopt($ch, CURLOPT_HEADER, FALSE); if (!empty($this->header)) { curl_setopt($ch, CURLOPT_HTTPHEADER, $this->header); } - if ($isSSL && is_array($isSSL)) { - curl_setopt($ch, CURLOPT_SSLCERT, $isSSL[0]); - curl_setopt($ch, CURLOPT_SSLKEY, $isSSL[1]); + + if ($this->isSSL) { + curl_setopt($ch, CURLOPT_SSLCERT, $this->header['ssl_cert_file']); + curl_setopt($ch, CURLOPT_SSLKEY, $this->header['ssl_key_file']); } + curl_setopt($ch, CURLOPT_NOBODY, FALSE); curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 1);// 超时设置 curl_setopt($ch, CURLOPT_RETURNTRANSFER, TRUE);//返回内容 @@ -146,20 +239,45 @@ class WxClient return $ch; } - private function build($body, $callback, $_data) + /** + * @param $body + * @param $_data + * @return array|mixed|Result + * 构建返回体 + */ + private function structure($body, $_data) + { + $this->setIsSSL(false); + $this->setHeaders(null); + + if ($this->callback !== NULL) { + $result = call_user_func($this->callback, $body, $_data); + } else { + $result = $this->formatResponseBody($body); + } + + $this->setCallback(null); + if (!is_array($result)) { + return $result; + } + + return new Result($result); + } + + /** + * @param $body + * @return array|Result + */ + private function formatResponseBody($body) { $result = []; - if ($callback !== NULL) { - return call_user_func($callback, $body, $_data); - } if (is_null($results = json_decode($body, TRUE))) { $data = simplexml_load_string($body, 'SimpleXMLElement', LIBXML_NOCDATA); $results = json_decode(json_encode($data), TRUE); } if (!is_array($results)) { - return new Result(['code' => 505, 'message' => '服务器返回体错误!']); - } - if (isset($results['errcode'])) { + $result = new Result(['code' => 505, 'message' => '服务器返回体错误!']); + } else if (isset($results['errcode'])) { $result['code'] = $results['errcode']; $result['message'] = $results['errmsg']; } else { @@ -167,10 +285,7 @@ class WxClient $result['message'] = 'system success.'; $result['data'] = $results; } - if (!is_array($result)) { - return $result; - } - return new Result($result); + return $result; } /** @@ -196,82 +311,73 @@ class WxClient /** * @param $url * @param array $data - * @param callable|NULL $callback - * @param array|NULL $header - * @param bool $isSSl * @return array|mixed|Result * @throws */ - public static function post($url, $data = [], callable $callback = NULL, array $header = NULL, $isSSl = FALSE) + public function post($url, $data = []) { - static $_class = NULL; - if ($_class == NULL) $_class = new WxClient(); - if (!empty($header)) $_class->setHeaders($header); - return $_class->request($url, 'post', $data, $callback, $isSSl); + $this->setMethod(self::POST); + return $this->request($url, $data); } /** * @param $url * @param array $data - * @param callable|NULL $callback - * @param array|NULL $header - * @param bool $isSSl * @return array|mixed|Result * @throws */ - public static function put($url, $data = [], callable $callback = NULL, array $header = NULL, $isSSl = FALSE) + public function put($url, $data = []) { - static $_class = NULL; - if ($_class == NULL) $_class = new WxClient(); - if (!empty($header)) $_class->setHeaders($header); - return $_class->request($url, 'put', $data, $callback, $isSSl); + $this->setMethod(self::PUT); + return $this->request($url, $data); } /** * @param $url * @param array $data - * @param callable|NULL $callback - * @param array $header * @return array|mixed|Result * @throws */ - public static function get($url, $data = [], callable $callback = NULL, $header = []) + public function get($url, $data = []) { - static $_class = NULL; - if ($_class == NULL) $_class = new WxClient(); - if (!empty($header)) $_class->setHeaders($header); - return $_class->request($url, 'get', $data, $callback); + $this->setMethod(self::GET); + return $this->request($url, $data); } /** * @param $url * @param array $data - * @param array $header * @return array|mixed|Result * @throws \Exception */ - public static function option($url, $data = [], $header = []) + public function option($url, $data = []) { - static $_class = NULL; - if ($_class == NULL) $_class = new WxClient(); - if (!empty($header)) $_class->setHeaders($header); - return $_class->request($url, 'option', $data); + $this->setMethod(self::OPTIONS); + return $this->request($url, $data); } /** * @param $url * @param array $data - * @param array $header * @return array|mixed|Result * @throws \Exception */ - public static function delete($url, $data = [], $header = []) + public function delete($url, $data = []) { - static $_class = NULL; - if ($_class == NULL) $_class = new WxClient(); - if (!empty($header)) $_class->setHeaders($header); - return $_class->request($url, 'delete', $data); + $this->setMethod(self::DELETE); + return $this->request($url, $data); + } + + /** + * @param $url + * @param array $data + * @return array|mixed|Result + * @throws \Exception + */ + public function send($url, $data = []) + { + return $this->request($url, $data); } /**