ordrin 0.1.0

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.
data/bin/ordrindemo.rb ADDED
@@ -0,0 +1,323 @@
1
+ #! /usr/bin/env ruby
2
+ require 'date'
3
+ require 'securerandom'
4
+ require 'pp'
5
+
6
+ begin
7
+ require 'ordrin'
8
+ rescue LoadError
9
+ require 'rubygems'
10
+ begin
11
+ require 'ordrin'
12
+ rescue LoadError
13
+ require_relative '../lib/ordrin'
14
+ end
15
+ end
16
+
17
+ module OrdrinDemo
18
+ #
19
+ # Global Variables
20
+ #
21
+ print "Please input your API key: "
22
+ STDOUT.flush
23
+ api_key = gets.chomp
24
+
25
+ @@api = Ordrin::APIs.new(api_key, :test)
26
+
27
+ # Create an Address object
28
+ @@address = Ordrin::Data::Address.new('1 Main Street', 'College Station', 'TX', '77840', '(555) 555-5555')
29
+ @@address_nick = 'addr1'
30
+
31
+ # Create a CreditCard object
32
+ @@first_name = 'Test'
33
+ @@last_name = 'User'
34
+ @@credit_card = Ordrin::Data::CreditCard.new("#{@@first_name} #{@@last_name}", '01', (Date.today.year+2).to_s, @@address, '4111111111111111', '123')
35
+ @@credit_card_nick = 'cc1'
36
+
37
+ unique_id = SecureRandom.uuid.to_s.gsub(/-/, '')
38
+ @@email = "demo+#{unique_id}ruby@ordr.in"
39
+ @@password = 'password'
40
+ @@login = Ordrin::Data::UserLogin.new(@@email, @@password)
41
+ @@alt_first_name = 'Example'
42
+ @@alt_email = "demo+#{unique_id}rubyalt@ordr.in"
43
+ @@alt_login = Ordrin::Data::UserLogin.new(@@alt_email, @@password)
44
+ @@new_password = 'password1'
45
+
46
+ #
47
+ # Restaurant demo functions
48
+ #
49
+
50
+ def OrdrinDemo.delivery_list_immediate_demo()
51
+ puts "Get a list of restaurants that will deliver if you order now"
52
+ print "Press enter to execute and see the response"
53
+ gets
54
+ delivery_list_immediate = @@api.restaurant.get_delivery_list('ASAP', @@address)
55
+ PP.pp(delivery_list_immediate)
56
+ return delivery_list_immediate
57
+ end
58
+
59
+ def OrdrinDemo.delivery_list_future_demo()
60
+ puts "Get a list of restaurants that will deliver if you order for 12 hours from now"
61
+ print "Press enter to execute and see the response"
62
+ gets
63
+ future_datetime = DateTime.now + 0.5 #A timestamp twelve hours in the future
64
+ delivery_list_later = @@api.restaurant.get_delivery_list(future_datetime, @@address)
65
+ PP.pp(delivery_list_later)
66
+ end
67
+
68
+ def OrdrinDemo.delivery_check_demo(restaurant_id)
69
+ puts "Get whether a particular restaurant will deliver if you order now"
70
+ print "Press enter to execute and see the response"
71
+ gets
72
+ delivery_check = @@api.restaurant.get_delivery_check(restaurant_id, 'ASAP', @@address)
73
+ PP.pp(delivery_check)
74
+ end
75
+
76
+ def OrdrinDemo.fee_demo(restaurant_id)
77
+ puts "Get fee and other info for ordering a given amount with a given tip"
78
+ print "Press enter to execute and see the response"
79
+ gets
80
+ subtotal = "$30.00"
81
+ tip = "$5.00"
82
+ fee_info = @@api.restaurant.get_fee(restaurant_id, subtotal, tip, 'ASAP', @@address)
83
+ PP.pp(fee_info)
84
+ end
85
+
86
+ def OrdrinDemo.detail_demo(restaurant_id)
87
+ puts "Get detailed information about a single restaurant"
88
+ print "Press enter to execute and see the response"
89
+ gets
90
+ restaurant_detail = @@api.restaurant.get_details(restaurant_id)
91
+ PP.pp(restaurant_detail)
92
+ return restaurant_detail
93
+ end
94
+
95
+ def OrdrinDemo.find_deliverable_time(restaurant_id)
96
+ puts "Find a time when this restaurant will deliver"
97
+ print "Press enter to execute and see the response"
98
+ gets
99
+ delivery_check = @@api.restaurant.get_delivery_check(restaurant_id, 'ASAP', @@address)
100
+ delivery = delivery_check['delivery']
101
+ if delivery
102
+ return 'ASAP'
103
+ end
104
+ dt = DateTime.now + 1/24.0
105
+ while not delivery
106
+ delivery_check = @@api.restaurant.get_delivery_check(restaurant_id, dt, @@address)
107
+ delivery = delivery_check['delivery']
108
+ dt += 1/24.0
109
+ end
110
+ return dt
111
+ end
112
+ #
113
+ # User demo functions
114
+ #
115
+
116
+ def OrdrinDemo.get_user_demo()
117
+ puts "Get information about a user"
118
+ print "Press enter to execute and see the response"
119
+ gets
120
+ user_info = @@api.user.get(@@login)
121
+ PP.pp(user_info)
122
+ end
123
+
124
+ def OrdrinDemo.create_user_demo()
125
+ puts "Create a user"
126
+ print "Press enter to execute and see the response"
127
+ gets
128
+ response = @@api.user.create(@@login, @@first_name, @@last_name)
129
+ PP.pp(response)
130
+ end
131
+
132
+ def OrdrinDemo.update_user_demo()
133
+ puts "Update a user"
134
+ print "Press enter to execute and see the response"
135
+ gets
136
+ response = @@api.user.update(@@login, @@alt_first_name, @@last_name)
137
+ PP.pp(response)
138
+ end
139
+
140
+ def OrdrinDemo.get_all_addresses_demo()
141
+ puts "Get a list of all saved addresses"
142
+ print "Press enter to execute and see the response"
143
+ gets
144
+ address_list = @@api.user.get_all_addresses(@@login)
145
+ PP.pp(address_list)
146
+ end
147
+
148
+ def OrdrinDemo.get_address_demo()
149
+ puts "Get an address by nickname"
150
+ print "Press enter to execute and see the response"
151
+ gets
152
+ addr = @@api.user.get_address(@@login, @@address_nick)
153
+ PP.pp(addr)
154
+ end
155
+
156
+ def OrdrinDemo.set_address_demo()
157
+ puts "Save an address with a nickname"
158
+ response = @@api.user.set_address(@@login, @@address_nick, @@address)
159
+ PP.pp(response)
160
+ end
161
+
162
+ def OrdrinDemo.remove_address_demo()
163
+ puts "Remove a saved address by nickname"
164
+ print "Press enter to execute and see the response"
165
+ gets
166
+ response = @@api.user.remove_address(@@login, @@address_nick)
167
+ PP.pp(response)
168
+ end
169
+
170
+ def OrdrinDemo.get_all_credit_cards_demo()
171
+ puts "Get a list of all saved credit cards"
172
+ credit_card_list = @@api.user.get_all_credit_cards(@@login)
173
+ PP.pp(credit_card_list)
174
+ end
175
+
176
+ def OrdrinDemo.get_credit_card_demo()
177
+ puts "Get a saved credit card by nickname"
178
+ print "Press enter to execute and see the response"
179
+ gets
180
+ credit_card = @@api.user.get_credit_card(@@login, @@credit_card_nick)
181
+ PP.pp(credit_card)
182
+ end
183
+
184
+ def OrdrinDemo.set_credit_card_demo()
185
+ puts "Save a credit card with a nickname"
186
+ print "Press enter to execute and see the response"
187
+ gets
188
+ response = @@api.user.set_credit_card(@@login, @@credit_card_nick, @@credit_card)
189
+ PP.pp(response)
190
+ end
191
+
192
+ def OrdrinDemo.remove_credit_card_demo()
193
+ puts "Remove a saved credit card by nickname"
194
+ print "Press enter to execute and see the response"
195
+ gets
196
+ response = @@api.user.remove_credit_card(@@login, @@credit_card_nick)
197
+ PP.pp(response)
198
+ end
199
+
200
+ def OrdrinDemo.get_order_history_demo(login)
201
+ puts "Get a list of all orders made by this user"
202
+ print "Press enter to execute and see the response"
203
+ gets
204
+ order_list = @@api.user.get_order_history(@@login)
205
+ PP.pp(order_list)
206
+ end
207
+
208
+ def OrdrinDemo.get_order_detail_demo(oid)
209
+ puts "Get the details of a particular order made by this user"
210
+ print "Press enter to execute and see the response"
211
+ gets
212
+ order_detail = @@api.user.get_order_detail(@@login, oid)
213
+ PP.pp(order_detail)
214
+ end
215
+
216
+ def OrdrinDemo.set_password_demo()
217
+ puts "Set a new password for a user"
218
+ print "Press enter to execute and see the response"
219
+ gets
220
+ response = @@api.user.set_password(@@login, @@new_password)
221
+ PP.pp(response)
222
+ end
223
+
224
+ #
225
+ # Order demo functions
226
+ #
227
+
228
+ def OrdrinDemo.anonymous_order_demo(restaurant_id, tray, date_time)
229
+ puts "Order food as someone without a user account"
230
+ print "Press enter to execute and see the response"
231
+ gets
232
+ tip = Random.rand(500)/100.0
233
+ response = @@api.order.order(restaurant_id, tray, tip, date_time, @@first_name, @@last_name, @@address, @@credit_card, @@email)
234
+ PP.pp(response)
235
+ end
236
+
237
+ def OrdrinDemo.create_user_and_order_demo(restaurant_id, tray, date_time)
238
+ puts "Order food and create an account"
239
+ print "Press enter to execute and see the response"
240
+ gets
241
+ tip = Random.rand(500)/100.0
242
+ response = @@api.order.order_create_user(restaurant_id, tray, tip, date_time, @@first_name, @@last_name, @@address, @@credit_card, @@alt_email, @@password)
243
+ PP.pp(response)
244
+ end
245
+
246
+ def OrdrinDemo.order_with_nicks_demo(restaurant_id, tray, date_time)
247
+ puts "Order food as a logged in user using previously stored address and credit card"
248
+ print "Press enter to execute and see the response"
249
+ gets
250
+ tip = Random.rand(500)/100.0
251
+ response = @@api.order.order(restaurant_id, tray, tip, date_time, @@first_name, @@last_name, @@address_nick, @@credit_card_nick, nil, @@login)
252
+ PP.pp(response)
253
+ return response
254
+ end
255
+
256
+ def OrdrinDemo.find_item_to_order(item_list)
257
+ for item in item_list
258
+ if item['is_orderable']=='1'
259
+ if item['price'].to_f>=5.00
260
+ return item['id']
261
+ end
262
+ else
263
+ if item.has_key?('children')
264
+ item_id = find_item_to_order(item['children'])
265
+ unless item_id.nil?
266
+ return item_id
267
+ end
268
+ end
269
+ end
270
+ end
271
+ nil
272
+ end
273
+
274
+
275
+ #
276
+ # Main
277
+ #
278
+ def OrdrinDemo.run_demo()
279
+ puts "Run through the entire demo sequence"
280
+ # Restaurant functions
281
+ delivery_list = delivery_list_immediate_demo()
282
+ delivery_list_future_demo()
283
+ restaurant_id = delivery_list[0]['id']
284
+ delivery_check_demo(restaurant_id)
285
+ fee_demo(restaurant_id)
286
+ detail = detail_demo(restaurant_id)
287
+
288
+ # User functions
289
+ create_user_demo()
290
+ get_user_demo()
291
+ update_user_demo()
292
+ get_user_demo()
293
+ set_address_demo()
294
+ get_address_demo()
295
+ set_credit_card_demo()
296
+ get_credit_card_demo()
297
+
298
+ # Order functions
299
+ order_date_time = find_deliverable_time(restaurant_id)
300
+ puts "Ordering food at #{order_date_time}"
301
+ item_id = find_item_to_order(detail['menu'])
302
+ item = Ordrin::Data::TrayItem.new(item_id, 10)
303
+ tray = Ordrin::Data::Tray.new(item)
304
+ anonymous_order_demo(restaurant_id, tray, order_date_time)
305
+ order = order_with_nicks_demo(restaurant_id, tray, order_date_time)
306
+ unless order.nil?
307
+ get_order_detail_demo(order['refnum'])
308
+ end
309
+
310
+ create_user_and_order_demo(restaurant_id, tray, order_date_time)
311
+ get_order_history_demo(@@alt_login)
312
+
313
+ # Clean up/removing stuff
314
+ remove_address_demo()
315
+ get_all_addresses_demo()
316
+ remove_credit_card_demo()
317
+ get_all_credit_cards_demo()
318
+ set_password_demo()
319
+ #After changing the password I must change the login object to continue to access user info
320
+ end
321
+
322
+ run_demo()
323
+ end
@@ -0,0 +1,148 @@
1
+ require 'digest'
2
+ require_relative 'normalize'
3
+ module Ordrin
4
+ module Data
5
+ # Base class for objects that can save any data with the constructor and then
6
+ # extract it as a dictionary
7
+ class OrdrinData
8
+ # Return a dictionary of particular fields to values, determined per subclass
9
+ def make_dict
10
+ dict = {}
11
+ fields.map {|f| dict[f.to_s]=self.send(f) unless self.send(f).nil?}
12
+ return dict
13
+ end
14
+ end
15
+
16
+ # Represents a street address
17
+ class Address < OrdrinData
18
+
19
+ attr_reader :addr, :city, :state, :zip, :phone, :addr2, :fields
20
+
21
+ # Store the parts of the address as fields in this object.
22
+ # Arguments:
23
+ # addr -- Street address
24
+ # city -- City
25
+ # state -- State
26
+ # zip -- Zip code
27
+ # phone -- Phone number
28
+ # addr2 -- Optional second street address line
29
+ def initialize(addr, city, state, zip, phone, addr2='')
30
+ @fields = [:addr, :city, :state, :zip, :phone, :addr2]
31
+ @addr = addr
32
+ @city = city
33
+ @state = Normalize.normalize(state, :state)
34
+ @zip = Normalize.normalize(zip, :zip)
35
+ @phone = Normalize.normalize(phone, :phone)
36
+ end
37
+ end
38
+
39
+ # Represents information about a credit card
40
+ class CreditCard < OrdrinData
41
+
42
+ attr_reader :expiry_month, :expiry_year, :number, :cvc, :type, :name, :bill_address, :fields
43
+
44
+ # Store the credit card info as fields in this object.
45
+ # Arguments:
46
+ # name -- The name (first and last) on the credit card
47
+ # expiry_month -- The month that the card expires (two digits)
48
+ # expiry_year -- The year that the card expires (four digits)
49
+ # bill_address -- The billing address. Should be an Ordrin::Data::Address object
50
+ # number -- The credit card number
51
+ # cvc -- The card verification number
52
+ def initialize(name, expiry_month, expiry_year, bill_address, number, cvc)
53
+ @fields = [:number, :cvc, :expiry_month, :expiry_year, :expiry,
54
+ :bill_addr, :bill_addr2, :bill_city, :bill_state, :bill_zip,
55
+ :phone, :name]
56
+ @expiry_month = Normalize.normalize(expiry_month, :month)
57
+ @expiry_year = Normalize.normalize(expiry_year, :year)
58
+ @number, @cvc, @type = Normalize.normalize([number, cvc], :credit_card)
59
+ @name = name
60
+ @bill_address = bill_address
61
+ end
62
+
63
+ def bill_addr
64
+ @bill_address.addr
65
+ end
66
+
67
+ def bill_addr2
68
+ @bill_address.addr2
69
+ end
70
+
71
+ def bill_city
72
+ @bill_address.city
73
+ end
74
+
75
+ def bill_state
76
+ @bill_address.state
77
+ end
78
+
79
+ def bill_zip
80
+ @bill_address.zip
81
+ end
82
+
83
+ def phone
84
+ @bill_address.phone
85
+ end
86
+
87
+ # A combination of the expiry_month and expiry_date
88
+ def expiry
89
+ "#{expiry_month}/#{expiry_year}"
90
+ end
91
+ end
92
+
93
+ #Represents a user's login information
94
+ class UserLogin < OrdrinData
95
+
96
+ attr_reader :email, :password, :fields
97
+
98
+ # Store the email and password in this object. Saves only the hash of the
99
+ # password, not the password itself
100
+ # Arguments:
101
+ # email -- The user's email address
102
+ # password -- The user's password (in plain text)
103
+ def initialize(email, password)
104
+ @fields = [:email, :password]
105
+ @email = email
106
+ @password = UserLogin.hash_password(password)
107
+ end
108
+
109
+ def UserLogin.hash_password(password)
110
+ return Digest::SHA256.new.hexdigest(password)
111
+ end
112
+ end
113
+
114
+ # Represents a single item in an order
115
+ class TrayItem
116
+
117
+ # Store the descriptors of an order item in this object.
118
+ # Arguments:
119
+ # item_id -- the restaurants's numerial ID for the item
120
+ # quantity -- the quantity
121
+ # options -- any number of options to apply to the item
122
+ def initialize(item_id, quantity, *options)
123
+ @item_id = Normalize.normalize(item_id, :number)
124
+ @quantity = Normalize.normalize(quantity, :number)
125
+ @options = options.map {|opt| Normalize.normalize(opt, :number)}
126
+ end
127
+
128
+ def to_s
129
+ "#{@item_id}/#{@quantity},#{@options*','}"
130
+ end
131
+ end
132
+
133
+ # Represents a list of items in an order
134
+ class Tray
135
+
136
+ # Store the list of items in this object. Each argument should be of type Item
137
+ # Arguments:
138
+ # items -- A list of items to be ordered in this tray
139
+ def initialize(*items)
140
+ @items = items
141
+ end
142
+
143
+ def to_s
144
+ return @items*'+'
145
+ end
146
+ end
147
+ end
148
+ end
@@ -0,0 +1,147 @@
1
+ module Ordrin
2
+ module Errors
3
+ # This is the base class for errors specific to this ordrin package
4
+ class OrdrinError < Exception
5
+ attr_reader :msg
6
+
7
+ def initialize(msg=nil)
8
+ @msg = msg
9
+ end
10
+ end
11
+
12
+ # This error encapsulates an API error returned by the server.
13
+ class ApiError < OrdrinError
14
+ attr_reader :text
15
+ def initialize(msg=nil, text=nil)
16
+ super(msg)
17
+ @text = text
18
+ end
19
+
20
+ def to_s
21
+ "ApiError(msg='#{msg}', text='#{text}')"
22
+ end
23
+ end
24
+
25
+ # This error indicates that the server returned a response that could not be
26
+ # parsed into JSON
27
+ class ApiInvalidResponseError < OrdrinError
28
+ def to_s
29
+ "ApiInvalidResponseError(msg='#{msg}')"
30
+ end
31
+ end
32
+
33
+ def Errors.state
34
+ lambda do |value|
35
+ ArgumentError.new("State must be a two letter postal code abbreviation: #{value}")
36
+ end
37
+ end
38
+
39
+ def Errors.money
40
+ lambda do |value|
41
+ ArgumentError.new("Money must be dollars.cents: #{value}")
42
+ end
43
+ end
44
+
45
+ def Errors.zip
46
+ lambda do |value|
47
+ ArgumentError.new("Zip code must be exactly 5 digits: #{value}")
48
+ end
49
+ end
50
+
51
+ def Errors.phone
52
+ lambda do |value|
53
+ ArgumentError.new("Phone numbers must have exactly 10 digits: #{value}")
54
+ end
55
+ end
56
+
57
+ def Errors.number
58
+ lambda do |value|
59
+ ArgumentError.new("This value must be only digits: #{value}")
60
+ end
61
+ end
62
+
63
+ def Errors.month
64
+ lambda do |value|
65
+ ArgumentError.new("Months must be two digits: #{value}")
66
+ end
67
+ end
68
+
69
+ def Errors.year
70
+ lambda do |value|
71
+ ArgumentError.new("Years must be four digits: #{value}")
72
+ end
73
+ end
74
+
75
+ def Errors.cvc
76
+ lambda do |value|
77
+ ArgumentError.new("Credit card CVC must be 3 or 4 digits, depending on the card type: #{value}")
78
+ end
79
+ end
80
+
81
+ def Errors.credit_card
82
+ lambda do |value|
83
+ ArgumentError.new("Credit card number must be a valid AmEx, Discover, Mastercard, or Visa card number: #{value}")
84
+ end
85
+ end
86
+
87
+ def Errors.email
88
+ lambda do |value|
89
+ ArgumentError.new("Bad email format: #{value}")
90
+ end
91
+ end
92
+
93
+ def Errors.normalizer
94
+ lambda do |value|
95
+ ArgumentError.new("Unknown validator name: #{value}")
96
+ end
97
+ end
98
+
99
+ def Errors.nick
100
+ lambda do |value|
101
+ ArgumentError.new("Nick names can only have letters, nubmers, dashes, and underscores: #{value}")
102
+ end
103
+ end
104
+
105
+ def Errors.date_time
106
+ lambda do |value|
107
+ ArgumentError.new("date_time must be a datetime.datetime object or the string 'ASAP': #{value}")
108
+ end
109
+ end
110
+
111
+ def Errors.date
112
+ lambda do |value|
113
+ ArgumentError.new("date must be a datetime.datetime or datetime.date object or the string 'ASAP': #{value}")
114
+ end
115
+ end
116
+
117
+ def Errors.time
118
+ lambda do |value|
119
+ ArgumentError.new("time must be a datetime.datetime or datetime.time object: #{value}")
120
+ end
121
+ end
122
+
123
+ def Errors.url
124
+ lambda do |value|
125
+ ArgumentError.new("url must be a proper url: #{value}")
126
+ end
127
+ end
128
+
129
+ def Errors.method
130
+ lambda do |value|
131
+ ArgumentError.new("method must be a word: #{value}")
132
+ end
133
+ end
134
+
135
+ def Errors.alphanum
136
+ lambda do |value|
137
+ ArgumentError.new("This value must be alphanumeric: #{value}")
138
+ end
139
+ end
140
+
141
+ def Errors.request_method
142
+ lambda do |value|
143
+ ApiError.new("Method not a valid HTTP request method: #{value}")
144
+ end
145
+ end
146
+ end
147
+ end
@@ -0,0 +1,186 @@
1
+ require_relative 'errors'
2
+
3
+ module Ordrin
4
+ module Normalize
5
+ class Normalizers
6
+ private
7
+ def regex(regex, error)
8
+ lambda do |value|
9
+ result = value.to_s[regex]
10
+ if result.nil?
11
+ raise error[value]
12
+ else
13
+ result
14
+ end
15
+ end
16
+ end
17
+
18
+ public
19
+ def phone(phone_number)
20
+ #strips out everything but digits from the phone number
21
+ phone = phone_number.to_s.gsub(/\D/, '')
22
+ if phone.length == 10
23
+ phone[/^(\d{3})(\d{3})(\d{4})$/]
24
+ "#{$1}-#{$2}-#{$3}"
25
+ else
26
+ raise Errors.phone[phone_number]
27
+ end
28
+ end
29
+
30
+ def money(money)
31
+ value = money.to_s.gsub(/,/, '')[/^\$?(\d+(\.\d+)?)$/, 1]
32
+ if value.nil?
33
+ raise Errors.money[money]
34
+ else
35
+ value
36
+ end
37
+ end
38
+
39
+ def datetime(date_time)
40
+ if date_time.to_s.upcase == 'ASAP'
41
+ 'ASAP'
42
+ else
43
+ begin
44
+ date_time.strftime('%m-%d+%H:%M')
45
+ rescue NoMethodError
46
+ raise Errors.date_time[date_time]
47
+ end
48
+ end
49
+ end
50
+
51
+ def date(date)
52
+ if date.to_s.upcase == 'ASAP'
53
+ 'ASAP'
54
+ else
55
+ begin
56
+ date.strftime('%m-%d')
57
+ rescue NoMethodError
58
+ raise Errors.date[date_time]
59
+ end
60
+ end
61
+ end
62
+
63
+ def time(time)
64
+ if time.to_s.upcase == 'ASAP'
65
+ 'ASAP'
66
+ else
67
+ begin
68
+ time.strftime('%m-%d+%H:%M')
69
+ rescue NoMethodError
70
+ raise Errors.time[date_time]
71
+ end
72
+ end
73
+ end
74
+
75
+ def url(url)
76
+ url.to_s[/^(https?:\/\/)[-\w.~]+(:\d+)?(\/[-\w.~]+)*/]
77
+ end
78
+
79
+ def state(state)
80
+ state = state.to_s[/^[A-Za-z]{2}$/]
81
+ if state.nil?
82
+ raise Errors.state[state]
83
+ else
84
+ state.upcase
85
+ end
86
+ end
87
+
88
+ private
89
+ def cc_type(cc_number)
90
+ cc_map = {'American Express' => /^3[47]\d{13}$/,
91
+ 'Visa' => /^4\d{12}(?:\d{3})?$/,
92
+ 'Mastercard' => /^5[1-5]\d{14}$/,
93
+ 'Discover' => /^6(?:011|5\d{2})\d{12}$/}
94
+
95
+ type = nil
96
+ cc_map.each {|key, value| type = key unless cc_number[value].nil?}
97
+ type
98
+ end
99
+
100
+ def luhn_checksum(card_number)
101
+ digits = card_number.to_s.scan(/./).map(&:to_i)
102
+ checksum = 0
103
+ digits.each_index do |index|
104
+ digit = digits[index]
105
+ if (digits.length-index)%2==1
106
+ checksum += digit
107
+ else
108
+ checksum += 2 * digit
109
+ end
110
+ end
111
+ checksum % 10
112
+ end
113
+
114
+ def luhn_valid?(card_number)
115
+ luhn_checksum(card_number) == 0
116
+ end
117
+
118
+ public
119
+ def credit_card(values)
120
+ number, cvc = values
121
+ num = number.to_s.gsub(/\D/, '')
122
+ unless luhn_valid?(num)
123
+ raise Errors.credit_card[number]
124
+ end
125
+ card_type = cc_type(num)
126
+ if card_type.nil?
127
+ raise Errors.credit_card[number]
128
+ else
129
+ if card_type=='American Express'
130
+ cvc_length = 4
131
+ else
132
+ cvc_length = 3
133
+ end
134
+ if cvc.length == cvc_length and !cvc.to_s[/^\d+$/].nil?
135
+ [num, cvc, card_type]
136
+ else
137
+ raise Errors.cvc[cvc]
138
+ end
139
+ end
140
+ end
141
+
142
+ def unchecked(value)
143
+ value.to_s
144
+ end
145
+
146
+ def name(value)
147
+ unchecked(value)
148
+ end
149
+
150
+ def zip(zip)
151
+ regex(/^\d{5}$/, Errors.zip)[zip]
152
+ end
153
+
154
+ def number(num)
155
+ regex(/^\d+/, Errors.number)[num]
156
+ end
157
+
158
+ def year(year)
159
+ regex(/^\d{4}$/, Errors.year)[year]
160
+ end
161
+
162
+ def month(mon)
163
+ regex(/^\d{2}$/, Errors.month)[mon]
164
+ end
165
+
166
+ def email(email)
167
+ regex(/^[^@\s]+@[^@\s]+\.[a-zA-Z]{2,3}/, Errors.email)[email]
168
+ end
169
+
170
+ def nick(nick)
171
+ regex(/^[-\w]+/, Errors.nick)[nick]
172
+ end
173
+
174
+ def alphanum(value)
175
+ regex(/^[a-zA-Z\d]+$/, Errors.alphanum)[value]
176
+ end
177
+ end
178
+
179
+ @@normalizer = Normalizers.new
180
+
181
+ public
182
+ def Normalize.normalize(value, normalizer)
183
+ @@normalizer.send(normalizer, value)
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,79 @@
1
+ require_relative 'ordrinapi'
2
+ require_relative 'normalize'
3
+ require_relative 'data'
4
+
5
+ module Ordrin
6
+ # This class will be used to access the order API. All return values
7
+ # are documented at http://ordr.in/developers/order
8
+ class OrderApi < OrdrinApi
9
+
10
+ private
11
+
12
+ #Put all of the data that needs to be passed to the POST request normalized into a dict.
13
+ def build_dict(restaurant_id, tray, tip, delivery_date_time, first_name, last_name, address, credit_card, email, login=nil)
14
+ data = {'restaurant_id' => Normalize.normalize(restaurant_id, :number),
15
+ 'tray' => tray.to_s,
16
+ 'tip' => Normalize.normalize(tip, :money),
17
+ 'delivery_date' => Normalize.normalize(delivery_date_time, :date)}
18
+ if data['delivery_date'] != 'ASAP'
19
+ data['delivery_time'] = Normalize.normalize(delivery_date_time, :time)
20
+ end
21
+ data['first_name'] = Normalize.normalize(first_name, :name)
22
+ data['last_name'] = Normalize.normalize(last_name, :name)
23
+ begin
24
+ data.merge!(address.make_dict)
25
+ rescue NoMethodError=>e
26
+ data['nick'] = Normalize.normalize(address, :nick)
27
+ end
28
+ if login.nil?
29
+ data['em'] = Normalize.normalize(email, :email)
30
+ end
31
+ begin
32
+ credit_card.make_dict.each_pair do |key, value|
33
+ data["card_#{key}"] = value
34
+ end
35
+ rescue NoMethodError=>e
36
+ data['card_nick'] = Normalize.normalize(credit_card, :nick)
37
+ end
38
+ data['type'] = 'res'
39
+ return data
40
+ end
41
+
42
+ public
43
+ # Place an order, either anonymously or as a logged in user. At least one
44
+ # of email and login must be passed. If both are passed, email will be ignored.
45
+ # Arguments:
46
+ # restaurant_id -- Ordr.in's restaurant identifier
47
+ # tray -- A tray of food to order. Should be an ordrin.data.Tray object
48
+ # tip -- The tip amount
49
+ # delivery_date_time -- Either 'ASAP' or a datetime object in the future
50
+ # first_name -- The orderer's first name
51
+ # last_name -- The orderer's last name
52
+ # address -- An address object (Ordrin::Data::Address) or the nickname of a saved address
53
+ # credit_card -- A credit card object (Ordrin::Data::CreditCard) or the nickname of a saved credit card
54
+ # email -- The email address of an anonymous user
55
+ # login -- The logged in user's login information. Should be an ordrin.data.UserLogin object
56
+ def order(restaurant_id, tray, tip, delivery_date_time, first_name, last_name, address, credit_card, email=nil, login=nil)
57
+ data = build_dict(restaurant_id, tray, tip, delivery_date_time, first_name, last_name, address, credit_card, email, login)
58
+ return call_api(:post, ['o', restaurant_id], login, data)
59
+ end
60
+
61
+ # Place an order and create a user account
62
+ # Arguments:
63
+ # restaurant_id -- Ordr.in's restaurant identifier
64
+ # tray -- A tray of food to order. Should be an ordrin.data.Tray object
65
+ # tip -- The tip amount
66
+ # delivery_date_time -- Either 'ASAP' or a datetime object in the future
67
+ # first_name -- The orderer's first name
68
+ # last_name -- The orderer's last name
69
+ # address -- An address object (Ordrin::Data::Address) or the nickname of a saved address
70
+ # credit_card -- A credit card object (Ordrin::Data::CreditCard) or the nickname of a saved credit card
71
+ # email -- The email address of the user
72
+ # password -- The user's password (in plain text)
73
+ def order_create_user(restaurant_id, tray, tip, delivery_date_time, first_name, last_name, address, credit_card, email, password)
74
+ data = build_dict(restaurant_id, tray, tip, delivery_date_time, first_name, last_name, address, credit_card, email)
75
+ data['pw'] = Data::UserLogin.hash_password(password)
76
+ return call_api(:post, ['o', restaurant_id], nil, data)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,66 @@
1
+ require_relative 'normalize'
2
+ require 'net/http'
3
+ require 'json'
4
+ require 'digest'
5
+
6
+ module Ordrin
7
+ # A base object for calling one part of the ordr.in API
8
+ class OrdrinApi
9
+
10
+ attr_reader :base_url
11
+
12
+ # Save the url and key parameters in the object
13
+ # Arguments:
14
+ # key -- The developer's API key
15
+ # base_url -- the url that all API call urls will expand from
16
+ def initialize(key, base_url)
17
+ @key = key
18
+ @base_url = Normalize.normalize(base_url, :url)
19
+ end
20
+
21
+ protected
22
+
23
+ # Calls the api at the saved url and returns the return value as Python data structures.
24
+ # Rethrows any api error as a Ruby exception
25
+ def call_api(method, arguments, login=nil, data=nil)
26
+ methods = {:get => Net::HTTP::Get,
27
+ :post => Net::HTTP::Post,
28
+ :put => Net::HTTP::Put,
29
+ :delete => Net::HTTP::Delete}
30
+ method = methods[method]
31
+ uri = ('/'+(arguments.collect {|a| URI.encode a.to_s})*'/')
32
+ full_url = URI.parse(base_url+uri)
33
+ req = method.new(full_url.request_uri)
34
+ unless data.nil?
35
+ req.body = URI.encode_www_form(data)
36
+ end
37
+ unless @key.empty?
38
+ req['X-NAAMA-CLIENT-AUTHENTICATION'] = "id=\"#{@key}\", version=\"1\""
39
+ end
40
+ unless login.nil?
41
+ hash_code = Digest::SHA256.new.hexdigest("#{login.password}#{login.email}#{uri}")
42
+ req['X-NAAMA-AUTHENTICATION'] = "username=\"#{login.email}\", response=\"#{hash_code}\", version=\"1\""
43
+ end
44
+ http = Net::HTTP.new(full_url.host, full_url.port)
45
+ if full_url.scheme == "https"
46
+ http.use_ssl = true
47
+ http.ca_file = File.join(File.dirname(__FILE__), "cacert.pem")
48
+ end
49
+ res = http.start {|http| http.request(req)}
50
+ #error if not OK response
51
+ res.value
52
+ result = JSON.parse(res.body)
53
+ if result.is_a? Hash
54
+ if result.has_key?('_error') and result['_error']!=0
55
+ if result.has_key?('text')
56
+ raise Errors::ApiError.new(result['msg'], result['text'])
57
+ else
58
+ raise Errors::ApiError.new(result['msg'])
59
+ end
60
+ result
61
+ end
62
+ end
63
+ result
64
+ end
65
+ end
66
+ end
@@ -0,0 +1,56 @@
1
+ require_relative 'ordrinapi'
2
+ require_relative 'normalize'
3
+
4
+ module Ordrin
5
+ # This object's methods access the ordr.in restaurant API. All return values
6
+ # are documented at http://ordr.in/developers/restaurant
7
+ class RestaurantApi < OrdrinApi
8
+ # Get a list of dicts representing restaurants that will deliver to the
9
+ # given address at the given time.
10
+ # Arguments:
11
+ # date_time -- Either 'ASAP' or a datetime object in the future
12
+ # address -- the address to deliver to. Should be an Ordrin::Data::Address object
13
+ def get_delivery_list(date_time, address)
14
+ dt = Normalize.normalize(date_time, :datetime)
15
+ return call_api(:get, ['dl', dt, address.zip, address.city, address.addr])
16
+ end
17
+
18
+ # Get data about a given restaurant, including whether it will deliver to
19
+ # the specified address at the specified time
20
+ # Arguments:
21
+ # restaurant_id -- Ordr.in's restaurant identifier
22
+ # date_time -- Either 'ASAP' or a datetime object in the future
23
+ # address -- the address to deliver to. Should be an Ordrin::Data::Address object
24
+ def get_delivery_check(restaurant_id, date_time, address)
25
+ dt = Normalize.normalize(date_time, :datetime)
26
+ restauant_id = Normalize.normalize(restaurant_id, :number)
27
+ return call_api(:get, ['dc', restaurant_id, dt, address.zip, address.city, address.addr])
28
+ end
29
+
30
+ # Get data about a given restaurant, including whether it will deliver to
31
+ # the specified address at the specified time, and what the fee will be on an
32
+ # order with the given subtotal and tip
33
+ # Arguments:
34
+ # restaurant_id -- Ordr.in's restaurant identifier
35
+ # subtotal -- the subtotal of the order
36
+ # tip -- the tip on the order
37
+ # date_time -- Either 'ASAP' or a datetime object in the future
38
+ # address -- the address to deliver to. Should be an Ordrin::Data::Address object
39
+ def get_fee(restaurant_id, subtotal, tip, date_time, address)
40
+ dt = Normalize.normalize(date_time, :datetime)
41
+ restaurant_id = Normalize.normalize(restaurant_id, :number)
42
+ subtotal = Normalize.normalize(subtotal, :money)
43
+ tip = Normalize.normalize(tip, :money)
44
+ return call_api(:get, ['fee', restaurant_id, subtotal, tip, dt, address.zip, address.city, address.addr])
45
+ end
46
+
47
+ # Get details of the given restaurant, including contact information and
48
+ # the menu
49
+ # Arguments:
50
+ # restaurant_id -- Ordr.in's restaurant identifier
51
+ def get_details(restaurant_id)
52
+ restaurant_id = Normalize.normalize(restaurant_id, :number)
53
+ return call_api(:get, ['rd', restaurant_id])
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,142 @@
1
+ require_relative 'ordrinapi'
2
+ require_relative 'normalize'
3
+ require_relative 'data'
4
+
5
+ module Ordrin
6
+ # This class will be used to access the user API. All return values
7
+ # are documented at http://ordr.in/developers/user
8
+ class UserApi < OrdrinApi
9
+ # Gets account information for the user associated with login
10
+ # Arguments:
11
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
12
+ def get(login)
13
+ return call_api(:get, ['u', login.email], login)
14
+ end
15
+
16
+ # Creates account for the user associated with login. Throws a relevant exception
17
+ # on failure.
18
+ # Arguments:
19
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
20
+ # first_name -- the user's first name
21
+ # last_name -- the user's last name
22
+ def create(login, first_name, last_name)
23
+ data = {'email' => login.email,
24
+ 'first_name' => Normalize.normalize(first_name, :name),
25
+ 'last_name' => Normalize.normalize(last_name, :name),
26
+ 'pw' => login.password}
27
+ return call_api(:post, ['u', login.email], nil, data)
28
+ end
29
+
30
+ # Updates account for the user associated with login. Throws a relevant exception
31
+ # on failure.
32
+ # Arguments:
33
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
34
+ # first_name -- the user's first name
35
+ # last_name -- the user's last name
36
+ def update(login, first_name, last_name)
37
+ data = {'email' => login.email,
38
+ 'first_name' => Normalize.normalize(first_name, :name),
39
+ 'last_name' => Normalize.normalize(last_name, :name),
40
+ 'pw' => login.password}
41
+ return call_api(:post, ['u', login.email], login, data)
42
+ end
43
+
44
+ # Get a list of all saved addresses for the user associated with login.
45
+ # Arguments:
46
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
47
+ def get_all_addresses(login)
48
+ return call_api(:get, ['u', login.email, 'addrs'], login)
49
+ end
50
+
51
+ # Get a saved address belonging to the logged in user by nickname.
52
+ # Arguments:
53
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
54
+ # addr_nick -- the nickname of the address to get
55
+ def get_address(login, addr_nick)
56
+ return call_api(:get, ['u', login.email, 'addrs', Normalize.normalize(addr_nick, :nick)], login)
57
+ end
58
+
59
+ # Save an address by nickname for the logged in user
60
+ # Throws a relevant exception on failure
61
+ # Arguments:
62
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
63
+ # addr_nick -- the nickname of the address to save
64
+ # address -- the address to save. Should be an Ordrin::Data::Address object
65
+ def set_address(login, addr_nick, address)
66
+ return call_api(:put, ['u', login.email, 'addrs', Normalize.normalize(addr_nick, :nick)], login, address.make_dict)
67
+ end
68
+
69
+ # Remove an address, saved by the logged in user, by nickname
70
+ # Throws a relevant exception on failure.
71
+ # Arguments:
72
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
73
+ # addr_nick -- the nickname of the address to remove
74
+ def remove_address(login, addr_nick)
75
+ return call_api(:delete, ['u', login.email, 'addrs', Normalize.normalize(addr_nick, :nick)], login)
76
+ end
77
+
78
+ # Get a list of all saved credit cards for the user associated with login.
79
+ # Arguments:
80
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
81
+ def get_all_credit_cards(login)
82
+ return call_api(:get, ['u', login.email, 'ccs'], login)
83
+ end
84
+
85
+ # Get a saved credit card belonging to the logged in user by nickname.
86
+ # Arguments:
87
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
88
+ # card_nick -- the nickname of the credit card to get
89
+ def get_credit_card(login, card_nick)
90
+ return call_api(:get, ['u', login.email, 'ccs', Normalize.normalize(card_nick, :nick)], login)
91
+ end
92
+
93
+ # Save an credit card by nickname for the logged in user
94
+ # Throws a relevant exception on failure
95
+ # Arguments:
96
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
97
+ # card_nick -- the nickname of the credit card to save
98
+ # credit_card -- the credit card to save. Should be an Ordrin::Data::CreditCard object
99
+ def set_credit_card(login, card_nick, credit_card)
100
+ card_nick = Normalize.normalize(card_nick, :nick)
101
+ data = credit_card.make_dict
102
+ data.merge!(login.make_dict)
103
+ data['nick'] = card_nick
104
+ return call_api(:put, ['u', login.email, 'ccs', card_nick], login, data)
105
+ end
106
+
107
+ # Remove an credit card, saved by the logged in user, by nickname
108
+ # Throws a relevant exception on failure.
109
+ # Arguments:
110
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
111
+ # card_nick -- the nickname of the credit card to remove
112
+ def remove_credit_card(login, card_nick)
113
+ return call_api(:delete, ['u', login.email, 'ccs', Normalize.normalize(card_nick, :nick)], login)
114
+ end
115
+
116
+ # Get a list of previous orders by the logged in user.
117
+ # Arguments:
118
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
119
+ def get_order_history(login)
120
+ return call_api(:get, ['u', login.email, 'orders'], login)
121
+ end
122
+
123
+ # Get details of a particular previous order by the logged in user.
124
+ # Arguments:
125
+ # login -- the user's login information. Should be an Ordrin::Data::UserLogin object
126
+ # order_id -- The order ID
127
+ def get_order_detail(login, order_id)
128
+ return call_api(:get, ['u', login.email, 'orders', Normalize.normalize(order_id, :alphanum)], login)
129
+ end
130
+
131
+ # Change the logged in user's password.
132
+ # Arguments:
133
+ # login -- the user's current login information. Should be an Ordrin::Data::UserLogin object
134
+ # new_password -- the new password (in plain text)
135
+ def set_password(login, new_password)
136
+ data = {'email' => login.email,
137
+ 'password' => Data::UserLogin.hash_password(new_password),
138
+ 'previous_password' => login.password}
139
+ return self.call_api(:put, ['u', login.email, 'password'], login, data)
140
+ end
141
+ end
142
+ end
data/lib/ordrin.rb ADDED
@@ -0,0 +1,58 @@
1
+ require_relative 'ordrin/restaurant'
2
+ require_relative 'ordrin/user'
3
+ require_relative 'ordrin/order'
4
+ require_relative 'ordrin/data'
5
+
6
+ module Ordrin
7
+ class APIs
8
+
9
+ attr_reader :restaurant, :user, :order
10
+
11
+ # Sets up this module to make API calls. The first argument is the developer's
12
+ # API key. The other three are the URLs corresponding to the three parts of the api.
13
+ # No API calls will work until this function is called. API objects will only be
14
+ # instantiated for URLs that are passed in.
15
+
16
+ # Arguments:
17
+ # api_key -- The developer's API key
18
+ # servers -- How the server URLs should be set. Must be :production, :test, or :custom
19
+ # restaurant_url -- The base url for the restaurant API. Can only be set if servers==:custom.
20
+ # user_url -- The base url for the user API. Can only be set if servers==:custom.
21
+ # order_url -- The base url for the order API. Can only be set if servers==:custom.
22
+ def initialize(api_key, servers, restaurant_url=nil, user_url=nil, order_url=nil)
23
+ @api_key = api_key
24
+ if servers!=:custom
25
+ unless restaurant_url.nil? and user_url.nil? and order_url.nil?
26
+ raise ArgumentError.new("Individual URL parameters can only be set if servers is set to :custom")
27
+ end
28
+ end
29
+ if servers==:production
30
+ restaurant_url = "https://r.ordr.in/"
31
+ user_url = "https://u.ordr.in/"
32
+ order_url = "https://o.ordr.in/"
33
+ elsif servers==:test
34
+ restaurant_url = "https://r-test.ordr.in/"
35
+ user_url = "https://u-test.ordr.in/"
36
+ order_url = "https://o-test.ordr.in/"
37
+ elsif servers!=:custom
38
+ raise ArgumentError.new("servers must be set to :production, :test, or :custom")
39
+ end
40
+ unless restaurant_url.nil?
41
+ @restaurant = RestaurantApi.new(api_key, restaurant_url)
42
+ end
43
+ unless user_url.nil?
44
+ @user = UserApi.new(api_key, user_url)
45
+ end
46
+ unless order_url.nil?
47
+ @order = OrderApi.new(api_key, order_url)
48
+ end
49
+ end
50
+
51
+ def config
52
+ return {"API key" => api_key,
53
+ "Restaurant URL" => @restaurant.base_url,
54
+ "User URL" => @user.base_url,
55
+ "Order URL" => @order.base_url}
56
+ end
57
+ end
58
+ end
metadata ADDED
@@ -0,0 +1,72 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ordrin
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Ordr.in
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-06-14 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: json
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: 1.7.3
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ! '>='
28
+ - !ruby/object:Gem::Version
29
+ version: 1.7.3
30
+ description: Ordrin API wrapper. Used to simplify making calls to the Ordr.in API
31
+ in Ruby
32
+ email:
33
+ - tech@ordr.in
34
+ executables:
35
+ - ordrindemo.rb
36
+ extensions: []
37
+ extra_rdoc_files: []
38
+ files:
39
+ - lib/ordrin.rb
40
+ - lib/ordrin/data.rb
41
+ - lib/ordrin/errors.rb
42
+ - lib/ordrin/normalize.rb
43
+ - lib/ordrin/order.rb
44
+ - lib/ordrin/ordrinapi.rb
45
+ - lib/ordrin/restaurant.rb
46
+ - lib/ordrin/user.rb
47
+ - bin/ordrindemo.rb
48
+ homepage: https://github.com/ordrin/api-ruby
49
+ licenses: []
50
+ post_install_message:
51
+ rdoc_options: []
52
+ require_paths:
53
+ - lib
54
+ required_ruby_version: !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - ! '>='
58
+ - !ruby/object:Gem::Version
59
+ version: '0'
60
+ required_rubygems_version: !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ requirements: []
67
+ rubyforge_project:
68
+ rubygems_version: 1.8.23
69
+ signing_key:
70
+ specification_version: 3
71
+ summary: Ordrin API wrapper
72
+ test_files: []