ordrin 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
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: []