loyalty_lab_sdk 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/CHANGELOG.rdoc ADDED
@@ -0,0 +1,10 @@
1
+ = Loyalty Lab SDK
2
+
3
+ == Version 0.0.0
4
+ * Initial release
5
+ * Supports all LoyaltyAPI calls
6
+ * Supports lazy authentication
7
+ * Supports environment variable authentication
8
+ * Helpers for creating shoppers
9
+ * Supports retries on timeout errors
10
+ * Supports re-authentication when authentication token expires
data/LICENSE.txt ADDED
@@ -0,0 +1,9 @@
1
+ Copyright (c) 2012 RevPAR Collective, Inc.
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
4
+
5
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
6
+
7
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
8
+
9
+ THIS SOFTWARE IS IN NO WAY AFFILIATED WITH THE LOYALTY LAB ORGANIZATION.
data/README.rdoc ADDED
@@ -0,0 +1,45 @@
1
+ = Loyalty Lab SDK
2
+
3
+ A ruby library for accessing the Loyalty Lab API documented here: http://api.loyaltylab.com/loyaltyapi/help/index.html
4
+
5
+ This library attempts to be as transparent as possible by making all method calls, parameters, and objects identical to those documented at the above URL. Capitalization goes against typical ruby conventions, and is instead as documented by Loyalty Lab.
6
+
7
+ NOTE: This software is in no way affiliated with the Loyalty Lab organization.
8
+
9
+ == Synopsis
10
+
11
+ LoyaltyLabSDK.config(:username => 'foo', :password => 'bar')
12
+
13
+ api = LoyaltyLabSDK::LoyaltyAPI.new
14
+ shopper = api.GetShopper 'shopperId' => 1234
15
+ shopper_id = shopper['ShopperId']
16
+ point_balance = api.AdjustShopperPoints 'shopperId' => shopper_id,
17
+ 'pointChange' => 1000,
18
+ 'pointType' => 'Base',
19
+ 'description' => 'Surprise bonus'
20
+
21
+ new_retailer_shopper_id = 1234
22
+ new_shopper = api.build_default_shopper(new_retailer_shopper_id)
23
+ new_shopper['EmailAddress'] = 'test@gmail.com'
24
+ new_shopper['FirstName'] = 'Joe'
25
+ new_shopper['LastName'] = 'Schmoe'
26
+ new_card = api.build_default_card(new_retailer_shopper_id)
27
+ api.CreateShopperWithCard 'shopper' => new_shopper, 'card' => new_card
28
+
29
+ == Authentication
30
+
31
+ By default, a new LoyaltyAPI instance will authenticate itself against the API (using #authenticate!) and store the authentication token when it is constructed. Authentication may be skipped by setting the :lazy_authentication option to true when calling the constructor. In this case, authentication will occur when the first API call is made (or it may be done explicitly).
32
+
33
+ According to Loyalty Lab, the authentication token will expire after 20 minutes of inactivity. By default, LoyaltyAPI has an option :allow_reauthenticate that defaults to true (may be overridden). If it's true, then when you receive an AuthenticationError while making an API call on an already-authenticated LoyaltyAPI instance, it will try once to re-authenticate automatically. This behavior could be overridden by the client by setting :allow_reauthenticate to false and then either ignoring the error, pro-actively calling #authenticate!, or catching AuthenticationError exceptions and re-authenticating then.
34
+
35
+ == Timeouts
36
+
37
+ By default, all requests will timeout (for opening connections and making requests) after 15 seconds. This may be overridden by setting the :open_timeout and :read_timeout options when calling LoyaltyLabSDK.config.
38
+
39
+ == Environment Variables
40
+
41
+ For convenience in a command-line environment, configuration of username and password may be skipped by setting the LOYALTY_LAB_SDK_USERNAME and LOYALTY_LAB_SDK_PASSWORD environment variables.
42
+
43
+ == Rails
44
+
45
+ If running in a rails environment, this configuration will automatically use the global Rails.logger instance. This behavior may be overridden by passing in a :logger option to LoyaltyLabSDK.config.
@@ -0,0 +1,62 @@
1
+ require 'logger'
2
+
3
+ module LoyaltyLabSDK
4
+
5
+ DEFAULT_TIMEOUT = 15
6
+ DEFAULT_RETRIES = 2
7
+
8
+ # Globally configures and retrieves configuration for the Loyalty Lab SDK.
9
+ #
10
+ # == Environment Variables
11
+ #
12
+ # For convenience in a command-line environment, configuration may be skipped
13
+ # by setting the LOYALTY_LAB_SDK_USERNAME and LOYALTY_LAB_SDK_PASSWORD
14
+ # environment variables, which are self-explanatory.
15
+ #
16
+ # == Rails
17
+ #
18
+ # If running in a rails environment, this configuration will automatically use
19
+ # the global Rails.logger instance. This behavior may be overridden by passing
20
+ # in a :logger option.
21
+ #
22
+ # @param [Hash] options
23
+ # @option options [String] :username (nil) Loyalty Lab account username
24
+ # @option options [String] :password (nil) Loyalty Lab account password
25
+ # @option options [Logger] :logger (Rails.logger) Logger to use
26
+ # @option options [Numeric] :open_timeout (LoyaltyLabSDK::DEFAULT_TIMEOUT)
27
+ # Number of seconds to wait for the connection to open
28
+ # (see Net::HTTP#open_timeout)
29
+ # @option options [Numeric] :read_timeout (LoyaltyLabSDK::DEFAULT_TIMEOUT)
30
+ # Number of seconds to wait for one block to be read
31
+ # (see Net::HTTP#read_timeout)
32
+ # @option options [Integer] :connection_error_retries
33
+ # (LoyaltyLabSDK::DEFAULT_RETRIES) Number of retries that will be attempted
34
+ # if a connection error (timeout) occurs
35
+ def self.config(options = nil)
36
+ @config ||= {
37
+ :username => ENV['LOYALTY_LAB_SDK_USERNAME'],
38
+ :password => ENV['LOYALTY_LAB_SDK_PASSWORD'],
39
+ :logger => default_logger,
40
+ :open_timeout => DEFAULT_TIMEOUT,
41
+ :read_timeout => DEFAULT_TIMEOUT,
42
+ :connection_error_retries => DEFAULT_RETRIES,
43
+ }
44
+
45
+ @config.merge!(options) if options
46
+
47
+ @config
48
+ end
49
+
50
+ private
51
+
52
+ def self.default_logger
53
+ if defined?(::Rails)
54
+ ::Rails.logger
55
+ else
56
+ logger = ::Logger.new(STDERR)
57
+ logger.level = ::Logger::INFO
58
+ logger
59
+ end
60
+ end
61
+
62
+ end
@@ -0,0 +1,18 @@
1
+ module LoyaltyLabSDK
2
+
3
+ # An abstract super-class of all other LoyaltyLabApi Error classes
4
+ class Error < StandardError; end
5
+
6
+ # Indicates an error establishing a connection to the API, or a timeout that occurs while
7
+ # making an API call. Is relatively common and transient- worth retrying in important cases.
8
+ class ConnectionError < Error; end
9
+
10
+ # Indicates an error authenticating with the provided credentials.
11
+ class AuthenticationError < Error; end
12
+
13
+ # Indicates any other API error (generally should be non-transient and not worth retrying). The
14
+ # error code and string will be present in this error's message if one was provided in the
15
+ # response.
16
+ class UnknownError < Error; end
17
+
18
+ end
@@ -0,0 +1,329 @@
1
+ require 'date'
2
+ require 'loyalty_lab_sdk/exceptions'
3
+ require 'active_support/inflector'
4
+ require 'savon'
5
+
6
+ module LoyaltyLabSDK
7
+ class LoyaltyAPI
8
+
9
+ ENDPOINT = 'https://api.loyaltylab.com/loyaltyapi/loyaltyapi.asmx'
10
+ NAMESPACE = 'http://www.loyaltylab.com/loyaltyapi/'
11
+
12
+ # Constructs an object-oriented API to loyalty lab.
13
+ #
14
+ # In addition to the options specified below, any of the options documented
15
+ # in LoyaltyLabSDK#config may be overridden.
16
+ #
17
+ # It will establish a connection, authenticate the username/password, and store
18
+ # the authentication data upon construction unless the lazy_authentication
19
+ # parameter is set. This authentication token times out after 20 minutes
20
+ # (double-check Loyalty Lab's documentation), so a client wishing to use this
21
+ # object should accommodate that, and re-call #authenticate! if necessary.
22
+ #
23
+ # @param [Hash] options
24
+ # @option options [boolean] :lazy_authentication (false) Delays authentication
25
+ # until either the first call is made or it is done explicitly.
26
+ # @option options [boolean] :allow_reauthenticate (true) If true, will
27
+ # attempt to re-authenticate the client once automatically if an
28
+ # AuthenticationError is thrown (on any call).
29
+ def initialize(options = {})
30
+ self.config = {
31
+ :lazy_authentication => false,
32
+ :allow_reauthenticate => true,
33
+ }.merge!(LoyaltyLabSDK.config).merge!(options)
34
+
35
+ Savon.configure do |c|
36
+ c.logger = config[:logger]
37
+ c.raise_errors = false
38
+ end
39
+
40
+ initialize_client
41
+
42
+ @authenticated = false
43
+
44
+ unless config[:lazy_authentication]
45
+ authenticate!
46
+ end
47
+ end
48
+
49
+ # Authenticates the client.
50
+ #
51
+ # This method is called by the constructor unless lazy_authentication is
52
+ # on, so this will not typically need to be invoked directly.
53
+ #
54
+ # An object that hasn't been called in 20 minutes will have its
55
+ # authentication headers expired by Loyalty Lab, requiring this method to
56
+ # be invoked again on long-lived objects.
57
+ def authenticate!
58
+ response = self.AuthenticateUser(
59
+ { :username => config[:username], :password => config[:password] },
60
+ :is_authenticate => true,
61
+ :allow_reauthenticate => false)
62
+
63
+ auth_data = {
64
+ :retailer_guid => response['RetailerGuid'],
65
+ :authenticated => response['Authenticated'],
66
+ :token => response['Token'],
67
+ :ics_user_id => response['ICSUserID']
68
+ }
69
+
70
+ if !auth_data[:authenticated]
71
+ raise AuthenticationError, 'authentication failed'
72
+ end
73
+
74
+ self.retailer_guid = auth_data[:retailer_guid]
75
+
76
+ self.auth_header = {
77
+ "wsdl:AuthenticationResult" => {
78
+ "wsdl:RetailerGuid" => auth_data[:retailer_guid],
79
+ "wsdl:Authenticated" => auth_data[:authenticated],
80
+ "wsdl:Token" => auth_data[:token],
81
+ "wsdl:ICSUserID" => auth_data[:ics_user_id],
82
+ }
83
+ }
84
+
85
+ @authenticated = true
86
+ end
87
+
88
+ # Implements most API calls. The API method should be passed as defined in Loyalty
89
+ # Lab's API documentation here: http://api.loyaltylab.com/loyaltyapi/help/index.html
90
+ #
91
+ # Each of the parameters should be passed in a hash as documented.
92
+ #
93
+ # Responses will be in the documented format (a "string" return value with respond
94
+ # with a string, etc...), or if it's an object (such as a Shopper object), the
95
+ # result will be a hash with string keys for object
96
+ #
97
+ # == Examples:
98
+ #
99
+ # === Retrieve a shopper
100
+ # shopper = api.GetShopperByEmail 'email' => 'test@gmail.com'
101
+ #
102
+ # === Adjust shopper point balance
103
+ # point_balance = api.AdjustShopperPoints 'shopperId' => shopper['ShopperId'],
104
+ # 'pointChange' => 1000,
105
+ # 'pointType' => 'Base',
106
+ # 'description' => 'Bonus'
107
+ #
108
+ # === Create a shopper with card
109
+ # new_shopper = api.build_default_shopper(1234)
110
+ # new_shopper['EmailAddress'] = 'test2@gmail.com'
111
+ # new_shopper['FirstName'] = 'Joe'
112
+ # new_shopper['LastName'] = 'Schmoe'
113
+ # new_card = api.build_default_card(1234)
114
+ # api.CreateShopperWithCard 'shopper' => new_shopper, 'card' => new_card
115
+ def method_missing(method_name, *args)
116
+ call_api_method(method_name, *args)
117
+ end
118
+
119
+ # Initializes and returns a shopper object with default fields set for use with a
120
+ # CreateShopper call.
121
+ #
122
+ # This object should be updated with all relevant fields (ie. email address, first
123
+ # name, phone number, etc) before being saved.
124
+ def build_default_shopper(retailer_shopper_id)
125
+ now = DateTime.now
126
+
127
+ {
128
+ 'ShopperId' => 0,
129
+ 'RetailerGUID' => retailer_guid,
130
+ 'EmailAddress' => '',
131
+ 'EmailFrequency' => 1,
132
+ 'EmailFrequencyUnit' => 'D',
133
+ 'EmailFormat' => 'HTML',
134
+ 'Password' => ' ',
135
+ 'Status' => 'A',
136
+ 'LastName' => '',
137
+ 'MiddleInitial' => '',
138
+ 'FirstName' => '',
139
+ 'Address1' => '',
140
+ 'City' => '',
141
+ 'State' => '',
142
+ 'Zip' => '',
143
+ 'PhoneNumber' => '',
144
+ 'ProfileCreateDateTime' => now,
145
+ 'ProfileUpdateDateTime' => now,
146
+ 'CreateDateTime' => now,
147
+ 'PasswordLastChanged' => now,
148
+ 'Origin' => 'W',
149
+ 'RetailerShopperId' => retailer_shopper_id.to_s,
150
+ 'FileImportId' => 0,
151
+ 'BulkEmail' => 1,
152
+ 'LoyaltyMember' => true,
153
+ 'PersonStatus' => 'P',
154
+ 'RetailerRegistered' => false,
155
+ 'MailOptIn' => false,
156
+ 'PhoneOptIn' => false,
157
+ 'RetailerShopperCreationDate' => now,
158
+ 'LoyaltyLabCreateDateTime' => now,
159
+ 'StatusUpdateDateTime' => now
160
+ }
161
+ end
162
+
163
+ # Initializes and returns a card object with default fields set for use with a
164
+ # CreateShopper call.
165
+ #
166
+ # This object should be updated with all relevant fields before being saved (if
167
+ # necessary).
168
+ def build_default_card(retailer_shopper_id)
169
+ now = DateTime.now
170
+
171
+ {
172
+ 'RegisteredCardId' => 0,
173
+ 'ShopperId' => 0,
174
+ 'CommonName' => 'loyalty member id',
175
+ 'AlternateCardIdentifier' => retailer_shopper_id.to_s,
176
+ 'CardType' => 'L',
177
+ 'ExpirationMonth' => 12,
178
+ 'ExpirationYear' => 3010,
179
+ 'LastFour' => ' ',
180
+ 'CardHolderName' => ' ',
181
+ 'Status' => 'A',
182
+ 'CreateDateTime' => now,
183
+ 'IsPreferred' => ' ',
184
+ 'FileImportId' => 0
185
+ }
186
+ end
187
+
188
+ private
189
+
190
+ ERROR_HANDLERS = {
191
+ '100' => AuthenticationError
192
+ }
193
+
194
+ SHOPPER_FIELD_MAPPINGS = {
195
+ :retailer_guid => 'RetailerGUID'
196
+ }
197
+
198
+ FIELD_MAPPING = {
199
+ 'AuthenticateUser' => {
200
+ :ics_user_id => 'ICSUserID'
201
+ },
202
+ 'GetShopperByID' => SHOPPER_FIELD_MAPPINGS,
203
+ 'GetShopperByEmail' => SHOPPER_FIELD_MAPPINGS,
204
+ 'GetShopperByRetailerID' => SHOPPER_FIELD_MAPPINGS
205
+ }
206
+
207
+ attr_accessor :config, :client, :retailer_guid, :auth_header
208
+
209
+ def initialize_client
210
+ self.client = ::Savon::Client.new do
211
+ wsdl.endpoint = ENDPOINT
212
+ wsdl.namespace = NAMESPACE
213
+ http.open_timeout = config[:open_timeout]
214
+ http.read_timeout = config[:read_timeout]
215
+ end
216
+ end
217
+
218
+ def call_api_method(method_name, request_body = nil, options = {})
219
+ options = {
220
+ :is_authenticate => false,
221
+ :connection_error_retries => config[:connection_error_retries],
222
+ :allow_reauthenticate => config[:allow_reauthenticate],
223
+ }.merge(options)
224
+
225
+ if !@authenticated && !options[:is_authenticate]
226
+ authenticate!
227
+ end
228
+
229
+ method_name = method_name.to_s
230
+
231
+ request_body ||= {}
232
+
233
+ prepared_request_body = prepare_request(request_body)
234
+
235
+ begin
236
+ response = client.request "wsdl:#{method_name}" do |soap, wsdl, http, wsse|
237
+ http.headers["SOAPAction"] = "http://www.loyaltylab.com/loyaltyapi/#{method_name}"
238
+ soap.header = auth_header unless options[:is_authenticate]
239
+ soap.body = prepared_request_body
240
+ end
241
+ rescue Exception => e
242
+ config[:logger].debug "communication exception during request: #{e.message}"
243
+ if options[:connection_error_retries] > 0
244
+ config[:logger].debug "#{options[:connection_error_retries]} retry attempt(s) remaining; retrying..."
245
+ options[:connection_error_retries] -= 1
246
+ return call_api_method(method_name, request_body, options)
247
+ else
248
+ config[:logger].debug 'no retry attempts remaining'
249
+ raise ConnectionError, e.message
250
+ end
251
+ end
252
+
253
+ begin
254
+ check_response_for_errors(response)
255
+ rescue AuthenticationError => e
256
+ if options[:allow_reauthenticate]
257
+ authenticate!
258
+ options[:allow_reauthenticate] = false
259
+ return call_api_method(method_name, request_body, options)
260
+ else
261
+ raise e
262
+ end
263
+ end
264
+
265
+ modified_method_name = method_name.underscore
266
+
267
+ response = response.to_hash["#{modified_method_name}_response".to_sym]
268
+ response = response["#{modified_method_name}_result".to_sym] unless response.nil?
269
+
270
+ normalize_response method_name, response
271
+ end
272
+
273
+ # Returns a request hash with proper namespacing prefixes and transformations applied
274
+ # to the given request hash.
275
+ def prepare_request(request_hash = {}, nested_object = false)
276
+ result = {}
277
+ request_hash.each do |key, value|
278
+ # handle nested objects in the request (like a shopper)
279
+ value = prepare_request(value, true) if value.instance_of?(Hash)
280
+
281
+ # add the wsdl: prefix to the key
282
+ result["wsdl:#{key}"] = value
283
+ end
284
+ result
285
+ end
286
+
287
+ # Takes a raw response and checks it for errors, throwing proper exceptions if
288
+ # present.
289
+ def check_response_for_errors(response)
290
+ # take no action if there are no errors
291
+ return unless response.soap_fault? || response.http_error?
292
+
293
+ response_hash = response.to_hash
294
+ if response_hash[:fault] && response_hash[:fault][:detail]
295
+ # we can parse the response, so check the specific error code
296
+ error_code = response_hash[:fault][:detail][:code]
297
+
298
+ # if we can recognize the specific code, dispatch the proper exception
299
+ if ERROR_HANDLERS.has_key? error_code
300
+ raise ERROR_HANDLERS[error_code], response_hash[:fault][:faultstring]
301
+ end
302
+
303
+ # otherwise return a well-defined error string with the code and error message
304
+ raise UnknownError, "#{response_hash[:fault][:detail][:code]} #{response_hash[:fault][:detail][:description]}: #{response_hash[:fault][:faultstring]}"
305
+ end
306
+
307
+ # we can't parse the error, so just pass back the entire response body
308
+ raise UnknownError, response.to_s
309
+ end
310
+
311
+ def normalize_response(method_name, response, mapping = FIELD_MAPPING[method_name])
312
+ if response.instance_of?(Hash)
313
+ mapping ||= {}
314
+ response.inject({}) do |hash, (key, value)|
315
+ new_key = mapping[key] || key.to_s.camelize
316
+ hash[new_key] = normalize_response(nil, value, nil) # TODO: implement nested mappings
317
+ hash
318
+ end
319
+ elsif response.instance_of?(Array)
320
+ response.collect do |value|
321
+ normalize_response(nil, value, nil) # TODO: implement nested mappings
322
+ end
323
+ else
324
+ response
325
+ end
326
+ end
327
+
328
+ end
329
+ end
@@ -0,0 +1,3 @@
1
+ module LoyaltyLabSDK
2
+ VERSION = '0.0.0'
3
+ end
@@ -0,0 +1,24 @@
1
+ require 'httpi'
2
+ require 'net/http'
3
+
4
+ # prevents debug log messages that clutter output
5
+ HTTPI.log = false
6
+
7
+ # prevents warning
8
+ class Net::HTTP
9
+ alias_method :old_initialize, :initialize
10
+ def initialize(*args)
11
+ old_initialize(*args)
12
+ @ssl_context = OpenSSL::SSL::SSLContext.new
13
+ @ssl_context.verify_mode = OpenSSL::SSL::VERIFY_NONE
14
+ end
15
+ end
16
+
17
+ require 'loyalty_lab_sdk/config'
18
+ require 'loyalty_lab_sdk/exceptions'
19
+
20
+ module LoyaltyLabSDK
21
+
22
+ autoload :LoyaltyAPI, 'loyalty_lab_sdk/loyalty_api'
23
+
24
+ end
metadata ADDED
@@ -0,0 +1,115 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: loyalty_lab_sdk
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - David Dawson
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-01-09 00:00:00.000000000Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: activesupport
16
+ requirement: &70125150677680 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70125150677680
25
+ - !ruby/object:Gem::Dependency
26
+ name: httpi
27
+ requirement: &70125150676260 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ~>
31
+ - !ruby/object:Gem::Version
32
+ version: '0.9'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70125150676260
36
+ - !ruby/object:Gem::Dependency
37
+ name: i18n
38
+ requirement: &70125150674620 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '0.6'
44
+ type: :runtime
45
+ prerelease: false
46
+ version_requirements: *70125150674620
47
+ - !ruby/object:Gem::Dependency
48
+ name: savon
49
+ requirement: &70125150673640 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ~>
53
+ - !ruby/object:Gem::Version
54
+ version: '0.9'
55
+ type: :runtime
56
+ prerelease: false
57
+ version_requirements: *70125150673640
58
+ - !ruby/object:Gem::Dependency
59
+ name: guid
60
+ requirement: &70125150672220 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ~>
64
+ - !ruby/object:Gem::Version
65
+ version: '0.1'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70125150672220
69
+ description: Full documentation of the API is at http://api.loyaltylab.com/loyaltyapi/help/index.html.
70
+ email: david@stashrewards.com
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files:
74
+ - README.rdoc
75
+ - CHANGELOG.rdoc
76
+ - LICENSE.txt
77
+ files:
78
+ - lib/loyalty_lab_sdk/config.rb
79
+ - lib/loyalty_lab_sdk/exceptions.rb
80
+ - lib/loyalty_lab_sdk/loyalty_api.rb
81
+ - lib/loyalty_lab_sdk/version.rb
82
+ - lib/loyalty_lab_sdk.rb
83
+ - README.rdoc
84
+ - CHANGELOG.rdoc
85
+ - LICENSE.txt
86
+ homepage: http://www.stashrewards.com
87
+ licenses: []
88
+ post_install_message:
89
+ rdoc_options:
90
+ - --main
91
+ - README.rdoc
92
+ require_paths:
93
+ - lib
94
+ required_ruby_version: !ruby/object:Gem::Requirement
95
+ none: false
96
+ requirements:
97
+ - - ! '>='
98
+ - !ruby/object:Gem::Version
99
+ version: '0'
100
+ segments:
101
+ - 0
102
+ hash: -3780691586861817189
103
+ required_rubygems_version: !ruby/object:Gem::Requirement
104
+ none: false
105
+ requirements:
106
+ - - ! '>='
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubyforge_project:
111
+ rubygems_version: 1.8.10
112
+ signing_key:
113
+ specification_version: 3
114
+ summary: Library for accessing the Loyalty Lab API.
115
+ test_files: []