From 843be054ea5157d43fa69c3132fd82e31aa99d0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9E=97=E4=B8=80=E5=B3=B0?= <1feng.0595@gmail.com> Date: Thu, 10 Aug 2017 18:25:37 +0800 Subject: [PATCH] alipay app --- .env.example | 1 + app/Http/Controllers/Home/IndexController.php | 5 +- config/app.php | 3 +- config/custom.php | 4 +- .../alipay_app/AlipayTradeAppPayRequest.php | 118 ++ resources/org/alipay_app/AopClient.php | 1193 +++++++++++++++++ resources/org/alipay_app/AopEncrypt.php | 73 + resources/org/alipay_app/demo.php | 43 + resources/org/alipay_app/说明.txt | 0 说明.txt | 3 +- 10 files changed, 1437 insertions(+), 6 deletions(-) create mode 100644 resources/org/alipay_app/AlipayTradeAppPayRequest.php create mode 100644 resources/org/alipay_app/AopClient.php create mode 100644 resources/org/alipay_app/AopEncrypt.php create mode 100644 resources/org/alipay_app/demo.php create mode 100644 resources/org/alipay_app/说明.txt diff --git a/.env.example b/.env.example index 70377b9..e896d19 100644 --- a/.env.example +++ b/.env.example @@ -6,6 +6,7 @@ APP_LOG_LEVEL=debug APP_URL=http://localhost APP_DOMAIN=www.lqycms.com APP_SUBDOMAIN=m.lqycms.com +APP_API_URL=http://localhost/dataapi DB_CONNECTION=mysql DB_HOST=127.0.0.1 diff --git a/app/Http/Controllers/Home/IndexController.php b/app/Http/Controllers/Home/IndexController.php index 81beb1e..7ecae98 100644 --- a/app/Http/Controllers/Home/IndexController.php +++ b/app/Http/Controllers/Home/IndexController.php @@ -259,8 +259,9 @@ class IndexController extends CommonController //测试页面 public function test() { - $qrcode = new \SimpleSoftwareIO\QrCode\BaconQrCodeGenerator; - return $qrcode->size(500)->generate('Make a qrcode without Laravel!'); + return base_path('resources/org'); + //$qrcode = new \SimpleSoftwareIO\QrCode\BaconQrCodeGenerator; + //return $qrcode->size(500)->generate('Make a qrcode without Laravel!'); //return ''; //set_exception_handler('myException'); //return uniqid(); diff --git a/config/app.php b/config/app.php index 3cfd63d..f0fb33c 100644 --- a/config/app.php +++ b/config/app.php @@ -230,5 +230,4 @@ return [ 'Excel' => Maatwebsite\Excel\Facades\Excel::class, //Excel导入导出 By FLi 'QrCode' => SimpleSoftwareIO\QrCode\Facades\QrCode::class, //Simple QrCode By FLi ], - -]; +]; \ No newline at end of file diff --git a/config/custom.php b/config/custom.php index 8593a09..47657aa 100644 --- a/config/custom.php +++ b/config/custom.php @@ -9,4 +9,6 @@ return [ "3"=>"三级推荐", "4"=>"四级推荐" ], -]; + + +]; \ No newline at end of file diff --git a/resources/org/alipay_app/AlipayTradeAppPayRequest.php b/resources/org/alipay_app/AlipayTradeAppPayRequest.php new file mode 100644 index 0000000..c8abc34 --- /dev/null +++ b/resources/org/alipay_app/AlipayTradeAppPayRequest.php @@ -0,0 +1,118 @@ +bizContent = $bizContent; + $this->apiParas["biz_content"] = $bizContent; + } + + public function getBizContent() + { + return $this->bizContent; + } + + public function getApiMethodName() + { + return "alipay.trade.app.pay"; + } + + public function setNotifyUrl($notifyUrl) + { + $this->notifyUrl=$notifyUrl; + } + + public function getNotifyUrl() + { + return $this->notifyUrl; + } + + public function setReturnUrl($returnUrl) + { + $this->returnUrl=$returnUrl; + } + + public function getReturnUrl() + { + return $this->returnUrl; + } + + public function getApiParas() + { + return $this->apiParas; + } + + public function getTerminalType() + { + return $this->terminalType; + } + + public function setTerminalType($terminalType) + { + $this->terminalType = $terminalType; + } + + public function getTerminalInfo() + { + return $this->terminalInfo; + } + + public function setTerminalInfo($terminalInfo) + { + $this->terminalInfo = $terminalInfo; + } + + public function getProdCode() + { + return $this->prodCode; + } + + public function setProdCode($prodCode) + { + $this->prodCode = $prodCode; + } + + public function setApiVersion($apiVersion) + { + $this->apiVersion=$apiVersion; + } + + public function getApiVersion() + { + return $this->apiVersion; + } + + public function setNeedEncrypt($needEncrypt) + { + + $this->needEncrypt=$needEncrypt; + + } + + public function getNeedEncrypt() + { + return $this->needEncrypt; + } + +} diff --git a/resources/org/alipay_app/AopClient.php b/resources/org/alipay_app/AopClient.php new file mode 100644 index 0000000..d3f4380 --- /dev/null +++ b/resources/org/alipay_app/AopClient.php @@ -0,0 +1,1193 @@ +sign($this->getSignContent($params), $signType); + } + + public function rsaSign($params, $signType = "RSA") { + return $this->sign($this->getSignContent($params), $signType); + } + + public function getSignContent($params) { + ksort($params); + + $stringToBeSigned = ""; + $i = 0; + foreach ($params as $k => $v) { + if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) { + + // 转换成目标字符集 + $v = $this->characet($v, $this->postCharset); + + if ($i == 0) { + $stringToBeSigned .= "$k" . "=" . "$v"; + } else { + $stringToBeSigned .= "&" . "$k" . "=" . "$v"; + } + $i++; + } + } + + unset ($k, $v); + return $stringToBeSigned; + } + + + //此方法对value做urlencode + public function getSignContentUrlencode($params) { + ksort($params); + + $stringToBeSigned = ""; + $i = 0; + foreach ($params as $k => $v) { + if (false === $this->checkEmpty($v) && "@" != substr($v, 0, 1)) { + + // 转换成目标字符集 + $v = $this->characet($v, $this->postCharset); + + if ($i == 0) { + $stringToBeSigned .= "$k" . "=" . urlencode($v); + } else { + $stringToBeSigned .= "&" . "$k" . "=" . urlencode($v); + } + $i++; + } + } + + unset ($k, $v); + return $stringToBeSigned; + } + + protected function sign($data, $signType = "RSA") { + if($this->checkEmpty($this->rsaPrivateKeyFilePath)){ + $priKey=$this->rsaPrivateKey; + $res = "-----BEGIN RSA PRIVATE KEY-----\n" . + wordwrap($priKey, 64, "\n", true) . + "\n-----END RSA PRIVATE KEY-----"; + }else { + $priKey = file_get_contents($this->rsaPrivateKeyFilePath); + $res = openssl_get_privatekey($priKey); + } + + ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置'); + + if ("RSA2" == $signType) { + openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256); + } else { + openssl_sign($data, $sign, $res); + } + + if(!$this->checkEmpty($this->rsaPrivateKeyFilePath)){ + openssl_free_key($res); + } + $sign = base64_encode($sign); + return $sign; + } + + /** + * RSA单独签名方法,未做字符串处理,字符串处理见getSignContent() + * @param $data 待签名字符串 + * @param $privatekey 商户私钥,根据keyfromfile来判断是读取字符串还是读取文件,false:填写私钥字符串去回车和空格 true:填写私钥文件路径 + * @param $signType 签名方式,RSA:SHA1 RSA2:SHA256 + * @param $keyfromfile 私钥获取方式,读取字符串还是读文件 + * @return string + * @author mengyu.wh + */ + public function alonersaSign($data,$privatekey,$signType = "RSA",$keyfromfile=false) { + + if(!$keyfromfile){ + $priKey=$privatekey; + $res = "-----BEGIN RSA PRIVATE KEY-----\n" . + wordwrap($priKey, 64, "\n", true) . + "\n-----END RSA PRIVATE KEY-----"; + } + else{ + $priKey = file_get_contents($privatekey); + $res = openssl_get_privatekey($priKey); + } + + ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置'); + + if ("RSA2" == $signType) { + openssl_sign($data, $sign, $res, OPENSSL_ALGO_SHA256); + } else { + openssl_sign($data, $sign, $res); + } + + if($keyfromfile){ + openssl_free_key($res); + } + $sign = base64_encode($sign); + return $sign; + } + + + protected function curl($url, $postFields = null) { + $ch = curl_init(); + curl_setopt($ch, CURLOPT_URL, $url); + curl_setopt($ch, CURLOPT_FAILONERROR, false); + curl_setopt($ch, CURLOPT_RETURNTRANSFER, true); + curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false); + + $postBodyString = ""; + $encodeArray = Array(); + $postMultipart = false; + + + if (is_array($postFields) && 0 < count($postFields)) { + + foreach ($postFields as $k => $v) { + if ("@" != substr($v, 0, 1)) //判断是不是文件上传 + { + + $postBodyString .= "$k=" . urlencode($this->characet($v, $this->postCharset)) . "&"; + $encodeArray[$k] = $this->characet($v, $this->postCharset); + } else //文件上传用multipart/form-data,否则用www-form-urlencoded + { + $postMultipart = true; + $encodeArray[$k] = new \CURLFile(substr($v, 1)); + } + + } + unset ($k, $v); + curl_setopt($ch, CURLOPT_POST, true); + if ($postMultipart) { + curl_setopt($ch, CURLOPT_POSTFIELDS, $encodeArray); + } else { + curl_setopt($ch, CURLOPT_POSTFIELDS, substr($postBodyString, 0, -1)); + } + } + + if ($postMultipart) { + + $headers = array('content-type: multipart/form-data;charset=' . $this->postCharset . ';boundary=' . $this->getMillisecond()); + } else { + + $headers = array('content-type: application/x-www-form-urlencoded;charset=' . $this->postCharset); + } + curl_setopt($ch, CURLOPT_HTTPHEADER, $headers); + + + + + $reponse = curl_exec($ch); + + if (curl_errno($ch)) { + + throw new Exception(curl_error($ch), 0); + } else { + $httpStatusCode = curl_getinfo($ch, CURLINFO_HTTP_CODE); + if (200 !== $httpStatusCode) { + throw new Exception($reponse, $httpStatusCode); + } + } + + curl_close($ch); + return $reponse; + } + + protected function getMillisecond() { + list($s1, $s2) = explode(' ', microtime()); + return (float)sprintf('%.0f', (floatval($s1) + floatval($s2)) * 1000); + } + + + protected function logCommunicationError($apiName, $requestUrl, $errorCode, $responseTxt) { + $localIp = isset ($_SERVER["SERVER_ADDR"]) ? $_SERVER["SERVER_ADDR"] : "CLI"; + $logger = new LtLogger; + $logger->conf["log_file"] = rtrim(AOP_SDK_WORK_DIR, '\\/') . '/' . "logs/aop_comm_err_" . $this->appId . "_" . date("Y-m-d") . ".log"; + $logger->conf["separator"] = "^_^"; + $logData = array( + date("Y-m-d H:i:s"), + $apiName, + $this->appId, + $localIp, + PHP_OS, + $this->alipaySdkVersion, + $requestUrl, + $errorCode, + str_replace("\n", "", $responseTxt) + ); + $logger->log($logData); + } + + /** + * 生成用于调用收银台SDK的字符串 + * @param $request SDK接口的请求参数对象 + * @return string + * @author guofa.tgf + */ + public function sdkExecute($request) { + + $this->setupCharsets($request); + + $params['app_id'] = $this->appId; + $params['method'] = $request->getApiMethodName(); + $params['format'] = $this->format; + $params['sign_type'] = $this->signType; + $params['timestamp'] = date("Y-m-d H:i:s"); + $params['alipay_sdk'] = $this->alipaySdkVersion; + $params['charset'] = $this->postCharset; + + $version = $request->getApiVersion(); + $params['version'] = $this->checkEmpty($version) ? $this->apiVersion : $version; + + if ($notify_url = $request->getNotifyUrl()) { + $params['notify_url'] = $notify_url; + } + + $dict = $request->getApiParas(); + $params['biz_content'] = $dict['biz_content']; + + ksort($params); + + $params['sign'] = $this->generateSign($params, $this->signType); + + foreach ($params as &$value) { + $value = $this->characet($value, $params['charset']); + } + + return http_build_query($params); + } + + /* + 页面提交执行方法 + @param:跳转类接口的request; $httpmethod 提交方式。两个值可选:post、get + @return:构建好的、签名后的最终跳转URL(GET)或String形式的form(POST) + auther:笙默 + */ + public function pageExecute($request,$httpmethod = "POST") { + + $this->setupCharsets($request); + + if (strcasecmp($this->fileCharset, $this->postCharset)) { + + // writeLog("本地文件字符集编码与表单提交编码不一致,请务必设置成一样,属性名分别为postCharset!"); + throw new Exception("文件编码:[" . $this->fileCharset . "] 与表单提交编码:[" . $this->postCharset . "]两者不一致!"); + } + + $iv=null; + + if(!$this->checkEmpty($request->getApiVersion())){ + $iv=$request->getApiVersion(); + }else{ + $iv=$this->apiVersion; + } + + //组装系统参数 + $sysParams["app_id"] = $this->appId; + $sysParams["version"] = $iv; + $sysParams["format"] = $this->format; + $sysParams["sign_type"] = $this->signType; + $sysParams["method"] = $request->getApiMethodName(); + $sysParams["timestamp"] = date("Y-m-d H:i:s"); + $sysParams["alipay_sdk"] = $this->alipaySdkVersion; + $sysParams["terminal_type"] = $request->getTerminalType(); + $sysParams["terminal_info"] = $request->getTerminalInfo(); + $sysParams["prod_code"] = $request->getProdCode(); + $sysParams["notify_url"] = $request->getNotifyUrl(); + $sysParams["return_url"] = $request->getReturnUrl(); + $sysParams["charset"] = $this->postCharset; + + //获取业务参数 + $apiParams = $request->getApiParas(); + + if (method_exists($request,"getNeedEncrypt") &&$request->getNeedEncrypt()){ + + $sysParams["encrypt_type"] = $this->encryptType; + + if ($this->checkEmpty($apiParams['biz_content'])) { + + throw new Exception(" api request Fail! The reason : encrypt request is not supperted!"); + } + + if ($this->checkEmpty($this->encryptKey) || $this->checkEmpty($this->encryptType)) { + + throw new Exception(" encryptType and encryptKey must not null! "); + } + + if ("AES" != $this->encryptType) { + + throw new Exception("加密类型只支持AES"); + } + + // 执行加密 + $enCryptContent = encrypt_laravel($apiParams['biz_content'], $this->encryptKey); + $apiParams['biz_content'] = $enCryptContent; + + } + + //print_r($apiParams); + $totalParams = array_merge($apiParams, $sysParams); + + //待签名字符串 + $preSignStr = $this->getSignContent($totalParams); + + //签名 + $totalParams["sign"] = $this->generateSign($totalParams, $this->signType); + + if ("GET" == strtoupper($httpmethod)) { + + //value做urlencode + $preString=$this->getSignContentUrlencode($totalParams); + //拼接GET请求串 + $requestUrl = $this->gatewayUrl."?".$preString; + + return $requestUrl; + } else { + //拼接表单字符串 + return $this->buildRequestForm($totalParams); + } + + + } + + + + /** + * 建立请求,以表单HTML形式构造(默认) + * @param $para_temp 请求参数数组 + * @return 提交表单HTML文本 + */ + protected function buildRequestForm($para_temp) { + + $sHtml = "
"; + while (list ($key, $val) = each ($para_temp)) { + if (false === $this->checkEmpty($val)) { + //$val = $this->characet($val, $this->postCharset); + $val = str_replace("'","'",$val); + //$val = str_replace("\"",""",$val); + $sHtml.= ""; + } + } + + //submit按钮控件请不要含有name属性 + $sHtml = $sHtml."
"; + + $sHtml = $sHtml.""; + + return $sHtml; + } + + + public function execute($request, $authToken = null, $appInfoAuthtoken = null) { + + $this->setupCharsets($request); + + // // 如果两者编码不一致,会出现签名验签或者乱码 + if (strcasecmp($this->fileCharset, $this->postCharset)) { + + // writeLog("本地文件字符集编码与表单提交编码不一致,请务必设置成一样,属性名分别为postCharset!"); + throw new Exception("文件编码:[" . $this->fileCharset . "] 与表单提交编码:[" . $this->postCharset . "]两者不一致!"); + } + + $iv = null; + + if (!$this->checkEmpty($request->getApiVersion())) { + $iv = $request->getApiVersion(); + } else { + $iv = $this->apiVersion; + } + + + //组装系统参数 + $sysParams["app_id"] = $this->appId; + $sysParams["version"] = $iv; + $sysParams["format"] = $this->format; + $sysParams["sign_type"] = $this->signType; + $sysParams["method"] = $request->getApiMethodName(); + $sysParams["timestamp"] = date("Y-m-d H:i:s"); + $sysParams["auth_token"] = $authToken; + $sysParams["alipay_sdk"] = $this->alipaySdkVersion; + $sysParams["terminal_type"] = $request->getTerminalType(); + $sysParams["terminal_info"] = $request->getTerminalInfo(); + $sysParams["prod_code"] = $request->getProdCode(); + $sysParams["notify_url"] = $request->getNotifyUrl(); + $sysParams["charset"] = $this->postCharset; + $sysParams["app_auth_token"] = $appInfoAuthtoken; + + + //获取业务参数 + $apiParams = $request->getApiParas(); + + if (method_exists($request,"getNeedEncrypt") &&$request->getNeedEncrypt()){ + + $sysParams["encrypt_type"] = $this->encryptType; + + if ($this->checkEmpty($apiParams['biz_content'])) { + + throw new Exception(" api request Fail! The reason : encrypt request is not supperted!"); + } + + if ($this->checkEmpty($this->encryptKey) || $this->checkEmpty($this->encryptType)) { + + throw new Exception(" encryptType and encryptKey must not null! "); + } + + if ("AES" != $this->encryptType) { + + throw new Exception("加密类型只支持AES"); + } + + // 执行加密 + $enCryptContent = encrypt_laravel($apiParams['biz_content'], $this->encryptKey); + $apiParams['biz_content'] = $enCryptContent; + + } + + + //签名 + $sysParams["sign"] = $this->generateSign(array_merge($apiParams, $sysParams), $this->signType); + + + //系统参数放入GET请求串 + $requestUrl = $this->gatewayUrl . "?"; + foreach ($sysParams as $sysParamKey => $sysParamValue) { + $requestUrl .= "$sysParamKey=" . urlencode($this->characet($sysParamValue, $this->postCharset)) . "&"; + } + $requestUrl = substr($requestUrl, 0, -1); + + + //发起HTTP请求 + try { + $resp = $this->curl($requestUrl, $apiParams); + } catch (Exception $e) { + + $this->logCommunicationError($sysParams["method"], $requestUrl, "HTTP_ERROR_" . $e->getCode(), $e->getMessage()); + return false; + } + + //解析AOP返回结果 + $respWellFormed = false; + + + // 将返回结果转换本地文件编码 + $r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp); + + + + $signData = null; + + if ("json" == $this->format) { + + $respObject = json_decode($r); + if (null !== $respObject) { + $respWellFormed = true; + $signData = $this->parserJSONSignData($request, $resp, $respObject); + } + } else if ("xml" == $this->format) { + + $respObject = @ simplexml_load_string($resp); + if (false !== $respObject) { + $respWellFormed = true; + + $signData = $this->parserXMLSignData($request, $resp); + } + } + + + //返回的HTTP文本不是标准JSON或者XML,记下错误日志 + if (false === $respWellFormed) { + $this->logCommunicationError($sysParams["method"], $requestUrl, "HTTP_RESPONSE_NOT_WELL_FORMED", $resp); + return false; + } + + // 验签 + $this->checkResponseSign($request, $signData, $resp, $respObject); + + // 解密 + if (method_exists($request,"getNeedEncrypt") &&$request->getNeedEncrypt()){ + + if ("json" == $this->format) { + + + $resp = $this->encryptJSONSignSource($request, $resp); + + // 将返回结果转换本地文件编码 + $r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp); + $respObject = json_decode($r); + }else{ + + $resp = $this->encryptXMLSignSource($request, $resp); + + $r = iconv($this->postCharset, $this->fileCharset . "//IGNORE", $resp); + $respObject = @ simplexml_load_string($r); + + } + } + + return $respObject; + } + + /** + * 转换字符集编码 + * @param $data + * @param $targetCharset + * @return string + */ + function characet($data, $targetCharset) { + + if (!empty($data)) { + $fileType = $this->fileCharset; + if (strcasecmp($fileType, $targetCharset) != 0) { + $data = mb_convert_encoding($data, $targetCharset, $fileType); + // $data = iconv($fileType, $targetCharset.'//IGNORE', $data); + } + } + + + return $data; + } + + public function exec($paramsArray) { + if (!isset ($paramsArray["method"])) { + trigger_error("No api name passed"); + } + $inflector = new LtInflector; + $inflector->conf["separator"] = "."; + $requestClassName = ucfirst($inflector->camelize(substr($paramsArray["method"], 7))) . "Request"; + if (!class_exists($requestClassName)) { + trigger_error("No such api: " . $paramsArray["method"]); + } + + $session = isset ($paramsArray["session"]) ? $paramsArray["session"] : null; + + $req = new $requestClassName; + foreach ($paramsArray as $paraKey => $paraValue) { + $inflector->conf["separator"] = "_"; + $setterMethodName = $inflector->camelize($paraKey); + $inflector->conf["separator"] = "."; + $setterMethodName = "set" . $inflector->camelize($setterMethodName); + if (method_exists($req, $setterMethodName)) { + $req->$setterMethodName ($paraValue); + } + } + return $this->execute($req, $session); + } + + /** + * 校验$value是否非空 + * if not set ,return true; + * if is null , return true; + **/ + protected function checkEmpty($value) { + if (!isset($value)) + return true; + if ($value === null) + return true; + if (trim($value) === "") + return true; + + return false; + } + + /** rsaCheckV1 & rsaCheckV2 + * 验证签名 + * 在使用本方法前,必须初始化AopClient且传入公钥参数。 + * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。 + **/ + public function rsaCheckV1($params, $rsaPublicKeyFilePath,$signType='RSA') { + $sign = $params['sign']; + $params['sign_type'] = null; + $params['sign'] = null; + return $this->verify($this->getSignContent($params), $sign, $rsaPublicKeyFilePath,$signType); + } + public function rsaCheckV2($params, $rsaPublicKeyFilePath, $signType='RSA') { + $sign = $params['sign']; + $params['sign'] = null; + return $this->verify($this->getSignContent($params), $sign, $rsaPublicKeyFilePath, $signType); + } + + function verify($data, $sign, $rsaPublicKeyFilePath, $signType = 'RSA') { + + if($this->checkEmpty($this->alipayPublicKey)){ + + $pubKey= $this->alipayrsaPublicKey; + $res = "-----BEGIN PUBLIC KEY-----\n" . + wordwrap($pubKey, 64, "\n", true) . + "\n-----END PUBLIC KEY-----"; + }else { + //读取公钥文件 + $pubKey = file_get_contents($rsaPublicKeyFilePath); + //转换为openssl格式密钥 + $res = openssl_get_publickey($pubKey); + } + + ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确'); + + //调用openssl内置方法验签,返回bool值 + + if ("RSA2" == $signType) { + $result = (bool)openssl_verify($data, base64_decode($sign), $res, OPENSSL_ALGO_SHA256); + } else { + $result = (bool)openssl_verify($data, base64_decode($sign), $res); + } + + if(!$this->checkEmpty($this->alipayPublicKey)) { + //释放资源 + openssl_free_key($res); + } + + return $result; + } + +/** + * 在使用本方法前,必须初始化AopClient且传入公私钥参数。 + * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。 + **/ + public function checkSignAndDecrypt($params, $rsaPublicKeyPem, $rsaPrivateKeyPem, $isCheckSign, $isDecrypt, $signType='RSA') { + $charset = $params['charset']; + $bizContent = $params['biz_content']; + if ($isCheckSign) { + if (!$this->rsaCheckV2($params, $rsaPublicKeyPem, $signType)) { + echo "
checkSign failure
"; + exit; + } + } + if ($isDecrypt) { + return $this->rsaDecrypt($bizContent, $rsaPrivateKeyPem, $charset); + } + + return $bizContent; + } + + /** + * 在使用本方法前,必须初始化AopClient且传入公私钥参数。 + * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。 + **/ + public function encryptAndSign($bizContent, $rsaPublicKeyPem, $rsaPrivateKeyPem, $charset, $isEncrypt, $isSign, $signType='RSA') { + // 加密,并签名 + if ($isEncrypt && $isSign) { + $encrypted = $this->rsaEncrypt($bizContent, $rsaPublicKeyPem, $charset); + $sign = $this->sign($encrypted, $signType); + $response = "$encryptedRSA$sign$signType"; + return $response; + } + // 加密,不签名 + if ($isEncrypt && (!$isSign)) { + $encrypted = $this->rsaEncrypt($bizContent, $rsaPublicKeyPem, $charset); + $response = "$encrypted$signType"; + return $response; + } + // 不加密,但签名 + if ((!$isEncrypt) && $isSign) { + $sign = $this->sign($bizContent, $signType); + $response = "$bizContent$sign$signType"; + return $response; + } + // 不加密,不签名 + $response = "$bizContent"; + return $response; + } + + /** + * 在使用本方法前,必须初始化AopClient且传入公私钥参数。 + * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。 + **/ + public function rsaEncrypt($data, $rsaPublicKeyPem, $charset) { + if($this->checkEmpty($this->alipayPublicKey)){ + //读取字符串 + $pubKey= $this->alipayrsaPublicKey; + $res = "-----BEGIN PUBLIC KEY-----\n" . + wordwrap($pubKey, 64, "\n", true) . + "\n-----END PUBLIC KEY-----"; + }else { + //读取公钥文件 + $pubKey = file_get_contents($rsaPublicKeyFilePath); + //转换为openssl格式密钥 + $res = openssl_get_publickey($pubKey); + } + + ($res) or die('支付宝RSA公钥错误。请检查公钥文件格式是否正确'); + $blocks = $this->splitCN($data, 0, 30, $charset); + $chrtext  = null; + $encodes  = array(); + foreach ($blocks as $n => $block) { + if (!openssl_public_encrypt($block, $chrtext , $res)) { + echo "
" . openssl_error_string() . "
"; + } + $encodes[] = $chrtext ; + } + $chrtext = implode(",", $encodes); + + return base64_encode($chrtext); + } + + /** + * 在使用本方法前,必须初始化AopClient且传入公私钥参数。 + * 公钥是否是读取字符串还是读取文件,是根据初始化传入的值判断的。 + **/ + public function rsaDecrypt($data, $rsaPrivateKeyPem, $charset) { + + if($this->checkEmpty($this->rsaPrivateKeyFilePath)){ + //读字符串 + $priKey=$this->rsaPrivateKey; + $res = "-----BEGIN RSA PRIVATE KEY-----\n" . + wordwrap($priKey, 64, "\n", true) . + "\n-----END RSA PRIVATE KEY-----"; + }else { + $priKey = file_get_contents($this->rsaPrivateKeyFilePath); + $res = openssl_get_privatekey($priKey); + } + ($res) or die('您使用的私钥格式错误,请检查RSA私钥配置'); + //转换为openssl格式密钥 + $decodes = explode(',', $data); + $strnull = ""; + $dcyCont = ""; + foreach ($decodes as $n => $decode) { + if (!openssl_private_decrypt($decode, $dcyCont, $res)) { + echo "
" . openssl_error_string() . "
"; + } + $strnull .= $dcyCont; + } + return $strnull; + } + + function splitCN($cont, $n = 0, $subnum, $charset) { + //$len = strlen($cont) / 3; + $arrr = array(); + for ($i = $n; $i < strlen($cont); $i += $subnum) { + $res = $this->subCNchar($cont, $i, $subnum, $charset); + if (!empty ($res)) { + $arrr[] = $res; + } + } + + return $arrr; + } + + function subCNchar($str, $start = 0, $length, $charset = "gbk") { + if (strlen($str) <= $length) { + return $str; + } + $re['utf-8'] = "/[\x01-\x7f]|[\xc2-\xdf][\x80-\xbf]|[\xe0-\xef][\x80-\xbf]{2}|[\xf0-\xff][\x80-\xbf]{3}/"; + $re['gb2312'] = "/[\x01-\x7f]|[\xb0-\xf7][\xa0-\xfe]/"; + $re['gbk'] = "/[\x01-\x7f]|[\x81-\xfe][\x40-\xfe]/"; + $re['big5'] = "/[\x01-\x7f]|[\x81-\xfe]([\x40-\x7e]|\xa1-\xfe])/"; + preg_match_all($re[$charset], $str, $match); + $slice = join("", array_slice($match[0], $start, $length)); + return $slice; + } + + function parserResponseSubCode($request, $responseContent, $respObject, $format) { + + if ("json" == $format) { + + $apiName = $request->getApiMethodName(); + $rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX; + $errorNodeName = $this->ERROR_RESPONSE; + + $rootIndex = strpos($responseContent, $rootNodeName); + $errorIndex = strpos($responseContent, $errorNodeName); + + if ($rootIndex > 0) { + // 内部节点对象 + $rInnerObject = $respObject->$rootNodeName; + } elseif ($errorIndex > 0) { + + $rInnerObject = $respObject->$errorNodeName; + } else { + return null; + } + + // 存在属性则返回对应值 + if (isset($rInnerObject->sub_code)) { + + return $rInnerObject->sub_code; + } else { + + return null; + } + + + } elseif ("xml" == $format) { + + // xml格式sub_code在同一层级 + return $respObject->sub_code; + + } + + + } + + function parserJSONSignData($request, $responseContent, $responseJSON) { + + $signData = new SignData(); + + $signData->sign = $this->parserJSONSign($responseJSON); + $signData->signSourceData = $this->parserJSONSignSource($request, $responseContent); + + + return $signData; + + } + + function parserJSONSignSource($request, $responseContent) { + + $apiName = $request->getApiMethodName(); + $rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX; + + $rootIndex = strpos($responseContent, $rootNodeName); + $errorIndex = strpos($responseContent, $this->ERROR_RESPONSE); + + + if ($rootIndex > 0) { + + return $this->parserJSONSource($responseContent, $rootNodeName, $rootIndex); + } else if ($errorIndex > 0) { + + return $this->parserJSONSource($responseContent, $this->ERROR_RESPONSE, $errorIndex); + } else { + + return null; + } + + + } + + function parserJSONSource($responseContent, $nodeName, $nodeIndex) { + $signDataStartIndex = $nodeIndex + strlen($nodeName) + 2; + $signIndex = strpos($responseContent, "\"" . $this->SIGN_NODE_NAME . "\""); + // 签名前-逗号 + $signDataEndIndex = $signIndex - 1; + $indexLen = $signDataEndIndex - $signDataStartIndex; + if ($indexLen < 0) { + + return null; + } + + return substr($responseContent, $signDataStartIndex, $indexLen); + + } + + function parserJSONSign($responseJSon) { + + return $responseJSon->sign; + } + + function parserXMLSignData($request, $responseContent) { + + + $signData = new SignData(); + + $signData->sign = $this->parserXMLSign($responseContent); + $signData->signSourceData = $this->parserXMLSignSource($request, $responseContent); + + + return $signData; + + + } + + function parserXMLSignSource($request, $responseContent) { + + + $apiName = $request->getApiMethodName(); + $rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX; + + + $rootIndex = strpos($responseContent, $rootNodeName); + $errorIndex = strpos($responseContent, $this->ERROR_RESPONSE); + // $this->echoDebug("
rootNodeName:" . $rootNodeName); + // $this->echoDebug("
responseContent:" . $responseContent . ""); + + + if ($rootIndex > 0) { + + return $this->parserXMLSource($responseContent, $rootNodeName, $rootIndex); + } else if ($errorIndex > 0) { + + return $this->parserXMLSource($responseContent, $this->ERROR_RESPONSE, $errorIndex); + } else { + + return null; + } + + + } + + function parserXMLSource($responseContent, $nodeName, $nodeIndex) { + $signDataStartIndex = $nodeIndex + strlen($nodeName) + 1; + $signIndex = strpos($responseContent, "<" . $this->SIGN_NODE_NAME . ">"); + // 签名前-逗号 + $signDataEndIndex = $signIndex - 1; + $indexLen = $signDataEndIndex - $signDataStartIndex + 1; + + if ($indexLen < 0) + { + return null; + } + + return substr($responseContent, $signDataStartIndex, $indexLen); + } + + function parserXMLSign($responseContent) { + $signNodeName = "<" . $this->SIGN_NODE_NAME . ">"; + $signEndNodeName = "SIGN_NODE_NAME . ">"; + + $indexOfSignNode = strpos($responseContent, $signNodeName); + $indexOfSignEndNode = strpos($responseContent, $signEndNodeName); + + + if ($indexOfSignNode < 0 || $indexOfSignEndNode < 0) { + return null; + } + + $nodeIndex = ($indexOfSignNode + strlen($signNodeName)); + + $indexLen = $indexOfSignEndNode - $nodeIndex; + + if ($indexLen < 0) { + return null; + } + + // 签名 + return substr($responseContent, $nodeIndex, $indexLen); + } + + /** + * 验签 + * @param $request + * @param $signData + * @param $resp + * @param $respObject + * @throws Exception + */ + public function checkResponseSign($request, $signData, $resp, $respObject) { + + if (!$this->checkEmpty($this->alipayPublicKey) || !$this->checkEmpty($this->alipayrsaPublicKey)) { + + + if ($signData == null || $this->checkEmpty($signData->sign) || $this->checkEmpty($signData->signSourceData)) { + + throw new Exception(" check sign Fail! The reason : signData is Empty"); + } + + // 获取结果sub_code + $responseSubCode = $this->parserResponseSubCode($request, $resp, $respObject, $this->format); + + if (!$this->checkEmpty($responseSubCode) || ($this->checkEmpty($responseSubCode) && !$this->checkEmpty($signData->sign))) { + + $checkResult = $this->verify($signData->signSourceData, $signData->sign, $this->alipayPublicKey, $this->signType); + + if (!$checkResult) { + + if (strpos($signData->signSourceData, "\\/") > 0) { + + $signData->signSourceData = str_replace("\\/", "/", $signData->signSourceData); + + $checkResult = $this->verify($signData->signSourceData, $signData->sign, $this->alipayPublicKey, $this->signType); + + if (!$checkResult) { + throw new Exception("check sign Fail! [sign=" . $signData->sign . ", signSourceData=" . $signData->signSourceData . "]"); + } + + } else { + + throw new Exception("check sign Fail! [sign=" . $signData->sign . ", signSourceData=" . $signData->signSourceData . "]"); + } + } + } + } + } + + private function setupCharsets($request) { + if ($this->checkEmpty($this->postCharset)) { + $this->postCharset = 'UTF-8'; + } + $str = preg_match('/[\x80-\xff]/', $this->appId) ? $this->appId : print_r($request, true); + $this->fileCharset = mb_detect_encoding($str, "UTF-8, GBK") == 'UTF-8' ? 'UTF-8' : 'GBK'; + } + + // 获取加密内容 + + private function encryptJSONSignSource($request, $responseContent) { + + $parsetItem = $this->parserEncryptJSONSignSource($request, $responseContent); + + $bodyIndexContent = substr($responseContent, 0, $parsetItem->startIndex); + $bodyEndContent = substr($responseContent, $parsetItem->endIndex, strlen($responseContent) + 1 - $parsetItem->endIndex); + + $bizContent = decrypt_laravel($parsetItem->encryptContent, $this->encryptKey); + return $bodyIndexContent . $bizContent . $bodyEndContent; + } + + private function parserEncryptJSONSignSource($request, $responseContent) { + + $apiName = $request->getApiMethodName(); + $rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX; + + $rootIndex = strpos($responseContent, $rootNodeName); + $errorIndex = strpos($responseContent, $this->ERROR_RESPONSE); + + + if ($rootIndex > 0) { + + return $this->parserEncryptJSONItem($responseContent, $rootNodeName, $rootIndex); + } else if ($errorIndex > 0) { + + return $this->parserEncryptJSONItem($responseContent, $this->ERROR_RESPONSE, $errorIndex); + } else { + + return null; + } + } + + private function parserEncryptJSONItem($responseContent, $nodeName, $nodeIndex) { + $signDataStartIndex = $nodeIndex + strlen($nodeName) + 2; + $signIndex = strpos($responseContent, "\"" . $this->SIGN_NODE_NAME . "\""); + // 签名前-逗号 + $signDataEndIndex = $signIndex - 1; + + if ($signDataEndIndex < 0) { + + $signDataEndIndex = strlen($responseContent)-1 ; + } + + $indexLen = $signDataEndIndex - $signDataStartIndex; + + $encContent = substr($responseContent, $signDataStartIndex+1, $indexLen-2); + + + $encryptParseItem = new EncryptParseItem(); + + $encryptParseItem->encryptContent = $encContent; + $encryptParseItem->startIndex = $signDataStartIndex; + $encryptParseItem->endIndex = $signDataEndIndex; + + return $encryptParseItem; + } + + // 获取加密内容 + + private function encryptXMLSignSource($request, $responseContent) { + + $parsetItem = $this->parserEncryptXMLSignSource($request, $responseContent); + + $bodyIndexContent = substr($responseContent, 0, $parsetItem->startIndex); + $bodyEndContent = substr($responseContent, $parsetItem->endIndex, strlen($responseContent) + 1 - $parsetItem->endIndex); + $bizContent = decrypt_laravel($parsetItem->encryptContent, $this->encryptKey); + + return $bodyIndexContent . $bizContent . $bodyEndContent; + } + + private function parserEncryptXMLSignSource($request, $responseContent) { + + + $apiName = $request->getApiMethodName(); + $rootNodeName = str_replace(".", "_", $apiName) . $this->RESPONSE_SUFFIX; + + + $rootIndex = strpos($responseContent, $rootNodeName); + $errorIndex = strpos($responseContent, $this->ERROR_RESPONSE); + // $this->echoDebug("
rootNodeName:" . $rootNodeName); + // $this->echoDebug("
responseContent:" . $responseContent . ""); + + + if ($rootIndex > 0) { + + return $this->parserEncryptXMLItem($responseContent, $rootNodeName, $rootIndex); + } else if ($errorIndex > 0) { + + return $this->parserEncryptXMLItem($responseContent, $this->ERROR_RESPONSE, $errorIndex); + } else { + + return null; + } + } + + private function parserEncryptXMLItem($responseContent, $nodeName, $nodeIndex) { + + $signDataStartIndex = $nodeIndex + strlen($nodeName) + 1; + + $xmlStartNode="<".$this->ENCRYPT_XML_NODE_NAME.">"; + $xmlEndNode="ENCRYPT_XML_NODE_NAME.">"; + + $indexOfXmlNode=strpos($responseContent,$xmlEndNode); + if($indexOfXmlNode<0){ + + $item = new EncryptParseItem(); + $item->encryptContent = null; + $item->startIndex = 0; + $item->endIndex = 0; + return $item; + } + + $startIndex=$signDataStartIndex+strlen($xmlStartNode); + $bizContentLen=$indexOfXmlNode-$startIndex; + $bizContent=substr($responseContent,$startIndex,$bizContentLen); + + $encryptParseItem = new EncryptParseItem(); + $encryptParseItem->encryptContent = $bizContent; + $encryptParseItem->startIndex = $signDataStartIndex; + $encryptParseItem->endIndex = $indexOfXmlNode+strlen($xmlEndNode); + + return $encryptParseItem; + } + + function echoDebug($content) { + + if ($this->debugInfo) { + echo "
" . $content; + } + + } +} \ No newline at end of file diff --git a/resources/org/alipay_app/AopEncrypt.php b/resources/org/alipay_app/AopEncrypt.php new file mode 100644 index 0000000..4dde8eb --- /dev/null +++ b/resources/org/alipay_app/AopEncrypt.php @@ -0,0 +1,73 @@ +gatewayUrl = "https://openapi.alipay.com/gateway.do"; + $aop->appId = "2017070707673008"; + $aop->rsaPrivateKey = '1fengMIIEpAIBAAKCAQEA5Eccv1+NjMMEMYC9ePnldw8MgCvIsXq1A4VUTlfzCpLpEAe8Losf4lDqZhcwJOhk6+ZC6dWi1rKE7P+huG0Mh88PpXQAy9zPcbbdFqcuijZFxLNj0qcZGhurS6m0cWofAJxVcuSYpHNwJF24kCPrUje46mpNd3J8hsGXjZQBQpU3BdB4DM0hRA7BopR8WTaiGrNIzJSrAi6hIwDqjMFez3AYD4RhQRt9k7NPY04uKI7UR0D6Me+f6Cu0exodB87L1EXTm/ICG/F8rj+4xUXgqWOWw0QuUHMEYB0S5dSxy2sQg/2A67PogsxIUkOB+UdcsI0W2ZmpO5MKSusPlV6VwwIDAQABAoIBAQCscwBHnkLLtMNlNjFsw7PSln7GEM2DLgSzDTUcHhrPwR3p6z4BFz7V9HSu/RN0vk8HWqLwDWD/ukrq007ziQXvTsAuKI01dLEN4avxoghphwh7yV0+1NcEvyRPe3uCNj4HcxKmQgUCLubnwhlcYpYyPUAnbnjpJIboMjVwUgAFsFBlm5aQ1cCTR2aPg/KkC+VlxWOEBaY6Y2RH59sMon+G2LPIvzYJW8Ive+rccyZ0ly7dqEiaW5+dhcLRUC7z/Z4R9ZZm5zrzwFhI+v4vDN+oaTUSjijVbRkZN4U+PGvNYj+OyYd3rQvGS41EmCiO1L23jI7ve0XnbfXYpnpILVN5AoGBAPRF/4uc2ug3HFfP7jgmxuZHJR6GXZz9S9Au6CFi6SQ3D5bgvIN4RiZ8kIXnf1FAJ+/Gi97QPFpozAbNWgBj42w25FRXCe60HKV7K89ovSyeKcbKQ/PhV4MQnsyUU+bkqVzTK5uCedwYPG9rDGHkS3t0kvtvL+QtaK5FRXjtmmHnAoGBAO88iQsw3cE6Xf4B5byj1C3NaIvW7G8ZqLLKc190aNrMtDym8HPw4H2h0MrQ12fRIU/v06DzKII2SbywaICEpMQnAg2WS+X9oeiFGwVB8L2npHz6TX+TGgC8zjJuzW2wVX5NYATP5nSUbZE5oVEYh44gyE4JkY/iwesupO1PGunFAoGAW81qytd6VcdQeZgFmUjJe4XFZ4Fr8TIoqebXCqUXpaqjyzpO3sH260PpNMnZyXlpCO3/Zw+vfvLfqrbGWlsv/11p1mCXtQQvt+lgf6SHZBtU7AbcHu3Ta8h1RcGA/sd09xPN0bXpglQBcoYysx+PVqhrDN+uifye2M/j2hzB5oUCgYBns13UNAJr19kWWcwz0PAQSpGezDMAlabCmW8ZWWR6M3GNOO/R0f/9dT8EKzK0FbrS46pgggZ1KwMbf3xM+TJStHX3XcbYkvCz0b68sLCiBSEP64/cVO9Ykn7u7Yium1jzvqZ4b4X90rkL0mdSt8dKnHs3GH64WBqmzzk+hKOt4QKBgQCwAPwnlLiZmtORdja3rNFrTTXvjua8HVTemMdp2rUuSFB2FXS3suRhqkH2ilMRvbdiP/GlaCOyOTMSOVis88KZKie5Dy26TUgWUMsvG/7d21meRP6SCqpFhw5tkNkTzX+7ul5Si7iabmMtdlhw1OiD7yv4bo3Sw4YTWMa4s4D2ag=='; //请填写开发者私钥去头去尾去回车,一行字符串 + $aop->format = "json"; + $aop->charset = "UTF-8"; + $aop->signType = "RSA2"; + $aop->alipayrsaPublicKey = '1fengMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAmLo5catqqLXWcf+LhRs/WDziyCAB+HPb/+xls2BAtNtvfLHCM9xej5VGTzX7mw6e5/Et3yVAhFnnTZ9U9RWq1m3MiEv19n17/yIbGMXpxSSujYnL0drFBY6Z4f19tzfqWQPETpEf1atFSHbcJQfpaslyr9W2NmS5dbWIe+sJVmZjRN5cYEhFY7U0JHqIPr653XSDzsQ152rHZIb0wJmEVfkr0yyOZl1ja0sx+Gv3/BcHDK1brK94mi9I6J78dDXQS6WSQY7mup9l74Z78FLHf22LtS9GvpkzlL5zAKh0LzTVsgGlyJNMnh0/aRYK4p4IKiSAvQRhLXjfbWLc9XFAzQIDAQAB'; //请填写支付宝公钥,一行字符串 + //实例化具体API对应的request类,类名称和接口名称对应,当前调用接口名称:alipay.trade.app.pay + $request = new \AlipayTradeAppPayRequest(); + //SDK已经封装掉了公共参数,这里只需要传入业务参数 + $bizcontent = "{\"body\":\"我是测试数据\"," + . "\"subject\": \"App支付测试\"," + . "\"out_trade_no\": \"20170125test01\"," + . "\"timeout_express\": \"30m\"," + . "\"total_amount\": \"0.01\"," + . "\"product_code\":\"QUICK_MSECURITY_PAY\"" + . "}"; + $request->setNotifyUrl("http://59.110.220.223:8087/receive_notify"); //商户外网可以访问的异步地址,回调地址 + $request->setBizContent($bizcontent); + //这里和普通的接口调用不同,使用的是sdkExecute + $response = $aop->sdkExecute($request); + //htmlspecialchars是为了输出到页面时防止被浏览器将关键参数html转义,实际打印到日志以及http传输不会有这个问题 + //echo htmlspecialchars($response);//就是orderString 可以直接给客户端请求,无需再做处理。 + echo $response; +} + +//PHP服务端验证异步通知信息参数示例 +public function AlipayTradeAppPayNotify() +{ + require_once './AopClient.php'; + + $aop = new \AopClient; + $aop->alipayrsaPublicKey = ''; //请填写支付宝公钥,一行字符串 + $flag = $aop->rsaCheckV1($_POST, NULL, "RSA2"); //RSA2与上面一致 +} \ No newline at end of file diff --git a/resources/org/alipay_app/说明.txt b/resources/org/alipay_app/说明.txt new file mode 100644 index 0000000..e69de29 diff --git a/说明.txt b/说明.txt index 0b72700..5d0a636 100644 --- a/说明.txt +++ b/说明.txt @@ -41,7 +41,8 @@ return $qrcode->size(500)->generate('Make a qrcode without Laravel!'); ֱʾɶάͼƬreturn ''; - +΢ſ֧ +https://easywechat.org/