brans-nfe 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- brans_nfe-0.1.0/.gitignore +54 -0
- brans_nfe-0.1.0/LICENSE +21 -0
- brans_nfe-0.1.0/PKG-INFO +307 -0
- brans_nfe-0.1.0/README.md +267 -0
- brans_nfe-0.1.0/pyproject.toml +70 -0
- brans_nfe-0.1.0/src/brans_nfe/__init__.py +107 -0
- brans_nfe-0.1.0/src/brans_nfe/_patches.py +22 -0
- brans_nfe-0.1.0/src/brans_nfe/certificado.py +141 -0
- brans_nfe-0.1.0/src/brans_nfe/chain.py +52 -0
- brans_nfe-0.1.0/src/brans_nfe/client.py +481 -0
- brans_nfe-0.1.0/src/brans_nfe/danfse_pdf.py +535 -0
- brans_nfe-0.1.0/src/brans_nfe/dps.py +288 -0
- brans_nfe-0.1.0/src/brans_nfe/enums.py +124 -0
- brans_nfe-0.1.0/src/brans_nfe/eventos.py +100 -0
- brans_nfe-0.1.0/src/brans_nfe/exceptions.py +45 -0
- brans_nfe-0.1.0/src/brans_nfe/models.py +206 -0
- brans_nfe-0.1.0/src/brans_nfe/signer.py +99 -0
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
*.so
|
|
5
|
+
docs oficiais/
|
|
6
|
+
.Python
|
|
7
|
+
build/
|
|
8
|
+
develop-eggs/
|
|
9
|
+
dist/
|
|
10
|
+
downloads/
|
|
11
|
+
eggs/
|
|
12
|
+
.eggs/
|
|
13
|
+
lib/
|
|
14
|
+
lib64/
|
|
15
|
+
parts/
|
|
16
|
+
sdist/
|
|
17
|
+
var/
|
|
18
|
+
wheels/
|
|
19
|
+
*.egg-info/
|
|
20
|
+
*.egg
|
|
21
|
+
.installed.cfg
|
|
22
|
+
|
|
23
|
+
.pytest_cache/
|
|
24
|
+
.coverage
|
|
25
|
+
.coverage.*
|
|
26
|
+
htmlcov/
|
|
27
|
+
.tox/
|
|
28
|
+
.nox/
|
|
29
|
+
.mypy_cache/
|
|
30
|
+
.ruff_cache/
|
|
31
|
+
|
|
32
|
+
.venv/
|
|
33
|
+
venv/
|
|
34
|
+
env/
|
|
35
|
+
ENV/
|
|
36
|
+
|
|
37
|
+
.idea/
|
|
38
|
+
.vscode/
|
|
39
|
+
*.swp
|
|
40
|
+
*.swo
|
|
41
|
+
.DS_Store
|
|
42
|
+
Thumbs.db
|
|
43
|
+
|
|
44
|
+
*.pfx
|
|
45
|
+
*.p12
|
|
46
|
+
certs/
|
|
47
|
+
.env
|
|
48
|
+
.env.*
|
|
49
|
+
!.env.example
|
|
50
|
+
|
|
51
|
+
.claude/
|
|
52
|
+
.cursor/
|
|
53
|
+
CLAUDE.md
|
|
54
|
+
.aider*
|
brans_nfe-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Raphael Brans
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
brans_nfe-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: brans-nfe
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cliente Python para a NFS-e Nacional do Brasil (SEFIN) — assinatura, transmissão, consulta, cancelamento, DFe e DANFSe.
|
|
5
|
+
Project-URL: Homepage, https://github.com/badbrans/brans-nfe
|
|
6
|
+
Project-URL: Repository, https://github.com/badbrans/brans-nfe
|
|
7
|
+
Project-URL: Issues, https://github.com/badbrans/brans-nfe/issues
|
|
8
|
+
Author-email: Raphael Brans <raphael_brans@hotmail.com>
|
|
9
|
+
License: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: brasil,fiscal,icp-brasil,nfs-e,nfse,nota fiscal,sefin
|
|
12
|
+
Classifier: Development Status :: 3 - Alpha
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
15
|
+
Classifier: Natural Language :: Portuguese (Brazilian)
|
|
16
|
+
Classifier: Operating System :: OS Independent
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Topic :: Office/Business :: Financial
|
|
22
|
+
Requires-Python: >=3.10
|
|
23
|
+
Requires-Dist: cryptography>=42.0
|
|
24
|
+
Requires-Dist: lxml>=5.0
|
|
25
|
+
Requires-Dist: nfelib>=2.5.2
|
|
26
|
+
Requires-Dist: pydantic>=2.5
|
|
27
|
+
Requires-Dist: requests>=2.31
|
|
28
|
+
Requires-Dist: xsdata>=24.0
|
|
29
|
+
Provides-Extra: danfse
|
|
30
|
+
Requires-Dist: reportlab>=4.0; extra == 'danfse'
|
|
31
|
+
Provides-Extra: dev
|
|
32
|
+
Requires-Dist: black>=24.0; extra == 'dev'
|
|
33
|
+
Requires-Dist: isort>=5.13; extra == 'dev'
|
|
34
|
+
Requires-Dist: mypy>=1.10; extra == 'dev'
|
|
35
|
+
Requires-Dist: pytest-cov>=5.0; extra == 'dev'
|
|
36
|
+
Requires-Dist: pytest>=8.0; extra == 'dev'
|
|
37
|
+
Requires-Dist: reportlab>=4.0; extra == 'dev'
|
|
38
|
+
Requires-Dist: responses>=0.25; extra == 'dev'
|
|
39
|
+
Description-Content-Type: text/markdown
|
|
40
|
+
|
|
41
|
+
# brans-nfe
|
|
42
|
+
|
|
43
|
+
[](https://pypi.org/project/brans-nfe/)
|
|
44
|
+
[](https://pypi.org/project/brans-nfe/)
|
|
45
|
+
[](https://opensource.org/licenses/MIT)
|
|
46
|
+
[](https://github.com/badbrans/brans-nfe/actions/workflows/tests.yml)
|
|
47
|
+
|
|
48
|
+
Cliente Python para a **NFS-e Nacional do Brasil** (SEFIN/Receita Federal) — emissão, consulta, cancelamento, distribuição de DFe e DANFSe, com assinatura XMLDSIG, mTLS com certificado A1 ICP-Brasil e payloads compactados em gzip+base64.
|
|
49
|
+
|
|
50
|
+
A `nfelib` fornece os bindings do XSD oficial. A `brans-nfe` foca na conversa com o SEFIN: PKCS#12, cadeia ICP-Brasil, assinatura XMLDSIG, transporte mTLS, paginação de DFe, mapeamentos de enums e modelagem de input.
|
|
51
|
+
|
|
52
|
+
## Recursos
|
|
53
|
+
|
|
54
|
+
- ✅ Emissão de NFS-e a partir de DPS (`POST /nfse`)
|
|
55
|
+
- ✅ Consulta de NFS-e por chave de acesso (`GET /nfse/{chave}`)
|
|
56
|
+
- ✅ Consulta/verificação de DPS por Id (`GET /dps/{id}`, `HEAD /dps/{id}`)
|
|
57
|
+
- ✅ Cancelamento com evento e101101 assinado (`POST /nfse/{chave}/eventos`)
|
|
58
|
+
- ✅ Consulta de evento específico (`GET /nfse/{chave}/eventos/{tipo}/{seq}`)
|
|
59
|
+
- ✅ Download do DANFSe oficial (`GET /danfse/{chave}` no ADN)
|
|
60
|
+
- ✅ Sincronização DFe (`GET /contribuintes/DFe/{NSU}?cnpjConsulta=...`)
|
|
61
|
+
- ✅ Listagem de eventos de uma NFS-e (`GET /contribuintes/NFSe/{chave}/Eventos`)
|
|
62
|
+
- ✅ Carregamento de certificado A1 (.pfx/.p12) com extração de CNPJ do subject **ou** SAN ICP-Brasil
|
|
63
|
+
- ✅ Resolução automática da cadeia ICP-Brasil a partir de bundle PEM
|
|
64
|
+
- ✅ Modelos Pydantic v2 com validação (CEP, CNPJ, codigos IBGE, codigos LC 116)
|
|
65
|
+
- ✅ Sanitização Latin-1 do `xInfComp` (en-dash, aspas curvas, emojis)
|
|
66
|
+
- ✅ Patch automático do enum `TstipoRetPiscofins` da nfelib 2.5.2
|
|
67
|
+
- ✅ Tipagem estrita (mypy-friendly), erros tipados
|
|
68
|
+
|
|
69
|
+
## Instalação
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
pip install brans-nfe
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
Para gerar DANFSe não-oficial em PDF (auxiliar, quando o portal SEFIN está fora):
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
pip install "brans-nfe[danfse]"
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## Quick start
|
|
82
|
+
|
|
83
|
+
### Emitir NFS-e
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
from datetime import date
|
|
87
|
+
from decimal import Decimal
|
|
88
|
+
from brans_nfe import (
|
|
89
|
+
Ambiente, Certificado, NfseClient, NotaServico,
|
|
90
|
+
Prestador, Tomador, Servico, Valores, Endereco,
|
|
91
|
+
RegimeTributario, TributacaoIss,
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
certificado = Certificado.from_pfx_path("certificado.pfx", "senha-do-pfx")
|
|
95
|
+
|
|
96
|
+
client = NfseClient(
|
|
97
|
+
certificado=certificado,
|
|
98
|
+
ambiente=Ambiente.HOMOLOGACAO,
|
|
99
|
+
)
|
|
100
|
+
|
|
101
|
+
nota = NotaServico(
|
|
102
|
+
serie_rps="1",
|
|
103
|
+
numero_rps="42",
|
|
104
|
+
data_competencia=date(2026, 5, 18),
|
|
105
|
+
prestador=Prestador(
|
|
106
|
+
cnpj=certificado.cnpj,
|
|
107
|
+
razao_social=certificado.razao_social,
|
|
108
|
+
regime_tributario=RegimeTributario.SIMPLES_NACIONAL,
|
|
109
|
+
endereco=Endereco(
|
|
110
|
+
codigo_municipio_ibge="3304557",
|
|
111
|
+
cep="20010000",
|
|
112
|
+
logradouro="Rua do Prestador",
|
|
113
|
+
numero="100",
|
|
114
|
+
bairro="Centro",
|
|
115
|
+
),
|
|
116
|
+
),
|
|
117
|
+
tomador=Tomador(
|
|
118
|
+
cpf_cnpj="98.765.432/0001-10",
|
|
119
|
+
razao_social="CLIENTE TESTE SA",
|
|
120
|
+
endereco=Endereco(
|
|
121
|
+
codigo_municipio_ibge="3304557",
|
|
122
|
+
cep="20010001",
|
|
123
|
+
logradouro="Av. do Tomador",
|
|
124
|
+
numero="200",
|
|
125
|
+
bairro="Centro",
|
|
126
|
+
),
|
|
127
|
+
email="financeiro@cliente.com",
|
|
128
|
+
),
|
|
129
|
+
servico=Servico(
|
|
130
|
+
codigo_tributacao_nacional="010101",
|
|
131
|
+
codigo_municipio_prestacao="3304557",
|
|
132
|
+
discriminacao="Servico de consultoria em TI",
|
|
133
|
+
),
|
|
134
|
+
valores=Valores(
|
|
135
|
+
valor_bruto=Decimal("1000.00"),
|
|
136
|
+
valor_liquido=Decimal("950.00"),
|
|
137
|
+
),
|
|
138
|
+
iss=TributacaoIss(
|
|
139
|
+
aliquota=Decimal("5.00"),
|
|
140
|
+
valor=Decimal("50.00"),
|
|
141
|
+
base_calculo=Decimal("1000.00"),
|
|
142
|
+
),
|
|
143
|
+
)
|
|
144
|
+
|
|
145
|
+
resp = client.transmitir(nota)
|
|
146
|
+
print(resp.chave_acesso)
|
|
147
|
+
print(resp.id_dps)
|
|
148
|
+
print(resp.xml_nfse_retorno)
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Cancelar NFS-e
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
from brans_nfe import MotivoCancelamento
|
|
155
|
+
|
|
156
|
+
resp = client.cancelar(
|
|
157
|
+
chave_acesso=resp.chave_acesso,
|
|
158
|
+
motivo="Erro no preenchimento dos valores tributados",
|
|
159
|
+
motivo_codigo=MotivoCancelamento.ERRO_EMISSAO,
|
|
160
|
+
)
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
### Consultar e baixar DANFSe oficial
|
|
164
|
+
|
|
165
|
+
```python
|
|
166
|
+
nfse = client.consultar(chave_acesso="33...50_digitos")
|
|
167
|
+
pdf_bytes = client.baixar_danfse(chave_acesso="33...50_digitos")
|
|
168
|
+
|
|
169
|
+
with open("danfse.pdf", "wb") as f:
|
|
170
|
+
f.write(pdf_bytes)
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
### Sincronizar DFe (notas recebidas)
|
|
174
|
+
|
|
175
|
+
```python
|
|
176
|
+
resp = client.sincronizar_dfe(ultimo_nsu=0)
|
|
177
|
+
for item in resp.itens:
|
|
178
|
+
print(item.nsu, item.chave_acesso, item.tipo_documento)
|
|
179
|
+
print("Proximo NSU:", resp.ultimo_nsu)
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
### Recuperar de timeout: consultar DPS por Id
|
|
183
|
+
|
|
184
|
+
Se a transmissão deu timeout mas você quer descobrir se a NFS-e foi gerada:
|
|
185
|
+
|
|
186
|
+
```python
|
|
187
|
+
if client.existe_dps(id_dps="DPS3304557..."):
|
|
188
|
+
resp = client.consultar_dps(id_dps="DPS3304557...")
|
|
189
|
+
print("Foi emitida:", resp.chave_acesso)
|
|
190
|
+
```
|
|
191
|
+
|
|
192
|
+
### Construir DPS sem transmitir
|
|
193
|
+
|
|
194
|
+
Útil para preview, auditoria ou armazenamento prévio:
|
|
195
|
+
|
|
196
|
+
```python
|
|
197
|
+
from brans_nfe import construir_dps, serializar_dps, assinar_xml
|
|
198
|
+
|
|
199
|
+
dps = construir_dps(nota, Ambiente.HOMOLOGACAO)
|
|
200
|
+
xml = serializar_dps(dps)
|
|
201
|
+
xml_assinado = assinar_xml(xml, certificado)
|
|
202
|
+
```
|
|
203
|
+
|
|
204
|
+
## API
|
|
205
|
+
|
|
206
|
+
### Modelos (Pydantic v2)
|
|
207
|
+
|
|
208
|
+
| Modelo | Descrição |
|
|
209
|
+
|---|---|
|
|
210
|
+
| `NotaServico` | DPS completa: prestador, tomador, serviço, valores, ISS, PIS/COFINS, retenções |
|
|
211
|
+
| `Prestador` | CNPJ + razão social + regime tributário + endereço |
|
|
212
|
+
| `Tomador` | CPF ou CNPJ + razão social + endereço (opcional) + contato |
|
|
213
|
+
| `Servico` | Código LC 116 (6 dígitos) + município prestação + discriminação |
|
|
214
|
+
| `Endereco` | IBGE 7 dígitos + CEP 8 dígitos + logradouro + bairro |
|
|
215
|
+
| `Valores` | Valor bruto, líquido, descontos condicional/incondicional |
|
|
216
|
+
| `TributacaoIss` | Alíquota, valor, base, retenção (prestador/tomador/intermediário) |
|
|
217
|
+
| `TributacaoPisCofins` | CST, alíquotas, valores, retidos |
|
|
218
|
+
| `Retencoes` | IRRF, INSS, CSLL |
|
|
219
|
+
|
|
220
|
+
### Cliente
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
NfseClient(
|
|
224
|
+
certificado: Certificado,
|
|
225
|
+
ambiente: Ambiente = Ambiente.HOMOLOGACAO,
|
|
226
|
+
ca_bundle: str | Path | None = None,
|
|
227
|
+
chain_bundle: str | Path | None = None,
|
|
228
|
+
timeout: int = 120,
|
|
229
|
+
versao_aplicativo: str = "brans-nfe-0.1.0",
|
|
230
|
+
)
|
|
231
|
+
```
|
|
232
|
+
|
|
233
|
+
| Método | Endpoint | Retorno |
|
|
234
|
+
|---|---|---|
|
|
235
|
+
| `transmitir(nota)` | `POST /nfse` | `RespostaTransmissao` |
|
|
236
|
+
| `consultar(chave)` | `GET /nfse/{chave}` | `dict` |
|
|
237
|
+
| `consultar_dps(id)` | `GET /dps/{id}` | `RespostaConsultaDps` |
|
|
238
|
+
| `existe_dps(id)` | `HEAD /dps/{id}` | `bool` |
|
|
239
|
+
| `cancelar(chave, motivo)` | `POST /nfse/{chave}/eventos` | `RespostaEvento` |
|
|
240
|
+
| `consultar_evento(chave, codigo, seq)` | `GET /nfse/{chave}/eventos/{tipo}/{seq}` | `RespostaEvento` |
|
|
241
|
+
| `baixar_danfse(chave)` | `GET /danfse/{chave}` | `bytes` |
|
|
242
|
+
| `sincronizar_dfe(ultimo_nsu)` | `GET /contribuintes/DFe/{NSU}` | `RespostaDfe` |
|
|
243
|
+
| `listar_eventos_nfse(chave)` | `GET /contribuintes/NFSe/{chave}/Eventos` | `RespostaDfe` |
|
|
244
|
+
|
|
245
|
+
### Erros
|
|
246
|
+
|
|
247
|
+
Todos derivam de `BransNfeError`:
|
|
248
|
+
|
|
249
|
+
- `CertificadoError`, `CertificadoExpiradoError`, `CertificadoSenhaInvalidaError`
|
|
250
|
+
- `ValidacaoDpsError`
|
|
251
|
+
- `AssinaturaXmlError`
|
|
252
|
+
- `TransmissaoError`, `ConsultaError`, `CancelamentoError`, `DanfseIndisponivelError`, `SincronizacaoDfeError` (todos com `status_code` e `corpo`)
|
|
253
|
+
|
|
254
|
+
## Ambientes oficiais
|
|
255
|
+
|
|
256
|
+
| Serviço | Homologação | Produção |
|
|
257
|
+
|---|---|---|
|
|
258
|
+
| SEFIN | `https://sefin.producaorestrita.nfse.gov.br/SefinNacional` | `https://sefin.nfse.gov.br/SefinNacional` |
|
|
259
|
+
| ADN | `https://adn.producaorestrita.nfse.gov.br` | `https://adn.nfse.gov.br` |
|
|
260
|
+
|
|
261
|
+
O `NfseClient` resolve automaticamente com base no `Ambiente`.
|
|
262
|
+
|
|
263
|
+
## Por que outra lib?
|
|
264
|
+
|
|
265
|
+
A `nfelib` é excelente — fornece os bindings gerados a partir do XSD oficial — mas é apenas o **modelo de dados**. Pra emitir realmente uma NFS-e Nacional você precisa de:
|
|
266
|
+
|
|
267
|
+
| Etapa | nfelib | brans-nfe |
|
|
268
|
+
|---|---|---|
|
|
269
|
+
| Dataclasses do XSD (`Dps`, `Tcserv`, enums) | ✅ | reutiliza |
|
|
270
|
+
| Serializar dataclass → XML | ✅ (via xsdata) | reutiliza |
|
|
271
|
+
| Carregar PKCS#12 + extrair CNPJ ICP-Brasil | ❌ | ✅ |
|
|
272
|
+
| Resolver cadeia ICP-Brasil | ❌ | ✅ |
|
|
273
|
+
| Assinar XML-DSig (C14N + SHA1 + KeyInfo) | ❌ | ✅ |
|
|
274
|
+
| Gzip + base64 do payload | ❌ | ✅ |
|
|
275
|
+
| POST mTLS ao SEFIN | ❌ | ✅ |
|
|
276
|
+
| Consultar / Cancelar / Sincronizar DFe | ❌ | ✅ |
|
|
277
|
+
| Baixar DANFSe oficial | ❌ | ✅ |
|
|
278
|
+
| Patch de enum bugado da nfelib 2.5.2 | ❌ | ✅ |
|
|
279
|
+
| Sanitização Latin-1 do `xInfComp` | ❌ | ✅ |
|
|
280
|
+
| Modelagem Pydantic pronta pra usar | ❌ | ✅ |
|
|
281
|
+
|
|
282
|
+
## Desenvolvimento
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
git clone https://github.com/badbrans/brans-nfe.git
|
|
286
|
+
cd brans-nfe
|
|
287
|
+
|
|
288
|
+
python -m venv venv
|
|
289
|
+
.\venv\Scripts\Activate.ps1 # Windows
|
|
290
|
+
source venv/bin/activate # Linux/Mac
|
|
291
|
+
|
|
292
|
+
pip install -e ".[dev]"
|
|
293
|
+
|
|
294
|
+
pytest # 89 testes
|
|
295
|
+
pytest --cov=brans_nfe # com coverage
|
|
296
|
+
black src tests
|
|
297
|
+
isort src tests
|
|
298
|
+
mypy src
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
## Licença
|
|
302
|
+
|
|
303
|
+
[MIT](LICENSE) — Copyright (c) 2026 Raphael Brans
|
|
304
|
+
|
|
305
|
+
## Disclaimer
|
|
306
|
+
|
|
307
|
+
Esta biblioteca é um projeto independente e **não tem vínculo com a Receita Federal, SEFIN, ICP-Brasil ou Comitê Gestor da NFS-e Nacional**. Use em produção por sua conta e risco, em conformidade com a legislação vigente.
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
# brans-nfe
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/brans-nfe/)
|
|
4
|
+
[](https://pypi.org/project/brans-nfe/)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+
[](https://github.com/badbrans/brans-nfe/actions/workflows/tests.yml)
|
|
7
|
+
|
|
8
|
+
Cliente Python para a **NFS-e Nacional do Brasil** (SEFIN/Receita Federal) — emissão, consulta, cancelamento, distribuição de DFe e DANFSe, com assinatura XMLDSIG, mTLS com certificado A1 ICP-Brasil e payloads compactados em gzip+base64.
|
|
9
|
+
|
|
10
|
+
A `nfelib` fornece os bindings do XSD oficial. A `brans-nfe` foca na conversa com o SEFIN: PKCS#12, cadeia ICP-Brasil, assinatura XMLDSIG, transporte mTLS, paginação de DFe, mapeamentos de enums e modelagem de input.
|
|
11
|
+
|
|
12
|
+
## Recursos
|
|
13
|
+
|
|
14
|
+
- ✅ Emissão de NFS-e a partir de DPS (`POST /nfse`)
|
|
15
|
+
- ✅ Consulta de NFS-e por chave de acesso (`GET /nfse/{chave}`)
|
|
16
|
+
- ✅ Consulta/verificação de DPS por Id (`GET /dps/{id}`, `HEAD /dps/{id}`)
|
|
17
|
+
- ✅ Cancelamento com evento e101101 assinado (`POST /nfse/{chave}/eventos`)
|
|
18
|
+
- ✅ Consulta de evento específico (`GET /nfse/{chave}/eventos/{tipo}/{seq}`)
|
|
19
|
+
- ✅ Download do DANFSe oficial (`GET /danfse/{chave}` no ADN)
|
|
20
|
+
- ✅ Sincronização DFe (`GET /contribuintes/DFe/{NSU}?cnpjConsulta=...`)
|
|
21
|
+
- ✅ Listagem de eventos de uma NFS-e (`GET /contribuintes/NFSe/{chave}/Eventos`)
|
|
22
|
+
- ✅ Carregamento de certificado A1 (.pfx/.p12) com extração de CNPJ do subject **ou** SAN ICP-Brasil
|
|
23
|
+
- ✅ Resolução automática da cadeia ICP-Brasil a partir de bundle PEM
|
|
24
|
+
- ✅ Modelos Pydantic v2 com validação (CEP, CNPJ, codigos IBGE, codigos LC 116)
|
|
25
|
+
- ✅ Sanitização Latin-1 do `xInfComp` (en-dash, aspas curvas, emojis)
|
|
26
|
+
- ✅ Patch automático do enum `TstipoRetPiscofins` da nfelib 2.5.2
|
|
27
|
+
- ✅ Tipagem estrita (mypy-friendly), erros tipados
|
|
28
|
+
|
|
29
|
+
## Instalação
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
pip install brans-nfe
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Para gerar DANFSe não-oficial em PDF (auxiliar, quando o portal SEFIN está fora):
|
|
36
|
+
|
|
37
|
+
```bash
|
|
38
|
+
pip install "brans-nfe[danfse]"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Quick start
|
|
42
|
+
|
|
43
|
+
### Emitir NFS-e
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from datetime import date
|
|
47
|
+
from decimal import Decimal
|
|
48
|
+
from brans_nfe import (
|
|
49
|
+
Ambiente, Certificado, NfseClient, NotaServico,
|
|
50
|
+
Prestador, Tomador, Servico, Valores, Endereco,
|
|
51
|
+
RegimeTributario, TributacaoIss,
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
certificado = Certificado.from_pfx_path("certificado.pfx", "senha-do-pfx")
|
|
55
|
+
|
|
56
|
+
client = NfseClient(
|
|
57
|
+
certificado=certificado,
|
|
58
|
+
ambiente=Ambiente.HOMOLOGACAO,
|
|
59
|
+
)
|
|
60
|
+
|
|
61
|
+
nota = NotaServico(
|
|
62
|
+
serie_rps="1",
|
|
63
|
+
numero_rps="42",
|
|
64
|
+
data_competencia=date(2026, 5, 18),
|
|
65
|
+
prestador=Prestador(
|
|
66
|
+
cnpj=certificado.cnpj,
|
|
67
|
+
razao_social=certificado.razao_social,
|
|
68
|
+
regime_tributario=RegimeTributario.SIMPLES_NACIONAL,
|
|
69
|
+
endereco=Endereco(
|
|
70
|
+
codigo_municipio_ibge="3304557",
|
|
71
|
+
cep="20010000",
|
|
72
|
+
logradouro="Rua do Prestador",
|
|
73
|
+
numero="100",
|
|
74
|
+
bairro="Centro",
|
|
75
|
+
),
|
|
76
|
+
),
|
|
77
|
+
tomador=Tomador(
|
|
78
|
+
cpf_cnpj="98.765.432/0001-10",
|
|
79
|
+
razao_social="CLIENTE TESTE SA",
|
|
80
|
+
endereco=Endereco(
|
|
81
|
+
codigo_municipio_ibge="3304557",
|
|
82
|
+
cep="20010001",
|
|
83
|
+
logradouro="Av. do Tomador",
|
|
84
|
+
numero="200",
|
|
85
|
+
bairro="Centro",
|
|
86
|
+
),
|
|
87
|
+
email="financeiro@cliente.com",
|
|
88
|
+
),
|
|
89
|
+
servico=Servico(
|
|
90
|
+
codigo_tributacao_nacional="010101",
|
|
91
|
+
codigo_municipio_prestacao="3304557",
|
|
92
|
+
discriminacao="Servico de consultoria em TI",
|
|
93
|
+
),
|
|
94
|
+
valores=Valores(
|
|
95
|
+
valor_bruto=Decimal("1000.00"),
|
|
96
|
+
valor_liquido=Decimal("950.00"),
|
|
97
|
+
),
|
|
98
|
+
iss=TributacaoIss(
|
|
99
|
+
aliquota=Decimal("5.00"),
|
|
100
|
+
valor=Decimal("50.00"),
|
|
101
|
+
base_calculo=Decimal("1000.00"),
|
|
102
|
+
),
|
|
103
|
+
)
|
|
104
|
+
|
|
105
|
+
resp = client.transmitir(nota)
|
|
106
|
+
print(resp.chave_acesso)
|
|
107
|
+
print(resp.id_dps)
|
|
108
|
+
print(resp.xml_nfse_retorno)
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
### Cancelar NFS-e
|
|
112
|
+
|
|
113
|
+
```python
|
|
114
|
+
from brans_nfe import MotivoCancelamento
|
|
115
|
+
|
|
116
|
+
resp = client.cancelar(
|
|
117
|
+
chave_acesso=resp.chave_acesso,
|
|
118
|
+
motivo="Erro no preenchimento dos valores tributados",
|
|
119
|
+
motivo_codigo=MotivoCancelamento.ERRO_EMISSAO,
|
|
120
|
+
)
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Consultar e baixar DANFSe oficial
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
nfse = client.consultar(chave_acesso="33...50_digitos")
|
|
127
|
+
pdf_bytes = client.baixar_danfse(chave_acesso="33...50_digitos")
|
|
128
|
+
|
|
129
|
+
with open("danfse.pdf", "wb") as f:
|
|
130
|
+
f.write(pdf_bytes)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### Sincronizar DFe (notas recebidas)
|
|
134
|
+
|
|
135
|
+
```python
|
|
136
|
+
resp = client.sincronizar_dfe(ultimo_nsu=0)
|
|
137
|
+
for item in resp.itens:
|
|
138
|
+
print(item.nsu, item.chave_acesso, item.tipo_documento)
|
|
139
|
+
print("Proximo NSU:", resp.ultimo_nsu)
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
### Recuperar de timeout: consultar DPS por Id
|
|
143
|
+
|
|
144
|
+
Se a transmissão deu timeout mas você quer descobrir se a NFS-e foi gerada:
|
|
145
|
+
|
|
146
|
+
```python
|
|
147
|
+
if client.existe_dps(id_dps="DPS3304557..."):
|
|
148
|
+
resp = client.consultar_dps(id_dps="DPS3304557...")
|
|
149
|
+
print("Foi emitida:", resp.chave_acesso)
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
### Construir DPS sem transmitir
|
|
153
|
+
|
|
154
|
+
Útil para preview, auditoria ou armazenamento prévio:
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from brans_nfe import construir_dps, serializar_dps, assinar_xml
|
|
158
|
+
|
|
159
|
+
dps = construir_dps(nota, Ambiente.HOMOLOGACAO)
|
|
160
|
+
xml = serializar_dps(dps)
|
|
161
|
+
xml_assinado = assinar_xml(xml, certificado)
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
## API
|
|
165
|
+
|
|
166
|
+
### Modelos (Pydantic v2)
|
|
167
|
+
|
|
168
|
+
| Modelo | Descrição |
|
|
169
|
+
|---|---|
|
|
170
|
+
| `NotaServico` | DPS completa: prestador, tomador, serviço, valores, ISS, PIS/COFINS, retenções |
|
|
171
|
+
| `Prestador` | CNPJ + razão social + regime tributário + endereço |
|
|
172
|
+
| `Tomador` | CPF ou CNPJ + razão social + endereço (opcional) + contato |
|
|
173
|
+
| `Servico` | Código LC 116 (6 dígitos) + município prestação + discriminação |
|
|
174
|
+
| `Endereco` | IBGE 7 dígitos + CEP 8 dígitos + logradouro + bairro |
|
|
175
|
+
| `Valores` | Valor bruto, líquido, descontos condicional/incondicional |
|
|
176
|
+
| `TributacaoIss` | Alíquota, valor, base, retenção (prestador/tomador/intermediário) |
|
|
177
|
+
| `TributacaoPisCofins` | CST, alíquotas, valores, retidos |
|
|
178
|
+
| `Retencoes` | IRRF, INSS, CSLL |
|
|
179
|
+
|
|
180
|
+
### Cliente
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
NfseClient(
|
|
184
|
+
certificado: Certificado,
|
|
185
|
+
ambiente: Ambiente = Ambiente.HOMOLOGACAO,
|
|
186
|
+
ca_bundle: str | Path | None = None,
|
|
187
|
+
chain_bundle: str | Path | None = None,
|
|
188
|
+
timeout: int = 120,
|
|
189
|
+
versao_aplicativo: str = "brans-nfe-0.1.0",
|
|
190
|
+
)
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
| Método | Endpoint | Retorno |
|
|
194
|
+
|---|---|---|
|
|
195
|
+
| `transmitir(nota)` | `POST /nfse` | `RespostaTransmissao` |
|
|
196
|
+
| `consultar(chave)` | `GET /nfse/{chave}` | `dict` |
|
|
197
|
+
| `consultar_dps(id)` | `GET /dps/{id}` | `RespostaConsultaDps` |
|
|
198
|
+
| `existe_dps(id)` | `HEAD /dps/{id}` | `bool` |
|
|
199
|
+
| `cancelar(chave, motivo)` | `POST /nfse/{chave}/eventos` | `RespostaEvento` |
|
|
200
|
+
| `consultar_evento(chave, codigo, seq)` | `GET /nfse/{chave}/eventos/{tipo}/{seq}` | `RespostaEvento` |
|
|
201
|
+
| `baixar_danfse(chave)` | `GET /danfse/{chave}` | `bytes` |
|
|
202
|
+
| `sincronizar_dfe(ultimo_nsu)` | `GET /contribuintes/DFe/{NSU}` | `RespostaDfe` |
|
|
203
|
+
| `listar_eventos_nfse(chave)` | `GET /contribuintes/NFSe/{chave}/Eventos` | `RespostaDfe` |
|
|
204
|
+
|
|
205
|
+
### Erros
|
|
206
|
+
|
|
207
|
+
Todos derivam de `BransNfeError`:
|
|
208
|
+
|
|
209
|
+
- `CertificadoError`, `CertificadoExpiradoError`, `CertificadoSenhaInvalidaError`
|
|
210
|
+
- `ValidacaoDpsError`
|
|
211
|
+
- `AssinaturaXmlError`
|
|
212
|
+
- `TransmissaoError`, `ConsultaError`, `CancelamentoError`, `DanfseIndisponivelError`, `SincronizacaoDfeError` (todos com `status_code` e `corpo`)
|
|
213
|
+
|
|
214
|
+
## Ambientes oficiais
|
|
215
|
+
|
|
216
|
+
| Serviço | Homologação | Produção |
|
|
217
|
+
|---|---|---|
|
|
218
|
+
| SEFIN | `https://sefin.producaorestrita.nfse.gov.br/SefinNacional` | `https://sefin.nfse.gov.br/SefinNacional` |
|
|
219
|
+
| ADN | `https://adn.producaorestrita.nfse.gov.br` | `https://adn.nfse.gov.br` |
|
|
220
|
+
|
|
221
|
+
O `NfseClient` resolve automaticamente com base no `Ambiente`.
|
|
222
|
+
|
|
223
|
+
## Por que outra lib?
|
|
224
|
+
|
|
225
|
+
A `nfelib` é excelente — fornece os bindings gerados a partir do XSD oficial — mas é apenas o **modelo de dados**. Pra emitir realmente uma NFS-e Nacional você precisa de:
|
|
226
|
+
|
|
227
|
+
| Etapa | nfelib | brans-nfe |
|
|
228
|
+
|---|---|---|
|
|
229
|
+
| Dataclasses do XSD (`Dps`, `Tcserv`, enums) | ✅ | reutiliza |
|
|
230
|
+
| Serializar dataclass → XML | ✅ (via xsdata) | reutiliza |
|
|
231
|
+
| Carregar PKCS#12 + extrair CNPJ ICP-Brasil | ❌ | ✅ |
|
|
232
|
+
| Resolver cadeia ICP-Brasil | ❌ | ✅ |
|
|
233
|
+
| Assinar XML-DSig (C14N + SHA1 + KeyInfo) | ❌ | ✅ |
|
|
234
|
+
| Gzip + base64 do payload | ❌ | ✅ |
|
|
235
|
+
| POST mTLS ao SEFIN | ❌ | ✅ |
|
|
236
|
+
| Consultar / Cancelar / Sincronizar DFe | ❌ | ✅ |
|
|
237
|
+
| Baixar DANFSe oficial | ❌ | ✅ |
|
|
238
|
+
| Patch de enum bugado da nfelib 2.5.2 | ❌ | ✅ |
|
|
239
|
+
| Sanitização Latin-1 do `xInfComp` | ❌ | ✅ |
|
|
240
|
+
| Modelagem Pydantic pronta pra usar | ❌ | ✅ |
|
|
241
|
+
|
|
242
|
+
## Desenvolvimento
|
|
243
|
+
|
|
244
|
+
```bash
|
|
245
|
+
git clone https://github.com/badbrans/brans-nfe.git
|
|
246
|
+
cd brans-nfe
|
|
247
|
+
|
|
248
|
+
python -m venv venv
|
|
249
|
+
.\venv\Scripts\Activate.ps1 # Windows
|
|
250
|
+
source venv/bin/activate # Linux/Mac
|
|
251
|
+
|
|
252
|
+
pip install -e ".[dev]"
|
|
253
|
+
|
|
254
|
+
pytest # 89 testes
|
|
255
|
+
pytest --cov=brans_nfe # com coverage
|
|
256
|
+
black src tests
|
|
257
|
+
isort src tests
|
|
258
|
+
mypy src
|
|
259
|
+
```
|
|
260
|
+
|
|
261
|
+
## Licença
|
|
262
|
+
|
|
263
|
+
[MIT](LICENSE) — Copyright (c) 2026 Raphael Brans
|
|
264
|
+
|
|
265
|
+
## Disclaimer
|
|
266
|
+
|
|
267
|
+
Esta biblioteca é um projeto independente e **não tem vínculo com a Receita Federal, SEFIN, ICP-Brasil ou Comitê Gestor da NFS-e Nacional**. Use em produção por sua conta e risco, em conformidade com a legislação vigente.
|