-
Notifications
You must be signed in to change notification settings - Fork 2
08. Montagem da DPS
Este documento demonstra o fluxo de transformação de dados, desde o payload simplificado que recebemos na API, até a geração do XML final da DPS (Documento Provisório de Serviço) que será assinado e transmitido para a API Nacional de NFS-e.
A API foi desenhada para receber um JSON "amigável", abstraindo a complexidade das siglas e estruturas da Receita Federal. O uso de palavras em inglês é mera conveniência, e pode ser alterado no NfseMapper.php se desejado.
Exemplo de Payload de Entrada:
{
"company_id": 1,
"customer": {
"name": "Empresa Cliente LTDA",
"cpfCnpj": "12345678000199",
"address": {
"street": "Av. Paulista",
"number": "1000",
"zip_code": "01310100",
"city_code": "3550308",
"country_code": "1058"
}
},
"service": {
"code": "1.05",
"description": "Licenciamento de Software",
"nbs_code": "123456789",
"amount": 1500.00,
"tax_rate": 0
}
}O primeiro passo é converter esse JSON amigável para a estrutura interna de arrays que espelha a estrutura do XML da NFS-e. Isso é feito pela classe App\Services\Nfse\NfseMapper.
O objetivo é desacoplar a interface pública da API das regras de nomenclatura do governo (ex: xNome, cTribNac), que não possuem uma nomenclatura amigável, dificultando a integração.
Exemplo no Código (NfseMapper.php):
public function toInternal(array $data): array
{
// ... extração dos dados ...
// Mapeamento para siglas da NFS-e
$tomador = [
'xNome' => $customer['name'],
'cpfCnpj' => $customer['cpfCnpj'] ?? null,
// ...
];
$servico = [
'cTribNac' => $service['code'], // Código de Tributação Nacional
'xDescServ' => $service['description'],
'cNBS' => $service['nbs_code'],
// ...
];
return [
'tomador' => $tomador,
'valores' => $valores,
'servico' => Tools::removeNullValues($servico),
];
}Antes de tentar gerar qualquer XML, aplicamos regras de negócio para garantir a consistência dos dados. Isso ocorre em App\Services\Nfse\NfseValidator. As regras de validação são baseadas no manual oficial da Receita Federal, localizadas no arquivo schemas/docs/anexo_i-sefin_adn-dps_nfse-snnfse-v1-00-20251210.xlsx e nos schemas XSD.
**IMPORTANTE: ** Atualmente, apenas algumas validações básicas de exemplo foram implementadas. Certifique-se de revisar e implementar todas as regras necessárias para o seu caso de uso. É provável que, com atualizações futuras, mais regras sejam adicionadas ao código fonte de exemplo incluso neste material. Algumas regras geralmente são validadas pelo próprio webservice, mas é recomendável validar o máximo possível antes de enviar a requisição.
Exemplo no Código (NfseValidator.php):
protected function validateCustomer(array $data): void
{
$isExterior = isset($tomador['endereco']['cPais']) && $tomador['endereco']['cPais'] != '1058';
if ($isExterior) {
if (!empty($tomador['cpfCnpj'])) {
throw new Exception("Para tomador no exterior, não deve ser informado CPF/CNPJ.");
}
} else {
if (empty($tomador['cpfCnpj'])) {
throw new Exception("CPF ou CNPJ do tomador é obrigatório para tomadores nacionais.");
}
}
}Esta é a etapa mais crítica. A classe App\Services\Nfse\XmlBuilderService transforma o array interno em uma string XML compatível com o padrão DPS (Documento Provisório de Serviço).
O atributo Id da tag infDPS segue uma regra estrita de concatenação:
DPS + CMun(7) + TipoInscr(1) + CNPJ(14) + Serie(5) + Numero(15).
public function generateDpsId(int $dpsNumber, string $dpsSeries): string
{
// ...
return "DPS{$cMun}{$tipoInscr}{$cnpj}{$serie}{$nDps}";
}Utilizamos a biblioteca spatie/array-to-xml para converter o array PHP. A estrutura deve seguir fielmente o XSD. Até mesmo a ordem dos elementos é importante..
$dpsArray = [
'infDPS' => [
'_attributes' => ['Id' => $infDpsId],
'tpAmb' => $this->company->environment->value, // 1=Produção, 2=Homologação
'dhEmi' => $issueDate,
'prest' => [ ... ], // Dados do Prestador (Sua empresa)
'toma' => $this->buildCustomer($data['tomador']), // Dados do Tomador
'serv' => $this->buildService($data), // Dados do Serviço
'valores' => $this->buildValues($data['valores']), // Valores monetários
]
];Após gerar o XML e assiná-lo, o XML é validado contra os arquivos XSD oficiais da Receita Federal.
A classe App\Services\Nfse\XmlValidatorService realiza este trabalho.
Os arquivos XSD oficiais (tiposSimples_v1.00.xsd) contêm expressões regulares (Regex) que funcionam em validadores de algumas linguagens como Java/.NET, mas falham no libxml do PHP.
O método prepareSchemas cria uma cópia dos schemas em cache e aplica correções:
// Substitui regex incompatível com PHP
$content = str_replace(
'pattern value="^(?!0{1,5}$)\d{1,5}$"',
'pattern value="[0-9]{1,5}"',
$content
);Outra correção é que, às vezes, o schema XSD contém um padrão de versão incorreto. Por exemplo, apesar da versão atual da NFS-e ser 1.01, o schema pode conter o pattern 1.\00, o que fará com que a validação falhe. Por isso, modificamos o validador para trocar o pattern pela versão atual configurada no sistema:
$content = str_replace(
'<xs:pattern value="1\.00"/>',
'<xs:pattern value="' . config('services.nfse.version') . '"/>',
$content
);Outra forma de corrigir seria simplesmente corrigir os arquivos XSD manualmente, mas isso não é recomendado, pois a cada atualização oficial você teria que repetir o processo.
Os XSD corrigidos são armazenados em cache na pasta storage/app/schemas_cache/ para evitar retrabalho. É recomendado limpar o cache sempre que atualizar os schemas oficiais, ou sempre que realizar deploys da aplicação, com o comando:
php artisan nfse:clear-schemasUtilizamos a função nativa schemaValidate do PHP.
$dom->loadXML($xmlContent);
if (!$dom->schemaValidate($schemaFile)) {
throw new Exception("Erro de validação do Schema XSD: ...");
}Se o código passar por esta etapa, o XML geralmente está pronto para ser transmitido para a API Nacional.