reward_station-gilman 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (34) hide show
  1. data/.gitignore +5 -0
  2. data/.rspec +1 -0
  3. data/.rvmrc +1 -0
  4. data/Gemfile +9 -0
  5. data/LICENSE +0 -0
  6. data/README.md +228 -0
  7. data/Rakefile +5 -0
  8. data/lib/reward_station.rb +9 -0
  9. data/lib/reward_station/client.rb +210 -0
  10. data/lib/reward_station/errors.rb +33 -0
  11. data/lib/reward_station/responses/award_points.xml +12 -0
  12. data/lib/reward_station/responses/award_points_invalid_token.xml +12 -0
  13. data/lib/reward_station/responses/create_user.xml +34 -0
  14. data/lib/reward_station/responses/create_user_exists.xml +9 -0
  15. data/lib/reward_station/responses/create_user_missing_info.xml +9 -0
  16. data/lib/reward_station/responses/return_point_summary.xml +22 -0
  17. data/lib/reward_station/responses/return_point_summary_invalid_token.xml +12 -0
  18. data/lib/reward_station/responses/return_popular_products.xml +362 -0
  19. data/lib/reward_station/responses/return_popular_products_invalid_token.xml +10 -0
  20. data/lib/reward_station/responses/return_token.xml +10 -0
  21. data/lib/reward_station/responses/return_token_invalid.xml +10 -0
  22. data/lib/reward_station/responses/return_user.xml +35 -0
  23. data/lib/reward_station/responses/return_user_invalid_token.xml +10 -0
  24. data/lib/reward_station/responses/return_user_invalid_user.xml +10 -0
  25. data/lib/reward_station/stub_client.rb +22 -0
  26. data/lib/reward_station/stub_response.rb +45 -0
  27. data/lib/reward_station/version.rb +3 -0
  28. data/lib/saml/auth_response.rb +176 -0
  29. data/lib/wsdl/reward_services.xml +511 -0
  30. data/reward_station.gemspec +25 -0
  31. data/spec/reward_station/service_spec.rb +351 -0
  32. data/spec/savon_helper.rb +82 -0
  33. data/spec/spec_helper.rb +13 -0
  34. metadata +132 -0
data/.gitignore ADDED
@@ -0,0 +1,5 @@
1
+ *.gem
2
+ .bundle
3
+ Gemfile.lock
4
+ pkg/*
5
+ .idea
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ree
data/Gemfile ADDED
@@ -0,0 +1,9 @@
1
+ source :rubygems
2
+
3
+ gem 'rake'
4
+
5
+ gemspec
6
+
7
+ group 'development' do
8
+ gem 'rspec', '2.6.0'
9
+ end
data/LICENSE ADDED
File without changes
data/README.md ADDED
@@ -0,0 +1,228 @@
1
+ # Xceleration Reward Station
2
+
3
+ Client library for Xceleration rewardstation.com SOAP service
4
+
5
+ ## Basic Usage
6
+
7
+ Common scenario is creating client instance using required parameters `:client_id` and `:client_password`.
8
+ Client implements several methods for accessing reward station SOAP API.
9
+
10
+ ### Initialization
11
+
12
+ reward_station = RewardStation::Client.new {
13
+ :client_id => "112112", # required
14
+ :client_password => "fsdftr#", # required
15
+ :organization_id => '150', # optional, default Organization ID
16
+ :program_id => 25, # optional, default Program ID
17
+ :point_reasond_code_id => 129 # optional, default Point Reason Code ID
18
+ :token => "sdfweqwrtwerfasdfas" # optional, initial Access Token value
19
+ :new_token_callback => lambda{ |token| ... } # optional, callback on Access Token change
20
+ }
21
+
22
+ ### New Token Callback
23
+ You can specify callback in constructor as `lambda` or `proc`
24
+ Or you can specify callback as block:
25
+
26
+ reward_station.new_token_calback do |new_token|
27
+ # notify other client instance about new token
28
+ end
29
+
30
+ ### Return Token
31
+ Access token request. Usually not needed on businnes level because client requests it when it became invalid or expired
32
+
33
+ token = reward_station.return_token
34
+
35
+ ### Award Points
36
+ Update award points
37
+
38
+ user_id = "130"
39
+ points = 10
40
+ description = "Action 'Call to client' "
41
+ program_id = 90 # optional
42
+ point_reasond_code_id = 129 # optional
43
+
44
+ confirmation_number = reward_station.award_points user_id, points, description, program_id, point_reason_code_id
45
+
46
+ ### Create User
47
+
48
+ user_attributes = reward_station.create_user :organization_id => '150',
49
+ :email => 'john5@company.com',
50
+ :first_name => 'John',
51
+ :last_name => 'Smith',
52
+ :user_name => 'john5@company.com',
53
+ :balance => 0
54
+ puts user_attributes.inspect
55
+ # {
56
+ # :user_id => '6727',
57
+ # :client_id => '100080',
58
+ # :user_name => 'john5@company.com',
59
+ # :email => 'john5@company.com',
60
+ # :encrypted_password => nil,
61
+ # :first_name => 'John',
62
+ # :last_name => 'Smith',
63
+ # :address_one => nil,
64
+ # :address_two => nil,
65
+ # :city => nil,
66
+ # :state_code => nil,
67
+ # :province => nil,
68
+ # :postal_code => nil,
69
+ # :country_code => 'USA',
70
+ # :phone => nil,
71
+ # :organization_id => '150',
72
+ # :organization_name => nil,
73
+ # :rep_type_id => '0',
74
+ # :client_region_id => '0',
75
+ #
76
+ # :is_active => true,
77
+ # :point_balance => '0',
78
+ # :manager_id => '0',
79
+ # :error_message => nil
80
+ # }
81
+
82
+ ### Update User
83
+
84
+ Similar to 'Create User'
85
+
86
+ user_attributes = reward_station.update_user user_id, :organization_id => '150',
87
+ :email => 'john5@company.com',
88
+ :first_name => 'John',
89
+ :last_name => 'Smith',
90
+ :user_name => 'john5@company.com',
91
+ :balance => 0
92
+
93
+
94
+ ### Return Points Summary
95
+
96
+ Returns user points. Required parameter - User ID
97
+
98
+ user_id = 130
99
+
100
+ summary = service.return_point_summary user_id
101
+
102
+ puts summary.inspect
103
+
104
+ # {
105
+ # :user_id => '577',
106
+ # :is_active => true,
107
+ # :points_earned => '465',
108
+ # :points_redeemed => '0',
109
+ # :points_credited => '0',
110
+ # :points_balance => '465'
111
+ # }
112
+
113
+
114
+ ### Return Popular Products
115
+
116
+ Returns an array of popular products for user
117
+
118
+ user_id = 130
119
+
120
+ popular_products = service.return_popular_products user_id
121
+
122
+ puts popular_products.inspect
123
+
124
+ # [
125
+ # {
126
+ # :product_id => 'MC770LLA',
127
+ # :name => 'iPad 2 with Wifi - 32GB',
128
+ # :description => 'The NEW Apple iPad 2 - Thinner, lighter, and full of great ideas. Once you pick up iPad 2, it’ll be hard to put down. That’s the idea behind the all-new design. It’s 33 percent thinner and up to 15 percent lighter, so it feels even more comfortable in your hands. And, it makes surfing the web, checking email, watching movies, and reading books so natural, you might forget there’s incredible technology under your fingers.<br><br><b>Dual-core A5 chip</b>.<br> Two powerful cores in one A5 chip mean iPad can do twice the work at once. You’ll notice the difference when you’re surfing the web, watching movies, making FaceTime video calls, gaming, and going from app to app to app. Multitasking is smoother, apps load faster, and everything just works better.<br><br><b>Superfast graphics</b>. <br>With up to nine times the graphics performance, gameplay on iPad is even smoother and more realistic. And faster graphics help apps perform better — especially those with video. You’ll see it when you’re scrolling through your photo library, editing video with iMovie, and viewing animations in Keynote.<br><br><b>Battery life keeps on going. So you can, too.</b><br> Even with the new thinner and lighter design, iPad has the same amazing 10-hour battery life. That’s enough juice for one flight across the ocean, or one movie-watching all-nighter, or a week’s commute across town. The power-efficient A5 chip and iOS keep battery life from fading away, so you can get carried away.<br><br><b>Two cameras.</b><br> You’ll see two cameras on iPad — one on the front and one on the back. They may be tiny, but they’re a big deal. They’re designed for FaceTime video calling, and they work together so you can talk to your favorite people and see them smile and laugh back at you. The front camera puts you and your friend face-to-face. Switch to the back camera during your video call to share where you are, who you’re with, or what’s going on around you. When you’re not using FaceTime, let the back camera roll if you see something movie-worthy. It’s HD, so whatever you shoot is a mini-masterpiece. And you can take wacky snapshots in Photo Booth. It’s the most fun a face can have.<br><br><b>Due to the demand for this item, please allow up to 8-10 weeks for delivery</b>.',
129
+ # :points => '10927',
130
+ # :category => 'Office & Computer',
131
+ # :manufacturer => 'Apple',
132
+ # :small_image_url => 'https://www.rewardstation.com/catalogimages/MC769LLA.gif',
133
+ # :large_image_url => 'https://www.rewardstation.com/catalogimages/MC769LLA.jpg'
134
+ # }, {
135
+ # ...
136
+ # }
137
+ # ]
138
+
139
+
140
+ ## Client Stub
141
+
142
+ Client supports stub mode.
143
+ Client stub supports all request methods supported by `RewardStation::Client` but it doesn't make requests to Reward Station API. It just returns predefined SOAP responses.
144
+ You can override those responses in `config/reward_station/responses` folder. For example if `award_points` method response should be overridden then add `award_points.xml` file to `config/reward_station/responses` folder.
145
+
146
+ ### Stub Initialization
147
+
148
+ stub = RewardStation::Client.stub
149
+
150
+ ### config/reward_station/responses/award_points.xml
151
+
152
+ <soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema">
153
+ <soap:Body>
154
+ <AwardPointsResponse xmlns="http://rswebservices.rewardstation.com/">
155
+ <AwardPointsResult>
156
+ <UserID>577</UserID>
157
+ <Points>10</Points>
158
+ <ConfirmationNumber>9376</ConfirmationNumber>
159
+ <ErrorMessage/>
160
+ </AwardPointsResult>
161
+ </AwardPointsResponse>
162
+ </soap:Body>
163
+ </soap:Envelope>
164
+
165
+
166
+ ## Single-Sign-On
167
+
168
+ Basic SSO logic implemented in SAML::AuthResponse class. Example usage of AuthResponse:
169
+
170
+ ### SessionController
171
+
172
+ require 'saml/auth_response'
173
+
174
+ class SessionController < ApplicationController
175
+
176
+ def create
177
+ @user_session = UserSession.new(params[:user_session])
178
+ if @user_session.save
179
+ if session[:saml_request].present?
180
+ sso_params(session[:saml_request], session[:relay_state], current_user)
181
+ session[:saml_request] = session[:relay_state] = nil
182
+ render :template => "session/signon"
183
+ return
184
+ ...
185
+ end
186
+ ...
187
+ end
188
+ end
189
+
190
+ def signon
191
+ if current_user.present?
192
+ sso_params(params[:SAMLRequest], params[:RelayState], current_user)
193
+
194
+ render :template => "session/signon"
195
+ else
196
+ session[:saml_request] = params[:SAMLRequest]
197
+ session[:relay_state] = params[:RelayState]
198
+ redirect_to signin_url
199
+ end
200
+ end
201
+
202
+ def destroy
203
+ current_user_session.destroy
204
+ redirect_to signin_url
205
+ end
206
+
207
+ protected
208
+
209
+ def sso_params saml_request, relay_state, user
210
+ @saml_response = SAML::AuthResponse.new(saml_request).response_url(user.xceleration_id)
211
+ @relay_state = relay_state
212
+ end
213
+ end
214
+
215
+ ### signon.html.erb
216
+
217
+ <html>
218
+ <body>
219
+ <form id="sso_form" action="http://www6.rewardstation.net/sso/100080/AssertionService.aspx?binding=urn:oasis:names:tc:SAML:2.0:bindings:HTTP-POST" method="post">
220
+ <input type="hidden" name="SAMLResponse" value="<%= @saml_response %>"/>
221
+ <input type="hidden" name="RelayState" value="<%= @relay_state %>"/>
222
+ </form>
223
+ <script type="text/javascript">
224
+ document.getElementById('sso_form').submit();
225
+ </script>
226
+ </body>
227
+ </html>
228
+
data/Rakefile ADDED
@@ -0,0 +1,5 @@
1
+ require 'bundler'
2
+ Bundler::GemHelper.install_tasks
3
+
4
+ require 'rspec/core/rake_task'
5
+ RSpec::Core::RakeTask.new('spec')
@@ -0,0 +1,9 @@
1
+ require 'savon'
2
+ require 'ruby-saml'
3
+
4
+ require 'reward_station/stub_response'
5
+ require 'reward_station/errors'
6
+ require 'reward_station/client'
7
+ require 'reward_station/stub_client'
8
+
9
+
@@ -0,0 +1,210 @@
1
+ module RewardStation
2
+ class Client
3
+
4
+ attr_accessor :token
5
+
6
+ def initialize options = {}
7
+ [:client_id, :client_password].each do |arg|
8
+ raise ArgumentError, "Missing required option '#{arg}'" unless options.has_key? arg
9
+ end
10
+
11
+ @client_id = options[:client_id]
12
+ @client_password = options[:client_password]
13
+ @token = options[:token]
14
+ @organization_id = options[:organization_id]
15
+
16
+ @program_id = options[:program_id]
17
+ @point_reason_code_id = options[:point_reason_code_id]
18
+
19
+ if options[:new_token_callback]
20
+ raise ArgumentError, "new_token_callback option should be proc or lambda" unless options[:new_token_callback].is_a?(Proc)
21
+ @new_token_callback = options[:new_token_callback]
22
+ end
23
+
24
+ end
25
+
26
+ def new_token_callback &block
27
+ @new_token_callback = block
28
+ end
29
+
30
+ class << self
31
+
32
+ def stub options = {}
33
+ RewardStation::StubClient.new options
34
+ end
35
+
36
+ def get_client
37
+ @@client ||= Savon::Client.new do |wsdl|
38
+ wsdl.document = File.join(File.dirname(__FILE__), '..', 'wsdl', 'reward_services.xml')
39
+ end
40
+ end
41
+
42
+ def logger
43
+ @@logger ||= defined?(Rails) ? Rails.logger : Logger.new(STDOUT)
44
+ end
45
+ end
46
+
47
+ def logger
48
+ Client.logger
49
+ end
50
+
51
+ def return_token
52
+ result = request :return_token, :body => {
53
+ 'AccountNumber' => @client_id,
54
+ 'AccountCode' => @client_password
55
+ }
56
+
57
+ logger.info "xceleration token #{result[:token]}"
58
+
59
+ result[:token]
60
+ end
61
+
62
+ def return_user user_id
63
+ request_with_token(:return_user, :body => { 'UserID' => user_id} )[:user_profile]
64
+ end
65
+
66
+ def award_points user_id, points, description, program_id = nil, point_reason_code_id = nil
67
+ request_with_token(:award_points, :body => {
68
+ 'UserID' => user_id,
69
+ 'Points' => points,
70
+ 'ProgramID' => program_id || @program_id,
71
+ 'PointReasonCodeID' => point_reason_code_id || @point_reason_code_id,
72
+ 'Description' => description
73
+ })[:confirmation_number]
74
+ end
75
+
76
+
77
+ def return_point_summary user_id
78
+ request_with_token(:return_point_summary, :body => {
79
+ 'clientId' => @client_id,
80
+ 'userId' => user_id
81
+ })[:point_summary_collection][:point_summary]
82
+ end
83
+
84
+ def return_user user_id, attrs = {}
85
+ organization_id = attrs[:organization_id] || @organization_id
86
+ user_name = attrs[:user_name] || email
87
+
88
+ request_with_token(:return_user_by_user_name , :body => {
89
+ 'ReturnUserByUserName' => {
90
+ 'UserName' => user_name,
91
+ }[:return_user_by_user_name]
92
+ })
93
+ end
94
+
95
+ def update_user user_id, attrs = {}
96
+
97
+ organization_id = attrs[:organization_id] || @organization_id
98
+ email = attrs[:email] || ""
99
+ first_name = attrs[:first_name] || ""
100
+ last_name = attrs[:last_name] || ""
101
+ user_name = attrs[:user_name] || email
102
+ balance = attrs[:balance] || 0
103
+
104
+ request_with_token(:update_user , :body => {
105
+ 'updateUser' => {
106
+ 'UserID' => user_id,
107
+ 'ClientID' => @client_id,
108
+ 'UserName' => user_name,
109
+ 'FirstName' => first_name,
110
+ 'LastName' => last_name,
111
+ 'CountryCode' => 'USA',
112
+ 'Email' => email,
113
+ 'IsActive' => true,
114
+ 'PointBalance' => balance,
115
+ 'OrganizationID' => organization_id,
116
+ 'AddressOne' => "",
117
+ 'AddressTwo' => "",
118
+ 'City' => "",
119
+ 'StateCode' => "",
120
+ 'Province' => "",
121
+ 'PostalCode' => "",
122
+ 'Phone' => "",
123
+ 'OrganizationName' => "",
124
+ 'RepTypeID' => "",
125
+ 'ClientRegionID' => "",
126
+ 'ManagerID' => "",
127
+ 'ManagerName' => ""
128
+ }
129
+ })[:update_user]
130
+ end
131
+
132
+
133
+ def create_user attrs = {}
134
+ update_user -1, attrs
135
+ end
136
+
137
+ def return_popular_products user_id
138
+ request_with_token(:return_popular_products , :body => { 'userId' => user_id} )[:products][:product]
139
+ end
140
+
141
+ protected
142
+
143
+ def get_response method_name, params
144
+ Client.get_client.request(:wsdl, method_name , params).to_hash
145
+ end
146
+
147
+ def request method_name, params
148
+ response = get_response method_name, params
149
+
150
+ logger.debug response.inspect
151
+
152
+ result = response[:"#{method_name}_response"][:"#{method_name}_result"]
153
+
154
+ unless (error_message = result.delete(:error_message).to_s).blank?
155
+ raise(InvalidToken, error_message) if error_message.start_with?("Invalid Token")
156
+ raise InvalidAccount if error_message.start_with?("Invalid Account Number")
157
+ raise InvalidUser if error_message.start_with?("Invalid User")
158
+ raise(UserAlreadyExists, error_message) if error_message.start_with?("User Name:") && error_message.end_with?("Please enter a different user name.")
159
+ raise MissingInformation if error_message.start_with?("The following information is missing:")
160
+
161
+ raise(UnknownError, error_message)
162
+ end
163
+
164
+ result
165
+ rescue Savon::SOAP::Fault, Savon::HTTP::Error => ex
166
+ logger.error ex.to_s
167
+ logger.error ex.backtrace.inspect
168
+ raise ConnectionError.new
169
+ end
170
+
171
+ def update_token
172
+ @token = return_token
173
+ @new_token_callback.call(@token) if @new_token_callback
174
+ end
175
+
176
+ def inject_token params = {}
177
+ (params[:body] ||= {})['Token'] = @token
178
+ end
179
+
180
+ def request_with_token method_name, params
181
+ update_token unless @token
182
+
183
+ retry_with_token :tries => 2 do
184
+ inject_token params
185
+ request method_name, params.dup
186
+ end
187
+ end
188
+
189
+ private
190
+
191
+ def retry_with_token( options = {})
192
+ opts = {:tries => 1}.merge(options)
193
+
194
+ if (tries = opts[:tries]) <= 0
195
+ return
196
+ end
197
+
198
+ begin
199
+ return yield
200
+ rescue InvalidToken => ex
201
+ if (tries -= 1) > 0
202
+ update_token
203
+ retry
204
+ else
205
+ raise ex
206
+ end
207
+ end
208
+ end
209
+ end
210
+ end