booker_ruby 1.0.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/lib/booker/booker.rb +5 -0
- data/lib/booker/business_client.rb +32 -0
- data/lib/booker/business_rest.rb +89 -0
- data/lib/booker/client.rb +182 -0
- data/lib/booker/common_rest.rb +38 -0
- data/lib/booker/customer_client.rb +31 -0
- data/lib/booker/customer_rest.rb +43 -0
- data/lib/booker/errors.rb +19 -0
- data/lib/booker/generic_token_store.rb +25 -0
- data/lib/booker/models/address.rb +12 -0
- data/lib/booker/models/appointment.rb +77 -0
- data/lib/booker/models/appointment_treatment.rb +51 -0
- data/lib/booker/models/available_time.rb +13 -0
- data/lib/booker/models/business_type.rb +5 -0
- data/lib/booker/models/category.rb +5 -0
- data/lib/booker/models/class_instance.rb +25 -0
- data/lib/booker/models/country.rb +5 -0
- data/lib/booker/models/current_price.rb +5 -0
- data/lib/booker/models/customer.rb +51 -0
- data/lib/booker/models/customer_2.rb +5 -0
- data/lib/booker/models/customer_record_type.rb +5 -0
- data/lib/booker/models/discount.rb +5 -0
- data/lib/booker/models/dynamic_price.rb +11 -0
- data/lib/booker/models/employee.rb +10 -0
- data/lib/booker/models/final_total.rb +5 -0
- data/lib/booker/models/gender.rb +5 -0
- data/lib/booker/models/location.rb +18 -0
- data/lib/booker/models/model.rb +123 -0
- data/lib/booker/models/online_booking_settings.rb +23 -0
- data/lib/booker/models/original_price.rb +5 -0
- data/lib/booker/models/payment_method.rb +5 -0
- data/lib/booker/models/preferred_staff_gender.rb +5 -0
- data/lib/booker/models/price.rb +8 -0
- data/lib/booker/models/receipt_display_price.rb +5 -0
- data/lib/booker/models/room.rb +12 -0
- data/lib/booker/models/shipping_address.rb +5 -0
- data/lib/booker/models/source.rb +5 -0
- data/lib/booker/models/spa.rb +5 -0
- data/lib/booker/models/spa_employee_availability_search_item.rb +11 -0
- data/lib/booker/models/status.rb +5 -0
- data/lib/booker/models/sub_category.rb +5 -0
- data/lib/booker/models/tag_price.rb +5 -0
- data/lib/booker/models/teacher.rb +5 -0
- data/lib/booker/models/teacher_2.rb +5 -0
- data/lib/booker/models/time_zone.rb +8 -0
- data/lib/booker/models/treatment.rb +19 -0
- data/lib/booker/models/type.rb +8 -0
- data/lib/booker/models/user.rb +73 -0
- data/lib/booker/version.rb +3 -0
- data/lib/booker_ruby.rb +86 -0
- metadata +192 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 8b39923daabcfec1bb62dd8c6c0a479249cd6846
|
4
|
+
data.tar.gz: cd5967c5745415270ea1d9d16f53b58b2a5d11f0
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: bc9b5ff26181dcb216b03755ac25467e87ee928a9156a6f726692bf741bbfd5ccb1bfc321076aa8f1cd68283d53e9fe6e3d9f0f4aa0fa38f1ac634717f2f1867
|
7
|
+
data.tar.gz: 3013213b0f4c4d360ddabbf8877b1d68a079bbc24a0872c9a3c763e20410293c8cfff9ea82607784086f27637ab8d82c0bdcf5d6273984df874d2f17ded0b6e9
|
@@ -0,0 +1,32 @@
|
|
1
|
+
module Booker
|
2
|
+
class BusinessClient < Client
|
3
|
+
include Booker::BusinessREST
|
4
|
+
|
5
|
+
attr_accessor :booker_account_name, :booker_username, :booker_password
|
6
|
+
|
7
|
+
def initialize(options={})
|
8
|
+
self.base_url = ENV['BOOKER_BUSINESS_SERVICE_URL'] || 'https://apicurrent-app.booker.ninja/webservice4/json/BusinessService.svc'
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_access_token
|
13
|
+
http_options = {
|
14
|
+
client_id: self.client_id,
|
15
|
+
client_secret: self.client_secret,
|
16
|
+
'AccountName' => self.booker_account_name,
|
17
|
+
'UserName' => self.booker_username,
|
18
|
+
'Password' => self.booker_password
|
19
|
+
}
|
20
|
+
response = post('/accountlogin', http_options, nil).parsed_response
|
21
|
+
|
22
|
+
raise Booker::InvalidApiCredentials.new(http_options, response) unless response.present?
|
23
|
+
|
24
|
+
self.temp_access_token_expires_at = Time.now + response['expires_in'].to_i.seconds
|
25
|
+
self.temp_access_token = response['access_token']
|
26
|
+
|
27
|
+
update_token_store
|
28
|
+
|
29
|
+
self.temp_access_token
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,89 @@
|
|
1
|
+
module Booker
|
2
|
+
module BusinessREST
|
3
|
+
include CommonREST
|
4
|
+
|
5
|
+
def get_logged_in_user
|
6
|
+
response = get('/user', build_params)
|
7
|
+
result = Booker::Models::User.from_hash(response['User'])
|
8
|
+
result.LocationID = response['LocationID']
|
9
|
+
result.BrandID = response['BrandID']
|
10
|
+
result
|
11
|
+
end
|
12
|
+
|
13
|
+
def get_location(booker_location_id:)
|
14
|
+
response = get("/location/#{booker_location_id}", build_params)
|
15
|
+
Booker::Models::Location.from_hash(response)
|
16
|
+
end
|
17
|
+
|
18
|
+
def find_locations(params: {})
|
19
|
+
paginated_request(
|
20
|
+
method: :post,
|
21
|
+
path: '/locations',
|
22
|
+
params: build_params({}, params, true),
|
23
|
+
model: Booker::Models::Location
|
24
|
+
)
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_employees(booker_location_id:, fetch_all: true, params: {})
|
28
|
+
paginated_request(
|
29
|
+
method: :post,
|
30
|
+
path: '/employees',
|
31
|
+
params: build_params({'LocationID' => booker_location_id}, params, true),
|
32
|
+
model: Booker::Models::Employee,
|
33
|
+
fetch_all: fetch_all
|
34
|
+
)
|
35
|
+
end
|
36
|
+
|
37
|
+
def find_treatments(booker_location_id:, fetch_all: true, params: {})
|
38
|
+
paginated_request(
|
39
|
+
method: :post,
|
40
|
+
path: '/treatments',
|
41
|
+
params: build_params({'LocationID' => booker_location_id}, params, true),
|
42
|
+
model: Booker::Models::Treatment,
|
43
|
+
fetch_all: fetch_all
|
44
|
+
)
|
45
|
+
end
|
46
|
+
|
47
|
+
def find_customers(booker_location_id:, fetch_all: true, params: {})
|
48
|
+
additional_params = {
|
49
|
+
'FilterByExactLocationID' => true,
|
50
|
+
'LocationID' => booker_location_id,
|
51
|
+
'CustomerRecordType' => 1,
|
52
|
+
}
|
53
|
+
|
54
|
+
paginated_request(
|
55
|
+
method: :post,
|
56
|
+
path: '/customers',
|
57
|
+
params: build_params(additional_params, params, true),
|
58
|
+
model: Booker::Models::Customer,
|
59
|
+
fetch_all: fetch_all
|
60
|
+
)
|
61
|
+
end
|
62
|
+
|
63
|
+
def find_appointments(booker_location_id:, start_at:, end_at:, fetch_all: true, params: {})
|
64
|
+
additional_params = {
|
65
|
+
'LocationID' => booker_location_id,
|
66
|
+
'FromStartDate' => start_at,
|
67
|
+
'ToStartDate' => end_at
|
68
|
+
}
|
69
|
+
|
70
|
+
paginated_request(
|
71
|
+
method: :post,
|
72
|
+
path: '/appointments',
|
73
|
+
params: build_params(additional_params, params, true),
|
74
|
+
model: Booker::Models::Appointment,
|
75
|
+
fetch_all: fetch_all
|
76
|
+
)
|
77
|
+
end
|
78
|
+
|
79
|
+
def create_special(booker_location_id:, start_date:, end_date:, coupon_code:, name:, params: {})
|
80
|
+
post('/special', build_params({
|
81
|
+
'LocationID' => booker_location_id,
|
82
|
+
'ApplicableStartDate' => start_date.in_time_zone,
|
83
|
+
'ApplicableEndDate' => end_date.in_time_zone,
|
84
|
+
'CouponCode' => coupon_code,
|
85
|
+
'Name' => name
|
86
|
+
}, params))
|
87
|
+
end
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,182 @@
|
|
1
|
+
module Booker
|
2
|
+
class Client
|
3
|
+
attr_accessor :base_url, :client_id, :client_secret, :temp_access_token, :temp_access_token_expires_at, :token_store, :token_store_callback_method
|
4
|
+
|
5
|
+
TimeZone = 'Eastern Time (US & Canada)'.freeze
|
6
|
+
|
7
|
+
def initialize(options = {})
|
8
|
+
options.each do |key, value|
|
9
|
+
send(:"#{key}=", value)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
|
13
|
+
def get(path, params, booker_model=nil)
|
14
|
+
booker_resources = get_booker_resources(:get, path, params, nil, booker_model)
|
15
|
+
|
16
|
+
build_resources(booker_resources, booker_model)
|
17
|
+
end
|
18
|
+
|
19
|
+
def post(path, data, booker_model=nil)
|
20
|
+
booker_resources = get_booker_resources(:post, path, nil, data.to_json, booker_model)
|
21
|
+
|
22
|
+
build_resources(booker_resources, booker_model)
|
23
|
+
end
|
24
|
+
|
25
|
+
def put(path, data, booker_model=nil)
|
26
|
+
booker_resources = get_booker_resources(:put, path, nil, data.to_json, booker_model)
|
27
|
+
|
28
|
+
build_resources(booker_resources, booker_model)
|
29
|
+
end
|
30
|
+
|
31
|
+
def paginated_request(method:, path:, params:, model: nil, fetched: [], fetch_all: true)
|
32
|
+
page_size = params['PageSize']
|
33
|
+
page_number = params['PageNumber']
|
34
|
+
|
35
|
+
if page_size.nil? || page_size < 1 || page_number.nil? || page_number < 1 || !params['UsePaging']
|
36
|
+
raise ArgumentError, 'params must include valid PageSize, PageNumber and UsePaging'
|
37
|
+
end
|
38
|
+
|
39
|
+
puts "fetching #{path} with #{params.except('access_token')}. #{fetched.length} results so far."
|
40
|
+
|
41
|
+
results = self.send(method, path, params, model)
|
42
|
+
|
43
|
+
unless results.is_a?(Array)
|
44
|
+
raise StandardError, "Result from paginated request to #{path} with params: #{params} is not a collection"
|
45
|
+
end
|
46
|
+
|
47
|
+
fetched.concat(results)
|
48
|
+
results_length = results.length
|
49
|
+
|
50
|
+
if fetch_all
|
51
|
+
if results_length > 0
|
52
|
+
# TODO (#111186744): Add logging to see if any pages with less than expected data (as seen in the /appointments endpoint)
|
53
|
+
new_params = params.deep_dup
|
54
|
+
new_params['PageNumber'] = page_number + 1
|
55
|
+
paginated_request(method: method, path: path, params: new_params, model: model, fetched: fetched)
|
56
|
+
else
|
57
|
+
fetched
|
58
|
+
end
|
59
|
+
else
|
60
|
+
results
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def log_issue(message, extra_info = {})
|
65
|
+
if (log_message_block = Booker.config[:log_message])
|
66
|
+
log_message_block.call(message, extra_info)
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def get_booker_resources(http_method, path, params=nil, body=nil, booker_model=nil)
|
71
|
+
http_options = request_options(params, body)
|
72
|
+
puts "BOOKER REQUEST: #{http_method} #{path} #{http_options}" if ENV['BOOKER_API_DEBUG'] == 'true'
|
73
|
+
|
74
|
+
# Allow it to retry the first time unless it is an authorization error
|
75
|
+
begin
|
76
|
+
booker_resources = handle_errors!(http_options, HTTParty.send(http_method, "#{self.base_url}#{path}", http_options))
|
77
|
+
rescue Booker::Error, Net::ReadTimeout => ex
|
78
|
+
if ex.is_a? Booker::InvalidApiCredentials
|
79
|
+
raise ex
|
80
|
+
else
|
81
|
+
sleep 1
|
82
|
+
booker_resources = nil
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
if booker_resources
|
87
|
+
results_from_response(booker_resources, booker_model)
|
88
|
+
else
|
89
|
+
booker_resources = handle_errors!(http_options, HTTParty.send(http_method, "#{self.base_url}#{path}", http_options))
|
90
|
+
|
91
|
+
if booker_resources
|
92
|
+
results_from_response(booker_resources, booker_model)
|
93
|
+
else
|
94
|
+
raise Booker::Error.new(http_options, booker_resources)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
def handle_errors!(request, response)
|
100
|
+
puts "BOOKER RESPONSE: #{response}" if ENV['BOOKER_API_DEBUG'] == 'true'
|
101
|
+
|
102
|
+
ex = Booker::Error.new(request, response)
|
103
|
+
if ex.error.present? || !response.success?
|
104
|
+
case ex.error
|
105
|
+
when 'invalid_client'
|
106
|
+
raise Booker::InvalidApiCredentials.new(request, response)
|
107
|
+
when 'invalid access token'
|
108
|
+
get_access_token
|
109
|
+
return nil
|
110
|
+
else
|
111
|
+
raise ex
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
response
|
116
|
+
end
|
117
|
+
|
118
|
+
def access_token
|
119
|
+
if self.temp_access_token && !temp_access_token_expired?
|
120
|
+
self.temp_access_token
|
121
|
+
else
|
122
|
+
get_access_token
|
123
|
+
end
|
124
|
+
end
|
125
|
+
|
126
|
+
private
|
127
|
+
def request_options(query=nil, body=nil)
|
128
|
+
options = {
|
129
|
+
headers: {
|
130
|
+
'Content-Type' => 'application/json; charset=utf-8'
|
131
|
+
},
|
132
|
+
timeout: 120
|
133
|
+
}
|
134
|
+
|
135
|
+
if body.present?
|
136
|
+
options[:body] = body
|
137
|
+
end
|
138
|
+
|
139
|
+
if query.present?
|
140
|
+
options[:query] = query
|
141
|
+
end
|
142
|
+
|
143
|
+
options
|
144
|
+
end
|
145
|
+
|
146
|
+
def build_resources(resources, booker_model)
|
147
|
+
return resources if booker_model.nil?
|
148
|
+
|
149
|
+
if resources.is_a? Hash
|
150
|
+
booker_model.from_hash(resources)
|
151
|
+
elsif resources.is_a? Array
|
152
|
+
booker_model.from_list(resources)
|
153
|
+
else
|
154
|
+
resources
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
def temp_access_token_expired?
|
159
|
+
self.temp_access_token_expires_at.nil? || self.temp_access_token_expires_at <= Time.now
|
160
|
+
end
|
161
|
+
|
162
|
+
def update_token_store
|
163
|
+
if self.token_store && self.token_store_callback_method
|
164
|
+
token_store.send(token_store_callback_method, self.temp_access_token, self.temp_access_token_expires_at)
|
165
|
+
end
|
166
|
+
end
|
167
|
+
|
168
|
+
def results_from_response(response, booker_model=nil)
|
169
|
+
return response['Results'] unless response['Results'].nil?
|
170
|
+
|
171
|
+
if booker_model
|
172
|
+
model_name = booker_model.to_s.demodulize
|
173
|
+
return response[model_name] unless response[model_name].nil?
|
174
|
+
|
175
|
+
pluralized = model_name.pluralize
|
176
|
+
return response[pluralized] unless response[pluralized].nil?
|
177
|
+
end
|
178
|
+
|
179
|
+
response
|
180
|
+
end
|
181
|
+
end
|
182
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
module Booker
|
2
|
+
module CommonREST
|
3
|
+
DEFAULT_PAGINATION_PARAMS = {
|
4
|
+
'UsePaging' => true,
|
5
|
+
'PageSize' => Integer(ENV['BOOKER_DEFAULT_PAGE_SIZE'] || 10),
|
6
|
+
'PageNumber' => 1
|
7
|
+
}
|
8
|
+
|
9
|
+
def get_online_booking_settings(booker_location_id:)
|
10
|
+
response = get("/location/#{booker_location_id}/online_booking_settings", build_params)
|
11
|
+
Booker::Models::OnlineBookingSettings.from_hash(response['OnlineBookingSettings'])
|
12
|
+
end
|
13
|
+
|
14
|
+
def confirm_appointment(appointment_id:)
|
15
|
+
put '/appointment/confirm', build_params('ID' => appointment_id), Booker::Models::Appointment
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def build_params(default_params={}, overrides={}, paginated=false)
|
21
|
+
merged = {"access_token" => access_token}.merge(default_params.merge(overrides))
|
22
|
+
|
23
|
+
merged.each do |k, v|
|
24
|
+
if v.is_a?(Time) || v.is_a?(DateTime)
|
25
|
+
merged[k] = Booker::Models::Model.time_to_booker_datetime(v)
|
26
|
+
elsif v.is_a?(Date)
|
27
|
+
merged[k] = Booker::Models::Model.time_to_booker_datetime(v.in_time_zone)
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
if paginated
|
32
|
+
DEFAULT_PAGINATION_PARAMS.merge(merged)
|
33
|
+
else
|
34
|
+
merged
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module Booker
|
2
|
+
class CustomerClient < Client
|
3
|
+
include Booker::CustomerREST
|
4
|
+
|
5
|
+
def initialize(options={})
|
6
|
+
self.base_url = ENV['BOOKER_CUSTOMER_SERVICE_URL'] || 'https://apicurrent-app.booker.ninja/webservice4/json/CustomerService.svc'
|
7
|
+
self.token_store = GenericTokenStore
|
8
|
+
self.token_store_callback_method = :update_booker_access_token!
|
9
|
+
super
|
10
|
+
end
|
11
|
+
|
12
|
+
def get_access_token
|
13
|
+
http_options = {
|
14
|
+
client_id: self.client_id,
|
15
|
+
client_secret: self.client_secret,
|
16
|
+
grant_type: 'client_credentials'
|
17
|
+
}
|
18
|
+
|
19
|
+
response = get("/access_token", http_options, nil).parsed_response
|
20
|
+
|
21
|
+
raise Booker::InvalidApiCredentials.new(http_options, response) unless response.present?
|
22
|
+
|
23
|
+
self.temp_access_token_expires_at = Time.now + response['expires_in'].to_i.seconds
|
24
|
+
self.temp_access_token = response['access_token']
|
25
|
+
|
26
|
+
update_token_store
|
27
|
+
|
28
|
+
self.temp_access_token
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,43 @@
|
|
1
|
+
module Booker
|
2
|
+
module CustomerREST
|
3
|
+
include CommonREST
|
4
|
+
|
5
|
+
def create_appointment(booker_location_id:, available_time:, customer:, options: {})
|
6
|
+
post '/appointment/create', build_params({
|
7
|
+
'LocationID' => booker_location_id,
|
8
|
+
'ItineraryTimeSlotList' => [
|
9
|
+
'TreatmentTimeSlots' => [available_time]
|
10
|
+
],
|
11
|
+
'Customer' => customer
|
12
|
+
}, options), Booker::Models::Appointment
|
13
|
+
end
|
14
|
+
|
15
|
+
def create_class_appointment(booker_location_id:, class_instance_id:, customer:, options: {})
|
16
|
+
post '/class_appointment/create', build_params({
|
17
|
+
'LocationID' => booker_location_id,
|
18
|
+
'ClassInstanceID' => class_instance_id,
|
19
|
+
'Customer' => customer
|
20
|
+
}, options), Booker::Models::Appointment
|
21
|
+
end
|
22
|
+
|
23
|
+
def run_multi_spa_multi_sub_category_availability(booker_location_ids:, treatment_sub_category_ids:, start_date_time:, end_date_time:, options: {})
|
24
|
+
post '/availability/multispamultisubcategory', build_params({
|
25
|
+
'LocationIDs' => booker_location_ids,
|
26
|
+
'TreatmentSubCategoryIDs' => treatment_sub_category_ids,
|
27
|
+
'StartDateTime' => start_date_time,
|
28
|
+
'EndDateTime' => end_date_time,
|
29
|
+
'MaxTimesPerTreatment' => 1000
|
30
|
+
}, options), Booker::Models::SpaEmployeeAvailabilitySearchItem
|
31
|
+
end
|
32
|
+
|
33
|
+
def run_class_availability(booker_location_id:, from_start_date_time:, to_start_date_time:, options: {})
|
34
|
+
post '/availability/class', build_params({
|
35
|
+
'FromStartDateTime' => from_start_date_time,
|
36
|
+
'LocationID' => booker_location_id,
|
37
|
+
'OnlyIfAvailable' => true,
|
38
|
+
'ToStartDateTime' => to_start_date_time,
|
39
|
+
'ExcludeClosedDates' => true
|
40
|
+
}, options), Booker::Models::ClassInstance
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module Booker
|
2
|
+
class Error < StandardError
|
3
|
+
attr_accessor :error, :description, :request, :response
|
4
|
+
|
5
|
+
def initialize(request = nil, response = nil)
|
6
|
+
if request.present?
|
7
|
+
self.request = request
|
8
|
+
end
|
9
|
+
|
10
|
+
if response.present?
|
11
|
+
self.response = response
|
12
|
+
self.error = response['error'] || response['ErrorMessage']
|
13
|
+
self.description = response['error_description']
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
|
18
|
+
class InvalidApiCredentials < Error; end
|
19
|
+
end
|