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
|
@@ -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 =
|
|
68
|
-
|
|
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)
|
data/lib/seatlayer/inventory.rb
CHANGED
|
@@ -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.
|
|
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
|
data/lib/seatlayer/resources.rb
CHANGED
|
@@ -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
|
-
|
|
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
|
-
|
|
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
|