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 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,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module CorvoNfse
4
+ module Resources
5
+ # Shared constructor for every resource namespace.
6
+ class Base
7
+ def initialize(transport)
8
+ @t = transport
9
+ end
10
+ end
11
+ end
12
+ 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