perata-sdk 0.3.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 4ce69fcabc384e43ef9a829c444ced516fb44a64d82daf72fd4d8b7b9ad6dab9
4
- data.tar.gz: 43e9821c4ad1cd264bfe59fb8b3fdafabf6d44d31755d067e1e626d21fba753a
3
+ metadata.gz: ffb21818636a4985aed2e37bed5c804482d4e33f36d59ac855170d95747dac5b
4
+ data.tar.gz: 2201ef7ba61a245ccf70aed0c7d48d11999f81fd8025a84acaf551ccec09441b
5
5
  SHA512:
6
- metadata.gz: 74f8e47737ccb716ff1c9d18354c97eb7260f016ade4b5c4ee4d5b3caa4ba4b676f943c1cf60d089f33eb76f63b5f724983edacc1179021e4cc2b2031adea0c2
7
- data.tar.gz: f3e2c307da8270f8db0c41d28b1d4b78bd283e65a83c63d6e6d789c466c65be78976f7c1ce080be492f01e9fd189c1cfefee3c08f3daae08d3caf2507f773f92
6
+ metadata.gz: e9d0be391bcc52e48eeee7fca3ac3ded3bd246aaf5aa595f7c568a231ed6195e7b70cb01bd12df25f2731d3e4ceece23f3c03cf7b9e7f233896f09ebb917e062
7
+ data.tar.gz: daee592161aed12eaabe847b9d8f43b4651ea18d8cf1ab98decf477343c1153fc356cc78d8443594c694c55cbab2c32b9a2226105b97bf5ab0982333db9749ee
data/README.md CHANGED
@@ -38,15 +38,36 @@ end
38
38
  | `perata.products` | `archive`, `create`, `get`, `list`, `list_iter`, `update` |
39
39
  | `perata.refunds` | `create`, `list`, `list_iter` |
40
40
  | `perata.subscriptions` | `cancel`, `get`, `list`, `list_iter`, `update` |
41
+ | `perata.usage` | `ingest` |
41
42
  | `perata.webhooks` | `create_endpoint`, `delete_endpoint`, `get_endpoint`, `list_deliveries`, `list_deliveries_iter`, `list_endpoints`, `list_endpoints_iter`, `update_endpoint` |
42
43
 
43
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).
44
45
 
45
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.
46
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.
67
+
47
68
  ## Erros
48
69
 
49
- 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.
50
71
 
51
72
  ## Webhooks
52
73
 
@@ -63,4 +84,4 @@ A assinatura é a do Standard Webhooks (`webhook-id`, `webhook-timestamp`, `webh
63
84
 
64
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.
65
86
 
66
- Versão do SDK: 0.3.0 · contrato da API: 0.1.0
87
+ Versão do SDK: 0.4.0 · contrato da API: 0.1.0
data/lib/perata/client.rb CHANGED
@@ -64,6 +64,8 @@ module Perata
64
64
  attr_reader :refunds
65
65
  # @return [SubscriptionsService]
66
66
  attr_reader :subscriptions
67
+ # @return [UsageService]
68
+ attr_reader :usage
67
69
  # @return [WebhooksService]
68
70
  attr_reader :webhooks
69
71
 
@@ -91,6 +93,7 @@ module Perata
91
93
  @products = ProductsService.new(self)
92
94
  @refunds = RefundsService.new(self)
93
95
  @subscriptions = SubscriptionsService.new(self)
96
+ @usage = UsageService.new(self)
94
97
  @webhooks = WebhooksService.new(self)
95
98
  end
96
99
 
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. +message+ is the API's explanation, +fields+ says, for
11
- # invalid_request, what is wrong with each field, and +retry_after+ is how
12
- # many seconds to wait on a 429.
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
data/lib/perata/models.rb CHANGED
@@ -1693,6 +1693,45 @@ module Perata
1693
1693
  end
1694
1694
  end
1695
1695
 
1696
+ # UsageError is a resource of Peráta's API.
1697
+ class UsageError < Model
1698
+ MEMBERS = %i[error fields message record].freeze
1699
+
1700
+ # Código do erro: invalid_credential, invalid_request, conflict, unsupported_media_type, body_too_large, rate_limited ou unavailable.
1701
+ # @return [String]
1702
+ attr_reader :error
1703
+
1704
+ # Em invalid_request, o que há de errado em cada campo do evento.
1705
+ # @return [Hash{String => Array<String>}, nil]
1706
+ attr_reader :fields
1707
+
1708
+ # O que explica a recusa, em inglês.
1709
+ # @return [String, nil]
1710
+ attr_reader :message
1711
+
1712
+ # Posição, a partir de 1, do evento do lote que causou a recusa (invalid_request e conflict); o lote inteiro foi revertido.
1713
+ # @return [Integer, nil]
1714
+ attr_reader :record
1715
+
1716
+ def initialize(error:, fields: nil, message: nil, record: nil)
1717
+ @error = error
1718
+ @fields = fields
1719
+ @message = message
1720
+ @record = record
1721
+ freeze
1722
+ end
1723
+
1724
+ # Builds a UsageError from the API's JSON.
1725
+ def self.from_hash(data)
1726
+ new(
1727
+ error: data.fetch("error"),
1728
+ fields: data["fields"],
1729
+ message: data["message"],
1730
+ record: data["record"]
1731
+ )
1732
+ end
1733
+ end
1734
+
1696
1735
  # WebhookBenefit is a resource of Peráta's API.
1697
1736
  class WebhookBenefit < Model
1698
1737
  MEMBERS = %i[created_at description id modified_at name status type visibility].freeze
@@ -2208,4 +2247,29 @@ module Perata
2208
2247
  )
2209
2248
  end
2210
2249
  end
2250
+
2251
+ # What usage.ingest answers.
2252
+ class UsageIngestReceipt < Model
2253
+ MEMBERS = %i[created replayed].freeze
2254
+
2255
+ # @return [Integer]
2256
+ attr_reader :created
2257
+
2258
+ # @return [Integer]
2259
+ attr_reader :replayed
2260
+
2261
+ def initialize(created:, replayed:)
2262
+ @created = created
2263
+ @replayed = replayed
2264
+ freeze
2265
+ end
2266
+
2267
+ # Builds a UsageIngestReceipt from the API's JSON.
2268
+ def self.from_hash(data)
2269
+ new(
2270
+ created: data.fetch("created"),
2271
+ replayed: data.fetch("replayed")
2272
+ )
2273
+ end
2274
+ end
2211
2275
  end
data/lib/perata/params.rb CHANGED
@@ -346,6 +346,7 @@ module Perata
346
346
  # @return [String]
347
347
  attr_reader :email
348
348
 
349
+ # Até 120 caracteres, sem barra (/) nem caracteres de controle.
349
350
  # @return [String, nil]
350
351
  attr_reader :external_id
351
352
 
@@ -376,7 +377,7 @@ module Perata
376
377
  # @return [String, nil]
377
378
  attr_reader :email
378
379
 
379
- # Só pode ser definido uma vez.
380
+ # Só pode ser definido uma vez. Até 120 caracteres, sem barra (/) nem caracteres de controle.
380
381
  # @return [String, nil]
381
382
  attr_reader :external_id
382
383
 
@@ -559,7 +560,7 @@ module Perata
559
560
  # @return [String, nil]
560
561
  attr_reader :email
561
562
 
562
- # Só pode ser definido uma vez.
563
+ # Só pode ser definido uma vez. Até 120 caracteres, sem barra (/) nem caracteres de controle.
563
564
  # @return [String, nil]
564
565
  attr_reader :external_id
565
566
 
@@ -590,6 +591,7 @@ module Perata
590
591
  # @return [Integer]
591
592
  attr_reader :amount_cents
592
593
 
594
+ # Obrigatório no preço recorrente; o preço one_time não tem intervalo.
593
595
  # @return [String ("month", "year"), nil]
594
596
  attr_reader :interval
595
597
 
@@ -761,6 +763,143 @@ module Perata
761
763
  end
762
764
  end
763
765
 
766
+ # The body of usage.ingest.
767
+ class UsageIngestParams < Params
768
+ MEMBERS = %i[events].freeze
769
+
770
+ # @return [Array<UsageIngestEvent>]
771
+ attr_reader :events
772
+
773
+ def initialize(events:)
774
+ @events = events
775
+ freeze
776
+ end
777
+
778
+ # The body as the API reads it.
779
+ def to_wire
780
+ wire = {}
781
+ wire["events"] = Perata.wire(@events)
782
+ wire
783
+ end
784
+ end
785
+
786
+ # Opcional. O custo de atender o evento para o vendedor, em metadata._cost; alimenta Análises › Custos e as margens.
787
+ class UsageIngestEventCost < Params
788
+ MEMBERS = %i[amount currency].freeze
789
+
790
+ # Custo em centavos, como string decimal com até seis casas.
791
+ # @return [String]
792
+ attr_reader :amount
793
+
794
+ # Moeda do custo. Apenas brl.
795
+ # @return [String ("brl")]
796
+ attr_reader :currency
797
+
798
+ def initialize(amount:, currency:)
799
+ @amount = amount
800
+ @currency = currency
801
+ freeze
802
+ end
803
+
804
+ # The body as the API reads it.
805
+ def to_wire
806
+ wire = {}
807
+ wire["amount"] = @amount
808
+ wire["currency"] = @currency
809
+ wire
810
+ end
811
+ end
812
+
813
+ # Opcional. O modelo e o fornecedor que atenderam o evento, em metadata._llm; os contadores continuam nos campos de tokens.
814
+ class UsageIngestEventLlm < Params
815
+ MEMBERS = %i[model vendor].freeze
816
+
817
+ # Modelo que atendeu o evento.
818
+ # @return [String]
819
+ attr_reader :model
820
+
821
+ # Fornecedor do modelo.
822
+ # @return [String]
823
+ attr_reader :vendor
824
+
825
+ def initialize(model:, vendor:)
826
+ @model = model
827
+ @vendor = vendor
828
+ freeze
829
+ end
830
+
831
+ # The body as the API reads it.
832
+ def to_wire
833
+ wire = {}
834
+ wire["model"] = @model
835
+ wire["vendor"] = @vendor
836
+ wire
837
+ end
838
+ end
839
+
840
+ # One item of events in UsageIngestParams.
841
+ class UsageIngestEvent < Params
842
+ MEMBERS = %i[_cost _llm cached_tokens customer_id external_event_id input_tokens occurred_at output_tokens parent_id].freeze
843
+
844
+ # Opcional. O custo de atender o evento para o vendedor, em metadata._cost; alimenta Análises › Custos e as margens.
845
+ # @return [UsageIngestEventCost, nil]
846
+ attr_reader :_cost
847
+
848
+ # Opcional. O modelo e o fornecedor que atenderam o evento, em metadata._llm; os contadores continuam nos campos de tokens.
849
+ # @return [UsageIngestEventLlm, nil]
850
+ attr_reader :_llm
851
+
852
+ # @return [String]
853
+ attr_reader :cached_tokens
854
+
855
+ # @return [String]
856
+ attr_reader :customer_id
857
+
858
+ # @return [String]
859
+ attr_reader :external_event_id
860
+
861
+ # @return [String]
862
+ attr_reader :input_tokens
863
+
864
+ # @return [String]
865
+ attr_reader :occurred_at
866
+
867
+ # @return [String]
868
+ attr_reader :output_tokens
869
+
870
+ # Opcional. O evento pai, pelo id da Peráta ou pelo external_event_id que a mesma fonte enviou; o pai precisa chegar antes, inclusive antes no mesmo lote.
871
+ # @return [String, nil]
872
+ attr_reader :parent_id
873
+
874
+ def initialize(_cost: nil, _llm: nil, cached_tokens:, customer_id:, external_event_id:, input_tokens:, occurred_at:, output_tokens:, parent_id: nil)
875
+ @_cost = _cost
876
+ @_llm = _llm
877
+ @cached_tokens = cached_tokens
878
+ @customer_id = customer_id
879
+ @external_event_id = external_event_id
880
+ @input_tokens = input_tokens
881
+ @occurred_at = occurred_at
882
+ @output_tokens = output_tokens
883
+ @parent_id = parent_id
884
+ freeze
885
+ end
886
+
887
+ # The body as the API reads it.
888
+ def to_wire
889
+ wire = {}
890
+ wire["_cost"] = Perata.wire(@_cost) unless @_cost.nil?
891
+ wire["_llm"] = Perata.wire(@_llm) unless @_llm.nil?
892
+ wire["cached_tokens"] = @cached_tokens
893
+ wire["customer_id"] = @customer_id
894
+ wire["external_event_id"] = @external_event_id
895
+ wire["input_tokens"] = @input_tokens
896
+ wire["occurred_at"] = @occurred_at
897
+ wire["output_tokens"] = @output_tokens
898
+ wire["parent_id"] = @parent_id unless @parent_id.nil?
899
+ wire
900
+ end
901
+ end
902
+
764
903
  # The body of webhooks:create_endpoint.
765
904
  class WebhooksCreateEndpointParams < Params
766
905
  MEMBERS = %i[description events format url].freeze
@@ -730,6 +730,22 @@ module Perata
730
730
  end
731
731
  end
732
732
 
733
+ # The API's usage.
734
+ class UsageService
735
+ def initialize(client)
736
+ @client = client
737
+ end
738
+
739
+ # Ingerir lote atômico de eventos de uso. Autentica com a credencial de uma fonte de ingestão (pus_test_… ou pus_live_…), não com a chave de API da organização.
740
+ #
741
+ # @param params [UsageIngestParams, Hash]
742
+ # @return [UsageIngestReceipt]
743
+ def ingest(params, api_key: nil)
744
+ data = @client.request("POST", "/api/v1/usage/token-events", body: UsageIngestParams.coerce(params).to_wire, api_key: api_key)
745
+ UsageIngestReceipt.from_hash(data)
746
+ end
747
+ end
748
+
733
749
  # The API's webhooks.
734
750
  class WebhooksService
735
751
  def initialize(client)
@@ -4,7 +4,7 @@
4
4
 
5
5
  module Perata
6
6
  # This SDK's version.
7
- VERSION = "0.3.0"
7
+ VERSION = "0.4.0"
8
8
 
9
9
  # The contract this SDK was generated from.
10
10
  API_VERSION = "0.1.0"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: perata-sdk
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Peráta
@@ -45,7 +45,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
45
45
  - !ruby/object:Gem::Version
46
46
  version: '0'
47
47
  requirements: []
48
- rubygems_version: 3.6.9
48
+ rubygems_version: 4.0.20
49
49
  specification_version: 4
50
50
  summary: 'Peráta''s SDK for Ruby: products, customers, checkouts, payments, subscriptions
51
51
  and webhooks.'