seatlayer 0.7.0 → 0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a65cc789351eb854341bf167fdedbd818035c29639e212df5312597f46e3f23c
4
- data.tar.gz: 0fc4163ccaedd380264fbc53853c71c476f4bbfdb4f99180663f66c434d94b66
3
+ metadata.gz: 841def7560b63781babf931924a29204c724ba5b4513eff1de283a370cb22699
4
+ data.tar.gz: 7027c38a8aeeee9aa5c339d90d04b33c4d708e47362687951d30dc280402bef4
5
5
  SHA512:
6
- metadata.gz: dc59365e346c8a14cf52014ca804f70aa63eabee928214906913c060bf58f4c2edb0dbb3eaec90305096b396bf2257468f444b5d8ba3f154e78f57625b125126
7
- data.tar.gz: c74c63603bd068dd4566b18b1c940762de0258fbcf186b083608c263ce10ffa8a9934db69a135cdbb0d3c225a4334ecfb15a6f672e6e853c745a11d3cc9eab44
6
+ metadata.gz: 2ef313b0db74c36aef5d0f512264586448d99330e1257c8a28b835da54a7b539fd3f9ab7183a91ae912cf1e55ec2c5dcede861260d80b77a4ed0eaaa6d1faaba
7
+ data.tar.gz: e85e01d772d6639a36d7fa7154a062d60564fbbe750258c1080fa4f7cc17eba25277ebc0e7e31ad0f04589b9d716402c486c6c325b3d202e7c7fd2ed9e96c903
data/README.md CHANGED
@@ -5,13 +5,13 @@
5
5
  [![Ruby](https://img.shields.io/badge/Ruby-%E2%89%A53.0-CC342D.svg)](https://www.ruby-lang.org/)
6
6
  [![License: MIT](https://img.shields.io/badge/license-MIT-111827.svg)](LICENSE)
7
7
 
8
- The official SeatLayer Ruby server SDK the trusted side of a reserved-seating
9
- integration. Inspect what a hold really contains, price from server-owned seating-chart
10
- data, and book with a stable `booking_ref`, while managing charts, events, inventory,
11
- allocations, and webhooks through one typed ticketing API client.
8
+ SeatLayer's official Ruby server SDK is the trusted side of its reserved seating and seat booking
9
+ API. Inspect what a hold really contains, price from server-owned seating-chart data, and book
10
+ with a stable `booking_ref`, while managing charts, events, inventory, allocations, and webhooks
11
+ through one typed ticketing API client.
12
12
 
13
13
  [`seatlayer` gem on RubyGems](https://rubygems.org/gems/seatlayer) ·
14
- [SeatLayer server SDK documentation](https://docs.seatlayer.io/server-sdk/install/) ·
14
+ [Ruby server SDK guide](https://docs.seatlayer.io/server-sdk/ruby/) ·
15
15
  [SeatLayer developer platform](https://seatlayer.io/developers/) ·
16
16
  [SeatLayer JavaScript seat map SDK](https://www.npmjs.com/package/@seatlayer/js) ·
17
17
  [Server API reference](https://docs.seatlayer.io/server-api/events/)
@@ -19,7 +19,7 @@ allocations, and webhooks through one typed ticketing API client.
19
19
  > **Server-side only.** This gem authenticates with your secret key. Never load it anywhere a
20
20
  > ticket buyer can reach — browser surfaces get short-lived, origin-bound tokens that you mint here.
21
21
 
22
- ## Install
22
+ ## Install the Ruby seat booking SDK
23
23
 
24
24
  ```ruby
25
25
  gem "seatlayer"
@@ -40,11 +40,16 @@ require "seatlayer"
40
40
  client = SeatLayer::Client.new(ENV.fetch("SEATLAYER_SECRET_KEY"))
41
41
 
42
42
  # 1. Provision a venue for a new organiser from a public template.
43
- chart = client.templates.instantiate_template("arena-standard")["meta"]
43
+ # Replace this placeholder with a template id from your catalog.
44
+ chart = client.templates.instantiate_template("your-published-template")["meta"]
44
45
  client.charts.publish(chart["id"])
45
46
 
46
47
  # 2. Create an event on it.
47
- event = client.events.create(chart_id: chart["id"], name: "Spring Gala")["meta"]
48
+ event = client.events.create(
49
+ chart_id: chart["id"], name: "Spring Gala",
50
+ currency: "EUR", # omit to inherit the workspace currency
51
+ region: "western-europe" # India: "asia-pacific"
52
+ )["meta"]
48
53
 
49
54
  # 3. Sell four seats over the phone.
50
55
  held = client.inventory.hold_best_available(event["key"], qty: 4)
@@ -52,11 +57,23 @@ held = client.inventory.hold_best_available(event["key"], qty: 4)
52
57
  client.inventory.book(event["key"], hold_id: held["holdId"], booking_ref: "order-8842")
53
58
  ```
54
59
 
60
+ ## Event hosting region
61
+
62
+ Pass `region:` to `events.create` based on the **event venue**, not your API server or office. It
63
+ controls the initial placement of the Event's live inventory; an existing Event
64
+ cannot be moved later. Omit it to inherit the workspace default (`western-europe` for new accounts).
65
+ Set that default with `workspaces.create(default_region: ...)` or
66
+ `workspaces.update(workspace_id, default_region: ...)`; changing it affects only future Events.
67
+
68
+ - `western-europe`, `eastern-europe`, `north-america-east`, `north-america-west`, `south-america`
69
+ - `asia-pacific`, `northeast-asia`, `southeast-asia`, `oceania`, `africa`, `middle-east`
70
+
71
+ The hint is best effort, not a data-residency guarantee. See the
72
+ [full Event region guide](https://docs.seatlayer.io/server-api/event-regions/).
73
+
55
74
  Nullable event-create fields distinguish omission from an explicit reset: passing, for example,
56
75
  `venue: nil` sends JSON `null`; leaving `venue` out sends no field.
57
76
 
58
- ## Test vs live
59
-
60
77
  ## Fixed Renewable Seasons
61
78
 
62
79
  Version `0.7.0` exposes all 48 trusted organizer operations through
@@ -84,6 +101,7 @@ returned operation identity. Buyer-session minting and domain-exact booking,
84
101
  cancellation, and renewal actions remain single-attempt; only declared
85
102
  header-replay catalogue mutations retry automatically.
86
103
 
104
+ ## Test vs live
87
105
 
88
106
  Keys carry their own mode. `sk_test_…` keys can only touch test-mode events and `sk_live_…` only
89
107
  live ones; crossing them returns `403 mode_mismatch`, surfaced as `AuthError` with `mode_mismatch?`.
@@ -97,15 +115,21 @@ raise "Refusing to boot production against test-mode seating data." if
97
115
  A publishable `pk_` key is rejected at construction with a message naming the mistake, rather than
98
116
  failing as a `401` three round-trips later.
99
117
 
100
- ## The two selling flows
118
+ ## Book reserved seats from Ruby
101
119
 
102
120
  **Buyer picks seats in the browser.** Your frontend holds them; your backend confirms the price and
103
121
  books. Never price from what the browser sent you — `retrieve_hold` is authoritative.
104
122
 
105
123
  ```ruby
106
124
  hold = client.inventory.retrieve_hold(event_key, hold_id)
107
- total = hold["items"].sum { |item| item["unitPrice"] }
108
- # charge `total` in hold["currency"]
125
+ currencies = hold["items"].map { |item| item.fetch("currency") }.uniq
126
+ raise "A hold must use one currency" unless currencies.one?
127
+
128
+ currency = currencies.first
129
+ total = hold["items"].sum do |item|
130
+ item.fetch("unitPrice") * item.fetch("quantity", 1)
131
+ end
132
+ # … charge `total` in `currency` …
109
133
  client.inventory.book(event_key, hold_id: hold_id, booking_ref: charge.id)
110
134
  ```
111
135
 
@@ -205,12 +229,12 @@ The full set, all opt-in:
205
229
  |---|---|
206
230
  | `event:view` | Read the seat map and its live states |
207
231
  | `event:block` | Block and unblock seats |
208
- | `event:cancel` | Unbook paid seats and issue gateway refunds destructive, moves money |
232
+ | `event:cancel` | Cancel a Platform/SDK booking by reference and return its inventory to sale; does not move gateway money |
209
233
  | `event:reports` | Read sales and availability reports |
210
234
  | `event:channels:view` | Read sales channels and their allocations |
211
235
  | `event:channels:manage` | Create, pause and archive channels; rotate access links |
212
236
  | `event:orders:read` | Read SeatLayer-managed orders |
213
- | `event:refund` | Refund a SeatLayer-managed order |
237
+ | `event:refund` | Refund an eligible Managed Ticketing order through its connected gateway |
214
238
  | `event:tickets:send` | Send SeatLayer-managed tickets |
215
239
  | `event:door:view` | Read the door list |
216
240
  | `event:door:checkin` | Check tickets in and out |
@@ -289,17 +313,20 @@ error carries `status`, `code`, `body` and `request_id` — quote the request id
289
313
  ## Reliability
290
314
 
291
315
  **Retries.** Reads (`GET`/`HEAD`) retry 429, 408 and 5xx with exponential backoff and full jitter;
292
- `Retry-After` wins when the server sends it. Automatic mutation retries are limited to the five
293
- operations backed by exact response replay: `charts.create`, `charts.copy`,
294
- `templates.instantiate_template`, `events.create`, and `workspaces.create`. Other 4xx responses
316
+ `Retry-After` wins when the server sends it. Fourteen mutations use exact header replay:
317
+ `charts.create`, `charts.copy`, `templates.instantiate_template`, `events.create`,
318
+ `workspaces.create`, `performance_groups.create`, `seasons.create_season`,
319
+ `seasons.update_season`, `seasons.delete_season`, `seasons.create_season_plan`,
320
+ `seasons.duplicate_season_to_live`, `seasons.create_season_holder_import`,
321
+ `seasons.create_season_renewal_offers`, and `seasons.create_season_amendment`. Other 4xx responses
295
322
  are never retried.
296
323
 
297
- **Idempotency.** Those five replay-backed operations carry an `Idempotency-Key`, generated when you
298
- do not supply one and reused across attempts. Other mutations are single-attempt and receive no
299
- automatic key. A caller-supplied key is forwarded but does not enable retries. This includes
300
- inventory holds and bookings, show-once credential or secret creation, unsupported operations, and
301
- raw `request` mutations. Keep `booking_ref` in the booking body for reconciliation, but handle an
302
- unknown network outcome explicitly instead of automatically repeating the sale.
324
+ **Idempotency.** Those 14 replay-backed operations carry an `Idempotency-Key`, generated when you
325
+ do not supply one and reused across attempts. All remaining SDK mutations are single-attempt. Some
326
+ have a server-side domain idempotency contract, but the SDK does not retry them automatically. This
327
+ includes inventory holds and bookings, show-once credential or secret creation, unsupported
328
+ operations, and raw `request` mutations. Keep `booking_ref` in the booking body for reconciliation,
329
+ but handle an unknown network outcome explicitly instead of automatically repeating the sale.
303
330
 
304
331
  ```ruby
305
332
  client.events.create(chart_id: chart_id, idempotency_key: "provision-event-#{event_id}")
@@ -324,6 +351,10 @@ client.request("POST", "/v1/events/ev_1/some-new-route", body: { "qty" => 2 })
324
351
 
325
352
  ## API surface
326
353
 
354
+ The client exposes these resources. Performance Groups cover runs, sessions, holds, and bookings;
355
+ Seasons cover catalogue, plan, sales, buyer-session, booking, renewal, occurrence, reporting,
356
+ outbox, and support operations.
357
+
327
358
  | Resource | Methods |
328
359
  | --- | --- |
329
360
  | `charts` | `list` `list_all` `create` `retrieve` `update` `delete` `copy` `archive` `unarchive` `publish` |
@@ -334,8 +365,10 @@ client.request("POST", "/v1/events/ev_1/some-new-route", body: { "qty" => 2 })
334
365
  | `sessions` | `create_manage_session` `revoke_manage_session` `create_designer_session` `revoke_designer_session` |
335
366
  | `webhooks` | `list` `create` `update` `delete` `list_deliveries` |
336
367
  | `workspaces` | `list` `create` `retrieve` `update` |
368
+ | `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` |
369
+ | `seasons` | 48 operations for catalogue and Plan lifecycle, sales windows, buyer access and booking, holder imports, renewals, occurrence amendments, reports, audit, outbox, and support export |
337
370
 
338
- Full reference: [docs.seatlayer.io/server-sdk](https://docs.seatlayer.io/server-sdk/install/)
371
+ Full reference: [SeatLayer Ruby server SDK guide](https://docs.seatlayer.io/server-sdk/ruby/)
339
372
 
340
373
  ### Deliberately not in this SDK
341
374
 
@@ -386,16 +419,16 @@ outcome before trying again.
386
419
 
387
420
  ### Can I use my own payment provider?
388
421
 
389
- Yes. SeatLayer never processes payment. Inspect the hold, compute the charge from
390
- the returned `items` and their authoritative `unitPrice` and `currency`, take the
391
- money through whichever provider you already use Stripe, Adyen, Razorpay, or your
392
- own and then book the hold with your order id as `booking_ref`. SeatLayer owns
393
- seating state, holds, booking concurrency, and the inventory ledger; your platform
394
- owns payments, commercial orders, tickets, delivery, and refunds.
422
+ Yes. This server SDK does not process payment in a Platform/SDK integration. Inspect the hold,
423
+ compute the charge from each returned item's authoritative `unitPrice`, `quantity`, and `currency`,
424
+ take the money through whichever provider you already use, and then book the hold with your order
425
+ id as `booking_ref`. SeatLayer owns seating state, holds, booking concurrency, and the inventory
426
+ ledger in this integration; your platform owns payments, commercial orders, tickets, delivery,
427
+ and refunds. Managed Ticketing is a separate product path with organizer-connected payments.
395
428
 
396
429
  ## Continue your Ruby integration
397
430
 
398
- - [Follow the SeatLayer server SDK guide](https://docs.seatlayer.io/server-sdk/install/)
431
+ - [Follow the Ruby server SDK guide](https://docs.seatlayer.io/server-sdk/ruby/)
399
432
  for installation, authentication, and the full hold-to-booking flow.
400
433
  - [Handle errors, retries, and safe booking repeats](https://docs.seatlayer.io/server-sdk/reliability/)
401
434
  before connecting a production order flow.
@@ -252,9 +252,14 @@ module SeatLayer
252
252
  @client.get("/v1/workspaces")
253
253
  end
254
254
 
255
- def create(name:, external_ref: UNSET, idempotency_key: nil)
255
+ def create(name:, external_ref: UNSET, default_region: nil, idempotency_key: nil)
256
+ unless default_region.nil? || EVENT_HOSTING_REGIONS.include?(default_region)
257
+ raise ArgumentError, "default_region must be a supported SeatLayer Event region"
258
+ end
259
+
256
260
  body = { "name" => name }
257
261
  body.merge!(supplied({ "externalRef" => external_ref }))
262
+ body["defaultRegion"] = default_region unless default_region.nil?
258
263
  @client.post(
259
264
  "/v1/workspaces", body, idempotency_key: idempotency_key, retry_policy: :header_replay
260
265
  )
@@ -269,6 +274,13 @@ module SeatLayer
269
274
  # The organisation's default workspace cannot be disabled — the API answers
270
275
  # 409 +default_workspace_required+. Promote another one first.
271
276
  def update(workspace_id, fields)
277
+ region = fields[:default_region] || fields["defaultRegion"]
278
+ unless region.nil? || EVENT_HOSTING_REGIONS.include?(region)
279
+ raise ArgumentError, "default_region must be a supported SeatLayer Event region"
280
+ end
281
+
282
+ fields = fields.dup
283
+ fields["defaultRegion"] = fields.delete(:default_region) if fields.key?(:default_region)
272
284
  @client.patch("/v1/workspaces/#{encode(workspace_id)}", fields)
273
285
  end
274
286
  end
@@ -1,6 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SeatLayer
4
+ EVENT_HOSTING_REGIONS = %w[
5
+ western-europe eastern-europe north-america-east north-america-west
6
+ south-america asia-pacific northeast-asia southeast-asia oceania africa middle-east
7
+ ].freeze
8
+
4
9
  # Shared plumbing for the resource namespaces.
5
10
  class Resource
6
11
  UNSET = Object.new.freeze
@@ -179,8 +184,13 @@ module SeatLayer
179
184
  def create(chart_id:, name: nil, slug: nil, starts_at: UNSET, venue: UNSET,
180
185
  external_ref: UNSET, currency: UNSET, idempotency_key: nil,
181
186
  description: UNSET, ends_at: UNSET, timezone: UNSET, locale: UNSET,
182
- poster_asset_id: UNSET, mode: nil)
183
- body = compact({ "chartId" => chart_id, "name" => name, "slug" => slug, "mode" => mode })
187
+ poster_asset_id: UNSET, mode: nil, region: nil)
188
+ unless region.nil? || EVENT_HOSTING_REGIONS.include?(region)
189
+ raise ArgumentError, "region must be a supported SeatLayer Event region"
190
+ end
191
+
192
+ body = compact({ "chartId" => chart_id, "name" => name, "slug" => slug,
193
+ "mode" => mode, "region" => region })
184
194
  body.merge!(supplied({ "startsAt" => starts_at, "venue" => venue,
185
195
  "externalRef" => external_ref, "currency" => currency,
186
196
  "description" => description, "endsAt" => ends_at,
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SeatLayer
4
- VERSION = "0.7.0"
4
+ VERSION = "0.8.0"
5
5
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: seatlayer
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.7.0
4
+ version: 0.8.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - SeatLayer