paymos 1.1.1 → 1.3.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: 170d4a77262048eb627400e9019deb92e25d0c6d3e73a49d5e477cc432a00baf
4
- data.tar.gz: bb92e9a4e584630a22271c93512ad094ec7cd5b47921c90ed9df19e0780fee3e
3
+ metadata.gz: c747bf5230f29f3a2dbed376f50af76f02c05b65b9a89558190dace7f76971dc
4
+ data.tar.gz: 6f26d4b9c429dd3d09d9f9c4edda6772bd8fd9b4fa77eb184188de10d831c61e
5
5
  SHA512:
6
- metadata.gz: '084533c7bcc70a3eb7b1a50b8b2ca3e6617d2de251a19b8c9a04e6a4e8d9817898c21c1587977111afbb86ddcedc464d348d1739cb2d40fd01d023c01c00d528'
7
- data.tar.gz: 0a7d4bfc9102170947b98c356c3b97df9ec405056854d1040676024e0fd7371d8a0115975087a1171f3324e73a1ed533e0a3f50a82761783e57a630a609417db
6
+ metadata.gz: 865bc2412c06f86d0f152bf2767308a2fcb06cf7386f253bd2467d509a1fb8609c1a16aa3130c85600e1c57302d1c7d598bc05c8f8d7907b289b93a0dd540153
7
+ data.tar.gz: 9ecf3a0cce176412d201d3ec956731b1924f9b4c0ec188d5d0717e9a39367a24beac7031c74e0678cd887513443791d73aaae9c918929094b044196e723ce552
data/CHANGELOG.md CHANGED
@@ -1,5 +1,28 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ## [1.3.0] - 2026-10-14
6
+
7
+ - Make the SDK CI tier green
8
+ - Fix invoice-late-payment audit findings (tasks 4.1-4.8)
9
+ - feat(invoices): accept full late payments without reopening invoices
10
+ - fix(sdk-conformance): восемь копий контракта разошлись с каноном — 403 снова permission
11
+ - fix(sdk): HTTPS обязателен, а 403 перестал притворяться ошибкой ключа
12
+
13
+ - **403 is no longer reported as an authentication failure.** `Paymos::PermissionError` (subclasses `Paymos::AuthenticationError`, so existing `rescue` blocks keep working) now carries every 403.
14
+ - **Base URL must use HTTPS.** Plain HTTP is accepted only against a loopback host, which is what a local integration test points at. The API secret signs every request, so a plaintext base URL put that credential on the wire.
15
+
16
+ ## [1.2.0] - 2026-08-28
17
+
18
+ - release: the changelog rot had a cause, and it was not the one I named
19
+ - audit: the shipped plugin and SDK docs described a product we stopped shipping
20
+ - docs(sdk): the READMEs say static wallets, not payment channels
21
+ - feat(sdk): payment-channel webhooks and the flow in every README
22
+ - feat(ruby-sdk): payment channels, and a feed reader that cannot stop early
23
+ - feat(sdk-conformance): declare the payment-channel operations
24
+ - docs(api): pin the empty-body signing rule and fix the published POST vector
25
+
3
26
  ## [1.1.1] - 2026-08-02
4
27
 
5
28
  - fix(ecosystem): recover SDK releases
data/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # Paymos Ruby SDK
2
2
 
3
- Official Ruby client for the Paymos Merchant API. Its only runtime dependency is
3
+ Official Ruby client for the Paymos Merchant API — invoices, withdrawals, balances,
4
+ and static per-customer wallets with their confirmed-deposit feed. Its only runtime dependency is
4
5
  Ruby's official `base64` gem (required separately by modern Ruby versions).
5
6
 
6
7
  ```bash
@@ -31,6 +32,36 @@ Responses are immutable Ruby objects with documented RBS signatures; request
31
32
  keywords remain idiomatic `snake_case` and are converted directly to the Paymos
32
33
  wire contract.
33
34
 
35
+ A payment channel is one payer's reusable set of deposit addresses. Create it,
36
+ read its rails, then poll the confirmed-deposit feed and persist the cursor:
37
+
38
+ ```ruby
39
+ channel = paymos.payment_channels.create(project_id: "prj_...", external_id: "customer_42")
40
+ channel.networks.each do |rail|
41
+ next if rail.address.nil? # this rail has not provisioned yet
42
+
43
+ puts "#{rail.network} #{rail.status} #{rail.address}"
44
+ end
45
+
46
+ feed = paymos.payment_channel_deposits.read(cursor: saved_cursor, limit: 100)
47
+ feed.items.each { |deposit| credit(deposit) }
48
+ save_cursor(feed.next_cursor)
49
+ ```
50
+
51
+ Four things that bite a caller who guesses. Repeating the same `external_id`
52
+ returns the **same** channel — `200` instead of `201`, and both bodies are a
53
+ channel, so calling this on every checkout is safe: a repeat is not a duplicate
54
+ and not an error. A rail's `address` is nil until that rail finishes
55
+ provisioning and never changes once set, so nil means "not yet", not "no
56
+ address". A nil `minimum_deposit` means "we cannot quote a minimum right now",
57
+ never "there is no minimum" — reading it as zero is how a merchant accepts a
58
+ deposit that lands below the live minimum and is never credited. And
59
+ `next_cursor` is never empty, not even on a page with no items: store it and
60
+ resume from it, and never loop until it is nil the way `each` ends a list,
61
+ because that loop either spins forever or stops on the first quiet page and
62
+ leaves reconciliation silently behind. `confirmed_from` is the first poll's
63
+ lower bound only; afterwards the stored cursor is the resume mechanism.
64
+
34
65
  API failures raise typed `Paymos::ApiError` subclasses and preserve the status,
35
66
  problem fields, headers, response body, and `Retry-After`. Cursor helpers are
36
67
  lazy, bounded, and reject a repeated cursor.
@@ -40,9 +71,48 @@ signatures against the exact raw request body before parsing JSON.
40
71
 
41
72
  ```ruby
42
73
  event = Paymos::WebhookVerifier.new(ENV.fetch("PAYMOS_WEBHOOK_SECRET"))
43
- .construct_event(request.env.fetch("HTTP_PAYMOS_SIGNATURE"), request.body.read)
74
+ .construct_event(request.env.fetch("HTTP_X_WEBHOOK_SIGNATURE"), request.body.read)
75
+ ```
76
+
77
+ Every `payment_channel.deposit.*` event carries a full payment-channel deposit as its
78
+ `data`; `data` comes back as a plain Hash, so hand it to `PaymentChannelDeposit.from`
79
+ to get the same typed model the deposit feed returns:
80
+
81
+ ```ruby
82
+ case event.event_type
83
+ when "payment_channel.deposit.confirmed"
84
+ deposit = Paymos::PaymentChannelDeposit.from(event.data)
85
+ credit(deposit.payment_channel_external_id, deposit.net) if deposit.is_final
86
+ end
44
87
  ```
45
88
 
89
+ `confirming` and `reorged` are advisory and may arrive out of order — a `confirming`
90
+ can land after the `confirmed` for the same deposit, and a `reorged` can be superseded
91
+ by a later `confirmed`. Credit only on `payment_channel.deposit.confirmed` with
92
+ `is_final` true, and never let an advisory event regress a deposit you already know is
93
+ confirmed: crediting on `confirming` releases goods against money a reorg can still
94
+ take back.
95
+
46
96
  Ruby 3.1 or newer is supported. See `conformance/contract.json` for the shared
47
97
  cross-language protocol contract and https://paymos.io/docs/server-sdks for the
48
98
  full API guide.
99
+
100
+ ## 401 and 403 are different problems
101
+
102
+ A **401** means the credential is absent, malformed or unverifiable — fix the key or the signing
103
+ clock, and stop retrying.
104
+
105
+ A **403** means the credential was accepted and something else refused. It covers two very
106
+ different situations:
107
+
108
+ - **the key is wrong for this call** — `payout_key_required`, `payment_key_required`,
109
+ `forbidden`;
110
+ - **the key is fine, the account or project state says no** — `whitelist_required`,
111
+ `merchant_suspended`, `widget_inactive`, `terminal_not_enabled`, `not_sandbox`.
112
+
113
+ A customer entering a payout address that is not whitelisted yet is the second kind: nothing is
114
+ wrong with your credentials, and paging the on-call engineer about them is the wrong move. Always
115
+ switch on the error `code` before deciding what to do.
116
+
117
+ `Paymos::PermissionError` subclasses `Paymos::AuthenticationError`, so a `rescue` written before
118
+ this split keeps catching 403.
data/lib/paymos/client.rb CHANGED
@@ -19,7 +19,7 @@ module Paymos
19
19
  class Client
20
20
  SAFE_METHODS = %w[GET HEAD OPTIONS].freeze
21
21
 
22
- attr_reader :invoices, :withdrawals, :balances, :system
22
+ attr_reader :invoices, :withdrawals, :balances, :system, :payment_channels, :payment_channel_deposits
23
23
 
24
24
  def initialize(api_key:, api_secret:, base_url: 'https://api.paymos.io', timeout: 30,
25
25
  max_retries: 2, base_delay: 0.150, transport: nil,
@@ -29,16 +29,10 @@ module Paymos
29
29
  raise ConfigurationError,
30
30
  'timeout, max_retries, and base_delay are invalid'
31
31
  end
32
- raise ConfigurationError, 'base_url is required' if base_url.to_s.strip.empty?
32
+ validate_base_url(base_url)
33
33
 
34
34
  @api_key = api_key
35
35
  @api_secret = api_secret
36
- origin = URI.parse(base_url)
37
- unless origin.absolute? && origin.host && [nil, '',
38
- '/'].include?(origin.path) && origin.query.nil? && origin.fragment.nil?
39
- raise ConfigurationError, 'base_url must be an absolute origin without a path'
40
- end
41
-
42
36
  @base_url = base_url.delete_suffix('/')
43
37
  @timeout = timeout
44
38
  @max_retries = max_retries
@@ -49,6 +43,8 @@ module Paymos
49
43
  @random = random
50
44
  @invoices = Invoices.new(self)
51
45
  @withdrawals = Withdrawals.new(self)
46
+ @payment_channels = PaymentChannels.new(self)
47
+ @payment_channel_deposits = PaymentChannelDeposits.new(self)
52
48
  @balances = Balances.new(self)
53
49
  @system = System.new(self)
54
50
  end
@@ -104,6 +100,24 @@ module Paymos
104
100
 
105
101
  private
106
102
 
103
+ # The API secret signs every request, so the scheme decides whether that credential is
104
+ # readable in transit. HTTPS in production; plain HTTP only against a loopback host, which
105
+ # is what a local integration test points at. Mirrors the Rust SDK's validate_base_url.
106
+ def validate_base_url(base_url)
107
+ raise ConfigurationError, 'base_url is required' if base_url.to_s.strip.empty?
108
+
109
+ origin = URI.parse(base_url)
110
+ unless origin.absolute? && origin.host && [nil, '', '/'].include?(origin.path) &&
111
+ origin.query.nil? && origin.fragment.nil?
112
+ raise ConfigurationError, 'base_url must be an absolute origin without a path'
113
+ end
114
+
115
+ loopback = %w[localhost 127.0.0.1 ::1].include?(origin.host.to_s.downcase)
116
+ return if origin.scheme == 'https' || (origin.scheme == 'http' && loopback)
117
+
118
+ raise ConfigurationError, 'base_url must use HTTPS (HTTP is allowed only for loopback tests)'
119
+ end
120
+
107
121
  def default_transport(method, url, headers, body, timeout)
108
122
  uri = URI(url)
109
123
  request_type = Net::HTTP.const_get(method.capitalize)
@@ -228,7 +242,7 @@ module Paymos
228
242
  end
229
243
 
230
244
  def simulate_payment(invoice_id, stage:)
231
- raise ArgumentError, 'Invalid simulation stage' unless %w[paid overpaid underpay cancel].include?(stage)
245
+ raise ArgumentError, 'Invalid simulation stage' unless %w[paid overpaid underpay cancel paid_late].include?(stage)
232
246
 
233
247
  Invoice.from(@client.request('POST', "/v1/sandbox/invoices/#{segment(invoice_id)}/simulate-payment",
234
248
  payload: { stage: }))
@@ -282,6 +296,80 @@ module Paymos
282
296
  end
283
297
  end
284
298
 
299
+ class PaymentChannels < Resource
300
+ # Create — or fetch. The same project plus external id answers 200 with the channel that already
301
+ # exists instead of 201 with a duplicate; both are a channel, so a caller never reads the status.
302
+ def create(project_id:, external_id:)
303
+ values = { project_id:, external_id: }
304
+ missing = values.select { |_name, value| !value.is_a?(String) || value.strip.empty? }.keys
305
+ raise ArgumentError, "Required values are missing: #{missing.join(', ')}" unless missing.empty?
306
+
307
+ PaymentChannel.from(@client.request('POST', '/v1/payment-channels', payload: values))
308
+ end
309
+
310
+ def get(payment_channel_id)
311
+ PaymentChannel.from(@client.request('GET', "/v1/payment-channels/#{segment(payment_channel_id)}"))
312
+ end
313
+
314
+ def list(limit: nil, cursor: nil, status: nil, external_id: nil, project_id: nil)
315
+ filters = validate_list_filters({ limit:, cursor:, status:, external_id:, project_id: }.compact)
316
+ Page.from(@client.request('GET', '/v1/payment-channels', query: Signing.query(filters)),
317
+ item_type: PaymentChannel)
318
+ end
319
+
320
+ def block(payment_channel_id)
321
+ PaymentChannel.from(@client.request('POST', "/v1/payment-channels/#{segment(payment_channel_id)}/block"))
322
+ end
323
+
324
+ def unblock(payment_channel_id)
325
+ PaymentChannel.from(@client.request('POST', "/v1/payment-channels/#{segment(payment_channel_id)}/unblock"))
326
+ end
327
+
328
+ # Sandbox only. network is required; stage defaults to confirmed server-side and is omitted from
329
+ # the payload entirely when unset, never sent as null.
330
+ def simulate_deposit(payment_channel_id, amount:, currency:, network:, stage: nil)
331
+ values = { amount:, currency:, network: }
332
+ missing = values.select { |_name, value| !value.is_a?(String) || value.strip.empty? }.keys
333
+ raise ArgumentError, "Required values are missing: #{missing.join(', ')}" unless missing.empty?
334
+
335
+ unless stage.nil? || %w[confirming reorged confirmed].include?(stage)
336
+ raise ArgumentError, 'Invalid simulation stage'
337
+ end
338
+
339
+ path = "/v1/sandbox/payment-channels/#{segment(payment_channel_id)}/simulate-deposit"
340
+ payload = { amount:, currency:, network:, stage: }.compact
341
+ PaymentChannelDeposit.from(@client.request('POST', path, payload:))
342
+ end
343
+
344
+ def each(max_pages: 100, **filters, &block)
345
+ return enum_for(__method__, max_pages: max_pages, **filters) unless block_given?
346
+
347
+ Pagination.each(method(:list), filters, max_pages:, &block)
348
+ end
349
+ end
350
+
351
+ class PaymentChannelDeposits < Resource
352
+ def get(deposit_id)
353
+ PaymentChannelDeposit.from(@client.request('GET', "/v1/payment-channel-deposits/#{segment(deposit_id)}"))
354
+ end
355
+
356
+ # Reads ONE page of the confirmed-deposit feed. Deliberately not named list and deliberately
357
+ # without an iterator: the feed never ends, so a helper that loops until the cursor is empty would
358
+ # either spin forever or stop on the first quiet page and leave reconciliation silently behind.
359
+ # confirmed_from is the initial-sync lower bound only; afterwards resume from the stored cursor.
360
+ def read(limit: nil, cursor: nil, project_id: nil, payment_channel_id: nil, confirmed_from: nil)
361
+ unless confirmed_from.nil? || (confirmed_from.is_a?(Integer) && confirmed_from >= 0)
362
+ raise ArgumentError, 'confirmed_from must be non-negative Unix seconds'
363
+ end
364
+
365
+ filters = validate_list_filters({ limit:, cursor:, project_id:, payment_channel_id:,
366
+ confirmed_from: }.compact)
367
+ PaymentChannelDepositFeedPage.from(
368
+ @client.request('GET', '/v1/payment-channel-deposits', query: Signing.query(filters))
369
+ )
370
+ end
371
+ end
372
+
285
373
  class Balances < Resource
286
374
  def get = Array(@client.request('GET', '/v1/balances')).map { |item| Balance.from(item) }.freeze
287
375
  end
data/lib/paymos/errors.rb CHANGED
@@ -69,6 +69,15 @@ module Paymos
69
69
 
70
70
  class ValidationError < ApiError; end
71
71
  class AuthenticationError < ApiError; end
72
+
73
+ # A 403: the credential was accepted, something else refused. Deliberately a SUBCLASS of
74
+ # AuthenticationError so existing rescue blocks keep working, and deliberately its own class
75
+ # because the remedy is usually not "fix the key": 403 carries wrong-key-type
76
+ # (payout_key_required, payment_key_required, forbidden) next to plain business
77
+ # state (whitelist_required, merchant_suspended, widget_inactive, terminal_not_enabled,
78
+ # not_sandbox). An unwhitelisted payout address reported as an authentication failure sends the
79
+ # on-call engineer to rotate perfectly good credentials. Switch on `code` for the remedy.
80
+ class PermissionError < AuthenticationError; end
72
81
  class NotFoundError < ApiError; end
73
82
  class ConflictError < ApiError; end
74
83
  class GoneError < ApiError; end
@@ -79,7 +88,7 @@ module Paymos
79
88
  ERROR_TYPES = {
80
89
  400 => ValidationError,
81
90
  401 => AuthenticationError,
82
- 403 => AuthenticationError,
91
+ 403 => PermissionError,
83
92
  404 => NotFoundError,
84
93
  409 => ConflictError,
85
94
  410 => GoneError,
data/lib/paymos/models.rb CHANGED
@@ -98,8 +98,11 @@ module Paymos
98
98
  end
99
99
 
100
100
  class Invoice < Model
101
+ # paid_late_at: invoice-late-payment — money crossed the invoice's total after it had already
102
+ # closed as expired/underpaid. Unix seconds; nil while there is no late mark (field omitted).
103
+ # status keeps its terminal meaning; the marker travels beside it.
101
104
  fields :invoice_id, :project_id, :status, :is_final, :is_test, :payment_url,
102
- :order, :payment, :created_at, :updated_at, :expires_at, :completed_at,
105
+ :order, :payment, :created_at, :updated_at, :expires_at, :completed_at, :paid_late_at,
103
106
  required: %i[invoice_id project_id status is_final is_test payment_url order created_at updated_at]
104
107
 
105
108
  def self.from(value)
@@ -109,9 +112,10 @@ module Paymos
109
112
  end
110
113
 
111
114
  class InvoiceListItem < Model
115
+ # paid_late_at: invoice-late-payment additive late mark, Unix seconds; nil while there is no mark (field omitted).
112
116
  fields :invoice_id, :project_id, :external_order_id, :client_id, :status,
113
117
  :is_final, :is_test, :amount, :currency, :network, :created_at,
114
- :expires_at, :completed_at,
118
+ :expires_at, :completed_at, :paid_late_at,
115
119
  required: %i[invoice_id project_id external_order_id status is_final is_test amount currency created_at]
116
120
  end
117
121
 
@@ -123,6 +127,80 @@ module Paymos
123
127
  network destination_address created_at]
124
128
  end
125
129
 
130
+ # One accepted token on one rail. A missing minimum_deposit means the rail cannot be quoted right
131
+ # now — it never means the token has no minimum.
132
+ class PaymentChannelToken < Model
133
+ fields :symbol, :minimum_deposit, required: %i[symbol]
134
+ end
135
+
136
+ # One rail of a channel. The address is absent until that rail provisions, and never changes after.
137
+ class PaymentChannelNetwork < Model
138
+ fields :network, :status, :address, :tokens, required: %i[network status tokens]
139
+
140
+ def self.from(value)
141
+ model = super
142
+ new(**model.to_h, tokens: model.tokens.map { |item| PaymentChannelToken.from(item) })
143
+ end
144
+ end
145
+
146
+ class PaymentChannel < Model
147
+ fields :id, :project_id, :external_id, :status, :is_accepting_payments, :is_fully_provisioned,
148
+ :is_test, :applied_fee_percent, :customer_fee_percent, :networks, :created_at, :updated_at,
149
+ required: %i[id project_id external_id status is_accepting_payments is_fully_provisioned
150
+ is_test applied_fee_percent customer_fee_percent networks created_at updated_at]
151
+
152
+ def self.from(value)
153
+ model = super
154
+ new(**model.to_h, networks: model.networks.map { |item| PaymentChannelNetwork.from(item) })
155
+ end
156
+ end
157
+
158
+ # Doubles as the `data` payload of every payment_channel.deposit.* webhook event (decoded via
159
+ # WebhookVerifier#construct_event, then PaymentChannelDeposit.from(event.data)). `confirming` and
160
+ # `reorged` are ADVISORY and may arrive out of order — a `confirming` can land after the
161
+ # `confirmed` for the same deposit, and a `reorged` can be superseded by a later `confirmed`.
162
+ # Credit only on payment_channel.deposit.confirmed with is_final true, and never let an advisory
163
+ # event regress a deposit already known to be confirmed: crediting on `confirming` releases goods
164
+ # against money a reorg can still take back.
165
+ class PaymentChannelDeposit < Model
166
+ fields :id, :payment_channel_id, :project_id, :payment_channel_external_id, :status, :is_final,
167
+ :is_test, :currency, :network, :chain_id, :contract_address, :gross, :fee, :net,
168
+ :applied_fee_percent, :customer_fee_percent, :tx_hash, :transfer_id, :source_address,
169
+ :destination_address, :block_height, :first_included_block_timestamp, :explorer_url,
170
+ :created_at, :updated_at, :confirmed_at,
171
+ required: %i[id payment_channel_id project_id payment_channel_external_id status is_final
172
+ is_test currency network chain_id gross fee net applied_fee_percent
173
+ customer_fee_percent tx_hash transfer_id destination_address block_height
174
+ created_at updated_at]
175
+ end
176
+
177
+ # The one feed position that could not be rendered, and why. Without it a stuck feed is
178
+ # byte-identical to a quiet day, and a merchant cannot tell "no new payments" from "I am not
179
+ # seeing my money". Reasons: booked_auth_missing, inconsistent, deposit_missing, channel_missing,
180
+ # transfer_missing, not_confirmed.
181
+ class PaymentChannelDepositFeedBlockage < Model
182
+ fields :deposit_id, :reason
183
+ end
184
+
185
+ # One page of the confirmed-deposit feed. Deliberately NOT a Page: next_cursor is required here,
186
+ # because the feed has no last page. An empty page still hands back a cursor past the global
187
+ # publication positions this merchant's filter skipped, so store it and resume from it — never
188
+ # stop polling because a page came back empty.
189
+ class PaymentChannelDepositFeedPage < Model
190
+ fields :items, :next_cursor, :blocked, required: %i[items next_cursor]
191
+
192
+ def self.from(value)
193
+ model = super
194
+ raise Error, 'Paymos API page items must be an array' unless model.items.is_a?(Array)
195
+
196
+ cursor = model.next_cursor
197
+ raise Error, 'Paymos deposit feed page is missing a cursor' unless cursor.is_a?(String) && !cursor.empty?
198
+
199
+ new(items: model.items.map { |item| PaymentChannelDeposit.from(item) }, next_cursor: cursor,
200
+ blocked: model.blocked && PaymentChannelDepositFeedBlockage.from(model.blocked))
201
+ end
202
+ end
203
+
126
204
  class Balance < Model
127
205
  fields :currency, :available
128
206
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Paymos
4
- VERSION = '1.1.1'
4
+ VERSION = '1.3.0'
5
5
  end
data/sig/paymos.rbs CHANGED
@@ -133,6 +133,75 @@ module Paymos
133
133
  attr_reader cancelled_at: Integer?
134
134
  end
135
135
 
136
+ class PaymentChannelToken < Model
137
+ attr_reader symbol: String
138
+ attr_reader minimum_deposit: String?
139
+ end
140
+
141
+ class PaymentChannelNetwork < Model
142
+ attr_reader network: String
143
+ attr_reader status: String
144
+ attr_reader address: String?
145
+ attr_reader tokens: Array[PaymentChannelToken]
146
+ end
147
+
148
+ class PaymentChannel < Model
149
+ attr_reader id: String
150
+ attr_reader project_id: String
151
+ attr_reader external_id: String
152
+ attr_reader status: String
153
+ attr_reader is_accepting_payments: bool
154
+ attr_reader is_fully_provisioned: bool
155
+ attr_reader is_test: bool
156
+ attr_reader applied_fee_percent: Numeric
157
+ attr_reader customer_fee_percent: Integer
158
+ attr_reader networks: Array[PaymentChannelNetwork]
159
+ attr_reader created_at: Integer
160
+ attr_reader updated_at: Integer
161
+ end
162
+
163
+ class PaymentChannelDeposit < Model
164
+ attr_reader id: String
165
+ attr_reader payment_channel_id: String
166
+ attr_reader project_id: String
167
+ attr_reader payment_channel_external_id: String
168
+ attr_reader status: String
169
+ attr_reader is_final: bool
170
+ attr_reader is_test: bool
171
+ attr_reader currency: String
172
+ attr_reader network: String
173
+ attr_reader chain_id: Integer
174
+ attr_reader contract_address: String?
175
+ attr_reader gross: String
176
+ attr_reader fee: String
177
+ attr_reader net: String
178
+ attr_reader applied_fee_percent: Numeric
179
+ attr_reader customer_fee_percent: Integer
180
+ attr_reader tx_hash: String
181
+ attr_reader transfer_id: String
182
+ attr_reader source_address: String?
183
+ attr_reader destination_address: String
184
+ attr_reader block_height: Integer
185
+ attr_reader first_included_block_timestamp: Integer?
186
+ attr_reader explorer_url: String?
187
+ attr_reader created_at: Integer
188
+ attr_reader updated_at: Integer
189
+ attr_reader confirmed_at: Integer?
190
+ end
191
+
192
+ class PaymentChannelDepositFeedBlockage < Model
193
+ attr_reader deposit_id: String
194
+ attr_reader reason: String
195
+ end
196
+
197
+ # next_cursor is NOT optional: the feed never ends, so an empty page still carries the cursor to
198
+ # resume from. Store it; never stop because a page came back empty.
199
+ class PaymentChannelDepositFeedPage < Model
200
+ attr_reader items: Array[PaymentChannelDeposit]
201
+ attr_reader next_cursor: String
202
+ attr_reader blocked: PaymentChannelDepositFeedBlockage?
203
+ end
204
+
136
205
  class Balance < Model
137
206
  attr_reader currency: String
138
207
  attr_reader available: String
@@ -176,6 +245,22 @@ module Paymos
176
245
  | (?max_pages: Integer, **untyped filters) -> Enumerator[Withdrawal, void]
177
246
  end
178
247
 
248
+ class PaymentChannels
249
+ def create: (project_id: String, external_id: String) -> PaymentChannel
250
+ def get: (String payment_channel_id) -> PaymentChannel
251
+ def list: (?limit: Integer?, ?cursor: String?, ?status: String | Array[String]?, ?external_id: String?, ?project_id: String?) -> Page[PaymentChannel]
252
+ def block: (String payment_channel_id) -> PaymentChannel
253
+ def unblock: (String payment_channel_id) -> PaymentChannel
254
+ def simulate_deposit: (String payment_channel_id, amount: String, currency: String, network: String, ?stage: String?) -> PaymentChannelDeposit
255
+ def each: (?max_pages: Integer, **untyped filters) { (PaymentChannel) -> void } -> void
256
+ | (?max_pages: Integer, **untyped filters) -> Enumerator[PaymentChannel, void]
257
+ end
258
+
259
+ class PaymentChannelDeposits
260
+ def get: (String deposit_id) -> PaymentChannelDeposit
261
+ def read: (?limit: Integer?, ?cursor: String?, ?project_id: String?, ?payment_channel_id: String?, ?confirmed_from: Integer?) -> PaymentChannelDepositFeedPage
262
+ end
263
+
179
264
  class Balances
180
265
  def get: () -> Array[Balance]
181
266
  end
@@ -187,6 +272,8 @@ module Paymos
187
272
  class Client
188
273
  attr_reader invoices: Invoices
189
274
  attr_reader withdrawals: Withdrawals
275
+ attr_reader payment_channels: PaymentChannels
276
+ attr_reader payment_channel_deposits: PaymentChannelDeposits
190
277
  attr_reader balances: Balances
191
278
  attr_reader system: System
192
279
 
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: paymos
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.1.1
4
+ version: 1.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Paymos Labs