Last active
August 29, 2015 14:20
-
-
Save aguimaraes/c2a7403a72a131477d50 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <?php | |
| class Simp_Service_Nfe | |
| { | |
| /** | |
| * Se for true, vai encher os logs de merda | |
| * @var bool | |
| */ | |
| protected $debug = false; | |
| /** | |
| * Ambiente: 1 produção, 2 homologação | |
| * @var int | |
| */ | |
| protected $env = 1; | |
| /** | |
| * URLs dos WebServices | |
| * @var array | |
| */ | |
| protected $urls = array( | |
| 1 => 'https://www.nfe.fazenda.gov.br/NFeConsultaDest/NFeConsultaDest.asmx', | |
| 2 => 'https://hom.nfe.fazenda.gov.br/NFeConsultaDest/NFeConsultaDest.asmx' | |
| ); | |
| /** | |
| * CNPJ da empresa que está buscando as notas | |
| * @var int cnpj | |
| */ | |
| protected $cnpj = null; | |
| /** | |
| * Tipo de busca | |
| * 0 - Todas as notas | |
| * 1 - Somente as que não tiveram ciencia | |
| * 2 - Não tiveram manifestação alguma | |
| * @var int tipo | |
| */ | |
| protected $type = 0; | |
| /** | |
| * Indica se busca notas de transferências entre filiais | |
| * 0 - Todos os emitentes | |
| * 1 - Somente NFe's emitidas por outras empresas, não filiais | |
| * @var int issuer | |
| */ | |
| protected $issuer = 0; | |
| /** | |
| * Último número serial único que foi buscado | |
| * @var int | |
| */ | |
| protected $nsu = 0; | |
| /** | |
| * Dados formatados para enviar via CURL | |
| * @var string | |
| */ | |
| protected $data = null; | |
| /** | |
| * Documento XML que está sendo usado | |
| * @var DOMDocument | |
| */ | |
| protected $doc = null; | |
| /** | |
| * Diretório onde serão salvos os xmls de resposta que contém os documentos | |
| * @var string | |
| */ | |
| protected $storagePath = null; | |
| /** | |
| * Array com os dados da empresa | |
| * @var array | |
| */ | |
| protected $owner = null; | |
| /** | |
| * Envelope do SOAP com placeholder xml do corpo | |
| * @var string envelope | |
| */ | |
| protected $soapEnvelope = <<<ENVELOPE | |
| <?xml version="1.0" encoding="utf-8" ?> | |
| <soap12:Envelope xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:soap12="http://www.w3.org/2003/05/soap-envelope"> | |
| <soap12:Header> | |
| <nfeCabecMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsultaDest"><cUF>%s</cUF><versaoDados>1.01</versaoDados></nfeCabecMsg> | |
| </soap12:Header> | |
| <soap12:Body> | |
| <nfeDadosMsg xmlns="http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsultaDest">%s</nfeDadosMsg> | |
| </soap12:Body> | |
| </soap12:Envelope> | |
| ENVELOPE; | |
| protected $body = <<<BODY | |
| <consNFeDest xmlns="http://www.portalfiscal.inf.br/nfe" versao="1.01"> | |
| <tpAmb>%d</tpAmb> | |
| <xServ>CONSULTAR NFE DEST</xServ> | |
| <CNPJ>%s</CNPJ> | |
| <indNFe>%d</indNFe> | |
| <indEmi>%d</indEmi> | |
| <ultNSU>%s</ultNSU> | |
| </consNFeDest> | |
| BODY; | |
| /** | |
| * Logger | |
| * @var Logger | |
| */ | |
| protected $l = null; | |
| /** | |
| * cURL resource | |
| * @var resource | |
| */ | |
| protected $c = null; | |
| /** | |
| * Bootstrapping tudo o que for necessário | |
| */ | |
| public function __construct() | |
| { | |
| $this->l = Logger::getLogger('nf'); | |
| } | |
| /** | |
| * 1 produção, 2 homologação | |
| * @param int $env | |
| * @return \Simp_Service_Nfe | |
| */ | |
| protected function setEnv($env) | |
| { | |
| $this->env = $env; | |
| return $this; | |
| } | |
| /** | |
| * Retorna o ambiente da aplicação | |
| * @return int | |
| */ | |
| protected function getEnv() | |
| { | |
| return $this->env; | |
| } | |
| /** | |
| * Seta o tipo da busca | |
| * @param int $type | |
| * @return \Simp_Service_Nfe | |
| */ | |
| public function setType($type) | |
| { | |
| $this->type = $type; | |
| return $this; | |
| } | |
| /** | |
| * Retorna o tipo de busca | |
| * @return int | |
| */ | |
| protected function getType() | |
| { | |
| return $this->type; | |
| } | |
| /** | |
| * Seta o issuer | |
| * @param int $issuer | |
| * @return \Simp_Service_Nfe | |
| */ | |
| public function setIssuer($issuer) | |
| { | |
| $this->issuer = $issuer; | |
| return $this; | |
| } | |
| /** | |
| * Retorna o issuer | |
| * @return int | |
| */ | |
| protected function getIssuer() | |
| { | |
| return $this->issuer; | |
| } | |
| /** | |
| * Define o CNPJ da empresa | |
| * @param string $cnpj | |
| * @return \Simp_Service_Nfe | |
| */ | |
| public function setCNPJ($cnpj) | |
| { | |
| $this->cnpj = $cnpj; | |
| return $this; | |
| } | |
| /** | |
| * Retorna o CNPJ da empresa envolvida na operação | |
| * @return string | |
| */ | |
| protected function getCNPJ() | |
| { | |
| return $this->cnpj; | |
| } | |
| /** | |
| * Seta o valor da propriedade debug | |
| * @param bool $debug | |
| * @return \Simp_Service_Nfe | |
| */ | |
| public function setDebug($debug) | |
| { | |
| $this->debug = $debug; | |
| return $this; | |
| } | |
| /** | |
| * Pega o valor da propriedade debug | |
| * @return bool | |
| */ | |
| protected function getDebug() | |
| { | |
| return $this->debug; | |
| } | |
| /** | |
| * Retorna o último NSU utilizado | |
| * @return string | |
| */ | |
| protected function getLastNSU() | |
| { | |
| //return 0; | |
| $adp = Zend_Db_Table::getDefaultAdapter(); | |
| $sql = 'select last_nsu from nfe_addr_requests where owner_id = ? order by id desc limit 1'; | |
| $st = $adp->query($sql, array($this->owner['id'])); | |
| return $st->fetchColumn(0); | |
| } | |
| /** | |
| * Define o NSU a ser utilizado | |
| * @param string $nsu | |
| * @return \Simp_Service_Nfe | |
| */ | |
| protected function setNSU($nsu) | |
| { | |
| $this->nsu = $nsu; | |
| return $this; | |
| } | |
| /** | |
| * Retorna o NSU sendo utilizado agora na operação | |
| * @return string | |
| */ | |
| protected function getNSU() | |
| { | |
| return $this->nsu; | |
| } | |
| /** | |
| * Retorna o envelope SOAP | |
| * @return type | |
| */ | |
| protected function getSoapEnvelope() | |
| { | |
| return $this->soapEnvelope; | |
| } | |
| /** | |
| * Retorna o corpo XML da requisição | |
| * @return string | |
| */ | |
| protected function getBody() | |
| { | |
| return $this->body; | |
| } | |
| /** | |
| * Retorna e testa o diretório em que os certificados estão armazenados | |
| * @return string | |
| * @throws Zend_Exception | |
| */ | |
| protected function getCertsDir() | |
| { | |
| $bs = Zend_Controller_Front::getInstance()->getParam('bootstrap'); | |
| $eOpts = $bs->getOption('emission'); | |
| if (!isset($eOpts['certificate_storage_path'])) { | |
| throw new Zend_Exception('A propriedade certificate_storage_path não está definida no application.ini.'); | |
| } | |
| $certsDir = $eOpts['certificate_storage_path']; | |
| if (empty($certsDir)) { | |
| throw new Zend_Exception('A propriedade certificate_storage_path do application.ini está vazia.'); | |
| } | |
| if (! is_readable($certsDir)) { | |
| throw new Zend_Exception('Não tenho permissão de leitura no diretório dos certificados.'); | |
| } | |
| return $certsDir; | |
| } | |
| /** | |
| * Retorna e testa o path para a chave pública da empresa | |
| * @return string | |
| * @throws Zend_Exception | |
| */ | |
| protected function getPublicKey() | |
| { | |
| $path = sprintf('%s/%s_pubKEY.pem', $this->getCertsDir(), $this->getCNPJ()); | |
| if (! is_readable($path)) { | |
| throw new Zend_Exception('Não encontrei o arquivo da chave pública ou eu não tive permissão para isso.'); | |
| } | |
| return $path; | |
| } | |
| /** | |
| * Retorna e testa o path para a chave privada da empresa | |
| * @return string | |
| * @throws Zend_Exception | |
| */ | |
| protected function getPrivateKey() | |
| { | |
| $path = sprintf('%s/%s_priKEY.pem', $this->getCertsDir(), $this->getCNPJ()); | |
| if (! is_readable($path)) { | |
| throw new Zend_Exception('Não encontrei o arquivo da chave privada ou eu não tive permissão para isso.'); | |
| } | |
| return $path; | |
| } | |
| /** | |
| * Configura as opções do CURL | |
| * @return Simp_Service_Nfe | |
| */ | |
| protected function setCurlOptions() | |
| { | |
| $envelope = sprintf($this->soapEnvelope, $this->owner['addr_ibge'], $this->body); | |
| $this->data = trim(str_replace(array("\r", "\n"), '', sprintf($envelope, $this->getEnv(), $this->getCNPJ(), $this->getType(), $this->getIssuer(), $this->getNSU()))); | |
| $length = strlen($this->data); | |
| $headerParams = array( | |
| 'Content-Type: application/soap+xml;charset=utf-8;action="http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsultaDest/nfeConsultaNFDest"', | |
| //'SOAPAction: "http://www.portalfiscal.inf.br/nfe/wsdl/NfeConsultaDest/nfeConsultaNFDest"', | |
| "Content-length: $length" | |
| ); | |
| $this->c = curl_init(); | |
| curl_setopt($this->c, CURLOPT_URL, $this->urls[$this->env]); | |
| curl_setopt($this->c, CURLOPT_PORT, 443); | |
| if ($this->getDebug()) { | |
| curl_setopt($this->c, CURLOPT_VERBOSE, 1); | |
| curl_setopt($this->c, CURLOPT_HEADER, 1); | |
| } | |
| curl_setopt($this->c, CURLOPT_SSLVERSION, 3); | |
| curl_setopt($this->c, CURLOPT_SSL_VERIFYHOST, 0); | |
| curl_setopt($this->c, CURLOPT_SSL_VERIFYPEER, 0); | |
| curl_setopt($this->c, CURLOPT_SSLCERT, $this->getPublicKey()); | |
| curl_setopt($this->c, CURLOPT_SSLKEY, $this->getPrivateKey()); | |
| curl_setopt($this->c, CURLOPT_POST, 1); | |
| curl_setopt($this->c, CURLOPT_POSTFIELDS, $this->data); | |
| curl_setopt($this->c, CURLOPT_RETURNTRANSFER, 1); | |
| curl_setopt($this->c, CURLOPT_HTTPHEADER, $headerParams); | |
| return $this; | |
| } | |
| /** | |
| * Envia a requisição SOAP via CURL | |
| * @return false|string | |
| * @throws Zend_Exception | |
| */ | |
| protected function send() | |
| { | |
| $this->setCurlOptions(); | |
| $result = curl_exec($this->c); | |
| $info = curl_getinfo($this->c); | |
| if (false === $result) { | |
| $errMsg = curl_error($this->c); | |
| throw new Zend_Exception($errMsg); | |
| } | |
| // tamanho total do resultado | |
| $length = strlen($result); | |
| // index contém a posição do início do XML (lembrando que o resultado vem com partes do cabeçalho) | |
| $index = strpos($result, '<?xml'); | |
| // pega a string do início do xml até o final | |
| $xml = substr($result, $index, $length - $index); | |
| if ($this->getDebug()) { | |
| $this->l->debug($result); | |
| $this->l->debug($info); | |
| } | |
| curl_close($this->c); | |
| return $xml; | |
| } | |
| /** | |
| * Busca as tags (ou a tag) do segundo parâmetro dentro do documento do | |
| * primeiro parâmetro e retorna uma array (ou o resultado) | |
| * @param DOMElement|DOMDocument $parent | |
| * @param array $elements | |
| * @return mixed | |
| * @throws Zend_Exception | |
| */ | |
| protected function getElements($parent, $elements) | |
| { | |
| if (!($parent instanceof DOMElement) && ! ($parent instanceof DOMDocument)) { | |
| throw new Zend_Exception('O primeiro parâmetro precisa ser um DOMDocument ou um DOMElement.'); | |
| } | |
| if (!is_array($elements)) { | |
| $el = $this->getElement($parent, $elements); | |
| if (! $el) { | |
| return false; | |
| } | |
| return $el->nodeValue; | |
| } | |
| $result = array(); | |
| foreach ($elements as $e) { | |
| $el = $this->getElement($parent, $e); | |
| $result[$e] = false === $el ? $el : $el->nodeValue; | |
| } | |
| return $result; | |
| } | |
| /** | |
| * Procura e retorna a tag indicada no segundo parâmetro dentro | |
| * do elemento ou documento indicado no primeiro parâmetro | |
| * @param DOMElement|DOMDocument $parent | |
| * @param string $e | |
| * @return false|DOMElement | |
| * @throws Zend_Exception | |
| */ | |
| protected function getElement($parent, $e) | |
| { | |
| if (!($parent instanceof DOMElement) && ! ($parent instanceof DOMDocument)) { | |
| throw new Zend_Exception('O primeiro parâmetro precisa ser um DOMDocument ou um DOMElement.'); | |
| } | |
| $search = $parent->getElementsByTagName($e); | |
| if ($search->length < 1) { | |
| return false; | |
| } | |
| return $search->item(0); | |
| } | |
| /** | |
| * Retorna o diretório de armazenamento dos XML's | |
| * @return string | |
| */ | |
| protected function getStoragePath() | |
| { | |
| return $this->storagePath; | |
| } | |
| /** | |
| * Armazena e retorna o diretório em que serão armazenados os XMLs das requisicoes de consulta | |
| * de destinatário de NFe (cria o diretório se não existir) | |
| * @return string | |
| * @throws Zend_Exception | |
| */ | |
| protected function setStoragePath() | |
| { | |
| $bs = Zend_Controller_Front::getInstance()->getParam('bootstrap'); | |
| $eOpts = $bs->getOption('nf'); | |
| if (!isset($eOpts['addr_search_path']) || empty($eOpts['addr_search_path'])) { | |
| throw new Zend_Exception('O diretório de armazenamento dos XMLs da consulta de NF não está configurado no application.ini.'); | |
| } | |
| $dir = $eOpts['addr_search_path']; | |
| if (! file_exists($dir)) { | |
| $createDir = mkdir($dir, 0777); | |
| if (! $createDir) { | |
| throw new Zend_Exception(sprintf('Não consegui criar o diretório: %s', $dir)); | |
| } | |
| } | |
| $path = sprintf('%s/%s', $dir, $this->getCNPJ()); | |
| if (! file_exists($path)) { | |
| $createAnotherDir = mkdir($path, 0777); | |
| if (! $createAnotherDir) { | |
| throw new Zend_Exception(sprintf('Não consegui criar o diretório: %s', $path)); | |
| } | |
| } | |
| if (!is_writable($path)) { | |
| throw new Zend_Exception(sprintf('Não tenho permissão de escrita no diretório: %s', $path)); | |
| } | |
| $this->storagePath = $path; | |
| return $path; | |
| } | |
| /** | |
| * Busca a empresa referente a essa consulta no banco | |
| * @return array | |
| */ | |
| protected function getOwner() | |
| { | |
| $adp = Zend_Db_Table::getDefaultAdapter(); | |
| $sql = 'select persons.id, persons.cnpj, states.ibge as addr_ibge from persons inner join states on states.uf = persons.addr_uf where persons.cnpj = ? and persons.owner_id is null'; | |
| $st = $adp->query($sql, array($this->getCNPJ())); | |
| return $st->fetch(); | |
| } | |
| /** | |
| * Pega um documento do SEFAZ que pode conter ou não uma nota | |
| * @return bool | |
| * @throws Zend_Exception | |
| */ | |
| protected function getOneDocument() | |
| { | |
| $result = $this->send(); | |
| if (! $result) { | |
| throw new Zend_Exception('Erro no método send.'); | |
| } | |
| $this->doc = new DOMDocument('1.0', 'utf-8'); | |
| $this->doc->formatOutput = false; | |
| $this->doc->preserveWhiteSpace = false; | |
| $load = $this->doc->loadXML($result, LIBXML_NOBLANKS | LIBXML_NOEMPTYTAG); | |
| if (! $load) { | |
| throw new Zend_Exception('Não consegui carregar o XML de retorno com o DomDocument.'); | |
| } | |
| $baseElement = $this->getElement($this->doc, 'retConsNFeDest'); | |
| if (! $baseElement) { | |
| throw new Zend_Exception('Não consegui encontrar a tag retConsNFeDest no XML de retorno.'); | |
| } | |
| $results = $this->getElements($baseElement, array('cStat', 'dhResp', 'indCont', 'ultNSU')); | |
| $answeredAt = date('Y-m-d H:i:s'); | |
| $answeredAtObj = DateTime::createFromFormat('Y-m-d\TH:i:s', $results['dhResp']); | |
| if (false !== $answeredAtObj) { | |
| $answeredAt = $answeredAtObj->format('Y-m-d H:i:s'); | |
| } | |
| $saveXML = $this->doc->save(sprintf('%s/%s.xml', $this->getStoragePath(), $this->getNSU())); | |
| if (false === $saveXML) { | |
| $this->l->warn('Não consegui salvar o XML com a resposta.'); | |
| } | |
| $table = new Simp_Model_NfeAddrRequestTable(); | |
| $row = $table->createRow(array( | |
| 'owner_id' => $this->owner['id'], | |
| 'answered_at' => $answeredAt, | |
| 'status' => $results['cStat'], | |
| 'do_again' => (int) $results['indCont'], | |
| 'nsu' => $this->getNSU(), | |
| 'last_nsu' => $results['ultNSU'] | |
| )); | |
| $row->save(); | |
| return $results; | |
| } | |
| /** | |
| * Pega a lista de XMLs e guarda no sistema | |
| * @return boolean | |
| */ | |
| public function getList() | |
| { | |
| $this->setStoragePath(); | |
| $this->owner = $this->getOwner(); | |
| $lastNSU = $this->getLastNSU(); | |
| if ($lastNSU) { | |
| $this->setNSU($lastNSU); | |
| } | |
| do { | |
| $result = $this->getOneDocument(); | |
| // se não tiver indicando continuação, sai do loop | |
| if ($result['indCont'] != 1) { | |
| break; | |
| } | |
| $this->setNSU($result['ultNSU']); | |
| } while (1); | |
| return true; | |
| } | |
| public function getFiles() | |
| { | |
| $files = scandir($this->setStoragePath()); | |
| foreach ($files as $file) { | |
| if ($file == '.' || $file == '..') { | |
| continue; | |
| } | |
| $path = sprintf('%s/%s', $this->getStoragePath(), $file); | |
| $this->doc = new DOMDocument('1.0', 'utf-8'); | |
| $this->doc->formatOutput = false; | |
| $this->doc->preserveWhiteSpace = false; | |
| $load = $this->doc->load($path, LIBXML_NOBLANKS | LIBXML_NOEMPTYTAG); | |
| if (! $load) { | |
| throw new Zend_Exception('Não consegui dar load no arquivo'); | |
| } | |
| $this->hasSomething(); | |
| } | |
| } | |
| protected function hasSomething() | |
| { | |
| $searchRet = $this->doc->getElementsByTagName('resNFe'); | |
| if ($searchRet->length > 0) { | |
| die('Encontrei algo!!'); | |
| } | |
| var_dump($searchRet->length); | |
| } | |
| } |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment