sailplay 0.1.1

Sign up to get free protection for your applications and to get access to all the features.
data/.gitignore ADDED
@@ -0,0 +1,19 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ .idea
7
+ .ruby-version
8
+ Gemfile.lock
9
+ InstalledFiles
10
+ _yardoc
11
+ coverage
12
+ doc/
13
+ lib/bundler/man
14
+ pkg
15
+ rdoc
16
+ spec/reports
17
+ test/tmp
18
+ test/version_tmp
19
+ tmp
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --color
data/.travis.yml ADDED
@@ -0,0 +1,6 @@
1
+ language: ruby
2
+ rvm:
3
+ - 1.8.7
4
+ - 1.9.3
5
+ - 2.0.0
6
+
data/CHANGELOG.md ADDED
@@ -0,0 +1,3 @@
1
+ ## v0.0.1
2
+
3
+ * initial release
data/Gemfile ADDED
@@ -0,0 +1,11 @@
1
+ source 'https://rubygems.org'
2
+
3
+
4
+ gem 'json'
5
+ gem 'guard-bundler'
6
+ gem 'guard-rspec'
7
+ gem 'rb-fsevent'
8
+ gem 'terminal-notifier-guard'
9
+
10
+ # Specify your gem's dependencies in sailplay.gemspec
11
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,10 @@
1
+ guard 'rspec', :cli => '--color' do
2
+ watch(%r{^spec/.+_spec\.rb$})
3
+ watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" }
4
+ watch('spec/spec_helper.rb') { 'spec' }
5
+ end
6
+
7
+ guard 'bundler' do
8
+ watch('Gemfile')
9
+ watch(/^.+\.gemspec/)
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Sergey Nebolsin
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Sailplay for Rails
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/sailplay.png)](http://badge.fury.io/rb/sailplay)
4
+ [![Build Status](https://travis-ci.org/nebolsin/sailplay.png?branch=master)](https://travis-ci.org/nebolsin/sailplay)
5
+ [![Coverage Status](https://coveralls.io/repos/nebolsin/sailplay/badge.png)](https://coveralls.io/r/nebolsin/sailplay)
6
+ [![Code Climate](https://codeclimate.com/github/nebolsin/sailplay.png)](https://codeclimate.com/github/nebolsin/sailplay)
7
+
8
+ Integrate your Rails-based store with [Sailplay](http://sailplay.ru) to give reward points to consumers for making purchases.
9
+
10
+ This gem provides Ruby bindings for [Sailplay API](https://docs.google.com/document/d/1WZggz8i5EYbkAhbF790UB2M7qTzQq33AHBbq_7BNQcc/edit)
11
+ and helpers for Rails integration.
12
+
13
+ Sponsored by [Fotoshkola.net](http://fotoshkola.net).
14
+
15
+ ## Installation
16
+
17
+ Add this line to your application's Gemfile:
18
+
19
+ gem 'sailplay'
20
+
21
+ And then execute:
22
+
23
+ $ bundle
24
+
25
+ Or install it yourself as:
26
+
27
+ $ gem install sailplay
28
+
29
+ ## Usage
30
+
31
+ TODO: describe `sailplay_client(options)` method
32
+
33
+ ## Contributing
34
+
35
+ 1. Fork it
36
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
37
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
38
+ 4. Push to the branch (`git push origin my-new-feature`)
39
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,7 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ RSpec::Core::RakeTask.new
5
+
6
+ task :default => :spec
7
+ task :test => :spec
data/lib/sailplay.rb ADDED
@@ -0,0 +1,109 @@
1
+ require 'sailplay/version'
2
+
3
+ require 'sailplay/client'
4
+ require 'sailplay/error'
5
+ require 'sailplay/configuration'
6
+ require 'sailplay/response'
7
+
8
+ require 'sailplay/api/base'
9
+ require 'sailplay/api/user'
10
+ require 'sailplay/api/gift'
11
+ require 'sailplay/api/purchase'
12
+
13
+ module Sailplay
14
+ class << self
15
+ # The client object is responsible for communication with Sailplay API server.
16
+ attr_writer :client
17
+
18
+ # A Sailplay configuration object.
19
+ attr_writer :configuration
20
+
21
+ # Call this method to modify defaults in your initializers.
22
+ #
23
+ # @example
24
+ # Sailplay.configure do |config|
25
+ # config.store_id = '123'
26
+ # config.store_key = '4567890'
27
+ # config.store_pin = '3131'
28
+ # config.secure = true
29
+ # end
30
+ def configure
31
+ yield(configuration)
32
+ @client = Client.new(configuration)
33
+ end
34
+
35
+ # The configuration object.
36
+ # @see Sailplay.configure
37
+ def configuration
38
+ @configuration ||= Configuration.new
39
+ end
40
+
41
+ def client
42
+ @client ||= Client.new(configuration)
43
+ end
44
+
45
+ def reset!
46
+ @client = nil
47
+ @configuration = nil
48
+ end
49
+
50
+ def logger
51
+ self.configuration.logger
52
+ end
53
+
54
+ # @param [String] phone
55
+ # @param [Hash] options
56
+ #
57
+ # @option options [true|false] :auth — authenticate user
58
+ #
59
+ # @return [Sailplay::User]
60
+ def create_user(phone, options = {})
61
+ self.client.create_user(phone, options)
62
+ end
63
+
64
+ # @param [String] phone
65
+ # @param [Hash] options
66
+ #
67
+ # @option options [true|false] :auth — authenticate user
68
+ #
69
+ # @return [Sailplay::User]
70
+ def find_user(phone, options = {})
71
+ self.client.find_user(phone, options)
72
+ end
73
+
74
+ # @param [String|Integer] phone_or_user_id
75
+ # @param [BigDecimal] price
76
+ # @param [Hash] options
77
+ #
78
+ # @option options [Integer] :order_id — ID заказа
79
+ # @option options [Decimal] :points_rate — коэффициент конвертации рублей в баллы. Может принимать значение из полуинтервала (0,1].
80
+ # При отсутствии данного параметра, используется значение, указанное в настройках.
81
+ # Формат points_rate=0.45
82
+ # @option options [true|false] :force_complete — если true, транзакция считается подтвержденной несмотря на флаг в настройках.
83
+ # Данный аттрибут может быть использован, например, в случае когда часть оплат
84
+ # необходимо подтверждать, а про остальные известно что они уже подтверждены.
85
+ #
86
+ # @return [Sailplay::Purchase]
87
+ def create_purchase(phone_or_user_id, price, options = {})
88
+ self.client.create_purchase(phone_or_user_id, price, options)
89
+ end
90
+
91
+ # @param [Integer] order_id
92
+ # @param [Hash] options
93
+ #
94
+ # @option options [BigDecimal] :price
95
+ def confirm_purchase(order_id, options = {})
96
+ self.client.confirm_purchase(order_id, options)
97
+ end
98
+
99
+
100
+ # @param [String] gift_public_key
101
+ def confirm_gift(gift_public_key)
102
+ self.client.confirm_gift(gift_public_key)
103
+ end
104
+
105
+ def request(method, url, params)
106
+ self.client.request(method, url, params)
107
+ end
108
+ end
109
+ end
@@ -0,0 +1,4 @@
1
+ module Sailplay
2
+ class Base
3
+ end
4
+ end
@@ -0,0 +1,34 @@
1
+ require 'sailplay/api/base'
2
+
3
+ module Sailplay
4
+ class Gift < Base
5
+ attr_accessor :id, :sku, :name, :pic, :view_url, :pick_url, :points
6
+
7
+ #{
8
+ # sku: 5,
9
+ # name: "Подарок 1",
10
+ # pic: "gifts/gift/b6e011188b74d3e0d838fbbace84de92.jpeg",
11
+ # pick_url: "http://sailplay.ru/api/v1/ecommerce/gifts/pick/?gift_id=15&user_phone=79266054612...,
12
+ # points: 55,
13
+ # id: 25
14
+ #}
15
+ def self.parse(json)
16
+ Sailplay::Gift.new(
17
+ :id => json[:id],
18
+ :sku => json[:sku],
19
+ :name => json[:name],
20
+ :pic => json[:pic],
21
+ :view_url => json[:view_url],
22
+ :pick_url => json[:pick_url],
23
+ :points => json[:points]
24
+ )
25
+ end
26
+
27
+ def initialize(attrs = {})
28
+ [:id, :sku, :name, :pic, :view_url, :pick_url, :points].each do |attr|
29
+ instance_variable_set("@#{attr}", attrs[attr])
30
+ end
31
+ end
32
+
33
+ end
34
+ end
@@ -0,0 +1,40 @@
1
+ require 'sailplay/api/base'
2
+
3
+ module Sailplay
4
+ class Purchase < Base
5
+ attr_accessor :id, :order_id, :price, :points_delta, :complete_date, :public_key
6
+ attr_accessor :user
7
+
8
+ #{
9
+ # "complete_date":"2013-01-25T00:31:42.642",
10
+ # "price":"10",
11
+ # "id":8,
12
+ # "points_delta":4,
13
+ # "public_key":";ao08rj3tj09fu9jkwer20393urjflshg54h",
14
+ # "order_num":87573,
15
+ #}
16
+ def self.parse(json)
17
+ purchase_json = json[:purchase]
18
+ purchase = Sailplay::Purchase.new(
19
+ :id => purchase_json[:id],
20
+ :order_id => purchase_json[:order_num],
21
+ :price => purchase_json[:price],
22
+ :points_delta => purchase_json[:points_delta],
23
+ :complete_date => purchase_json[:complete_date],
24
+ :public_key => purchase_json[:public_key]
25
+ )
26
+
27
+ if user_json = json[:user]
28
+ purchase.user = User.parse(user_json)
29
+ end
30
+
31
+ purchase
32
+ end
33
+
34
+ def initialize(options = {})
35
+ [:id, :order_id, :price, :points_delta, :complete_date, :public_key].each do |attr|
36
+ instance_variable_set("@#{attr}", options[attr])
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,52 @@
1
+ require 'sailplay/api/base'
2
+ require 'sailplay/api/gift'
3
+
4
+ module Sailplay
5
+ class User < Base
6
+ attr_accessor :id, :phone, :full_name, :points, :media_url, :available_gifts, :unavailable_gifts, :auth_hash
7
+
8
+ # {
9
+ # user_phone: "79266054612",
10
+ # auth_hash: "1a159580bc111be0c288eb90afbce6f42ee48bba",
11
+ # user_points: 189,
12
+ # media_url: "http://d3257v5wstjx8h.cloudfront.net/media/"
13
+ # available_gifts: [
14
+ # {
15
+ # sku: 5,
16
+ # name: "Подарок 1",
17
+ # pic: "gifts/gift/b6e011188b74d3e0d838fbbace84de92.jpeg",
18
+ # pick_url: "http://sailplay.ru/api/v1/ecommerce/gifts/pick/?gift_id=15&user_phone=79266054612&token=239b3282621115d2e71bc844d546b7dea4385326&store_department_id=19",
19
+ # points: 55,
20
+ # id: 25
21
+ # }
22
+ # ],
23
+ # over_user_points_gifts: [
24
+ # {
25
+ # sku: 1,
26
+ # name: "Подарок 2",
27
+ # view_url: "http://sailplay.ru/gifts/view/97/",
28
+ # pic: "gifts/gift/83dd4abd6f13495f222113416103b716.jpg",
29
+ # points: 200,
30
+ # id: 27
31
+ # }
32
+ # ]
33
+ # }
34
+ def self.parse(json)
35
+ Sailplay::User.new(
36
+ :phone => json[:user_phone] || json[:phone],
37
+ :points => json[:user_points] || json[:points],
38
+ :full_name => json[:full_name],
39
+ :media_url => json[:media_url],
40
+ :auth_hash => json[:auth_hash],
41
+ :available_gifts => (json[:available_gifts] || []).map {|gift_json| Gift.parse(gift_json)},
42
+ :unavailable_gifts => (json[:over_user_points_gifts] || []).map {|gift_json| Gift.parse(gift_json)}
43
+ )
44
+ end
45
+
46
+ def initialize(attrs = {})
47
+ [:id, :phone, :full_name, :points, :media_url, :auth_hash, :available_gifts, :unavailable_gifts].each do |attr|
48
+ instance_variable_set("@#{attr}", attrs[attr])
49
+ end
50
+ end
51
+ end
52
+ end
@@ -0,0 +1,255 @@
1
+ require 'rest-client'
2
+ require 'multi_json'
3
+ require 'sailplay/configuration'
4
+ require 'sailplay/error'
5
+ require 'sailplay/response'
6
+
7
+ module Sailplay
8
+ class Client
9
+ attr_reader :protocol,
10
+ :host,
11
+ :port,
12
+ :endpoint,
13
+ :secure,
14
+ :connection_options,
15
+ :store_id,
16
+ :store_key,
17
+ :store_pin
18
+
19
+ alias_method :secure?, :secure
20
+
21
+ def initialize(options = {})
22
+ [ :protocol,
23
+ :host,
24
+ :port,
25
+ :endpoint,
26
+ :secure,
27
+ :connection_options,
28
+ :store_id,
29
+ :store_key,
30
+ :store_pin
31
+ ].each do |option|
32
+ instance_variable_set("@#{option}", options[option])
33
+ end
34
+ end
35
+
36
+ # @param [String] phone
37
+ # @param [Hash] options
38
+ #
39
+ # @option options [true|false] :auth — authenticate user
40
+ #
41
+ # @return [Sailplay::User]
42
+ def create_user(phone, options = {})
43
+ params = {:user_phone => phone}
44
+ params[:extra_fields] = 'auth_hash' if options[:auth]
45
+
46
+ response = request(:get, '/users/reg', :user_phone => phone)
47
+ if response.success?
48
+ User.parse(response.data)
49
+ else
50
+ raise APIError, "Cannot create user '#{phone}': #{response.error_message}"
51
+ end
52
+ end
53
+
54
+ # @param [String] phone
55
+ # @param [Hash] options
56
+ #
57
+ # @option options [true|false] :auth — authenticate user
58
+ #
59
+ # @return [Sailplay::User]
60
+ def find_user(phone, options = {})
61
+ params = {:user_phone => phone}
62
+ params[:extra_fields] = 'auth_hash' if options[:auth]
63
+
64
+ response = Sailplay.request(:get, '/users/points-info', params)
65
+ if response.success?
66
+ User.parse(response.data)
67
+ else
68
+ raise APIError, "Cannot find a user '#{phone}': #{response.error_message}"
69
+ end
70
+ end
71
+
72
+ # options[:points_rate] — коэффициент конвертации рублей в баллы. Может принимать значение из полуинтервала (0,1].
73
+ # При отсутствии данного параметра, используется значение, указанное в настройках.
74
+ # Формат points_rate=0.45
75
+ # options[:force_complete] — если true, транзакция считается подтвержденной несмотря на флаг в настройках.
76
+ # Данный аттрибут может быть использован, например, в случае когда часть оплат
77
+ # необходимо подтверждать, а про остальные известно что они уже подтверждены.
78
+ # options[:order_id] — ID заказа
79
+ #
80
+ # @return [Sailplay::Purchase]
81
+ def create_purchase(user_id, price, options = {})
82
+ params = {:price => price, :origin_user_id => user_id}
83
+
84
+ params[:user_phone] = options[:phone] if options[:phone]
85
+ params[:points_rate] = options[:points_rate] if options[:points_rate]
86
+ params[:force_complete] = options[:force_complete] if options[:force_complete]
87
+ params[:order_num] = options[:order_id] if options[:order_id]
88
+
89
+ params[:fields] = [:public_key, options[:order_id] && :order_num].compact.join(',')
90
+
91
+ response = Sailplay.request(:get, '/purchases/new', params)
92
+
93
+ if response.success?
94
+ Purchase.parse(response.data)
95
+ else
96
+ raise APIError, "Cannot create a purchase: #{response.error_message}"
97
+ end
98
+ end
99
+
100
+ # @param [Integer] order_id
101
+ # @param [Hash] options
102
+ #
103
+ # @option options [BigDecimal] :price
104
+ def confirm_purchase(order_id, options = {})
105
+ params = {:order_num => order_id}
106
+ params[:new_price] = options[:price] if options[:price]
107
+
108
+ response = request(:get, '/purchases/confirm', params)
109
+
110
+ if response.success?
111
+ Purchase.parse(response.data)
112
+ else
113
+ raise APIError, "Cannot confirm a purchase: #{response.error_message}"
114
+ end
115
+ end
116
+
117
+ # @param [String] gift_public_key
118
+ def confirm_gift(gift_public_key)
119
+ params = {:gift_public_key => gift_public_key}
120
+ response = request(:get, '/ecommerce/gifts/commit-transaction', params)
121
+
122
+ if response.success?
123
+ else
124
+ raise APIError, "Cannot confirm a gift: #{response.error_message}"
125
+ end
126
+ end
127
+
128
+ def request(method, url, params = {})
129
+ execute_request(method, url, auth_params.merge(params))
130
+ end
131
+
132
+ def logger
133
+ Sailplay.logger
134
+ end
135
+
136
+ private
137
+
138
+ def auth_params
139
+ {:store_department_id => store_id, :token => api_token}
140
+ end
141
+
142
+ def api_url(url = '')
143
+ URI.parse("#{protocol}://#{host}:#{port}").merge("#{endpoint}/#{url}").to_s
144
+ end
145
+
146
+ def api_token
147
+ @api_token ||= login
148
+ end
149
+
150
+ def login
151
+ raise ConfigurationError, 'Missing client configuration: ' +
152
+ 'please check that store_id, store_key and pin_code are ' +
153
+ 'configured' unless credentials?
154
+
155
+ response = execute_request(:get, 'login', credentials)
156
+
157
+ if response.success?
158
+ response.data[:token]
159
+ else
160
+ raise AuthenticationError.new("Cannot authenticate on Sailplay. Check your config. (Response: #{response})")
161
+ end
162
+ end
163
+
164
+ def credentials
165
+ {
166
+ :store_department_id => @store_id,
167
+ :store_department_key => @store_key,
168
+ :pin_code => @store_pin
169
+ }
170
+ end
171
+
172
+ def credentials?
173
+ credentials.values.all?
174
+ end
175
+
176
+ def execute_request(method, url, params = {})
177
+ logger.debug(self.class) {"Starting #{method} request to #{url} with #{params.inspect}"}
178
+ url = api_url(url)
179
+ headers = @connection_options[:headers].merge(:params => params)
180
+
181
+ request_opts = {:method => method, :url => url, :headers => headers}
182
+
183
+ begin
184
+ response = RestClient::Request.execute(request_opts)
185
+ rescue RestClient::ExceptionWithResponse => e
186
+ if e.http_code && e.http_body
187
+ handle_api_error(e.http_code, e.http_body)
188
+ else
189
+ handle_restclient_error(e)
190
+ end
191
+ rescue RestClient::Exception, SocketError, Errno::ECONNREFUSED => e
192
+ handle_restclient_error(e)
193
+ end
194
+
195
+
196
+ http_code, http_body = response.code, response.body
197
+
198
+ logger.debug(self.class) {"\t HTTP Code -> #{http_code}"}
199
+ #logger.debug(self.class) {"\t HTTP Body -> #{http_body}"}
200
+
201
+ begin
202
+ json_body = MultiJson.load(http_body, :symbolize_keys => true)
203
+ rescue MultiJson::DecodeError
204
+ raise APIError.new("Invalid response object from API: #{http_body.inspect} (HTTP response code was #{http_code})", http_code, http_body)
205
+ end
206
+
207
+ #logger.debug(self.class) {"\t JSON Body -> #{json_body}"}
208
+
209
+ response = Response.new(json_body)
210
+
211
+ logger.debug(self.class) {"\t Valid -> #{response.success?}"}
212
+ logger.debug(self.class) {"\t Payload -> #{response.data}"}
213
+
214
+ response
215
+ end
216
+
217
+ def handle_restclient_error(e)
218
+ case e
219
+ when RestClient::ServerBrokeConnection, RestClient::RequestTimeout
220
+ message = "Could not connect to Sailplay (#{endpoint}). " +
221
+ "Please check your internet connection and try again. " +
222
+ "If this problem persists, let us know at support@sailplay.ru."
223
+ when SocketError
224
+ message = "Unexpected error communicating when trying to connect to Sailplay. " +
225
+ "HINT: You may be seeing this message because your DNS is not working. " +
226
+ "To check, try running 'host sailplay.ru' from the command line."
227
+ else
228
+ message = "Unexpected error communicating with Sailplay. " +
229
+ "If this problem persists, let us know at support@sailplay.ru."
230
+ end
231
+ message += "\n\n(Network error: #{e.message})"
232
+ raise APIConnectionError, message
233
+ end
234
+
235
+ def handle_api_error(http_code, http_body)
236
+ begin
237
+ error_obj = MultiJson.load(http_body, :symbolize_keys => true)
238
+ message = error_obj[:message]
239
+ rescue MultiJson::DecodeError
240
+ message = "Invalid response object from API: #{http_body.inspect} (HTTP response code was #{http_code})"
241
+ raise APIError.new(message, http_code, http_body)
242
+ end
243
+
244
+ case http_code
245
+ when 400, 404 then
246
+ raise InvalidRequestError.new(message, http_code, http_body, error_obj)
247
+ when 401
248
+ raise AuthenticationError.new(message, http_code, http_body, error_obj)
249
+ else
250
+ raise APIError.new(message, http_code, http_body, error_obj)
251
+ end
252
+ end
253
+
254
+ end
255
+ end