booker_api 0.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 +22 -0
- data/lib/booker/business_rest.rb +130 -0
- data/lib/booker/client.rb +212 -0
- data/lib/booker/common_rest.rb +49 -0
- data/lib/booker/config/booker_country_ids_to_iso_codes.yml +269 -0
- data/lib/booker/customer_client.rb +17 -0
- data/lib/booker/customer_rest.rb +169 -0
- data/lib/booker/errors.rb +24 -0
- data/lib/booker/generic_token_store.rb +25 -0
- data/lib/booker/helpers/active_support_helper.rb +102 -0
- data/lib/booker/helpers/logging_helper.rb +11 -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 +19 -0
- data/lib/booker/models/current_price.rb +5 -0
- data/lib/booker/models/customer.rb +52 -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/feature_settings.rb +7 -0
- data/lib/booker/models/final_total.rb +5 -0
- data/lib/booker/models/gender.rb +5 -0
- data/lib/booker/models/itinerary_time_slot.rb +7 -0
- data/lib/booker/models/itinerary_time_slots_list.rb +7 -0
- data/lib/booker/models/location.rb +23 -0
- data/lib/booker/models/location_day_schedule.rb +18 -0
- data/lib/booker/models/model.rb +150 -0
- data/lib/booker/models/multi_service_availability_result.rb +7 -0
- data/lib/booker/models/notification_settings.rb +12 -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/treatment_time_slot.rb +5 -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_api.rb +97 -0
- metadata +216 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 834274a062ffb044410b9ccb07bb89c9c8f8c1ee
|
4
|
+
data.tar.gz: e2e8c83d3a7a653ed77da005d63991a58d76533b
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: dcc528461d36104babb6fadab2ecc7995ad86b770dfe560143c3a8664fb671294ab8952973769f3533fd7241ac2d6e114124d26736a885490d8cfea62e439b71
|
7
|
+
data.tar.gz: 6537a29844b3cefefd97a397b65545bdd8dc39f61d881cdda6ad1387363e7186079aa4dbf00e11f1d3989f37987cb64d144f2540abb76432c71f982aec41f12c
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Booker
|
2
|
+
class BusinessClient < Client
|
3
|
+
include Booker::BusinessREST
|
4
|
+
|
5
|
+
ACCESS_TOKEN_HTTP_METHOD = :post
|
6
|
+
ACCESS_TOKEN_ENDPOINT = '/accountlogin'.freeze
|
7
|
+
|
8
|
+
attr_accessor :booker_account_name, :booker_username, :booker_password
|
9
|
+
|
10
|
+
def env_base_url_key; 'BOOKER_BUSINESS_SERVICE_URL'; end
|
11
|
+
|
12
|
+
def default_base_url; 'https://apicurrent-app.booker.ninja/webservice4/json/BusinessService.svc'; end
|
13
|
+
|
14
|
+
def access_token_options
|
15
|
+
super.merge!(
|
16
|
+
'AccountName' => self.booker_account_name,
|
17
|
+
'UserName' => self.booker_username,
|
18
|
+
'Password' => self.booker_password
|
19
|
+
)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,130 @@
|
|
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_day_schedules(booker_location_id:, params: {})
|
14
|
+
# Booker requires fromDate and toDate for JSON API, but does not use them when getDefaultDaySchedule is true
|
15
|
+
# So datetime used for these fields does not matter
|
16
|
+
random_datetime = Booker::Models::Model.time_to_booker_datetime(Time.now)
|
17
|
+
|
18
|
+
additional_params = {'getDefaultDaySchedule' => true, 'fromDate' => random_datetime, 'toDate' => random_datetime}
|
19
|
+
response = get("/location/#{booker_location_id}/schedule", build_params(additional_params, params))
|
20
|
+
response['LocationDaySchedules'].map { |sched| Booker::Models::LocationDaySchedule.from_hash(sched) }
|
21
|
+
end
|
22
|
+
|
23
|
+
def find_locations(params: {})
|
24
|
+
paginated_request(
|
25
|
+
method: :post,
|
26
|
+
path: '/locations',
|
27
|
+
params: build_params({}, params, true),
|
28
|
+
model: Booker::Models::Location
|
29
|
+
)
|
30
|
+
end
|
31
|
+
|
32
|
+
def find_employees(booker_location_id:, fetch_all: true, params: {})
|
33
|
+
paginated_request(
|
34
|
+
method: :post,
|
35
|
+
path: '/employees',
|
36
|
+
params: build_params({'LocationID' => booker_location_id}, params, true),
|
37
|
+
model: Booker::Models::Employee,
|
38
|
+
fetch_all: fetch_all
|
39
|
+
)
|
40
|
+
end
|
41
|
+
|
42
|
+
def find_treatments(booker_location_id:, fetch_all: true, params: {})
|
43
|
+
paginated_request(
|
44
|
+
method: :post,
|
45
|
+
path: '/treatments',
|
46
|
+
params: build_params({'LocationID' => booker_location_id}, params, true),
|
47
|
+
model: Booker::Models::Treatment,
|
48
|
+
fetch_all: fetch_all
|
49
|
+
)
|
50
|
+
end
|
51
|
+
|
52
|
+
def find_customers(booker_location_id:, fetch_all: true, params: {})
|
53
|
+
additional_params = {
|
54
|
+
'FilterByExactLocationID' => true,
|
55
|
+
'LocationID' => booker_location_id,
|
56
|
+
'CustomerRecordType' => 1,
|
57
|
+
}
|
58
|
+
|
59
|
+
paginated_request(
|
60
|
+
method: :post,
|
61
|
+
path: '/customers',
|
62
|
+
params: build_params(additional_params, params, true),
|
63
|
+
model: Booker::Models::Customer,
|
64
|
+
fetch_all: fetch_all
|
65
|
+
)
|
66
|
+
end
|
67
|
+
|
68
|
+
def find_appointments(booker_location_id:, start_date:, end_date:, fetch_all: true, params: {})
|
69
|
+
additional_params = {
|
70
|
+
'LocationID' => booker_location_id,
|
71
|
+
'FromStartDate' => start_date.to_date,
|
72
|
+
'ToStartDate' => end_date.to_date
|
73
|
+
}
|
74
|
+
|
75
|
+
paginated_request(
|
76
|
+
method: :post,
|
77
|
+
path: '/appointments',
|
78
|
+
params: build_params(additional_params, params, true),
|
79
|
+
model: Booker::Models::Appointment,
|
80
|
+
fetch_all: fetch_all
|
81
|
+
)
|
82
|
+
end
|
83
|
+
|
84
|
+
def create_special(booker_location_id:, start_date:, end_date:, coupon_code:, name:, params: {})
|
85
|
+
post('/special', build_params({
|
86
|
+
'LocationID' => booker_location_id,
|
87
|
+
'ApplicableStartDate' => start_date.in_time_zone,
|
88
|
+
'ApplicableEndDate' => end_date.in_time_zone,
|
89
|
+
'CouponCode' => coupon_code,
|
90
|
+
'Name' => name
|
91
|
+
}, params))
|
92
|
+
end
|
93
|
+
|
94
|
+
def get_location_notification_settings(booker_location_id:)
|
95
|
+
response = get "/location/#{booker_location_id}/notification_settings", build_params
|
96
|
+
Booker::Models::NotificationSettings.from_hash response['NotificationSettings']
|
97
|
+
end
|
98
|
+
|
99
|
+
def update_location_notification_settings(booker_location_id:, send_appointment_reminders:)
|
100
|
+
put "/location/#{booker_location_id}/notification_settings", build_params({
|
101
|
+
NotificationSettings: {
|
102
|
+
SendAppointmentReminders: send_appointment_reminders
|
103
|
+
}
|
104
|
+
})
|
105
|
+
end
|
106
|
+
|
107
|
+
def get_location_feature_settings(booker_location_id:)
|
108
|
+
response = get "/location/#{booker_location_id}/feature_settings", build_params
|
109
|
+
Booker::Models::FeatureSettings.from_hash response['FeatureSettings']
|
110
|
+
end
|
111
|
+
|
112
|
+
def get_appointment(appointment_id)
|
113
|
+
get "/appointment/#{appointment_id}", build_params
|
114
|
+
end
|
115
|
+
|
116
|
+
def cancel_appointment(appointment_id)
|
117
|
+
put "/appointment/cancel", build_params({
|
118
|
+
'ID' => appointment_id
|
119
|
+
})
|
120
|
+
end
|
121
|
+
|
122
|
+
def place_order(order_id, amount)
|
123
|
+
post "/order/#{order_id}/place_order", build_params
|
124
|
+
end
|
125
|
+
|
126
|
+
def take_deposit(order_id)
|
127
|
+
post "/order/#{order_id}/deposit", build_params
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,212 @@
|
|
1
|
+
module Booker
|
2
|
+
class Client
|
3
|
+
attr_accessor :base_url, :client_id, :client_secret, :temp_access_token, :temp_access_token_expires_at,
|
4
|
+
:token_store, :token_store_callback_method
|
5
|
+
|
6
|
+
ACCESS_TOKEN_HTTP_METHOD = :get
|
7
|
+
ACCESS_TOKEN_ENDPOINT = '/access_token'.freeze
|
8
|
+
TimeZone = 'Eastern Time (US & Canada)'.freeze
|
9
|
+
|
10
|
+
def initialize(options = {})
|
11
|
+
options.each { |key, value| send(:"#{key}=", value) }
|
12
|
+
self.base_url ||= get_base_url
|
13
|
+
self.client_id ||= ENV['BOOKER_CLIENT_ID']
|
14
|
+
self.client_secret ||= ENV['BOOKER_CLIENT_SECRET']
|
15
|
+
end
|
16
|
+
|
17
|
+
def get_base_url
|
18
|
+
env_key = try(:env_base_url_key)
|
19
|
+
|
20
|
+
if env_key.present?
|
21
|
+
ENV[env_key] || try(:default_base_url)
|
22
|
+
else
|
23
|
+
try(:default_base_url)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def get(path, params, booker_model=nil)
|
28
|
+
booker_resources = get_booker_resources(:get, path, params, nil, booker_model)
|
29
|
+
|
30
|
+
build_resources(booker_resources, booker_model)
|
31
|
+
end
|
32
|
+
|
33
|
+
def post(path, data, booker_model=nil)
|
34
|
+
booker_resources = get_booker_resources(:post, path, nil, data.to_json, booker_model)
|
35
|
+
|
36
|
+
build_resources(booker_resources, booker_model)
|
37
|
+
end
|
38
|
+
|
39
|
+
def put(path, data, booker_model=nil)
|
40
|
+
booker_resources = get_booker_resources(:put, path, nil, data.to_json, booker_model)
|
41
|
+
|
42
|
+
build_resources(booker_resources, booker_model)
|
43
|
+
end
|
44
|
+
|
45
|
+
def paginated_request(method:, path:, params:, model: nil, fetched: [], fetch_all: true)
|
46
|
+
page_size = params['PageSize']
|
47
|
+
page_number = params['PageNumber']
|
48
|
+
|
49
|
+
if page_size.nil? || page_size < 1 || page_number.nil? || page_number < 1 || !params['UsePaging']
|
50
|
+
raise ArgumentError, 'params must include valid PageSize, PageNumber and UsePaging'
|
51
|
+
end
|
52
|
+
|
53
|
+
puts "fetching #{path} with #{params.except('access_token')}. #{fetched.length} results so far."
|
54
|
+
|
55
|
+
results = self.send(method, path, params, model)
|
56
|
+
|
57
|
+
unless results.is_a?(Array)
|
58
|
+
raise StandardError, "Result from paginated request to #{path} with params: #{params} is not a collection"
|
59
|
+
end
|
60
|
+
|
61
|
+
fetched.concat(results)
|
62
|
+
results_length = results.length
|
63
|
+
|
64
|
+
if fetch_all
|
65
|
+
if results_length > 0
|
66
|
+
# TODO (#111186744): Add logging to see if any pages with less than expected data (as seen in the /appointments endpoint)
|
67
|
+
new_params = params.deep_dup
|
68
|
+
new_params['PageNumber'] = page_number + 1
|
69
|
+
paginated_request(method: method, path: path, params: new_params, model: model, fetched: fetched)
|
70
|
+
else
|
71
|
+
fetched
|
72
|
+
end
|
73
|
+
else
|
74
|
+
results
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
def get_booker_resources(http_method, path, params=nil, body=nil, booker_model=nil)
|
79
|
+
http_options = request_options(params, body)
|
80
|
+
url = full_url(path)
|
81
|
+
puts "BOOKER REQUEST: #{http_method} #{url} #{http_options}" if ENV['BOOKER_API_DEBUG'] == 'true'
|
82
|
+
|
83
|
+
# Allow it to retry the first time unless it is an authorization error
|
84
|
+
begin
|
85
|
+
booker_resources = handle_errors!(url, http_options, HTTParty.send(http_method, url, http_options))
|
86
|
+
rescue Booker::Error, Net::ReadTimeout => ex
|
87
|
+
if ex.is_a? Booker::InvalidApiCredentials
|
88
|
+
raise ex
|
89
|
+
else
|
90
|
+
sleep 1
|
91
|
+
booker_resources = nil
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
return results_from_response(booker_resources, booker_model) if booker_resources.present?
|
96
|
+
booker_resources = handle_errors!(url, http_options, HTTParty.send(http_method, url, http_options))
|
97
|
+
return results_from_response(booker_resources, booker_model) if booker_resources.present?
|
98
|
+
raise Booker::Error.new(url: url, request: http_options, response: booker_resources)
|
99
|
+
end
|
100
|
+
|
101
|
+
def full_url(path)
|
102
|
+
uri = URI(path)
|
103
|
+
uri.scheme ? path : "#{self.base_url}#{path}"
|
104
|
+
end
|
105
|
+
|
106
|
+
def handle_errors!(url, request, response)
|
107
|
+
puts "BOOKER RESPONSE: #{response}" if ENV['BOOKER_API_DEBUG'] == 'true'
|
108
|
+
|
109
|
+
ex = Booker::Error.new(url: url, request: request, response: response)
|
110
|
+
if ex.error.present? || !response.success?
|
111
|
+
case ex.error
|
112
|
+
when 'invalid_client'
|
113
|
+
raise Booker::InvalidApiCredentials.new(url: url, request: request, response: response)
|
114
|
+
# when 'invalid access token'
|
115
|
+
# get_access_token
|
116
|
+
# return nil
|
117
|
+
else
|
118
|
+
raise ex
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
response
|
123
|
+
end
|
124
|
+
|
125
|
+
def access_token
|
126
|
+
(self.temp_access_token && !temp_access_token_expired?) ? self.temp_access_token : get_access_token
|
127
|
+
end
|
128
|
+
|
129
|
+
def access_token_options
|
130
|
+
{
|
131
|
+
client_id: self.client_id,
|
132
|
+
client_secret: self.client_secret
|
133
|
+
}
|
134
|
+
end
|
135
|
+
|
136
|
+
def update_token_store
|
137
|
+
if self.token_store.present? && self.token_store_callback_method.present?
|
138
|
+
self.token_store.send(self.token_store_callback_method, self.temp_access_token, self.temp_access_token_expires_at)
|
139
|
+
end
|
140
|
+
end
|
141
|
+
|
142
|
+
def get_access_token
|
143
|
+
http_options = access_token_options
|
144
|
+
response = raise_invalid_api_credentials_for_empty_resp! { access_token_response(http_options) }
|
145
|
+
|
146
|
+
self.temp_access_token_expires_at = Time.now + response['expires_in'].to_i.seconds
|
147
|
+
self.temp_access_token = response['access_token']
|
148
|
+
|
149
|
+
update_token_store
|
150
|
+
|
151
|
+
self.temp_access_token
|
152
|
+
end
|
153
|
+
|
154
|
+
def raise_invalid_api_credentials_for_empty_resp!
|
155
|
+
yield
|
156
|
+
rescue Booker::Error => ex
|
157
|
+
if (response = ex.response).present?
|
158
|
+
raise ex
|
159
|
+
else
|
160
|
+
raise Booker::InvalidApiCredentials.new(url: ex.url, request: ex.request, response: response)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def access_token_response(http_options)
|
165
|
+
send(self.class::ACCESS_TOKEN_HTTP_METHOD, self.class::ACCESS_TOKEN_ENDPOINT, http_options, nil)
|
166
|
+
end
|
167
|
+
|
168
|
+
private
|
169
|
+
def request_options(query=nil, body=nil)
|
170
|
+
options = {
|
171
|
+
headers: {
|
172
|
+
'Content-Type' => 'application/json; charset=utf-8'
|
173
|
+
},
|
174
|
+
open_timeout: 120
|
175
|
+
}
|
176
|
+
|
177
|
+
options[:body] = body if body.present?
|
178
|
+
options[:query] = query if query.present?
|
179
|
+
options
|
180
|
+
end
|
181
|
+
|
182
|
+
def build_resources(resources, booker_model)
|
183
|
+
return resources if booker_model.nil?
|
184
|
+
|
185
|
+
if resources.is_a? Hash
|
186
|
+
booker_model.from_hash(resources)
|
187
|
+
elsif resources.is_a? Array
|
188
|
+
booker_model.from_list(resources)
|
189
|
+
else
|
190
|
+
resources
|
191
|
+
end
|
192
|
+
end
|
193
|
+
|
194
|
+
def temp_access_token_expired?
|
195
|
+
self.temp_access_token_expires_at.nil? || self.temp_access_token_expires_at <= Time.now
|
196
|
+
end
|
197
|
+
|
198
|
+
def results_from_response(response, booker_model=nil)
|
199
|
+
return response['Results'] unless response['Results'].nil?
|
200
|
+
|
201
|
+
if booker_model
|
202
|
+
model_name = booker_model.to_s.demodulize
|
203
|
+
return response[model_name] unless response[model_name].nil?
|
204
|
+
|
205
|
+
pluralized = model_name.pluralize
|
206
|
+
return response[pluralized] unless response[pluralized].nil?
|
207
|
+
end
|
208
|
+
|
209
|
+
response.parsed_response if response
|
210
|
+
end
|
211
|
+
end
|
212
|
+
end
|
@@ -0,0 +1,49 @@
|
|
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
|
+
def get_location(booker_location_id:)
|
19
|
+
response = get("/location/#{booker_location_id}", build_params)
|
20
|
+
Booker::Models::Location.from_hash(response)
|
21
|
+
end
|
22
|
+
|
23
|
+
private
|
24
|
+
|
25
|
+
def build_params(default_params={}, overrides={}, paginated=false)
|
26
|
+
# merged = default_params.merge(overrides)
|
27
|
+
# merged.merge({"access_token" => access_token}) unless merged.has_key?("access_token")
|
28
|
+
original = {}
|
29
|
+
unless overrides.has_key?(:access_token)
|
30
|
+
original[:access_token] = access_token
|
31
|
+
end
|
32
|
+
merged = original.merge(default_params.merge(overrides))
|
33
|
+
|
34
|
+
merged.each do |k, v|
|
35
|
+
if v.is_a?(Time) || v.is_a?(DateTime)
|
36
|
+
merged[k] = Booker::Models::Model.time_to_booker_datetime(v)
|
37
|
+
elsif v.is_a?(Date)
|
38
|
+
merged[k] = Booker::Models::Model.time_to_booker_datetime(v.in_time_zone)
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
if paginated
|
43
|
+
DEFAULT_PAGINATION_PARAMS.merge(merged)
|
44
|
+
else
|
45
|
+
merged
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,269 @@
|
|
1
|
+
---
|
2
|
+
1: 'US'
|
3
|
+
2: 'CA'
|
4
|
+
3: 'AL'
|
5
|
+
4: 'DZ'
|
6
|
+
5: 'AS'
|
7
|
+
6: 'AD'
|
8
|
+
7: 'AO'
|
9
|
+
8: 'AI'
|
10
|
+
9: 'AG'
|
11
|
+
10: 'AR'
|
12
|
+
11: 'AM'
|
13
|
+
12: 'AW'
|
14
|
+
13: 'AU'
|
15
|
+
14: 'AT'
|
16
|
+
15: 'AZ'
|
17
|
+
16: 'AP'
|
18
|
+
17: 'BS'
|
19
|
+
18: 'BH'
|
20
|
+
19: 'BD'
|
21
|
+
20: 'BB'
|
22
|
+
21: 'BY'
|
23
|
+
22: 'BE'
|
24
|
+
23: 'BZ'
|
25
|
+
24: 'BJ'
|
26
|
+
25: 'BM'
|
27
|
+
26: 'BT'
|
28
|
+
27: 'BO'
|
29
|
+
28: 'BL'
|
30
|
+
29: 'BA'
|
31
|
+
30: 'BW'
|
32
|
+
31: 'BV'
|
33
|
+
32: 'BR'
|
34
|
+
33: 'IO'
|
35
|
+
34: 'BN'
|
36
|
+
35: 'BG'
|
37
|
+
36: 'BF'
|
38
|
+
37: 'BI'
|
39
|
+
38: 'KH'
|
40
|
+
39: 'CM'
|
41
|
+
41: 'IC'
|
42
|
+
42: 'CV'
|
43
|
+
43: 'KY'
|
44
|
+
44: 'CF'
|
45
|
+
45: 'TD'
|
46
|
+
46: 'CD'
|
47
|
+
47: 'CL'
|
48
|
+
48: 'CN'
|
49
|
+
49: 'CX'
|
50
|
+
50: 'CC'
|
51
|
+
51: 'CO'
|
52
|
+
52: 'KM'
|
53
|
+
53: 'ZP'
|
54
|
+
54: 'CK'
|
55
|
+
55: 'CR'
|
56
|
+
56: 'CI'
|
57
|
+
57: 'HR'
|
58
|
+
58: 'CB'
|
59
|
+
59: 'CY'
|
60
|
+
60: 'CZ'
|
61
|
+
61: 'DK'
|
62
|
+
62: 'DJ'
|
63
|
+
63: 'DM'
|
64
|
+
64: 'DO'
|
65
|
+
65: 'TP'
|
66
|
+
66: 'EC'
|
67
|
+
67: 'EG'
|
68
|
+
68: 'SV'
|
69
|
+
69: 'GB' # Must be GB not UK!!!
|
70
|
+
70: 'GQ'
|
71
|
+
71: 'ER'
|
72
|
+
72: 'EE'
|
73
|
+
73: 'ET'
|
74
|
+
74: 'FO'
|
75
|
+
75: 'FK'
|
76
|
+
76: 'FJ'
|
77
|
+
77: 'FI'
|
78
|
+
78: 'FR'
|
79
|
+
79: 'GF'
|
80
|
+
80: 'PF'
|
81
|
+
81: 'TF'
|
82
|
+
82: 'GA'
|
83
|
+
83: 'GM'
|
84
|
+
84: 'GE'
|
85
|
+
85: 'DE'
|
86
|
+
86: 'GH'
|
87
|
+
87: 'GI'
|
88
|
+
89: 'GR'
|
89
|
+
90: 'GL'
|
90
|
+
91: 'GD'
|
91
|
+
92: 'GP'
|
92
|
+
93: 'GU'
|
93
|
+
94: 'GT'
|
94
|
+
95: 'GN'
|
95
|
+
96: 'GW'
|
96
|
+
97: 'GY'
|
97
|
+
98: 'HT'
|
98
|
+
99: 'HM'
|
99
|
+
100: 'HN'
|
100
|
+
101: 'HK'
|
101
|
+
102: 'HU'
|
102
|
+
103: 'IS'
|
103
|
+
104: 'IN'
|
104
|
+
105: 'ID'
|
105
|
+
106: 'IE'
|
106
|
+
107: 'IL'
|
107
|
+
108: 'IT'
|
108
|
+
109: 'JM'
|
109
|
+
110: 'JP'
|
110
|
+
111: 'JO'
|
111
|
+
112: 'KZ'
|
112
|
+
113: 'KE'
|
113
|
+
114: 'KI'
|
114
|
+
115: 'KO'
|
115
|
+
116: 'KW'
|
116
|
+
117: 'KG'
|
117
|
+
118: 'LA'
|
118
|
+
119: 'LV'
|
119
|
+
120: 'LB'
|
120
|
+
121: 'LS'
|
121
|
+
122: 'LR'
|
122
|
+
123: 'LY'
|
123
|
+
124: 'LI'
|
124
|
+
125: 'LT'
|
125
|
+
126: 'LU'
|
126
|
+
127: 'MO'
|
127
|
+
128: 'MK'
|
128
|
+
129: 'MG'
|
129
|
+
130: 'ME'
|
130
|
+
131: 'MW'
|
131
|
+
132: 'MY'
|
132
|
+
133: 'MV'
|
133
|
+
134: 'ML'
|
134
|
+
135: 'MT'
|
135
|
+
136: 'MH'
|
136
|
+
137: 'MQ'
|
137
|
+
138: 'MR'
|
138
|
+
139: 'MU'
|
139
|
+
140: 'YT'
|
140
|
+
141: 'MX'
|
141
|
+
142: 'FM'
|
142
|
+
143: 'MD'
|
143
|
+
144: 'MC'
|
144
|
+
145: 'MN'
|
145
|
+
146: 'MS'
|
146
|
+
147: 'MA'
|
147
|
+
148: 'MZ'
|
148
|
+
149: 'MM'
|
149
|
+
150: 'NA'
|
150
|
+
151: 'NR'
|
151
|
+
152: 'NP'
|
152
|
+
153: 'NL'
|
153
|
+
154: 'AN'
|
154
|
+
155: 'NT'
|
155
|
+
156: 'NC'
|
156
|
+
157: 'NZ'
|
157
|
+
158: 'NI'
|
158
|
+
159: 'NE'
|
159
|
+
160: 'NG'
|
160
|
+
161: 'NU'
|
161
|
+
162: 'NF'
|
162
|
+
163: 'KP'
|
163
|
+
164: 'NB'
|
164
|
+
165: 'MP'
|
165
|
+
166: 'NO'
|
166
|
+
167: 'OM'
|
167
|
+
168: 'PK'
|
168
|
+
169: 'PW'
|
169
|
+
170: 'PA'
|
170
|
+
171: 'PG'
|
171
|
+
172: 'PY'
|
172
|
+
173: 'PE'
|
173
|
+
174: 'PH'
|
174
|
+
175: 'PN'
|
175
|
+
176: 'PL'
|
176
|
+
177: 'PO'
|
177
|
+
178: 'PT'
|
178
|
+
179: 'PR'
|
179
|
+
180: 'QA'
|
180
|
+
181: 'RE'
|
181
|
+
182: 'RO'
|
182
|
+
183: 'RT'
|
183
|
+
184: 'RU'
|
184
|
+
185: 'RW'
|
185
|
+
186: 'SS'
|
186
|
+
187: 'KN'
|
187
|
+
188: 'LC'
|
188
|
+
189: 'VC'
|
189
|
+
190: 'SP'
|
190
|
+
191: 'WS'
|
191
|
+
192: 'SM'
|
192
|
+
193: 'ST'
|
193
|
+
194: 'SA'
|
194
|
+
195: 'SF'
|
195
|
+
196: 'SN'
|
196
|
+
197: 'SC'
|
197
|
+
198: 'SL'
|
198
|
+
199: 'SG'
|
199
|
+
200: 'SK'
|
200
|
+
201: 'SI'
|
201
|
+
202: 'SB'
|
202
|
+
203: 'SO'
|
203
|
+
204: 'ZA'
|
204
|
+
205: 'GS'
|
205
|
+
206: 'KR'
|
206
|
+
207: 'ES'
|
207
|
+
208: 'LK'
|
208
|
+
209: 'NT'
|
209
|
+
210: 'SW'
|
210
|
+
211: 'VI'
|
211
|
+
212: 'EU'
|
212
|
+
213: 'SH'
|
213
|
+
214: 'UV'
|
214
|
+
215: 'KN'
|
215
|
+
216: 'LC'
|
216
|
+
217: 'MB'
|
217
|
+
218: 'TB'
|
218
|
+
219: 'PM'
|
219
|
+
220: 'VL'
|
220
|
+
221: 'VC'
|
221
|
+
222: 'SD'
|
222
|
+
223: 'SR'
|
223
|
+
224: 'SJ'
|
224
|
+
225: 'SZ'
|
225
|
+
226: 'SE'
|
226
|
+
227: 'CH'
|
227
|
+
228: 'SY'
|
228
|
+
229: 'TA'
|
229
|
+
230: 'TW'
|
230
|
+
231: 'TJ'
|
231
|
+
232: 'TZ'
|
232
|
+
233: 'TH'
|
233
|
+
234: 'TI'
|
234
|
+
235: 'TG'
|
235
|
+
236: 'TK'
|
236
|
+
237: 'TO'
|
237
|
+
238: 'TL'
|
238
|
+
239: 'TT'
|
239
|
+
240: 'TU'
|
240
|
+
241: 'TN'
|
241
|
+
242: 'TR'
|
242
|
+
243: 'TM'
|
243
|
+
244: 'TC'
|
244
|
+
245: 'TV'
|
245
|
+
246: 'UG'
|
246
|
+
247: 'UA'
|
247
|
+
248: 'UI'
|
248
|
+
249: 'AE'
|
249
|
+
251: 'UY'
|
250
|
+
252: 'UM'
|
251
|
+
253: 'SU'
|
252
|
+
254: 'UZ'
|
253
|
+
255: 'VU'
|
254
|
+
256: 'VA'
|
255
|
+
257: 'VE'
|
256
|
+
258: 'VN'
|
257
|
+
259: 'VR'
|
258
|
+
260: 'VG'
|
259
|
+
261: 'VI'
|
260
|
+
262: 'WL'
|
261
|
+
263: 'WF'
|
262
|
+
264: 'WS'
|
263
|
+
265: 'YA'
|
264
|
+
266: 'YE'
|
265
|
+
267: 'YU'
|
266
|
+
268: 'ZR'
|
267
|
+
269: 'ZM'
|
268
|
+
270: 'ZW'
|
269
|
+
271: 'RS'
|