booker_ruby 1.0.1
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 +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
|