rail0-sdk 1.7.1 → 1.8.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 +76 -20
- data/lib/rail0/error_hints.rb +7 -3
- data/lib/rail0/resources/accounts.rb +30 -4
- data/lib/rail0/resources/payments.rb +42 -14
- data/lib/rail0/resources/wallets.rb +6 -1
- data/lib/rail0/resources/webhooks.rb +12 -0
- data/lib/rail0/types.rb +58 -39
- data/lib/rail0/version.rb +1 -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: '078c5043f2867777ccba9da57fe022e3db252cff6010f50dbad7c9dd37b7c8a7'
|
|
4
|
+
data.tar.gz: e64f79000eeb6b94b78367c223eb33f48ac5ffc23218ac89dffb3b99e23504d1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4030fedd465183a1e4dba618ad1c3d7ff75d667032e3ebce6e7da87aeb4e1c75c9ab1205378d89fd2329a005ceb7f8a1b22ab3504b466e0c06b387117a5736a3
|
|
7
|
+
data.tar.gz: d894eea1f54c0c8514be643b3fdb7300cdc8c2158dd4c4db0de29b0b8fb09c7226d2910e981c921735a5b5589310484243cc9ddc9b01b92b75692ae68e5c43f0
|
data/README.md
CHANGED
|
@@ -382,6 +382,7 @@ client.wallets.list(account_id, token_active: true)
|
|
|
382
382
|
|
|
383
383
|
client.wallets.get(account_id, id_or_address)
|
|
384
384
|
client.wallets.update(account_id, id_or_address, label: "Renamed", active: false)
|
|
385
|
+
client.wallets.update(account_id, id_or_address, active: true) # back on (see below)
|
|
385
386
|
client.wallets.delete(account_id, id_or_address) # 204
|
|
386
387
|
client.wallets.balances(account_id, id_or_address, chain_id: 84532) # live on-chain balances
|
|
387
388
|
```
|
|
@@ -392,6 +393,17 @@ refused with 422 `last_active_wallet` — login resolves the account from a wall
|
|
|
392
393
|
so an account with none could neither act nor add one back. Another wallet must
|
|
393
394
|
be active first.
|
|
394
395
|
|
|
396
|
+
**Switching a wallet back on.** Every switch-off records who did it in the wallet's
|
|
397
|
+
`deactivated_by`: `"owner"` (a session signed in with that wallet) or `"operator"`
|
|
398
|
+
(an operator session); `nil` while active, and on a switch-off older than the record.
|
|
399
|
+
A deactivated wallet's session is read-only (403 `wallet_deactivated`) except for one
|
|
400
|
+
write: `update(account_id, id, active: true)` on its **own** wallet, which switches it
|
|
401
|
+
back on when its owner switched it off. A wallet an operator switched off (or with
|
|
402
|
+
`deactivated_by: nil`) raises 403 `wallet_deactivated_by_operator`: an operator's
|
|
403
|
+
switch-off revokes the key, so only an operator lifts it. The flip side: a wallet its
|
|
404
|
+
owner switched off can be switched back on by whoever holds its key, so a definitive
|
|
405
|
+
revocation (a leaked key) must come from an operator.
|
|
406
|
+
|
|
395
407
|
**A session is bound to the wallet it signed in with.** Reads (`list`, `get`,
|
|
396
408
|
`balances`) are account-wide, but `update`, `delete` and the token-holding methods
|
|
397
409
|
act only on the session's own wallet: another wallet of the same account is
|
|
@@ -459,6 +471,11 @@ client.payments.create(params, idempotency_key: nil) # or keyword fields
|
|
|
459
471
|
client.payments.get(id)
|
|
460
472
|
client.payments.update(id, description: "Order #42") # → the payment, same shape as get
|
|
461
473
|
client.payments.update(id, description: nil) # nil (or "") clears it
|
|
474
|
+
# The payee's own fields (the payer gets 403 not_the_payee): an internal note never shown
|
|
475
|
+
# to the payer, and the payment's identifier in the merchant's system. Only what you pass
|
|
476
|
+
# is sent.
|
|
477
|
+
client.payments.update(id, merchant_note: "refund agreed by email")
|
|
478
|
+
client.payments.update(id, reference: "ORD-1042", reference_origin: "commercelayer")
|
|
462
479
|
# The description is the only editable field: off-chain, so any status, settled included.
|
|
463
480
|
# Payer or payee only (a non-participant gets 404); over 255 characters is a 422.
|
|
464
481
|
client.payments.list(status: "authorized", disputed: false, chain_id: 84532, sort: "-created_at")
|
|
@@ -632,6 +649,7 @@ client.webhooks.enable(id)
|
|
|
632
649
|
client.webhooks.disable(id)
|
|
633
650
|
client.webhooks.rotate_secret(id) # returns a fresh shared_secret
|
|
634
651
|
client.webhooks.reset_circuit(id)
|
|
652
|
+
client.webhooks.test(id) # signed webhooks.test event now → { delivered:, response_code:, … }; not logged
|
|
635
653
|
# status is "delivered" or "failed" only; payment_id takes a UUID or a rail0_id.
|
|
636
654
|
client.webhooks.event_callbacks(id, status: "failed", response_code: "500")
|
|
637
655
|
client.webhooks.redeliver(id, callback_id) # 202 { status: "queued" } — replay one delivery
|
|
@@ -728,11 +746,13 @@ client.accounts.get(account_id)
|
|
|
728
746
|
client.accounts.update(account_id, email: "ops@merchant.example")
|
|
729
747
|
client.accounts.update(account_id, name: "Merchant Ltd", email: "ops@merchant.example")
|
|
730
748
|
client.accounts.update(account_id, priority_tip: false) # minimum tip on the account's own transactions
|
|
749
|
+
# Payment windows of NEW payments: seconds, or :default (sent as null) for the gateway default.
|
|
750
|
+
client.accounts.update(account_id, authorization_ttl: 2 * 86_400, refund_ttl: :default)
|
|
731
751
|
```
|
|
732
752
|
|
|
733
753
|
`email` is in the response because the holder is this endpoint's only possible caller.
|
|
734
754
|
|
|
735
|
-
`update` needs at least one of `name`/`email`/`priority_tip
|
|
755
|
+
`update` needs at least one of `name`/`email`/`priority_tip`/a payment window (it raises `ArgumentError`
|
|
736
756
|
before sending an empty PATCH, which the gateway would answer with 400). Name and email are
|
|
737
757
|
unique across accounts, so a value another account holds answers **409**. It is a write on
|
|
738
758
|
the account, so a deactivated wallet (403 `wallet_deactivated`) or account (403
|
|
@@ -743,6 +763,15 @@ exposed here.
|
|
|
743
763
|
capture, void, release, refund): `true`, the default, tips the network's suggested priority
|
|
744
764
|
fee; `false` uses the chain's minimum tip (0 on most chains, 25 gwei on Polygon PoS/Amoy) —
|
|
745
765
|
cheaper, slower to confirm under load. The buyer's dispute/close-dispute never read it.
|
|
766
|
+
|
|
767
|
+
**Payment windows** — `authorization_ttl` (how long an authorization can be captured) and
|
|
768
|
+
`refund_ttl` (how long a payment can be refunded or disputed), in seconds — are the deadlines
|
|
769
|
+
the account's **new** payments are created with: its own value, or the gateway default. The
|
|
770
|
+
profile's `payment_windows` says which, and the bounds; outside them, or with the refund
|
|
771
|
+
window less than `min_refund_window` (one day) past the authorization, the gateway answers
|
|
772
|
+
**422** `invalid_payment_windows`. A payment's deadlines are signed into it, so a change moves
|
|
773
|
+
only the payments created afterwards.
|
|
774
|
+
|
|
746
775
|
The account's wallets are a collection under the same path (`client.wallets`), and
|
|
747
776
|
buyer-facing discovery is `client.payment_methods`.
|
|
748
777
|
|
|
@@ -1070,25 +1099,52 @@ uses them everywhere, table-aligned hashes because some literals are tables, and
|
|
|
1070
1099
|
(`retry_on_429`, `eip712`, `secp256k1`) that each spell themselves their own way. A linter
|
|
1071
1100
|
that argues with the codebase teaches people to ignore it.
|
|
1072
1101
|
|
|
1073
|
-
##
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1102
|
+
## Releasing
|
|
1103
|
+
|
|
1104
|
+
A release is an approved pull request: merging it is what publishes.
|
|
1105
|
+
|
|
1106
|
+
1. `bin/release-prepare`, on an up-to-date, clean `main`. It computes the version from the titles of the pull requests merged since the last tag
|
|
1107
|
+
(Conventional Commits): a breaking change — `!` after the type, as in `feat(api)!:`, or a
|
|
1108
|
+
`BREAKING CHANGE:` footer — makes it a major, any `feat` a minor, anything else a patch,
|
|
1109
|
+
and it refuses when nothing merged. `bin/release-prepare minor` (or `patch`, `major`,
|
|
1110
|
+
`X.Y.Z`) sets it by hand instead. The release PR says which pull requests decided it.
|
|
1111
|
+
It then bumps `Rail0::VERSION` and `Gemfile.lock`, writes the release
|
|
1112
|
+
notes from the pull requests merged since the last tag, and opens
|
|
1113
|
+
`chore(release): vX.Y.Z`. `--dry-run` prints all of it and writes nothing.
|
|
1114
|
+
|
|
1115
|
+
**Or on a feature branch**, to release a change in the same review: run it on a clean
|
|
1116
|
+
branch that is pushed, contains `origin/main` and has an open pull request into `main`.
|
|
1117
|
+
The bump is committed and pushed on that branch (`chore(release): vX.Y.Z`), the pull
|
|
1118
|
+
request's own title counts toward the version and gets a line in the notes, and the
|
|
1119
|
+
notes go into its description between `<!-- release:start -->` markers — the text above
|
|
1120
|
+
them is kept. Re-run it after new commits or a merge of `main`: it refreshes the version
|
|
1121
|
+
and the notes instead of bumping twice. One approval then covers the change and its
|
|
1122
|
+
release.
|
|
1123
|
+
2. Review the pull request (edit the notes in its body if needed), approve and merge it.
|
|
1124
|
+
The approval must come from a reviewer **other than the pull request's author**, on its
|
|
1125
|
+
final commit: release.yml checks it before publishing anything and stops otherwise —
|
|
1126
|
+
an admin merge that bypassed the review, a direct push to `main` or a resume of an
|
|
1127
|
+
unapproved release publishes nothing.
|
|
1128
|
+
3. `.github/workflows/release.yml` runs on that push to `main`: CI again on the release
|
|
1129
|
+
commit, `gem push` through RubyGems **Trusted Publishing**, then the tag `vX.Y.Z` and
|
|
1130
|
+
the GitHub release (notes from the pull request), then a Slack message.
|
|
1131
|
+
|
|
1132
|
+
A merge that does not change the version releases nothing. If a run stops halfway, Actions
|
|
1133
|
+
→ release → Run workflow (on `main`) resumes it: what is already on RubyGems.org or already
|
|
1134
|
+
tagged is skipped. `rake release` is disabled (it would tag and push from a laptop), and no
|
|
1135
|
+
API key exists anywhere: RubyGems.org trusts only `release.yml` in this repo.
|
|
1136
|
+
|
|
1137
|
+
One-time settings (repository admins):
|
|
1138
|
+
|
|
1139
|
+
- environment `release` (Settings → Environments), deployment limited to `main`;
|
|
1140
|
+
- on rubygems.org, rail0-sdk's trusted publisher (repository `commercelayer/rail0-ruby`,
|
|
1141
|
+
workflow `release.yml`) bound to environment `release` — only once the new
|
|
1142
|
+
`release.yml` is on `main`;
|
|
1143
|
+
- a tag ruleset on `v*`: block updates and deletions, restrict creation to GitHub Actions
|
|
1144
|
+
and admins;
|
|
1145
|
+
- branch protection on `main`: required `ci` check (`spec`), "Include administrators";
|
|
1146
|
+
- the Slack channel: `release.yml` posts through the `PIPELINE_SLACK_CHANNEL_WEBHOOK_URL`
|
|
1147
|
+
secret, named in one place (the `announce` job).
|
|
1092
1148
|
|
|
1093
1149
|
## License
|
|
1094
1150
|
|
data/lib/rail0/error_hints.rb
CHANGED
|
@@ -55,7 +55,10 @@ module Rail0
|
|
|
55
55
|
"not_payer_or_payee" => "only the payer or the payee may do this",
|
|
56
56
|
"release_submitter_not_a_party" => "a release must be sent by the payment's payer or payee — omit `from` to build it for the signed-in wallet",
|
|
57
57
|
"payer_must_be_caller" => "the payer must be the signed-in wallet — sign in as the buyer to create its payment",
|
|
58
|
-
|
|
58
|
+
# gateway#439: a deactivated session may switch its OWN wallet back on when its owner
|
|
59
|
+
# switched it off; an operator's switch-off is the operator's to lift.
|
|
60
|
+
"wallet_deactivated" => "this wallet is deactivated: it can still read, but not act on payments or change the account — if you switched it off, its own session can switch it back on (wallets.update(..., active: true)), or sign in with another of the account's wallets",
|
|
61
|
+
"wallet_deactivated_by_operator" => "an operator deactivated this wallet, so only an operator can reactivate it — ask an operator to reactivate it",
|
|
59
62
|
"account_deactivated" => "the merchant account is deactivated: void, release and refund still work, but authorize, charge, capture and account changes do not — ask an operator to reactivate it",
|
|
60
63
|
# A session is bound to the one wallet it signed in with: the account's other wallets
|
|
61
64
|
# are readable, but not this session's to change (operators are exempt).
|
|
@@ -65,8 +68,9 @@ module Rail0
|
|
|
65
68
|
"wallet_add_unavailable" => "adding a wallet to an account is not available yet — it returns with the merchant wallet-management section",
|
|
66
69
|
# Login resolves the account FROM a wallet, so an account left with no active one
|
|
67
70
|
# could neither act nor add a wallet back. Adding a wallet is suspended and a session
|
|
68
|
-
# changes only its own wallet, so the other wallet
|
|
69
|
-
|
|
71
|
+
# changes only its own wallet, so the other wallet comes back on from its own session
|
|
72
|
+
# (if its owner switched it off) or from an operator (gateway#439).
|
|
73
|
+
"last_active_wallet" => "this is the account's only active wallet — another wallet must be active before this one can be switched off: sign in with one you switched off and reactivate it, or ask an operator (adding a wallet is not available yet)",
|
|
70
74
|
"last_admin" => "the change would leave no active operator — grant the operator role to another active account first",
|
|
71
75
|
"not_redrivable" => "only a pending transaction that already holds its signed bytes can be redriven — check the transaction's `redrivable` flag, and submit the signature if it has none",
|
|
72
76
|
"refund_expired" => "the refund window has closed (refundExpiry passed) — refund/dispute is no longer possible",
|
|
@@ -30,12 +30,16 @@ module Rail0
|
|
|
30
30
|
|
|
31
31
|
# The account's own profile.
|
|
32
32
|
# @param account_id [String] The account UUID — must be the one this JWT belongs to.
|
|
33
|
-
# @return [Hash] `id`, `name`, `email`, `priority_tip`, `
|
|
33
|
+
# @return [Hash] `id`, `name`, `email`, `priority_tip`, `authorization_ttl`, `refund_ttl`,
|
|
34
|
+
# `payment_windows`, `created_at`, `updated_at`.
|
|
34
35
|
# `email` is part of the response because only the account's owner, or an operator,
|
|
35
36
|
# can read it. `priority_tip` is the fee setting of the account's OWN transactions
|
|
36
37
|
# (authorize, capture, void, release, refund): true, the default, tips the network's
|
|
37
38
|
# suggested priority fee; false uses the chain's minimum tip — 0 on most chains,
|
|
38
39
|
# 25 gwei on Polygon PoS/Amoy. The buyer's dispute/close_dispute never read it.
|
|
40
|
+
# `authorization_ttl` / `refund_ttl` are the payment windows the account's NEW
|
|
41
|
+
# payments are created with, in seconds — its own or the gateway default;
|
|
42
|
+
# `payment_windows` says which, and the bounds a new value must fit.
|
|
39
43
|
def get(account_id)
|
|
40
44
|
http.get("/accounts/#{segment(account_id)}")
|
|
41
45
|
end
|
|
@@ -48,6 +52,10 @@ module Rail0
|
|
|
48
52
|
# are UNIQUE across accounts, so a value another account already holds answers 409.
|
|
49
53
|
# `priority_tip: false` is a value, not an absence: it is sent as false and switches
|
|
50
54
|
# the account's own transactions to the chain's minimum tip; nil leaves it alone.
|
|
55
|
+
# The payment windows take a number of seconds, or `:default` to put one back on the
|
|
56
|
+
# gateway default (sent as null); nil leaves it alone. Outside the bounds in
|
|
57
|
+
# `payment_windows`, or closer than its `min_refund_window`, the gateway answers 422
|
|
58
|
+
# invalid_payment_windows. A change moves only the payments created afterwards.
|
|
51
59
|
#
|
|
52
60
|
# This is a write on the account itself, so a deactivated wallet (403
|
|
53
61
|
# wallet_deactivated) or a deactivated account (403 account_deactivated) cannot
|
|
@@ -60,17 +68,35 @@ module Rail0
|
|
|
60
68
|
# @param priority_tip [Boolean, nil] Tip the network's suggested priority fee on the
|
|
61
69
|
# account's own transactions (true, the gateway default) or the chain's minimum tip
|
|
62
70
|
# (false). Owner or admin.
|
|
71
|
+
# @param authorization_ttl [Integer, :default, nil] Authorization (capture) window of
|
|
72
|
+
# new payments, in seconds, or :default. Owner or admin.
|
|
73
|
+
# @param refund_ttl [Integer, :default, nil] Refund and dispute window of new
|
|
74
|
+
# payments, in seconds, or :default. Owner or admin.
|
|
63
75
|
# @return [Hash] the updated profile, same shape as {get}.
|
|
64
|
-
# @raise [ArgumentError] when
|
|
65
|
-
|
|
76
|
+
# @raise [ArgumentError] when no field is given, or a window is neither an Integer
|
|
77
|
+
# nor :default.
|
|
78
|
+
def update(account_id, name: nil, email: nil, priority_tip: nil, authorization_ttl: nil, refund_ttl: nil)
|
|
66
79
|
body = {}
|
|
67
80
|
body[:name] = name unless name.nil?
|
|
68
81
|
body[:email] = email unless email.nil?
|
|
69
82
|
body[:priority_tip] = priority_tip unless priority_tip.nil?
|
|
70
|
-
|
|
83
|
+
{ authorization_ttl: authorization_ttl, refund_ttl: refund_ttl }.each do |key, value|
|
|
84
|
+
body[key] = window(key, value) unless value.nil?
|
|
85
|
+
end
|
|
86
|
+
raise ArgumentError, "at least one of name, email, priority_tip or a payment window is required" if body.empty?
|
|
71
87
|
|
|
72
88
|
http.patch("/accounts/#{segment(account_id)}", body)
|
|
73
89
|
end
|
|
90
|
+
|
|
91
|
+
private
|
|
92
|
+
|
|
93
|
+
# A payment window as the gateway takes it: whole seconds, or null for the default.
|
|
94
|
+
def window(key, value)
|
|
95
|
+
return if value == :default
|
|
96
|
+
return value if value.is_a?(Integer)
|
|
97
|
+
|
|
98
|
+
raise ArgumentError, "#{key} must be an Integer number of seconds or :default"
|
|
99
|
+
end
|
|
74
100
|
end
|
|
75
101
|
end
|
|
76
102
|
end
|
|
@@ -13,6 +13,11 @@ module Rail0
|
|
|
13
13
|
# step (e.g. MetaMask) skip the local signer and report the hash via
|
|
14
14
|
# {submit_by_hash} instead.
|
|
15
15
|
class Payments
|
|
16
|
+
# The default of {update}'s keywords: "not passed", told apart from an explicit +nil+
|
|
17
|
+
# (which is sent, as JSON null, and clears the field).
|
|
18
|
+
UNSET = Object.new.freeze
|
|
19
|
+
private_constant :UNSET
|
|
20
|
+
|
|
16
21
|
include Query
|
|
17
22
|
|
|
18
23
|
# Operations accepted by the prepare/submit endpoints.
|
|
@@ -62,6 +67,10 @@ module Rail0
|
|
|
62
67
|
# `last_error_code`, false = none.
|
|
63
68
|
# @param expiring_within [Integer, nil] scope "all" only: `authorized` payments whose
|
|
64
69
|
# `authorization_expiry` falls within this many SECONDS from now (1..90 days).
|
|
70
|
+
# @param reference [String, nil] Only payments with this reference — the payment's
|
|
71
|
+
# identifier in the merchant's own system (exact match).
|
|
72
|
+
# @param reference_origin [String, nil] Only payments whose reference comes from this
|
|
73
|
+
# system (case-insensitive).
|
|
65
74
|
# @param sort [String, nil] Comma-separated sort fields; prefix with - for desc.
|
|
66
75
|
# @param page [Integer, nil] Page number (1-based; 1..1,000,000, 400 outside).
|
|
67
76
|
# @param per_page [Integer, nil] Items per page (max 100).
|
|
@@ -74,9 +83,11 @@ module Rail0
|
|
|
74
83
|
def list(status: nil, mode: nil, payer: nil, payee: nil, token: nil, rail0_id: nil,
|
|
75
84
|
chain_id: nil, disputed: nil, operation: nil, min_amount: nil, max_amount: nil,
|
|
76
85
|
created_from: nil, created_to: nil, scope: nil, account_id: nil, has_error: nil,
|
|
77
|
-
expiring_within: nil,
|
|
86
|
+
expiring_within: nil, reference: nil, reference_origin: nil, sort: nil, page: nil,
|
|
87
|
+
per_page: nil)
|
|
78
88
|
query = build_query(status: status, mode: mode, payer: payer, payee: payee, token: token,
|
|
79
|
-
rail0_id: rail0_id,
|
|
89
|
+
rail0_id: rail0_id, reference: reference, reference_origin: reference_origin,
|
|
90
|
+
chain_id: chain_id, disputed: disputed,
|
|
80
91
|
operation: operation,
|
|
81
92
|
min_amount: min_amount, max_amount: max_amount,
|
|
82
93
|
created_from: created_from, created_to: created_to,
|
|
@@ -97,7 +108,10 @@ module Rail0
|
|
|
97
108
|
# create(chain_id: 84532, mode: "authorize", amount: "100.00", token: "0x…", payer: "0x…", payee: "0x…")
|
|
98
109
|
# create({ chain_id: 84532, ... }, idempotency_key: "order-42")
|
|
99
110
|
#
|
|
100
|
-
# @param params [Hash, nil] chain_id, mode, amount, token, payer, payee, description (opt),
|
|
111
|
+
# @param params [Hash, nil] chain_id, mode, amount, token, payer, payee, description (opt),
|
|
112
|
+
# metadata (opt), reference (opt — the payment's identifier in the merchant's own
|
|
113
|
+
# system, max 128 characters, not unique, an idempotency term), reference_origin (opt
|
|
114
|
+
# — which system: a lowercase slug, e.g. "commercelayer"; needs a reference).
|
|
101
115
|
# @param idempotency_key [String, nil] Optional Idempotency-Key header value.
|
|
102
116
|
# @param fields [Hash] Field keywords, used when +params+ is omitted.
|
|
103
117
|
# @return [Hash]
|
|
@@ -116,22 +130,36 @@ module Rail0
|
|
|
116
130
|
http.get("/payments/#{segment(id)}")
|
|
117
131
|
end
|
|
118
132
|
|
|
119
|
-
# Set or clear a payment's
|
|
133
|
+
# Set or clear a payment's off-chain fields (PATCH /payments/{id}).
|
|
134
|
+
#
|
|
135
|
+
# Off-chain metadata, so it can change at any status, settled payments included. Only
|
|
136
|
+
# a participant (the payer or the payee) may call it — a non-participant gets the same
|
|
137
|
+
# 404 as an unknown id, and a deactivated caller wallet a 403 `wallet_deactivated`.
|
|
138
|
+
#
|
|
139
|
+
# Only the keywords you PASS are sent, and at least one is required (ArgumentError
|
|
140
|
+
# first, rather than the gateway's 400). +nil+ or +""+ clears a field.
|
|
120
141
|
#
|
|
121
|
-
#
|
|
122
|
-
#
|
|
123
|
-
# payer
|
|
124
|
-
#
|
|
142
|
+
# - +description:+ either party, max 255 characters (422 over).
|
|
143
|
+
# - +merchant_note:+ the PAYEE's internal note, max 2000 characters — never shown to
|
|
144
|
+
# the payer, not in webhooks.
|
|
145
|
+
# - +reference:+ / +reference_origin:+ the payment's identifier in the merchant's own
|
|
146
|
+
# system and which system; the payee corrects them, and clearing the reference
|
|
147
|
+
# clears its origin.
|
|
125
148
|
#
|
|
126
|
-
#
|
|
127
|
-
# which clears it (as does +""+). Over 255 characters is a 422.
|
|
128
|
-
# (rail0-gateway#373)
|
|
149
|
+
# The last three are the payee's: the payer gets 403 `not_the_payee`.
|
|
129
150
|
#
|
|
130
151
|
# @param id [String] Payment UUID or rail0_id.
|
|
131
|
-
# @param description [String, nil] The new description; +nil+ or +""+ clears it.
|
|
132
152
|
# @return [Hash] The updated payment, the same shape {get} returns.
|
|
133
|
-
|
|
134
|
-
|
|
153
|
+
# @raise [ArgumentError] when no field is given.
|
|
154
|
+
def update(id, description: UNSET, merchant_note: UNSET, reference: UNSET, reference_origin: UNSET)
|
|
155
|
+
body = { description: description, merchant_note: merchant_note, reference: reference,
|
|
156
|
+
reference_origin: reference_origin }.reject { |_, v| v.equal?(UNSET) }
|
|
157
|
+
if body.empty?
|
|
158
|
+
raise ArgumentError,
|
|
159
|
+
"at least one of description, merchant_note, reference or reference_origin is required"
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
http.patch("/payments/#{segment(id)}", body)
|
|
135
163
|
end
|
|
136
164
|
|
|
137
165
|
# List on-chain transactions for a payment.
|
|
@@ -92,11 +92,16 @@ module Rail0
|
|
|
92
92
|
# operator may change any). +active: false+ on the account's only active wallet
|
|
93
93
|
# raises ApiError 422 +last_active_wallet+: login resolves the account from a
|
|
94
94
|
# wallet, so an account with none could neither act nor add one back.
|
|
95
|
+
#
|
|
96
|
+
# +active: true+ is the one write a deactivated wallet's own session may make: it
|
|
97
|
+
# switches the wallet back on when its owner switched it off. A wallet an operator
|
|
98
|
+
# switched off (+deactivated_by+ "operator", or nil on an older switch-off) raises
|
|
99
|
+
# ApiError 403 +wallet_deactivated_by_operator+: only an operator lifts it.
|
|
95
100
|
# @param account_id [String] Account UUID.
|
|
96
101
|
# @param id_or_address [String] Wallet UUID or 0x address.
|
|
97
102
|
# @param label [String, nil] New label.
|
|
98
103
|
# @param active [Boolean, nil] New active status.
|
|
99
|
-
# @return [Hash] id, address, label, active
|
|
104
|
+
# @return [Hash] id, address, label, active, deactivated_by
|
|
100
105
|
def update(account_id, id_or_address, label: nil, active: nil)
|
|
101
106
|
body = {}
|
|
102
107
|
body[:label] = label unless label.nil?
|
|
@@ -127,6 +127,18 @@ module Rail0
|
|
|
127
127
|
http.put("/webhooks/#{segment(id)}/reset_circuit")
|
|
128
128
|
end
|
|
129
129
|
|
|
130
|
+
# Send a test event to the webhook's callback now and report what it did
|
|
131
|
+
# (POST /webhooks/{id}/test). The event has topic "webhooks.test", +test: true+ and
|
|
132
|
+
# no payment, and is signed and delivered like a real one. Synchronous, one attempt,
|
|
133
|
+
# not logged in {event_callbacks} and not counted by the circuit breaker; it works on
|
|
134
|
+
# a disabled webhook. A refusal by the endpoint is a result, not an error.
|
|
135
|
+
# @param id [String] Webhook UUID.
|
|
136
|
+
# @return [Hash] +delivered+, +response_code+, +response_message+, +error_reason+,
|
|
137
|
+
# +duration_ms+.
|
|
138
|
+
def test(id)
|
|
139
|
+
http.post("/webhooks/#{segment(id)}/test", {})
|
|
140
|
+
end
|
|
141
|
+
|
|
130
142
|
# List delivery attempts for a webhook.
|
|
131
143
|
# @param id [String] Webhook UUID.
|
|
132
144
|
# @param status [String, nil] Filter by delivery status: "delivered" or "failed" only
|
data/lib/rail0/types.rb
CHANGED
|
@@ -209,10 +209,11 @@ module Rail0
|
|
|
209
209
|
|
|
210
210
|
# Public-safe wallet view (the reduced set a buyer needs to discover a merchant's payment methods).
|
|
211
211
|
Wallet = Struct.new(
|
|
212
|
-
:id,
|
|
213
|
-
:address,
|
|
212
|
+
:id, # String
|
|
213
|
+
:address, # String
|
|
214
214
|
:label,
|
|
215
|
-
:active,
|
|
215
|
+
:active, # Boolean
|
|
216
|
+
:deactivated_by, # Who switched the wallet off: `owner` (a session signed in with this wallet) or `operator` (an admin session). Null while active, and on a switch-off that predates the record — treated as `operator`. A wallet an operator switched off can be switched back on only by an operator (403 `wallet_deactivated_by_operator`). Always null on GET /payment_methods, which serves active wallets only.
|
|
216
217
|
keyword_init: true
|
|
217
218
|
)
|
|
218
219
|
|
|
@@ -229,11 +230,12 @@ module Rail0
|
|
|
229
230
|
# A wallet with its token holdings nested inline. `tokens` is empty when the wallet has none — the
|
|
230
231
|
# wallet is still returned, never omitted.
|
|
231
232
|
WalletWithTokens = Struct.new(
|
|
232
|
-
:id,
|
|
233
|
-
:address,
|
|
233
|
+
:id, # String
|
|
234
|
+
:address, # String
|
|
234
235
|
:label,
|
|
235
|
-
:active,
|
|
236
|
-
:
|
|
236
|
+
:active, # Boolean
|
|
237
|
+
:deactivated_by, # Who switched the wallet off: `owner` (a session signed in with this wallet) or `operator` (an admin session). Null while active, and on a switch-off that predates the record — treated as `operator`. A wallet an operator switched off can be switched back on only by an operator (403 `wallet_deactivated_by_operator`). Always null on GET /payment_methods, which serves active wallets only.
|
|
238
|
+
:tokens, # Array
|
|
237
239
|
keyword_init: true
|
|
238
240
|
)
|
|
239
241
|
|
|
@@ -263,6 +265,9 @@ module Rail0
|
|
|
263
265
|
:last_error_code, # Decoded reason of the last failed on-chain attempt; null once the payment makes forward progress. Non-null means the latest attempt failed.
|
|
264
266
|
:last_error_message, # Human-readable form of last_error_code.
|
|
265
267
|
:description,
|
|
268
|
+
:reference, # This payment's identifier in the merchant's own system; set at create, correctable by the payee.
|
|
269
|
+
:reference_origin, # The system the reference comes from, a lowercase slug (e.g. commercelayer).
|
|
270
|
+
:merchant_note, # The payee's internal note (PATCH /payments/{id}). Present only when the session is the payment's PAYEE, and on the operator's Full view; absent for the payer.
|
|
266
271
|
:metadata,
|
|
267
272
|
:signed_at, # When the payer signature was stored (null while unsigned).
|
|
268
273
|
:created_at, # String
|
|
@@ -289,6 +294,16 @@ module Rail0
|
|
|
289
294
|
keyword_init: true
|
|
290
295
|
)
|
|
291
296
|
|
|
297
|
+
# What the callback did with a test event (POST /webhooks/{id}/test).
|
|
298
|
+
WebhookTestResult = Struct.new(
|
|
299
|
+
:delivered, # Boolean — The endpoint answered 2xx.
|
|
300
|
+
:response_code, # The HTTP status it answered; null when no response came (see error_reason).
|
|
301
|
+
:response_message, # Up to 1000 printable characters of its response body.
|
|
302
|
+
:error_reason, # Why no response came: a refused address, a TLS or connection error, the deadline.
|
|
303
|
+
:duration_ms, # Integer — How long the attempt took.
|
|
304
|
+
keyword_init: true
|
|
305
|
+
)
|
|
306
|
+
|
|
292
307
|
Transaction = Struct.new(
|
|
293
308
|
:id, # String
|
|
294
309
|
:payment_id, # String
|
|
@@ -334,32 +349,48 @@ module Rail0
|
|
|
334
349
|
)
|
|
335
350
|
|
|
336
351
|
# The account's own profile as the holder reads it (GET) and as a PATCH returns it — id, name,
|
|
337
|
-
# email, priority_tip, timestamps. Deliberately no admin/role field: the operator
|
|
338
|
-
# separate table, so a standard account's profile carries no trace of that axis. An
|
|
339
|
-
# any account gets the whole record plus `admin` instead, which is a different shape
|
|
340
|
-
# one.
|
|
352
|
+
# email, priority_tip, payment windows, timestamps. Deliberately no admin/role field: the operator
|
|
353
|
+
# grant lives in a separate table, so a standard account's profile carries no trace of that axis. An
|
|
354
|
+
# ADMIN reading any account gets the whole record plus `admin` instead, which is a different shape
|
|
355
|
+
# and not this one.
|
|
341
356
|
Account = Struct.new(
|
|
342
|
-
:id,
|
|
343
|
-
:name,
|
|
357
|
+
:id, # String
|
|
358
|
+
:name, # String
|
|
344
359
|
:email,
|
|
345
|
-
:priority_tip,
|
|
346
|
-
:
|
|
347
|
-
:
|
|
360
|
+
:priority_tip, # Boolean — Whether the merchant's own transactions tip the network's suggested priority fee (true, the default) or the chain's minimum tip (false: 0 on most chains, 25 gwei on Polygon PoS/Amoy). The buyer's dispute/close_dispute never read it.
|
|
361
|
+
:authorization_ttl, # Integer — Seconds an authorization of the account's new payments stays capturable after the payment is created: the account's own value, or the gateway default (AUTHORIZATION_TTL).
|
|
362
|
+
:refund_ttl, # Integer — Seconds the account's new payments stay refundable, and open to a dispute, after they are created: the account's own value, or the gateway default (REFUND_TTL).
|
|
363
|
+
:payment_windows, # PaymentWindows
|
|
364
|
+
:created_at, # String
|
|
365
|
+
:updated_at, # String
|
|
366
|
+
keyword_init: true
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
# How the account's windows are set and what a new value must meet: per window, whether it is the
|
|
370
|
+
# account's own (custom) or the default, the default, and the bounds. The refund window has no floor
|
|
371
|
+
# of its own: the pair must keep refund_ttl - authorization_ttl >= min_refund_window.
|
|
372
|
+
PaymentWindows = Struct.new(
|
|
373
|
+
:authorization_ttl, # Hash
|
|
374
|
+
:refund_ttl, # Hash
|
|
375
|
+
:min_refund_window, # Integer
|
|
348
376
|
keyword_init: true
|
|
349
377
|
)
|
|
350
378
|
|
|
351
379
|
# The whole account record, as an operator reads it (Full entity: GET /accounts, and GET/PATCH
|
|
352
380
|
# /accounts/{account_id} for an admin session): every column plus `admin` and the derived `status`.
|
|
353
381
|
AccountFull = Struct.new(
|
|
354
|
-
:id,
|
|
355
|
-
:name,
|
|
356
|
-
:email,
|
|
357
|
-
:active,
|
|
358
|
-
:priority_tip,
|
|
359
|
-
:
|
|
360
|
-
:
|
|
361
|
-
:
|
|
362
|
-
:
|
|
382
|
+
:id, # String
|
|
383
|
+
:name, # String
|
|
384
|
+
:email, # String — Stored lowercase.
|
|
385
|
+
:active, # Boolean — The operator's switch. false = deactivated.
|
|
386
|
+
:priority_tip, # Boolean — The merchant's fee setting (see Account.priority_tip).
|
|
387
|
+
:authorization_ttl, # Integer — Seconds an authorization of the account's new payments stays capturable after the payment is created: the account's own value, or the gateway default (AUTHORIZATION_TTL).
|
|
388
|
+
:refund_ttl, # Integer — Seconds the account's new payments stay refundable, and open to a dispute, after they are created: the account's own value, or the gateway default (REFUND_TTL).
|
|
389
|
+
:payment_windows, # PaymentWindows
|
|
390
|
+
:created_at, # String
|
|
391
|
+
:updated_at, # String
|
|
392
|
+
:admin, # Boolean — Whether the account holds the operator grant (a row in administrators).
|
|
393
|
+
:status, # String — One-word reading of active: active, or deactivated (switched off by an operator).
|
|
363
394
|
keyword_init: true
|
|
364
395
|
)
|
|
365
396
|
|
|
@@ -474,7 +505,8 @@ module Rail0
|
|
|
474
505
|
:webhooks, # Hash
|
|
475
506
|
:settlement, # Hash
|
|
476
507
|
:rate_limit, # Hash
|
|
477
|
-
:siwe, # Hash
|
|
508
|
+
:siwe, # Hash — domains (SIWE_DOMAIN), accepted_chain_ids (1 plus the ids of the active catalog chains: the chainId values a login may carry) and nonce_ttl.
|
|
509
|
+
:session, # Hash — environment (RAIL0_ENVIRONMENT, default RACK_ENV: the scope every issued token is bound to), jwt_ttl and jwt_audience: the session token's lifetime (a constant) and audience (`rail0-gateway:<environment>`).
|
|
478
510
|
:fees, # Hash
|
|
479
511
|
:rpc, # Hash
|
|
480
512
|
:onboarding, # Hash
|
|
@@ -532,18 +564,5 @@ module Rail0
|
|
|
532
564
|
:contract_address, # String — Address (lowercase) of the RAIL0 deployment the payment is bound to, archived versions included. The sweeper accepts a receipt's event only when this contract emitted it.
|
|
533
565
|
keyword_init: true
|
|
534
566
|
)
|
|
535
|
-
|
|
536
|
-
# Per-chain indexer config.
|
|
537
|
-
SyncBlockchain = Struct.new(
|
|
538
|
-
:chain_id, # Integer
|
|
539
|
-
:start_block, # Integer
|
|
540
|
-
:required_confirmations, # Integer — Fallback burial depth, used where the chain serves no finality tag.
|
|
541
|
-
:finality_tag, # String — Which block the chain calls settled; the indexer gates every notify on it.
|
|
542
|
-
:explorer_url, # Block explorer base URL; null when the chain has none.
|
|
543
|
-
:network_type, # String — "testnet" or "mainnet"; selects which chains a deployment indexes.
|
|
544
|
-
:rpc_urls, # Array — Ordered list of public RPC endpoints tried in turn (serial fallback).
|
|
545
|
-
:contracts, # Array — Active RAIL0 contract addresses on the chain (all watched versions).
|
|
546
|
-
keyword_init: true
|
|
547
|
-
)
|
|
548
567
|
end
|
|
549
568
|
end
|
data/lib/rail0/version.rb
CHANGED
|
@@ -5,5 +5,5 @@ module Rail0
|
|
|
5
5
|
# consumed by path from a sibling checkout, where the number bought nothing: no registry
|
|
6
6
|
# ever served a 1.x, no tag was ever cut, and the one consumer resolves the working tree.
|
|
7
7
|
# Publishing from 1.2.0 would have implied two releases nobody can install.
|
|
8
|
-
VERSION = "1.
|
|
8
|
+
VERSION = "1.8.0"
|
|
9
9
|
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: rail0-sdk
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.
|
|
4
|
+
version: 1.8.0
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Commerce Layer
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2026-10-
|
|
11
|
+
date: 2026-10-28 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: logger
|