booker_api 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (62) hide show
  1. checksums.yaml +7 -0
  2. data/lib/booker/booker.rb +5 -0
  3. data/lib/booker/business_client.rb +22 -0
  4. data/lib/booker/business_rest.rb +130 -0
  5. data/lib/booker/client.rb +212 -0
  6. data/lib/booker/common_rest.rb +49 -0
  7. data/lib/booker/config/booker_country_ids_to_iso_codes.yml +269 -0
  8. data/lib/booker/customer_client.rb +17 -0
  9. data/lib/booker/customer_rest.rb +169 -0
  10. data/lib/booker/errors.rb +24 -0
  11. data/lib/booker/generic_token_store.rb +25 -0
  12. data/lib/booker/helpers/active_support_helper.rb +102 -0
  13. data/lib/booker/helpers/logging_helper.rb +11 -0
  14. data/lib/booker/models/address.rb +12 -0
  15. data/lib/booker/models/appointment.rb +77 -0
  16. data/lib/booker/models/appointment_treatment.rb +51 -0
  17. data/lib/booker/models/available_time.rb +13 -0
  18. data/lib/booker/models/business_type.rb +5 -0
  19. data/lib/booker/models/category.rb +5 -0
  20. data/lib/booker/models/class_instance.rb +25 -0
  21. data/lib/booker/models/country.rb +19 -0
  22. data/lib/booker/models/current_price.rb +5 -0
  23. data/lib/booker/models/customer.rb +52 -0
  24. data/lib/booker/models/customer_2.rb +5 -0
  25. data/lib/booker/models/customer_record_type.rb +5 -0
  26. data/lib/booker/models/discount.rb +5 -0
  27. data/lib/booker/models/dynamic_price.rb +11 -0
  28. data/lib/booker/models/employee.rb +10 -0
  29. data/lib/booker/models/feature_settings.rb +7 -0
  30. data/lib/booker/models/final_total.rb +5 -0
  31. data/lib/booker/models/gender.rb +5 -0
  32. data/lib/booker/models/itinerary_time_slot.rb +7 -0
  33. data/lib/booker/models/itinerary_time_slots_list.rb +7 -0
  34. data/lib/booker/models/location.rb +23 -0
  35. data/lib/booker/models/location_day_schedule.rb +18 -0
  36. data/lib/booker/models/model.rb +150 -0
  37. data/lib/booker/models/multi_service_availability_result.rb +7 -0
  38. data/lib/booker/models/notification_settings.rb +12 -0
  39. data/lib/booker/models/online_booking_settings.rb +23 -0
  40. data/lib/booker/models/original_price.rb +5 -0
  41. data/lib/booker/models/payment_method.rb +5 -0
  42. data/lib/booker/models/preferred_staff_gender.rb +5 -0
  43. data/lib/booker/models/price.rb +8 -0
  44. data/lib/booker/models/receipt_display_price.rb +5 -0
  45. data/lib/booker/models/room.rb +12 -0
  46. data/lib/booker/models/shipping_address.rb +5 -0
  47. data/lib/booker/models/source.rb +5 -0
  48. data/lib/booker/models/spa.rb +5 -0
  49. data/lib/booker/models/spa_employee_availability_search_item.rb +11 -0
  50. data/lib/booker/models/status.rb +5 -0
  51. data/lib/booker/models/sub_category.rb +5 -0
  52. data/lib/booker/models/tag_price.rb +5 -0
  53. data/lib/booker/models/teacher.rb +5 -0
  54. data/lib/booker/models/teacher_2.rb +5 -0
  55. data/lib/booker/models/time_zone.rb +8 -0
  56. data/lib/booker/models/treatment.rb +19 -0
  57. data/lib/booker/models/treatment_time_slot.rb +5 -0
  58. data/lib/booker/models/type.rb +8 -0
  59. data/lib/booker/models/user.rb +73 -0
  60. data/lib/booker/version.rb +3 -0
  61. data/lib/booker_api.rb +97 -0
  62. metadata +216 -0
@@ -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,5 @@
1
+ module Booker
2
+ def self.config
3
+ @config ||= {}
4
+ end
5
+ end
@@ -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'