webtopay 1.2.1 → 1.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (59) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +1 -0
  3. data/README.md +145 -0
  4. data/lib/webtopay.rb +5 -3
  5. data/lib/webtopay/exception.rb +11 -1
  6. data/lib/webtopay/payment.rb +45 -0
  7. data/lib/webtopay/response.rb +198 -0
  8. data/lib/webtopay/version.rb +1 -1
  9. data/lib/webtopay_controller.rb +14 -19
  10. data/lib/webtopay_helper.rb +19 -15
  11. data/spec/dummy/README.rdoc +28 -0
  12. data/spec/dummy/Rakefile +6 -0
  13. data/spec/dummy/app/assets/images/.keep +0 -0
  14. data/spec/dummy/app/assets/javascripts/application.js +13 -0
  15. data/spec/dummy/app/assets/stylesheets/application.css +13 -0
  16. data/spec/dummy/app/controllers/application_controller.rb +5 -0
  17. data/spec/dummy/app/controllers/concerns/.keep +0 -0
  18. data/spec/dummy/app/controllers/payments_controller.rb +11 -0
  19. data/spec/dummy/app/helpers/application_helper.rb +2 -0
  20. data/spec/dummy/app/mailers/.keep +0 -0
  21. data/spec/dummy/app/models/.keep +0 -0
  22. data/spec/dummy/app/models/concerns/.keep +0 -0
  23. data/spec/dummy/app/views/layouts/application.html.erb +14 -0
  24. data/spec/dummy/bin/bundle +3 -0
  25. data/spec/dummy/bin/rails +4 -0
  26. data/spec/dummy/bin/rake +4 -0
  27. data/spec/dummy/config.ru +4 -0
  28. data/spec/dummy/config/application.rb +23 -0
  29. data/spec/dummy/config/boot.rb +5 -0
  30. data/spec/dummy/config/database.yml +25 -0
  31. data/spec/dummy/config/environment.rb +5 -0
  32. data/spec/dummy/config/environments/development.rb +29 -0
  33. data/spec/dummy/config/environments/production.rb +80 -0
  34. data/spec/dummy/config/environments/test.rb +36 -0
  35. data/spec/dummy/config/initializers/backtrace_silencers.rb +7 -0
  36. data/spec/dummy/config/initializers/filter_parameter_logging.rb +4 -0
  37. data/spec/dummy/config/initializers/inflections.rb +16 -0
  38. data/spec/dummy/config/initializers/mime_types.rb +5 -0
  39. data/spec/dummy/config/initializers/secret_token.rb +12 -0
  40. data/spec/dummy/config/initializers/session_store.rb +3 -0
  41. data/spec/dummy/config/initializers/webtopay.rb +4 -0
  42. data/spec/dummy/config/initializers/wrap_parameters.rb +14 -0
  43. data/spec/dummy/config/locales/en.yml +23 -0
  44. data/spec/dummy/config/routes.rb +61 -0
  45. data/spec/dummy/db/test.sqlite3 +0 -0
  46. data/spec/dummy/lib/assets/.keep +0 -0
  47. data/spec/dummy/log/.keep +0 -0
  48. data/spec/dummy/public/404.html +58 -0
  49. data/spec/dummy/public/422.html +58 -0
  50. data/spec/dummy/public/500.html +57 -0
  51. data/spec/dummy/public/favicon.ico +0 -0
  52. data/spec/lib/webtopay/payment_spec.rb +31 -0
  53. data/spec/lib/webtopay/response_spec.rb +57 -0
  54. data/spec/lib/webtopay_controller_spec.rb +19 -0
  55. data/spec/spec_helper.rb +59 -0
  56. data/webtopay.gemspec +10 -3
  57. metadata +195 -47
  58. data/README +0 -71
  59. data/lib/webtopay/api.rb +0 -407
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ff4f52a85253c3642d03c9f73fbdea3b6078a47c
4
+ data.tar.gz: 9bfca8246d27fc7d85e7924520acf2f476594b06
5
+ SHA512:
6
+ metadata.gz: dbfac9082c0800b94d4052ab6e9c21a691c2ef2308d1634253722ec7e28cc3c1df3c8a52a93406b372b9bc6c7b59970606a1cf4bbc7f71f1641966162200a0e8
7
+ data.tar.gz: 12ad2c8eaad9d42bd1e1fe325b7f5f28761e1c812392c232e08bd8a296ff912528c7ee6a95b0b7860cd1d92190be8e82ed790c0dc32a53558ed835b4397cae31
data/.gitignore CHANGED
@@ -2,3 +2,4 @@
2
2
  .bundle
3
3
  Gemfile.lock
4
4
  pkg/*
5
+ spec/dummy/log/test.log
@@ -0,0 +1,145 @@
1
+ # WebToPay
2
+
3
+ It is a gem which could be very useful on working with https://www.webtopay.com/ (https://www.mokejimai.lt/) billing system.
4
+ The main function of this gem is protection against forgeries that could be done by sending required parameters to the application and getting services without paying for free.
5
+ This gem could be integrated with both billing types - MACRO (VISA, MasterCard, banks etc.) and MICRO (SMS, phone calls etc.).
6
+
7
+ *It works with OpenSSL so be sure that you have installed all necessary libs or modules on your system.
8
+
9
+ ## Installation
10
+
11
+ Add to your gemfile:
12
+
13
+ ```ruby
14
+ gem "webtopay", github: 'bloomrain'
15
+ ```
16
+
17
+ ## Configuration
18
+
19
+ Create initializer
20
+ config/initializers/webtopay.rb
21
+
22
+ ```ruby
23
+ WebToPay.configure do |config|
24
+ config.project_id = 00000
25
+ config.sign_password = 'your sign password'
26
+ end
27
+ ```
28
+
29
+ ## Basic usage
30
+
31
+ Add this to your controller:
32
+ ```ruby
33
+ webtopay :confirm_order, ...
34
+
35
+ def confirm_order
36
+ # do some ordering stuff here...
37
+ render text: "ok" # response with value "ok" is necessary
38
+ end
39
+ ```
40
+
41
+ Add this in your view:
42
+ Or if need only order confirmation button (no action in controller needed:
43
+ ```erb
44
+ <%= webtopay_confirm_button("Buy", {amount: 2000, p_name: "Jonas", callback_url: confirm_order_url, ... })
45
+ ```
46
+
47
+
48
+ ## Advanced usage
49
+
50
+ If you want more control you can use this:
51
+ ```ruby
52
+ before_filter :webtopay, only:[:controller_method1, :controller_method2] ...
53
+ ```
54
+ instead of:
55
+ ```ruby
56
+ webtopay :controller_method1, :controller_method2 ...
57
+ ```
58
+
59
+ ### Response validation
60
+
61
+ By default only projectid and sign fields are validated. It is highly recommended to check that order specific params
62
+ (like paid money amount) also matches params in response.
63
+ To specify which fields you want to validate add this method in controller:
64
+
65
+ ```ruby
66
+ def webtopay_expected_params(webtopay_params) # webtopay_params is a hash returned from mokejimai.lt
67
+ order = Order.find(webtopay_params[:orderid])
68
+ { # hash keys should be the same as in mokejimai.lt specification
69
+ p_email: order.user_email,
70
+ amount: order.price_in_cents,
71
+ # ... and any other fields that we save in database
72
+ }
73
+ end
74
+ ```
75
+
76
+ ### On invalid response
77
+
78
+ If response is invalid then exception will be raised. To change this behavior add this to controller:
79
+ ```ruby
80
+ def webtopay_failed_validation_response(api_response) # api_response is instance of WebToPay::Response
81
+ render json: api_response.errors # default behavior: raise api_response.errors.first
82
+ end
83
+ ```
84
+
85
+ ### Payment form
86
+
87
+ You can generate payment form like this
88
+
89
+ ```erb
90
+ <%= form_for WebToPay::Payment.new, url: order_products_path do |f| %>
91
+ <%= f.text_field :p_name %>
92
+ <%= f.text_field :p_surname %>
93
+ <%= f.text_field :p_email %>
94
+ ... any other fields you like (for complete field list please read mokejimai.lt api specification) ...
95
+ <%= f.submit "test paying" %>
96
+ <% end %>
97
+ ```
98
+
99
+ Then in your controller
100
+ ```ruby
101
+ class ProductsController < ApplicationController
102
+ def order
103
+ # do some order stuff here ...
104
+ @payment = WebToPay::Payment(params[:web_to_pay_payment])
105
+ redirect_to @payment.url
106
+ end
107
+ end
108
+ ```
109
+
110
+ Or if need only order confirmation button (no action in controller needed:
111
+ ```erb
112
+ <%= webtopay_confirm_button("Buy", {amount: 2000, p_name: "Jonas"})
113
+ ```
114
+
115
+ ## Examples
116
+
117
+ These code slices will protect your controller actions, which work with webtopay.com billing, against forgeries.
118
+
119
+ ### Usage for MICRO and MACRO billing on controller.
120
+
121
+ ```ruby
122
+ webtopay :activate_user, :confirm_cart # You can add here as many actions as you want
123
+
124
+ def activate_user
125
+ # write here code which do some stuff
126
+ render :text => "Your user has been successfully activated. Thank you!" # it sends SMS answer
127
+ end
128
+
129
+ def confirm_cart
130
+ # write here code which do some stuff
131
+ render :text => "ok" # it sends successful answer to webtopay.com crawler
132
+ end
133
+ ```
134
+
135
+
136
+ TODO
137
+ ===========
138
+
139
+ 1. Write more clear documentation with real examples
140
+ 2. Write unit tests for each billing method (requires some testing data from https://www.webtopay.com/)
141
+ 3. Validate Api request by ss2 param
142
+ 4. Check if mikro payments works well
143
+
144
+ ===========
145
+ Copyright (c) 2009 Kristijonas Urbaitis, released under the MIT license
@@ -1,10 +1,13 @@
1
1
  require 'webtopay/exception'
2
2
  require 'webtopay/configuration'
3
- require 'webtopay/api'
3
+ require 'webtopay/payment'
4
+ require 'webtopay/response'
4
5
  require 'webtopay_controller'
5
6
  require 'webtopay_helper'
6
7
 
7
8
  module WebToPay
9
+ API_VERSION = '1.6'
10
+
8
11
  class << self
9
12
  attr_accessor :config
10
13
 
@@ -16,5 +19,4 @@ module WebToPay
16
19
  end
17
20
 
18
21
  ActionController::Base.send(:include, WebToPayController)
19
- ActionView::Base.send(:include, WebToPayHelper)
20
-
22
+ ActionView::Base.send(:include, WebToPayHelper)
@@ -1,5 +1,5 @@
1
1
  module WebToPay
2
- class Exception < ::Exception
2
+ class Exception < ::StandardError
3
3
  # Missing field.
4
4
  E_MISSING = 1
5
5
 
@@ -22,5 +22,15 @@ module WebToPay
22
22
  E_SMS_ANSWER = 7
23
23
 
24
24
  attr_accessor :code, :field_name
25
+
26
+ def as_json(options = {})
27
+ {
28
+ error: {
29
+ message: message,
30
+ field_name: field_name,
31
+ code: code
32
+ }
33
+ }
34
+ end
25
35
  end
26
36
  end
@@ -0,0 +1,45 @@
1
+ class WebToPay::Payment
2
+ ATTRIBUTES = [:projectid, :orderid, :lang, :amount, :currency, :accepturl, :cancelurl, :callbackurl,
3
+ :country, :paytext, :p_email, :p_name, :p_surname, :payment, :test, :version]
4
+ attr_accessor *ATTRIBUTES
5
+
6
+ extend ActiveModel::Naming
7
+ include ActiveModel::AttributeMethods
8
+
9
+ def initialize(params = {}, user_params = {})
10
+ self.version = WebToPay::API_VERSION
11
+ self.projectid = user_params[:projectid] || WebToPay.config.project_id
12
+ @sign_password = user_params[:sign_password] || WebToPay.config.sign_password
13
+
14
+ params.each_pair do |field, value|
15
+ self.public_send("#{field}=", value)
16
+ end
17
+ end
18
+
19
+ def query
20
+ @query ||= begin
21
+ query = []
22
+ ATTRIBUTES.each do |field|
23
+ value = self.public_send(field)
24
+ next if value.blank?
25
+ query << "#{field}=#{ CGI::escape value.to_s}"
26
+ end
27
+ query.join('&')
28
+ end
29
+ end
30
+
31
+ def data # encoded query
32
+ @data ||= Base64.encode64(query).gsub("\n", '').gsub('/', '+').gsub('_', '-')
33
+ end
34
+
35
+ def url
36
+ "https://www.mokejimai.lt/pay?data=#{CGI::escape data}&sign=#{CGI::escape sign}"
37
+ end
38
+
39
+ def sign
40
+ Digest::MD5.hexdigest(data + @sign_password)
41
+ end
42
+
43
+ def to_key
44
+ end
45
+ end
@@ -0,0 +1,198 @@
1
+ class WebToPay::Response
2
+ PREFIX = "wp_"
3
+ SPECS_BY_TYPE = {
4
+ # Array structure:
5
+ # * name – request item name.
6
+ # * maxlen – max allowed value for item.
7
+ # * required – is this item is required in response.
8
+ # * mustcheck – this item must be checked by user.
9
+ # * isresponse – if false, item must not be included in response array.
10
+ # * regexp – regexp to test item value.
11
+ makro: [
12
+ [ :projectid, 11, true, true, true, /^\d+$/ ],
13
+ [ :orderid, 40, false, false, true, '' ],
14
+ [ :lang, 3, false, false, true, /^[a-z]{3}$/i ],
15
+ [ :amount, 11, false, false, true, /^\d+$/ ],
16
+ [ :currency, 3, false, false, true, /^[a-z]{3}$/i ],
17
+ [ :payment, 20, false, false, true, '' ],
18
+ [ :country, 2, false, false, true, /^[a-z]{2}$/i ],
19
+ [ :paytext, 0, false, false, true, '' ],
20
+ [ :ss2, 0, true, false, true, '' ],
21
+ [ :ss1, 0, false, false, true, '' ],
22
+ [ :name, 255, false, false, true, '' ],
23
+ [ :surename, 255, false, false, true, '' ],
24
+ [ :status, 255, false, false, true, '' ],
25
+ [ :error, 20, false, false, true, '' ],
26
+ [ :test, 1, false, false, true, /^[01]$/ ],
27
+ [ :p_email, 0, false, false, true, '' ],
28
+ [ :payamount, 0, false, false, true, '' ],
29
+ [ :paycurrency, 0, false, false, true, '' ],
30
+ [ :version, 9, true, false, true, /^\d+\.\d+$/ ],
31
+ [ :sign_password, 255, false, true, false, '' ]
32
+ ],
33
+
34
+ # Specification array for mikro response.
35
+ #
36
+ # Array structure:
37
+ # * name – request item name.
38
+ # * maxlen – max allowed value for item.
39
+ # * required – is this item is required in data.
40
+ # * mustcheck – this item must be checked by user.
41
+ # * isresponse – if false, item must not be included in response array.
42
+ # * regexp – regexp to test item value.
43
+ mikro: [
44
+ [ :to, 0, true, false, true, '' ],
45
+ [ :sms, 0, true, false, true, '' ],
46
+ [ :from, 0, true, false, true, '' ],
47
+ [ :operator, 0, true, false, true, '' ],
48
+ [ :amount, 0, true, false, true, '' ],
49
+ [ :currency, 0, true, false, true, '' ],
50
+ [ :country, 0, true, false, true, '' ],
51
+ [ :id, 0, true, false, true, '' ],
52
+ [ :_ss2, 0, true, false, true, '' ],
53
+ [ :_ss1, 0, true, false, true, '' ],
54
+ [ :test, 0, true, false, true, '' ],
55
+ [ :key, 0, true, false, true, '' ],
56
+ #[ :version, 9, true, false, true, /^\d+\.\d+$/ ]
57
+ ]
58
+ }
59
+
60
+ attr_reader :query, :user_data, :errors, :project_id
61
+ attr_accessor :data, :ss1, :ss2
62
+
63
+ def initialize(params, user_params = {})
64
+ params.each_pair do |field, value|
65
+ self.public_send("#{field}=", value)
66
+ end
67
+ @sign_password = user_params[:sign_password] || WebToPay.config.sign_password
68
+ @project_id = user_params[:projectid] || WebToPay.config.project_id
69
+ @errors = []
70
+ end
71
+
72
+ def query
73
+ @query ||= begin
74
+ data = self.data || ''
75
+ Base64.decode64( data.gsub('-', '+').gsub('_', '/') )
76
+ end
77
+ end
78
+
79
+ def query_params
80
+ @query_params ||= begin
81
+ params = CGI::parse(query)
82
+ params.each_pair do |key, value|
83
+ params[key] = value.first
84
+ end
85
+ params.merge(ss1: ss1, ss2: ss2).with_indifferent_access
86
+ end
87
+ end
88
+
89
+ def valid?(params = {})
90
+ @errors = []
91
+ params = {
92
+ projectid: @project_id
93
+ }.merge(params)
94
+
95
+ valid = custom_params_valid?(params)
96
+ valid &&= ss1_valid?
97
+ valid &&= all_required_fields_included?
98
+ valid &&= no_blacklisted_fields_included?
99
+ valid &&= payment_status_valid?
100
+ valid
101
+ end
102
+
103
+ def validate!(params = {})
104
+ raise errors.first unless valid?(params)
105
+ end
106
+
107
+ def type
108
+ @type ||= if query_params[:to] && query_params[:from] && query_params[:sms] && query_params[:projectid].nil?
109
+ :mikro
110
+ else
111
+ :makro
112
+ end
113
+ end
114
+
115
+ def specs
116
+ SPECS_BY_TYPE[type]
117
+ end
118
+
119
+ def mikro?
120
+ type == :mikro
121
+ end
122
+
123
+ def makro?
124
+ type == :makro
125
+ end
126
+
127
+ private
128
+ def custom_params_valid?(params)
129
+ valid = true
130
+ params.each_pair do |key, expected_value|
131
+ query_value = query_params[key].presence
132
+ if query_value.is_a?(String)
133
+ if expected_value.is_a?(Integer)
134
+ query_value = query_value.to_i
135
+ elsif expected_value.is_a?(Float)
136
+ query_value = query_value.to_f
137
+ end
138
+ end
139
+ if query_value != expected_value
140
+ @errors << WebToPay::Exception.new("\"#{key}\" is invalid. Expected \"#{expected_value}\", but was \"#{query_value}\"")
141
+ valid = false
142
+ end
143
+ end
144
+ valid
145
+ end
146
+
147
+ def required_response_fields
148
+ specs.select{|s| s[2]}.map(&:first).map(&:to_sym)
149
+ end
150
+
151
+ def blacklisted_response_fields
152
+ specs.reject{|s| s[4]}.map(&:first).map(&:to_sym)
153
+ end
154
+
155
+ def all_required_fields_included?
156
+ fields = query_params.keys.map(&:to_sym)
157
+ if (required_response_fields & fields).size != required_response_fields.size
158
+ @errors << WebToPay::Exception.new("\"#{(required_response_fields - fields).join('" , "')}\" parameter(s) not found in response query")
159
+ return false
160
+ end
161
+ true
162
+ end
163
+
164
+ def no_blacklisted_fields_included?
165
+ fields = query_params.keys.map(&:to_sym)
166
+ if (blacklisted_response_fields & fields).size != 0
167
+ @errors << WebToPay::Exception.new("\"#{(fields & blacklisted_response_fields).join('" , "')}\" blacklisted parameter(s) found in response query")
168
+ return false
169
+ end
170
+ true
171
+ end
172
+
173
+ def ss1_valid?
174
+ if self.ss1 != expected_ss1
175
+ @errors << WebToPay::Exception.new("ss1 param is invalid. Expected \"#{expected_ss1}\", but was \"#{self.ss1}\"")
176
+ return false
177
+ end
178
+ true
179
+ end
180
+
181
+ def payment_status_valid?
182
+ if makro? && query_params[:status].to_i != 1
183
+ e = WebToPay::Exception.new("Returned transaction status is #{query_params[:status]}, successful status should be 1.")
184
+ e.code = WebToPay::Exception::E_INVALID
185
+ @errors << e
186
+ return false
187
+ end
188
+ return true
189
+ end
190
+
191
+ def expected_ss1
192
+ Digest::MD5.hexdigest(expected_data + @sign_password)
193
+ end
194
+
195
+ def expected_data
196
+ Base64.encode64(query).gsub("\n", '').gsub('/', '+').gsub('_', '-')
197
+ end
198
+ end