Skip to main content
O MDF-e (Manifesto Eletrônico de Documentos Fiscais) agrupa CT-e e NF-e em uma única declaração de carga por viagem no transporte rodoviário, sendo exigido pela SEFAZ para operações interestaduais e de cabotagem. O SUOT Fiscal SDK expõe Commands e Handlers tipados para cada operação do ciclo de vida do MDF-e — emissão, encerramento, inclusão de condutor e cancelamento —, sempre retornando um Result estruturado ou lançando InfrastructureException em caso de falha técnica. Da ordem ao resultado.

Operações disponíveis

Emissão

Cria e transmite um novo MDF-e à SEFAZ. Retorna EmitirMDFeResult com chave, protocolo e XML autorizado.

Encerramento

Encerra um MDF-e autorizado ao final da viagem. Obrigatório antes de emitir um novo manifesto para o mesmo veículo na mesma UF.

Inclusão de condutor

Adiciona um condutor a um MDF-e já autorizado sem a necessidade de cancelamento e reemissão.

Cancelamento

Cancela um MDF-e autorizado dentro do prazo legal.

Em desenvolvimento — disponível em breve.

Construindo um MDF-e

Use MDFeBuilder com o padrão Step Builder para compor o EmitirMDFeCommand. Cada etapa da construção retorna a próxima interface obrigatória, impedindo estados inválidos antes mesmo da execução.
<?php

use Suot\Fiscal\MDFe\Builder\MDFeBuilder;
use Suot\Fiscal\MDFe\Command\EmitirMDFeCommand;
use Suot\Fiscal\MDFe\Enum\ModalTransporte;
use Suot\Fiscal\MDFe\Enum\TipoEmitente;
use Suot\Fiscal\ValueObjects\Cnpj;
use Suot\Fiscal\ValueObjects\ChaveCTe;
use Suot\Fiscal\ValueObjects\Municipio;
use Suot\Fiscal\ValueObjects\Uf;
use Suot\Fiscal\ValueObjects\Placa;
use Suot\Fiscal\ValueObjects\Rntrc;
use Suot\Fiscal\ValueObjects\Peso;

$command = MDFeBuilder::novo()
    ->comModal(ModalTransporte::Rodoviario)
    ->comTipoEmitente(TipoEmitente::TransportadorCargaPropria)
    ->comDataEmissao(new DateTimeImmutable('2025-06-15'))
    ->comEmitente(
        cnpj: Cnpj::from('12.345.678/0001-99'),
        razaoSocial: 'Transportadora Exemplo Ltda',
        uf: Uf::SP,
    )
    ->comVeiculo(
        placa: Placa::from('ABC1D23'),
        rntrc: Rntrc::from('12345678'),
        uf: Uf::SP,
    )
    ->comCondutor(
        nome: 'João da Silva',
        cpf: \Suot\Fiscal\ValueObjects\Cpf::from('123.456.789-09'),
    )
    ->comPercurso(
        ufInicio: Uf::SP,
        ufFim: Uf::RJ,
    )
    ->adicionarMunicipioCarga(
        municipio: Municipio::from(codigoIbge: '3550308'), // São Paulo - SP
        documentos: [
            ChaveCTe::from('35250612345678000199570010000010011234567890'),
            ChaveCTe::from('35250612345678000199570010000010021234567891'),
        ],
    )
    ->comCargaTotal(
        pesoTotal: Peso::bruto(quilos: 8_500.00),
        quantidadeDocumentos: 2,
    )
    ->build(); // retorna EmitirMDFeCommand (imutável)
As chaves de CT-e informadas em adicionarMunicipioCarga() devem corresponder a documentos já autorizados pela SEFAZ. O SDK valida o formato das chaves (44 dígitos com dígito verificador correto) no momento da construção, mas a validação de existência é feita pelo fisco no momento da transmissão.

Executando e inspecionando o Result

<?php

use Suot\Fiscal\MDFe\Handler\EmitirMDFeHandler;
use Suot\Fiscal\MDFe\Result\EmitirMDFeResult;
use Suot\Fiscal\Exception\InfrastructureException;

// $handler é injetado via DI (Laravel Bridge) ou instanciado manualmente
/** @var EmitirMDFeHandler $handler */

try {
    $result = $handler->handle($command);
} catch (InfrastructureException $e) {
    // Falha técnica: conexão, certificado, webservice indisponível.
    logger()->critical('Falha ao transmitir MDF-e', [
        'mensagem' => $e->getMessage(),
    ]);
    throw $e;
}

if ($result->isSuccess()) {
    $chave       = $result->getChave();        // ChaveMDFe (44 dígitos)
    $protocolo   = $result->getProtocolo();    // Protocolo de autorização
    $xmlAutorizado = $result->getXmlAutorizado();

    logger()->info('MDF-e autorizado', [
        'chave'     => (string) $chave,
        'protocolo' => (string) $protocolo,
    ]);
} else {
    $rejeicao = $result->getRejeicao();

    logger()->warning('MDF-e rejeitado pela SEFAZ', [
        'codigo'    => $rejeicao->getCodigo(),
        'descricao' => $rejeicao->getDescricao(),
    ]);
}

Encerramento

O encerramento deve ser realizado ao término de cada viagem. Use EncerrarMDFeCommand com a chave e protocolo do manifesto autorizado, além do município e UF de encerramento.
<?php

use Suot\Fiscal\MDFe\Command\EncerrarMDFeCommand;
use Suot\Fiscal\MDFe\Handler\EncerrarMDFeHandler;
use Suot\Fiscal\ValueObjects\ChaveMDFe;
use Suot\Fiscal\ValueObjects\Protocolo;
use Suot\Fiscal\ValueObjects\Municipio;
use Suot\Fiscal\ValueObjects\Uf;

$encerramento = new EncerrarMDFeCommand(
    chave: ChaveMDFe::from('35250612345678000199580010000005011234567890'),
    protocolo: Protocolo::from('135250000012345'),
    dataEncerramento: new DateTimeImmutable('2025-06-15'),
    municipioEncerramento: Municipio::from(codigoIbge: '3304557'), // Rio de Janeiro - RJ
    ufEncerramento: Uf::RJ,
);

/** @var EncerrarMDFeHandler $handler */
$resultado = $handler->handle($encerramento);

if ($resultado->isSuccess()) {
    logger()->info('MDF-e encerrado com sucesso', [
        'protocolo_encerramento' => (string) $resultado->getProtocolo(),
    ]);
}
Um MDF-e deve ser encerrado antes que um novo manifesto seja emitido para o mesmo veículo (placa) com cargas na mesma UF de início de percurso. A SEFAZ rejeita novos MDF-e enquanto o anterior permanecer aberto — código de rejeição 677.

Próximos passos

Resultados

Anatomia do padrão Result: todos os campos, status e metadados retornados nas operações de MDF-e.

Rejeições

Códigos de rejeição da SEFAZ para MDF-e e estratégias de tratamento.