convertaja 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.
- convertaja-0.1.0/LICENSE +21 -0
- convertaja-0.1.0/PKG-INFO +234 -0
- convertaja-0.1.0/README.md +214 -0
- convertaja-0.1.0/pyproject.toml +35 -0
- convertaja-0.1.0/setup.cfg +4 -0
- convertaja-0.1.0/src/convertaja/__init__.py +23 -0
- convertaja-0.1.0/src/convertaja/client.py +658 -0
- convertaja-0.1.0/src/convertaja/errors.py +74 -0
- convertaja-0.1.0/src/convertaja/py.typed +0 -0
- convertaja-0.1.0/src/convertaja/webhooks.py +93 -0
- convertaja-0.1.0/src/convertaja.egg-info/PKG-INFO +234 -0
- convertaja-0.1.0/src/convertaja.egg-info/SOURCES.txt +13 -0
- convertaja-0.1.0/src/convertaja.egg-info/dependency_links.txt +1 -0
- convertaja-0.1.0/src/convertaja.egg-info/top_level.txt +1 -0
- convertaja-0.1.0/tests/test_client.py +393 -0
convertaja-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Convertajá
|
|
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.
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: convertaja
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: SDK oficial da API do Convertajá: PDF, DANFE, etiquetas ZPL, assinatura eletrônica, CNPJ, CEP, boleto e Pix.
|
|
5
|
+
Author-email: Convertajá <contato@convertaja.com.br>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://convertaja.com.br/api
|
|
8
|
+
Project-URL: Documentation, https://convertaja.com.br/api/docs
|
|
9
|
+
Keywords: convertaja,pdf,danfe,nfe,zpl,cnpj,cep,boleto,pix,assinatura eletronica,api,sdk
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Natural Language :: Portuguese (Brazilian)
|
|
15
|
+
Classifier: Typing :: Typed
|
|
16
|
+
Requires-Python: >=3.9
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Dynamic: license-file
|
|
20
|
+
|
|
21
|
+
# convertaja (Python)
|
|
22
|
+
|
|
23
|
+
SDK oficial da [API do Convertajá](https://convertaja.com.br/api) para Python: comprimir e converter PDF, gerar DANFE a partir do XML da NF-e, converter etiquetas ZPL, assinatura eletrônica, Central de Expedição e consultas de CNPJ, CEP, boleto e Pix.
|
|
24
|
+
|
|
25
|
+
- Só biblioteca padrão (`urllib`), sem `requests`. Python 3.9 ou mais novo, com type hints (`py.typed`).
|
|
26
|
+
- Repete sozinho em falha de rede, 429 (respeitando `Retry-After`) e 5xx, **sem cobrar duas vezes** (Idempotency-Key automática).
|
|
27
|
+
|
|
28
|
+
> **Ainda não publicado no PyPI.** Até a publicação, instale pelo caminho local ou pelo repositório git (veja abaixo).
|
|
29
|
+
|
|
30
|
+
## Instalação
|
|
31
|
+
|
|
32
|
+
```bash
|
|
33
|
+
# caminho local
|
|
34
|
+
pip install ./caminho/para/integrations/sdk-python
|
|
35
|
+
|
|
36
|
+
# ou direto do git (quando o repositório estiver disponível)
|
|
37
|
+
pip install "git+https://github.com/convertaja/sdk-python.git"
|
|
38
|
+
|
|
39
|
+
# depois da publicação
|
|
40
|
+
pip install convertaja
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Primeiros passos
|
|
44
|
+
|
|
45
|
+
Crie a chave em **Minha conta → API** (`/conta/api`). Use `cj_test_…` para testar (não consome créditos, até 20 chamadas por dia) e `cj_live_…` em produção.
|
|
46
|
+
|
|
47
|
+
```python
|
|
48
|
+
import os
|
|
49
|
+
from convertaja import Convertaja
|
|
50
|
+
|
|
51
|
+
cj = Convertaja(api_key=os.environ["CONVERTAJA_API_KEY"]) # ou só Convertaja() lendo CONVERTAJA_API_KEY
|
|
52
|
+
|
|
53
|
+
me = cj.me()
|
|
54
|
+
print(me["plan"]["label"], me["credits"]["remaining"])
|
|
55
|
+
print("Créditos depois da última chamada:", cj.credits_remaining)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Opções: `base_url` (padrão `https://convertaja.com.br`), `max_retries` (padrão 2), `timeout` em segundos (padrão 120), `retry_delay` (padrão 0,5 s).
|
|
59
|
+
|
|
60
|
+
### Comprimir um PDF
|
|
61
|
+
|
|
62
|
+
```python
|
|
63
|
+
# wait=True segura a resposta até 60 s; se ainda não terminou, use jobs.wait
|
|
64
|
+
job = cj.tools.run(
|
|
65
|
+
"comprimir-pdf",
|
|
66
|
+
files=["contrato.pdf"], # caminho, bytes, arquivo aberto, {"data", "name"}, {"url"} ou {"base64", "name"}
|
|
67
|
+
options={"level": "high"}, # low | medium | high
|
|
68
|
+
wait=True,
|
|
69
|
+
)
|
|
70
|
+
if job["status"] not in ("done", "error"):
|
|
71
|
+
job = cj.jobs.wait(job["id"])
|
|
72
|
+
if job["status"] == "error":
|
|
73
|
+
raise RuntimeError(job["error"]["message"])
|
|
74
|
+
|
|
75
|
+
arquivo = cj.jobs.download(job["id"]) # um arquivo, ou ZIP se houver vários
|
|
76
|
+
arquivo.save(arquivo.filename or "comprimido.pdf")
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Consultar CNPJ e CEP (grátis)
|
|
80
|
+
|
|
81
|
+
```python
|
|
82
|
+
empresa = cj.lookups.cnpj("11.222.333/0001-81") # aceita o CNPJ alfanumérico
|
|
83
|
+
print(empresa["razaoSocial"], empresa["situacao"])
|
|
84
|
+
|
|
85
|
+
endereco = cj.lookups.cep("01310-100")
|
|
86
|
+
print(endereco["logradouro"], endereco["cidade"], endereco["uf"])
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### DANFE a partir do XML da NF-e
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
job = cj.tools.run(
|
|
93
|
+
"xml-nfe-para-pdf",
|
|
94
|
+
files=["nota.xml"], # vários XMLs ou um ZIP
|
|
95
|
+
options={"layout": "a4"}, # a4 | simplificado (10×15, etiqueta)
|
|
96
|
+
wait=True,
|
|
97
|
+
)
|
|
98
|
+
cj.jobs.download(job["id"]).save("danfe.pdf")
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### ZPL para PDF
|
|
102
|
+
|
|
103
|
+
```python
|
|
104
|
+
# código ZPL direto nas opções (sem arquivo)...
|
|
105
|
+
job = cj.tools.run(
|
|
106
|
+
"zpl-para-pdf",
|
|
107
|
+
options={"zpl": "^XA^FO50,50^A0N,40,40^FDOlá^FS^XZ", "width_mm": 100, "height_mm": 150, "dpmm": "8"},
|
|
108
|
+
wait=True,
|
|
109
|
+
)
|
|
110
|
+
# ...ou arquivos .zpl/.txt
|
|
111
|
+
job = cj.tools.run("zpl-para-pdf", files=["etiquetas.zpl"], wait=True)
|
|
112
|
+
cj.jobs.download(job["id"]).save("etiquetas.pdf")
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Liste as ferramentas, extensões aceitas e opções (JSON Schema) com `cj.tools.list()` ou `cj.tools.get("comprimir-pdf")`.
|
|
116
|
+
|
|
117
|
+
### Assinatura eletrônica (envelope)
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
envelope = cj.esign.envelopes.create(
|
|
121
|
+
"contrato.pdf", # ou {"url": "https://…/contrato.pdf"}
|
|
122
|
+
signers=[
|
|
123
|
+
{"name": "Ana Souza", "email": "ana@cliente.com.br", "role": "sign"},
|
|
124
|
+
{"name": "Bruno Lima", "email": "bruno@empresa.com.br", "role": "witness"},
|
|
125
|
+
],
|
|
126
|
+
fields=[
|
|
127
|
+
{"signer": 0, "type": "signature", "page": 0, "x": 0.1, "y": 0.75, "w": 0.3, "h": 0.07},
|
|
128
|
+
{"signer": 1, "type": "signature", "page": 0, "x": 0.55, "y": 0.75, "w": 0.3, "h": 0.07},
|
|
129
|
+
],
|
|
130
|
+
name="Contrato de prestação de serviços",
|
|
131
|
+
message="Olá! Segue o contrato para assinatura.",
|
|
132
|
+
webhook_url="https://seusite.com.br/webhooks/convertaja",
|
|
133
|
+
)
|
|
134
|
+
print(envelope["id"], envelope["status"]) # K7QD-9WZ2-M4HT-R8PA pending
|
|
135
|
+
|
|
136
|
+
# depois que todos assinarem:
|
|
137
|
+
cj.esign.envelopes.download_final(envelope["id"]).save("contrato-assinado.pdf")
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Também há `list`, `list_all`, `get(id, events=True, links=True)`, `send` (rascunhos criados com `send=False`), `remind`, `cancel`, `delete` e `download_audit`.
|
|
141
|
+
|
|
142
|
+
### Outros recursos
|
|
143
|
+
|
|
144
|
+
```python
|
|
145
|
+
cj.validate("12.ABC.345/01DE-35", type="cnpj")
|
|
146
|
+
cj.validate(values=["123.456.789-09", "11.222.333/0001-81"]) # lote: 1 crédito a cada 50
|
|
147
|
+
cj.boleto.parse("23793.38128 86000.782713 36950.000632 1 10000000012345")
|
|
148
|
+
cj.pix.brcode("contato@empresa.com.br", "Empresa", "SAO PAULO", amount=10.5, format="png")
|
|
149
|
+
cj.qrcode("https://convertaja.com.br", format="svg")
|
|
150
|
+
cj.barcode("ean13", "789123456789", format="png")
|
|
151
|
+
|
|
152
|
+
# Tarefas
|
|
153
|
+
page = cj.jobs.list(status="done", limit=20) # page["next_cursor"] → starting_after
|
|
154
|
+
for job in cj.jobs.list_all(tool="comprimir-pdf"):
|
|
155
|
+
print(job["id"])
|
|
156
|
+
cj.jobs.download_file(job_id, 0).save("saida.pdf")
|
|
157
|
+
cj.jobs.delete(job_id) # apaga os arquivos antes de 1 hora (LGPD)
|
|
158
|
+
|
|
159
|
+
# Central de Expedição: etiquetas + XMLs das notas
|
|
160
|
+
lote = cj.expedicao.lotes.create(["etiquetas.pdf"], ["notas.zip"], intercalar=True, wait=True)
|
|
161
|
+
lote = cj.expedicao.lotes.wait(lote["id"])
|
|
162
|
+
cj.expedicao.lotes.download(lote["id"]).save("expedicao.zip")
|
|
163
|
+
|
|
164
|
+
# Webhook de teste (envia um evento assinado para a sua URL)
|
|
165
|
+
cj.webhooks.test("https://seusite.com.br/webhooks/convertaja")
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
## Webhooks: verificação da assinatura
|
|
169
|
+
|
|
170
|
+
Cada aviso chega com `X-Convertaja-Signature: t=<unix>,v1=<hex>`, onde `v1 = HMAC-SHA256(segredo, "<t>.<corpo bruto>")`. O segredo (`whsec_…`) fica em **Minha conta → API**. Verifique sempre com o **corpo bruto** e recuse assinaturas com mais de 300 s.
|
|
171
|
+
|
|
172
|
+
```python
|
|
173
|
+
import os
|
|
174
|
+
from flask import Flask, request, abort
|
|
175
|
+
from convertaja import webhooks, ConvertajaSignatureError
|
|
176
|
+
|
|
177
|
+
app = Flask(__name__)
|
|
178
|
+
|
|
179
|
+
@app.post("/webhooks/convertaja")
|
|
180
|
+
def convertaja_webhook():
|
|
181
|
+
try:
|
|
182
|
+
event = webhooks.construct_event(
|
|
183
|
+
request.get_data(), # bytes exatos recebidos
|
|
184
|
+
request.headers.get("X-Convertaja-Signature"),
|
|
185
|
+
os.environ["CONVERTAJA_WEBHOOK_SECRET"],
|
|
186
|
+
)
|
|
187
|
+
except ConvertajaSignatureError:
|
|
188
|
+
abort(400)
|
|
189
|
+
if event["type"] == "job.completed":
|
|
190
|
+
tarefa = event["data"] # id, outputs, download_url…
|
|
191
|
+
elif event["type"] == "esign.envelope.completed":
|
|
192
|
+
envelope = event["data"]
|
|
193
|
+
return "", 200 # responda 2xx rápido; o Convertajá tenta até 3 vezes
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
`webhooks.verify(raw_body, header, secret, tolerance_sec=300)` devolve `True`/`False` se você preferir tratar sozinho. Use o cabeçalho `X-Convertaja-Delivery` para descartar entregas repetidas.
|
|
197
|
+
|
|
198
|
+
## Erros
|
|
199
|
+
|
|
200
|
+
Toda falha vira `ConvertajaError`:
|
|
201
|
+
|
|
202
|
+
```python
|
|
203
|
+
from convertaja import ConvertajaError
|
|
204
|
+
|
|
205
|
+
try:
|
|
206
|
+
cj.tools.run("comprimir-pdf", files=["grande.pdf"])
|
|
207
|
+
except ConvertajaError as e:
|
|
208
|
+
print(e.status) # 402
|
|
209
|
+
print(e.code) # "insufficient_credits" (estável, para tratar no código)
|
|
210
|
+
print(e.message) # mensagem em português
|
|
211
|
+
print(e.request_id) # informe ao suporte
|
|
212
|
+
print(e.details) # corpo de erro completo (ex.: credits_required)
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
Códigos do SDK, além dos da API: `network_error`, `timeout`, `wait_timeout` (em `jobs.wait`), `missing_api_key`, `invalid_signature`. Lista completa em [/api/docs](https://convertaja.com.br/api/docs).
|
|
216
|
+
|
|
217
|
+
## Novas tentativas e idempotência
|
|
218
|
+
|
|
219
|
+
- Repete até `max_retries` vezes (padrão 2) em falha de rede, tempo esgotado, 408, 429 (exceto `test_limit` e `reminder_too_soon`), 5xx e 409 `idempotency_in_progress`.
|
|
220
|
+
- Em 429 espera o `Retry-After` da API (até 60 s); nos outros casos usa recuo exponencial (0,5 s, 1 s, 2 s… até 8 s).
|
|
221
|
+
- Todo POST leva uma `Idempotency-Key` (UUID) gerada **uma vez por chamada** e repetida em todas as tentativas: se a primeira chegou a ser processada, a repetição devolve a mesma resposta, sem novo débito.
|
|
222
|
+
- Para repetir com segurança entre processos, passe a sua chave: `cj.tools.run("comprimir-pdf", files=[...], idempotency_key=f"pedido-{pedido.id}")`. A mesma chave com outro conteúdo em 24 h dá 409 `idempotency_key_reused`.
|
|
223
|
+
- Depois de cada chamada: `cj.credits_remaining`, `cj.last_rate_limit`, `cj.last_request_id` e `cj.last_response` (`status`, `attempts`, `idempotency_key`, `replayed`).
|
|
224
|
+
- `timeout` vale por operação de rede (conexão e cada leitura), como no `urllib`.
|
|
225
|
+
|
|
226
|
+
## Testes
|
|
227
|
+
|
|
228
|
+
```bash
|
|
229
|
+
python3 -I -m unittest discover -s tests -v # servidor HTTP local numa thread; nenhuma chamada sai da máquina
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## Licença
|
|
233
|
+
|
|
234
|
+
MIT
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# convertaja (Python)
|
|
2
|
+
|
|
3
|
+
SDK oficial da [API do Convertajá](https://convertaja.com.br/api) para Python: comprimir e converter PDF, gerar DANFE a partir do XML da NF-e, converter etiquetas ZPL, assinatura eletrônica, Central de Expedição e consultas de CNPJ, CEP, boleto e Pix.
|
|
4
|
+
|
|
5
|
+
- Só biblioteca padrão (`urllib`), sem `requests`. Python 3.9 ou mais novo, com type hints (`py.typed`).
|
|
6
|
+
- Repete sozinho em falha de rede, 429 (respeitando `Retry-After`) e 5xx, **sem cobrar duas vezes** (Idempotency-Key automática).
|
|
7
|
+
|
|
8
|
+
> **Ainda não publicado no PyPI.** Até a publicação, instale pelo caminho local ou pelo repositório git (veja abaixo).
|
|
9
|
+
|
|
10
|
+
## Instalação
|
|
11
|
+
|
|
12
|
+
```bash
|
|
13
|
+
# caminho local
|
|
14
|
+
pip install ./caminho/para/integrations/sdk-python
|
|
15
|
+
|
|
16
|
+
# ou direto do git (quando o repositório estiver disponível)
|
|
17
|
+
pip install "git+https://github.com/convertaja/sdk-python.git"
|
|
18
|
+
|
|
19
|
+
# depois da publicação
|
|
20
|
+
pip install convertaja
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Primeiros passos
|
|
24
|
+
|
|
25
|
+
Crie a chave em **Minha conta → API** (`/conta/api`). Use `cj_test_…` para testar (não consome créditos, até 20 chamadas por dia) e `cj_live_…` em produção.
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
import os
|
|
29
|
+
from convertaja import Convertaja
|
|
30
|
+
|
|
31
|
+
cj = Convertaja(api_key=os.environ["CONVERTAJA_API_KEY"]) # ou só Convertaja() lendo CONVERTAJA_API_KEY
|
|
32
|
+
|
|
33
|
+
me = cj.me()
|
|
34
|
+
print(me["plan"]["label"], me["credits"]["remaining"])
|
|
35
|
+
print("Créditos depois da última chamada:", cj.credits_remaining)
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Opções: `base_url` (padrão `https://convertaja.com.br`), `max_retries` (padrão 2), `timeout` em segundos (padrão 120), `retry_delay` (padrão 0,5 s).
|
|
39
|
+
|
|
40
|
+
### Comprimir um PDF
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
# wait=True segura a resposta até 60 s; se ainda não terminou, use jobs.wait
|
|
44
|
+
job = cj.tools.run(
|
|
45
|
+
"comprimir-pdf",
|
|
46
|
+
files=["contrato.pdf"], # caminho, bytes, arquivo aberto, {"data", "name"}, {"url"} ou {"base64", "name"}
|
|
47
|
+
options={"level": "high"}, # low | medium | high
|
|
48
|
+
wait=True,
|
|
49
|
+
)
|
|
50
|
+
if job["status"] not in ("done", "error"):
|
|
51
|
+
job = cj.jobs.wait(job["id"])
|
|
52
|
+
if job["status"] == "error":
|
|
53
|
+
raise RuntimeError(job["error"]["message"])
|
|
54
|
+
|
|
55
|
+
arquivo = cj.jobs.download(job["id"]) # um arquivo, ou ZIP se houver vários
|
|
56
|
+
arquivo.save(arquivo.filename or "comprimido.pdf")
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### Consultar CNPJ e CEP (grátis)
|
|
60
|
+
|
|
61
|
+
```python
|
|
62
|
+
empresa = cj.lookups.cnpj("11.222.333/0001-81") # aceita o CNPJ alfanumérico
|
|
63
|
+
print(empresa["razaoSocial"], empresa["situacao"])
|
|
64
|
+
|
|
65
|
+
endereco = cj.lookups.cep("01310-100")
|
|
66
|
+
print(endereco["logradouro"], endereco["cidade"], endereco["uf"])
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
### DANFE a partir do XML da NF-e
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
job = cj.tools.run(
|
|
73
|
+
"xml-nfe-para-pdf",
|
|
74
|
+
files=["nota.xml"], # vários XMLs ou um ZIP
|
|
75
|
+
options={"layout": "a4"}, # a4 | simplificado (10×15, etiqueta)
|
|
76
|
+
wait=True,
|
|
77
|
+
)
|
|
78
|
+
cj.jobs.download(job["id"]).save("danfe.pdf")
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### ZPL para PDF
|
|
82
|
+
|
|
83
|
+
```python
|
|
84
|
+
# código ZPL direto nas opções (sem arquivo)...
|
|
85
|
+
job = cj.tools.run(
|
|
86
|
+
"zpl-para-pdf",
|
|
87
|
+
options={"zpl": "^XA^FO50,50^A0N,40,40^FDOlá^FS^XZ", "width_mm": 100, "height_mm": 150, "dpmm": "8"},
|
|
88
|
+
wait=True,
|
|
89
|
+
)
|
|
90
|
+
# ...ou arquivos .zpl/.txt
|
|
91
|
+
job = cj.tools.run("zpl-para-pdf", files=["etiquetas.zpl"], wait=True)
|
|
92
|
+
cj.jobs.download(job["id"]).save("etiquetas.pdf")
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
Liste as ferramentas, extensões aceitas e opções (JSON Schema) com `cj.tools.list()` ou `cj.tools.get("comprimir-pdf")`.
|
|
96
|
+
|
|
97
|
+
### Assinatura eletrônica (envelope)
|
|
98
|
+
|
|
99
|
+
```python
|
|
100
|
+
envelope = cj.esign.envelopes.create(
|
|
101
|
+
"contrato.pdf", # ou {"url": "https://…/contrato.pdf"}
|
|
102
|
+
signers=[
|
|
103
|
+
{"name": "Ana Souza", "email": "ana@cliente.com.br", "role": "sign"},
|
|
104
|
+
{"name": "Bruno Lima", "email": "bruno@empresa.com.br", "role": "witness"},
|
|
105
|
+
],
|
|
106
|
+
fields=[
|
|
107
|
+
{"signer": 0, "type": "signature", "page": 0, "x": 0.1, "y": 0.75, "w": 0.3, "h": 0.07},
|
|
108
|
+
{"signer": 1, "type": "signature", "page": 0, "x": 0.55, "y": 0.75, "w": 0.3, "h": 0.07},
|
|
109
|
+
],
|
|
110
|
+
name="Contrato de prestação de serviços",
|
|
111
|
+
message="Olá! Segue o contrato para assinatura.",
|
|
112
|
+
webhook_url="https://seusite.com.br/webhooks/convertaja",
|
|
113
|
+
)
|
|
114
|
+
print(envelope["id"], envelope["status"]) # K7QD-9WZ2-M4HT-R8PA pending
|
|
115
|
+
|
|
116
|
+
# depois que todos assinarem:
|
|
117
|
+
cj.esign.envelopes.download_final(envelope["id"]).save("contrato-assinado.pdf")
|
|
118
|
+
```
|
|
119
|
+
|
|
120
|
+
Também há `list`, `list_all`, `get(id, events=True, links=True)`, `send` (rascunhos criados com `send=False`), `remind`, `cancel`, `delete` e `download_audit`.
|
|
121
|
+
|
|
122
|
+
### Outros recursos
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
cj.validate("12.ABC.345/01DE-35", type="cnpj")
|
|
126
|
+
cj.validate(values=["123.456.789-09", "11.222.333/0001-81"]) # lote: 1 crédito a cada 50
|
|
127
|
+
cj.boleto.parse("23793.38128 86000.782713 36950.000632 1 10000000012345")
|
|
128
|
+
cj.pix.brcode("contato@empresa.com.br", "Empresa", "SAO PAULO", amount=10.5, format="png")
|
|
129
|
+
cj.qrcode("https://convertaja.com.br", format="svg")
|
|
130
|
+
cj.barcode("ean13", "789123456789", format="png")
|
|
131
|
+
|
|
132
|
+
# Tarefas
|
|
133
|
+
page = cj.jobs.list(status="done", limit=20) # page["next_cursor"] → starting_after
|
|
134
|
+
for job in cj.jobs.list_all(tool="comprimir-pdf"):
|
|
135
|
+
print(job["id"])
|
|
136
|
+
cj.jobs.download_file(job_id, 0).save("saida.pdf")
|
|
137
|
+
cj.jobs.delete(job_id) # apaga os arquivos antes de 1 hora (LGPD)
|
|
138
|
+
|
|
139
|
+
# Central de Expedição: etiquetas + XMLs das notas
|
|
140
|
+
lote = cj.expedicao.lotes.create(["etiquetas.pdf"], ["notas.zip"], intercalar=True, wait=True)
|
|
141
|
+
lote = cj.expedicao.lotes.wait(lote["id"])
|
|
142
|
+
cj.expedicao.lotes.download(lote["id"]).save("expedicao.zip")
|
|
143
|
+
|
|
144
|
+
# Webhook de teste (envia um evento assinado para a sua URL)
|
|
145
|
+
cj.webhooks.test("https://seusite.com.br/webhooks/convertaja")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
## Webhooks: verificação da assinatura
|
|
149
|
+
|
|
150
|
+
Cada aviso chega com `X-Convertaja-Signature: t=<unix>,v1=<hex>`, onde `v1 = HMAC-SHA256(segredo, "<t>.<corpo bruto>")`. O segredo (`whsec_…`) fica em **Minha conta → API**. Verifique sempre com o **corpo bruto** e recuse assinaturas com mais de 300 s.
|
|
151
|
+
|
|
152
|
+
```python
|
|
153
|
+
import os
|
|
154
|
+
from flask import Flask, request, abort
|
|
155
|
+
from convertaja import webhooks, ConvertajaSignatureError
|
|
156
|
+
|
|
157
|
+
app = Flask(__name__)
|
|
158
|
+
|
|
159
|
+
@app.post("/webhooks/convertaja")
|
|
160
|
+
def convertaja_webhook():
|
|
161
|
+
try:
|
|
162
|
+
event = webhooks.construct_event(
|
|
163
|
+
request.get_data(), # bytes exatos recebidos
|
|
164
|
+
request.headers.get("X-Convertaja-Signature"),
|
|
165
|
+
os.environ["CONVERTAJA_WEBHOOK_SECRET"],
|
|
166
|
+
)
|
|
167
|
+
except ConvertajaSignatureError:
|
|
168
|
+
abort(400)
|
|
169
|
+
if event["type"] == "job.completed":
|
|
170
|
+
tarefa = event["data"] # id, outputs, download_url…
|
|
171
|
+
elif event["type"] == "esign.envelope.completed":
|
|
172
|
+
envelope = event["data"]
|
|
173
|
+
return "", 200 # responda 2xx rápido; o Convertajá tenta até 3 vezes
|
|
174
|
+
```
|
|
175
|
+
|
|
176
|
+
`webhooks.verify(raw_body, header, secret, tolerance_sec=300)` devolve `True`/`False` se você preferir tratar sozinho. Use o cabeçalho `X-Convertaja-Delivery` para descartar entregas repetidas.
|
|
177
|
+
|
|
178
|
+
## Erros
|
|
179
|
+
|
|
180
|
+
Toda falha vira `ConvertajaError`:
|
|
181
|
+
|
|
182
|
+
```python
|
|
183
|
+
from convertaja import ConvertajaError
|
|
184
|
+
|
|
185
|
+
try:
|
|
186
|
+
cj.tools.run("comprimir-pdf", files=["grande.pdf"])
|
|
187
|
+
except ConvertajaError as e:
|
|
188
|
+
print(e.status) # 402
|
|
189
|
+
print(e.code) # "insufficient_credits" (estável, para tratar no código)
|
|
190
|
+
print(e.message) # mensagem em português
|
|
191
|
+
print(e.request_id) # informe ao suporte
|
|
192
|
+
print(e.details) # corpo de erro completo (ex.: credits_required)
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
Códigos do SDK, além dos da API: `network_error`, `timeout`, `wait_timeout` (em `jobs.wait`), `missing_api_key`, `invalid_signature`. Lista completa em [/api/docs](https://convertaja.com.br/api/docs).
|
|
196
|
+
|
|
197
|
+
## Novas tentativas e idempotência
|
|
198
|
+
|
|
199
|
+
- Repete até `max_retries` vezes (padrão 2) em falha de rede, tempo esgotado, 408, 429 (exceto `test_limit` e `reminder_too_soon`), 5xx e 409 `idempotency_in_progress`.
|
|
200
|
+
- Em 429 espera o `Retry-After` da API (até 60 s); nos outros casos usa recuo exponencial (0,5 s, 1 s, 2 s… até 8 s).
|
|
201
|
+
- Todo POST leva uma `Idempotency-Key` (UUID) gerada **uma vez por chamada** e repetida em todas as tentativas: se a primeira chegou a ser processada, a repetição devolve a mesma resposta, sem novo débito.
|
|
202
|
+
- Para repetir com segurança entre processos, passe a sua chave: `cj.tools.run("comprimir-pdf", files=[...], idempotency_key=f"pedido-{pedido.id}")`. A mesma chave com outro conteúdo em 24 h dá 409 `idempotency_key_reused`.
|
|
203
|
+
- Depois de cada chamada: `cj.credits_remaining`, `cj.last_rate_limit`, `cj.last_request_id` e `cj.last_response` (`status`, `attempts`, `idempotency_key`, `replayed`).
|
|
204
|
+
- `timeout` vale por operação de rede (conexão e cada leitura), como no `urllib`.
|
|
205
|
+
|
|
206
|
+
## Testes
|
|
207
|
+
|
|
208
|
+
```bash
|
|
209
|
+
python3 -I -m unittest discover -s tests -v # servidor HTTP local numa thread; nenhuma chamada sai da máquina
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
## Licença
|
|
213
|
+
|
|
214
|
+
MIT
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "convertaja"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "SDK oficial da API do Convertajá: PDF, DANFE, etiquetas ZPL, assinatura eletrônica, CNPJ, CEP, boleto e Pix."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = { text = "MIT" }
|
|
11
|
+
authors = [{ name = "Convertajá", email = "contato@convertaja.com.br" }]
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
dependencies = []
|
|
14
|
+
keywords = ["convertaja", "pdf", "danfe", "nfe", "zpl", "cnpj", "cep", "boleto", "pix", "assinatura eletronica", "api", "sdk"]
|
|
15
|
+
classifiers = [
|
|
16
|
+
"Programming Language :: Python :: 3",
|
|
17
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
18
|
+
"License :: OSI Approved :: MIT License",
|
|
19
|
+
"Operating System :: OS Independent",
|
|
20
|
+
"Natural Language :: Portuguese (Brazilian)",
|
|
21
|
+
"Typing :: Typed",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://convertaja.com.br/api"
|
|
26
|
+
Documentation = "https://convertaja.com.br/api/docs"
|
|
27
|
+
|
|
28
|
+
[tool.setuptools]
|
|
29
|
+
package-dir = { "" = "src" }
|
|
30
|
+
|
|
31
|
+
[tool.setuptools.packages.find]
|
|
32
|
+
where = ["src"]
|
|
33
|
+
|
|
34
|
+
[tool.setuptools.package-data]
|
|
35
|
+
convertaja = ["py.typed"]
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"""SDK oficial da API do Convertajá (https://convertaja.com.br/api).
|
|
2
|
+
|
|
3
|
+
>>> import convertaja
|
|
4
|
+
>>> client = convertaja.Convertaja(api_key="cj_live_...")
|
|
5
|
+
>>> client.lookups.cnpj("11.222.333/0001-81")["razaoSocial"]
|
|
6
|
+
"""
|
|
7
|
+
from . import webhooks
|
|
8
|
+
from .client import DEFAULT_BASE_URL, BinaryFile, Convertaja, LastResponse, __version__
|
|
9
|
+
from .errors import ConvertajaError, ConvertajaSignatureError
|
|
10
|
+
|
|
11
|
+
Client = Convertaja
|
|
12
|
+
|
|
13
|
+
__all__ = [
|
|
14
|
+
"Convertaja",
|
|
15
|
+
"Client",
|
|
16
|
+
"ConvertajaError",
|
|
17
|
+
"ConvertajaSignatureError",
|
|
18
|
+
"BinaryFile",
|
|
19
|
+
"LastResponse",
|
|
20
|
+
"webhooks",
|
|
21
|
+
"DEFAULT_BASE_URL",
|
|
22
|
+
"__version__",
|
|
23
|
+
]
|