walletone 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ab9a944684b8e39c9412cb122c2fd1d7b128a06f
4
+ data.tar.gz: 9ed98422688067832bed0ca481d1302e80237e47
5
+ SHA512:
6
+ metadata.gz: a9bf93be1b557b034411849dcdaeceb4280b19a9c75f87c3d57cc85913456ae2528b42d0b6b82837f10e80563e9c34bee779f597b60f0b96587672742d82d16b
7
+ data.tar.gz: 39f6840032fac8b8fbfc0ab3856338bce8c2095ae59fa592e3a4462715416b53efd456303d6efa1da72495463211c4791fe4b65efdd775a8420069e091552361
data/LICENSE ADDED
File without changes
data/README.md ADDED
@@ -0,0 +1,110 @@
1
+ # walletone checkout client for ruby
2
+
3
+ [![Build Status](https://travis-ci.org/BrandyMint/walletone.svg)](https://travis-ci.org/BrandyMint/walletone)
4
+ [![Code Climate](https://codeclimate.com/github/BrandyMint/walletone/badges/gpa.svg)](https://codeclimate.com/github/BrandyMint/walletone)
5
+
6
+ ---
7
+
8
+ ## Прием уведомлений об оплате (rack middleware)
9
+
10
+ ### 1. Создаем rack middleware для приема уведомлений
11
+
12
+ Сначала определимся как мы будем получать уведомление об оплате.
13
+
14
+ #### Вариант N1. Через callback
15
+
16
+ ```ruby
17
+ wm = Walletone::Middleware::Callback.new do |notify, env|
18
+ # notify is a Walletone::Notification instance
19
+
20
+ raise 'Wrong sign' unless notify.valid? W1_SECRET_KEY
21
+
22
+ # TODO something with notify
23
+
24
+ 'Return some message for OK response'
25
+ end
26
+ ```
27
+
28
+ #### Вариант N2. Создаем свой класс middleware
29
+
30
+ ```ruby
31
+ class MyApp::WalletoneMiddleware < Waletone::Middleware::Base
32
+ def perform notify, env
33
+ raise 'Wrong sign' unless notify.valid? W1_SECRET_KEY
34
+
35
+ # TODO something with notify
36
+ 'Return some message for OK response'
37
+ end
38
+ end
39
+
40
+ wm = MyApp::WalletoneMiddleware.new
41
+ ```
42
+
43
+ ### 2. Подключаем middleware
44
+
45
+ ```ruby
46
+ # config.ru
47
+ run wm
48
+
49
+ # rails
50
+ mount wm => '/w1_callback'
51
+ ```
52
+
53
+ ### 3. Прописываем в настройках мерчанта на walletone.com получившийся url
54
+
55
+ Готово. Принимаем уведомления.
56
+
57
+ ## Генерация формы оплаты
58
+
59
+ ### Сначала заполняем необходимые для формы поля
60
+
61
+ Поля хранятся в объекте типа `Walletone::Payment`, который представляет из
62
+ себя `Hash` с возможностью прямого доступа к основным полям с префиксом `WMI_`.
63
+
64
+ Для полей требующих множественное значение (типа `WMI_PTENABLED`) в качестве
65
+ значений можно передавать массив строк.
66
+
67
+ ```ruby
68
+ payment = Walletone::Payment.new(
69
+ WMI_MERCHANT_ID: 'Номер вашего merchant ID',
70
+ WMI_PAYMENT_AMOUNT: 10000, # Сумма
71
+ WMI_CURRENCY_ID: 643, # ISO номер валюты (По умолчанию 643 - Рубль),
72
+ WMI_PTENABLED: ['WebMoneyRUB', 'WebMoneyUSD'],
73
+ SOME_CUSTOM_FIELD: 'value'
74
+ # etc любые другие поля
75
+ )
76
+ ```
77
+
78
+
79
+ ### Собственно генераця формы
80
+
81
+ ```haml
82
+ - form = Walletone::Form.new payment
83
+
84
+ %h5 В течение 5-и секунд вы будете переправлены на страницу оплаты.
85
+ = form_tag form.checkout_url, form.options.merge(data: {autosubmit: true}) do |f|
86
+ = form.hidden_fields_tags
87
+ = submit_tag 'Перейти к оплате',
88
+ :class=>'btn btn-primary',
89
+ :data =>{:disable_with => 'Переправляю на сайт оплаты..'}
90
+
91
+ :coffee
92
+ # Автоматически сабмитим форму, чтобы не тревожить лишний раз
93
+ # пользователя
94
+ $ -> $('[data-autosubmit]').submit()
95
+ ```
96
+
97
+ # Прочие ссылки
98
+
99
+ 1. [Walletone Open API](https://api.w1.ru/OpenApi/)
100
+ 2. [Документация (API)](http://www.walletone.com/ru/merchant/documentation/)
101
+
102
+ ---
103
+
104
+ ### Contributing
105
+
106
+ 1. Fork it
107
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
108
+ 3. Commit your changes (`git commit -am 'add some feature'`)
109
+ 4. Push to the branch (`git push origin my-new-feature`)
110
+ 5. Create new Pull Request
data/lib/walletone.rb ADDED
@@ -0,0 +1,52 @@
1
+ require 'logger'
2
+ require 'virtus'
3
+
4
+ require 'walletone/errors/walletone_error'
5
+ require 'walletone/errors/bad_url_error'
6
+
7
+ require 'walletone/payment'
8
+ require 'walletone/notification'
9
+ require 'walletone/middleware/base'
10
+
11
+ module Walletone
12
+ API_URL = 'https://api.w1.ru/OpenApi/'
13
+ V1_CHECKOUT_URL = 'https://www.walletone.com/checkout/default.aspx'
14
+ V2_CHECKOUT_URL = 'https://wl.walletone.com/checkout/checkout/Index'
15
+
16
+ class Configuration
17
+ include ::Singleton
18
+ attr_accessor :logger, :error_notifier, :error_notify_method, :web_checkout_url
19
+
20
+ def self.default_logger
21
+ logger = Logger.new(STDOUT)
22
+ logger.progname = 'walletone'
23
+ logger
24
+ end
25
+
26
+ def initialize
27
+ self.logger = self.class.default_logger
28
+ self.web_checkout_url = V2_CHECKOUT_URL
29
+ self.error_notify_method = :notify
30
+ self.error_notifier = Honeybadger if defined? Honeybadger
31
+ self.error_notifier = Bugsnag if defined? Bugsnag
32
+ end
33
+ end
34
+
35
+ def self.notify_error error, *args
36
+ logger.error "Catch error #{error}"
37
+ return unless config.error_notifier
38
+ config.error_notifier.send self.config.error_notify_method, error, *args
39
+ end
40
+
41
+ def self.config
42
+ Configuration.instance
43
+ end
44
+
45
+ def self.configure
46
+ yield config
47
+ end
48
+
49
+ def self.logger
50
+ config.logger
51
+ end
52
+ end
@@ -0,0 +1,4 @@
1
+ module Walletone
2
+ class BadUrlError < WalletoneError
3
+ end
4
+ end
@@ -0,0 +1,4 @@
1
+ module Walletone
2
+ class WalletoneError < StandardError
3
+ end
4
+ end
@@ -0,0 +1,81 @@
1
+ # Базовый класс для полей формы и уведомления
2
+ #
3
+ module Walletone
4
+ class Fields < Hash
5
+ # http://www.walletone.com/ru/merchant/documentation/#step2
6
+ #
7
+ #
8
+ W1_KEYS = %i(
9
+ WMI_MERCHANT_ID
10
+ WMI_PAYMENT_AMOUNT
11
+ WMI_CURRENCY_ID
12
+ WMI_PAYMENT_NO
13
+ WMI_DESCRIPTION
14
+ WMI_SUCCESS_URL WMI_FAIL_URL
15
+ WMI_EXPIRED_DATE
16
+ WMI_PTENABLED WMI_PTDISABLED
17
+ WMI_RECIPIENT_LOGIN
18
+ WMI_CUSTOMER_FIRSTNAME WMI_CUSTOMER_LASTNAME WMI_CUSTOMER_EMAIL
19
+ WMI_CULTURE_ID
20
+ WMI_SIGNATURE
21
+ WMI_DELIVERY_REQUEST
22
+ WMI_DELIVERY_COUNTRY WMI_DELIVERY_REGION WMI_DELIVERY_CITY WMI_DELIVERY_ADDRESS
23
+ WMI_DELIVERY_CONTACTINFO WMI_DELIVERY_COMMENTS WMI_DELIVERY_ORDERID
24
+ WMI_DELIVERY_DATEFROM WMI_DELIVERY_DATETILL
25
+ WMI_PSP_MERCHANT_ID
26
+ )
27
+
28
+ MULTIPLE_VALUES = %i(WMI_PTENABLED WMI_PTDISABLED)
29
+
30
+ # Определяем методы для прямого доступа
31
+ # > payment.WMI_MERCHANT_ID
32
+ # > payment.WMI_MERCHANT_ID=123
33
+ #
34
+ # > payment.WMI_PTENABLED =
35
+ W1_KEYS.each do |k|
36
+ define_method k do
37
+ fetch k
38
+ end
39
+
40
+ define_method "#{k}=" do |value|
41
+ self[k]= value
42
+ end
43
+ end
44
+
45
+ def initialize attrs={}
46
+ fields = super
47
+ return fields if attrs.empty?
48
+ symbolyzed_attrs = attrs.inject({}) { |acc, e| acc[e.first.to_sym]=e.last; acc }
49
+ fields.merge! symbolyzed_attrs
50
+ end
51
+
52
+ def [] key
53
+ fetch key.to_sym, nil
54
+ end
55
+
56
+ def []= key, value
57
+ super key.to_sym, value
58
+ end
59
+
60
+ def fetch key, default=nil
61
+ super key.to_sym, default
62
+ end
63
+
64
+ def as_list
65
+ keys.inject([]) do |acc, name|
66
+ # Делаем именно так, чтобы работало переопределение методов
67
+ # в Payment
68
+ value = respond_to?(name) ? send(name) : fetch(name)
69
+ Array(value).each { |v| acc << [name.to_s, v.to_s] }
70
+ acc
71
+ end
72
+ end
73
+
74
+ private
75
+
76
+ def signer
77
+ Signer.new fields: self
78
+ end
79
+
80
+ end
81
+ end
@@ -0,0 +1,30 @@
1
+ require 'cgi'
2
+ module Walletone
3
+ class Form
4
+ include Virtus.model strict: true, coerce: false
5
+ attribute :payment, Payment, required: true
6
+
7
+ def checkout_url
8
+ Walletone.config.web_checkout_url
9
+ end
10
+
11
+ def hidden_fields_tags
12
+ payment.as_list.map do |field|
13
+ key, value = field
14
+ hidden_field_tag(key, value)
15
+ end.join
16
+ end
17
+
18
+ # Рекомендуемые опции
19
+ def options
20
+ { authenticity_token: false, enforce_utf8: false }
21
+ end
22
+
23
+ private
24
+
25
+ def hidden_field_tag name, value
26
+ "<input name=\"#{CGI.escapeHTML(name.to_s)}\" type=\"hidden\" value=\"#{CGI.escapeHTML(value.to_s)}\" />"
27
+ end
28
+
29
+ end
30
+ end
@@ -0,0 +1,5 @@
1
+ module Walletone
2
+ module Middleware
3
+ # just define namespace
4
+ end
5
+ end
@@ -0,0 +1,68 @@
1
+ require 'rack'
2
+ require 'walletone/notification'
3
+
4
+ # Это rack-middleware который принимает на себя
5
+ # первый удар с уведомлением от walletone
6
+ # Несет следующие обязанности:
7
+ #
8
+ # 1. Перевести параметры из cp1251 в unicode
9
+ # 2. Построить объект типа Noficiation
10
+ # 3. Вызвать метод perform с аргументами notify и env
11
+ #
12
+ # Метод peform должет быть переопределен в классах наследниках.
13
+ # Например Walletone::Middleware::Base
14
+ #
15
+ module Walletone::Middleware
16
+ class Base
17
+ OK = 'OK'
18
+ RETRY = 'RETRY'
19
+
20
+ def call(env)
21
+ logger.info 'Middleware start'
22
+ request = Rack::Request.new env
23
+ encoded_params = from_cp1251_to_utf8 request.params
24
+
25
+ logger.info "Middleware parameters is #{encoded_params}"
26
+ notify = Walletone::Notification.new encoded_params
27
+
28
+ logger.info 'Middleware perform'
29
+ ok_message = perform notify, env
30
+
31
+ logger.info "Middleware result is #{ok_message}'"
32
+
33
+ body = make_response OK, ok_message
34
+ [200, {}, [body]]
35
+ rescue => err
36
+ Walletone.notify_error err
37
+
38
+ [200, {}, [make_response(RETRY, err.message)]]
39
+ end
40
+
41
+ private
42
+
43
+ def logger
44
+ Walletone.logger
45
+ end
46
+
47
+ def perform notify, env
48
+ raise 'Walletone middleware perform method is not implemented'
49
+ end
50
+
51
+ def make_response(result, description)
52
+ # NOTE А нужно ли тут конвертировать в cp1251?
53
+ if description
54
+ "WMI_RESULT=#{result}&WMI_DESCRIPTION=#{description}"
55
+ else
56
+ "WMI_RESULT=#{result}"
57
+ end
58
+ end
59
+
60
+ # walletone передает все параметры в cp1251
61
+ # приводим в uft-8
62
+ def from_cp1251_to_utf8(params)
63
+ params.map do |k, v|
64
+ [k, v.force_encoding("cp1251").encode("utf-8", undef: :replace)]
65
+ end
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,19 @@
1
+ module Walletone::Middleware
2
+ class Callback < Base
3
+
4
+ def initialize callback
5
+ raise 'Callback must be a Proc' unless callback.is_a? Proc
6
+ @callback = callback
7
+ super()
8
+ end
9
+
10
+ private
11
+
12
+ attr_reader :callback
13
+
14
+ def perform notify, env
15
+ callback.call notify, env
16
+ end
17
+
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ require 'walletone/fields'
2
+ # Уведомление от walletone которое пришло через rack middleware callback
3
+ #
4
+
5
+ module Walletone
6
+ class Notification < Fields
7
+
8
+ def initialize params
9
+ super( params ).freeze
10
+ end
11
+
12
+ def valid?(secret_key, hash_type = Signer::DEFAULT_HASH_TYPE)
13
+ self.WMI_SIGNATURE == signer.signature( secret_key, hash_type )
14
+ end
15
+
16
+ end
17
+ end
@@ -0,0 +1,44 @@
1
+ require 'walletone/fields'
2
+ require 'walletone/signer'
3
+ require 'base64'
4
+ require 'time'
5
+
6
+ # Поля для формы оплаты формируемые приложением
7
+ #
8
+ module Walletone
9
+ class Payment < Fields
10
+ RUB_ISO_NUMBER = 643
11
+ DEFAULT_CURRENTY = RUB_ISO_NUMBER
12
+
13
+ def sign!(secret_key, hash_type=Signer::DEFAULT_HASH_TYPE)
14
+ raise 'Already signed!' if signed?
15
+ self.WMI_SIGNATURE = signer.signature secret_key, hash_type
16
+
17
+ freeze
18
+ end
19
+
20
+ def signed?
21
+ self.WMI_SIGNATURE.is_a?(String) && ! self.WMI_SIGNATURE.empty?
22
+ end
23
+
24
+ def WMI_CURRENCY_ID
25
+ super || DEFAULT_CURRENTY
26
+ end
27
+
28
+ def WMI_PAYMENT_AMOUNT
29
+ value = super
30
+ "%.2f" % value if value
31
+ end
32
+
33
+ def WMI_DESCRIPTION
34
+ value = super
35
+ "BASE64:#{Base64.encode64(value[0...250])[0..-2]}" if value
36
+ end
37
+
38
+ def WMI_EXPIRED_DATE
39
+ value = super
40
+ value ? value.iso8601 : value
41
+ end
42
+
43
+ end
44
+ end
@@ -0,0 +1,45 @@
1
+ require 'digest/sha1'
2
+ require 'digest/md5'
3
+
4
+ require 'walletone/fields'
5
+
6
+ module Walletone
7
+ class Signer
8
+ include Virtus.model strict: true, coerce: false
9
+ SIGN_HASH_TYPES = [:md5, :sha1]
10
+ DEFAULT_HASH_TYPE = :md5
11
+ WMI_SIGNATURE = 'WMI_SIGNATURE'
12
+
13
+ attribute :fields, Walletone::Fields, requried: true
14
+
15
+ def signature secret_key, hash_type=DEFAULT_HASH_TYPE
16
+ case hash_type
17
+ when :md5
18
+ Digest::MD5.base64digest( fields_as_string( secret_key ) )
19
+ when :sha1
20
+ Digest::SHA1.base64digest( fields_as_string( secret_key ) )
21
+ else
22
+ raise ArgumentError, hash_type
23
+ end
24
+ end
25
+
26
+ private
27
+
28
+ def fields_as_string secret_key
29
+ [sorted_values, secret_key].join.encode('cp1251')
30
+ end
31
+
32
+ # Удаляем подпись и сортируем поля в алфавитном порядке
33
+ #
34
+ def sorted_values
35
+ fields
36
+ .as_list
37
+ .reject { |k, v| k == WMI_SIGNATURE }
38
+ .sort { |a, b| [ a[0], a[1] ] <=> [ b[0], b[1] ] }
39
+ .map(&:last)
40
+ .map(&:to_s)
41
+ .compact
42
+ end
43
+
44
+ end
45
+ end
@@ -0,0 +1,3 @@
1
+ module Walletone
2
+ VERSION = '0.1.0'
3
+ end
@@ -0,0 +1,6 @@
1
+ Fabricator(:fields, from: 'Walletone::Fields') do
2
+ WMI_MERCHANT_ID { FFaker.numerify('############').to_i }
3
+ WMI_CURRENCY_ID { [643, 840, 978].sample }
4
+ WMI_PAYMENT_AMOUNT { FFaker.numerify('##.##').to_f }
5
+ end
6
+
@@ -0,0 +1,5 @@
1
+ Fabricator(:payment, from: 'Walletone::Payment') do
2
+ WMI_MERCHANT_ID { FFaker.numerify('############').to_i }
3
+ WMI_CURRENCY_ID { [643, 840, 978].sample }
4
+ WMI_PAYMENT_AMOUNT { FFaker.numerify('##.##').to_f }
5
+ end
@@ -0,0 +1,14 @@
1
+ require 'walletone'
2
+ require 'fabrication'
3
+ require 'ffaker'
4
+ require 'pry'
5
+
6
+ Dir["./spec/support/**/*.rb"].sort.each { |f| require f}
7
+
8
+ RSpec.configure do |config|
9
+ config.order = :random
10
+
11
+ config.before(:all) do
12
+ Walletone.logger.level = Logger::FATAL
13
+ end
14
+ end
@@ -0,0 +1,54 @@
1
+ require 'walletone/fields'
2
+
3
+ describe Walletone::Fields do
4
+
5
+ describe 'Именованные поля' do
6
+ subject { described_class.new }
7
+ let(:merchant_id) { 'some_id' }
8
+
9
+ it 'nill by default' do
10
+ expect(subject.WMI_MERCHANT_ID).to eq nil
11
+ end
12
+ it { expect(subject.WMI_MERCHANT_ID=merchant_id).to eq merchant_id }
13
+
14
+ context 'установили заранее' do
15
+ let(:merchant_id) { 'some_id2' }
16
+ before { subject.WMI_MERCHANT_ID= merchant_id }
17
+ it { expect(subject.WMI_MERCHANT_ID).to eq merchant_id }
18
+ end
19
+
20
+ end
21
+
22
+ describe '#initialize' do
23
+ let(:fail_url) { 'someurl' }
24
+ let(:attrs) { { 'WMI_FAIL_URL' => fail_url } }
25
+ subject { described_class.new attrs }
26
+
27
+ it { expect(subject.WMI_FAIL_URL).to eq fail_url }
28
+ it { expect(subject['WMI_FAIL_URL']).to eq fail_url }
29
+ it { expect(subject[:WMI_FAIL_URL]).to eq fail_url }
30
+ end
31
+
32
+ describe '#fetch as keys and keys maybe as symbols or as strings' do
33
+ let(:fail_url) { 'someurl' }
34
+ let(:attrs) { { WMI_FAIL_URL: fail_url } }
35
+ subject { described_class.new attrs }
36
+
37
+ it { expect(subject[:WMI_FAIL_URL]).to eq fail_url }
38
+ it { expect(subject['WMI_FAIL_URL']).to eq fail_url }
39
+ end
40
+
41
+
42
+ describe '#as_list' do
43
+ let(:fail_url) { 'someurl' }
44
+ let(:pt_enabled) { ['WebMoneyRUB', 'WebMoneyUSD'] }
45
+ let(:attrs) { { WMI_FAIL_URL: fail_url, WMI_PTENABLED: pt_enabled } }
46
+
47
+ subject { described_class.new( attrs ).as_list }
48
+
49
+ it 'получаем список полей в строчном варианте' do
50
+ expect(subject).to eq [["WMI_FAIL_URL", "someurl"], ["WMI_PTENABLED", 'WebMoneyRUB'], ["WMI_PTENABLED", 'WebMoneyUSD']]
51
+ end
52
+ end
53
+
54
+ end
@@ -0,0 +1,20 @@
1
+ require 'walletone/form'
2
+
3
+ describe Walletone::Form do
4
+ let(:payment) { Fabricate :payment }
5
+
6
+ subject { described_class.new payment: payment }
7
+
8
+ describe '#checkout_url' do
9
+ it { expect(subject.checkout_url).to eq Walletone.config.web_checkout_url }
10
+ end
11
+
12
+ describe '#hidden_fields_tags' do
13
+ it { expect(subject.hidden_fields_tags).to include 'input name' }
14
+ end
15
+
16
+ describe '#options' do
17
+ it { expect(subject.options).to be_a Hash }
18
+ end
19
+
20
+ end
@@ -0,0 +1,22 @@
1
+ require 'walletone/middleware'
2
+ require 'walletone/middleware/base'
3
+
4
+ describe Walletone::Middleware::Base do
5
+ let(:middleware) { described_class.new }
6
+ let!(:params) { { cp1251: 'привет'.encode('windows-1251') } }
7
+ subject! { Rack::MockRequest.new(middleware).post('/', params: params ) }
8
+
9
+ it do
10
+ expect(subject.status).to eq 200
11
+ end
12
+
13
+ it do
14
+ expect(subject.body).to eq 'WMI_RESULT=RETRY&WMI_DESCRIPTION=Walletone middleware perform method is not implemented'
15
+ end
16
+
17
+ #Walletone.notify_callback = lambda do |resp, env|
18
+ #expect(resp.param(:cp1251).first).to eq('привет')
19
+ #resp.ok
20
+ #end
21
+ #end
22
+ end
@@ -0,0 +1,14 @@
1
+ require 'walletone/middleware'
2
+ require 'walletone/middleware/callback'
3
+
4
+ describe Walletone::Middleware::Callback do
5
+ let(:callback) { -> (notify, env) { } }
6
+ let(:middleware) { described_class.new callback }
7
+ let!(:params) { { cp1251: 'привет'.encode('windows-1251') } }
8
+ subject! { Rack::MockRequest.new(middleware).post('/', params: params ) }
9
+
10
+ it do
11
+ expect(subject.body).to eq 'WMI_RESULT=OK'
12
+ end
13
+
14
+ end
@@ -0,0 +1,36 @@
1
+ require 'walletone/notification'
2
+
3
+ describe Walletone::Notification do
4
+ let(:secret) { '623577687055676e43446f6c474f385165356e64556e474e5d7366' }
5
+ let(:signature) { '0v/qXN0eonX2+8AuiLLONQ==' }
6
+ let(:raw_params) do
7
+ {
8
+ 'WMI_MERCHANT_ID' => '127696943127',
9
+ 'WMI_CURRENCY_ID' => '643',
10
+ 'WMI_PAYMENT_AMOUNT' => '3000.00',
11
+ 'WMI_AUTO_ACCEPT' => '1',
12
+ 'WMI_COMMISSION_AMOUNT' => '120.00',
13
+ 'WMI_CREATE_DATE' => '2014-12-16 20:04:27',
14
+ 'WMI_DESCRIPTION' => 'Браслет цепочка Хамса с цирконами золоченый',
15
+ 'WMI_EXPIRED_DATE' => '2015-01-16 20:04:27',
16
+ 'WMI_EXTERNAL_ACCOUNT_ID' => '4276********3093',
17
+ 'WMI_LAST_NOTIFY_DATE' => '2014-12-16 20:07:44',
18
+ 'WMI_NOTIFY_COUNT' => '3',
19
+ 'WMI_ORDER_ID' => '346935973926',
20
+ 'WMI_ORDER_STATE' => 'Accepted',
21
+ 'WMI_PAYMENT_NO' => 'kiosk:5-604-pro',
22
+ 'WMI_PAYMENT_TYPE' => 'CreditCardRUB',
23
+ 'WMI_FAIL_URL' => 'http://wannabe.kiiiosk.ru/payments/w1/failure',
24
+ 'WMI_SUCCESS_URL' => 'http://wannabe.kiiiosk.ru/payments/w1/success',
25
+ 'WMI_UPDATE_DATE' => '2014-12-16 20:05:51',
26
+ 'WMI_SIGNATURE' => signature
27
+ }
28
+ end
29
+ subject { described_class.new(raw_params) }
30
+
31
+ context '#valid?' do
32
+ it 'compare signatures from request and response' do
33
+ expect( subject.valid?(secret) ).to be_truthy
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,60 @@
1
+ describe Walletone::Payment do
2
+ let(:merchant_id) { 127830694690 }
3
+ let(:secret_key) { '3475706857624f46344e573753316e387c396e5f4b54767b796c4c' }
4
+
5
+ subject { described_class.new fields }
6
+
7
+ context '.new' do
8
+ let(:fields) { Walletone::Fields.new WMI_MERCHANT_ID: merchant_id }
9
+ it 'works' do
10
+ expect(subject.WMI_MERCHANT_ID).to eq(merchant_id)
11
+ end
12
+ end
13
+
14
+ context '#sign' do
15
+ let(:signature) { 'uPmNH+YVl154kSH9hQdWNA==' }
16
+ let(:fields) { Walletone::Fields.new({
17
+ WMI_MERCHANT_ID: 127830694690,
18
+ WMI_PAYMENT_AMOUNT: 100.00,
19
+ WMI_PAYMENT_NO: '123-45',
20
+ WMI_CURRENCY_ID: 643,
21
+ WMI_DESCRIPTION: 'payment test',
22
+ WMI_EXPIRED_DATE: Time.new(2019, 12, 31, 23, 59, 59, "+00:00").utc,
23
+ WMI_SUCCESS_URL: 'http://w1.artemeff.com/w1/success',
24
+ WMI_FAIL_URL: 'http://w1.artemeff.com/w1/fail'
25
+ })
26
+ }
27
+
28
+ before do
29
+ subject.sign! secret_key
30
+ end
31
+
32
+ it { expect(subject).to be_frozen }
33
+ it { expect(subject).to be_signed }
34
+ it { expect(subject.WMI_SIGNATURE).to eq signature }
35
+
36
+ end
37
+
38
+ it 'WMI_PAYMENT_AMOUNT' do
39
+ payment = Fabricate(:payment, {WMI_PAYMENT_AMOUNT: 1.2})
40
+ expect(payment.WMI_PAYMENT_AMOUNT).to eq('1.20')
41
+ end
42
+
43
+ it 'WMI_DESCRIPTION as base64' do
44
+ payment = Fabricate(:payment, {WMI_DESCRIPTION: 'test'})
45
+ expect(payment.WMI_DESCRIPTION).to eq('BASE64:dGVzdA==')
46
+ end
47
+
48
+ it 'WMI_DESCRIPTION reduces to 250 symbols' do
49
+ description = FFaker::Lorem.sentence(128)
50
+ payment = Fabricate(:payment, {WMI_DESCRIPTION: description})
51
+ text = Base64.decode64(payment.WMI_DESCRIPTION)
52
+ expect(text.size).to eq(255)
53
+ end
54
+
55
+ it 'WMI_EXPIRED_DATE as ISO8601' do
56
+ date = Time.new(2015, 4, 1, 12, 00, 00)
57
+ payment = Fabricate(:payment, {WMI_EXPIRED_DATE: date})
58
+ expect(payment.WMI_EXPIRED_DATE).to eq date.iso8601
59
+ end
60
+ end
@@ -0,0 +1,42 @@
1
+ require 'walletone/signer'
2
+
3
+
4
+ describe Walletone::Signer do
5
+ let(:secret_key) { '3475706857624f46344e573753316e387c396e5f4b54767b796c4c' }
6
+
7
+ subject { described_class.new fields: fields }
8
+
9
+ context '#sign' do
10
+ describe 'variant1' do
11
+ let(:fields) { Walletone::Fields.new(
12
+ WMI_MERCHANT_ID: '127830694690',
13
+ WMI_PAYMENT_AMOUNT: '100.00',
14
+ WMI_PAYMENT_NO: '123-45',
15
+ WMI_CURRENCY_ID: '643',
16
+ WMI_DESCRIPTION: 'BASE64:cGF5bWVudCB0ZXN0',
17
+ WMI_EXPIRED_DATE: '2019-12-31T23:59:59Z',
18
+ WMI_SUCCESS_URL: 'http://w1.artemeff.com/w1/success',
19
+ WMI_FAIL_URL: 'http://w1.artemeff.com/w1/fail'
20
+ )}
21
+
22
+ let(:signature) { 'uPmNH+YVl154kSH9hQdWNA==' }
23
+ it { expect( subject.signature( secret_key ) ).to eq signature }
24
+ end
25
+
26
+ describe 'variant 2 (complex)' do
27
+ let(:payment_types) { ['WebMoneyRUB', 'WebMoneyUSD'] }
28
+ let(:fields) { Walletone::Fields.new({
29
+ WMI_MERCHANT_ID: 127830694690,
30
+ WMI_PAYMENT_AMOUNT: '100.00',
31
+ WMI_CURRENCY_ID: 643,
32
+ WMI_PTENABLED: payment_types,
33
+ foo: ['bar', 'baz']
34
+ }) }
35
+ let(:signature) { 'BF737gCE9O3JOR5iqQF1kg==' }
36
+
37
+ it { expect( subject.signature( secret_key ) ).to eq signature }
38
+ end
39
+ end
40
+
41
+ #is_valid = response.valid?('0v/qXN0eonX2+8AuiLLONQ==', secret)
42
+ end
@@ -0,0 +1,18 @@
1
+ describe Walletone do
2
+ context '.logger' do
3
+ it 'has logger' do
4
+ expect(described_class.logger).to be_a(Logger)
5
+ end
6
+ end
7
+
8
+ context '.notify_error' do
9
+ let(:error) { StandardError.new }
10
+ it do
11
+ error_notifier = double
12
+ allow(Walletone.config).to receive(:error_notifier).and_return error_notifier
13
+ expect(error_notifier).to receive(:notify).with(error)
14
+ expect(described_class.logger).to receive(:error)
15
+ described_class.notify_error error
16
+ end
17
+ end
18
+ end
data/walletone.gemspec ADDED
@@ -0,0 +1,35 @@
1
+ # coding: utf-8
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require 'walletone/version'
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "walletone"
7
+ s.version = Walletone::VERSION
8
+ s.authors = ["Danil Pismenny", "Yuri Artemev"]
9
+ s.email = ["danil@brandymint.ru", "i@artemeff.com"]
10
+ s.homepage = "https://github.com/BrandyMint/walletone"
11
+ s.summary = %q{walleton.com Checkout client}
12
+ s.description = %q{Клиент для приема оплаты через walletone.com}
13
+ s.licenses = ['MIT']
14
+
15
+ s.files = %w(README.md LICENSE walletone.gemspec)
16
+ s.files += Dir.glob("lib/**/*.rb")
17
+ s.files += Dir.glob("spec/**/*")
18
+ s.require_paths = ['lib']
19
+ s.test_files = Dir.glob("spec/**/*")
20
+
21
+ s.add_dependency 'virtus', '~> 1.0.5'
22
+ s.add_dependency 'rack', '~> 1.6'
23
+
24
+ s.add_development_dependency 'rake'
25
+ s.add_development_dependency 'rspec', '~> 3.2.0'
26
+ s.add_development_dependency 'ffaker', '>= 2.0.0'
27
+ s.add_development_dependency 'fabrication', '~> 2.12.2'
28
+ s.add_development_dependency "pry"
29
+ s.add_development_dependency "pry-nav"
30
+ s.add_development_dependency "webmock"
31
+ s.add_development_dependency "guard"
32
+ s.add_development_dependency "guard-rspec"
33
+ s.add_development_dependency 'guard-ctags-bundler'
34
+ s.add_development_dependency 'yard'
35
+ end
metadata ADDED
@@ -0,0 +1,266 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: walletone
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Danil Pismenny
8
+ - Yuri Artemev
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2015-04-10 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: virtus
16
+ requirement: !ruby/object:Gem::Requirement
17
+ requirements:
18
+ - - "~>"
19
+ - !ruby/object:Gem::Version
20
+ version: 1.0.5
21
+ type: :runtime
22
+ prerelease: false
23
+ version_requirements: !ruby/object:Gem::Requirement
24
+ requirements:
25
+ - - "~>"
26
+ - !ruby/object:Gem::Version
27
+ version: 1.0.5
28
+ - !ruby/object:Gem::Dependency
29
+ name: rack
30
+ requirement: !ruby/object:Gem::Requirement
31
+ requirements:
32
+ - - "~>"
33
+ - !ruby/object:Gem::Version
34
+ version: '1.6'
35
+ type: :runtime
36
+ prerelease: false
37
+ version_requirements: !ruby/object:Gem::Requirement
38
+ requirements:
39
+ - - "~>"
40
+ - !ruby/object:Gem::Version
41
+ version: '1.6'
42
+ - !ruby/object:Gem::Dependency
43
+ name: rake
44
+ requirement: !ruby/object:Gem::Requirement
45
+ requirements:
46
+ - - ">="
47
+ - !ruby/object:Gem::Version
48
+ version: '0'
49
+ type: :development
50
+ prerelease: false
51
+ version_requirements: !ruby/object:Gem::Requirement
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: '0'
56
+ - !ruby/object:Gem::Dependency
57
+ name: rspec
58
+ requirement: !ruby/object:Gem::Requirement
59
+ requirements:
60
+ - - "~>"
61
+ - !ruby/object:Gem::Version
62
+ version: 3.2.0
63
+ type: :development
64
+ prerelease: false
65
+ version_requirements: !ruby/object:Gem::Requirement
66
+ requirements:
67
+ - - "~>"
68
+ - !ruby/object:Gem::Version
69
+ version: 3.2.0
70
+ - !ruby/object:Gem::Dependency
71
+ name: ffaker
72
+ requirement: !ruby/object:Gem::Requirement
73
+ requirements:
74
+ - - ">="
75
+ - !ruby/object:Gem::Version
76
+ version: 2.0.0
77
+ type: :development
78
+ prerelease: false
79
+ version_requirements: !ruby/object:Gem::Requirement
80
+ requirements:
81
+ - - ">="
82
+ - !ruby/object:Gem::Version
83
+ version: 2.0.0
84
+ - !ruby/object:Gem::Dependency
85
+ name: fabrication
86
+ requirement: !ruby/object:Gem::Requirement
87
+ requirements:
88
+ - - "~>"
89
+ - !ruby/object:Gem::Version
90
+ version: 2.12.2
91
+ type: :development
92
+ prerelease: false
93
+ version_requirements: !ruby/object:Gem::Requirement
94
+ requirements:
95
+ - - "~>"
96
+ - !ruby/object:Gem::Version
97
+ version: 2.12.2
98
+ - !ruby/object:Gem::Dependency
99
+ name: pry
100
+ requirement: !ruby/object:Gem::Requirement
101
+ requirements:
102
+ - - ">="
103
+ - !ruby/object:Gem::Version
104
+ version: '0'
105
+ type: :development
106
+ prerelease: false
107
+ version_requirements: !ruby/object:Gem::Requirement
108
+ requirements:
109
+ - - ">="
110
+ - !ruby/object:Gem::Version
111
+ version: '0'
112
+ - !ruby/object:Gem::Dependency
113
+ name: pry-nav
114
+ requirement: !ruby/object:Gem::Requirement
115
+ requirements:
116
+ - - ">="
117
+ - !ruby/object:Gem::Version
118
+ version: '0'
119
+ type: :development
120
+ prerelease: false
121
+ version_requirements: !ruby/object:Gem::Requirement
122
+ requirements:
123
+ - - ">="
124
+ - !ruby/object:Gem::Version
125
+ version: '0'
126
+ - !ruby/object:Gem::Dependency
127
+ name: webmock
128
+ requirement: !ruby/object:Gem::Requirement
129
+ requirements:
130
+ - - ">="
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ type: :development
134
+ prerelease: false
135
+ version_requirements: !ruby/object:Gem::Requirement
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ version: '0'
140
+ - !ruby/object:Gem::Dependency
141
+ name: guard
142
+ requirement: !ruby/object:Gem::Requirement
143
+ requirements:
144
+ - - ">="
145
+ - !ruby/object:Gem::Version
146
+ version: '0'
147
+ type: :development
148
+ prerelease: false
149
+ version_requirements: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ - !ruby/object:Gem::Dependency
155
+ name: guard-rspec
156
+ requirement: !ruby/object:Gem::Requirement
157
+ requirements:
158
+ - - ">="
159
+ - !ruby/object:Gem::Version
160
+ version: '0'
161
+ type: :development
162
+ prerelease: false
163
+ version_requirements: !ruby/object:Gem::Requirement
164
+ requirements:
165
+ - - ">="
166
+ - !ruby/object:Gem::Version
167
+ version: '0'
168
+ - !ruby/object:Gem::Dependency
169
+ name: guard-ctags-bundler
170
+ requirement: !ruby/object:Gem::Requirement
171
+ requirements:
172
+ - - ">="
173
+ - !ruby/object:Gem::Version
174
+ version: '0'
175
+ type: :development
176
+ prerelease: false
177
+ version_requirements: !ruby/object:Gem::Requirement
178
+ requirements:
179
+ - - ">="
180
+ - !ruby/object:Gem::Version
181
+ version: '0'
182
+ - !ruby/object:Gem::Dependency
183
+ name: yard
184
+ requirement: !ruby/object:Gem::Requirement
185
+ requirements:
186
+ - - ">="
187
+ - !ruby/object:Gem::Version
188
+ version: '0'
189
+ type: :development
190
+ prerelease: false
191
+ version_requirements: !ruby/object:Gem::Requirement
192
+ requirements:
193
+ - - ">="
194
+ - !ruby/object:Gem::Version
195
+ version: '0'
196
+ description: "Клиент для приема оплаты через walletone.com"
197
+ email:
198
+ - danil@brandymint.ru
199
+ - i@artemeff.com
200
+ executables: []
201
+ extensions: []
202
+ extra_rdoc_files: []
203
+ files:
204
+ - LICENSE
205
+ - README.md
206
+ - lib/walletone.rb
207
+ - lib/walletone/errors/bad_url_error.rb
208
+ - lib/walletone/errors/walletone_error.rb
209
+ - lib/walletone/fields.rb
210
+ - lib/walletone/form.rb
211
+ - lib/walletone/middleware.rb
212
+ - lib/walletone/middleware/base.rb
213
+ - lib/walletone/middleware/callback.rb
214
+ - lib/walletone/notification.rb
215
+ - lib/walletone/payment.rb
216
+ - lib/walletone/signer.rb
217
+ - lib/walletone/version.rb
218
+ - spec/fabricators/fields_fabricator.rb
219
+ - spec/fabricators/payment_fabricator.rb
220
+ - spec/spec_helper.rb
221
+ - spec/walletone/fields_spec.rb
222
+ - spec/walletone/form_spec.rb
223
+ - spec/walletone/middleware/base_spec.rb
224
+ - spec/walletone/middleware/callback_spec.rb
225
+ - spec/walletone/notification_spec.rb
226
+ - spec/walletone/payment_spec.rb
227
+ - spec/walletone/signer_spec.rb
228
+ - spec/walletone_spec.rb
229
+ - walletone.gemspec
230
+ homepage: https://github.com/BrandyMint/walletone
231
+ licenses:
232
+ - MIT
233
+ metadata: {}
234
+ post_install_message:
235
+ rdoc_options: []
236
+ require_paths:
237
+ - lib
238
+ required_ruby_version: !ruby/object:Gem::Requirement
239
+ requirements:
240
+ - - ">="
241
+ - !ruby/object:Gem::Version
242
+ version: '0'
243
+ required_rubygems_version: !ruby/object:Gem::Requirement
244
+ requirements:
245
+ - - ">="
246
+ - !ruby/object:Gem::Version
247
+ version: '0'
248
+ requirements: []
249
+ rubyforge_project:
250
+ rubygems_version: 2.2.2
251
+ signing_key:
252
+ specification_version: 4
253
+ summary: walleton.com Checkout client
254
+ test_files:
255
+ - spec/walletone/payment_spec.rb
256
+ - spec/walletone/fields_spec.rb
257
+ - spec/walletone/form_spec.rb
258
+ - spec/walletone/notification_spec.rb
259
+ - spec/walletone/middleware/callback_spec.rb
260
+ - spec/walletone/middleware/base_spec.rb
261
+ - spec/walletone/signer_spec.rb
262
+ - spec/spec_helper.rb
263
+ - spec/fabricators/payment_fabricator.rb
264
+ - spec/fabricators/fields_fabricator.rb
265
+ - spec/walletone_spec.rb
266
+ has_rdoc: