webtopay 1.2.1 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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