1. Go to this page and download the library: Download ownerpro/nfsen-php-sdk library. Choose the download type require.
2. Extract the ZIP file and open the index.php.
3. Add this code to the index.php.
<?php
require_once('vendor/autoload.php');
/* Start to develop here. Best regards https://php-download.com/ */
ownerpro / nfsen-php-sdk example snippets
use OwnerPro\Nfsen\Enums\NfseAmbiente;
use OwnerPro\Nfsen\NfsenClient;
$client = NfsenClient::forStandalone(
pfxContent: file_get_contents('/caminho/certificado.pfx'),
senha: 'senha_certificado',
prefeitura: '3550308',
ambiente: NfseAmbiente::HOMOLOGACAO,
);
use OwnerPro\Nfsen\Enums\CodigoJustificativaCancelamento;
$response = $client->cancelar(
chave: '00000000000000000000000000000000000000000000000000',
codigoMotivo: CodigoJustificativaCancelamento::ErroEmissao,
descricao: 'Erro na emissao da nota fiscal',
);
use OwnerPro\Nfsen\Enums\CodigoJustificativaCancelamento;
$response = $client->solicitarAnaliseFiscalCancelamento(
chave: '00000000000000000000000000000000000000000000000000',
codigoMotivo: CodigoJustificativaCancelamento::ErroEmissao,
descricao: 'Prazo de cancelamento direto expirado',
);
use OwnerPro\Nfsen\Enums\SituacaoCancelamento;
$situacao = $client->consultar()->situacaoCancelamento($chave);
// SemPedido | EmAnalise | Deferido | Indeferido
if ($situacao === SituacaoCancelamento::EmAnalise) {
// "cancelamento sob análise fiscal" — mesmo estado que o portal nacional exibe
}
use OwnerPro\Nfsen\Enums\CodigoJustificativaSubstituicao;
$response = $client->substituir(
chave: '00000000000000000000000000000000000000000000000000',
dps: $dpsSubstituta, // DPS da nota substituta (mesma estrutura do emitir)
codigoMotivo: CodigoJustificativaSubstituicao::Outros,
descricao: 'Substituicao por correcao de dados',
);
// NfseResponse (mesmo retorno do emitir)
if ($response->sucesso) {
echo "Chave substituta: {$response->chave}";
} else {
foreach ($response->erros as $erro) {
echo "[{$erro->codigo}] {$erro->descricao}\n";
}
}
use OwnerPro\Nfsen\Enums\TipoEvento;
// Consultar NFSe por chave de acesso
$response = $client->consultar()->nfse($chave);
// sucesso: true apenas em HTTP 2xx com corpo legível e com `nfseXmlGZipB64`
// presente (um 2xx sem ele não ocorre em operação normal e lança
// IndeterminateResultException — nunca vira sucesso com xml: null). Qualquer
// outro status (401, 404, 429, 5xx…) lança HttpException — inclusive quando o
// corpo é um JSON de gateway sem o envelope `erros`/`erro` da SEFIN. Um 5xx
// que traz o envelope é rejeição definitiva e volta como sucesso: false.
// Consultar DPS por ID
$response = $client->consultar()->dps($idDps);
// Quando a SEFIN responde 404 (DPS inexistente), a resposta traz um erro
// dedicado: $response->erros[0]->codigo === NfseResponse::DPS_NOT_FOUND.
// Sinal inequívoco de "não existe" — distinto de erros transitórios
// (401/403/429/5xx lançam HttpException, pois consulta não altera estado;
// falha de transporte lança IndeterminateResultException). Um 2xx sem
// `chaveAcesso` não ocorre em operação normal e lança
// IndeterminateResultException (nunca vira sucesso com chave: null).
// Obter PDF do DANFSE
$response = $client->consultar()->danfse($chave);
// $response->pdf contém o conteúdo binário do PDF
file_put_contents('danfse.pdf', $response->pdf);
// Consultar eventos (tipoEvento é obrigatório)
$response = $client->consultar()->eventos(
chave: $chave,
tipoEvento: TipoEvento::Cancelamento, // e101101
nSequencial: 1,
);
// Tipos disponíveis (nome — código, conforme tiposEventos_v1.01.xsd):
// Cancelamento — 101101 SolicitacaoCancelamentoAnaliseFiscal — 101103
// CancelamentoPorSubstituicao — 105102 CancelamentoDeferidoAnaliseFiscal — 105104
// CancelamentoIndeferidoAnaliseFiscal — 105105 ConfirmacaoPrestador — 202201
// RejeicaoPrestador — 202205 ConfirmacaoTomador — 203202
// RejeicaoTomador — 203206 ConfirmacaoIntermediario — 204203
// RejeicaoIntermediario — 204207 ConfirmacaoTacita — 205204
// AnulacaoRejeicao — 205208 CancelamentoPorOficio — 305101
// BloqueioPorOficio — 305102 DesbloqueioPorOficio — 305103
// InclusaoNfseDan — 467201 TributosNfseRecolhidos — 907201
//
// No sucesso, $response->eventos traz um EventoConsultado por item devolvido e
// $response->xml repete o XML do primeiro:
foreach ($response->eventos as $evento) {
$evento->tipoEvento; // TipoEvento|null
$evento->numeroPedidoRegistroEvento; // int|null
$evento->dataHoraRecebimento; // string|null
$evento->xml; // string|null, já descomprimido
$evento->parseError; // string|null — por que o item não pôde ser lido
}
//
// Quando a SEFIN responde 404 (evento inexistente), a resposta traz um erro
// dedicado: $response->erros[0]->codigo === EventsResponse::EVENT_NOT_FOUND.
// Sinal inequívoco de "não existe" — qualquer outro sucesso: false é
// inconclusivo. Um 2xx que não traga nem a lista `eventos` nem o
// `eventoXmlGZipB64` de topo lança IndeterminateResultException (nunca vira
// sucesso silencioso). Já um evento cujo XML não pôde ser lido mantém
// sucesso: true, com xml: null e o motivo em $evento->parseError.
// Situação do cancelamento por análise fiscal (uma chamada ao ADN, resume os
// eventos 101103/105104/105105 da nota)
$situacao = $client->consultar()->situacaoCancelamento($chave);
// SituacaoCancelamento::SemPedido | EmAnalise | Deferido | Indeferido
// Verificar se DPS foi processada
// true em HTTP 200; false APENAS em HTTP 404 (comprovadamente não existe).
// Qualquer outro status (401, 403, 429, redirect, 5xx…) lança HttpException;
// falha de transporte lança IndeterminateResultException — nesses casos a
// existência é indeterminada e NÃO é seguro re-emitir.
$processada = $client->consultar()->verificarDps($idDps); // true ou false
use OwnerPro\Nfsen\Exceptions\IndeterminateResultException;
use OwnerPro\Nfsen\Responses\NfseResponse;
use OwnerPro\Nfsen\Support\DpsId;
try {
$response = $client->emitir($payload);
} catch (IndeterminateResultException $e) {
// O SDK não conseguiu ler o resultado — a nota pode ou não ter sido emitida.
$idDps = DpsId::generate($cLocEmi, $cnpj, null, $serie, (string) $nDps);
$lookup = $client->consultar()->dps($idDps);
if ($lookup->sucesso) {
// A nota FOI emitida: salvar chave e continuar o fluxo normal.
} elseif (($lookup->erros[0]->codigo ?? null) === NfseResponse::DPS_NOT_FOUND) {
// A emissão NÃO aconteceu: seguro re-emitir com o mesmo nDPS.
}
}
use OwnerPro\Nfsen\Enums\TipoEvento;
use OwnerPro\Nfsen\Exceptions\IndeterminateResultException;
use OwnerPro\Nfsen\Responses\EventsResponse;
try {
$response = $client->cancelar($chave, $codigoMotivo, $descricao);
} catch (IndeterminateResultException $e) {
$lookup = $client->consultar()->eventos(
chave: $chave,
tipoEvento: TipoEvento::Cancelamento,
nSequencial: 1,
);
if ($lookup->sucesso) {
// O cancelamento REGISTROU: siga o fluxo normal com $lookup->xml.
} elseif (($lookup->erros[0]->codigo ?? null) === EventsResponse::EVENT_NOT_FOUND) {
// O cancelamento NÃO registrou: seguro reenviar.
} else {
// Inconclusivo (401/429/5xx com corpo estruturado): aguarde e tente de novo.
}
}
// Buscar lote de documentos a partir do NSU 0
$response = $client->distribuicao()->documentos(0);
if ($response->sucesso) {
foreach ($response->lote as $doc) {
// Um item que o SDK não conseguiu interpretar por completo não interrompe o
// lote: os campos afetados vêm null e parseError diz o que faltou. O nsu é
// sempre preservado, então dá para refazer a busca daquele documento.
if ($doc->parseError !== null) {
echo "NSU {$doc->nsu} incompleto: {$doc->parseError}\n";
continue;
}
echo "NSU: {$doc->nsu} | Tipo: {$doc->tipoDocumento->value} | Chave: {$doc->chaveAcesso}\n";
// $doc->arquivoXml contém o XML já descomprimido
}
}
// Buscar documento unitário pelo NSU
$response = $client->distribuicao()->documento(42);
// Buscar todos os eventos de uma NFS-e
$response = $client->distribuicao()->eventos($chave);
// Usar CNPJ diferente do certificado (procurador/filiais)
$response = $client->distribuicao()->documentos(0, '99999999000100');
use OwnerPro\Nfsen\Facades\Nfsen;
// Emitir
$response = Nfsen::emitir($dps);
// Cancelar
$response = Nfsen::cancelar($chave, $motivo, $descricao);
// Pedir análise fiscal quando o prazo de cancelamento do município já passou
$response = Nfsen::solicitarAnaliseFiscalCancelamento($chave, $motivo, $descricao);
// Consultar
$response = Nfsen::consultar()->nfse($chave);
$danfse = Nfsen::consultar()->danfse($chave);
// Usar certificado diferente por requisicao
$client = Nfsen::for($pfxContent, $senha, '3550308');
$response = $client->emitir($dps);
// Sobrescrever ambiente (ignorar config)
use OwnerPro\Nfsen\Enums\NfseAmbiente;
$client = Nfsen::for($pfxContent, $senha, '3550308', NfseAmbiente::PRODUCAO);
$response = $client->emitir($dps);
$response = $client->cancelar($chave, $motivo, $descricao);
if (! $response->sucesso && $response->erros === []) {
logger()->warning('NFSe: rejeição fora do contrato', ['raw' => $response->raw]);
}
use OwnerPro\Nfsen\Exceptions\CertificateExpiredException;
use OwnerPro\Nfsen\Exceptions\HttpException;
use OwnerPro\Nfsen\Exceptions\IndeterminateResultException;
use OwnerPro\Nfsen\Exceptions\InvalidDpsArgument;
use OwnerPro\Nfsen\Exceptions\NfseException;
use OwnerPro\Nfsen\Exceptions\RequestNotDeliveredException;
try {
$response = $client->emitir($dps);
} catch (CertificateExpiredException $e) {
// Certificado expirado -- renovar
} catch (InvalidDpsArgument $e) {
// Dados da DPS inválidos -- corrigir payload
} catch (RequestNotDeliveredException $e) {
// Nada chegou à SEFIN -- retry direto seguro.
} catch (IndeterminateResultException $e) {
// Resultado INDETERMINADO -- a nota pode ou não ter sido emitida.
// NUNCA re-emita sem antes reconciliar com consultar()->dps($idDps).
} catch (HttpException $e) {
// Resposta HTTP recebida com erro -- $e->getCode() para status, $e->getResponseBody() para corpo
} catch (NfseException $e) {
// Outros erros (XML, compressão, etc.)
}
try {
$response = $client->emitir($payload);
} catch (RequestNotDeliveredException $e) {
// Nada chegou à SEFIN ($e->phase: dns|connect|tls) — repetir o envio direto.
} catch (IndeterminateResultException $e) {
// Pode ter sido processada — reconciliar antes de qualquer retry.
}
use OwnerPro\Nfsen\Adapters\DanfseDataBuilder;
$data = (new DanfseDataBuilder)->build($response->xml);
echo $data->situacao; // "NFS-e Gerada"
echo $data->emitidaPor; // "Prestador"
echo $data->emitente->nome; // prestador, de infDPS/prest
echo $data->tribIbsCbs->valorTotalIbs; // "R$ 108,00"
use OwnerPro\Nfsen\Enums\SituacaoNfse;
SituacaoNfse::from('102')->label(); // "NFS-e de Decisão Judicial"
SituacaoNfse::labelOf('999'); // "-" — código fora do leiaute
$resp = NfsenClient::for($pfx, $senha, $ibge)->emitir($dps);
echo $resp->pdf; // string com o PDF (ou null se render falhou)
print_r($resp->pdfErrors); // list<ProcessingMessage> quando render falha