corvo-nfse 0.1.1
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +182 -0
- data/lib/corvo_nfse/client.rb +59 -0
- data/lib/corvo_nfse/error.rb +37 -0
- data/lib/corvo_nfse/resources/base.rb +12 -0
- data/lib/corvo_nfse/resources/billing.rb +16 -0
- data/lib/corvo_nfse/resources/certificates.rb +37 -0
- data/lib/corvo_nfse/resources/companies.rb +41 -0
- data/lib/corvo_nfse/resources/credits.rb +27 -0
- data/lib/corvo_nfse/resources/health.rb +16 -0
- data/lib/corvo_nfse/resources/invoices.rb +81 -0
- data/lib/corvo_nfse/resources/plans.rb +26 -0
- data/lib/corvo_nfse/resources/webhooks.rb +65 -0
- data/lib/corvo_nfse/transport.rb +302 -0
- data/lib/corvo_nfse/types/billing.rb +17 -0
- data/lib/corvo_nfse/types/certificate.rb +11 -0
- data/lib/corvo_nfse/types/common.rb +15 -0
- data/lib/corvo_nfse/types/company.rb +16 -0
- data/lib/corvo_nfse/types/credits.rb +41 -0
- data/lib/corvo_nfse/types/invoice.rb +30 -0
- data/lib/corvo_nfse/types/webhook.rb +25 -0
- data/lib/corvo_nfse/types.rb +51 -0
- data/lib/corvo_nfse/util.rb +32 -0
- data/lib/corvo_nfse/version.rb +5 -0
- data/lib/corvo_nfse/webhooks.rb +50 -0
- data/lib/corvo_nfse.rb +27 -0
- metadata +118 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 1a33e26dcbf646148caedcdea5d278c58aca67bd3cca85c67926c33e81f19a2a
|
|
4
|
+
data.tar.gz: 4951671a468a867155484cc8c42e48cf2d39bdd9ac198966be234e75ba144e44
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: d9a528b0402af2bce09e14ea51a74836609357a4acbb31764944ca18155eb73291fdbd28ae3d4301f20e82e7ea8cbea2c903de89de701e2037eeeab0fd3d4645
|
|
7
|
+
data.tar.gz: f531954b335a40ba209d2da5423fb82c0db8e19df01bf96e2a28b5398eca203771fe6e7acd3c855a98e9b538318f1d80333aca299714bfabd9b1fa9e0ffe771f
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Corvo Technology
|
|
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.
|
data/README.md
ADDED
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
# corvo-nfse
|
|
2
|
+
|
|
3
|
+
Ruby SDK for the [Corvo](https://corvo.tech) NFS-e API. Register companies, calculate taxes, issue Brazilian service invoices (NFS-e), follow them by webhook and download the PDF and XML, from Ruby 3.2+.
|
|
4
|
+
|
|
5
|
+
- **Result-style returns.** Every call returns `[data, error]` where exactly one is `nil`. API, network and timeout failures are values, never raised. The error carries the server's stable `code`, the HTTP `status`, the `request_id`, field-level `details` and `retry_after`.
|
|
6
|
+
- **Pagination built in.** Lists return `Types::Paginated`, and `iterate` walks every page.
|
|
7
|
+
- **Safe retries.** Issuing an invoice and sending an email accept an idempotency key.
|
|
8
|
+
- **Webhook signature check** in constant time.
|
|
9
|
+
- **No runtime dependencies**: it uses `net/http`, `uri`, `json` and `openssl` from the standard library. Library only, no CLI.
|
|
10
|
+
|
|
11
|
+
Full API reference: <https://corvo.tech/en/api-docs>.
|
|
12
|
+
|
|
13
|
+
## Installation
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
gem install corvo-nfse
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
Or in your `Gemfile`:
|
|
20
|
+
|
|
21
|
+
```ruby
|
|
22
|
+
gem "corvo-nfse"
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
## Quickstart
|
|
26
|
+
|
|
27
|
+
Start with a `corvo_test_...` key: invoices issued with it have no fiscal value.
|
|
28
|
+
|
|
29
|
+
```ruby
|
|
30
|
+
require "corvo_nfse"
|
|
31
|
+
|
|
32
|
+
client = CorvoNfse::Client.new(api_key: "corvo_test_...") # or set CORVO_API_KEY
|
|
33
|
+
|
|
34
|
+
page, error = client.companies.list(limit: 10)
|
|
35
|
+
|
|
36
|
+
if error
|
|
37
|
+
warn "#{error.code}: #{error.message} (HTTP #{error.status}, request #{error.request_id})"
|
|
38
|
+
else
|
|
39
|
+
page.data.each { |company| puts company.legalName }
|
|
40
|
+
end
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`Client.new` is the only call that raises (`ArgumentError`): it fails fast when no API key is configured or the base URL is invalid. `iterate` also raises, see below.
|
|
44
|
+
|
|
45
|
+
### Register a company
|
|
46
|
+
|
|
47
|
+
```ruby
|
|
48
|
+
company, error = client.companies.create(
|
|
49
|
+
legalName: "Empresa Exemplo Ltda",
|
|
50
|
+
taxId: "11222333000181",
|
|
51
|
+
taxRegime: "Simples Nacional", # "MEI" | "Simples Nacional" | "Lucro Presumido" | "Lucro Real"
|
|
52
|
+
municipalityCode: "3550308", # IBGE code, 7 digits
|
|
53
|
+
defaultServiceCode: "010101" # optional: used when an invoice omits service.code
|
|
54
|
+
)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
In `live`, also upload the A1 certificate. The company is ready when `company.status == "ready"` and `company.setupGaps` is empty.
|
|
58
|
+
|
|
59
|
+
```ruby
|
|
60
|
+
p12 = File.binread("certificado.p12")
|
|
61
|
+
cert, error = client.certificates.upload(company.id, p12, "senha-do-certificado")
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Estimate the taxes before issuing
|
|
65
|
+
|
|
66
|
+
Nothing is issued, stored or charged.
|
|
67
|
+
|
|
68
|
+
```ruby
|
|
69
|
+
estimate, error = client.invoices.calculate(
|
|
70
|
+
companyTaxId: "11222333000181",
|
|
71
|
+
service: { description: "Consultoria" },
|
|
72
|
+
amount: 1500,
|
|
73
|
+
competenceDate: "2026-10-16"
|
|
74
|
+
)
|
|
75
|
+
# estimate["status"] is "ready" or "blocked": a fiscal gap is data, not an error.
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Issue an invoice
|
|
79
|
+
|
|
80
|
+
Issuing is asynchronous: a successful call means the request was accepted, not that the invoice is authorized. Follow the status with `invoices.get` or webhooks.
|
|
81
|
+
|
|
82
|
+
```ruby
|
|
83
|
+
invoice, error = client.invoices.create(
|
|
84
|
+
{
|
|
85
|
+
companyTaxId: "11222333000181", # or companyId
|
|
86
|
+
# customer is optional; when sent, taxId (CPF or CNPJ) is required.
|
|
87
|
+
customer: { taxId: "52998224725", name: "Cliente", email: "cliente@example.com" },
|
|
88
|
+
service: { description: "Consultoria" }, # code and city default to the company's
|
|
89
|
+
amount: 150.5,
|
|
90
|
+
competenceDate: "2026-10-16",
|
|
91
|
+
options: { email: { send: true } }
|
|
92
|
+
},
|
|
93
|
+
idempotency_key: "order-42" # the same key never issues twice
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
raise error if error # CorvoNfse::CorvoApiError is safe to raise
|
|
97
|
+
puts invoice.id
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
Use `options: { type: "SCHEDULE" }` with a future `competenceDate` to schedule the issuance. Parameters and response fields use the API's camelCase wire format, unchanged.
|
|
101
|
+
|
|
102
|
+
### Walk every page
|
|
103
|
+
|
|
104
|
+
```ruby
|
|
105
|
+
client.invoices.iterate(companyId: "c_1", status: "authorized") do |inv|
|
|
106
|
+
puts inv.nfseNumber
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
client.companies.iterate(limit: 100).lazy.map(&:taxId).first(5) # or lazily, as an Enumerator
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
`iterate` is the one method that raises: a failed page raises `CorvoNfse::CorvoApiError`, since a block or an Enumerator has no slot for a result.
|
|
113
|
+
|
|
114
|
+
### Download the PDF or XML
|
|
115
|
+
|
|
116
|
+
The invoice must be `authorized` and have its documents ready.
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
file, error = client.invoices.pdf("inv_1")
|
|
120
|
+
File.binwrite("nfse.pdf", file.content) unless error # also file.mime_type and file.file_name
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Verify webhook signatures
|
|
124
|
+
|
|
125
|
+
```ruby
|
|
126
|
+
raw = request.body.read # the raw bytes: do not re-serialize the JSON
|
|
127
|
+
sig = request.env["HTTP_X_CORVO_SIGNATURE"]
|
|
128
|
+
|
|
129
|
+
if CorvoNfse::Webhooks.verify_signature(raw, sig, secret)
|
|
130
|
+
# authentic; deduplicate by the event id, the same event can arrive twice
|
|
131
|
+
else
|
|
132
|
+
# reject with 400
|
|
133
|
+
end
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
`CorvoNfse::Webhooks::EVENTS` lists the 9 events you can subscribe to. The `webhook.test` event is only sent by `client.webhooks.test`.
|
|
137
|
+
|
|
138
|
+
## Resources
|
|
139
|
+
|
|
140
|
+
| Namespace | Methods |
|
|
141
|
+
| --- | --- |
|
|
142
|
+
| `client.companies` | `create`, `get`, `update`, `remove`, `list`, `iterate` |
|
|
143
|
+
| `client.certificates` | `upload`, `list`, `remove` |
|
|
144
|
+
| `client.invoices` | `create`, `calculate`, `get`, `list`, `iterate`, `events`, `pdf`, `xml`, `download_url`, `send_email` |
|
|
145
|
+
| `client.webhooks` | `list`, `create`, `remove`, `test`, plus `deliveries.list/iterate/stats/retry` |
|
|
146
|
+
| `client.credits` | `balance`, `plans`, `history`, `iterate_history` |
|
|
147
|
+
| `client.billing` | `plan`, `usage` |
|
|
148
|
+
| `client.plans` | `list`, `get`, `quote` (public price catalog) |
|
|
149
|
+
| `client.health` | `live`, `ready` |
|
|
150
|
+
|
|
151
|
+
Changing plans and the billing portal exist only in the dashboard, so they are not in the SDK. Immutable response types live under `CorvoNfse::Types` as `Data` classes whose members mirror the wire format (for example `Types::Invoice#accessKey`).
|
|
152
|
+
|
|
153
|
+
## Errors
|
|
154
|
+
|
|
155
|
+
`CorvoNfse::CorvoApiError` carries `status`, `code`, `message`, `details`, `request_id` and `retry_after`. `code` is the API's `ERROR_*` code, falling back to `http_<status>`; transport failures use `status = 0` and a `code` of `network_error`, `timeout` or `invalid_json`. On `429`, `retry_after` holds the seconds to wait.
|
|
156
|
+
|
|
157
|
+
## Configuration
|
|
158
|
+
|
|
159
|
+
```ruby
|
|
160
|
+
CorvoNfse::Client.new(
|
|
161
|
+
api_key: "corvo_test_...", # falls back to ENV["CORVO_API_KEY"]
|
|
162
|
+
base_url: "https://api.corvo.tech", # falls back to ENV["CORVO_BASE_URL"], then the default
|
|
163
|
+
prefix: "v1",
|
|
164
|
+
timeout: 30, # open/read/write timeout, seconds
|
|
165
|
+
allow_insecure_http: false # http:// only for loopback and private hosts
|
|
166
|
+
)
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Auth is the `X-API-Key` header. Keys are `corvo_test_...` or `corvo_live_...` and belong to one account and environment; the key decides the environment.
|
|
170
|
+
|
|
171
|
+
## Development
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
bundle install
|
|
175
|
+
bundle exec rspec # the contract spec is pending without env
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The contract spec runs only when `CORVO_API_KEY` and `CORVO_BASE_URL` point to a reachable instance.
|
|
179
|
+
|
|
180
|
+
## License
|
|
181
|
+
|
|
182
|
+
MIT, see [LICENSE](LICENSE).
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "uri"
|
|
4
|
+
|
|
5
|
+
module CorvoNfse
|
|
6
|
+
# Entry point for the Corvo NFS-e API.
|
|
7
|
+
#
|
|
8
|
+
# client = CorvoNfse::Client.new(api_key: "corvo_live_xxx") # or CORVO_API_KEY
|
|
9
|
+
# companies, error = client.companies.list(limit: 10)
|
|
10
|
+
class Client
|
|
11
|
+
DEFAULT_BASE_URL = "https://api.corvo.tech"
|
|
12
|
+
ENV_API_KEY = "CORVO_API_KEY"
|
|
13
|
+
ENV_BASE_URL = "CORVO_BASE_URL"
|
|
14
|
+
|
|
15
|
+
attr_reader :billing, :certificates, :companies, :credits, :health,
|
|
16
|
+
:invoices, :plans, :webhooks, :base_url
|
|
17
|
+
|
|
18
|
+
def initialize(api_key: nil, base_url: nil, prefix: Transport::DEFAULT_PREFIX,
|
|
19
|
+
timeout: Transport::DEFAULT_TIMEOUT, allow_insecure_http: false)
|
|
20
|
+
key = api_key || ENV[ENV_API_KEY]
|
|
21
|
+
if key.nil? || key.empty?
|
|
22
|
+
raise ArgumentError, "Corvo: api_key is required — pass api_key or set CORVO_API_KEY"
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
base = (base_url || ENV[ENV_BASE_URL] || DEFAULT_BASE_URL).to_s.sub(%r{/+\z}, "")
|
|
26
|
+
uri = parse_base(base)
|
|
27
|
+
|
|
28
|
+
if uri.scheme == "http" && !allow_insecure_http && !Util.private_host?(uri.host)
|
|
29
|
+
raise ArgumentError,
|
|
30
|
+
"Corvo: http:// is only allowed for loopback/private hosts " \
|
|
31
|
+
"(got #{uri.host}). Use https:// or pass allow_insecure_http: true."
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
@transport = Transport.new(api_key: key, base_url: base, prefix: prefix, timeout: timeout)
|
|
35
|
+
@base_url = @transport.base_url
|
|
36
|
+
@billing = Resources::Billing.new(@transport)
|
|
37
|
+
@certificates = Resources::Certificates.new(@transport)
|
|
38
|
+
@companies = Resources::Companies.new(@transport)
|
|
39
|
+
@credits = Resources::Credits.new(@transport)
|
|
40
|
+
@health = Resources::Health.new(@transport)
|
|
41
|
+
@invoices = Resources::Invoices.new(@transport)
|
|
42
|
+
@plans = Resources::Plans.new(@transport)
|
|
43
|
+
@webhooks = Resources::Webhooks.new(@transport)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def parse_base(base)
|
|
49
|
+
uri = URI.parse(base)
|
|
50
|
+
unless uri.is_a?(URI::HTTP) && uri.host
|
|
51
|
+
raise ArgumentError, "Corvo: invalid base_url: #{base}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
uri
|
|
55
|
+
rescue URI::InvalidURIError
|
|
56
|
+
raise ArgumentError, "Corvo: invalid base_url: #{base}"
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CorvoNfse
|
|
4
|
+
# Typed error carrying the server's stable ``code`` and HTTP context.
|
|
5
|
+
#
|
|
6
|
+
# It is both the value inside a ``[nil, error]`` result AND a raiseable
|
|
7
|
+
# exception. API, network, and timeout failures are returned as values, not
|
|
8
|
+
# raised. The single documented exception is the ``iterate`` helpers, which
|
|
9
|
+
# raise the error on first-page failure because a block/enumerator has no
|
|
10
|
+
# return slot for it.
|
|
11
|
+
#
|
|
12
|
+
# Network and timeout failures use ``status = 0`` and a ``code`` of
|
|
13
|
+
# ``"network_error"`` or ``"timeout"``.
|
|
14
|
+
class CorvoApiError < StandardError
|
|
15
|
+
attr_reader :status, :code, :details, :request_id, :retry_after
|
|
16
|
+
|
|
17
|
+
def initialize(status:, code:, message:, details: nil, request_id: nil, retry_after: nil)
|
|
18
|
+
super(message)
|
|
19
|
+
@status = status
|
|
20
|
+
@code = code
|
|
21
|
+
@details = details || []
|
|
22
|
+
@request_id = request_id
|
|
23
|
+
@retry_after = retry_after
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def to_h
|
|
27
|
+
{
|
|
28
|
+
status: status,
|
|
29
|
+
code: code,
|
|
30
|
+
message: message,
|
|
31
|
+
details: details,
|
|
32
|
+
request_id: request_id,
|
|
33
|
+
retry_after: retry_after
|
|
34
|
+
}
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CorvoNfse
|
|
4
|
+
module Resources
|
|
5
|
+
# Billing — plan, usage.
|
|
6
|
+
class Billing < Base
|
|
7
|
+
def plan
|
|
8
|
+
@t.request("GET", "/billing/plan", type: Types::BillingPlanResponse)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def usage
|
|
12
|
+
@t.request("GET", "/billing/usage", type: Types::QuotaStatus)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CorvoNfse
|
|
4
|
+
module Resources
|
|
5
|
+
# Certificates — upload (multipart), list, remove.
|
|
6
|
+
class Certificates < Base
|
|
7
|
+
# Upload a PKCS#12 certificate (max 50 KiB). The SDK builds the form.
|
|
8
|
+
def upload(company_id, file, password, file_name: "certificate.p12")
|
|
9
|
+
fields = [
|
|
10
|
+
["file", file, "application/x-pkcs12", file_name],
|
|
11
|
+
["password", password]
|
|
12
|
+
]
|
|
13
|
+
@t.request_form(
|
|
14
|
+
"POST",
|
|
15
|
+
"/companies/#{Util.enc(company_id)}/certificates",
|
|
16
|
+
fields,
|
|
17
|
+
type: Types::Certificate
|
|
18
|
+
)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def list(company_id, limit: nil, cursor: nil)
|
|
22
|
+
@t.list_page(
|
|
23
|
+
"/companies/#{Util.enc(company_id)}/certificates",
|
|
24
|
+
{ limit: limit, cursor: cursor },
|
|
25
|
+
mapper: ->(item) { Types::Certificate.from_wire(item) }
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def remove(company_id, certificate_id)
|
|
30
|
+
@t.request(
|
|
31
|
+
"DELETE",
|
|
32
|
+
"/companies/#{Util.enc(company_id)}/certificates/#{Util.enc(certificate_id)}"
|
|
33
|
+
)
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CorvoNfse
|
|
4
|
+
module Resources
|
|
5
|
+
# Companies — create, get, update, remove, list, iterate.
|
|
6
|
+
class Companies < Base
|
|
7
|
+
def create(**params)
|
|
8
|
+
@t.request("POST", "/companies", json: params, type: Types::Company)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def list(limit: nil, cursor: nil)
|
|
12
|
+
@t.list_page(
|
|
13
|
+
"/companies",
|
|
14
|
+
{ limit: limit, cursor: cursor },
|
|
15
|
+
mapper: ->(item) { Types::Company.from_wire(item) }
|
|
16
|
+
)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def iterate(limit: nil, cursor: nil, &block)
|
|
20
|
+
@t.iterate(
|
|
21
|
+
"/companies",
|
|
22
|
+
{ limit: limit, cursor: cursor },
|
|
23
|
+
mapper: ->(item) { Types::Company.from_wire(item) },
|
|
24
|
+
&block
|
|
25
|
+
)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get(id)
|
|
29
|
+
@t.request("GET", "/companies/#{Util.enc(id)}", type: Types::Company)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def update(id, **params)
|
|
33
|
+
@t.request("PATCH", "/companies/#{Util.enc(id)}", json: params, type: Types::Company)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def remove(id)
|
|
37
|
+
@t.request("DELETE", "/companies/#{Util.enc(id)}")
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CorvoNfse
|
|
4
|
+
module Resources
|
|
5
|
+
# Credits — balance, plans, history, iterate_history.
|
|
6
|
+
class Credits < Base
|
|
7
|
+
PLAN_MAPPER = ->(item) { Types::CreditPlan.from_wire(item) }
|
|
8
|
+
TX_MAPPER = ->(item) { Types::CreditTransaction.from_wire(item) }
|
|
9
|
+
|
|
10
|
+
def balance
|
|
11
|
+
@t.request("GET", "/credits", type: Types::CreditBalance)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def plans(limit: nil, cursor: nil)
|
|
15
|
+
@t.list_page("/credits/plans", { limit: limit, cursor: cursor }, mapper: PLAN_MAPPER)
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def history(limit: nil, cursor: nil)
|
|
19
|
+
@t.list_page("/credits/history", { limit: limit, cursor: cursor }, mapper: TX_MAPPER)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def iterate_history(limit: nil, cursor: nil, &block)
|
|
23
|
+
@t.iterate("/credits/history", { limit: limit, cursor: cursor }, mapper: TX_MAPPER, &block)
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CorvoNfse
|
|
4
|
+
module Resources
|
|
5
|
+
# Public health checks (no envelope — the raw body is returned).
|
|
6
|
+
class Health < Base
|
|
7
|
+
def live
|
|
8
|
+
@t.request("GET", "/health/live", unwrap: "none")
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def ready
|
|
12
|
+
@t.request("GET", "/health/ready", unwrap: "none")
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CorvoNfse
|
|
4
|
+
module Resources
|
|
5
|
+
# Invoices: create, calculate, get, list, iterate, events, pdf, xml,
|
|
6
|
+
# download_url, send_email.
|
|
7
|
+
class Invoices < Base
|
|
8
|
+
INVOICE_MAPPER = ->(item) { Types::Invoice.from_wire(item) }
|
|
9
|
+
EVENT_MAPPER = ->(item) { Types::InvoiceEvent.from_wire(item) }
|
|
10
|
+
|
|
11
|
+
def create(params, idempotency_key: nil)
|
|
12
|
+
@t.request(
|
|
13
|
+
"POST", "/invoices",
|
|
14
|
+
json: params,
|
|
15
|
+
idempotency_key: idempotency_key,
|
|
16
|
+
type: Types::Invoice
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Estimate the taxes without issuing, storing or charging anything.
|
|
21
|
+
# Same body as +create+. A fiscal gap is not an error: the result has
|
|
22
|
+
# +"status" => "blocked"+ with +"blocking"+ and +"pendingFields"+. Money
|
|
23
|
+
# values are decimal strings. Returns the parsed JSON (a Hash).
|
|
24
|
+
def calculate(params)
|
|
25
|
+
@t.request("POST", "/invoices/calculate", json: params)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get(id)
|
|
29
|
+
@t.request("GET", "/invoices/#{Util.enc(id)}", type: Types::Invoice)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def list(companyId: nil, status: nil, from: nil, to: nil, externalId: nil, limit: nil, cursor: nil)
|
|
33
|
+
query = {
|
|
34
|
+
companyId: companyId, status: status, from: from, to: to,
|
|
35
|
+
externalId: externalId, limit: limit, cursor: cursor
|
|
36
|
+
}
|
|
37
|
+
@t.list_page("/invoices", query, mapper: INVOICE_MAPPER)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def iterate(companyId: nil, status: nil, from: nil, to: nil, externalId: nil, limit: nil, cursor: nil, &block)
|
|
41
|
+
query = {
|
|
42
|
+
companyId: companyId, status: status, from: from, to: to,
|
|
43
|
+
externalId: externalId, limit: limit, cursor: cursor
|
|
44
|
+
}
|
|
45
|
+
@t.iterate("/invoices", query, mapper: INVOICE_MAPPER, &block)
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def events(id, limit: nil, cursor: nil)
|
|
49
|
+
@t.list_page(
|
|
50
|
+
"/invoices/#{Util.enc(id)}/events",
|
|
51
|
+
{ limit: limit, cursor: cursor },
|
|
52
|
+
mapper: EVENT_MAPPER
|
|
53
|
+
)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def pdf(id)
|
|
57
|
+
@t.request_raw("GET", "/invoices/#{Util.enc(id)}/pdf")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def xml(id)
|
|
61
|
+
@t.request_raw("GET", "/invoices/#{Util.enc(id)}/xml")
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def download_url(id, format = "pdf")
|
|
65
|
+
@t.request(
|
|
66
|
+
"POST", "/invoices/#{Util.enc(id)}/download-url",
|
|
67
|
+
json: { format: format },
|
|
68
|
+
type: Types::InvoiceDownloadUrl
|
|
69
|
+
)
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def send_email(id, idempotency_key: nil)
|
|
73
|
+
@t.request(
|
|
74
|
+
"POST", "/invoices/#{Util.enc(id)}/email",
|
|
75
|
+
idempotency_key: idempotency_key,
|
|
76
|
+
type: Types::InvoiceEmailResult
|
|
77
|
+
)
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CorvoNfse
|
|
4
|
+
module Resources
|
|
5
|
+
# Plans: the public price catalog (monthly cycle only). Prices in centavos.
|
|
6
|
+
class Plans < Base
|
|
7
|
+
MAPPER = ->(item) { Types::PlanQuote.from_wire(item) }
|
|
8
|
+
|
|
9
|
+
# One page of plan prices, from the smallest to the largest allowance.
|
|
10
|
+
def list(limit: nil, cursor: nil)
|
|
11
|
+
@t.list_page("/plans", { limit: limit, cursor: cursor }, mapper: MAPPER)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
# The price of one plan: free, developer, growth, scale or volume.
|
|
15
|
+
def get(slug)
|
|
16
|
+
@t.request("GET", "/plans/#{Util.enc(slug)}", type: Types::PlanQuote)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# The plan recommended for +volume+ invoices per month. The data is nil
|
|
20
|
+
# (with no error) when the volume is missing or not positive.
|
|
21
|
+
def quote(volume)
|
|
22
|
+
@t.request("GET", "/plans/quote", query: { volume: volume }, type: Types::PlanRecommendation)
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module CorvoNfse
|
|
4
|
+
module Resources
|
|
5
|
+
# Nested ``/webhooks/deliveries`` sub-resource.
|
|
6
|
+
class Deliveries < Base
|
|
7
|
+
DELIVERY_MAPPER = ->(item) { Types::WebhookDelivery.from_wire(item) }
|
|
8
|
+
|
|
9
|
+
def list(hours: nil, environment: nil, limit: nil, cursor: nil)
|
|
10
|
+
query = { hours: hours, environment: environment, limit: limit, cursor: cursor }
|
|
11
|
+
@t.list_page("/webhooks/deliveries", query, mapper: DELIVERY_MAPPER)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def iterate(hours: nil, environment: nil, limit: nil, cursor: nil, &block)
|
|
15
|
+
query = { hours: hours, environment: environment, limit: limit, cursor: cursor }
|
|
16
|
+
@t.iterate("/webhooks/deliveries", query, mapper: DELIVERY_MAPPER, &block)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def stats(hours: nil, environment: nil)
|
|
20
|
+
@t.request(
|
|
21
|
+
"GET", "/webhooks/deliveries/stats",
|
|
22
|
+
query: { hours: hours, environment: environment },
|
|
23
|
+
type: Types::WebhookDeliveryStats
|
|
24
|
+
)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def retry(delivery_id)
|
|
28
|
+
@t.request("POST", "/webhooks/deliveries/#{Util.enc(delivery_id)}/retry")
|
|
29
|
+
end
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Webhooks — list, create, remove, test + nested deliveries.
|
|
33
|
+
class Webhooks < Base
|
|
34
|
+
WEBHOOK_MAPPER = ->(item) { Types::Webhook.from_wire(item) }
|
|
35
|
+
|
|
36
|
+
attr_reader :deliveries
|
|
37
|
+
|
|
38
|
+
def initialize(transport)
|
|
39
|
+
super
|
|
40
|
+
@deliveries = Deliveries.new(transport)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def list(limit: nil, cursor: nil)
|
|
44
|
+
@t.list_page("/webhooks", { limit: limit, cursor: cursor }, mapper: WEBHOOK_MAPPER)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def iterate(limit: nil, cursor: nil, &block)
|
|
48
|
+
@t.iterate("/webhooks", { limit: limit, cursor: cursor }, mapper: WEBHOOK_MAPPER, &block)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Create a webhook. The +secret+ is returned once, at creation.
|
|
52
|
+
def create(params)
|
|
53
|
+
@t.request("POST", "/webhooks", json: params, type: Types::WebhookWithSecret)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def remove(id)
|
|
57
|
+
@t.request("DELETE", "/webhooks/#{Util.enc(id)}")
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def test(id)
|
|
61
|
+
@t.request("POST", "/webhooks/#{Util.enc(id)}/test")
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
end
|