rail0-sdk 1.7.2 → 1.8.1

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: 3c69ef678235fc14acf0f3bcaebc302bd3b63d6dd1b41a1358381b49fd6a9738
4
- data.tar.gz: 1d42362b54cf2118b2150087211bd7f5df49a8248278af49603fd745d32be08b
3
+ metadata.gz: eaaadaf08346573c426e649979548088c8cbbb1a2b037e63026c5512fa44feb8
4
+ data.tar.gz: b4eea2d0cd8cb675f2d31e5fbddee08f62fd3421f2cb969db246fc8e4d4338fb
5
5
  SHA512:
6
- metadata.gz: 061516a40ea00e3ebf114f9ae841bfac830682f443caaa8f82fbe4789de91d1eab760896c6a0447a371092cd01f5bbb9f30200e8e17f0897bce60390ba541e82
7
- data.tar.gz: 5f0e8c80653d3234d24f2816f607a84ecfb99c743afa2dd58fa64d3c184510458796ab54efc8cad00c7b478f35a26bf68d6339e1c592dff8d2712d087e3aae03
6
+ metadata.gz: 9433218dbe34e8d9a6cf7c200aca99d15695940fc099e5347b85e14c9612dbef6d824c303b3cd52e81e00787c25a0c2459cc40aa5e62bd0adb4555294f984816
7
+ data.tar.gz: 5e185810e4e78e7282ceda6760e5c2ee2857bf7c5469806fbb48fdc92fed12592fd8367aa1707bdafc76a52d4d7e65251d3dce36222174434bce9c53125398ff
data/README.md CHANGED
@@ -471,6 +471,11 @@ client.payments.create(params, idempotency_key: nil) # or keyword fields
471
471
  client.payments.get(id)
472
472
  client.payments.update(id, description: "Order #42") # → the payment, same shape as get
473
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")
474
479
  # The description is the only editable field: off-chain, so any status, settled included.
475
480
  # Payer or payee only (a non-participant gets 404); over 255 characters is a 422.
476
481
  client.payments.list(status: "authorized", disputed: false, chain_id: 84532, sort: "-created_at")
@@ -644,6 +649,7 @@ client.webhooks.enable(id)
644
649
  client.webhooks.disable(id)
645
650
  client.webhooks.rotate_secret(id) # returns a fresh shared_secret
646
651
  client.webhooks.reset_circuit(id)
652
+ client.webhooks.test(id) # signed webhooks.test event now → { delivered:, response_code:, … }; not logged
647
653
  # status is "delivered" or "failed" only; payment_id takes a UUID or a rail0_id.
648
654
  client.webhooks.event_callbacks(id, status: "failed", response_code: "500")
649
655
  client.webhooks.redeliver(id, callback_id) # 202 { status: "queued" } — replay one delivery
@@ -740,11 +746,13 @@ client.accounts.get(account_id)
740
746
  client.accounts.update(account_id, email: "ops@merchant.example")
741
747
  client.accounts.update(account_id, name: "Merchant Ltd", email: "ops@merchant.example")
742
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)
743
751
  ```
744
752
 
745
753
  `email` is in the response because the holder is this endpoint's only possible caller.
746
754
 
747
- `update` needs at least one of `name`/`email`/`priority_tip` (it raises `ArgumentError`
755
+ `update` needs at least one of `name`/`email`/`priority_tip`/a payment window (it raises `ArgumentError`
748
756
  before sending an empty PATCH, which the gateway would answer with 400). Name and email are
749
757
  unique across accounts, so a value another account holds answers **409**. It is a write on
750
758
  the account, so a deactivated wallet (403 `wallet_deactivated`) or account (403
@@ -755,6 +763,15 @@ exposed here.
755
763
  capture, void, release, refund): `true`, the default, tips the network's suggested priority
756
764
  fee; `false` uses the chain's minimum tip (0 on most chains, 25 gwei on Polygon PoS/Amoy) —
757
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
+
758
775
  The account's wallets are a collection under the same path (`client.wallets`), and
759
776
  buyer-facing discovery is `client.payment_methods`.
760
777
 
@@ -1082,25 +1099,52 @@ uses them everywhere, table-aligned hashes because some literals are tables, and
1082
1099
  (`retry_on_429`, `eip712`, `secp256k1`) that each spell themselves their own way. A linter
1083
1100
  that argues with the codebase teaches people to ignore it.
1084
1101
 
1085
- ## Publishing
1086
-
1087
- Releases publish from GitHub Actions (`.github/workflows/release.yml`) through RubyGems
1088
- **Trusted Publishing**: RubyGems.org trusts that workflow in this repo, so there is no API
1089
- key to store or rotate and no OTP prompt.
1090
-
1091
- 1. Merge a PR that bumps `Rail0::VERSION` in `lib/rail0/version.rb`.
1092
- 2. Publish a GitHub release on `main` tagged `v<version>` (e.g. `v1.2.0`).
1093
-
1094
- The workflow checks the tag against `Rail0::VERSION`, runs `rake build` (rubocop and the
1095
- specs first, then the gem) and pushes it. A version already on RubyGems.org is skipped, so
1096
- a re-run (Actions → release → Run workflow, from `main`, with the tag as input) is always
1097
- safe — that is also how a release cut before the workflow existed gets published.
1098
-
1099
- One-time setup, by an owner of the gem on rubygems.org: rail0-sdk → Trusted publishers →
1100
- GitHub Actions, repository `commercelayer/rail0-ruby`, workflow `release.yml`.
1101
-
1102
- Publishing by hand still works as a fallback (`gem build rail0-sdk.gemspec && gem push
1103
- rail0-sdk-<version>.gem`, with the OTP the gem's `rubygems_mfa_required` asks for).
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).
1104
1148
 
1105
1149
  ## License
1106
1150
 
@@ -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`, `created_at`, `updated_at`.
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 none of name, email or priority_tip is given.
65
- def update(account_id, name: nil, email: nil, priority_tip: nil)
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
- raise ArgumentError, "at least one of name, email or priority_tip is required" if body.empty?
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
@@ -58,7 +58,7 @@ module Rail0
58
58
  #
59
59
  # +admin+ is true only for an account holding the operator (administrators)
60
60
  # grant: the gateway adds the field on an admin's session alone, and it is
61
- # normalised here to a boolean so a standard login reads false rather than nil.
61
+ # normalized here to a boolean so a standard login reads false rather than nil.
62
62
  # Visibility only — every gated route re-checks the grant per request.
63
63
  def verify(message:, signature:)
64
64
  session = http.post("/auth", { message: message, signature: signature })
@@ -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, sort: nil, page: nil, per_page: 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, chain_id: chain_id, disputed: disputed,
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), metadata (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 description (PATCH /payments/{id}).
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
- # The description is the only field this route edits: it is off-chain metadata, so
122
- # it can change at any status, settled payments included. Only a participant (the
123
- # payer or the payee) may edit it — a non-participant gets the same 404 as an unknown
124
- # id, and a deactivated caller wallet a 403 `wallet_deactivated`.
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
- # +description:+ is required, and is always sent: passing +nil+ sends JSON +null+,
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
- def update(id, description:)
134
- http.patch("/payments/#{segment(id)}", { description: description })
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.
@@ -11,7 +11,7 @@ module Rail0
11
11
  # segment the same way ({#segment}).
12
12
  module Query
13
13
  # Segments that would not stay put once interpolated: "." and ".." are dot
14
- # segments that a URL normaliser (a proxy, a CDN, the server's router) may
14
+ # segments that a URL normalizer (a proxy, a CDN, the server's router) may
15
15
  # resolve away, and an empty one collapses the path onto its parent.
16
16
  UNSAFE_SEGMENTS = ["", ".", ".."].freeze
17
17
  private_constant :UNSAFE_SEGMENTS
@@ -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
@@ -265,6 +265,9 @@ module Rail0
265
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.
266
266
  :last_error_message, # Human-readable form of last_error_code.
267
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.
268
271
  :metadata,
269
272
  :signed_at, # When the payer signature was stored (null while unsigned).
270
273
  :created_at, # String
@@ -291,6 +294,16 @@ module Rail0
291
294
  keyword_init: true
292
295
  )
293
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
+
294
307
  Transaction = Struct.new(
295
308
  :id, # String
296
309
  :payment_id, # String
@@ -336,32 +349,48 @@ module Rail0
336
349
  )
337
350
 
338
351
  # The account's own profile as the holder reads it (GET) and as a PATCH returns it — id, name,
339
- # email, priority_tip, timestamps. Deliberately no admin/role field: the operator grant lives in a
340
- # separate table, so a standard account's profile carries no trace of that axis. An ADMIN reading
341
- # any account gets the whole record plus `admin` instead, which is a different shape and not this
342
- # 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.
343
356
  Account = Struct.new(
344
- :id, # String
345
- :name, # String
357
+ :id, # String
358
+ :name, # String
346
359
  :email,
347
- :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.
348
- :created_at, # String
349
- :updated_at, # String
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
350
376
  keyword_init: true
351
377
  )
352
378
 
353
379
  # The whole account record, as an operator reads it (Full entity: GET /accounts, and GET/PATCH
354
380
  # /accounts/{account_id} for an admin session): every column plus `admin` and the derived `status`.
355
381
  AccountFull = Struct.new(
356
- :id, # String
357
- :name, # String
358
- :email, # String — Stored lowercase.
359
- :active, # Boolean — The operator's switch. false = deactivated.
360
- :priority_tip, # Boolean — The merchant's fee setting (see Account.priority_tip).
361
- :created_at, # String
362
- :updated_at, # String
363
- :admin, # Boolean — Whether the account holds the operator grant (a row in administrators).
364
- :status, # String — One-word reading of active: active, or deactivated (switched off by an operator).
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).
365
394
  keyword_init: true
366
395
  )
367
396
 
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.7.2"
8
+ VERSION = "1.8.1"
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.7.2
4
+ version: 1.8.1
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-21 00:00:00.000000000 Z
11
+ date: 2026-10-28 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: logger