Skip to content

Instantly share code, notes, and snippets.

@aguimaraes
Last active August 29, 2015 14:20
Show Gist options
  • Select an option

  • Save aguimaraes/c2a7403a72a131477d50 to your computer and use it in GitHub Desktop.

Select an option

Save aguimaraes/c2a7403a72a131477d50 to your computer and use it in GitHub Desktop.
<?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