walletone 0.1.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.
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: