perata-sdk 0.2.0 → 0.4.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/README.md +37 -9
- data/lib/perata/client.rb +53 -0
- data/lib/perata/error.rb +13 -7
- data/lib/perata/models.rb +1597 -143
- data/lib/perata/params.rb +765 -2
- data/lib/perata/services.rb +535 -19
- data/lib/perata/version.rb +1 -1
- data/lib/perata/webhooks.rb +19 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: ffb21818636a4985aed2e37bed5c804482d4e33f36d59ac855170d95747dac5b
|
|
4
|
+
data.tar.gz: 2201ef7ba61a245ccf70aed0c7d48d11999f81fd8025a84acaf551ccec09441b
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e9d0be391bcc52e48eeee7fca3ac3ded3bd246aaf5aa595f7c568a231ed6195e7b70cb01bd12df25f2731d3e4ceece23f3c03cf7b9e7f233896f09ebb917e062
|
|
7
|
+
data.tar.gz: daee592161aed12eaabe847b9d8f43b4651ea18d8cf1ab98decf477343c1153fc356cc78d8443594c694c55cbab2c32b9a2226105b97bf5ab0982333db9749ee
|
data/README.md
CHANGED
|
@@ -22,24 +22,52 @@ end
|
|
|
22
22
|
|
|
23
23
|
| Serviço | Métodos |
|
|
24
24
|
|---|---|
|
|
25
|
-
| `perata.
|
|
25
|
+
| `perata.benefits` | `create`, `delete`, `get`, `list`, `list_iter`, `list_grants`, `list_grants_iter`, `update` |
|
|
26
|
+
| `perata.checkout_links` | `create`, `delete`, `get`, `list`, `list_iter`, `update` |
|
|
27
|
+
| `perata.checkouts` | `create`, `get`, `update` |
|
|
26
28
|
| `perata.customer_sessions` | `create` |
|
|
27
|
-
| `perata.customers` | `create`, `get`, `list`, `list_iter` |
|
|
28
|
-
| `perata.
|
|
29
|
+
| `perata.customers` | `create`, `delete`, `get`, `get_external`, `get_state`, `list`, `list_iter`, `update`, `update_external` |
|
|
30
|
+
| `perata.discounts` | `create`, `delete`, `get`, `list`, `list_iter`, `update` |
|
|
31
|
+
| `perata.disputes` | `get`, `list`, `list_iter` |
|
|
32
|
+
| `perata.events` | `list`, `list_iter` |
|
|
33
|
+
| `perata.meters` | `get`, `list`, `list_iter` |
|
|
34
|
+
| `perata.orders` | `get`, `get_invoice`, `get_receipt`, `list`, `list_iter` |
|
|
29
35
|
| `perata.payment_methods` | `detach`, `list`, `list_iter` |
|
|
30
36
|
| `perata.payments` | `get`, `list`, `list_iter` |
|
|
31
37
|
| `perata.prices` | `create`, `list`, `list_iter` |
|
|
32
|
-
| `perata.products` | `create`, `get`, `list`, `list_iter` |
|
|
33
|
-
| `perata.refunds` | `create` |
|
|
34
|
-
| `perata.subscriptions` | `cancel`, `get`, `list`, `list_iter` |
|
|
38
|
+
| `perata.products` | `archive`, `create`, `get`, `list`, `list_iter`, `update` |
|
|
39
|
+
| `perata.refunds` | `create`, `list`, `list_iter` |
|
|
40
|
+
| `perata.subscriptions` | `cancel`, `get`, `list`, `list_iter`, `update` |
|
|
41
|
+
| `perata.usage` | `ingest` |
|
|
42
|
+
| `perata.webhooks` | `create_endpoint`, `delete_endpoint`, `get_endpoint`, `list_deliveries`, `list_deliveries_iter`, `list_endpoints`, `list_endpoints_iter`, `update_endpoint` |
|
|
35
43
|
|
|
36
44
|
Os modelos são objetos imutáveis com leitores em snake_case (`payment.amount_cents`). O corpo de uma escrita é a classe de parâmetros do método (`Perata::ProductsCreateParams.new(name: "Curso")`) ou um `Hash` com os mesmos nomes; um campo opcional não informado fica fora do JSON. Os métodos `_iter` devolvem um `Enumerator` que percorre todas as páginas a partir de `page:` (ou recebem um bloco).
|
|
37
45
|
|
|
38
|
-
As escritas aceitam `idempotency_key:`, e todas aceitam `api_key:` para usar a chave de outra organização numa chamada. `Perata::Client.new` aceita `base_url:`, `timeout:` (30 segundos) e `transport:`.
|
|
46
|
+
As escritas aceitam `idempotency_key:`, e todas aceitam `api_key:` para usar a chave de outra organização numa chamada. `Perata::Client.new` aceita `base_url:`, `timeout:` (30 segundos) e `transport:`. Um POST sem chave de idempotência é enviado com uma `Idempotency-Key` própria (UUID v4 aleatório, uma por chamada); passe a sua para poder repetir a mesma operação com segurança.
|
|
47
|
+
|
|
48
|
+
## Consumo (ingestão de eventos)
|
|
49
|
+
|
|
50
|
+
A ingestão de consumo usa outra credencial: a de uma fonte de ingestão (**Configurações › Fontes de ingestão**), `pus_test_…` em teste e `pus_live_…` em produção. Monte um cliente com ela; a chave de API da organização (`prk_…`) é recusada aqui com 401, e uma credencial de um ambiente nunca escreve no outro.
|
|
51
|
+
|
|
52
|
+
```ruby
|
|
53
|
+
usage = Perata::Client.new(ENV.fetch("PERATA_USAGE_KEY")) # pus_test_… ou pus_live_…
|
|
54
|
+
|
|
55
|
+
receipt = usage.usage.ingest({ events: [{
|
|
56
|
+
customer_id: customer_id,
|
|
57
|
+
external_event_id: "requisicao-001", # a mesma fonte repetindo o id com o mesmo conteúdo é replay
|
|
58
|
+
input_tokens: "1200",
|
|
59
|
+
output_tokens: "300",
|
|
60
|
+
cached_tokens: "0",
|
|
61
|
+
occurred_at: Time.now.utc.strftime("%Y-%m-%dT%H:%M:%SZ") # segundos inteiros, até 90 dias atrás
|
|
62
|
+
}] })
|
|
63
|
+
# receipt.created, receipt.replayed
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
De 1 a 100 eventos por chamada, até 64 KiB, todos gravados ou nenhum. Não há `Idempotency-Key` aqui: a identidade de um evento é o `external_event_id` da fonte, e repetir uma chamada é seguro (`replayed`); o mesmo id com outro conteúdo responde 409 (`conflict`), e um evento inválido, 422 (`invalid_request`) com `record` dizendo qual, a partir de 1.
|
|
39
67
|
|
|
40
68
|
## Erros
|
|
41
69
|
|
|
42
|
-
Uma recusa da API levanta `Perata::Error`, com `status`, `code` (`invalid_request`, `not_found`…), `message`, `fields` (o problema de cada campo) e, num 429, `retry_after` em segundos.
|
|
70
|
+
Uma recusa da API levanta `Perata::Error`, com `status`, `code` (`invalid_request`, `not_found`…), `message`, `fields` (o problema de cada campo), `record` (na ingestão, o evento recusado) e, num 429, `retry_after` em segundos.
|
|
43
71
|
|
|
44
72
|
## Webhooks
|
|
45
73
|
|
|
@@ -56,4 +84,4 @@ A assinatura é a do Standard Webhooks (`webhook-id`, `webhook-timestamp`, `webh
|
|
|
56
84
|
|
|
57
85
|
O evento é tipado: um tipo para cada dado que o webhook carrega (`OrderWebhookEvent` traz um `Order`, `DisputeWebhookEvent` a disputa, `IdentifiersWebhookEvent` os identificadores…), e o `type` diz o que aconteceu; um tipo que esta versão do SDK ainda não conhece chega como `UnknownWebhookEvent`, com os dados como vieram. Durante a rotação do segredo o cabeçalho traz mais de uma assinatura, e basta uma conferir. `Perata.verify_webhook_signature` só confere a assinatura.
|
|
58
86
|
|
|
59
|
-
Versão do SDK: 0.
|
|
87
|
+
Versão do SDK: 0.4.0 · contrato da API: 0.1.0
|
data/lib/perata/client.rb
CHANGED
|
@@ -2,8 +2,10 @@
|
|
|
2
2
|
|
|
3
3
|
# Code generated by sdk/generator. DO NOT EDIT.
|
|
4
4
|
|
|
5
|
+
require "ipaddr"
|
|
5
6
|
require "json"
|
|
6
7
|
require "net/http"
|
|
8
|
+
require "securerandom"
|
|
7
9
|
require "uri"
|
|
8
10
|
|
|
9
11
|
module Perata
|
|
@@ -30,12 +32,24 @@ module Perata
|
|
|
30
32
|
# product = perata.products.create({ name: "Curso" })
|
|
31
33
|
# perata.products.list_iter { |product| puts product.name }
|
|
32
34
|
class Client
|
|
35
|
+
# @return [BenefitsService]
|
|
36
|
+
attr_reader :benefits
|
|
37
|
+
# @return [CheckoutLinksService]
|
|
38
|
+
attr_reader :checkout_links
|
|
33
39
|
# @return [CheckoutsService]
|
|
34
40
|
attr_reader :checkouts
|
|
35
41
|
# @return [CustomerSessionsService]
|
|
36
42
|
attr_reader :customer_sessions
|
|
37
43
|
# @return [CustomersService]
|
|
38
44
|
attr_reader :customers
|
|
45
|
+
# @return [DiscountsService]
|
|
46
|
+
attr_reader :discounts
|
|
47
|
+
# @return [DisputesService]
|
|
48
|
+
attr_reader :disputes
|
|
49
|
+
# @return [EventsService]
|
|
50
|
+
attr_reader :events
|
|
51
|
+
# @return [MetersService]
|
|
52
|
+
attr_reader :meters
|
|
39
53
|
# @return [OrdersService]
|
|
40
54
|
attr_reader :orders
|
|
41
55
|
# @return [PaymentMethodsService]
|
|
@@ -50,15 +64,28 @@ module Perata
|
|
|
50
64
|
attr_reader :refunds
|
|
51
65
|
# @return [SubscriptionsService]
|
|
52
66
|
attr_reader :subscriptions
|
|
67
|
+
# @return [UsageService]
|
|
68
|
+
attr_reader :usage
|
|
69
|
+
# @return [WebhooksService]
|
|
70
|
+
attr_reader :webhooks
|
|
53
71
|
|
|
72
|
+
# The base URL must be https; http is accepted only for localhost,
|
|
73
|
+
# 127.0.0.0/8 and ::1, and any other raises ArgumentError.
|
|
54
74
|
def initialize(api_key, base_url: DEFAULT_BASE_URL, timeout: 30, transport: NetHTTPTransport.new)
|
|
55
75
|
@api_key = api_key
|
|
56
76
|
@base_url = base_url.chomp("/")
|
|
77
|
+
check_base_url!(@base_url)
|
|
57
78
|
@timeout = timeout
|
|
58
79
|
@transport = transport
|
|
80
|
+
@benefits = BenefitsService.new(self)
|
|
81
|
+
@checkout_links = CheckoutLinksService.new(self)
|
|
59
82
|
@checkouts = CheckoutsService.new(self)
|
|
60
83
|
@customer_sessions = CustomerSessionsService.new(self)
|
|
61
84
|
@customers = CustomersService.new(self)
|
|
85
|
+
@discounts = DiscountsService.new(self)
|
|
86
|
+
@disputes = DisputesService.new(self)
|
|
87
|
+
@events = EventsService.new(self)
|
|
88
|
+
@meters = MetersService.new(self)
|
|
62
89
|
@orders = OrdersService.new(self)
|
|
63
90
|
@payment_methods = PaymentMethodsService.new(self)
|
|
64
91
|
@payments = PaymentsService.new(self)
|
|
@@ -66,6 +93,8 @@ module Perata
|
|
|
66
93
|
@products = ProductsService.new(self)
|
|
67
94
|
@refunds = RefundsService.new(self)
|
|
68
95
|
@subscriptions = SubscriptionsService.new(self)
|
|
96
|
+
@usage = UsageService.new(self)
|
|
97
|
+
@webhooks = WebhooksService.new(self)
|
|
69
98
|
end
|
|
70
99
|
|
|
71
100
|
# Calls the API and answers what it wrapped in +data+; a refusal raises
|
|
@@ -84,11 +113,35 @@ module Perata
|
|
|
84
113
|
data = JSON.generate(body)
|
|
85
114
|
headers["Content-Type"] = "application/json"
|
|
86
115
|
end
|
|
116
|
+
# A POST is sent with a key of its own, one a call, unless the caller gave one.
|
|
117
|
+
idempotency_key = SecureRandom.uuid if (idempotency_key.nil? || idempotency_key.empty?) && method == "POST"
|
|
87
118
|
headers["Idempotency-Key"] = idempotency_key if idempotency_key && !idempotency_key.empty?
|
|
88
119
|
status, response_headers, raw = @transport.call(method, url, headers, data, @timeout)
|
|
89
120
|
raise Error.from_response(status, response_headers, raw) if status >= 300
|
|
90
121
|
|
|
122
|
+
return nil if status == 204
|
|
123
|
+
|
|
91
124
|
JSON.parse(raw).fetch("data")
|
|
92
125
|
end
|
|
126
|
+
|
|
127
|
+
private
|
|
128
|
+
|
|
129
|
+
def check_base_url!(base_url)
|
|
130
|
+
uri = begin
|
|
131
|
+
URI.parse(base_url)
|
|
132
|
+
rescue URI::InvalidURIError
|
|
133
|
+
nil
|
|
134
|
+
end
|
|
135
|
+
scheme = uri&.scheme.to_s.downcase
|
|
136
|
+
host = uri&.hostname.to_s.downcase
|
|
137
|
+
loopback = host == "localhost" || begin
|
|
138
|
+
IPAddr.new(host).loopback?
|
|
139
|
+
rescue IPAddr::Error
|
|
140
|
+
false
|
|
141
|
+
end
|
|
142
|
+
return if scheme == "https" || (scheme == "http" && loopback)
|
|
143
|
+
|
|
144
|
+
raise ArgumentError, "perata: the base URL must be https (http is accepted only for localhost, 127.0.0.0/8 and ::1), so the API key is never sent in clear text"
|
|
145
|
+
end
|
|
93
146
|
end
|
|
94
147
|
end
|
data/lib/perata/error.rb
CHANGED
|
@@ -6,19 +6,22 @@ module Perata
|
|
|
6
6
|
# A request the API refused.
|
|
7
7
|
#
|
|
8
8
|
# +code+ is the API's: invalid_credential, forbidden, not_found,
|
|
9
|
-
# invalid_request, conflict, idempotency_conflict, body_too_large or
|
|
10
|
-
# unavailable.
|
|
11
|
-
#
|
|
12
|
-
#
|
|
9
|
+
# invalid_request, conflict, idempotency_conflict, unsupported_media_type, body_too_large, rate_limited or
|
|
10
|
+
# unavailable.
|
|
11
|
+
# +message+ is the API's explanation, +fields+ says, for invalid_request,
|
|
12
|
+
# what is wrong with each field, +record+ is, for the usage ingestion, the
|
|
13
|
+
# position (from 1) in the batch of the event the API refused (0 for any other
|
|
14
|
+
# error), and +retry_after+ is how many seconds to wait on a 429.
|
|
13
15
|
class Error < StandardError
|
|
14
|
-
attr_reader :status, :code, :message, :fields, :retry_after
|
|
16
|
+
attr_reader :status, :code, :message, :fields, :retry_after, :record
|
|
15
17
|
|
|
16
|
-
def initialize(status, code, message = "", fields = {}, retry_after = nil)
|
|
18
|
+
def initialize(status, code, message = "", fields = {}, retry_after = nil, record = 0)
|
|
17
19
|
@status = status
|
|
18
20
|
@code = code
|
|
19
21
|
@message = message
|
|
20
22
|
@fields = fields
|
|
21
23
|
@retry_after = retry_after
|
|
24
|
+
@record = record
|
|
22
25
|
super(message.empty? ? "perata: #{status} #{code}" : "perata: #{status} #{code}: #{message}")
|
|
23
26
|
end
|
|
24
27
|
|
|
@@ -35,9 +38,12 @@ module Perata
|
|
|
35
38
|
rescue JSON::ParserError
|
|
36
39
|
{} # A refusal without a JSON body is still a refusal.
|
|
37
40
|
end
|
|
41
|
+
# Every error is the problem itself (error, message, fields). Servers from before
|
|
42
|
+
# that document wrapped the usage ingestion's errors in data: still read.
|
|
43
|
+
data = data["data"] if !data.key?("error") && data["data"].is_a?(Hash)
|
|
38
44
|
retry_after = headers["retry-after"].to_s
|
|
39
45
|
new(status, data["error"].to_s, data["message"].to_s, data["fields"] || {},
|
|
40
|
-
retry_after.match?(/\A\d+\z/) ? retry_after.to_i : nil)
|
|
46
|
+
retry_after.match?(/\A\d+\z/) ? retry_after.to_i : nil, data["record"].is_a?(Integer) ? data["record"] : 0)
|
|
41
47
|
end
|
|
42
48
|
end
|
|
43
49
|
end
|