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 = "
"; + + $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 "