PHP code example of ownerpro / nfsen-php-sdk

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,
);

$client = NfsenClient::forStandalone(
    pfxContent: file_get_contents('/caminho/certificado.pfx'),
    senha: 'senha_certificado',
    prefeitura: '3550308',
    ambiente: NfseAmbiente::HOMOLOGACAO,
    validateIdentity: false,
);

$response = $client->emitir([
    'infDPS' => [
        'tpAmb'    => '2',                          // 1 = Producao, 2 = Homologacao
        'dhEmi'    => gmdate('Y-m-d\TH:i:sP'),      // Data/hora emissao (UTC: TSDateTimeUTC exige offset de minuto zero)
        'verAplic' => 'MeuSistema_v1.0',
        'serie'    => '1',
        'nDPS'     => '1',
        'dCompet'  => date('Y-m-d'),                // Data de competencia
        'tpEmit'   => '1',                          // 1 = Prestador
        'cLocEmi'  => '3550308',                    // Codigo IBGE 7 digitos
    ],
    'prest' => [
        'CNPJ'    => '00000000000000',
        'fone'    => '11999999999',
        'regTrib' => [
            'opSimpNac'   => '2',                   // 1 = Nao Optante, 2 = MEI, 3 = ME/EPP
            'regEspTrib'  => '0',                   // 0 = Nenhum
        ],
    ],
    'toma' => [
        'xNome' => 'Tomador Exemplo Ltda',
        'CPF'   => '00000000000',
        'end'   => [
            'xLgr'   => 'Rua Exemplo',
            'nro'    => '100',
            'xBairro' => 'Centro',
            'endNac' => [
                'cMun' => '3550308',
                'CEP'  => '01001000',
            ],
        ],
    ],
    'serv' => [
        'cLocPrestacao' => '3550308',
        'cServ' => [
            'cTribNac'    => '010101',
            'xDescServ'   => 'Desenvolvimento de software sob encomenda',
            'cNBS'        => '116030000',
            'cIntContrib' => '1234',
        ],
    ],
    'valores' => [
        'vServPrest' => ['vServ' => '1000.00'],
        'trib' => [
            'tribMun' => [
                'tribISSQN'  => '1',               // 1 = Operacao tributavel
                'tpRetISSQN' => '1',               // 1 = Nao retido
            ],
            'indTotTrib' => '0',
        ],
    ],
]);

if ($response->sucesso) {
    echo "Chave: {$response->chave}\n";
    echo "XML: {$response->xml}\n";
} else {
    foreach ($response->erros as $erro) {
        echo "[{$erro->codigo}] {$erro->mensagem} - {$erro->descricao}";
    }
}

$client->emitirDecisaoJudicial($dps);   // NfseException

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\Support\DpsId;

// "DPS" + cLocEmi(7) + tpInsc(1=CPF|2=CNPJ) + inscrição(14, zero-pad)
//       + série(5, zero-pad) + nDPS(15, zero-pad)
$idDps = DpsId::generate(
    cLocEmi: '3550308',
    cnpj: '12345678000195',
    cpf: null,
    serie: '1',
    nDps: '42',
);
// => "DPS3550308212345678000195000010000000000000042"

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);

> $pdf = $client->danfse()->toPdf($response->xml);
> 

$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\NfsenClient;

$client = NfsenClient::for($pfx, $senha, $prefeitura);
$response = $client->emitir($dps);

$pdf = $client->danfse()->toPdf($response->xml);
file_put_contents('danfse.pdf', $pdf->pdf);

use OwnerPro\Nfsen\Enums\MarcaDagua;

$pdf = $client->danfse()->toPdf($xmlDaNotaCancelada, MarcaDagua::Cancelada);
$pdf = $client->danfse()->toPdf($xmlDaNotaSubstituida, MarcaDagua::Substituida);

$html = $client->danfse()->toHtml($response->xml);
file_put_contents('danfse.html', $html);

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

$client = NfsenClient::for($pfx, $senha, $ibge, danfse: true);

$client = NfsenClient::for($pfx, $senha, $ibge, danfse: false);
bash
composer 
bash
php artisan vendor:publish --tag=nfsen-config