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.
@@ -0,0 +1,78 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SeatLayer
4
+ # Event series: many dates of one show, planned from a schedule and run as
5
+ # one. A series is addressed by the key of its first event.
6
+ class EventSeries < Resource
7
+ # Plan an event series from a +schedule+ Hash, or create it as a job.
8
+ # Pass <tt>dry_run: true</tt> to get the dates back without creating any.
9
+ # Every date carries +name+ as its name.
10
+ #
11
+ # Give +name+ and +chart_key+, or +from_event_key+: an event you already set
12
+ # up becomes the first date under its own key, and every other date is a copy
13
+ # of it (floor plan, prices, checkout, poster and its own seat blocks, none of
14
+ # its orders, holds or sales). The series takes the event's name, zone and
15
+ # floor plan.
16
+ #
17
+ # A series from an event that never started and was given up answers 409
18
+ # create_abandoned for that key every time: send it again with a new
19
+ # +idempotency_key+.
20
+ def create_event_series( # rubocop:disable Metrics/ParameterLists
21
+ schedule:, name: nil, chart_key: nil, timezone: nil, venue: nil,
22
+ duration_minutes: nil, dry_run: nil, idempotency_key: nil, from_event_key: nil
23
+ )
24
+ if from_event_key.nil? && (name.nil? || chart_key.nil?)
25
+ raise ArgumentError, "give name: and chart_key:, or from_event_key:"
26
+ end
27
+
28
+ body = compact({ "fromEventKey" => from_event_key, "name" => name, "chartKey" => chart_key,
29
+ "schedule" => schedule, "timezone" => timezone, "venue" => venue,
30
+ "durationMinutes" => duration_minutes, "dryRun" => dry_run })
31
+ @client.post("/v1/event-series", body, idempotency_key: idempotency_key,
32
+ retry_policy: :header_replay)
33
+ end
34
+
35
+ # One page of event series.
36
+ def list_event_series(limit: nil, cursor: nil)
37
+ @client.get("/v1/event-series", compact({ "limit" => limit, "cursor" => cursor }))
38
+ end
39
+
40
+ # Read one event series.
41
+ def retrieve_event_series(event_key)
42
+ @client.get(path(event_key))
43
+ end
44
+
45
+ # Add dates to a series, copied from its first date. Pass one of a +schedule+
46
+ # Hash, a +dates+ list of "YYYY-MM-DDTHH:MM" strings, or +retry_failed: true+ to
47
+ # make again every date that failed to be made (422 nothing_to_retry when none
48
+ # did, 409 series_busy while another change runs).
49
+ def add_event_series_dates(event_key, schedule: nil, dates: nil, retry_failed: false,
50
+ idempotency_key: nil)
51
+ @client.post(path(event_key, "/dates"),
52
+ compact({ "schedule" => schedule, "dates" => dates,
53
+ "retryFailed" => (retry_failed ? true : nil) }),
54
+ idempotency_key: idempotency_key, retry_policy: :header_replay)
55
+ end
56
+
57
+ # Rename a series, change its prices, or turn sales "on" or "off" on every
58
+ # upcoming date. +prices+ ({ "categories" => [...] }) takes only the
59
+ # categories whose price changes (for a tiered category, only the tiers
60
+ # sent); every price left out keeps each date's own value. Sales "on" never
61
+ # opens a date its season cancelled: that date is a failed item of the job
62
+ # (season_date_cancelled), counted in the job's "failed" but not in
63
+ # "failedDates", which are the dates that could not be made.
64
+ def update_event_series(event_key, name: nil, prices: nil, sales: nil, idempotency_key: nil)
65
+ @client.mutation_with_header_replay(
66
+ "PATCH", path(event_key),
67
+ compact({ "name" => name, "prices" => prices, "sales" => sales }),
68
+ idempotency_key: idempotency_key
69
+ )
70
+ end
71
+
72
+ private
73
+
74
+ def path(event_key, suffix = "")
75
+ "/v1/event-series/#{encode(event_key)}#{suffix}"
76
+ end
77
+ end
78
+ end
@@ -0,0 +1,109 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SeatLayer
4
+ # Season holders and customers: contact details, sessions, tickets, ticket
5
+ # transfers between holders, returns, and renewals.
6
+ class Holders < Resource
7
+ # One page of customers, or the one matching +email+ or +holder_ref+.
8
+ def list_customers(email: nil, holder_ref: nil, cursor: nil, limit: nil)
9
+ @client.get("/v1/customers", compact({ "email" => email, "holderRef" => holder_ref,
10
+ "cursor" => cursor, "limit" => limit }))
11
+ end
12
+
13
+ # Read one holder.
14
+ def retrieve_holder(holder_ref)
15
+ @client.get(path(holder_ref))
16
+ end
17
+
18
+ # Set a holder's email, name or phone. Pass +nil+ for +name+ or +phone+ to
19
+ # send an explicit JSON null.
20
+ def update_holder(holder_ref, email: nil, name: UNSET, phone: UNSET, idempotency_key: nil)
21
+ body = compact({ "email" => email })
22
+ body.merge!(supplied({ "name" => name, "phone" => phone }))
23
+ @client.mutation_with_header_replay(
24
+ "PATCH", path(holder_ref), body, idempotency_key: idempotency_key
25
+ )
26
+ end
27
+
28
+ # Sign a holder out of every session, or only out of +session_id+.
29
+ # Single-attempt.
30
+ def revoke_holder_sessions(holder_ref, session_id: nil)
31
+ @client.post(path(holder_ref, "/sessions/revoke"), compact({ "sessionId" => session_id }))
32
+ end
33
+
34
+ # List the Seasons a holder belongs to.
35
+ def list_holder_seasons(holder_ref)
36
+ @client.get(path(holder_ref, "/seasons"))
37
+ end
38
+
39
+ # One page of a holder's tickets. +from+ and +to+ are epoch milliseconds.
40
+ def list_holder_tickets(holder_ref, season_key: nil, from: nil, to: nil, cursor: nil,
41
+ limit: nil)
42
+ @client.get(path(holder_ref, "/tickets"),
43
+ compact({ "seasonKey" => season_key, "from" => from, "to" => to,
44
+ "cursor" => cursor, "limit" => limit }))
45
+ end
46
+
47
+ # Read one of a holder's tickets.
48
+ def retrieve_holder_ticket(holder_ref, ticket_id)
49
+ @client.get(path(holder_ref, "/tickets/#{encode(ticket_id)}"))
50
+ end
51
+
52
+ # Offer tickets to another person by email. Turning customer sends off in
53
+ # Settings › Ticket site doesn't stop your server; cut-offs still apply.
54
+ def create_holder_transfer(holder_ref, ticket_ids:, to_email:, idempotency_key: nil)
55
+ @client.post(path(holder_ref, "/transfers"),
56
+ { "ticketIds" => ticket_ids, "toEmail" => to_email },
57
+ idempotency_key: idempotency_key, retry_policy: :header_replay)
58
+ end
59
+
60
+ # List a holder's ticket transfers.
61
+ def list_holder_transfers(holder_ref)
62
+ @client.get(path(holder_ref, "/transfers"))
63
+ end
64
+
65
+ # Cancel a transfer that has not been accepted yet. Single-attempt.
66
+ def cancel_holder_transfer(holder_ref, transfer_id)
67
+ @client.post(path(holder_ref, "/transfers/#{encode(transfer_id)}/cancel"))
68
+ end
69
+
70
+ # Accept a transfer for its recipient. Single-attempt.
71
+ def accept_holder_transfer(holder_ref, transfer_id)
72
+ @client.post(path(holder_ref, "/transfers/#{encode(transfer_id)}/accept"))
73
+ end
74
+
75
+ # Give back a holder's seat on one date, one per ticket: 1 to 20 tickets,
76
+ # all of one Season. A repeat with the same key answers with the same return.
77
+ # Turning give-backs off in Settings › Ticket site doesn't stop your server;
78
+ # cut-offs still apply.
79
+ def create_holder_return(holder_ref, ticket_ids:, idempotency_key: nil)
80
+ @client.post(path(holder_ref, "/returns"), { "ticketIds" => ticket_ids },
81
+ idempotency_key: idempotency_key, retry_policy: :header_replay)
82
+ end
83
+
84
+ # Start a SeatLayer checkout that renews one of a holder's offers (+renewalOffer+
85
+ # on +list_holder_seasons+). Send the buyer to +payment.url+. Retried with one
86
+ # Idempotency-Key, which answers the same order.
87
+ def create_holder_renewal_checkout(holder_ref, offer_id, return_url:, buyer_email: nil,
88
+ buyer_name: nil, provider: nil, idempotency_key: nil)
89
+ @client.post(path(holder_ref, "/renewal-offers/#{encode(offer_id)}/checkout"),
90
+ compact({ "returnUrl" => return_url, "buyerEmail" => buyer_email,
91
+ "buyerName" => buyer_name, "provider" => provider }),
92
+ idempotency_key: idempotency_key, retry_policy: :header_replay)
93
+ end
94
+
95
+ # Decline one of a holder's renewal offers and release its seats. +reason+
96
+ # (1 to 200 characters) is kept on the offer as +declineReason+; declining
97
+ # again answers the same offer, with the first reason. Single-attempt.
98
+ def decline_holder_renewal_offer(holder_ref, offer_id, reason: nil)
99
+ @client.post(path(holder_ref, "/renewal-offers/#{encode(offer_id)}/decline"),
100
+ compact({ "reason" => reason }))
101
+ end
102
+
103
+ private
104
+
105
+ def path(holder_ref, suffix = "")
106
+ "/v1/holders/#{encode(holder_ref)}#{suffix}"
107
+ end
108
+ end
109
+ end
@@ -64,17 +64,8 @@ module SeatLayer
64
64
 
65
65
  def request(method, path, query: nil, body: nil, raw_body: nil, content_type: nil,
66
66
  idempotency_key: nil, retry_policy: :none)
67
- url = @base_url + path
68
- if query
69
- pairs = query.compact
70
- url += "?#{URI.encode_www_form(pairs)}" unless pairs.empty?
71
- end
72
-
73
- headers = {
74
- "Authorization" => "Bearer #{@secret_key}",
75
- "Accept" => "application/json",
76
- "User-Agent" => "seatlayer-ruby"
77
- }
67
+ url = build_url(path, query)
68
+ headers = default_headers
78
69
  payload = build_payload(body, raw_body, content_type, headers)
79
70
 
80
71
  # Only operations with exact server-side response replay get an automatic
@@ -97,6 +88,13 @@ module SeatLayer
97
88
  request("GET", path, query: query)
98
89
  end
99
90
 
91
+ # Internal path for CSV exports. Same auth, retries and error mapping as
92
+ # #get, but a success body comes back as the raw String, never parsed.
93
+ def get_text(path, query = nil)
94
+ headers = default_headers.merge("Accept" => "text/csv")
95
+ execute("GET", build_url(path, query), headers, nil, retry_allowed: true, text: true)
96
+ end
97
+
100
98
  def post(path, body = nil, idempotency_key: nil, retry_policy: :none)
101
99
  request("POST", path, body: body, idempotency_key: idempotency_key, retry_policy: retry_policy)
102
100
  end
@@ -125,6 +123,22 @@ module SeatLayer
125
123
 
126
124
  private
127
125
 
126
+ def build_url(path, query)
127
+ url = @base_url + path
128
+ return url unless query
129
+
130
+ pairs = query.compact
131
+ pairs.empty? ? url : "#{url}?#{URI.encode_www_form(pairs)}"
132
+ end
133
+
134
+ def default_headers
135
+ {
136
+ "Authorization" => "Bearer #{@secret_key}",
137
+ "Accept" => "application/json",
138
+ "User-Agent" => "seatlayer-ruby"
139
+ }
140
+ end
141
+
128
142
  def build_payload(body, raw_body, content_type, headers)
129
143
  raise ArgumentError, "body and raw_body are mutually exclusive" if !body.nil? && !raw_body.nil?
130
144
 
@@ -140,7 +154,7 @@ module SeatLayer
140
154
 
141
155
  # The retry loop, extracted from #request so each piece stays readable: the
142
156
  # public method builds the call, this one decides how many times to make it.
143
- def execute(method, url, headers, payload, retry_allowed:)
157
+ def execute(method, url, headers, payload, retry_allowed:, text: false)
144
158
  last_error = nil
145
159
 
146
160
  @max_retries.times do |attempt|
@@ -155,7 +169,7 @@ module SeatLayer
155
169
  end
156
170
 
157
171
  status = response[:status]
158
- return parse_success(status, response[:body]) if status >= 200 && status < 300
172
+ return parse_success(status, response[:body], text) if status >= 200 && status < 300
159
173
 
160
174
  error_body = decode_error_body(response[:body])
161
175
  retry_after = parse_retry_after(response[:headers], error_body)
@@ -183,7 +197,8 @@ module SeatLayer
183
197
  {}
184
198
  end
185
199
 
186
- def parse_success(status, body)
200
+ def parse_success(status, body, text)
201
+ return body.to_s if text
187
202
  return {} if status == 204 || body.nil? || body.empty?
188
203
 
189
204
  JSON.parse(body)
@@ -205,9 +205,26 @@ module SeatLayer
205
205
 
206
206
  # Manage webhook subscriptions. To VERIFY a delivery, see SeatLayer::Webhook.
207
207
  class Webhooks < Resource
208
+ # All 58 public event names, in the order the API lists them.
208
209
  EVENT_NAMES = [
209
- "seat.booked", "seat.released", "seat.blocked", "hold.expired",
210
- "hold.created", "hold.extended", "event.created", "event.soldout"
210
+ "seat.booked", "seat.released", "seat.blocked", "hold.expired", "hold.created",
211
+ "hold.extended", "event.created", "event.soldout", "object.resale.listed",
212
+ "object.resale.unlisted", "object.resale.sold", "customer.created", "customer.email_changed",
213
+ "order.paid", "order.refunded", "order.cancelled", "season.ticket.transfer_sent",
214
+ "season.ticket.transferred", "season.ticket.transfer_cancelled", "season.ticket.issued",
215
+ "season.ticket.voided", "season.structure.activated", "season.structure.closed",
216
+ "season.structure.archived", "season.plan.published", "season.plan.superseded",
217
+ "season.sales.opened", "season.sales.paused", "season.sales.ended", "season.hold.committed",
218
+ "season.hold.released", "season.hold.expired", "season.booking.confirmed",
219
+ "season.booking.cancelled", "season.booking.emergency_requested",
220
+ "season.occurrence.returned", "season.occurrence.reclaimed", "season.renewal.offered",
221
+ "season.renewal.intent_received", "season.renewal.renewed", "season.renewal.declined",
222
+ "season.renewal.lapsed", "season.renewal.released", "season.operation.partial_terminal",
223
+ "season.amendment.applied", "season.performance.added", "season.performance.removed",
224
+ "season.performance.past", "season.performance.postponed", "season.performance.cancelled",
225
+ "season.performance.rescheduled", "season.bulk_action.completed", "season.block.ready",
226
+ "season.block.released", "season.order.paid", "season.order.refunded", "series.created",
227
+ "series.dates_added"
211
228
  ].freeze
212
229
 
213
230
  def list
@@ -252,9 +269,14 @@ module SeatLayer
252
269
  @client.get("/v1/workspaces")
253
270
  end
254
271
 
255
- def create(name:, external_ref: UNSET, idempotency_key: nil)
272
+ def create(name:, external_ref: UNSET, default_region: nil, idempotency_key: nil)
273
+ unless default_region.nil? || EVENT_HOSTING_REGIONS.include?(default_region)
274
+ raise ArgumentError, "default_region must be a supported SeatLayer Event region"
275
+ end
276
+
256
277
  body = { "name" => name }
257
278
  body.merge!(supplied({ "externalRef" => external_ref }))
279
+ body["defaultRegion"] = default_region unless default_region.nil?
258
280
  @client.post(
259
281
  "/v1/workspaces", body, idempotency_key: idempotency_key, retry_policy: :header_replay
260
282
  )
@@ -269,6 +291,13 @@ module SeatLayer
269
291
  # The organisation's default workspace cannot be disabled — the API answers
270
292
  # 409 +default_workspace_required+. Promote another one first.
271
293
  def update(workspace_id, fields)
294
+ region = fields[:default_region] || fields["defaultRegion"]
295
+ unless region.nil? || EVENT_HOSTING_REGIONS.include?(region)
296
+ raise ArgumentError, "default_region must be a supported SeatLayer Event region"
297
+ end
298
+
299
+ fields = fields.dup
300
+ fields["defaultRegion"] = fields.delete(:default_region) if fields.key?(:default_region)
272
301
  @client.patch("/v1/workspaces/#{encode(workspace_id)}", fields)
273
302
  end
274
303
  end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SeatLayer
4
+ # Orders SeatLayer took through its own checkout: reads, CSV export,
5
+ # refunds, cancellations and ticket resends.
6
+ class Orders < Resource
7
+ # One page of orders. +from+ and +to+ are epoch milliseconds. +holder_ref+ lists
8
+ # one holder's orders: the Season order with that id and renewals offered to that
9
+ # reference. Each order carries "holderRef" and "customerId"; "buyer"/"email" is
10
+ # nil for a box office sale taken without one.
11
+ def list_orders(event_key: nil, season_key: nil, status: nil, from: nil, to: nil,
12
+ cursor: nil, limit: nil, holder_ref: nil)
13
+ @client.get("/v1/orders", compact({ "eventKey" => event_key, "seasonKey" => season_key,
14
+ "holderRef" => holder_ref,
15
+ "status" => status, "from" => from, "to" => to,
16
+ "cursor" => cursor, "limit" => limit }))
17
+ end
18
+
19
+ # Read one order.
20
+ def retrieve_order(order_id)
21
+ @client.get("/v1/orders/#{encode(order_id)}")
22
+ end
23
+
24
+ # List one order's tickets.
25
+ def list_order_tickets(order_id)
26
+ @client.get("/v1/orders/#{encode(order_id)}/tickets")
27
+ end
28
+
29
+ # One page of an event's orders. +search+ is sent as the API's +q+
30
+ # parameter; +from+ and +to+ are epoch milliseconds.
31
+ def list_event_orders(event_key, search: nil, status: nil, paid_by: nil, from: nil, to: nil,
32
+ cursor: nil, limit: nil)
33
+ @client.get("/v1/events/#{encode(event_key)}/orders",
34
+ compact({ "q" => search, "status" => status, "paidBy" => paid_by,
35
+ "from" => from, "to" => to, "cursor" => cursor, "limit" => limit }))
36
+ end
37
+
38
+ # Read one of an event's orders.
39
+ def retrieve_event_order(event_key, order_id)
40
+ @client.get("/v1/events/#{encode(event_key)}/orders/#{encode(order_id)}")
41
+ end
42
+
43
+ # Download an event's orders as CSV, with the same filters as
44
+ # +list_event_orders+. Returns the raw CSV text.
45
+ def export_event_orders_csv(event_key, search: nil, status: nil, paid_by: nil, from: nil,
46
+ to: nil)
47
+ @client.get_text("/v1/events/#{encode(event_key)}/orders.csv",
48
+ compact({ "q" => search, "status" => status, "paidBy" => paid_by,
49
+ "from" => from, "to" => to }))
50
+ end
51
+
52
+ # Refund an event order, in full or by +amount_minor+.
53
+ def refund_event_order(event_key, order_id, amount_minor: nil, idempotency_key: nil)
54
+ @client.post("/v1/events/#{encode(event_key)}/orders/#{encode(order_id)}/refund",
55
+ compact({ "amountMinor" => amount_minor }),
56
+ idempotency_key: idempotency_key, retry_policy: :header_replay)
57
+ end
58
+
59
+ # Cancel an event order.
60
+ def cancel_event_order(event_key, order_id, reason:, note: nil, refund_confirmed: nil,
61
+ used_tickets_acknowledged: nil, notify_buyer: nil, idempotency_key: nil)
62
+ body = compact({ "reason" => reason, "note" => note, "refundConfirmed" => refund_confirmed,
63
+ "usedTicketsAcknowledged" => used_tickets_acknowledged,
64
+ "notifyBuyer" => notify_buyer })
65
+ @client.post("/v1/events/#{encode(event_key)}/orders/#{encode(order_id)}/cancel", body,
66
+ idempotency_key: idempotency_key, retry_policy: :header_replay)
67
+ end
68
+
69
+ # Send an event order's tickets again. Single-attempt: never retried automatically.
70
+ def resend_event_order_tickets(event_key, order_id, email: nil)
71
+ @client.post("/v1/events/#{encode(event_key)}/orders/#{encode(order_id)}/resend",
72
+ compact({ "email" => email }))
73
+ end
74
+ end
75
+ 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
@@ -152,11 +157,14 @@ module SeatLayer
152
157
  #
153
158
  # Live availability counts cost one round-trip per event server-side. They
154
159
  # are on by default because most callers of a single page want them; pass
155
- # <tt>counts: false</tt> when paging a whole catalogue.
156
- def list(workspace_id: nil, external_ref: nil, limit: nil, cursor: nil, counts: true)
160
+ # <tt>counts: false</tt> when paging a whole catalogue. +series_key+ lists only
161
+ # the dates of one event series.
162
+ def list(workspace_id: nil, external_ref: nil, limit: nil, cursor: nil, counts: true,
163
+ series_key: nil)
157
164
  query = compact({ "workspaceId" => workspace_id, "externalRef" => external_ref,
158
165
  "limit" => limit, "cursor" => cursor })
159
166
  query["counts"] = "0" unless counts
167
+ query["seriesKey"] = series_key unless series_key.nil?
160
168
  @client.get("/v1/events", query)
161
169
  end
162
170
 
@@ -179,8 +187,13 @@ module SeatLayer
179
187
  def create(chart_id:, name: nil, slug: nil, starts_at: UNSET, venue: UNSET,
180
188
  external_ref: UNSET, currency: UNSET, idempotency_key: nil,
181
189
  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 })
190
+ poster_asset_id: UNSET, mode: nil, region: nil)
191
+ unless region.nil? || EVENT_HOSTING_REGIONS.include?(region)
192
+ raise ArgumentError, "region must be a supported SeatLayer Event region"
193
+ end
194
+
195
+ body = compact({ "chartId" => chart_id, "name" => name, "slug" => slug,
196
+ "mode" => mode, "region" => region })
184
197
  body.merge!(supplied({ "startsAt" => starts_at, "venue" => venue,
185
198
  "externalRef" => external_ref, "currency" => currency,
186
199
  "description" => description, "endsAt" => ends_at,
@@ -239,6 +252,8 @@ module SeatLayer
239
252
  @client.post("/v1/events/#{encode(event_key)}/close")
240
253
  end
241
254
 
255
+ # Open a paused event's sales again. A date its season cancelled never opens
256
+ # again: 409 season_date_cancelled.
242
257
  def reopen(event_key)
243
258
  @client.post("/v1/events/#{encode(event_key)}/reopen")
244
259
  end
@@ -279,5 +294,101 @@ module SeatLayer
279
294
  @client.get("/v1/events/#{encode(event_key)}/log",
280
295
  compact({ "limit" => limit, "before" => before }))
281
296
  end
297
+
298
+ # Read who takes payment for the event, and what blocks a change.
299
+ def retrieve_event_buyer_checkout(event_key)
300
+ @client.get("/v1/events/#{encode(event_key)}/buyer-checkout")
301
+ end
302
+
303
+ # Choose who takes payment: "seatlayer" or "integrator". Single-attempt;
304
+ # +expected_revision+ stops two writers overwriting each other.
305
+ def replace_event_buyer_checkout(event_key, buyer_checkout:, expected_revision:)
306
+ @client.put("/v1/events/#{encode(event_key)}/buyer-checkout",
307
+ { "buyerCheckout" => buyer_checkout, "expectedRevision" => expected_revision })
308
+ end
309
+
310
+ # Read the payment gateway the event uses, optionally for one +currency+.
311
+ def retrieve_event_payment_gateway(event_key, currency: nil)
312
+ @client.get("/v1/events/#{encode(event_key)}/payment-gateway",
313
+ compact({ "currency" => currency }))
314
+ end
315
+
316
+ # Assign a payment gateway connection to the event. Pass +nil+ to send an
317
+ # explicit JSON null. Single-attempt: never retried automatically.
318
+ def replace_event_payment_gateway(event_key, connection_id: UNSET, provider: UNSET)
319
+ @client.put("/v1/events/#{encode(event_key)}/payment-gateway",
320
+ supplied({ "connectionId" => connection_id, "provider" => provider }))
321
+ end
322
+
323
+ # Read what the event still needs before it can sell.
324
+ def retrieve_event_sales_readiness(event_key)
325
+ @client.get("/v1/events/#{encode(event_key)}/sales/readiness")
326
+ end
327
+
328
+ # Check one order against the event's sales setup. Single-attempt.
329
+ def preflight_event_sales(event_key, order_id:)
330
+ @client.post("/v1/events/#{encode(event_key)}/sales/preflight", { "orderId" => order_id })
331
+ end
332
+
333
+ # Move the event's sales to another state (setup, testable, live, paused or ended).
334
+ # A date its season cancelled never opens again: moving it to testable or live
335
+ # answers 409 season_date_cancelled.
336
+ def transition_event_sales(event_key, to:, expected_revision:, reason: nil)
337
+ @client.post("/v1/events/#{encode(event_key)}/sales/transition",
338
+ compact({ "to" => to, "expectedRevision" => expected_revision,
339
+ "reason" => reason }))
340
+ end
341
+
342
+ # Read the event's booking fee.
343
+ def retrieve_event_booking_fee(event_key)
344
+ @client.get("/v1/events/#{encode(event_key)}/booking-fee")
345
+ end
346
+
347
+ # Set the booking fee in minor units; +nil+ clears it and stays an explicit
348
+ # JSON null. Single-attempt: never retried automatically.
349
+ def replace_event_booking_fee(event_key, booking_fee_minor:)
350
+ @client.put("/v1/events/#{encode(event_key)}/booking-fee",
351
+ { "bookingFeeMinor" => booking_fee_minor })
352
+ end
353
+
354
+ # List the event's discount codes.
355
+ def list_event_discount_codes(event_key)
356
+ @client.get("/v1/events/#{encode(event_key)}/discount-codes")
357
+ end
358
+
359
+ # Create a discount code. +kind+ is "percent" or "amount". Pass +nil+ for
360
+ # +starts_at+, +ends_at+ or +max_uses+ to send an explicit JSON null.
361
+ # Single-attempt: never retried automatically.
362
+ # rubocop:disable-next Metrics/ParameterLists
363
+ def create_event_discount_code(event_key, code:, kind:, percent: nil, amount_minor: nil,
364
+ starts_at: UNSET, ends_at: UNSET, max_uses: UNSET, active: nil)
365
+ body = compact({ "code" => code, "kind" => kind, "percent" => percent,
366
+ "amountMinor" => amount_minor, "active" => active })
367
+ body.merge!(supplied({ "startsAt" => starts_at, "endsAt" => ends_at, "maxUses" => max_uses }))
368
+ @client.post("/v1/events/#{encode(event_key)}/discount-codes", body)
369
+ end
370
+
371
+ # Change a discount code. Only the fields you pass are sent; pass +nil+ for
372
+ # +starts_at+, +ends_at+ or +max_uses+ to send an explicit JSON null.
373
+ # Single-attempt: never retried automatically.
374
+ # rubocop:disable-next Metrics/ParameterLists
375
+ def update_event_discount_code(event_key, code_id, code: nil, kind: nil, percent: nil,
376
+ amount_minor: nil, starts_at: UNSET, ends_at: UNSET,
377
+ max_uses: UNSET, active: nil, state: nil)
378
+ body = compact({ "code" => code, "kind" => kind, "percent" => percent,
379
+ "amountMinor" => amount_minor, "active" => active, "state" => state })
380
+ body.merge!(supplied({ "startsAt" => starts_at, "endsAt" => ends_at, "maxUses" => max_uses }))
381
+ @client.patch("/v1/events/#{encode(event_key)}/discount-codes/#{encode(code_id)}", body)
382
+ end
383
+
384
+ # End a discount code now, keeping its history.
385
+ def end_event_discount_code(event_key, code_id)
386
+ @client.post("/v1/events/#{encode(event_key)}/discount-codes/#{encode(code_id)}/end")
387
+ end
388
+
389
+ # Delete a discount code. The API answers 204, so this returns an empty Hash.
390
+ def delete_event_discount_code(event_key, code_id)
391
+ @client.delete("/v1/events/#{encode(event_key)}/discount-codes/#{encode(code_id)}")
392
+ end
282
393
  end
283
394
  end