| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236 |
- <?php
- namespace app\common\services\wechatApiV3;
- use app\common\services\Utils;
- use Ixudra\Curl\Facades\Curl;
- class ApiV3Request
- {
- /**
- * @var ApiV3Config
- */
- public $config;
- private $url;
- private $method;
- private $timestamp;
- private $params;
- private $nonceStr;
- private $certificationType = 'WECHATPAY2-SHA256-RSA2048';
- public $accept_language; //应答语种
- private $encrypt; //含有加密数据
- public function __construct(ApiV3Config $config)
- {
- $this->config = $config;
- }
- /**
- * @return string[]
- * @throws \Exception
- */
- private function header():array
- {
- $headers = [
- 'Accept-Language' => $this->accept_language,
- 'Content-Type' => 'application/json',
- 'Accept' => 'application/json',
- 'User-Agent' => $_SERVER['HTTP_USER_AGENT'] ? : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/101.0.0.0 Safari/537.36',
- ];
- if ($this->encrypt) {//加密数据需传递证书序列号
- $headers['Wechatpay-Serial'] = $this->config->platformSerialNo();
- }
- return $headers;
- }
- private function authorization()
- {
- $token = sprintf('mchid="%s",nonce_str="%s",timestamp="%d",serial_no="%s",signature="%s"',
- $this->config->mchId(),
- $this->nonceStr,
- $this->timestamp,
- $this->config->apiSerialNo(),
- $this->signature($this->method,$this->canonicalUrl(),$this->timestamp,$this->nonceStr,$this->getBody())
- );
- return $this->certificationType . ' ' . $token;
- }
- private function signature($method,$canonicalUrl,$timestamp,$nonce,$body)
- {
- $message = $method."\n".
- $canonicalUrl."\n".
- $timestamp."\n".
- $nonce."\n".
- $body."\n";;
- openssl_sign($message, $raw_sign, $this->config->privateKey(), 'sha256WithRSAEncryption');
- $sign = base64_encode($raw_sign);
- return $sign;
- }
- private function canonicalUrl()
- {
- $url_parts = parse_url($this->url);
- return $url_parts['path'] . (!empty($url_parts['query']) ? "?${url_parts['query']}" : "");
- }
- private function getBody()
- {
- return $this->params ? json_encode($this->params) : '';
- }
- private function createNonceStr($length = 32)
- {
- $str = '1234567890abcdefghijklmnopqrstuvwxyz';
- $result = '';
- for ($i = 1;$i <= $length;$i++) {
- $result .= substr($str,rand(0,(strlen($str)-1)),1);
- }
- $this->nonceStr = strtoupper($result);
- }
- /**
- * @param $url //请求url
- * @param $params //请求参数(有些GET请求参数需要在url拼上的请勿传到此参数,自行拼接到url)
- * @param $request_method //请求方式
- * @param $is_encrypt //是否有加密数据
- * @param $verify_sign //是否验签,验签可能存在获取平台证书错误,造成记录更新错误,所以自行根据接口重要程度判断要不要对返回数据进行验签
- * @param $accept_language //应答语言类型,默认中文
- * @return array
- * @throws \Exception
- */
- public function httpRequest($url,$params = [],$request_method = 'GET',$is_encrypt = false,$verify_sign = 0,$accept_language = 'zh-CN')
- {
- $this->url = $url;
- $this->params = $params;
- $this->method = $request_method;
- $this->encrypt = $is_encrypt;
- $this->accept_language = $accept_language;
- $this->timestamp = time();
- $this->createNonceStr();
- $method = "send" . ucfirst(strtolower($this->method));
- if (!method_exists($this,$method)) {
- throw new \Exception('method not found');
- }
- $res = $this->$method($this->url,$this->params,$this->authorization(),$this->header());
- $res_header = $res->headers;
- $res_content = $res->content;
- if (ApiV3Status::isCheckSign(($res_content['code']?:'')) && $verify_sign) {
- $this->verifySign($res_header['Wechatpay-Signature'],$res_header['Wechatpay-Timestamp'],$res_header['Wechatpay-Nonce'],$res_content,$res_header['Wechatpay-Serial']);
- }
- //处理统一返回体
- return [
- 'request_id' => $res_header['Request-ID'], //唯一请求ID
- 'http_code' => $res->status, //请求状态码
- 'code' => ApiV3Status::returnCode($res->status), //状态
- 'message' => $res_content['message'] ? : '', //错误消息
- 'data' => $res_content //应答消息体
- ];
- }
- /**
- * @param $url
- * @param $params
- * @param $header
- * @return array|mixed|\stdClass
- */
- private function sendGet($url,$params,$authorization,$header)
- {
- return Curl::to($url)
- ->withHeaders($header)
- ->withAuthorization($authorization)
- ->withData($params)
- ->asJson(true)
- ->withResponseHeaders()
- ->returnResponseObject()
- ->get();
- }
- /**
- * @param $url
- * @param $params
- * @param $header
- * @return array|mixed|\stdClass
- */
- private function sendPost($url,$params,$authorization,$header)
- {
- return Curl::to($url)
- ->withHeaders($header)
- ->withAuthorization($authorization)
- ->withData($params)
- ->asJson(true)
- ->withResponseHeaders()
- ->returnResponseObject()
- ->post();
- }
- /**
- * @param $path
- * @param $file_name
- * @return bool
- * @throws \Exception
- */
- public function platformCertApply($path,$file_name)
- {
- $this->url = 'https://api.mch.weixin.qq.com/v3/certificates';
- $this->params = [];
- $this->method = 'GET';
- $this->timestamp = time();
- $this->createNonceStr();
- $res = $this->sendGet($this->url,[],$this->authorization(),$this->header());
- $res_content = $res->content;
- if (!ApiV3Status::returnCode($res->status)) {
- throw new \Exception($res_content['message'] ? : '平台证书请求错误!');
- }
- $content = $res_content['data'];
- $data = $this->config->encrypt()->decrypt(
- $content[0]['encrypt_certificate']['associated_data'],
- $content[0]['encrypt_certificate']['nonce'],
- $content[0]['encrypt_certificate']['ciphertext']);
- Utils::mkdirs($path);
- if (!$data || !file_put_contents($path . $file_name,$data)) {
- throw new \Exception('平台证书更新保存失败!');
- }
- return true;
- }
- /**
- * 回调及应答签名验证
- * @param $sign
- * @param $timestamp
- * @param $nonce
- * @param $body
- * @param $serial
- * @return bool
- * @throws \Exception
- */
- public function verifySign($sign,$timestamp,$nonce,$body,$serial)
- {
- if ($serial != $this->config->platformSerialNo()) {
- //平台证书需更新
- $this->config->platformCert(1);
- }
- $body = $body ? json_encode($body,256) : '';
- $message = $timestamp . "\n"
- .$nonce . "\n"
- .$body . "\n";
- if (!openssl_verify($message,base64_decode($sign),$this->config->platformCertKey(), OPENSSL_ALGO_SHA256)) {
- \Log::debug('微信支付apiV3--应答签名验证失败',[$sign,$timestamp,$nonce,$body,$serial]);
- throw new \Exception('应答签名验证失败');
- }
- return true;
- }
- }
|