paymos 1.3.0 → 1.5.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/CHANGELOG.md +20 -0
- data/README.md +39 -1
- data/lib/paymos/client.rb +49 -1
- data/lib/paymos/models.rb +13 -0
- data/lib/paymos/version.rb +1 -1
- data/sig/paymos.rbs +39 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 49ac8a8d2ef52cb2e7b6bcfe98000a10f9dd01d5a3f2d5aee34dad4cbf9ff96f
|
|
4
|
+
data.tar.gz: 3d4e5c92f6a16ff27a3c44194f47366972d4a107de0f1547a75cabb1cca55a60
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 1db9fc7682ffbc870ea487f7012f4088e56b5c11eab2884254ae05fd17abc378cc54c507e2ac9e427c051b3ff8867a63327b5dbb41ff93214fe9751226605689
|
|
7
|
+
data.tar.gz: 371e65ded8041b3ed73b0688da5058fa09f08d42d3e91c51b5554cd166b6bc4738e27238e0c397d14b7fe0c0a063ecfd6e2c5bfd6092c1be2b15a6edfca0f593
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,26 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [1.5.0] - 2026-10-30
|
|
6
|
+
|
|
7
|
+
- fix(plugins): reject invalid Woo refund controls and unblock SDK checks
|
|
8
|
+
- fix(sdk): preserve current refund contracts in TON conformance
|
|
9
|
+
- fix(ton): return friendly addresses with existing webhook schema
|
|
10
|
+
- feat(sdk-ruby): add refund create, list and get
|
|
11
|
+
|
|
12
|
+
- **403 is no longer reported as an authentication failure.** `Paymos::PermissionError` (subclasses `Paymos::AuthenticationError`, so existing `rescue` blocks keep working) now carries every 403.
|
|
13
|
+
- **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.
|
|
14
|
+
|
|
15
|
+
## [1.4.0] - 2026-10-30
|
|
16
|
+
|
|
17
|
+
- fix(plugins): reject invalid Woo refund controls and unblock SDK checks
|
|
18
|
+
- fix(sdk): preserve current refund contracts in TON conformance
|
|
19
|
+
- fix(ton): return friendly addresses with existing webhook schema
|
|
20
|
+
- feat(sdk-ruby): add refund create, list and get
|
|
21
|
+
|
|
22
|
+
- **403 is no longer reported as an authentication failure.** `Paymos::PermissionError` (subclasses `Paymos::AuthenticationError`, so existing `rescue` blocks keep working) now carries every 403.
|
|
23
|
+
- **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.
|
|
24
|
+
|
|
5
25
|
## [1.3.0] - 2026-10-14
|
|
6
26
|
|
|
7
27
|
- Make the SDK CI tier green
|
data/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Paymos Ruby SDK
|
|
2
2
|
|
|
3
|
-
Official Ruby client for the Paymos Merchant API — invoices, withdrawals, balances,
|
|
3
|
+
Official Ruby client for the Paymos Merchant API — invoices, withdrawals, refunds, balances,
|
|
4
4
|
and static per-customer wallets with their confirmed-deposit feed. Its only runtime dependency is
|
|
5
5
|
Ruby's official `base64` gem (required separately by modern Ruby versions).
|
|
6
6
|
|
|
@@ -32,6 +32,44 @@ Responses are immutable Ruby objects with documented RBS signatures; request
|
|
|
32
32
|
keywords remain idiomatic `snake_case` and are converted directly to the Paymos
|
|
33
33
|
wire contract.
|
|
34
34
|
|
|
35
|
+
Create invoice refunds with a Payment key. The server calculates the amount and
|
|
36
|
+
returns it as a decimal string; there are no amount or destination arguments:
|
|
37
|
+
|
|
38
|
+
```ruby
|
|
39
|
+
refund = paymos.refunds.create(
|
|
40
|
+
invoice_id: invoice.invoice_id,
|
|
41
|
+
mode: Paymos::RefundMode::OVERPAYMENT,
|
|
42
|
+
external_order_id: "refund_order_123_overpayment"
|
|
43
|
+
)
|
|
44
|
+
puts "#{refund.withdrawal_id}: #{refund.amount} #{refund.currency}"
|
|
45
|
+
|
|
46
|
+
filters = {
|
|
47
|
+
invoice_id: invoice.invoice_id,
|
|
48
|
+
status: [Paymos::WithdrawalStatus::COMPLETED],
|
|
49
|
+
created_from: "2026-01-01T00:00:00Z",
|
|
50
|
+
created_to: "2027-01-01T00:00:00Z",
|
|
51
|
+
limit: 100
|
|
52
|
+
}
|
|
53
|
+
page = paymos.refunds.list(**filters)
|
|
54
|
+
next_page = paymos.refunds.list(**filters, cursor: page.next_cursor) if page.next_cursor
|
|
55
|
+
refund = paymos.refunds.get(refund.withdrawal_id)
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
`full` returns all remaining payment; `overpayment` returns all remaining excess.
|
|
59
|
+
For a payment of 120 against an invoice of 100, these are initially 120 and 20.
|
|
60
|
+
After the overpayment refund completes, a new full refund returns 100. Refunds
|
|
61
|
+
and their pages are immutable, including their decimal strings and first sender.
|
|
62
|
+
|
|
63
|
+
Keep the same invoice, mode and `external_order_id` when retrying an uncertain
|
|
64
|
+
outcome. Creation returns 201 and an identical replay returns 200; both yield a
|
|
65
|
+
`Paymos::Refund`. Changing the mode under the same key raises
|
|
66
|
+
`Paymos::ConflictError` (409). Missing or foreign refund IDs raise
|
|
67
|
+
`Paymos::NotFoundError` (404). List periods use RFC 3339 timestamps, and
|
|
68
|
+
`paymos.refunds.each` provides the same bounded cursor iteration as invoices.
|
|
69
|
+
In sandbox, use `paymos.withdrawals.simulate_completion(refund.withdrawal_id)`,
|
|
70
|
+
then read the refund again. Refunds have their own list and do not appear in the
|
|
71
|
+
ordinary withdrawals list.
|
|
72
|
+
|
|
35
73
|
A payment channel is one payer's reusable set of deposit addresses. Create it,
|
|
36
74
|
read its rails, then poll the confirmed-deposit feed and persist the cursor:
|
|
37
75
|
|
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, :payment_channels, :payment_channel_deposits
|
|
22
|
+
attr_reader :invoices, :withdrawals, :refunds, :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,
|
|
@@ -43,6 +43,7 @@ module Paymos
|
|
|
43
43
|
@random = random
|
|
44
44
|
@invoices = Invoices.new(self)
|
|
45
45
|
@withdrawals = Withdrawals.new(self)
|
|
46
|
+
@refunds = Refunds.new(self)
|
|
46
47
|
@payment_channels = PaymentChannels.new(self)
|
|
47
48
|
@payment_channel_deposits = PaymentChannelDeposits.new(self)
|
|
48
49
|
@balances = Balances.new(self)
|
|
@@ -296,6 +297,53 @@ module Paymos
|
|
|
296
297
|
end
|
|
297
298
|
end
|
|
298
299
|
|
|
300
|
+
class Refunds < Resource
|
|
301
|
+
def create(invoice_id:, mode:, external_order_id:)
|
|
302
|
+
values = { invoice_id:, mode:, external_order_id: }
|
|
303
|
+
missing = values.select { |_name, value| !value.is_a?(String) || value.strip.empty? }.keys
|
|
304
|
+
raise ArgumentError, "Required values are missing: #{missing.join(', ')}" unless missing.empty?
|
|
305
|
+
raise ArgumentError, 'Invalid refund mode' unless [RefundMode::FULL, RefundMode::OVERPAYMENT].include?(mode)
|
|
306
|
+
|
|
307
|
+
Refund.from(@client.request('POST', '/v1/refunds', payload: values))
|
|
308
|
+
end
|
|
309
|
+
|
|
310
|
+
def simulate(refund_id, status)
|
|
311
|
+
Refund.from(@client.request('POST', "/v1/sandbox/refunds/#{segment(refund_id)}/simulate",
|
|
312
|
+
payload: { status: }))
|
|
313
|
+
end
|
|
314
|
+
|
|
315
|
+
def get(refund_id) = Refund.from(@client.request('GET', "/v1/refunds/#{segment(refund_id)}"))
|
|
316
|
+
|
|
317
|
+
def list(limit: nil, cursor: nil, invoice_id: nil, status: nil, created_from: nil, created_to: nil)
|
|
318
|
+
filters = validate_list_filters({ limit:, cursor:, invoice_id:, status: }.compact)
|
|
319
|
+
validate_period(created_from, created_to)
|
|
320
|
+
filters.merge!({ created_from:, created_to: }.compact)
|
|
321
|
+
Page.from(@client.request('GET', '/v1/refunds', query: Signing.query(filters)), item_type: Refund)
|
|
322
|
+
end
|
|
323
|
+
|
|
324
|
+
def each(max_pages: 100, **filters, &block)
|
|
325
|
+
return enum_for(__method__, max_pages: max_pages, **filters) unless block_given?
|
|
326
|
+
|
|
327
|
+
Pagination.each(method(:list), filters, max_pages:, &block)
|
|
328
|
+
end
|
|
329
|
+
|
|
330
|
+
private
|
|
331
|
+
|
|
332
|
+
def validate_period(created_from, created_to)
|
|
333
|
+
from, to = [created_from, created_to].map do |value|
|
|
334
|
+
next if value.nil?
|
|
335
|
+
|
|
336
|
+
unless value.is_a?(String) &&
|
|
337
|
+
value.match?(/\A\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d+)?(?:Z|[+-]\d{2}:\d{2})\z/i)
|
|
338
|
+
raise ArgumentError, 'created_from and created_to must be RFC 3339 timestamps'
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
Time.iso8601(value)
|
|
342
|
+
end
|
|
343
|
+
raise ArgumentError, 'created_to must be later than created_from' if from && to && from >= to
|
|
344
|
+
end
|
|
345
|
+
end
|
|
346
|
+
|
|
299
347
|
class PaymentChannels < Resource
|
|
300
348
|
# Create — or fetch. The same project plus external id answers 200 with the channel that already
|
|
301
349
|
# exists instead of 201 with a duplicate; both are a channel, so a caller never reads the status.
|
data/lib/paymos/models.rb
CHANGED
|
@@ -23,6 +23,11 @@ module Paymos
|
|
|
23
23
|
CANCELLED = 'cancelled'
|
|
24
24
|
end
|
|
25
25
|
|
|
26
|
+
module RefundMode
|
|
27
|
+
FULL = 'full'
|
|
28
|
+
OVERPAYMENT = 'overpayment'
|
|
29
|
+
end
|
|
30
|
+
|
|
26
31
|
class Model
|
|
27
32
|
class << self
|
|
28
33
|
attr_reader :field_names, :required_names
|
|
@@ -127,6 +132,14 @@ module Paymos
|
|
|
127
132
|
network destination_address created_at]
|
|
128
133
|
end
|
|
129
134
|
|
|
135
|
+
class Refund < Model
|
|
136
|
+
fields :withdrawal_id, :invoice_id, :mode, :external_order_id, :status, :is_final, :is_test,
|
|
137
|
+
:amount, :fee, :currency, :network, :destination_address, :first_sender, :tx_hash,
|
|
138
|
+
:explorer_url, :created_at, :completed_at, :failed_at, :cancelled_at, :type, :project_id,
|
|
139
|
+
required: %i[withdrawal_id invoice_id mode external_order_id status is_final is_test amount
|
|
140
|
+
currency network destination_address first_sender created_at]
|
|
141
|
+
end
|
|
142
|
+
|
|
130
143
|
# One accepted token on one rail. A missing minimum_deposit means the rail cannot be quoted right
|
|
131
144
|
# now — it never means the token has no minimum.
|
|
132
145
|
class PaymentChannelToken < Model
|
data/lib/paymos/version.rb
CHANGED
data/sig/paymos.rbs
CHANGED
|
@@ -133,6 +133,35 @@ module Paymos
|
|
|
133
133
|
attr_reader cancelled_at: Integer?
|
|
134
134
|
end
|
|
135
135
|
|
|
136
|
+
module RefundMode
|
|
137
|
+
FULL: "full"
|
|
138
|
+
OVERPAYMENT: "overpayment"
|
|
139
|
+
end
|
|
140
|
+
|
|
141
|
+
class Refund < Model
|
|
142
|
+
attr_reader withdrawal_id: String
|
|
143
|
+
attr_reader invoice_id: String
|
|
144
|
+
attr_reader mode: "full" | "overpayment"
|
|
145
|
+
attr_reader external_order_id: String
|
|
146
|
+
attr_reader status: String
|
|
147
|
+
attr_reader is_final: bool
|
|
148
|
+
attr_reader is_test: bool
|
|
149
|
+
attr_reader amount: String
|
|
150
|
+
attr_reader fee: String?
|
|
151
|
+
attr_reader currency: String
|
|
152
|
+
attr_reader network: String
|
|
153
|
+
attr_reader destination_address: String
|
|
154
|
+
attr_reader first_sender: String
|
|
155
|
+
attr_reader type: String?
|
|
156
|
+
attr_reader project_id: String?
|
|
157
|
+
attr_reader tx_hash: String?
|
|
158
|
+
attr_reader explorer_url: String?
|
|
159
|
+
attr_reader created_at: Integer
|
|
160
|
+
attr_reader completed_at: Integer?
|
|
161
|
+
attr_reader failed_at: Integer?
|
|
162
|
+
attr_reader cancelled_at: Integer?
|
|
163
|
+
end
|
|
164
|
+
|
|
136
165
|
class PaymentChannelToken < Model
|
|
137
166
|
attr_reader symbol: String
|
|
138
167
|
attr_reader minimum_deposit: String?
|
|
@@ -245,6 +274,15 @@ module Paymos
|
|
|
245
274
|
| (?max_pages: Integer, **untyped filters) -> Enumerator[Withdrawal, void]
|
|
246
275
|
end
|
|
247
276
|
|
|
277
|
+
class Refunds
|
|
278
|
+
def simulate: (String refund_id, String status) -> Refund
|
|
279
|
+
def create: (invoice_id: String, mode: "full" | "overpayment", external_order_id: String) -> Refund
|
|
280
|
+
def get: (String refund_id) -> Refund
|
|
281
|
+
def list: (?limit: Integer?, ?cursor: String?, ?invoice_id: String?, ?status: String | Array[String]?, ?created_from: String?, ?created_to: String?) -> Page[Refund]
|
|
282
|
+
def each: (?max_pages: Integer, **untyped filters) { (Refund) -> void } -> void
|
|
283
|
+
| (?max_pages: Integer, **untyped filters) -> Enumerator[Refund, void]
|
|
284
|
+
end
|
|
285
|
+
|
|
248
286
|
class PaymentChannels
|
|
249
287
|
def create: (project_id: String, external_id: String) -> PaymentChannel
|
|
250
288
|
def get: (String payment_channel_id) -> PaymentChannel
|
|
@@ -272,6 +310,7 @@ module Paymos
|
|
|
272
310
|
class Client
|
|
273
311
|
attr_reader invoices: Invoices
|
|
274
312
|
attr_reader withdrawals: Withdrawals
|
|
313
|
+
attr_reader refunds: Refunds
|
|
275
314
|
attr_reader payment_channels: PaymentChannels
|
|
276
315
|
attr_reader payment_channel_deposits: PaymentChannelDeposits
|
|
277
316
|
attr_reader balances: Balances
|