seatlayer 0.7.0 → 0.9.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 +109 -0
- data/README.md +224 -66
- data/lib/seatlayer/event_series.rb +78 -0
- data/lib/seatlayer/holders.rb +109 -0
- data/lib/seatlayer/http_client.rb +29 -14
- data/lib/seatlayer/inventory.rb +32 -3
- data/lib/seatlayer/orders.rb +75 -0
- data/lib/seatlayer/resources.rb +115 -4
- data/lib/seatlayer/seasons.rb +432 -11
- data/lib/seatlayer/version.rb +1 -1
- data/lib/seatlayer.rb +7 -1
- metadata +6 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 2e7f487c6f37918712d5b9ad4967aefb979b5bbd85368cd1c669b3718837c19d
|
|
4
|
+
data.tar.gz: 1a350dcf149a427a410faf436a39ab8b4ada55a8a262956513316b7531a52b65
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: a1c8d7522480ed8f26c1858ad590fed53146a2d672fa698d4ac07009978f6cacb73ce63a9d8912ecbbae50439344f7b8117e25d72e307cf323f22d33fcaeafab
|
|
7
|
+
data.tar.gz: c909fa65b7a98c2e6d03ba1b69a79ee4074a33d4041647fbced2d15d923b40b7385ccabe8ff270fb5c4e90b8664ee0f89bf9adcb8ded0388489f9f1ef81c00ed
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,114 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## 0.9.0 (2026-11-11)
|
|
4
|
+
|
|
5
|
+
- Season seats: `set_aside_season_block` (`POST /v1/seasons/:key/block`), `retrieve_season_block`
|
|
6
|
+
and `release_season_block` (`POST /v1/seasons/:key/block/release`). Each write answers "job"
|
|
7
|
+
and "block"; follow the job with `retrieve_season_bulk_job` (kinds `season_block_set_aside` and
|
|
8
|
+
`season_block_release`, read by id, not listed). Both writes are retried safely with one
|
|
9
|
+
Idempotency-Key, generated when none is given.
|
|
10
|
+
- `inventory_mode:` on season create and update (opt a season into season seats), read back on
|
|
11
|
+
every season (`"inventoryMode"`).
|
|
12
|
+
- Webhook names `season.block.ready` and `season.block.released` (`Webhooks::EVENT_NAMES`, now 58).
|
|
13
|
+
- A season-seats season's date cancel (`create_season_amendment`) or past date
|
|
14
|
+
(`mark_season_performance_past`) can answer `202` while the date leaves every season seat:
|
|
15
|
+
`Seasons.date_leave_pending?` recognises it.
|
|
16
|
+
- New refusals come through `SeatLayer::APIError` and its subclasses with `code`:
|
|
17
|
+
`season_block_not_enabled`, `block_exists`, `block_not_ready`, `season_not_draft_or_active`,
|
|
18
|
+
`block_selection_empty`, `seat_not_in_block`, `season_block_set_aside`, `season_inventory_locked`,
|
|
19
|
+
`date_leave_in_progress`.
|
|
20
|
+
`inventory_mode:` can also answer `season_block_packages` (more than one package),
|
|
21
|
+
`season_block_on_sale`, `season_block_exists` and `invalid_inventory_mode`. The set-aside can
|
|
22
|
+
also answer `season_block_packages`, `season_block_one_package`, `plan_not_published`,
|
|
23
|
+
`season_block_undoing`, `chart_unavailable` and `dates_missing`, and the release
|
|
24
|
+
`release_mismatch`. On `503 season_busy` or `season_not_configured`, send the same request again.
|
|
25
|
+
|
|
26
|
+
- A date its season cancelled never opens again: `transition_event_sales` to `testable` or
|
|
27
|
+
`live` and `reopen` answer `409 season_date_cancelled`, and `update_event_series` with
|
|
28
|
+
`sales: "on"` leaves it closed as a failed item of the job (`season_date_cancelled`; not in
|
|
29
|
+
`failedDates`).
|
|
30
|
+
- `create_season_amendment` refuses to move (`reschedule`), postpone or replace a cancelled date,
|
|
31
|
+
and `mark_season_performance_past` refuses to mark one as taken place:
|
|
32
|
+
`409 performance_cancelled`.
|
|
33
|
+
- `update_event_series` sends only the prices that change: each date keeps its own price for
|
|
34
|
+
every category (or tier) left out, so weekend and weekday prices keep their difference.
|
|
35
|
+
- A season's `transferCutoffHours` / `returnCutoffHours` of `nil` means the club default
|
|
36
|
+
(Settings › Ticket site), or until the date starts when the club has none.
|
|
37
|
+
- `create_holder_transfer` and `create_holder_return` are not stopped by the club's customer
|
|
38
|
+
switches in Settings › Ticket site; cut-offs still apply.
|
|
39
|
+
- Cancelling a date (`create_season_amendment` with `kind: "cancel"`) voids its tickets as a
|
|
40
|
+
`date_cancel_void` job, named by `SeatLayer::Seasons.date_cancel_void_job_id(amendment_id)`
|
|
41
|
+
(`sbj_void_…`); follow it with `retrieve_season_bulk_job`. `season.ticket.voided` says
|
|
42
|
+
`date_cancelled` for each Season ticket it voids.
|
|
43
|
+
- A Season package holds 2 to 50 dates. `SeatLayer::Seasons.holder_import_rows_per_call(dates)`
|
|
44
|
+
gives the most rows one `create_season_holder_import` call takes (100 up to 20 dates, 43 at
|
|
45
|
+
50); a call with more raises `ValidationError` `invalid_rows` with `"maxRows"` in its `body`.
|
|
46
|
+
- `update_season`'s `singleTicketDates` may carry `jobRefused`, why a `season_singles` job could
|
|
47
|
+
not start; the change stands, and sending `single_tickets: true` again opens any date still
|
|
48
|
+
not open. An `update_season` retried with its first `idempotency_key:` and
|
|
49
|
+
`expected_revision:` is not applied twice.
|
|
50
|
+
- An `event_series.create_event_series` given up for its key raises `ConflictError`
|
|
51
|
+
`create_abandoned` with a message to send it again with a new idempotency key.
|
|
52
|
+
- Added the 39 remaining Fixed Renewable Season operations to `client.seasons` (87 in all):
|
|
53
|
+
performances, package edits and prices, publishing, checkout tests, occurrence returns and
|
|
54
|
+
reclaims, holders, Season order refunds and cancellations, hosted renewals, renewal quotes,
|
|
55
|
+
intents and checkouts, the sales,
|
|
56
|
+
seats and seat-count reports, and bulk refunds and lapsed-offer releases with their jobs.
|
|
57
|
+
- Added three resources: `client.holders` (holders, customers, holder tickets and ticket
|
|
58
|
+
transfers), `client.orders` (account and event orders, refunds, cancellations and ticket
|
|
59
|
+
resends) and `client.event_series` (plan, read, extend and update an event series).
|
|
60
|
+
- Added `holders.create_holder_return`: give back a holder's seat on one date, one per ticket.
|
|
61
|
+
- Added the event sales settings to `client.events`: buyer checkout, payment gateway, sales
|
|
62
|
+
readiness, preflight and transition, booking fee, and discount codes.
|
|
63
|
+
- `export_season_holders_csv`, `export_season_orders_csv` and `orders.export_event_orders_csv`
|
|
64
|
+
return the raw CSV text. `delete_season_plan` and `events.delete_event_discount_code` return
|
|
65
|
+
an empty Hash (the API answers 204).
|
|
66
|
+
- 27 of the new operations use exact header replay (41 in the SDK now) and retry with one
|
|
67
|
+
`Idempotency-Key`; every other new mutation is single-attempt.
|
|
68
|
+
- `Webhooks::EVENT_NAMES` adds `series.created`, `series.dates_added`, `order.paid`,
|
|
69
|
+
`order.refunded`, `order.cancelled`, `customer.created`, `customer.email_changed`,
|
|
70
|
+
`season.ticket.transfer_sent`, `season.ticket.transferred` and
|
|
71
|
+
`season.ticket.transfer_cancelled`.
|
|
72
|
+
- Added `seasons.refund_season_performance` (refund every order of a cancelled date as a job,
|
|
73
|
+
with header replay), `seasons.list_season_bulk_jobs`, and `seasons.retrieve_season_bulk_job`.
|
|
74
|
+
- `create_season_amendment` takes `refund:` and `reason:`, for the new `postpone` and `cancel`
|
|
75
|
+
kinds.
|
|
76
|
+
- `update_season` takes `checkout_mode:`, `transfer_cutoff_hours:`, and `return_cutoff_hours:`
|
|
77
|
+
(`nil` sends an explicit null).
|
|
78
|
+
- `replace_season_package_prices` documents `"priceTypeKey"` on each price, and
|
|
79
|
+
`create_season_holder_import` documents `"holderEmail"` and `"holderName"` on each row.
|
|
80
|
+
- `Webhooks::EVENT_NAMES` lists all 56 public event names, adding `object.resale.*`,
|
|
81
|
+
`season.occurrence.*`, `season.performance.*` (including `postponed`, `cancelled`, and
|
|
82
|
+
`rescheduled`), `season.bulk_action.completed`, `season.order.*`, `season.ticket.issued`,
|
|
83
|
+
`season.ticket.voided`, and the Season lifecycle, hold, booking, renewal, operation and
|
|
84
|
+
amendment events.
|
|
85
|
+
- `events.list` and `events.list_all` take `series_key:` to list one event series' dates.
|
|
86
|
+
- Added `seasons.retrieve_season_renewal_quote`: what renewing an offer costs, and whether SeatLayer checkout can take it now.
|
|
87
|
+
- Added `seasons.create_season_renewal_intent`: record a holder's intent for a renewal taken by phone or at the box office, so your own checkout can commit.
|
|
88
|
+
- Added `seasons.create_season_renewal_checkout`: a SeatLayer checkout that renews an offer with no hosted page, with header replay.
|
|
89
|
+
- Added `holders.create_holder_renewal_checkout`: the same checkout for one of a holder's offers, with header replay.
|
|
90
|
+
- Added `holders.decline_holder_renewal_offer`: decline one of a holder's offers, with an optional `reason:`.
|
|
91
|
+
- `seasons.decline_season_renewal_offer` takes an optional `reason:`, kept on the offer as `declineReason`.
|
|
92
|
+
- `update_season` takes `audience:` (`"embed_only"` or `"public"`).
|
|
93
|
+
- `open_season_sales` answers `warnings` (`checkout_test_missing` until a checkout test or rehearsal has passed) and can refuse with `409 plan_not_published` or `409 checkout_mode_not_chosen`.
|
|
94
|
+
- A cancelled date refunds per date only: an amendment's `refund:` is `"none"` or `"per_date"`, and `refund_season_performance` takes `amount: "per_date"` (`full` answers `422 date_refund_per_date_only`; refund a whole order with the order refund).
|
|
95
|
+
- `add_event_series_dates` takes `retry_failed: true` to make the failed dates again (`409 series_busy`, `422 nothing_to_retry`).
|
|
96
|
+
- `list_orders` takes `holder_ref:`; orders carry `holderRef` and `customerId`, and the buyer's email can be nil (a box office sale with no email).
|
|
97
|
+
- Season holds, give-backs and date cancels can refuse with `performance_cancelled`, `refund_not_started` (503), `missing_manage_capability` (403) or `invalid_refund`; a holder's status can be `reserved`; hosted renewal readiness carries `missingPrices`.
|
|
98
|
+
- `create_event_series` takes `from_event_key:`: an event you already set up becomes the first date under its own key, and every other date is a copy of it. `name:` and `chart_key:` are then left out. It refuses with `404 event_not_found`, or `422 event_has_no_date`, `event_in_season`, `event_in_performance_group`, `event_in_series` or `invalid_request`.
|
|
99
|
+
- Every date of an event series carries the series' plain name (`Hamlet`, not `Hamlet · 3 Nov`); tell dates apart by `startsAt`. `update_event_series` renames each date that carries the series name, and a series name cannot hold `{n}`, `{date}` or `{time}`.
|
|
100
|
+
- Added `seasons.release_season_lapsed_offers`: release the seats of every lapsed renewal offer as a job (`202 {"job"}`), with header replay. An offer extended before its turn is skipped and keeps its seat. It can refuse with `503 job_busy` or `season_not_configured`; send it again.
|
|
101
|
+
- `retrieve_season_hosted_renewals` and `list_season_renewal_offers` take `cursor:` and `limit:` (1 to 500 and 1 to 100) and answer one page, newest first, with `nextCursor` (nil on the last page); a bad cursor or limit is refused with `400 invalid_cursor` or `invalid_limit`.
|
|
102
|
+
- `create_season_renewal_offers` answers `201 {"offers"}` for 100 contracts or fewer, and `202 {"job", "batchId"}` above that: a `renewal_run` job makes the offers. Follow it with `retrieve_season_bulk_job`, then page the offers with `list_season_renewal_offers`.
|
|
103
|
+
- `create_season_holder_import_from_season` answers `202 {"job"}` when a commit brings across more than 100 holders: a `holder_import` job, whose `summary` has the same counts once it completes. It and `create_season_renewal_offers` can refuse with `409 job_mismatch` or `503 season_not_configured`, and the bring-across also with `503 job_busy`.
|
|
104
|
+
- `create_season_renewal_notices` takes up to 20,000 `offer_ids:`; more is refused with `422 too_many_offer_ids`.
|
|
105
|
+
- `create_season_renewal_notices` takes `batch_id:` instead of `offer_ids:`: email every open offer one `create_season_renewal_offers` batch made, such as the batch a `renewal_run` job made.
|
|
106
|
+
- Single tickets on a season: `create_season` and `update_season` take `single_tickets:` (default `false`, Season only; anything else is refused with `422 invalid_single_tickets`). Every season read carries `singleTickets`, and a season on sale may carry `singleTicketsNotOpen`. `update_season`, `open_season_sales` and `resume_season_sales` answer `singleTicketDates` (`direction`, `opened`, `closed`, `notOpened`, `jobId`) when they open or stop the dates' own sales; a `season_singles` job does it for more than 12 upcoming dates.
|
|
107
|
+
|
|
108
|
+
## 0.8.0 (2026-09-10)
|
|
109
|
+
|
|
110
|
+
- Added Event hosting regions: `region:` on event create, `default_region:` on workspace create and update, and `EVENT_HOSTING_REGIONS` to check a value before it is sent. Events created without a region run in Western Europe.
|
|
111
|
+
|
|
3
112
|
## 0.7.0 — 2026-08-30
|
|
4
113
|
|
|
5
114
|
- Added coverage for all 48 Fixed Renewable Season server
|
data/README.md
CHANGED
|
@@ -5,21 +5,31 @@
|
|
|
5
5
|
[](https://www.ruby-lang.org/)
|
|
6
6
|
[](LICENSE)
|
|
7
7
|
|
|
8
|
-
The official SeatLayer Ruby server
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
8
|
+
The official Ruby client for the SeatLayer API. The `seatlayer` gem lets a Ruby or Rails backend inspect seat holds, price orders from server data, book reserved seats and verify webhooks, with no runtime dependencies. SeatLayer is seating chart and reserved-seat ticketing software built for venues up to stadium scale.
|
|
9
|
+
|
|
10
|
+
SeatLayer's official Ruby server SDK is the trusted side of its reserved seating and seat booking
|
|
11
|
+
API. Inspect what a hold really contains, price from server-owned seating-chart data, and book
|
|
12
|
+
with a stable `booking_ref`, while managing charts, events, inventory, allocations, and webhooks
|
|
13
|
+
through one typed ticketing API client.
|
|
12
14
|
|
|
13
15
|
[`seatlayer` gem on RubyGems](https://rubygems.org/gems/seatlayer) ·
|
|
14
|
-
[
|
|
16
|
+
[Ruby server SDK guide](https://docs.seatlayer.io/server-sdk/ruby/) ·
|
|
15
17
|
[SeatLayer developer platform](https://seatlayer.io/developers/) ·
|
|
16
18
|
[SeatLayer JavaScript seat map SDK](https://www.npmjs.com/package/@seatlayer/js) ·
|
|
17
19
|
[Server API reference](https://docs.seatlayer.io/server-api/events/)
|
|
18
20
|
|
|
19
21
|
> **Server-side only.** This gem authenticates with your secret key. Never load it anywhere a
|
|
20
|
-
> ticket buyer can reach
|
|
22
|
+
> ticket buyer can reach. Browser surfaces get short-lived, origin-bound tokens that you mint here.
|
|
23
|
+
|
|
24
|
+
Two-step shape: the buyer picks and holds seats in the client with your public key, then this SDK confirms the booking from your server with your secret key while your platform keeps checkout and its own payment provider.
|
|
25
|
+
|
|
26
|
+
**Start here:** [Quickstart](https://docs.seatlayer.io/start/quickstart/) · [Holds and checkout](https://docs.seatlayer.io/buyer-sdk/holds-and-checkout/) · [Ruby server SDK guide](https://docs.seatlayer.io/server-sdk/ruby/) · [SDK catalog](https://docs.seatlayer.io/sdk-catalog.json) · [Pricing](https://seatlayer.io/pricing/): $0 entry, 100 free confirmed-sold-seat credits per organization each month, then $0.10 down to $0.05 a credit, and credits never expire.
|
|
27
|
+
|
|
28
|
+
## Scale evidence
|
|
29
|
+
|
|
30
|
+
Benchmarked on public 100,000-, 150,000- and 200,000-seat venue fixtures on 15 September 2026: 200,000 seats chart-ready in 1.95 s, desktop, local production build. Fixtures, method and run logs: https://github.com/seatlayer/seatlayer-performance. Live 200,000-seat stadium demo: https://app.seatlayer.io/demo/play/century-stadium-200k. This is renderer evidence, not a concurrent-buyer claim.
|
|
21
31
|
|
|
22
|
-
## Install
|
|
32
|
+
## Install the Ruby seat booking SDK
|
|
23
33
|
|
|
24
34
|
```ruby
|
|
25
35
|
gem "seatlayer"
|
|
@@ -29,7 +39,7 @@ gem "seatlayer"
|
|
|
29
39
|
gem install seatlayer
|
|
30
40
|
```
|
|
31
41
|
|
|
32
|
-
Requires Ruby 3.0 or newer. **No runtime dependencies
|
|
42
|
+
Requires Ruby 3.0 or newer. **No runtime dependencies**: `net/http`, `json` and `openssl` from the
|
|
33
43
|
standard library.
|
|
34
44
|
|
|
35
45
|
## Quick start
|
|
@@ -40,11 +50,16 @@ require "seatlayer"
|
|
|
40
50
|
client = SeatLayer::Client.new(ENV.fetch("SEATLAYER_SECRET_KEY"))
|
|
41
51
|
|
|
42
52
|
# 1. Provision a venue for a new organiser from a public template.
|
|
43
|
-
|
|
53
|
+
# Replace this placeholder with a template id from your catalog.
|
|
54
|
+
chart = client.templates.instantiate_template("your-published-template")["meta"]
|
|
44
55
|
client.charts.publish(chart["id"])
|
|
45
56
|
|
|
46
57
|
# 2. Create an event on it.
|
|
47
|
-
event = client.events.create(
|
|
58
|
+
event = client.events.create(
|
|
59
|
+
chart_id: chart["id"], name: "Spring Gala",
|
|
60
|
+
currency: "EUR", # omit to inherit the workspace currency
|
|
61
|
+
region: "western-europe" # India: "asia-pacific"
|
|
62
|
+
)["meta"]
|
|
48
63
|
|
|
49
64
|
# 3. Sell four seats over the phone.
|
|
50
65
|
held = client.inventory.hold_best_available(event["key"], qty: 4)
|
|
@@ -52,21 +67,47 @@ held = client.inventory.hold_best_available(event["key"], qty: 4)
|
|
|
52
67
|
client.inventory.book(event["key"], hold_id: held["holdId"], booking_ref: "order-8842")
|
|
53
68
|
```
|
|
54
69
|
|
|
70
|
+
## Event hosting region
|
|
71
|
+
|
|
72
|
+
Pass `region:` to `events.create` based on the **event venue**, not your API server or office. It
|
|
73
|
+
controls the initial placement of the Event's live inventory; an existing Event
|
|
74
|
+
cannot be moved later. Omit it to inherit the workspace default (`western-europe` for new accounts).
|
|
75
|
+
Set that default with `workspaces.create(default_region: ...)` or
|
|
76
|
+
`workspaces.update(workspace_id, default_region: ...)`; changing it affects only future Events.
|
|
77
|
+
|
|
78
|
+
- `western-europe`, `eastern-europe`, `north-america-east`, `north-america-west`, `south-america`
|
|
79
|
+
- `asia-pacific`, `northeast-asia`, `southeast-asia`, `oceania`, `africa`, `middle-east`
|
|
80
|
+
|
|
81
|
+
The hint is best effort, not a data-residency guarantee. See the
|
|
82
|
+
[full Event region guide](https://docs.seatlayer.io/server-api/event-regions/).
|
|
83
|
+
|
|
55
84
|
Nullable event-create fields distinguish omission from an explicit reset: passing, for example,
|
|
56
85
|
`venue: nil` sends JSON `null`; leaving `venue` out sends no field.
|
|
57
86
|
|
|
58
|
-
## Test vs live
|
|
59
|
-
|
|
60
87
|
## Fixed Renewable Seasons
|
|
61
88
|
|
|
62
|
-
|
|
63
|
-
|
|
89
|
+
`client.seasons` exposes all 87 trusted organizer operations: catalogue and
|
|
90
|
+
package lifecycle, performances, prices (each price hash may name a
|
|
91
|
+
`"priceTypeKey"`: `adult`, `child`, `concession`, `senior` or `student`),
|
|
92
|
+
checkout tests, sales windows, buyer access and booking, holder imports,
|
|
93
|
+
renewals with their quotes, intents and checkouts, occurrences, Season orders, reports and CSV exports, and the bulk
|
|
94
|
+
refund of a cancelled date (`refund_season_performance`) and the release of
|
|
95
|
+
lapsed renewal offers (`release_season_lapsed_offers`) with their jobs
|
|
96
|
+
(`list_season_bulk_jobs`, `retrieve_season_bulk_job`).
|
|
97
|
+
|
|
98
|
+
Above 100 contracts, `create_season_renewal_offers` answers `202` with
|
|
99
|
+
`{"job", "batchId"}` instead of the offers: follow the `renewal_run` job with
|
|
100
|
+
`retrieve_season_bulk_job`, then page the offers with `list_season_renewal_offers`
|
|
101
|
+
(`cursor:` and `limit:`, until `nextCursor` is nil). A bring-across of more than 100
|
|
102
|
+
holders (`create_season_holder_import_from_season`) answers `202` with a
|
|
103
|
+
`holder_import` job the same way.
|
|
64
104
|
|
|
65
105
|
After the test hold/book/cancel journey and matching webhook deliveries,
|
|
66
106
|
`validate_season_buyer_rehearsal(season_key)` sends no evidence body; SeatLayer
|
|
67
107
|
discovers the retained chain automatically. Retrieved Season holds contain
|
|
68
|
-
inventory identity, not an authoritative amount
|
|
69
|
-
price, payment, order, tax, refunds,
|
|
108
|
+
inventory identity, not an authoritative amount. When you sell a Season through
|
|
109
|
+
your own checkout, your platform owns package price, payment, order, tax, refunds,
|
|
110
|
+
benefits, and ticket or pass delivery.
|
|
70
111
|
|
|
71
112
|
```ruby
|
|
72
113
|
checked = client.seasons.validate_season(
|
|
@@ -84,6 +125,93 @@ returned operation identity. Buyer-session minting and domain-exact booking,
|
|
|
84
125
|
cancellation, and renewal actions remain single-attempt; only declared
|
|
85
126
|
header-replay catalogue mutations retry automatically.
|
|
86
127
|
|
|
128
|
+
When SeatLayer takes the Season's payments, renew a holder with no hosted page:
|
|
129
|
+
read the price, start a checkout, and send the holder to `payment.url`. For a
|
|
130
|
+
renewal taken by phone or at the box office, `create_season_renewal_intent`
|
|
131
|
+
records the holder's intent before you commit the offer. A decline can carry the
|
|
132
|
+
holder's reason.
|
|
133
|
+
|
|
134
|
+
```ruby
|
|
135
|
+
quote = client.seasons.retrieve_season_renewal_quote(season_key, offer_id)
|
|
136
|
+
if quote["problem"].nil?
|
|
137
|
+
started = client.seasons.create_season_renewal_checkout(
|
|
138
|
+
season_key, offer_id,
|
|
139
|
+
return_url: "https://tickets.yourclub.example/renewed",
|
|
140
|
+
idempotency_key: "renew-#{offer_id}"
|
|
141
|
+
)
|
|
142
|
+
redirect_to started.dig("payment", "url")
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
client.holders.decline_holder_renewal_offer("holder_123", offer_id, reason: "Moving away")
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### Season seats
|
|
149
|
+
|
|
150
|
+
Season seats are the seats a season sets aside on every date before its sales open. A season
|
|
151
|
+
buyer's hold then doesn't have to ask each date, and single-ticket buyers see those seats as
|
|
152
|
+
unavailable. They are off for every season until you pass `inventory_mode: "block"` to
|
|
153
|
+
`create_season` or `update_season`. The season needs one package, and its sales must not have
|
|
154
|
+
opened.
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
client.seasons.update_season(season_key, expected_revision: revision, inventory_mode: "block")
|
|
158
|
+
|
|
159
|
+
job = client.seasons.set_aside_season_block(
|
|
160
|
+
season_key,
|
|
161
|
+
expected_revision: revision,
|
|
162
|
+
keep_for_single_tickets: { "sectionIds" => ["sec_family"] }
|
|
163
|
+
).fetch("job")
|
|
164
|
+
while %w[planning running].include?(job["state"])
|
|
165
|
+
sleep 2
|
|
166
|
+
job = client.seasons.retrieve_season_bulk_job(season_key, job["jobId"]).fetch("job")
|
|
167
|
+
end
|
|
168
|
+
block = client.seasons.retrieve_season_block(season_key).fetch("block")
|
|
169
|
+
|
|
170
|
+
# Release unsold season seats to single sale. Leave out at: to release now.
|
|
171
|
+
client.seasons.release_season_block(
|
|
172
|
+
season_key,
|
|
173
|
+
expected_revision: block["revision"],
|
|
174
|
+
scope: { "all" => true },
|
|
175
|
+
at: "2026-12-01T09:00:00Z"
|
|
176
|
+
)
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
- On an active season, block mode turns on by itself once every date has the seats, and
|
|
180
|
+
`season.block.ready` is sent once. On a draft, `publish_season` turns it on.
|
|
181
|
+
`season.block.released` is sent once a release has run.
|
|
182
|
+
- In block mode, a date cancel (`create_season_amendment`, kind `"cancel"`) or
|
|
183
|
+
`mark_season_performance_past` first takes the date off every season seat. Until that finishes
|
|
184
|
+
the answer is a `202`: check it with `SeatLayer::Seasons.date_leave_pending?(answer)`, then send
|
|
185
|
+
the same request again after `Retry-After`.
|
|
186
|
+
- Refusals come through `SeatLayer::APIError` with `code`, and nothing changes. For example
|
|
187
|
+
`409 season_block_packages` (more than one package), `409 season_block_on_sale` (sales have
|
|
188
|
+
opened), or `409 block_not_ready` from `open_season_sales` before block mode is on.
|
|
189
|
+
- A partner's seat on a date (one a sales channel sells there) stays out of the block. It is
|
|
190
|
+
listed in `block["notFreeOnEveryDate"]` with reason `channel`.
|
|
191
|
+
|
|
192
|
+
The [Season seats docs](https://docs.seatlayer.io/server-api/seasons/#season-seats) list every refusal.
|
|
193
|
+
|
|
194
|
+
## Orders, holders, event sales and event series
|
|
195
|
+
|
|
196
|
+
`client.orders` reads, exports, refunds and cancels the orders SeatLayer took through its own
|
|
197
|
+
checkout; `client.holders` covers Season holders, customers, their tickets and ticket transfers;
|
|
198
|
+
the event sales settings (who takes payment, gateway, booking fee, sales state, discount codes)
|
|
199
|
+
are on `client.events`; and `client.event_series` plans many dates of one show from a schedule.
|
|
200
|
+
The CSV exports return the raw CSV text, not a Hash.
|
|
201
|
+
|
|
202
|
+
```ruby
|
|
203
|
+
page = client.orders.list_event_orders(event_key, status: "confirmed", limit: 50)
|
|
204
|
+
csv = client.orders.export_event_orders_csv(event_key, status: "confirmed") # a String
|
|
205
|
+
client.orders.refund_event_order(event_key, order_id, amount_minor: 1500)
|
|
206
|
+
|
|
207
|
+
client.events.transition_event_sales(event_key, to: "live", expected_revision: 4)
|
|
208
|
+
client.event_series.create_event_series(
|
|
209
|
+
name: "Hamlet", chart_key: chart_key, dry_run: true,
|
|
210
|
+
schedule: { "rule" => "weekly", "from" => "2027-01-04", "count" => 6, "times" => ["19:30"] }
|
|
211
|
+
)
|
|
212
|
+
```
|
|
213
|
+
|
|
214
|
+
## Test vs live
|
|
87
215
|
|
|
88
216
|
Keys carry their own mode. `sk_test_…` keys can only touch test-mode events and `sk_live_…` only
|
|
89
217
|
live ones; crossing them returns `403 mode_mismatch`, surfaced as `AuthError` with `mode_mismatch?`.
|
|
@@ -97,22 +225,28 @@ raise "Refusing to boot production against test-mode seating data." if
|
|
|
97
225
|
A publishable `pk_` key is rejected at construction with a message naming the mistake, rather than
|
|
98
226
|
failing as a `401` three round-trips later.
|
|
99
227
|
|
|
100
|
-
##
|
|
228
|
+
## Book reserved seats from Ruby
|
|
101
229
|
|
|
102
230
|
**Buyer picks seats in the browser.** Your frontend holds them; your backend confirms the price and
|
|
103
|
-
books. Never price from what the browser sent you
|
|
231
|
+
books. Never price from what the browser sent you: `retrieve_hold` is authoritative.
|
|
104
232
|
|
|
105
233
|
```ruby
|
|
106
234
|
hold = client.inventory.retrieve_hold(event_key, hold_id)
|
|
107
|
-
|
|
108
|
-
|
|
235
|
+
currencies = hold["items"].map { |item| item.fetch("currency") }.uniq
|
|
236
|
+
raise "A hold must use one currency" unless currencies.one?
|
|
237
|
+
|
|
238
|
+
currency = currencies.first
|
|
239
|
+
total = hold["items"].sum do |item|
|
|
240
|
+
item.fetch("unitPrice") * item.fetch("quantity", 1)
|
|
241
|
+
end
|
|
242
|
+
# … charge `total` in `currency` …
|
|
109
243
|
client.inventory.book(event_key, hold_id: hold_id, booking_ref: charge.id)
|
|
110
244
|
```
|
|
111
245
|
|
|
112
246
|
**Your backend picks the seats.** Phone orders, box office, comps.
|
|
113
247
|
|
|
114
248
|
```ruby
|
|
115
|
-
# Payment already taken
|
|
249
|
+
# Payment already taken: book outright, so nothing is stranded if a second call fails.
|
|
116
250
|
client.inventory.book_best_available(event_key, qty: 2, booking_ref: "phone-1183")
|
|
117
251
|
|
|
118
252
|
# Or name the seats yourself.
|
|
@@ -143,7 +277,7 @@ pause/archive controls, audit-safe session listing, and channel reports are on
|
|
|
143
277
|
## Listing and pagination
|
|
144
278
|
|
|
145
279
|
`list` returns one page plus a `nextCursor`. `list_all` pages for you and returns a lazy
|
|
146
|
-
`Enumerator` when no block is given
|
|
280
|
+
`Enumerator` when no block is given, because the point of paginating is to *not* hold an unbounded result
|
|
147
281
|
set in memory, so `.lazy.first(n)` stops fetching once it has enough.
|
|
148
282
|
|
|
149
283
|
```ruby
|
|
@@ -157,28 +291,30 @@ client.events.list_all do |event|
|
|
|
157
291
|
sync(event)
|
|
158
292
|
end
|
|
159
293
|
|
|
160
|
-
# Lazily
|
|
294
|
+
# Lazily: this fetches one page, not all of them.
|
|
161
295
|
client.charts.list_all.lazy.first(5)
|
|
162
296
|
```
|
|
163
297
|
|
|
164
298
|
Listing events includes live availability `counts` by default, which costs the server one
|
|
165
|
-
round-trip **per event**. `list_all` turns them off automatically
|
|
166
|
-
exactly when you don't want that
|
|
299
|
+
round-trip **per event**. `list_all` turns them off automatically, since walking a whole catalogue is
|
|
300
|
+
exactly when you don't want that, and you can control it explicitly:
|
|
167
301
|
|
|
168
302
|
```ruby
|
|
169
303
|
client.events.list(limit: 50, counts: false)
|
|
170
304
|
```
|
|
171
305
|
|
|
306
|
+
Pass `series_key: "ser_1"` to list only the dates of one event series.
|
|
307
|
+
|
|
172
308
|
## Keeping a hold alive
|
|
173
309
|
|
|
174
|
-
When an order takes longer than the checkout window
|
|
310
|
+
When an order takes longer than the checkout window (an invoice, a phone sale), extend rather than
|
|
175
311
|
release and re-hold. Releasing first hands the seats to whoever is racing for them in between.
|
|
176
312
|
|
|
177
313
|
```ruby
|
|
178
314
|
begin
|
|
179
315
|
client.inventory.extend_hold(event_key, hold_id, ttl_ms: 10 * 60_000)
|
|
180
316
|
rescue SeatLayer::ConflictError
|
|
181
|
-
# Gone, expired, or at its renewal cap
|
|
317
|
+
# Gone, expired, or at its renewal cap: the buyer has to re-pick.
|
|
182
318
|
end
|
|
183
319
|
```
|
|
184
320
|
|
|
@@ -205,19 +341,19 @@ The full set, all opt-in:
|
|
|
205
341
|
|---|---|
|
|
206
342
|
| `event:view` | Read the seat map and its live states |
|
|
207
343
|
| `event:block` | Block and unblock seats |
|
|
208
|
-
| `event:cancel` |
|
|
344
|
+
| `event:cancel` | Cancel a Platform/SDK booking by reference and return its inventory to sale; does not move gateway money |
|
|
209
345
|
| `event:reports` | Read sales and availability reports |
|
|
210
346
|
| `event:channels:view` | Read sales channels and their allocations |
|
|
211
347
|
| `event:channels:manage` | Create, pause and archive channels; rotate access links |
|
|
212
348
|
| `event:orders:read` | Read SeatLayer-managed orders |
|
|
213
|
-
| `event:refund` | Refund
|
|
349
|
+
| `event:refund` | Refund an eligible Managed Ticketing order through its connected gateway |
|
|
214
350
|
| `event:tickets:send` | Send SeatLayer-managed tickets |
|
|
215
351
|
| `event:door:view` | Read the door list |
|
|
216
352
|
| `event:door:checkin` | Check tickets in and out |
|
|
217
353
|
| `event:boxoffice` | Use the managed box-office surface |
|
|
218
354
|
|
|
219
|
-
The two `event:channels:*` capabilities are **not** in the default
|
|
220
|
-
channels existed must not silently acquire channel authority
|
|
355
|
+
The two `event:channels:*` capabilities are **not** in the default (a token minted before sales
|
|
356
|
+
channels existed must not silently acquire channel authority), so ask for them explicitly if the
|
|
221
357
|
page manages channels.
|
|
222
358
|
|
|
223
359
|
Designer minting returns the API envelope unchanged: read the token and effective safe-mode and
|
|
@@ -227,7 +363,7 @@ feature policy under `result["session"]`. Pass `safe_mode_options` only with `mo
|
|
|
227
363
|
|
|
228
364
|
Subscription responses use the wire envelopes exactly: `list` returns `{"subs" => [...]}`,
|
|
229
365
|
`create` returns `{"sub" => ..., "secret" => ...}` (the secret is shown once), and `update`
|
|
230
|
-
returns `{"sub" => ...}`. `SeatLayer::Webhooks::EVENT_NAMES` is the exact
|
|
366
|
+
returns `{"sub" => ...}`. `SeatLayer::Webhooks::EVENT_NAMES` is the exact 56-name public event set;
|
|
231
367
|
delivery history accepts `limit`, `status` (`"ok"` or `"failed"`), and `before`.
|
|
232
368
|
|
|
233
369
|
Verify every delivery against the **raw** body. Re-encoding a parsed Hash changes the bytes and
|
|
@@ -246,7 +382,7 @@ class WebhooksController < ApplicationController
|
|
|
246
382
|
)
|
|
247
383
|
|
|
248
384
|
# The signed body carries `at`, but nothing enforces a freshness window, so a
|
|
249
|
-
# captured delivery stays valid indefinitely. Deduplicate on occurrenceId
|
|
385
|
+
# captured delivery stays valid indefinitely. Deduplicate on occurrenceId:
|
|
250
386
|
# this is your replay protection, not an optimisation.
|
|
251
387
|
return head :ok if already_processed?(event["occurrenceId"])
|
|
252
388
|
|
|
@@ -281,25 +417,41 @@ end
|
|
|
281
417
|
| `ConflictError` | 409 | Inventory moved, or a guard rejected the change |
|
|
282
418
|
| `ValidationError` | 422 | Understood and rejected |
|
|
283
419
|
| `RateLimitError` | 429 | Over budget; carries `retry_after` |
|
|
284
|
-
| `ConnectionError` |
|
|
420
|
+
| `ConnectionError` | none | No answer: DNS, TLS, socket, timeout |
|
|
285
421
|
|
|
286
422
|
All descend from `SeatLayer::Error`, so `rescue SeatLayer::Error` catches everything. Every API
|
|
287
|
-
error carries `status`, `code`, `body` and `request_id
|
|
423
|
+
error carries `status`, `code`, `body` and `request_id`. Quote the request id in support requests.
|
|
288
424
|
|
|
289
425
|
## Reliability
|
|
290
426
|
|
|
291
427
|
**Retries.** Reads (`GET`/`HEAD`) retry 429, 408 and 5xx with exponential backoff and full jitter;
|
|
292
|
-
`Retry-After` wins when the server sends it.
|
|
293
|
-
|
|
294
|
-
`
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
428
|
+
`Retry-After` wins when the server sends it. Forty-one mutations use exact header replay:
|
|
429
|
+
|
|
430
|
+
- `charts.create`, `charts.copy`, `templates.instantiate_template`, `events.create`,
|
|
431
|
+
`workspaces.create`, and `performance_groups.create`;
|
|
432
|
+
- 26 on `seasons`: `create_season`, `update_season`, `delete_season`, `create_season_plan`,
|
|
433
|
+
`update_season_plan`, `delete_season_plan`, `publish_season`, `duplicate_season_to_live`,
|
|
434
|
+
`create_season_holder_import`, `create_season_holder_import_from_season`,
|
|
435
|
+
`create_season_renewal_offers`, `create_season_amendment`, `create_season_performances`,
|
|
436
|
+
`attach_season_performances`, `delete_season_performance`, `create_season_checkout_test`,
|
|
437
|
+
`replace_season_package_prices`, `attach_season_plan_performance`,
|
|
438
|
+
`delete_season_plan_performance`, `mark_season_performance_past`, `refund_season_order`,
|
|
439
|
+
`cancel_season_order`, `cancel_season_order_date`, `refund_season_performance`,
|
|
440
|
+
`release_season_lapsed_offers`, and `create_season_renewal_checkout`;
|
|
441
|
+
- `holders.update_holder`, `holders.create_holder_transfer`, `holders.create_holder_return`, and
|
|
442
|
+
`holders.create_holder_renewal_checkout`;
|
|
443
|
+
- `orders.refund_event_order` and `orders.cancel_event_order`;
|
|
444
|
+
- `event_series.create_event_series`, `event_series.add_event_series_dates`, and
|
|
445
|
+
`event_series.update_event_series`.
|
|
446
|
+
|
|
447
|
+
Other 4xx responses are never retried.
|
|
448
|
+
|
|
449
|
+
**Idempotency.** Those 41 replay-backed operations carry an `Idempotency-Key`, generated when you
|
|
450
|
+
do not supply one and reused across attempts. All remaining SDK mutations are single-attempt. Some
|
|
451
|
+
have a server-side domain idempotency contract, but the SDK does not retry them automatically. This
|
|
452
|
+
includes inventory holds and bookings, show-once credential or secret creation, unsupported
|
|
453
|
+
operations, and raw `request` mutations. Keep `booking_ref` in the booking body for reconciliation,
|
|
454
|
+
but handle an unknown network outcome explicitly instead of automatically repeating the sale.
|
|
303
455
|
|
|
304
456
|
```ruby
|
|
305
457
|
client.events.create(chart_id: chart_id, idempotency_key: "provision-event-#{event_id}")
|
|
@@ -324,41 +476,47 @@ client.request("POST", "/v1/events/ev_1/some-new-route", body: { "qty" => 2 })
|
|
|
324
476
|
|
|
325
477
|
## API surface
|
|
326
478
|
|
|
479
|
+
The client exposes these resources. Performance Groups cover runs, sessions, holds, and bookings;
|
|
480
|
+
Seasons cover catalogue, plan, sales, buyer-session, booking, renewal, occurrence, reporting,
|
|
481
|
+
outbox, and support operations.
|
|
482
|
+
|
|
327
483
|
| Resource | Methods |
|
|
328
484
|
| --- | --- |
|
|
329
485
|
| `charts` | `list` `list_all` `create` `retrieve` `update` `delete` `copy` `archive` `unarchive` `publish` |
|
|
330
486
|
| `templates` | `instantiate_template` |
|
|
331
|
-
| `events` | `list` `list_all` `create` `retrieve` `retrieve_configuration_binding` `update_configuration_binding` `update` `delete` `update_poster` `delete_poster` `update_chart` `close` `reopen` `archive` `retrieve_hold_ttl` `update_hold_ttl` `list_ticket_releases` `update_ticket_releases` `close_ticket_release` `retrieve_report` `retrieve_log` |
|
|
487
|
+
| `events` | `list` `list_all` `create` `retrieve` `retrieve_configuration_binding` `update_configuration_binding` `update` `delete` `update_poster` `delete_poster` `update_chart` `close` `reopen` `archive` `retrieve_hold_ttl` `update_hold_ttl` `list_ticket_releases` `update_ticket_releases` `close_ticket_release` `retrieve_report` `retrieve_log` `retrieve_event_buyer_checkout` `replace_event_buyer_checkout` `retrieve_event_payment_gateway` `replace_event_payment_gateway` `retrieve_event_sales_readiness` `preflight_event_sales` `transition_event_sales` `retrieve_event_booking_fee` `replace_event_booking_fee` `list_event_discount_codes` `create_event_discount_code` `update_event_discount_code` `end_event_discount_code` `delete_event_discount_code` |
|
|
332
488
|
| `inventory` | `hold` `hold_best_available` `book_best_available` `extend_hold` `retrieve_hold` `release` `book` `box_office_book` `unbook` `list_bookings` `retrieve_booking` `block` `unblock` `unblock_all` `retrieve_availability` `update_availability` |
|
|
333
489
|
| `channels` | `list_channels` `create_channel` `update_channel` `update_assignments` `list_allocation` `retrieve_access_preview` `retrieve_report` `pause` `unpause` `archive` `create_buyer_access_session` `list_buyer_access_sessions` `revoke_buyer_access_session` `create_access_link` `list_access_links` `rotate_access_link` `revoke_access_link` |
|
|
334
490
|
| `sessions` | `create_manage_session` `revoke_manage_session` `create_designer_session` `revoke_designer_session` |
|
|
335
491
|
| `webhooks` | `list` `create` `update` `delete` `list_deliveries` |
|
|
336
492
|
| `workspaces` | `list` `create` `retrieve` `update` |
|
|
493
|
+
| `performance_groups` | `list` `create` `retrieve` `delete` `activate` `close` `retrieve_lifecycle` `create_buyer_access_session` `list_buyer_access_sessions` `revoke_buyer_access_session` `retrieve_hold` `book_hold` `retrieve_booking` |
|
|
494
|
+
| `seasons` | 87 operations for catalogue and Plan lifecycle, performances, package prices, checkout tests, sales windows, buyer access and booking, holder imports, holders, renewals, renewal quotes and checkouts, occurrence amendments, returns and reclaims, Season orders, bulk refunds, lapsed-offer releases and jobs, reports and CSV exports, audit, outbox, and support export |
|
|
495
|
+
| `holders` | `list_customers` `retrieve_holder` `update_holder` `revoke_holder_sessions` `list_holder_seasons` `list_holder_tickets` `retrieve_holder_ticket` `create_holder_transfer` `list_holder_transfers` `cancel_holder_transfer` `accept_holder_transfer` `create_holder_return` `create_holder_renewal_checkout` `decline_holder_renewal_offer` |
|
|
496
|
+
| `orders` | `list_orders` `retrieve_order` `list_order_tickets` `list_event_orders` `retrieve_event_order` `export_event_orders_csv` `refund_event_order` `cancel_event_order` `resend_event_order_tickets` |
|
|
497
|
+
| `event_series` | `create_event_series` `list_event_series` `retrieve_event_series` `add_event_series_dates` `update_event_series` |
|
|
337
498
|
|
|
338
|
-
Full reference: [
|
|
499
|
+
Full reference: [SeatLayer Ruby server SDK guide](https://docs.seatlayer.io/server-sdk/ruby/)
|
|
339
500
|
|
|
340
501
|
### Deliberately not in this SDK
|
|
341
502
|
|
|
342
503
|
Some API surface is intentionally unwrapped, not merely pending:
|
|
343
504
|
|
|
344
|
-
- **
|
|
345
|
-
|
|
346
|
-
you run your own commerce store you refund in that store, through your own gateway.
|
|
347
|
-
- **Connecting or assigning payment gateways.** Connecting one is a dashboard flow, so shipping only
|
|
348
|
-
the assignment half across seven SDKs would hand you a method that cannot yet succeed.
|
|
505
|
+
- **Connecting a payment gateway.** Connecting one is a dashboard flow. Once it is connected,
|
|
506
|
+
`events.replace_event_payment_gateway` assigns it to an event.
|
|
349
507
|
- **Realtime seat updates.** Live seat state reaches the *browser* through the widget's own socket.
|
|
350
508
|
There is no server-side subscribe; a secret-key caller gets authoritative state from
|
|
351
509
|
`events.retrieve_report` and `inventory.retrieve_availability`.
|
|
352
510
|
|
|
353
|
-
|
|
354
|
-
|
|
511
|
+
Neither is reachable through `request` as a supported path either; both are excluded from the
|
|
512
|
+
public manifest, not just from the wrapper.
|
|
355
513
|
|
|
356
514
|
## Frequently asked questions
|
|
357
515
|
|
|
358
516
|
### How do I book seats from Ruby?
|
|
359
517
|
|
|
360
|
-
Create a client with your secret key, obtain a hold id
|
|
361
|
-
browser session or by holding server-side
|
|
518
|
+
Create a client with your secret key, obtain a hold id (either from the buyer's
|
|
519
|
+
browser session or by holding server-side) and call `inventory.book(event_key, hold_id: ..., booking_ref: ...)`.
|
|
362
520
|
`booking_ref` is your own stable order id and is the join between SeatLayer
|
|
363
521
|
inventory and your commercial order, so the same reference identifies the booking
|
|
364
522
|
in Booking History and when you later cancel it. For phone orders, box office, and
|
|
@@ -370,7 +528,7 @@ The buyer SDK runs where the ticket buyer is: it renders the interactive seating
|
|
|
370
528
|
chart, handles seat selection, and creates temporary holds. This server SDK is the
|
|
371
529
|
trusted side. It authenticates with your secret key, inspects what a hold actually
|
|
372
530
|
contains, prices from server-owned data, and books. Never bundle the secret key
|
|
373
|
-
into a browser or a mobile app
|
|
531
|
+
into a browser or a mobile app. Browser surfaces get short-lived, origin-bound
|
|
374
532
|
tokens that you mint here.
|
|
375
533
|
|
|
376
534
|
### How do temporary holds work server-side?
|
|
@@ -381,21 +539,21 @@ and at what price, so charge from its `items` rather than from anything the brow
|
|
|
381
539
|
sent you. When an order runs longer than the checkout window, `inventory.extend_hold`
|
|
382
540
|
renews the hold instead of releasing and re-holding, which would hand the seats to
|
|
383
541
|
whoever is racing for them. Bookings carry the server's exact-selection plus
|
|
384
|
-
`booking_ref` safeguard, but the SDK sends each booking once
|
|
542
|
+
`booking_ref` safeguard, but the SDK sends each booking once; reconcile an unknown
|
|
385
543
|
outcome before trying again.
|
|
386
544
|
|
|
387
545
|
### Can I use my own payment provider?
|
|
388
546
|
|
|
389
|
-
Yes.
|
|
390
|
-
the
|
|
391
|
-
money through whichever provider you already use
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
547
|
+
Yes. This server SDK does not process payment in a Platform/SDK integration. Inspect the hold,
|
|
548
|
+
compute the charge from each returned item's authoritative `unitPrice`, `quantity`, and `currency`,
|
|
549
|
+
take the money through whichever provider you already use, and then book the hold with your order
|
|
550
|
+
id as `booking_ref`. SeatLayer owns seating state, holds, booking concurrency, and the inventory
|
|
551
|
+
ledger in this integration; your platform owns payments, commercial orders, tickets, delivery,
|
|
552
|
+
and refunds. Managed Ticketing is a separate product path with organizer-connected payments.
|
|
395
553
|
|
|
396
554
|
## Continue your Ruby integration
|
|
397
555
|
|
|
398
|
-
- [Follow the
|
|
556
|
+
- [Follow the Ruby server SDK guide](https://docs.seatlayer.io/server-sdk/ruby/)
|
|
399
557
|
for installation, authentication, and the full hold-to-booking flow.
|
|
400
558
|
- [Handle errors, retries, and safe booking repeats](https://docs.seatlayer.io/server-sdk/reliability/)
|
|
401
559
|
before connecting a production order flow.
|