booker_ruby 1.14.0 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/booker/client.rb +154 -49
- data/lib/booker/errors.rb +6 -2
- data/lib/booker/model.rb +84 -0
- data/lib/booker/v4.1/availability.rb +21 -0
- data/lib/booker/v4.1/booking.rb +87 -0
- data/lib/booker/v4.1/merchant.rb +55 -0
- data/lib/booker/v4/business_client.rb +9 -0
- data/lib/booker/v4/business_rest.rb +88 -0
- data/lib/booker/v4/common_rest.rb +21 -0
- data/lib/booker/v4/customer_client.rb +15 -0
- data/lib/booker/v4/customer_rest.rb +15 -0
- data/lib/booker/v4/models/address.rb +14 -0
- data/lib/booker/v4/models/appointment.rb +79 -0
- data/lib/booker/v4/models/appointment_treatment.rb +53 -0
- data/lib/booker/v4/models/available_time.rb +15 -0
- data/lib/booker/v4/models/business_type.rb +7 -0
- data/lib/booker/v4/models/category.rb +7 -0
- data/lib/booker/v4/models/class_instance.rb +27 -0
- data/lib/booker/v4/models/country.rb +21 -0
- data/lib/booker/v4/models/current_price.rb +7 -0
- data/lib/booker/v4/models/customer.rb +54 -0
- data/lib/booker/v4/models/customer_2.rb +7 -0
- data/lib/booker/v4/models/customer_record_type.rb +7 -0
- data/lib/booker/v4/models/discount.rb +7 -0
- data/lib/booker/v4/models/dynamic_price.rb +13 -0
- data/lib/booker/v4/models/employee.rb +12 -0
- data/lib/booker/v4/models/feature_settings.rb +9 -0
- data/lib/booker/v4/models/final_total.rb +7 -0
- data/lib/booker/v4/models/gender.rb +7 -0
- data/lib/booker/v4/models/itinerary_time_slot.rb +9 -0
- data/lib/booker/v4/models/itinerary_time_slots_list.rb +9 -0
- data/lib/booker/v4/models/location.rb +40 -0
- data/lib/booker/v4/models/location_day_schedule.rb +20 -0
- data/lib/booker/v4/models/model.rb +78 -0
- data/lib/booker/v4/models/multi_service_availability_result.rb +9 -0
- data/lib/booker/v4/models/notification_settings.rb +14 -0
- data/lib/booker/v4/models/online_booking_settings.rb +25 -0
- data/lib/booker/v4/models/original_price.rb +7 -0
- data/lib/booker/v4/models/payment_method.rb +7 -0
- data/lib/booker/v4/models/preferred_staff_gender.rb +7 -0
- data/lib/booker/v4/models/price.rb +10 -0
- data/lib/booker/v4/models/receipt_display_price.rb +7 -0
- data/lib/booker/v4/models/room.rb +14 -0
- data/lib/booker/v4/models/shipping_address.rb +7 -0
- data/lib/booker/v4/models/source.rb +7 -0
- data/lib/booker/v4/models/spa.rb +7 -0
- data/lib/booker/v4/models/spa_employee_availability_search_item.rb +13 -0
- data/lib/booker/v4/models/status.rb +7 -0
- data/lib/booker/v4/models/sub_category.rb +7 -0
- data/lib/booker/v4/models/tag_price.rb +7 -0
- data/lib/booker/v4/models/teacher.rb +7 -0
- data/lib/booker/v4/models/teacher_2.rb +7 -0
- data/lib/booker/v4/models/time_zone.rb +10 -0
- data/lib/booker/v4/models/treatment.rb +21 -0
- data/lib/booker/v4/models/treatment_time_slot.rb +7 -0
- data/lib/booker/v4/models/type.rb +10 -0
- data/lib/booker/v4/models/user.rb +75 -0
- data/lib/booker/v4/request_helper.rb +33 -0
- data/lib/booker/v5/availability.rb +45 -0
- data/lib/booker/v5/models/availability.rb +18 -0
- data/lib/booker/v5/models/availability_result.rb +12 -0
- data/lib/booker/v5/models/location_hour.rb +16 -0
- data/lib/booker/v5/models/model.rb +9 -0
- data/lib/booker/v5/models/service.rb +13 -0
- data/lib/booker/v5/models/service_category.rb +11 -0
- data/lib/booker/version.rb +1 -1
- data/lib/booker_ruby.rb +70 -52
- metadata +80 -54
- data/lib/booker/business_client.rb +0 -22
- data/lib/booker/business_rest.rb +0 -112
- data/lib/booker/common_rest.rb +0 -43
- data/lib/booker/customer_client.rb +0 -17
- data/lib/booker/customer_rest.rb +0 -53
- data/lib/booker/models/address.rb +0 -12
- data/lib/booker/models/appointment.rb +0 -77
- data/lib/booker/models/appointment_treatment.rb +0 -51
- data/lib/booker/models/available_time.rb +0 -13
- data/lib/booker/models/business_type.rb +0 -5
- data/lib/booker/models/category.rb +0 -5
- data/lib/booker/models/class_instance.rb +0 -25
- data/lib/booker/models/country.rb +0 -19
- data/lib/booker/models/current_price.rb +0 -5
- data/lib/booker/models/customer.rb +0 -52
- data/lib/booker/models/customer_2.rb +0 -5
- data/lib/booker/models/customer_record_type.rb +0 -5
- data/lib/booker/models/discount.rb +0 -5
- data/lib/booker/models/dynamic_price.rb +0 -11
- data/lib/booker/models/employee.rb +0 -10
- data/lib/booker/models/feature_settings.rb +0 -7
- data/lib/booker/models/final_total.rb +0 -5
- data/lib/booker/models/gender.rb +0 -5
- data/lib/booker/models/itinerary_time_slot.rb +0 -7
- data/lib/booker/models/itinerary_time_slots_list.rb +0 -7
- data/lib/booker/models/location.rb +0 -38
- data/lib/booker/models/location_day_schedule.rb +0 -18
- data/lib/booker/models/model.rb +0 -150
- data/lib/booker/models/multi_service_availability_result.rb +0 -7
- data/lib/booker/models/notification_settings.rb +0 -12
- data/lib/booker/models/online_booking_settings.rb +0 -23
- data/lib/booker/models/original_price.rb +0 -5
- data/lib/booker/models/payment_method.rb +0 -5
- data/lib/booker/models/preferred_staff_gender.rb +0 -5
- data/lib/booker/models/price.rb +0 -8
- data/lib/booker/models/receipt_display_price.rb +0 -5
- data/lib/booker/models/room.rb +0 -12
- data/lib/booker/models/shipping_address.rb +0 -5
- data/lib/booker/models/source.rb +0 -5
- data/lib/booker/models/spa.rb +0 -5
- data/lib/booker/models/spa_employee_availability_search_item.rb +0 -11
- data/lib/booker/models/status.rb +0 -5
- data/lib/booker/models/sub_category.rb +0 -5
- data/lib/booker/models/tag_price.rb +0 -5
- data/lib/booker/models/teacher.rb +0 -5
- data/lib/booker/models/teacher_2.rb +0 -5
- data/lib/booker/models/time_zone.rb +0 -8
- data/lib/booker/models/treatment.rb +0 -19
- data/lib/booker/models/treatment_time_slot.rb +0 -5
- data/lib/booker/models/type.rb +0 -8
- data/lib/booker/models/user.rb +0 -73
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 131d08ade59a2472091a0a054a83a44e4ca61b3a
|
4
|
+
data.tar.gz: 1b310a5e471337ef53435cd490fefac9a6d70d12
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: eb7ca3e5420dfdcf5844bd3eb95da5372322998e000b76c3260d4399360ffde955c51061b9b4ee1bd88b991b1af25b09cb5df83c296afbe8514dc1dab386f2b4
|
7
|
+
data.tar.gz: ea1ea1d93267b933769e09516ffb1f4330819c278fca418443e710443e995ad7da2d65209f55560da699a8b37ca3945a2afc33c758804316ef2ee051d1882826
|
data/lib/booker/client.rb
CHANGED
@@ -1,27 +1,55 @@
|
|
1
1
|
module Booker
|
2
2
|
class Client
|
3
|
-
attr_accessor :base_url, :client_id, :client_secret, :temp_access_token,
|
4
|
-
:token_store, :token_store_callback_method
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
3
|
+
attr_accessor :base_url, :auth_base_url, :client_id, :client_secret, :temp_access_token,
|
4
|
+
:temp_access_token_expires_at, :token_store, :token_store_callback_method, :api_subscription_key,
|
5
|
+
:access_token_scope, :refresh_token, :location_id, :auth_with_client_credentials
|
6
|
+
|
7
|
+
CREATE_TOKEN_CONTENT_TYPE = 'application/x-www-form-urlencoded'.freeze
|
8
|
+
CLIENT_CREDENTIALS_GRANT_TYPE = 'client_credentials'.freeze
|
9
|
+
REFRESH_TOKEN_GRANT_TYPE = 'refresh_token'.freeze
|
10
|
+
CREATE_TOKEN_PATH = '/v5/auth/connect/token'.freeze
|
11
|
+
UPDATE_TOKEN_CONTEXT_PATH = '/v5/auth/context/update'.freeze
|
12
|
+
VALID_ACCESS_TOKEN_SCOPES = %w(public merchant parter-payment internal).map(&:freeze).freeze
|
13
|
+
API_GATEWAY_ERRORS = {
|
14
|
+
503 => Booker::ServiceUnavailable,
|
15
|
+
504 => Booker::ServiceUnavailable,
|
16
|
+
429 => Booker::RateLimitExceeded,
|
17
|
+
401 => Booker::InvalidApiCredentials,
|
18
|
+
403 => Booker::InvalidApiCredentials
|
19
|
+
}.freeze
|
20
|
+
BOOKER_SERVER_TIMEZONE = 'Eastern Time (US & Canada)'.freeze
|
21
|
+
DEFAULT_CONTENT_TYPE = 'application/json'.freeze
|
22
|
+
ENV_BASE_URL_KEY = 'BOOKER_API_BASE_URL'.freeze
|
23
|
+
DEFAULT_BASE_URL = 'https://api-staging.booker.com'.freeze
|
24
|
+
DEFAULT_AUTH_BASE_URL = 'https://api-staging.booker.com'
|
9
25
|
|
10
26
|
def initialize(options = {})
|
11
27
|
options.each { |key, value| send(:"#{key}=", value) }
|
12
28
|
self.base_url ||= get_base_url
|
29
|
+
self.auth_base_url ||= ENV['BOOKER_API_BASE_URL'] || DEFAULT_AUTH_BASE_URL
|
13
30
|
self.client_id ||= ENV['BOOKER_CLIENT_ID']
|
14
31
|
self.client_secret ||= ENV['BOOKER_CLIENT_SECRET']
|
32
|
+
self.api_subscription_key ||= ENV['BOOKER_API_SUBSCRIPTION_KEY']
|
33
|
+
if self.auth_with_client_credentials.nil?
|
34
|
+
self.auth_with_client_credentials = ENV['BOOKER_API_AUTH_WITH_CLIENT_CREDENTIALS'] == 'true'
|
35
|
+
end
|
36
|
+
if self.temp_access_token.present?
|
37
|
+
begin
|
38
|
+
self.temp_access_token_expires_at = token_expires_at(self.temp_access_token)
|
39
|
+
self.access_token_scope = token_scope(self.temp_access_token)
|
40
|
+
rescue JWT::ExpiredSignature => ex
|
41
|
+
raise ex unless self.auth_with_client_credentials || self.refresh_token.present?
|
42
|
+
end
|
43
|
+
end
|
44
|
+
if self.access_token_scope.blank?
|
45
|
+
self.access_token_scope = VALID_ACCESS_TOKEN_SCOPES.first
|
46
|
+
elsif !self.access_token_scope.in?(VALID_ACCESS_TOKEN_SCOPES)
|
47
|
+
raise ArgumentError, "access_token_scope must be one of: #{VALID_ACCESS_TOKEN_SCOPES.join(', ')}"
|
48
|
+
end
|
15
49
|
end
|
16
50
|
|
17
51
|
def get_base_url
|
18
|
-
|
19
|
-
|
20
|
-
if env_key.present?
|
21
|
-
ENV[env_key] || try(:default_base_url)
|
22
|
-
else
|
23
|
-
try(:default_base_url)
|
24
|
-
end
|
52
|
+
ENV[self.class::ENV_BASE_URL_KEY] || self.class::DEFAULT_BASE_URL
|
25
53
|
end
|
26
54
|
|
27
55
|
def get(path, params, booker_model=nil)
|
@@ -42,15 +70,21 @@ module Booker
|
|
42
70
|
build_resources(booker_resources, booker_model)
|
43
71
|
end
|
44
72
|
|
73
|
+
def delete(path, params=nil, body=nil, booker_model=nil)
|
74
|
+
booker_resources = get_booker_resources(:delete, path, params, body.to_json, booker_model)
|
75
|
+
|
76
|
+
build_resources(booker_resources, booker_model)
|
77
|
+
end
|
78
|
+
|
45
79
|
def paginated_request(method:, path:, params:, model: nil, fetched: [], fetch_all: true)
|
46
|
-
page_size = params[
|
47
|
-
page_number = params[
|
80
|
+
page_size = params[:PageSize]
|
81
|
+
page_number = params[:PageNumber]
|
48
82
|
|
49
|
-
if page_size.nil? || page_size < 1 || page_number.nil? || page_number < 1 || !params[
|
83
|
+
if page_size.nil? || page_size < 1 || page_number.nil? || page_number < 1 || !params[:UsePaging]
|
50
84
|
raise ArgumentError, 'params must include valid PageSize, PageNumber and UsePaging'
|
51
85
|
end
|
52
86
|
|
53
|
-
puts "fetching #{path} with #{params.except(
|
87
|
+
puts "fetching #{path} with #{params.except(:access_token)}. #{fetched.length} results so far."
|
54
88
|
|
55
89
|
results = self.send(method, path, params, model)
|
56
90
|
|
@@ -65,7 +99,7 @@ module Booker
|
|
65
99
|
if results_length > 0
|
66
100
|
# TODO (#111186744): Add logging to see if any pages with less than expected data (as seen in the /appointments endpoint)
|
67
101
|
new_params = params.deep_dup
|
68
|
-
new_params[
|
102
|
+
new_params[:PageNumber] = page_number + 1
|
69
103
|
paginated_request(method: method, path: path, params: new_params, model: model, fetched: fetched)
|
70
104
|
else
|
71
105
|
fetched
|
@@ -82,20 +116,28 @@ module Booker
|
|
82
116
|
|
83
117
|
# Allow it to retry the first time unless it is an authorization error
|
84
118
|
begin
|
85
|
-
|
119
|
+
response = handle_errors!(url, http_options, HTTParty.send(http_method, url, http_options))
|
86
120
|
rescue Booker::Error, Net::ReadTimeout => ex
|
87
121
|
if ex.is_a? Booker::InvalidApiCredentials
|
88
122
|
raise ex
|
89
123
|
else
|
90
124
|
sleep 1
|
91
|
-
|
125
|
+
response = nil # Force a retry (see logic below)
|
92
126
|
end
|
93
127
|
end
|
94
128
|
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
129
|
+
unless response.nil? || nil_or_empty_hash?(response.parsed_response)
|
130
|
+
return results_from_response(response, booker_model)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Retry on blank responses (happens in certain v4 API methods in lieu of an actual error)
|
134
|
+
response = handle_errors!(url, http_options, HTTParty.send(http_method, url, http_options))
|
135
|
+
unless response.nil? || nil_or_empty_hash?(response.parsed_response)
|
136
|
+
return results_from_response(response, booker_model)
|
137
|
+
end
|
138
|
+
|
139
|
+
# Raise if response is still blank
|
140
|
+
raise Booker::Error.new(url: url, request: http_options, response: response)
|
99
141
|
end
|
100
142
|
|
101
143
|
def full_url(path)
|
@@ -106,6 +148,9 @@ module Booker
|
|
106
148
|
def handle_errors!(url, request, response)
|
107
149
|
puts "BOOKER RESPONSE: #{response}" if ENV['BOOKER_API_DEBUG'] == 'true'
|
108
150
|
|
151
|
+
error_class = API_GATEWAY_ERRORS[response.code]
|
152
|
+
raise error_class.new(url: url, request: request, response: response) if error_class
|
153
|
+
|
109
154
|
ex = Booker::Error.new(url: url, request: request, response: response)
|
110
155
|
if ex.error.present? || !response.success?
|
111
156
|
case ex.error
|
@@ -126,13 +171,6 @@ module Booker
|
|
126
171
|
(self.temp_access_token && !temp_access_token_expired?) ? self.temp_access_token : get_access_token
|
127
172
|
end
|
128
173
|
|
129
|
-
def access_token_options
|
130
|
-
{
|
131
|
-
client_id: self.client_id,
|
132
|
-
client_secret: self.client_secret
|
133
|
-
}
|
134
|
-
end
|
135
|
-
|
136
174
|
def update_token_store
|
137
175
|
if self.token_store.present? && self.token_store_callback_method.present?
|
138
176
|
self.token_store.send(self.token_store_callback_method, self.temp_access_token, self.temp_access_token_expires_at)
|
@@ -140,36 +178,88 @@ module Booker
|
|
140
178
|
end
|
141
179
|
|
142
180
|
def get_access_token
|
143
|
-
|
144
|
-
|
181
|
+
unless self.auth_with_client_credentials || self.refresh_token
|
182
|
+
raise ArgumentError, 'Cannot get new access token without auth_with_client_credentials or a refresh_token'
|
183
|
+
end
|
145
184
|
|
146
|
-
|
147
|
-
|
185
|
+
resp = access_token_response
|
186
|
+
token = resp.parsed_response['access_token']
|
187
|
+
raise Booker::InvalidApiCredentials.new(response: resp) if token.blank?
|
188
|
+
|
189
|
+
if self.auth_with_client_credentials && self.location_id
|
190
|
+
self.temp_access_token = get_location_access_token(token, self.location_id)
|
191
|
+
else
|
192
|
+
self.temp_access_token = token
|
193
|
+
end
|
194
|
+
|
195
|
+
self.temp_access_token_expires_at = token_expires_at(self.temp_access_token)
|
148
196
|
|
149
197
|
update_token_store
|
150
198
|
|
151
199
|
self.temp_access_token
|
152
200
|
end
|
153
201
|
|
154
|
-
def
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
202
|
+
def access_token_response
|
203
|
+
body = {
|
204
|
+
grant_type: self.auth_with_client_credentials ? CLIENT_CREDENTIALS_GRANT_TYPE : REFRESH_TOKEN_GRANT_TYPE,
|
205
|
+
client_id: self.client_id,
|
206
|
+
client_secret: self.client_secret,
|
207
|
+
scope: self.access_token_scope
|
208
|
+
}
|
209
|
+
body[:refresh_token] = self.refresh_token if body[:grant_type] == REFRESH_TOKEN_GRANT_TYPE
|
210
|
+
options = {
|
211
|
+
headers: {
|
212
|
+
'Content-Type' => CREATE_TOKEN_CONTENT_TYPE,
|
213
|
+
'Ocp-Apim-Subscription-Key' => self.api_subscription_key
|
214
|
+
},
|
215
|
+
body: body.to_query
|
216
|
+
}
|
217
|
+
|
218
|
+
url = "#{self.auth_base_url}#{CREATE_TOKEN_PATH}"
|
219
|
+
|
220
|
+
begin
|
221
|
+
handle_errors! url, options, HTTParty.post(url, options)
|
222
|
+
rescue Booker::ServiceUnavailable, Booker::RateLimitExceeded
|
223
|
+
# retry once
|
224
|
+
sleep 1
|
225
|
+
handle_errors! url, options, HTTParty.post(url, options)
|
161
226
|
end
|
162
227
|
end
|
163
228
|
|
164
|
-
def
|
165
|
-
|
229
|
+
def get_location_access_token(existing_token, location_id)
|
230
|
+
options = {
|
231
|
+
headers: {
|
232
|
+
'Content-Type' => DEFAULT_CONTENT_TYPE,
|
233
|
+
'Accept' => 'application/json',
|
234
|
+
'Authorization' => "Bearer #{existing_token}",
|
235
|
+
'Ocp-Apim-Subscription-Key' => self.api_subscription_key
|
236
|
+
},
|
237
|
+
query: {
|
238
|
+
locationId: location_id
|
239
|
+
},
|
240
|
+
open_timeout: 120
|
241
|
+
}
|
242
|
+
url = "#{self.auth_base_url}#{UPDATE_TOKEN_CONTEXT_PATH}"
|
243
|
+
|
244
|
+
begin
|
245
|
+
resp = handle_errors! url, options, HTTParty.post(url, options)
|
246
|
+
rescue Booker::ServiceUnavailable, Booker::RateLimitExceeded
|
247
|
+
# retry once
|
248
|
+
sleep 1
|
249
|
+
resp = handle_errors! url, options, HTTParty.post(url, options)
|
250
|
+
end
|
251
|
+
|
252
|
+
resp.parsed_response
|
166
253
|
end
|
167
254
|
|
168
255
|
private
|
169
256
|
def request_options(query=nil, body=nil)
|
170
257
|
options = {
|
171
258
|
headers: {
|
172
|
-
'Content-Type' =>
|
259
|
+
'Content-Type' => DEFAULT_CONTENT_TYPE,
|
260
|
+
'Accept' => DEFAULT_CONTENT_TYPE,
|
261
|
+
'Authorization' => "Bearer #{access_token}",
|
262
|
+
'Ocp-Apim-Subscription-Key' => self.api_subscription_key
|
173
263
|
},
|
174
264
|
open_timeout: 120
|
175
265
|
}
|
@@ -196,17 +286,32 @@ module Booker
|
|
196
286
|
end
|
197
287
|
|
198
288
|
def results_from_response(response, booker_model=nil)
|
199
|
-
|
289
|
+
parsed_response = response.parsed_response
|
290
|
+
|
291
|
+
return parsed_response unless parsed_response.is_a?(Hash)
|
292
|
+
return parsed_response['Results'] unless parsed_response['Results'].nil?
|
200
293
|
|
201
294
|
if booker_model
|
202
295
|
model_name = booker_model.to_s.demodulize
|
203
|
-
return
|
296
|
+
return parsed_response[model_name] unless parsed_response[model_name].nil?
|
204
297
|
|
205
298
|
pluralized = model_name.pluralize
|
206
|
-
return
|
299
|
+
return parsed_response[pluralized] unless parsed_response[pluralized].nil?
|
207
300
|
end
|
208
301
|
|
209
|
-
|
302
|
+
parsed_response
|
303
|
+
end
|
304
|
+
|
305
|
+
def nil_or_empty_hash?(obj)
|
306
|
+
obj.nil? || (obj.is_a?(Hash) && obj.blank?)
|
307
|
+
end
|
308
|
+
|
309
|
+
def token_expires_at(token)
|
310
|
+
Time.at(JWT.decode(token, nil, false)[0]['exp'])
|
311
|
+
end
|
312
|
+
|
313
|
+
def token_scope(token)
|
314
|
+
JWT.decode(token, nil, false)[0]['scope']
|
210
315
|
end
|
211
316
|
end
|
212
317
|
end
|
data/lib/booker/errors.rb
CHANGED
@@ -9,8 +9,10 @@ module Booker
|
|
9
9
|
|
10
10
|
if response.present?
|
11
11
|
self.response = response
|
12
|
-
|
13
|
-
|
12
|
+
if response.parsed_response.is_a?(Hash)
|
13
|
+
self.error = response.parsed_response['error'] || response.parsed_response['ErrorMessage']
|
14
|
+
self.description = response.parsed_response['error_description']
|
15
|
+
end
|
14
16
|
end
|
15
17
|
|
16
18
|
self.url = url
|
@@ -18,4 +20,6 @@ module Booker
|
|
18
20
|
end
|
19
21
|
|
20
22
|
class InvalidApiCredentials < Error; end
|
23
|
+
class ServiceUnavailable < Error; end
|
24
|
+
class RateLimitExceeded < Error; end
|
21
25
|
end
|
data/lib/booker/model.rb
ADDED
@@ -0,0 +1,84 @@
|
|
1
|
+
module Booker
|
2
|
+
class Model
|
3
|
+
CONSTANTIZE_MODULE = Booker
|
4
|
+
|
5
|
+
def initialize(options = {})
|
6
|
+
@attributes = []
|
7
|
+
options.each do |k, v|
|
8
|
+
send(:"#{k}=", v)
|
9
|
+
@attributes << k.to_sym
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def to_hash
|
14
|
+
hash = {}
|
15
|
+
@attributes.each do |attr|
|
16
|
+
value = self.send(attr)
|
17
|
+
if value.is_a? Array
|
18
|
+
new_value = hash_list(value)
|
19
|
+
elsif value.is_a? Booker::Model
|
20
|
+
new_value = value.to_hash
|
21
|
+
elsif value.is_a? Time
|
22
|
+
new_value = self.class.try(:time_to_booker_datetime, value) || value
|
23
|
+
elsif value.is_a? Date
|
24
|
+
time = value.in_time_zone
|
25
|
+
new_value = self.class.try(:time_to_booker_datetime, time) || value
|
26
|
+
else
|
27
|
+
new_value = value
|
28
|
+
end
|
29
|
+
hash[attr] = new_value
|
30
|
+
end
|
31
|
+
hash
|
32
|
+
end
|
33
|
+
|
34
|
+
def self.from_hash(hash)
|
35
|
+
model = self.new
|
36
|
+
hash.each do |k, v|
|
37
|
+
if model.respond_to?(:"#{k}")
|
38
|
+
constantized = self.constantize(k)
|
39
|
+
if constantized
|
40
|
+
if v.is_a?(Array) && v.first.is_a?(Hash)
|
41
|
+
model.send(:"#{k}=", constantized.from_list(v))
|
42
|
+
next
|
43
|
+
elsif v.is_a? Hash
|
44
|
+
model.send(:"#{k}=", constantized.from_hash(v))
|
45
|
+
next
|
46
|
+
end
|
47
|
+
end
|
48
|
+
if v.is_a?(String) && v.start_with?('/Date(')
|
49
|
+
model.send(:"#{k}=", try(:time_from_booker_datetime, v) || v)
|
50
|
+
next
|
51
|
+
end
|
52
|
+
model.send(:"#{k}=", v)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
model
|
56
|
+
end
|
57
|
+
|
58
|
+
def to_json; Oj.dump(to_hash, mode: :compat); end
|
59
|
+
|
60
|
+
def self.from_list(array); array.map { |item| self.from_hash(item) }; end
|
61
|
+
|
62
|
+
def self.constantize(key)
|
63
|
+
begin
|
64
|
+
self::CONSTANTIZE_MODULE.const_get key.to_s.camelize.singularize
|
65
|
+
rescue NameError
|
66
|
+
nil
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
private
|
71
|
+
|
72
|
+
def hash_list(array)
|
73
|
+
array.map do |item|
|
74
|
+
if item.is_a? Array
|
75
|
+
hash_list(item)
|
76
|
+
elsif item.is_a? Booker::Model
|
77
|
+
item.to_hash
|
78
|
+
else
|
79
|
+
item
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module Booker
|
2
|
+
module V41
|
3
|
+
class Availability < Client
|
4
|
+
include Booker::V4::RequestHelper
|
5
|
+
|
6
|
+
API_METHODS = {
|
7
|
+
class_availability: '/v4.1/availability/availability/class'.freeze
|
8
|
+
}.freeze
|
9
|
+
|
10
|
+
def class_availability(location_id:, from_start_date_time:, to_start_date_time:, options: {})
|
11
|
+
post API_METHODS[:class_availability], build_params({
|
12
|
+
FromStartDateTime: from_start_date_time,
|
13
|
+
LocationID: location_id,
|
14
|
+
OnlyIfAvailable: true,
|
15
|
+
ToStartDateTime: to_start_date_time,
|
16
|
+
ExcludeClosedDates: true
|
17
|
+
}, options), Booker::V4::Models::ClassInstance
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,87 @@
|
|
1
|
+
module Booker
|
2
|
+
module V41
|
3
|
+
class Booking < Client
|
4
|
+
include Booker::V4::RequestHelper
|
5
|
+
|
6
|
+
API_METHODS = {
|
7
|
+
appointment: '/v4.1/booking/appointment'.freeze,
|
8
|
+
cancel_appointment: '/v4.1/booking/appointment/cancel'.freeze,
|
9
|
+
create_appointment: '/v4.1/booking/appointment/create'.freeze,
|
10
|
+
appointment_hold: '/v4.1/booking/appointment/hold'.freeze,
|
11
|
+
employees: '/v4.1/booking/employees'.freeze,
|
12
|
+
services: '/v4.1/booking/services'.freeze,
|
13
|
+
location: '/v4.1/booking/location'.freeze,
|
14
|
+
locations: '/v4.1/booking/locations'.freeze
|
15
|
+
}.freeze
|
16
|
+
|
17
|
+
def appointment(id:)
|
18
|
+
get "#{API_METHODS[:appointment]}/#{id}", build_params, Booker::V4::Models::Appointment
|
19
|
+
end
|
20
|
+
|
21
|
+
def cancel_appointment(id:, options:{})
|
22
|
+
put API_METHODS[:cancel_appointment], build_params({ID: id}, options), Booker::V4::Models::Appointment
|
23
|
+
end
|
24
|
+
|
25
|
+
def create_appointment(location_id:, available_time:, customer:, options: {})
|
26
|
+
post API_METHODS[:create_appointment], build_params({
|
27
|
+
LocationID: location_id,
|
28
|
+
ItineraryTimeSlotList: [
|
29
|
+
TreatmentTimeSlots: [available_time]
|
30
|
+
],
|
31
|
+
Customer: customer
|
32
|
+
}, options), Booker::V4::Models::Appointment
|
33
|
+
end
|
34
|
+
|
35
|
+
def create_appointment_hold(location_id:, available_time:, customer:, options: {})
|
36
|
+
post API_METHODS[:appointment_hold], build_params({
|
37
|
+
LocationID: location_id,
|
38
|
+
ItineraryTimeSlot: {
|
39
|
+
TreatmentTimeSlots: [available_time]
|
40
|
+
},
|
41
|
+
Customer: customer
|
42
|
+
}, options)
|
43
|
+
end
|
44
|
+
|
45
|
+
def delete_appointment_hold(location_id:, incomplete_appointment_id:)
|
46
|
+
delete API_METHODS[:appointment_hold], nil, build_params({
|
47
|
+
LocationID: location_id,
|
48
|
+
IncompleteAppointmentID: incomplete_appointment_id
|
49
|
+
})
|
50
|
+
end
|
51
|
+
|
52
|
+
def employees(location_id:, fetch_all: true, options: {})
|
53
|
+
paginated_request(
|
54
|
+
method: :post,
|
55
|
+
path: API_METHODS[:employees],
|
56
|
+
params: build_params({LocationID: location_id}, options, true),
|
57
|
+
model: Booker::V4::Models::Employee,
|
58
|
+
fetch_all: fetch_all
|
59
|
+
)
|
60
|
+
end
|
61
|
+
|
62
|
+
def location(id:)
|
63
|
+
response = get("#{API_METHODS[:location]}/#{id}", build_params)
|
64
|
+
Booker::V4::Models::Location.from_hash(response)
|
65
|
+
end
|
66
|
+
|
67
|
+
def locations(options: {})
|
68
|
+
paginated_request(
|
69
|
+
method: :post,
|
70
|
+
path: API_METHODS[:locations],
|
71
|
+
params: build_params({}, options, true),
|
72
|
+
model: Booker::V4::Models::Location
|
73
|
+
)
|
74
|
+
end
|
75
|
+
|
76
|
+
def services(location_id:, fetch_all: true, params: {})
|
77
|
+
paginated_request(
|
78
|
+
method: :post,
|
79
|
+
path: API_METHODS[:services],
|
80
|
+
params: build_params({LocationID: location_id}, params, true),
|
81
|
+
model: Booker::V4::Models::Treatment,
|
82
|
+
fetch_all: fetch_all
|
83
|
+
)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
end
|
87
|
+
end
|