nfe-io 1.0.0 → 1.1.0
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +82 -1
- data/README.md +33 -8
- data/lib/nfe/generated/generated_marker.rb +1 -0
- data/lib/nfe/generated/nf_servico_v1/errors_resource.rb +20 -0
- data/lib/nfe/generated.rb +1 -0
- data/lib/nfe/resources/dto/account_webhook.rb +50 -0
- data/lib/nfe/resources/dto/service_invoice.rb +34 -8
- data/lib/nfe/resources/dto/service_invoice_borrower.rb +59 -0
- data/lib/nfe/resources/dto/webhook.rb +6 -0
- data/lib/nfe/resources/webhooks.rb +195 -10
- data/lib/nfe/version.rb +1 -1
- data/lib/nfe/webhook_event.rb +1 -1
- data/sig/nfe/generated/nf_servico_v1/errors_resource.rbs +16 -0
- data/sig/nfe/resources/dto/account_webhook.rbs +21 -0
- data/sig/nfe/resources/dto/service_invoice.rbs +7 -3
- data/sig/nfe/resources/dto/service_invoice_borrower.rbs +21 -0
- data/sig/nfe/resources/webhooks.rbs +12 -0
- metadata +8 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5e9b7b2223a90779e218be5ddb727e1f4c9d707a24767075c7d56132c353347c
|
|
4
|
+
data.tar.gz: 55597041ea75f6c244a5af2e64e0f29ea022b9f5041caf987e6f2bcda6ef3bfa
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: d0c6297b2146225c8d9e7c228866a836367b484e727a47956ddb59ef084715a8be11f0a0bdbf8cf51d391a763e9736d293ecfb9a89bd21c47e0c56cc8aaeb57e
|
|
7
|
+
data.tar.gz: a6cbb185d13215213bec2387b06c988fd41de2446c2c779424fac01c54b8692fa2de7136c783581ea9160b40f15697d1ea83fad9ab9b06d7ac55a2d22e65c02e
|
data/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,86 @@ e o projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).
|
|
|
7
7
|
|
|
8
8
|
## [Não lançado]
|
|
9
9
|
|
|
10
|
+
## [1.1.0] - 2026-07-09
|
|
11
|
+
|
|
12
|
+
> Duas correções de contrato contra a API real: o CRUD de webhooks (provado por
|
|
13
|
+
> sonda ao vivo, 2026-07-02/03, três contas) e a cobertura do retrieve de NFS-e
|
|
14
|
+
> (o DTO descartava mais da metade dos campos). Em ambas, o contrato correto
|
|
15
|
+
> sempre esteve nos specs oficiais (`openapi/nf-servico-v1.yaml` e equivalentes) —
|
|
16
|
+
> o código manuscrito havia divergido deles.
|
|
17
|
+
|
|
18
|
+
### Corrigido
|
|
19
|
+
|
|
20
|
+
- **O CRUD de webhooks funcionava 0% das vezes**: a rota company-scoped
|
|
21
|
+
`/v1/companies/{id}/webhooks` retorna 404 na API atual. O contrato real é
|
|
22
|
+
account-scoped (`/v2/webhooks`) e exige o request envelopado em
|
|
23
|
+
`{ "webHook": {...} }` (sem ele responde
|
|
24
|
+
`400 "missing required properties: 'webHook'"`), devolvendo a resposta também
|
|
25
|
+
envelopada. Os novos métodos account-scoped envelopam o request
|
|
26
|
+
(create/update) e desembrulham as respostas (create/retrieve/update/list),
|
|
27
|
+
com fallback defensivo para corpo cru.
|
|
28
|
+
- **`Nfe::ServiceInvoice` descartava ~25 dos 44 campos do retrieve de NFS-e**
|
|
29
|
+
(toda a árvore de retenções, `provider`, `taxationType`, `location`,
|
|
30
|
+
`approximateTax`, ...): o `from_api` agora preserva o payload completo em
|
|
31
|
+
`invoice.raw` (padrão do `ConsumerInvoice`), em todas as leituras
|
|
32
|
+
(list/retrieve/cancel/201-issued).
|
|
33
|
+
|
|
34
|
+
### Adicionado
|
|
35
|
+
|
|
36
|
+
- Métodos account-scoped em `client.webhooks`: `list_account_webhooks`,
|
|
37
|
+
`create_account_webhook`, `retrieve_account_webhook`,
|
|
38
|
+
`update_account_webhook`, `delete_account_webhook`,
|
|
39
|
+
`delete_all_account_webhooks` (destrutivo, nome propositalmente distinto),
|
|
40
|
+
`ping_account_webhook` e `fetch_event_types`.
|
|
41
|
+
- Value object **`Nfe::AccountWebhook`** (`Data.define`, com RBS) com o shape
|
|
42
|
+
real da API: `uri`, `content_type`, `secret` (32–64 caracteres, ecoado no
|
|
43
|
+
create e omitido nas leituras), `filters`, `insecure_ssl`, `headers`,
|
|
44
|
+
`properties`, `status`, `created_on`, `modified_on`. Nota: o spec declara
|
|
45
|
+
`contentType`/`status` como enums inteiros, mas a API serializa strings
|
|
46
|
+
(`"json"`, `"Active"`) — o DTO segue o fio real.
|
|
47
|
+
- `fetch_event_types` retorna os event types reais de
|
|
48
|
+
`GET /v2/webhooks/eventTypes` (46 ids ao vivo, padrão
|
|
49
|
+
`service_invoice.*`/`product_invoice.*`/`consumer_invoice.*`).
|
|
50
|
+
- Teste de alinhamento (RSpec + Psych) amarrando o `Nfe::AccountWebhook` ao
|
|
51
|
+
schema de `/v2/webhooks` em `openapi/nf-servico-v1.yaml` — um sync de spec
|
|
52
|
+
que mude o contrato de webhooks quebra a suíte em vez de driftar.
|
|
53
|
+
- YARD do `create_account_webhook` documenta a verificação de URI na criação
|
|
54
|
+
(a NFE.io faz um ping e exige resposta 2xx) e o `secret` de 32–64 caracteres.
|
|
55
|
+
- YARD do `update_account_webhook` documenta que o `PUT` é substituição
|
|
56
|
+
integral (confirmado ao vivo em 2026-07-03): campos omitidos voltam ao
|
|
57
|
+
padrão — update sem `status` **desativa o webhook**. Envie o objeto completo
|
|
58
|
+
(parta do retrieve).
|
|
59
|
+
- Campos de ISS tipados em `Nfe::ServiceInvoice`: `base_tax_amount`,
|
|
60
|
+
`iss_rate`, `iss_tax_amount`.
|
|
61
|
+
- Value object **`Nfe::ServiceInvoiceBorrower`** (tomador, com RBS):
|
|
62
|
+
`federal_tax_number` sempre `String` (tolerante ao CNPJ alfanumérico da
|
|
63
|
+
IN RFB 2.229/2024, fio Integer ou String) e **ponte Hash** — leituras
|
|
64
|
+
`borrower["..."]`/`borrower.dig(...)` continuam funcionando (delegam ao
|
|
65
|
+
payload cru), agora ao lado dos leitores tipados.
|
|
66
|
+
- Teste de alinhamento (RSpec + Psych) amarrando `Nfe::ServiceInvoice` ao
|
|
67
|
+
schema inline do retrieve em `openapi/nf-servico-v1.yaml`, **ancorado por
|
|
68
|
+
path** (há colisão de `operationId` no spec) — também serve de gatilho de
|
|
69
|
+
migração: quando a resposta for componentizada upstream, o teste falha e
|
|
70
|
+
sinaliza migrar para o modelo gerado.
|
|
71
|
+
- Spec `nf-servico-v1.yaml` atualizado (respostas de erro tipadas com
|
|
72
|
+
`ErrorsResource` em `components.schemas`) + namespace gerado
|
|
73
|
+
`Nfe::Generated::NfServicoV1` (somente o modelo de erros; a resposta de
|
|
74
|
+
sucesso segue inline e o DTO manuscrito).
|
|
75
|
+
|
|
76
|
+
### Deprecado
|
|
77
|
+
|
|
78
|
+
- Métodos company-scoped de webhooks (`list`, `create`, `retrieve`, `update`,
|
|
79
|
+
`delete`, `test` sobre `/v1/companies/{id}/webhooks`): a rota retorna **404**
|
|
80
|
+
na API atual (confirmado em três contas, 2026-07-02/03). Use os equivalentes
|
|
81
|
+
account-scoped. O comportamento não mudou; remoção fica para a próxima major.
|
|
82
|
+
- `Nfe::WebhookSubscription` (`url`/`events`/`active`) e
|
|
83
|
+
`get_available_events`/`AVAILABLE_EVENTS` (literais `invoice.*`): shapes e
|
|
84
|
+
eventos que a API real rejeita ou desconhece. Use `Nfe::AccountWebhook` e
|
|
85
|
+
`fetch_event_types`.
|
|
86
|
+
- `Nfe::ServiceInvoice#pdf` e `#xml`: campos-fantasma — a resposta do retrieve
|
|
87
|
+
não os traz (sempre `nil`). Use `download_pdf`/`download_xml`. Remoção na
|
|
88
|
+
próxima major.
|
|
89
|
+
|
|
10
90
|
## [1.0.0] - 2026-07-02
|
|
11
91
|
|
|
12
92
|
### Adicionado
|
|
@@ -55,6 +135,7 @@ e o projeto adere ao [Versionamento Semântico](https://semver.org/lang/pt-BR/).
|
|
|
55
135
|
|
|
56
136
|
- Última versão da série `0.x` (legada, baseada em `rest-client`). Congelada, sem manutenção.
|
|
57
137
|
|
|
58
|
-
[Não lançado]: https://github.com/nfe/client-ruby/compare/v1.
|
|
138
|
+
[Não lançado]: https://github.com/nfe/client-ruby/compare/v1.1.0...HEAD
|
|
139
|
+
[1.1.0]: https://github.com/nfe/client-ruby/compare/v1.0.0...v1.1.0
|
|
59
140
|
[1.0.0]: https://github.com/nfe/client-ruby/compare/v0.3.2...v1.0.0
|
|
60
141
|
[0.3.2]: https://github.com/nfe/client-ruby/releases/tag/v0.3.2
|
data/README.md
CHANGED
|
@@ -49,6 +49,21 @@ Ou instalação direta:
|
|
|
49
49
|
gem install nfe-io
|
|
50
50
|
```
|
|
51
51
|
|
|
52
|
+
## Skill para agentes de IA
|
|
53
|
+
|
|
54
|
+
Além do gem, este repositório publica uma **skill de agente** (`nfeio-ruby-sdk`) que
|
|
55
|
+
ensina assistentes de IA (Claude Code, Cursor, Copilot, etc.) a usar o SDK corretamente.
|
|
56
|
+
São **dois canais distintos**:
|
|
57
|
+
|
|
58
|
+
| Canal | Comando | O quê |
|
|
59
|
+
|---|---|---|
|
|
60
|
+
| Código (RubyGems) | `gem install nfe-io` | O SDK Ruby |
|
|
61
|
+
| Skill de agente ([skills.sh](https://www.skills.sh/)) | `npx skills add https://github.com/nfe/client-ruby --skill nfeio-ruby-sdk` | O guia de uso para agentes |
|
|
62
|
+
|
|
63
|
+
O atalho `npx skills add nfe/client-ruby` também funciona. A skill é lida da árvore do
|
|
64
|
+
GitHub (slug `nfe/client-ruby`); ela **não** vem no `gem install` — o gemspec empacota
|
|
65
|
+
apenas `lib/`, `sig/` e os docs.
|
|
66
|
+
|
|
52
67
|
## Quickstart
|
|
53
68
|
|
|
54
69
|
```ruby
|
|
@@ -169,7 +184,7 @@ A `v1` expõe **17 recursos canônicos** no `Nfe::Client` (mais 2 addons RTC).
|
|
|
169
184
|
| `companies` | `api.nfe.io` (`/v1`) | Empresas + certificado | `create`, `retrieve`, `list`, `update`, `remove`, `upload_certificate`, `get_certificate_status` |
|
|
170
185
|
| `legal_people` | `api.nfe.io` (`/v1`) | Pessoas jurídicas (tomadores) | `create`, `retrieve`, `list`, `update`, `delete`, `create_batch`, `find_by_tax_number` |
|
|
171
186
|
| `natural_people` | `api.nfe.io` (`/v1`) | Pessoas físicas (tomadores) | `create`, `retrieve`, `list`, `update`, `delete`, `create_batch`, `find_by_tax_number` |
|
|
172
|
-
| `webhooks` | `api.nfe.io` (`/
|
|
187
|
+
| `webhooks` | `api.nfe.io` (`/v2`, conta) | Webhooks da conta | `create_account_webhook`, `retrieve_account_webhook`, `list_account_webhooks`, `update_account_webhook`, `delete_account_webhook`, `ping_account_webhook`, `fetch_event_types`, `verify_signature` |
|
|
173
188
|
| `product_invoices` | `api.nfse.io` (`/v2`) | NF-e | `create`, `create_with_state_tax`, `list`, `retrieve`, `cancel`, `send_correction_letter`, `disable`, `download_*` |
|
|
174
189
|
| `consumer_invoices` | `api.nfse.io` (`/v2`) | NFC-e | `create`, `create_with_state_tax`, `list`, `retrieve`, `cancel`, `disable_range`, `download_pdf`/`download_xml` |
|
|
175
190
|
| `transportation_invoices` | `api.nfse.io` (`/v2`) | CT-e (recepção) | `enable`, `disable`, `get_settings`, `retrieve`, `download_xml`, `get_event` |
|
|
@@ -267,7 +282,7 @@ discriminado**:
|
|
|
267
282
|
`pending?` ⇒ `true`, `issued?` ⇒ `false`.
|
|
268
283
|
- `*Issued` (HTTP 201, já materializado) — expõe `resource`; `issued?` ⇒ `true`.
|
|
269
284
|
|
|
270
|
-
Não há `create_and_wait` nem `create_batch` na v1.
|
|
285
|
+
Não há `create_and_wait` nem `create_batch` na v1.x — faça **polling** chamando
|
|
271
286
|
`retrieve` até um estado terminal, usando `Nfe::FlowStatus.terminal?`. Os estados
|
|
272
287
|
terminais são: `Issued`, `IssueFailed`, `Cancelled`, `CancelFailed`.
|
|
273
288
|
|
|
@@ -359,16 +374,26 @@ Devolvem bytes: `service_invoices`, `consumer_invoices`,
|
|
|
359
374
|
|
|
360
375
|
## Webhooks
|
|
361
376
|
|
|
362
|
-
Crie
|
|
377
|
+
Crie o webhook (escopo da **conta**, `/v2/webhooks`) e **verifique a
|
|
378
|
+
assinatura** de cada entrega. A NFE.io pinga a `uri` na criação e exige 2xx;
|
|
379
|
+
descubra os filtros válidos com `fetch_event_types`.
|
|
363
380
|
|
|
364
381
|
```ruby
|
|
365
|
-
client.webhooks.
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
secret: ENV["NFE_WEBHOOK_SECRET"]
|
|
369
|
-
|
|
382
|
+
client.webhooks.create_account_webhook(
|
|
383
|
+
uri: "https://minha-app.com/webhooks/nfe",
|
|
384
|
+
contentType: "json",
|
|
385
|
+
secret: ENV["NFE_WEBHOOK_SECRET"], # 32–64 caracteres
|
|
386
|
+
filters: ["service_invoice.issued_successfully", "service_invoice.issued_error"],
|
|
387
|
+
status: "Active"
|
|
388
|
+
)
|
|
370
389
|
```
|
|
371
390
|
|
|
391
|
+
> ⚠️ `update_account_webhook` faz **PUT integral**: campos omitidos voltam ao
|
|
392
|
+
> padrão — um update sem `status` desativa o webhook. Parta do
|
|
393
|
+
> `retrieve_account_webhook` e envie o objeto completo. Os métodos
|
|
394
|
+
> company-scoped (`create`/`list`/... com `company_id`) estão **deprecated** —
|
|
395
|
+
> a rota `/v1/companies/{id}/webhooks` retorna 404 na API atual.
|
|
396
|
+
|
|
372
397
|
A verificação é **HMAC-SHA1 sobre os bytes crus** da requisição (header
|
|
373
398
|
`X-Hub-Signature`, comparação case-insensitive e timing-safe). Leia o corpo bruto
|
|
374
399
|
**antes** de fazer parse do JSON — reserializar (`payload.to_json`) muda
|
|
@@ -13,6 +13,7 @@ module Nfe
|
|
|
13
13
|
"contribuintes-v2.json" => "sha256:e2d215a19f5dc85c08067d51644e807aae32b6c4754390872670f2e18a938102",
|
|
14
14
|
"nf-consumidor-v2.yaml" => "sha256:8c39e692ff794ccb2587ebe142be040e44d76cbf970f45e65b28d56a6165bdb5",
|
|
15
15
|
"nf-produto-v2.yaml" => "sha256:e565b47e4d8b17255f99efc2b6354d589d2903c4ba9b97caabd74f84de59e4e2",
|
|
16
|
+
"nf-servico-v1.yaml" => "sha256:621a3b8e437e5cb37367c8cd26fec93fa2b3f87c9a59252ac987c56bb0c7ba56",
|
|
16
17
|
"nfeio.yaml" => "sha256:813bda287538f8599c3565485eb523d1b1311b26b5be94ead62ba0b7a17f6af3",
|
|
17
18
|
"product-invoice-rtc-v1.yaml" => "sha256:4327ad141eeace6219dc4267678c44a134ae1fdde93f7dd69cf4c9ae9418415a",
|
|
18
19
|
"product-register-pt-br-v1.yaml" => "sha256:beba0a3fb4dc1bc157a5a4a28e55768cea0e7390b491bdd4bedee2ee2297ca64",
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
# AUTO-GENERATED — do not edit
|
|
3
|
+
# Source: openapi/nf-servico-v1.yaml
|
|
4
|
+
# Hash: sha256:621a3b8e437e5cb37367c8cd26fec93fa2b3f87c9a59252ac987c56bb0c7ba56
|
|
5
|
+
|
|
6
|
+
module Nfe
|
|
7
|
+
module Generated
|
|
8
|
+
module NfServicoV1
|
|
9
|
+
ErrorsResource = Data.define(:errors) do
|
|
10
|
+
def self.from_api(payload)
|
|
11
|
+
return nil if payload.nil?
|
|
12
|
+
|
|
13
|
+
new(
|
|
14
|
+
errors: payload["errors"],
|
|
15
|
+
)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
data/lib/nfe/generated.rb
CHANGED
|
@@ -362,6 +362,7 @@ require_relative "generated/nf_produto_v2/transport_vehicle_resource"
|
|
|
362
362
|
require_relative "generated/nf_produto_v2/vehicle_detail_resource"
|
|
363
363
|
require_relative "generated/nf_produto_v2/volume_resource"
|
|
364
364
|
require_relative "generated/nf_produto_v2/withdrawal_information_resource"
|
|
365
|
+
require_relative "generated/nf_servico_v1/errors_resource"
|
|
365
366
|
require_relative "generated/nfeio/batch_process_response"
|
|
366
367
|
require_relative "generated/nfeio/environment"
|
|
367
368
|
require_relative "generated/nfeio/file_parsing_options_request"
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Nfe
|
|
4
|
+
# Immutable value object for an account-level webhook, as returned by the
|
|
5
|
+
# +/v2/webhooks+ API (account scope, envelope +{"webHook": {...}}+ on the
|
|
6
|
+
# wire). This is the shape the live API accepts and returns — confirmed by
|
|
7
|
+
# probe against +api.nfe.io+ (2026-07-02/03) and matching the schema in
|
|
8
|
+
# +openapi/nf-servico-v1.yaml+.
|
|
9
|
+
#
|
|
10
|
+
# Wire-format note: the OpenAPI spec declares +contentType+/+status+ as int
|
|
11
|
+
# enums (0/1), but the API serializes strings (+"json"+, +"Active"+) — this
|
|
12
|
+
# DTO follows the wire. +secret+ (32–64 chars) is echoed only on create and
|
|
13
|
+
# omitted on reads, so it is +nil+ on retrieve/list.
|
|
14
|
+
#
|
|
15
|
+
# {from_api} maps API camelCase onto snake_case, drops unknown keys, and is
|
|
16
|
+
# nil-tolerant (+from_api(nil)+ returns +nil+).
|
|
17
|
+
class AccountWebhook < Data.define(
|
|
18
|
+
:id,
|
|
19
|
+
:uri,
|
|
20
|
+
:content_type,
|
|
21
|
+
:secret,
|
|
22
|
+
:filters,
|
|
23
|
+
:insecure_ssl,
|
|
24
|
+
:headers,
|
|
25
|
+
:properties,
|
|
26
|
+
:status,
|
|
27
|
+
:created_on,
|
|
28
|
+
:modified_on
|
|
29
|
+
)
|
|
30
|
+
# @param payload [Hash, nil] the unwrapped webhook object.
|
|
31
|
+
# @return [Nfe::AccountWebhook, nil] +nil+ when +payload+ is +nil+.
|
|
32
|
+
def self.from_api(payload)
|
|
33
|
+
return nil if payload.nil?
|
|
34
|
+
|
|
35
|
+
new(
|
|
36
|
+
id: payload["id"],
|
|
37
|
+
uri: payload["uri"],
|
|
38
|
+
content_type: payload["contentType"],
|
|
39
|
+
secret: payload["secret"],
|
|
40
|
+
filters: payload["filters"],
|
|
41
|
+
insecure_ssl: payload["insecureSsl"],
|
|
42
|
+
headers: payload["headers"],
|
|
43
|
+
properties: payload["properties"],
|
|
44
|
+
status: payload["status"],
|
|
45
|
+
created_on: payload["createdOn"],
|
|
46
|
+
modified_on: payload["modifiedOn"]
|
|
47
|
+
)
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -1,17 +1,33 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "nfe/resources/dto/service_invoice_borrower"
|
|
4
|
+
|
|
3
5
|
module Nfe
|
|
4
6
|
# Immutable value object for a service invoice (NFS-e) as returned by the
|
|
5
7
|
# NFE.io +api.nfe.io/v1+ service-invoice API.
|
|
6
8
|
#
|
|
7
|
-
# The +nf-servico-v1.yaml+ OpenAPI spec
|
|
8
|
-
#
|
|
9
|
-
#
|
|
10
|
-
#
|
|
11
|
-
# (+from_api(nil)+ returns +nil+).
|
|
9
|
+
# The +nf-servico-v1.yaml+ OpenAPI spec declares the retrieve success
|
|
10
|
+
# response **inline** (no named schema in +components.schemas+ — only the
|
|
11
|
+
# error model is componentized), so this DTO is hand-written and pinned to
|
|
12
|
+
# the spec by an alignment test. {from_api} maps the API camelCase keys onto
|
|
13
|
+
# snake_case members and is nil-tolerant (+from_api(nil)+ returns +nil+).
|
|
14
|
+
# The full parsed payload is preserved under +raw+ for forward
|
|
15
|
+
# compatibility — fields without a typed member (the withholding tree,
|
|
16
|
+
# +provider+, +taxationType+, +location+, +approximateTax+, ...) are
|
|
17
|
+
# accessible through it.
|
|
18
|
+
#
|
|
19
|
+
# +borrower+ is hydrated into {Nfe::ServiceInvoiceBorrower} (typed readers
|
|
20
|
+
# plus a Hash-compatibility bridge, so +borrower["name"]+ keeps working).
|
|
12
21
|
#
|
|
13
22
|
# +flow_status+ drives the polling lifecycle; pass it to
|
|
14
23
|
# {Nfe::FlowStatus.terminal?} to decide when the document is settled.
|
|
24
|
+
#
|
|
25
|
+
# @!attribute [r] pdf
|
|
26
|
+
# @deprecated Ghost field — the retrieve response carries no +pdf+ key
|
|
27
|
+
# (always +nil+). Use {Nfe::Resources::ServiceInvoices#download_pdf}.
|
|
28
|
+
# @!attribute [r] xml
|
|
29
|
+
# @deprecated Ghost field — the retrieve response carries no +xml+ key
|
|
30
|
+
# (always +nil+). Use {Nfe::Resources::ServiceInvoices#download_xml}.
|
|
15
31
|
class ServiceInvoice < Data.define(
|
|
16
32
|
:id,
|
|
17
33
|
:flow_status,
|
|
@@ -33,12 +49,17 @@ module Nfe
|
|
|
33
49
|
:pdf,
|
|
34
50
|
:xml,
|
|
35
51
|
:created_on,
|
|
36
|
-
:modified_on
|
|
52
|
+
:modified_on,
|
|
53
|
+
:base_tax_amount,
|
|
54
|
+
:iss_rate,
|
|
55
|
+
:iss_tax_amount,
|
|
56
|
+
:raw
|
|
37
57
|
)
|
|
38
58
|
# Build a {Nfe::ServiceInvoice} from an API payload.
|
|
39
59
|
#
|
|
40
60
|
# @param payload [Hash, nil] the response object.
|
|
41
61
|
# @return [Nfe::ServiceInvoice, nil] +nil+ when +payload+ is +nil+.
|
|
62
|
+
# rubocop:disable Metrics/MethodLength, Metrics/AbcSize -- wide value-object mapping kept inline for Steep keyword-arg verification
|
|
42
63
|
def self.from_api(payload)
|
|
43
64
|
return nil if payload.nil?
|
|
44
65
|
|
|
@@ -56,15 +77,20 @@ module Nfe
|
|
|
56
77
|
cancelled_on: payload["cancelledOn"],
|
|
57
78
|
amount_net: payload["amountNet"],
|
|
58
79
|
services_amount: payload["servicesAmount"],
|
|
59
|
-
borrower: payload["borrower"],
|
|
80
|
+
borrower: ServiceInvoiceBorrower.from_api(payload["borrower"]),
|
|
60
81
|
city_service_code: payload["cityServiceCode"],
|
|
61
82
|
federal_service_code: payload["federalServiceCode"],
|
|
62
83
|
description: payload["description"],
|
|
63
84
|
pdf: payload["pdf"],
|
|
64
85
|
xml: payload["xml"],
|
|
65
86
|
created_on: payload["createdOn"],
|
|
66
|
-
modified_on: payload["modifiedOn"]
|
|
87
|
+
modified_on: payload["modifiedOn"],
|
|
88
|
+
base_tax_amount: payload["baseTaxAmount"],
|
|
89
|
+
iss_rate: payload["issRate"],
|
|
90
|
+
iss_tax_amount: payload["issTaxAmount"],
|
|
91
|
+
raw: payload
|
|
67
92
|
)
|
|
68
93
|
end
|
|
94
|
+
# rubocop:enable Metrics/MethodLength, Metrics/AbcSize
|
|
69
95
|
end
|
|
70
96
|
end
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "nfe/resources/dto/company"
|
|
4
|
+
|
|
5
|
+
module Nfe
|
|
6
|
+
# Immutable value object for the borrower (tomador) of a service invoice,
|
|
7
|
+
# as embedded in the NFS-e retrieve response (+nf-servico-v1.yaml+, inline
|
|
8
|
+
# schema of +GET /v1/companies/{company_id}/serviceinvoices/{id}+).
|
|
9
|
+
#
|
|
10
|
+
# +federal_tax_number+ is normalized to +String+ via {Nfe::Company.stringify}:
|
|
11
|
+
# the spec declares it +integer int64+, but the alphanumeric CNPJ
|
|
12
|
+
# (IN RFB 2.229/2024) requires string tolerance — deliberate deviation,
|
|
13
|
+
# pinned by the alignment test. +address+ stays a raw +Hash+.
|
|
14
|
+
#
|
|
15
|
+
# Hash-compatibility bridge: before this DTO existed, +invoice.borrower+
|
|
16
|
+
# returned the raw wire +Hash+ — {#[]} and {#dig} delegate to {#raw} so
|
|
17
|
+
# +borrower["name"]+ keeps working alongside the typed readers.
|
|
18
|
+
class ServiceInvoiceBorrower < Data.define(
|
|
19
|
+
:id,
|
|
20
|
+
:name,
|
|
21
|
+
:federal_tax_number,
|
|
22
|
+
:email,
|
|
23
|
+
:phone_number,
|
|
24
|
+
:address,
|
|
25
|
+
:parent_id,
|
|
26
|
+
:raw
|
|
27
|
+
)
|
|
28
|
+
# Build a {Nfe::ServiceInvoiceBorrower} from an API payload.
|
|
29
|
+
#
|
|
30
|
+
# @param payload [Hash, nil] the borrower object from the wire.
|
|
31
|
+
# @return [Nfe::ServiceInvoiceBorrower, nil] +nil+ when +payload+ is +nil+.
|
|
32
|
+
def self.from_api(payload)
|
|
33
|
+
return nil if payload.nil?
|
|
34
|
+
|
|
35
|
+
new(
|
|
36
|
+
id: payload["id"],
|
|
37
|
+
name: payload["name"],
|
|
38
|
+
federal_tax_number: Company.stringify(payload["federalTaxNumber"]),
|
|
39
|
+
email: payload["email"],
|
|
40
|
+
phone_number: payload["phoneNumber"],
|
|
41
|
+
address: payload["address"],
|
|
42
|
+
parent_id: payload["parentId"],
|
|
43
|
+
raw: payload
|
|
44
|
+
)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# Hash-style read, delegated to the raw wire payload (camelCase keys),
|
|
48
|
+
# e.g. +borrower["federalTaxNumber"]+ returns the wire value untouched.
|
|
49
|
+
def [](key)
|
|
50
|
+
raw && raw[key]
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# Hash-style nested read over the raw wire payload,
|
|
54
|
+
# e.g. +borrower.dig("address", "city", "name")+.
|
|
55
|
+
def dig(*keys)
|
|
56
|
+
raw&.dig(*keys)
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -13,6 +13,12 @@ module Nfe
|
|
|
13
13
|
#
|
|
14
14
|
# {from_api} maps API camelCase onto snake_case, drops unknown keys, and is
|
|
15
15
|
# nil-tolerant (+from_api(nil)+ returns +nil+).
|
|
16
|
+
#
|
|
17
|
+
# @deprecated This shape (+url+/+events+/+active+) is rejected by the live
|
|
18
|
+
# API (+400 "The Uri field is required"+), and the company-scoped route
|
|
19
|
+
# that would return it responds 404 (confirmed on three accounts,
|
|
20
|
+
# 2026-07-02/03). Use {Nfe::AccountWebhook} with the account-scoped
|
|
21
|
+
# methods on {Nfe::Resources::Webhooks}.
|
|
16
22
|
class WebhookSubscription < Data.define(
|
|
17
23
|
:id,
|
|
18
24
|
:url,
|
|
@@ -2,17 +2,39 @@
|
|
|
2
2
|
|
|
3
3
|
require "json"
|
|
4
4
|
require "nfe/resources/abstract_resource"
|
|
5
|
+
require "nfe/resources/dto/account_webhook"
|
|
5
6
|
require "nfe/resources/dto/webhook"
|
|
6
7
|
require "nfe/webhook"
|
|
7
8
|
|
|
8
9
|
module Nfe
|
|
9
10
|
module Resources
|
|
10
|
-
# Webhooks resource
|
|
11
|
-
#
|
|
12
|
-
#
|
|
13
|
-
#
|
|
11
|
+
# Webhooks resource on the +:main+ host family.
|
|
12
|
+
#
|
|
13
|
+
# Webhooks are managed at the **account** level over +/v2/webhooks+ — use
|
|
14
|
+
# the +*_account_webhook*+ methods. The live API wraps create/update
|
|
15
|
+
# requests and single-object responses in a +webHook+ envelope; the SDK
|
|
16
|
+
# envelopes/unwraps transparently. Contract source of truth:
|
|
17
|
+
# +openapi/nf-servico-v1.yaml+ plus live probes (2026-07-02/03).
|
|
18
|
+
#
|
|
19
|
+
# The company-scoped methods (+list+/+create+/+retrieve+/+update+/
|
|
20
|
+
# +delete+/+test+ under +/v1/companies/{id}/webhooks+) are deprecated: the
|
|
21
|
+
# route returns 404 on the current API (confirmed on three accounts,
|
|
22
|
+
# 2026-07-02/03). They remain, unchanged, until the next major.
|
|
23
|
+
#
|
|
24
|
+
# Also exposes a thin {#verify_signature} delegation to {Nfe::Webhook}
|
|
25
|
+
# (the canonical signature-verification API is the module).
|
|
14
26
|
class Webhooks < AbstractResource
|
|
15
|
-
#
|
|
27
|
+
# Preserve the inherited HTTP DELETE helper under a private name before
|
|
28
|
+
# the public company-scoped +delete+ shadows it — the account-scoped
|
|
29
|
+
# deletes still need the raw verb.
|
|
30
|
+
alias http_delete delete
|
|
31
|
+
private :http_delete
|
|
32
|
+
|
|
33
|
+
# Legacy static list of event types.
|
|
34
|
+
#
|
|
35
|
+
# @deprecated These literals do not exist on the live API — the real
|
|
36
|
+
# event types follow +service_invoice.*+/+product_invoice.*+/
|
|
37
|
+
# +consumer_invoice.*+ (46 ids live). Use {#fetch_event_types}.
|
|
16
38
|
AVAILABLE_EVENTS = %w[
|
|
17
39
|
invoice.issued
|
|
18
40
|
invoice.cancelled
|
|
@@ -29,10 +51,144 @@ module Nfe
|
|
|
29
51
|
:main
|
|
30
52
|
end
|
|
31
53
|
|
|
54
|
+
# Account-scoped endpoints live under +/v2+ on the same +:main+ host,
|
|
55
|
+
# while the (deprecated) company-scoped ones keep the resource default
|
|
56
|
+
# +/v1+. A path that already carries an explicit version segment passes
|
|
57
|
+
# through unprefixed.
|
|
58
|
+
def full_path(path)
|
|
59
|
+
path.start_with?("/v2/") ? path : super
|
|
60
|
+
end
|
|
61
|
+
|
|
32
62
|
public
|
|
33
63
|
|
|
64
|
+
# List the account's webhooks (+GET /v2/webhooks+).
|
|
65
|
+
#
|
|
66
|
+
# The API wraps the collection as +{"webHooks": [...]}+; the SDK unwraps
|
|
67
|
+
# it into an {Nfe::ListResponse} of {Nfe::AccountWebhook}. +secret+ is
|
|
68
|
+
# omitted on reads.
|
|
69
|
+
#
|
|
70
|
+
# @return [Nfe::ListResponse]
|
|
71
|
+
def list_account_webhooks
|
|
72
|
+
response = get("/v2/webhooks")
|
|
73
|
+
hydrate_list(Nfe::AccountWebhook, parse_json(response.body), wrapper_key: "webHooks")
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
# Create an account webhook (+POST /v2/webhooks+).
|
|
77
|
+
#
|
|
78
|
+
# The request is wrapped in the mandatory +webHook+ envelope (the API
|
|
79
|
+
# rejects a bare body with +400 "missing required properties: 'webHook'"+)
|
|
80
|
+
# and the +201 {"webHook": {...}}+ response is unwrapped.
|
|
81
|
+
#
|
|
82
|
+
# NFE.io **pings the +uri+ at creation time and requires a 2xx
|
|
83
|
+
# response** — the endpoint must already be live, or the create fails.
|
|
84
|
+
# +secret+ must be 32–64 characters; it is echoed back on create and
|
|
85
|
+
# omitted on subsequent reads.
|
|
86
|
+
#
|
|
87
|
+
# @param data [Hash] webhook attributes in wire (camelCase) keys, e.g.
|
|
88
|
+
# +{ uri: "https://...", contentType: "json", secret: "<32-64 chars>",
|
|
89
|
+
# filters: ["service_invoice.issued_successfully"], status: "Active" }+.
|
|
90
|
+
# Discover valid +filters+ with {#fetch_event_types}.
|
|
91
|
+
# @return [Nfe::AccountWebhook]
|
|
92
|
+
def create_account_webhook(data)
|
|
93
|
+
response = post("/v2/webhooks",
|
|
94
|
+
body: json_body({ webHook: data }), headers: json_headers)
|
|
95
|
+
hydrate_account_webhook(response)
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
# Retrieve an account webhook by id (+GET /v2/webhooks/{id}+).
|
|
99
|
+
#
|
|
100
|
+
# Unwraps the +{"webHook": {...}}+ envelope, falling back to the raw
|
|
101
|
+
# body when the envelope is absent. +secret+ is omitted on reads.
|
|
102
|
+
#
|
|
103
|
+
# @param webhook_id [String]
|
|
104
|
+
# @return [Nfe::AccountWebhook]
|
|
105
|
+
def retrieve_account_webhook(webhook_id)
|
|
106
|
+
wid = Nfe::IdValidator.presence!(webhook_id, "webhook_id")
|
|
107
|
+
response = get("/v2/webhooks/#{wid}")
|
|
108
|
+
hydrate_account_webhook(response)
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
# Update an account webhook (+PUT /v2/webhooks/{id}+), request wrapped
|
|
112
|
+
# in the +webHook+ envelope.
|
|
113
|
+
#
|
|
114
|
+
# @note **+PUT+ is a full replacement** (live-confirmed 2026-07-03):
|
|
115
|
+
# omitted fields reset to their defaults — an update without +status+
|
|
116
|
+
# **deactivates the webhook**. Always send the complete object,
|
|
117
|
+
# starting from a retrieve:
|
|
118
|
+
#
|
|
119
|
+
# current = client.webhooks.retrieve_account_webhook(id)
|
|
120
|
+
# client.webhooks.update_account_webhook(id, {
|
|
121
|
+
# uri: current.uri,
|
|
122
|
+
# contentType: current.content_type,
|
|
123
|
+
# status: current.status, # keep it "Active"!
|
|
124
|
+
# filters: ["service_invoice.issued_successfully"]
|
|
125
|
+
# })
|
|
126
|
+
#
|
|
127
|
+
# @param webhook_id [String]
|
|
128
|
+
# @param data [Hash] the complete webhook attributes (wire camelCase keys).
|
|
129
|
+
# @return [Nfe::AccountWebhook]
|
|
130
|
+
def update_account_webhook(webhook_id, data)
|
|
131
|
+
wid = Nfe::IdValidator.presence!(webhook_id, "webhook_id")
|
|
132
|
+
response = put("/v2/webhooks/#{wid}",
|
|
133
|
+
body: json_body({ webHook: data }), headers: json_headers)
|
|
134
|
+
hydrate_account_webhook(response)
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
# Delete a single account webhook by id (+DELETE /v2/webhooks/{id}+).
|
|
138
|
+
#
|
|
139
|
+
# @param webhook_id [String]
|
|
140
|
+
# @return [nil]
|
|
141
|
+
def delete_account_webhook(webhook_id)
|
|
142
|
+
wid = Nfe::IdValidator.presence!(webhook_id, "webhook_id")
|
|
143
|
+
http_delete("/v2/webhooks/#{wid}")
|
|
144
|
+
nil
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# ⚠️ DESTRUCTIVE: delete **ALL** of the account's webhooks
|
|
148
|
+
# (+DELETE /v2/webhooks+). Named distinctly from
|
|
149
|
+
# {#delete_account_webhook} so it can never be reached by a mistyped
|
|
150
|
+
# single delete.
|
|
151
|
+
#
|
|
152
|
+
# @return [nil]
|
|
153
|
+
def delete_all_account_webhooks
|
|
154
|
+
http_delete("/v2/webhooks")
|
|
155
|
+
nil
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
# Trigger a test ping for an account webhook
|
|
159
|
+
# (+PUT /v2/webhooks/{id}/pings+, responds 204).
|
|
160
|
+
#
|
|
161
|
+
# @param webhook_id [String]
|
|
162
|
+
# @return [nil]
|
|
163
|
+
def ping_account_webhook(webhook_id)
|
|
164
|
+
wid = Nfe::IdValidator.presence!(webhook_id, "webhook_id")
|
|
165
|
+
put("/v2/webhooks/#{wid}/pings", body: json_body({}), headers: json_headers)
|
|
166
|
+
nil
|
|
167
|
+
end
|
|
168
|
+
|
|
169
|
+
# Fetch the live list of webhook event types
|
|
170
|
+
# (+GET /v2/webhooks/eventTypes+).
|
|
171
|
+
#
|
|
172
|
+
# The API wraps the result as +{"eventTypes": [{ "id": ... }, ...]}+;
|
|
173
|
+
# this extracts the ids (e.g. +"service_invoice.issued_successfully"+,
|
|
174
|
+
# +"product_invoice.issued"+ — 46 ids live). Use these as +filters+ when
|
|
175
|
+
# creating or updating a webhook.
|
|
176
|
+
#
|
|
177
|
+
# @return [Array<String>]
|
|
178
|
+
def fetch_event_types
|
|
179
|
+
response = get("/v2/webhooks/eventTypes")
|
|
180
|
+
payload = parse_json(response.body)
|
|
181
|
+
items = [] #: Array[untyped]
|
|
182
|
+
items = payload["eventTypes"] || items if payload.is_a?(Hash)
|
|
183
|
+
items.map { |item| item["id"] }.compact
|
|
184
|
+
end
|
|
185
|
+
|
|
34
186
|
# List a company's webhook subscriptions.
|
|
35
187
|
#
|
|
188
|
+
# @deprecated The +/v1/companies/{id}/webhooks+ route returns 404 on the
|
|
189
|
+
# current API (confirmed on three accounts, 2026-07-02/03). Use
|
|
190
|
+
# {#list_account_webhooks}.
|
|
191
|
+
#
|
|
36
192
|
# @param company_id [String]
|
|
37
193
|
# @return [Nfe::ListResponse]
|
|
38
194
|
def list(company_id)
|
|
@@ -45,9 +201,13 @@ module Nfe
|
|
|
45
201
|
|
|
46
202
|
# Create a webhook subscription. Accepts +url+, +events+, +secret+, +active+.
|
|
47
203
|
#
|
|
204
|
+
# @deprecated The +/v1/companies/{id}/webhooks+ route returns 404 on the
|
|
205
|
+
# current API (confirmed on three accounts, 2026-07-02/03). Use
|
|
206
|
+
# {#create_account_webhook}.
|
|
207
|
+
#
|
|
48
208
|
# @param company_id [String]
|
|
49
209
|
# @param data [Hash]
|
|
50
|
-
# @return [Nfe::
|
|
210
|
+
# @return [Nfe::WebhookSubscription, nil]
|
|
51
211
|
def create(company_id, data)
|
|
52
212
|
id = Nfe::IdValidator.company_id(company_id)
|
|
53
213
|
response = post("/companies/#{id}/webhooks",
|
|
@@ -57,9 +217,13 @@ module Nfe
|
|
|
57
217
|
|
|
58
218
|
# Retrieve a webhook by id.
|
|
59
219
|
#
|
|
220
|
+
# @deprecated The +/v1/companies/{id}/webhooks+ route returns 404 on the
|
|
221
|
+
# current API (confirmed on three accounts, 2026-07-02/03). Use
|
|
222
|
+
# {#retrieve_account_webhook}.
|
|
223
|
+
#
|
|
60
224
|
# @param company_id [String]
|
|
61
225
|
# @param webhook_id [String]
|
|
62
|
-
# @return [Nfe::
|
|
226
|
+
# @return [Nfe::WebhookSubscription, nil]
|
|
63
227
|
def retrieve(company_id, webhook_id)
|
|
64
228
|
id = Nfe::IdValidator.company_id(company_id)
|
|
65
229
|
wid = Nfe::IdValidator.presence!(webhook_id, "webhook_id")
|
|
@@ -69,10 +233,14 @@ module Nfe
|
|
|
69
233
|
|
|
70
234
|
# Update a webhook.
|
|
71
235
|
#
|
|
236
|
+
# @deprecated The +/v1/companies/{id}/webhooks+ route returns 404 on the
|
|
237
|
+
# current API (confirmed on three accounts, 2026-07-02/03). Use
|
|
238
|
+
# {#update_account_webhook}.
|
|
239
|
+
#
|
|
72
240
|
# @param company_id [String]
|
|
73
241
|
# @param webhook_id [String]
|
|
74
242
|
# @param data [Hash]
|
|
75
|
-
# @return [Nfe::
|
|
243
|
+
# @return [Nfe::WebhookSubscription, nil]
|
|
76
244
|
def update(company_id, webhook_id, data)
|
|
77
245
|
id = Nfe::IdValidator.company_id(company_id)
|
|
78
246
|
wid = Nfe::IdValidator.presence!(webhook_id, "webhook_id")
|
|
@@ -83,6 +251,10 @@ module Nfe
|
|
|
83
251
|
|
|
84
252
|
# Delete a webhook.
|
|
85
253
|
#
|
|
254
|
+
# @deprecated The +/v1/companies/{id}/webhooks+ route returns 404 on the
|
|
255
|
+
# current API (confirmed on three accounts, 2026-07-02/03). Use
|
|
256
|
+
# {#delete_account_webhook}.
|
|
257
|
+
#
|
|
86
258
|
# @param company_id [String]
|
|
87
259
|
# @param webhook_id [String]
|
|
88
260
|
# @return [nil]
|
|
@@ -95,6 +267,10 @@ module Nfe
|
|
|
95
267
|
|
|
96
268
|
# Trigger a synthetic delivery to verify a webhook is reachable.
|
|
97
269
|
#
|
|
270
|
+
# @deprecated The +/v1/companies/{id}/webhooks+ route returns 404 on the
|
|
271
|
+
# current API (confirmed on three accounts, 2026-07-02/03). Use
|
|
272
|
+
# {#ping_account_webhook}.
|
|
273
|
+
#
|
|
98
274
|
# @param company_id [String]
|
|
99
275
|
# @param webhook_id [String]
|
|
100
276
|
# @return [Hash] +{ success: bool, message: String? }+.
|
|
@@ -107,7 +283,10 @@ module Nfe
|
|
|
107
283
|
{ success: payload["success"] || false, message: payload["message"] }
|
|
108
284
|
end
|
|
109
285
|
|
|
110
|
-
# The static list of webhook event types
|
|
286
|
+
# The legacy static list of webhook event types.
|
|
287
|
+
#
|
|
288
|
+
# @deprecated These literals do not exist on the live API. Use
|
|
289
|
+
# {#fetch_event_types} for the real, live list.
|
|
111
290
|
#
|
|
112
291
|
# @return [Array<String>]
|
|
113
292
|
def get_available_events
|
|
@@ -124,6 +303,12 @@ module Nfe
|
|
|
124
303
|
|
|
125
304
|
private
|
|
126
305
|
|
|
306
|
+
# Unwrap a single-object +{"webHook": {...}}+ envelope (raw-body
|
|
307
|
+
# fallback) and hydrate an {Nfe::AccountWebhook}.
|
|
308
|
+
def hydrate_account_webhook(response)
|
|
309
|
+
hydrate(Nfe::AccountWebhook, unwrap(parse_json(response.body), "webHook"))
|
|
310
|
+
end
|
|
311
|
+
|
|
127
312
|
def json_headers
|
|
128
313
|
{ "Content-Type" => "application/json" }
|
|
129
314
|
end
|
|
@@ -133,7 +318,7 @@ module Nfe
|
|
|
133
318
|
end
|
|
134
319
|
|
|
135
320
|
# Tolerate either a bare array, a +data+-wrapped list, or a +webhooks+
|
|
136
|
-
# envelope for the list response.
|
|
321
|
+
# envelope for the (deprecated) company-scoped list response.
|
|
137
322
|
def webhook_items(payload)
|
|
138
323
|
return payload if payload.is_a?(Array)
|
|
139
324
|
return [] unless payload.is_a?(Hash)
|
data/lib/nfe/version.rb
CHANGED
data/lib/nfe/webhook_event.rb
CHANGED
|
@@ -4,7 +4,7 @@ module Nfe
|
|
|
4
4
|
# Immutable value object for a verified webhook delivery, produced by
|
|
5
5
|
# {Nfe::Webhook.construct_event} after the HMAC-SHA1 signature checks out.
|
|
6
6
|
#
|
|
7
|
-
# - +type+ is the event type (e.g. +"
|
|
7
|
+
# - +type+ is the event type (e.g. +"service_invoice.issued_successfully"+), unwrapped from the
|
|
8
8
|
# delivery envelope's +action+ or +event+ key.
|
|
9
9
|
# - +data+ is the payload +Hash+ (the envelope's +payload+ or +data+ key).
|
|
10
10
|
# - +id+ is a stable event/invoice id for deduplication, or +nil+ when the
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# AUTO-GENERATED — do not edit
|
|
2
|
+
# Source: openapi/nf-servico-v1.yaml
|
|
3
|
+
# Hash: sha256:621a3b8e437e5cb37367c8cd26fec93fa2b3f87c9a59252ac987c56bb0c7ba56
|
|
4
|
+
|
|
5
|
+
module Nfe
|
|
6
|
+
module Generated
|
|
7
|
+
module NfServicoV1
|
|
8
|
+
class ErrorsResource < Data
|
|
9
|
+
attr_reader errors: Array[Hash[String, untyped]]
|
|
10
|
+
def self.new: (?errors: Array[Hash[String, untyped]]) -> instance
|
|
11
|
+
def initialize: (?errors: Array[Hash[String, untyped]]) -> void
|
|
12
|
+
def self.from_api: (Hash[String, untyped]? payload) -> instance?
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Nfe
|
|
2
|
+
class AccountWebhook < Data
|
|
3
|
+
attr_reader id: String?
|
|
4
|
+
attr_reader uri: String?
|
|
5
|
+
attr_reader content_type: String?
|
|
6
|
+
attr_reader secret: String?
|
|
7
|
+
attr_reader filters: Array[String]?
|
|
8
|
+
attr_reader insecure_ssl: bool?
|
|
9
|
+
attr_reader headers: untyped
|
|
10
|
+
attr_reader properties: untyped
|
|
11
|
+
attr_reader status: String?
|
|
12
|
+
attr_reader created_on: String?
|
|
13
|
+
attr_reader modified_on: String?
|
|
14
|
+
|
|
15
|
+
def self.from_api: (Hash[untyped, untyped]? payload) -> Nfe::AccountWebhook?
|
|
16
|
+
|
|
17
|
+
def self.new: (id: String?, uri: String?, content_type: String?, secret: String?, filters: Array[String]?, insecure_ssl: bool?, headers: untyped, properties: untyped, status: String?, created_on: String?, modified_on: String?) -> instance
|
|
18
|
+
|
|
19
|
+
def initialize: (id: String?, uri: String?, content_type: String?, secret: String?, filters: Array[String]?, insecure_ssl: bool?, headers: untyped, properties: untyped, status: String?, created_on: String?, modified_on: String?) -> void
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -13,7 +13,7 @@ module Nfe
|
|
|
13
13
|
attr_reader cancelled_on: String?
|
|
14
14
|
attr_reader amount_net: untyped
|
|
15
15
|
attr_reader services_amount: untyped
|
|
16
|
-
attr_reader borrower:
|
|
16
|
+
attr_reader borrower: Nfe::ServiceInvoiceBorrower?
|
|
17
17
|
attr_reader city_service_code: untyped
|
|
18
18
|
attr_reader federal_service_code: untyped
|
|
19
19
|
attr_reader description: String?
|
|
@@ -21,10 +21,14 @@ module Nfe
|
|
|
21
21
|
attr_reader xml: untyped
|
|
22
22
|
attr_reader created_on: String?
|
|
23
23
|
attr_reader modified_on: String?
|
|
24
|
+
attr_reader base_tax_amount: (Float | Integer)?
|
|
25
|
+
attr_reader iss_rate: (Float | Integer)?
|
|
26
|
+
attr_reader iss_tax_amount: (Float | Integer)?
|
|
27
|
+
attr_reader raw: Hash[untyped, untyped]?
|
|
24
28
|
|
|
25
29
|
def self.from_api: (Hash[untyped, untyped]? payload) -> Nfe::ServiceInvoice?
|
|
26
30
|
|
|
27
|
-
def self.new: (id: String?, flow_status: String?, flow_message: String?, status: untyped, environment: untyped, rps_number: untyped, rps_serial_number: untyped, number: untyped, check_code: String?, issued_on: String?, cancelled_on: String?, amount_net: untyped, services_amount: untyped, borrower:
|
|
28
|
-
def initialize: (id: String?, flow_status: String?, flow_message: String?, status: untyped, environment: untyped, rps_number: untyped, rps_serial_number: untyped, number: untyped, check_code: String?, issued_on: String?, cancelled_on: String?, amount_net: untyped, services_amount: untyped, borrower:
|
|
31
|
+
def self.new: (id: String?, flow_status: String?, flow_message: String?, status: untyped, environment: untyped, rps_number: untyped, rps_serial_number: untyped, number: untyped, check_code: String?, issued_on: String?, cancelled_on: String?, amount_net: untyped, services_amount: untyped, borrower: Nfe::ServiceInvoiceBorrower?, city_service_code: untyped, federal_service_code: untyped, description: String?, pdf: untyped, xml: untyped, created_on: String?, modified_on: String?, base_tax_amount: (Float | Integer)?, iss_rate: (Float | Integer)?, iss_tax_amount: (Float | Integer)?, raw: Hash[untyped, untyped]?) -> instance
|
|
32
|
+
def initialize: (id: String?, flow_status: String?, flow_message: String?, status: untyped, environment: untyped, rps_number: untyped, rps_serial_number: untyped, number: untyped, check_code: String?, issued_on: String?, cancelled_on: String?, amount_net: untyped, services_amount: untyped, borrower: Nfe::ServiceInvoiceBorrower?, city_service_code: untyped, federal_service_code: untyped, description: String?, pdf: untyped, xml: untyped, created_on: String?, modified_on: String?, base_tax_amount: (Float | Integer)?, iss_rate: (Float | Integer)?, iss_tax_amount: (Float | Integer)?, raw: Hash[untyped, untyped]?) -> void
|
|
29
33
|
end
|
|
30
34
|
end
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
module Nfe
|
|
2
|
+
class ServiceInvoiceBorrower < Data
|
|
3
|
+
attr_reader id: String?
|
|
4
|
+
attr_reader name: String?
|
|
5
|
+
attr_reader federal_tax_number: String?
|
|
6
|
+
attr_reader email: String?
|
|
7
|
+
attr_reader phone_number: String?
|
|
8
|
+
attr_reader address: untyped
|
|
9
|
+
attr_reader parent_id: String?
|
|
10
|
+
attr_reader raw: Hash[untyped, untyped]?
|
|
11
|
+
|
|
12
|
+
def self.from_api: (Hash[untyped, untyped]? payload) -> Nfe::ServiceInvoiceBorrower?
|
|
13
|
+
|
|
14
|
+
def self.new: (id: String?, name: String?, federal_tax_number: String?, email: String?, phone_number: String?, address: untyped, parent_id: String?, raw: Hash[untyped, untyped]?) -> instance
|
|
15
|
+
|
|
16
|
+
def initialize: (id: String?, name: String?, federal_tax_number: String?, email: String?, phone_number: String?, address: untyped, parent_id: String?, raw: Hash[untyped, untyped]?) -> void
|
|
17
|
+
|
|
18
|
+
def []: (untyped key) -> untyped
|
|
19
|
+
def dig: (*untyped keys) -> untyped
|
|
20
|
+
end
|
|
21
|
+
end
|
|
@@ -4,6 +4,16 @@ module Nfe
|
|
|
4
4
|
AVAILABLE_EVENTS: Array[String]
|
|
5
5
|
|
|
6
6
|
def api_family: () -> Symbol
|
|
7
|
+
def full_path: (String path) -> String
|
|
8
|
+
|
|
9
|
+
def list_account_webhooks: () -> Nfe::ListResponse
|
|
10
|
+
def create_account_webhook: (Hash[untyped, untyped] data) -> Nfe::AccountWebhook?
|
|
11
|
+
def retrieve_account_webhook: (String webhook_id) -> Nfe::AccountWebhook?
|
|
12
|
+
def update_account_webhook: (String webhook_id, Hash[untyped, untyped] data) -> Nfe::AccountWebhook?
|
|
13
|
+
def delete_account_webhook: (String webhook_id) -> nil
|
|
14
|
+
def delete_all_account_webhooks: () -> nil
|
|
15
|
+
def ping_account_webhook: (String webhook_id) -> nil
|
|
16
|
+
def fetch_event_types: () -> Array[String]
|
|
7
17
|
|
|
8
18
|
def list: (String company_id) -> Nfe::ListResponse
|
|
9
19
|
def create: (String company_id, Hash[untyped, untyped] data) -> Nfe::WebhookSubscription?
|
|
@@ -16,6 +26,8 @@ module Nfe
|
|
|
16
26
|
|
|
17
27
|
private
|
|
18
28
|
|
|
29
|
+
def http_delete: (String path, ?query: Hash[untyped, untyped], ?request_options: Nfe::RequestOptions?, ?headers: Hash[String, String]) -> Nfe::Http::Response
|
|
30
|
+
def hydrate_account_webhook: (Nfe::Http::Response response) -> Nfe::AccountWebhook?
|
|
19
31
|
def json_headers: () -> Hash[String, String]
|
|
20
32
|
def json_body: (untyped data) -> String
|
|
21
33
|
def webhook_items: (untyped payload) -> Array[untyped]
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: nfe-io
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.1.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- NFE.io Team
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-07-
|
|
11
|
+
date: 2026-07-10 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: rake
|
|
@@ -505,6 +505,7 @@ files:
|
|
|
505
505
|
- lib/nfe/generated/nf_produto_v2/vehicle_detail_resource.rb
|
|
506
506
|
- lib/nfe/generated/nf_produto_v2/volume_resource.rb
|
|
507
507
|
- lib/nfe/generated/nf_produto_v2/withdrawal_information_resource.rb
|
|
508
|
+
- lib/nfe/generated/nf_servico_v1/errors_resource.rb
|
|
508
509
|
- lib/nfe/generated/nfeio/batch_process_response.rb
|
|
509
510
|
- lib/nfe/generated/nfeio/environment.rb
|
|
510
511
|
- lib/nfe/generated/nfeio/file_parsing_options_request.rb
|
|
@@ -710,6 +711,7 @@ files:
|
|
|
710
711
|
- lib/nfe/resources/companies.rb
|
|
711
712
|
- lib/nfe/resources/consumer_invoice_query.rb
|
|
712
713
|
- lib/nfe/resources/consumer_invoices.rb
|
|
714
|
+
- lib/nfe/resources/dto/account_webhook.rb
|
|
713
715
|
- lib/nfe/resources/dto/addresses/address_lookup_response.rb
|
|
714
716
|
- lib/nfe/resources/dto/company.rb
|
|
715
717
|
- lib/nfe/resources/dto/consumer_invoice.rb
|
|
@@ -726,6 +728,7 @@ files:
|
|
|
726
728
|
- lib/nfe/resources/dto/product_invoice_query/product_invoice_details.rb
|
|
727
729
|
- lib/nfe/resources/dto/product_invoice_query/product_invoice_events_response.rb
|
|
728
730
|
- lib/nfe/resources/dto/service_invoice.rb
|
|
731
|
+
- lib/nfe/resources/dto/service_invoice_borrower.rb
|
|
729
732
|
- lib/nfe/resources/dto/state_taxes/nfe_state_tax.rb
|
|
730
733
|
- lib/nfe/resources/dto/tax_codes/tax_code_paginated_response.rb
|
|
731
734
|
- lib/nfe/resources/dto/webhook.rb
|
|
@@ -1126,6 +1129,7 @@ files:
|
|
|
1126
1129
|
- sig/nfe/generated/nf_produto_v2/vehicle_detail_resource.rbs
|
|
1127
1130
|
- sig/nfe/generated/nf_produto_v2/volume_resource.rbs
|
|
1128
1131
|
- sig/nfe/generated/nf_produto_v2/withdrawal_information_resource.rbs
|
|
1132
|
+
- sig/nfe/generated/nf_servico_v1/errors_resource.rbs
|
|
1129
1133
|
- sig/nfe/generated/nfeio/batch_process_response.rbs
|
|
1130
1134
|
- sig/nfe/generated/nfeio/environment.rbs
|
|
1131
1135
|
- sig/nfe/generated/nfeio/file_parsing_options_request.rbs
|
|
@@ -1333,6 +1337,7 @@ files:
|
|
|
1333
1337
|
- sig/nfe/resources/consumer_invoice_pending.rbs
|
|
1334
1338
|
- sig/nfe/resources/consumer_invoice_query.rbs
|
|
1335
1339
|
- sig/nfe/resources/consumer_invoices.rbs
|
|
1340
|
+
- sig/nfe/resources/dto/account_webhook.rbs
|
|
1336
1341
|
- sig/nfe/resources/dto/addresses/address_lookup_response.rbs
|
|
1337
1342
|
- sig/nfe/resources/dto/company.rbs
|
|
1338
1343
|
- sig/nfe/resources/dto/consumer_invoice.rbs
|
|
@@ -1349,6 +1354,7 @@ files:
|
|
|
1349
1354
|
- sig/nfe/resources/dto/product_invoice_query/product_invoice_details.rbs
|
|
1350
1355
|
- sig/nfe/resources/dto/product_invoice_query/product_invoice_events_response.rbs
|
|
1351
1356
|
- sig/nfe/resources/dto/service_invoice.rbs
|
|
1357
|
+
- sig/nfe/resources/dto/service_invoice_borrower.rbs
|
|
1352
1358
|
- sig/nfe/resources/dto/state_taxes/nfe_state_tax.rbs
|
|
1353
1359
|
- sig/nfe/resources/dto/tax_codes/tax_code_paginated_response.rbs
|
|
1354
1360
|
- sig/nfe/resources/dto/webhook.rbs
|