webpay_by 0.0.1
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 +7 -0
- data/MIT-LICENSE +20 -0
- data/README.md +111 -0
- data/Rakefile +2 -0
- data/lib/webpay_by.rb +11 -0
- data/lib/webpay_by/client.rb +47 -0
- data/lib/webpay_by/confirmation.rb +99 -0
- data/lib/webpay_by/confirmation_response.rb +71 -0
- data/lib/webpay_by/form.rb +74 -0
- data/lib/webpay_by/item.rb +22 -0
- data/lib/webpay_by/request.rb +62 -0
- data/lib/webpay_by/response.rb +65 -0
- data/lib/webpay_by/version.rb +3 -0
- data/spec/models/webpay_by_spec.rb +83 -0
- data/spec/spec_helper.rb +17 -0
- metadata +101 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: e34e902b083183cc3d8ad2c2f136e8afdcc7e239
|
4
|
+
data.tar.gz: c90fa63b5585bf32fc8915dae5162876ff88060d
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3dcdbf2f33cade320a1fedadf67e30f77a713dd451fa5a3364afb1b3657b5676854413231e77c390ea34b4411cdff8756e0d448845c01b26a733e3554f46f9e9
|
7
|
+
data.tar.gz: fb83f7a6ae7fb81367c3232ce876b160a6da62efab7c9cd220a00d89405401e4f33003161f39423d4489c434f210828718b48233ab76f987d643b4ba5c17968f
|
data/MIT-LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright 2018 Bexeiitov Nursultan
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,111 @@
|
|
1
|
+
# Гем для работы с webpay.by
|
2
|
+
|
3
|
+
Гем для работы с платежной системой WebPay для использования в проектах, использующих Ruby (Ruby On Rails, Sinatra и др.).
|
4
|
+
|
5
|
+
WebPay («Вебпей») – белорусская система электронных платежей компании ООО «ВЭБ ПЭЙ»,
|
6
|
+
который позволяет осуществлять безопасные платежи при помощи банковских карт VISA и MasterCard
|
7
|
+
в режиме реального времени в любой валюте (BYN, USD, RUB, EUR и т.д.).
|
8
|
+
|
9
|
+
WebPay™ Sandbox — это самостоятельное Web-приложение, являющееся прототипом реальной системы и предназначенное для
|
10
|
+
тестирования и ознакомления с возможностями реальной системы WebPay™.
|
11
|
+
|
12
|
+
Официальный сайт: https://webpay.by
|
13
|
+
|
14
|
+
Документация: https://webpay.by/wp-content/uploads/2016/08/WebPay-Developer-Guide-2.1.2_RU.pdf
|
15
|
+
|
16
|
+
## Установка
|
17
|
+
|
18
|
+
Добавьте эту строку в ваш Gemfile:
|
19
|
+
|
20
|
+
gem 'webpay_by'
|
21
|
+
|
22
|
+
Затем установите gem, используя bundler:
|
23
|
+
|
24
|
+
$ bundle
|
25
|
+
|
26
|
+
Или выполните команду:
|
27
|
+
|
28
|
+
$ gem install webpay_by
|
29
|
+
|
30
|
+
## Использование (примеры с использованием Ruby On Rails)
|
31
|
+
|
32
|
+
### Настройка
|
33
|
+
|
34
|
+
Создаем клиент для работы с Webpay
|
35
|
+
|
36
|
+
```ruby
|
37
|
+
require 'webpay_by/client'
|
38
|
+
|
39
|
+
webpay_client = WebpayBy::Client.new(
|
40
|
+
secret_key: 'your_secret_key',
|
41
|
+
billing_id: '000000001',
|
42
|
+
debug_mode: !Rails.env.production?,
|
43
|
+
login: 'your_login',
|
44
|
+
password: 'your_password'
|
45
|
+
)
|
46
|
+
|
47
|
+
Rails.application.config.webpay_by = webpay_client
|
48
|
+
```
|
49
|
+
|
50
|
+
Формируем заказ и создаем форму
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
request = Rails.application.config.webpay_by.request(
|
54
|
+
order_id: 'item-1',
|
55
|
+
seed: '12.12.2019',
|
56
|
+
back_url: product_url,
|
57
|
+
notify_url: payments_epay_url,
|
58
|
+
items: [{price: 100, name: 'Пополнение счёта', quantity: 1}]
|
59
|
+
)
|
60
|
+
|
61
|
+
@form = request.form
|
62
|
+
|
63
|
+
```
|
64
|
+
```slim
|
65
|
+
= form_tag @form.action_url, method: @form.request_method, id: 'epay-form', enctype: @form.enctype do
|
66
|
+
- @form.fields.each do |key, value|
|
67
|
+
= hidden_field_tag key, value
|
68
|
+
= submit_tag 'Перейти к пополнению'
|
69
|
+
```
|
70
|
+
Важно:
|
71
|
+
- В режиме разработки, после подтверждения формы оплаты, система может не принять запрос, ссылаясь на неправильный формат wsb_notify_url или wsb_return_url.
|
72
|
+
Это связано с тем, что система валидирует эти поля на реальные домены.
|
73
|
+
Локальный сервер localhost:3000 или адреса с доменными зонами .dev, .localhost и т.д работат не будут.
|
74
|
+
Поэтому перед созданием формы передайте в заказ параметры notify_url и back_url c валидными адресами.
|
75
|
+
- Если у вас в биллинг-аккаунте подключена возможность приема оплаты и через систему ЕРИП,
|
76
|
+
то при тестировании платежей максимальная длина имени счета (wsb_order_num) равна 10 символам.
|
77
|
+
В реальной среде размер этого поля может измениться в зависимости от ограничений, которые будут установлены системой ЕРИП.
|
78
|
+
|
79
|
+
После совершения удачного платежа, система WebPay отсылает специально сформированный POST-запрос по адресу,
|
80
|
+
указанному в поле wsb_notify_url Интернет-ресурса. В этом запросе содержится информация по платежу.
|
81
|
+
Полученную информацию Интернет-ресурс должен проверить в соответствии с требованиями выполнения заказа
|
82
|
+
и ответить на запрос кодом: "HTTP/1.0 200 OK".
|
83
|
+
|
84
|
+
|
85
|
+
```ruby
|
86
|
+
answer_hash = params.except(:controller, :action).to_unsafe_h.symbolize_keys
|
87
|
+
response = webpay_client.response answer_hash
|
88
|
+
|
89
|
+
if response.approved?
|
90
|
+
user.add_balance response.amount
|
91
|
+
end
|
92
|
+
|
93
|
+
render nothing: true, status: 200
|
94
|
+
```
|
95
|
+
|
96
|
+
Прежде чем доставить товар (оказать услугу), Интернет-ресурс обязан проверить совершенный покупателем платеж.
|
97
|
+
Что такое "подтверждение": когда человек ввел данные карты, нужная сумма только блокируется на ней. Чтобы она
|
98
|
+
реально списалась, мы должны сообщить системе Webpay, что услуга оказана и сумму можно списать. Это и есть подтверждение.
|
99
|
+
Его нужно делать автоматически, поэтому через cron или аналог надо запускать робота, который будет выбирать
|
100
|
+
оплаченные заявки и их подтверджать.
|
101
|
+
|
102
|
+
```ruby
|
103
|
+
webpay_client = Rails.application.config.webpay_by
|
104
|
+
confirmation = webpay_client.confirmation(transaction_id: transaction_id)
|
105
|
+
confirmation_response = confirmation.send
|
106
|
+
|
107
|
+
# Проверяем ответ от системы на подлинность электронной подписи и подтверждения об оплате
|
108
|
+
if confirmation_response.approved?
|
109
|
+
order.update(confirmed: true)
|
110
|
+
end
|
111
|
+
```
|
data/Rakefile
ADDED
data/lib/webpay_by.rb
ADDED
@@ -0,0 +1,11 @@
|
|
1
|
+
require 'webpay_by/version'
|
2
|
+
|
3
|
+
module WebpayBy
|
4
|
+
autoload :Client, 'webpay_by/client'
|
5
|
+
autoload :Confirmation, 'webpay_by/confirmation'
|
6
|
+
autoload :ConfirmationResponse, 'webpay_by/confirmation_response'
|
7
|
+
autoload :Form, 'webpay_by/form'
|
8
|
+
autoload :Item, 'webpay_by/item'
|
9
|
+
autoload :Request, 'webpay_by/request'
|
10
|
+
autoload :Response, 'webpay_by/response'
|
11
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# Клиент для взаимодействия с Webpay. Он предоставляет доступ ко всем возможностям системы онлайн-оплаты.
|
2
|
+
# Чтобы создать клиент, вам нужно настроить следующие поля:
|
3
|
+
# billing_id - уникальный идентификатор магазина.
|
4
|
+
# secret_key - секретный ключ, необходим для формирования электронной подписи каждого Вашего платежа.
|
5
|
+
# debug_mode - поле, указывающее на проведение тестовой оплаты. Для работы с тестовой средой укажите true.
|
6
|
+
# login - имя пользователя.
|
7
|
+
# password - пароль.
|
8
|
+
#
|
9
|
+
# Пример:
|
10
|
+
#
|
11
|
+
# webpay_client = WebpayBy::Client.new(
|
12
|
+
# secret_key: 'your_secret_key',
|
13
|
+
# billing_id: '000000001',
|
14
|
+
# debug_mode: ENV.development?,
|
15
|
+
# login: 'your_login',
|
16
|
+
# password: 'your_password'
|
17
|
+
# )
|
18
|
+
#
|
19
|
+
require 'digest'
|
20
|
+
|
21
|
+
module WebpayBy
|
22
|
+
class Client
|
23
|
+
attr_reader :billing_id, :secret_key, :debug_mode, :login, :password
|
24
|
+
|
25
|
+
alias debug_mode? debug_mode
|
26
|
+
|
27
|
+
def initialize(billing_id:, secret_key:, debug_mode:, login:, password:)
|
28
|
+
@billing_id = billing_id
|
29
|
+
@secret_key = secret_key
|
30
|
+
@debug_mode = debug_mode
|
31
|
+
@login = login
|
32
|
+
@password = Digest::MD5.hexdigest password
|
33
|
+
end
|
34
|
+
|
35
|
+
def request(options = {})
|
36
|
+
WebpayBy::Request.new options.merge client: self
|
37
|
+
end
|
38
|
+
|
39
|
+
def response(options = {})
|
40
|
+
WebpayBy::Response.new options.merge client: self
|
41
|
+
end
|
42
|
+
|
43
|
+
def confirmation(options)
|
44
|
+
WebpayBy::Confirmation.new options.merge client: self
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,99 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Модель для создания запроса об подтверждении оплаты системе Webpay. Перед созданием заказа обязательно создайте клиента.
|
4
|
+
# Прежде чем доставить товар (оказать услугу), Интернет-ресурс обязан проверить совершенный покупателем платеж.
|
5
|
+
# Необходимо учитывать, что запрос к тестовой среде необходимо отсылать на адрес https://sandbox.webpay.by, а к реальной среде https://billing.webpay.by
|
6
|
+
#
|
7
|
+
# Пример:
|
8
|
+
#
|
9
|
+
# Создаем объект и передаем ему номер транзакции
|
10
|
+
# confirmation = webpay_client.confirmation(transaction_id: 'item-1')
|
11
|
+
#
|
12
|
+
# Создаем пост запрос к банку
|
13
|
+
# confirmation.send
|
14
|
+
# метод send возвращает объект WebpayBy::ConfirmationResponse, который содержить методы для проверки электронной подписи
|
15
|
+
# и подтверждения об оплате
|
16
|
+
#
|
17
|
+
require 'uri'
|
18
|
+
require 'net/https'
|
19
|
+
require 'openssl'
|
20
|
+
|
21
|
+
module WebpayBy
|
22
|
+
class Confirmation
|
23
|
+
SANDBOX_URL = 'https://sandbox.webpay.by'
|
24
|
+
BILLING_URL = 'https://billing.webpay.by'
|
25
|
+
|
26
|
+
attr_reader :client, :transaction_id
|
27
|
+
|
28
|
+
def initialize(client:, transaction_id:)
|
29
|
+
@client = client
|
30
|
+
@transaction_id = transaction_id
|
31
|
+
end
|
32
|
+
|
33
|
+
def url_string
|
34
|
+
@client.debug_mode? ? SANDBOX_URL : BILLING_URL
|
35
|
+
end
|
36
|
+
|
37
|
+
# ОТВЕТ ЗА ЗАПРОС НА ПОДТВЕРЖДЕНИЕ: Возвращаемый XML выглядит примерно так (без переносов строк):
|
38
|
+
# <?xml version="1.0" encoding="UTF-8"?>
|
39
|
+
# <wsb_api_response>
|
40
|
+
# <version>1</version>
|
41
|
+
# <command>get_transaction</command>
|
42
|
+
# <status>success</status>
|
43
|
+
# <fields>
|
44
|
+
# <transaction_id>123456789</transaction_id>
|
45
|
+
# <batch_timestamp>31231231</batch_timestamp>
|
46
|
+
# <currency_id>BYN</currency_id>
|
47
|
+
# <amount>100</amount>
|
48
|
+
# <payment_method>cc</payment_method>
|
49
|
+
# <payment_type>4</payment_type>
|
50
|
+
# <order_id>584236984</order_id>
|
51
|
+
# <order_num>5874129</order_num>
|
52
|
+
# <rrn>154789648154</rrn>
|
53
|
+
# <wsb_signature>3021e68df9a7200135725c6331369a22</wsb_signature>
|
54
|
+
# </fields>
|
55
|
+
# </wsb_api_response>
|
56
|
+
def send
|
57
|
+
xml = clear_xml_string api_request_xml
|
58
|
+
response = Net::HTTP.post_form(URI.parse(url_string), {'*API': '', 'API_XML_REQUEST': xml}).body
|
59
|
+
WebpayBy::ConfirmationResponse.new confirmation: self, response: response
|
60
|
+
end
|
61
|
+
|
62
|
+
def clear_xml_string(xml_str)
|
63
|
+
xml_str.gsub("\n", '').gsub(/\s{2,}/, '').gsub(' <', '<')
|
64
|
+
end
|
65
|
+
|
66
|
+
private
|
67
|
+
|
68
|
+
# Для проверки платежа при возврате на страницу Интернет-ресурса, указанному в поле wsb_return_url, необходимо выполнить
|
69
|
+
# API команду биллинга «get_transaction»
|
70
|
+
# Сгенерированный XML выглядит примерно так:
|
71
|
+
# <?xml version="1.0" encoding="ISO-8859-1"?>
|
72
|
+
# <wsb_api_request>
|
73
|
+
# <command>get_transaction</command>
|
74
|
+
# <authorization>
|
75
|
+
# <username>your_username</username>
|
76
|
+
# <password>your_md5_password</password>
|
77
|
+
# </authorization>
|
78
|
+
# <fields>
|
79
|
+
# <transaction_id>123456789</transaction_id>
|
80
|
+
# </fields>
|
81
|
+
# </wsb_api_request>
|
82
|
+
def api_request_xml
|
83
|
+
xml = Builder::XmlMarkup.new indent: 2
|
84
|
+
xml.instruct! :xml, encoding: 'ISO-8859-1'
|
85
|
+
xml.wsb_api_request do |req|
|
86
|
+
req.command 'get_transaction'
|
87
|
+
|
88
|
+
req.authorization do |auth|
|
89
|
+
auth.username @client.login
|
90
|
+
auth.password @client.password
|
91
|
+
end
|
92
|
+
|
93
|
+
req.fields do |merch|
|
94
|
+
merch.transaction_id @transaction_id
|
95
|
+
end
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
@@ -0,0 +1,71 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Модель для подтверждении оплаты системе Webpay.
|
4
|
+
# Пример:
|
5
|
+
#
|
6
|
+
# Создаем объект и передаем ему номер транзакции
|
7
|
+
# confirmation = webpay_client.confirmation(transaction_id: 'item-1')
|
8
|
+
#
|
9
|
+
# Создаем пост запрос к банку
|
10
|
+
# confirmation_response = confirmation.send
|
11
|
+
#
|
12
|
+
# Проверяем ответ от системы на подлинность электронной подписи и подтверждения об оплате
|
13
|
+
#
|
14
|
+
require 'digest'
|
15
|
+
require 'active_support/core_ext/hash'
|
16
|
+
|
17
|
+
module WebpayBy
|
18
|
+
class ConfirmationResponse
|
19
|
+
PAYMENT_TYPES = {
|
20
|
+
'Completed': 'Завершенная',
|
21
|
+
'Declined': 'Отклоненная',
|
22
|
+
'Pending': 'Вобработке',
|
23
|
+
'Authorized': 'Авторизованная',
|
24
|
+
'Refunded': 'Возвращенная',
|
25
|
+
'System': 'Системная',
|
26
|
+
'Voided': 'Сброшенная после авторизации',
|
27
|
+
'Failed': 'Ошибка в проведении операции',
|
28
|
+
'Partial Voided': 'Частичный сброс',
|
29
|
+
'Recurrent': 'Рекуррентный платеж'
|
30
|
+
}
|
31
|
+
|
32
|
+
# Успешной оплате соответствуют следующие значения: Completed, Authorized, Recurrent
|
33
|
+
# Ниже храним в константе их типы
|
34
|
+
SUCCESSFUL_TYPE_INDEXES = %w( 1 4 10 )
|
35
|
+
|
36
|
+
attr_reader :confirmation, :response, :parsed_response
|
37
|
+
|
38
|
+
def initialize(confirmation:, response:)
|
39
|
+
@confirmation = confirmation
|
40
|
+
@response = response
|
41
|
+
@parsed_response = Hash.from_xml(@response).deep_symbolize_keys rescue {}
|
42
|
+
end
|
43
|
+
|
44
|
+
def wsb_api_response
|
45
|
+
@parsed_response[:wsb_api_response] || {}
|
46
|
+
end
|
47
|
+
|
48
|
+
def response_fields
|
49
|
+
wsb_api_response[:fields] || {}
|
50
|
+
end
|
51
|
+
|
52
|
+
# Проверка аутентичности XML-документа, пришедшего от банка
|
53
|
+
def valid_signature?
|
54
|
+
return false unless response_fields.any?
|
55
|
+
|
56
|
+
signature == response_fields[:wsb_signature]
|
57
|
+
end
|
58
|
+
|
59
|
+
def signature
|
60
|
+
signed_fields = %i( transaction_id batch_timestamp currency_id amount payment_method payment_type order_id rrn )
|
61
|
+
signed_attrs = response_fields.values_at *signed_fields
|
62
|
+
signed_attrs << @confirmation.client.secret_key
|
63
|
+
Digest::MD5.hexdigest signed_attrs.join
|
64
|
+
end
|
65
|
+
|
66
|
+
def approved?
|
67
|
+
payment_type_index = response_fields[:payment_type]
|
68
|
+
valid_signature? && payment_type_index.in?(SUCCESSFUL_TYPE_INDEXES)
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Модель для формирования формы оплаты. Перед созданием заказа обязательно создайте заказ(Wepbay::Request).
|
4
|
+
# Для оплаты заказа необходимо сформировать форму со специальными полями, и POST методом перенаправить покупателя на страницу оплаты.
|
5
|
+
# Для тестирования необходимо указать адрес https://securesandbox.webpay.by, для совершения реальных платежей − https://payment.webpay.by.
|
6
|
+
#
|
7
|
+
# Все необходимые поля уже настроены в объекте заказа. Дополнительно можно настроить следующие поля:
|
8
|
+
# language_id - идентификатор языка формы оплаты. По умолчанию russian.
|
9
|
+
#
|
10
|
+
# Пример:
|
11
|
+
#
|
12
|
+
# request = webpay_client.request(
|
13
|
+
# order_id: 'item-1',
|
14
|
+
# seed: '12.12.2019',
|
15
|
+
# back_url: product_url,
|
16
|
+
# notify_url: payments_epay_url,
|
17
|
+
# items: [{price: 100, name: 'Пополнение счёта', quantity: 1}]
|
18
|
+
# )
|
19
|
+
#
|
20
|
+
# form = request.form(language_id: 'english')
|
21
|
+
#
|
22
|
+
module WebpayBy
|
23
|
+
class Form
|
24
|
+
SANDBOX_URL = 'https://securesandbox.webpay.by'
|
25
|
+
LIVE_URL = 'https://payment.webpay.by'
|
26
|
+
APP_VERSION = 2
|
27
|
+
LANGUAGE_LIST = %w( russian english )
|
28
|
+
REQUEST_METHOD = 'post'
|
29
|
+
ENCTYPE = 'application/x-www-form-urlencoded'
|
30
|
+
|
31
|
+
attr_reader :request, :version, :language_id, :request_method, :enctype
|
32
|
+
|
33
|
+
def initialize(request:, language_id: nil)
|
34
|
+
@request = request
|
35
|
+
@language_id ||= LANGUAGE_LIST.first
|
36
|
+
@version = APP_VERSION
|
37
|
+
@request_method = REQUEST_METHOD
|
38
|
+
@enctype = ENCTYPE
|
39
|
+
|
40
|
+
raise "Unsupported language, must be one of #{LANGUAGE_LIST.join ', '}" unless LANGUAGE_LIST.include? @language_id
|
41
|
+
end
|
42
|
+
|
43
|
+
def action_url
|
44
|
+
@request.client.debug_mode? ? SANDBOX_URL : LIVE_URL
|
45
|
+
end
|
46
|
+
|
47
|
+
def fields
|
48
|
+
fields_with_values = {
|
49
|
+
'*scart': '',
|
50
|
+
wsb_version: @version,
|
51
|
+
wsb_language_id: @language_id,
|
52
|
+
wsb_storeid: @request.client.billing_id,
|
53
|
+
wsb_order_num: @request.order_id,
|
54
|
+
wsb_test: @request.test_mode,
|
55
|
+
wsb_currency_id: @request.currency_id,
|
56
|
+
wsb_seed: @request.seed,
|
57
|
+
wsb_total: @request.total,
|
58
|
+
wsb_signature: @request.signature,
|
59
|
+
wsb_return_url: @request.back_url,
|
60
|
+
wsb_notify_url: @request.notify_url
|
61
|
+
}
|
62
|
+
|
63
|
+
@request.items.each_with_index do |item, i|
|
64
|
+
fields_with_values.merge!(
|
65
|
+
"wsb_invoice_item_name[#{i}]": item.name,
|
66
|
+
"wsb_invoice_item_quantity[#{i}]": item.quantity,
|
67
|
+
"wsb_invoice_item_price[#{i}]": item.price
|
68
|
+
)
|
69
|
+
end
|
70
|
+
|
71
|
+
fields_with_values.compact
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# Модель для создания товара для заказа.
|
2
|
+
# Чтобы создать товар, вам нужно настроить следующие поля:
|
3
|
+
# name - Наименование единицы товара.
|
4
|
+
# quantity - Количество единиц товара, целое число, обозначающее, количество единиц товара каждого наименования.
|
5
|
+
# price - Цена единицы товара, число, определяющее стоимость каждой единицы товара (BYN, USD, EUR, RUB с 2 знаками после запятой или точки).
|
6
|
+
#
|
7
|
+
# Пример:
|
8
|
+
#
|
9
|
+
# WebpayBy::Item.new price: 100, name: 'Пополнение счёта', quantity: 1
|
10
|
+
#
|
11
|
+
module WebpayBy
|
12
|
+
class Item
|
13
|
+
attr_reader :name, :quantity, :price, :total
|
14
|
+
|
15
|
+
def initialize(name:, quantity:, price:)
|
16
|
+
@price = price
|
17
|
+
@quantity = quantity
|
18
|
+
@name = name
|
19
|
+
@total = @quantity * @price
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# Модель для формирования заказа. Перед созданием заказа обязательно создайте клиента.
|
4
|
+
# Чтобы создать заказ, вам нужно настроить следующие поля:
|
5
|
+
# seed - случайная последовательность символов , участвующих в формировании электронной подписи заказа.
|
6
|
+
# order_id - уникальный идентификатор заказа, присваиваемый магазином.
|
7
|
+
# back_url - URL адрес на который возвращается покупатель в случае успешной оплаты.
|
8
|
+
# notify_url - данный URL вызывается вне зависимости от того, был ли переход по URL в поле wsb_return_url или нет.
|
9
|
+
# items - список товаров к оплате.
|
10
|
+
# currency_id - идентификатор валюты. Буквенный трехзначный код валюты согласно ISO4271. По умолчанию BYN(белорусский рубль).
|
11
|
+
#
|
12
|
+
# Пример:
|
13
|
+
#
|
14
|
+
# request = webpay_client.request(
|
15
|
+
# order_id: 'item-1',
|
16
|
+
# seed: '12.12.2019',
|
17
|
+
# back_url: product_url,
|
18
|
+
# notify_url: payments_epay_url,
|
19
|
+
# items: [{price: 100, name: 'Пополнение счёта', quantity: 1}]
|
20
|
+
# )
|
21
|
+
#
|
22
|
+
require 'digest'
|
23
|
+
|
24
|
+
module WebpayBy
|
25
|
+
class Request
|
26
|
+
CURRENCIES = %w( BYN USD EUR RUB )
|
27
|
+
|
28
|
+
attr_reader :client, :seed, :order_id, :currency_id, :back_url, :notify_url, :items
|
29
|
+
|
30
|
+
def initialize(client:, seed:, order_id:, back_url:, notify_url:, items:, currency_id: nil)
|
31
|
+
@client = client
|
32
|
+
@order_id = order_id
|
33
|
+
@seed = seed
|
34
|
+
@currency_id ||= CURRENCIES.first
|
35
|
+
@back_url = back_url
|
36
|
+
@notify_url = notify_url
|
37
|
+
@items = items.map { |item| item.is_a?(WebpayBy::Item) ? item : WebpayBy::Item.new(item) }
|
38
|
+
|
39
|
+
raise "Unsupported currency, must be one of #{CURRENCIES.join ', '}" unless CURRENCIES.include? @currency_id
|
40
|
+
end
|
41
|
+
|
42
|
+
def total
|
43
|
+
@items.map(&:total).sum
|
44
|
+
end
|
45
|
+
|
46
|
+
def test_mode
|
47
|
+
@client.debug_mode? ? 1 : 0
|
48
|
+
end
|
49
|
+
|
50
|
+
# Электронная подпись формируется для предотвращения изменений в форме платежа и должна присутствовать в каждой форме заказа.
|
51
|
+
# Все заказы без электронной подписи не будут рассматриваться системой WebPay
|
52
|
+
def signature
|
53
|
+
signed_attrs = [@seed, @client.billing_id, @order_id, test_mode, @currency_id, total, @client.secret_key].join
|
54
|
+
|
55
|
+
Digest::SHA1.hexdigest signed_attrs
|
56
|
+
end
|
57
|
+
|
58
|
+
def form(options = {})
|
59
|
+
WebpayBy::Form.new options.merge request: self
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
#
|
3
|
+
# После совершения удачного платежа, система WebPay отсылает специально сформированный POST-запрос по адресу,
|
4
|
+
# указанному в поле wsb_notify_url Интернет-ресурса. В этом запросе содержится информация по платежу.
|
5
|
+
# Полученную информацию Интернет-ресурс должен проверить в соответствии с требованиями выполнения заказа
|
6
|
+
# и ответить на запрос кодом: "HTTP/1.0 200 OK".
|
7
|
+
# Это модель служит для работы с полученные от системы запросом. Перед созданием заказа обязательно создайте клиента.
|
8
|
+
# Поля содержащиеся в запросе:
|
9
|
+
# batch_timestamp - время совершения транзакции.
|
10
|
+
# currency_id - валюта транзакции.
|
11
|
+
# amount - сумма транзакции.
|
12
|
+
# payment_method - метод совершения транзакции.
|
13
|
+
# order_id - номер заказа в системе WebPay.
|
14
|
+
# site_order_id - номер (имя) заказа, присвоенное магазином.
|
15
|
+
# transaction_id - номер транзакции.
|
16
|
+
# payment_type - тип транзакции.
|
17
|
+
# rrn - номер транзакции в системе Visa/MasterCard.
|
18
|
+
# wsb_signature - электронная подпись.
|
19
|
+
#
|
20
|
+
# Пример:
|
21
|
+
#
|
22
|
+
# response = webpay_client.response request.params
|
23
|
+
# response.valid_signature?
|
24
|
+
#
|
25
|
+
require 'digest'
|
26
|
+
|
27
|
+
module WebpayBy
|
28
|
+
class Response
|
29
|
+
SUCCESSFUL_TYPE_INDEXES = %w( 1 4 )
|
30
|
+
|
31
|
+
attr_reader :client, :batch_timestamp, :currency_id, :amount, :payment_method, :order_id, :site_order_id,
|
32
|
+
:transaction_id, :payment_type, :rrn
|
33
|
+
|
34
|
+
def initialize(options = {})
|
35
|
+
@client = options[:client]
|
36
|
+
@batch_timestamp = options[:batch_timestamp]
|
37
|
+
@currency_id = options[:currency_id]
|
38
|
+
@amount = options[:amount]
|
39
|
+
@payment_method = options[:payment_method]
|
40
|
+
@order_id = options[:order_id]
|
41
|
+
@site_order_id = options[:site_order_id]
|
42
|
+
@transaction_id = options[:transaction_id]
|
43
|
+
@payment_type = options[:payment_type]
|
44
|
+
@rrn = options[:rrn]
|
45
|
+
@wsb_signature = options[:wsb_signature]
|
46
|
+
end
|
47
|
+
|
48
|
+
def approved?
|
49
|
+
valid_signature? && @payment_type.in?(SUCCESSFUL_TYPE_INDEXES)
|
50
|
+
end
|
51
|
+
|
52
|
+
# wsb_signature представляет собой hex-последовательность и является результатом выполнения функции MD 5.
|
53
|
+
# В качестве аргумента функции MD5 служит текстовая последовательность, полученная путем простой конкатенации
|
54
|
+
def valid_signature?
|
55
|
+
@wsb_signature.to_s == signature
|
56
|
+
end
|
57
|
+
|
58
|
+
def signature
|
59
|
+
signed_attrs = [@batch_timestamp, @currency_id, @amount, @payment_method, @order_id, @site_order_id,
|
60
|
+
@transaction_id, @payment_type, @rrn, @client.secret_key].join
|
61
|
+
|
62
|
+
Digest::MD5.hexdigest signed_attrs
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require './lib/webpay_by'
|
3
|
+
|
4
|
+
describe WebpayBy::Client do
|
5
|
+
let(:webpay_client) do
|
6
|
+
WebpayBy::Client.new(
|
7
|
+
secret_key: 'your_secret_key',
|
8
|
+
billing_id: '000000001',
|
9
|
+
debug_mode: true,
|
10
|
+
login: 'your_login',
|
11
|
+
password: 'your_password'
|
12
|
+
)
|
13
|
+
end
|
14
|
+
|
15
|
+
describe 'Формирования заказа' do
|
16
|
+
it 'сгенерирует правильную электронную подпись' do
|
17
|
+
request = webpay_client.request(
|
18
|
+
order_id: 'item-1',
|
19
|
+
seed: '12.12.2019',
|
20
|
+
back_url: 'test.com',
|
21
|
+
notify_url: 'test.com/notify',
|
22
|
+
items: [{price: 100, name: 'Пополнение счёта', quantity: 1}]
|
23
|
+
)
|
24
|
+
|
25
|
+
# seed='12.12.2019' billing_id='000000001' order_id='item-1' test_mode=1 currency_id'BYN'
|
26
|
+
# amount=100 secret_key='your_secret_key'
|
27
|
+
valid_signature = '42fc5bd5f26360789488073d2d8e535f9e16e2f9'
|
28
|
+
|
29
|
+
expect(request.signature).to eq valid_signature
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
describe 'Ответ банка' do
|
34
|
+
it 'возвращает валидную электронную подпись' do
|
35
|
+
answer_params = {
|
36
|
+
batch_timestamp: '10.10.2019',
|
37
|
+
currency_id: 'BYN',
|
38
|
+
amount: '100',
|
39
|
+
payment_method: 'cc',
|
40
|
+
order_id: '1111',
|
41
|
+
site_order_id: 'item-1',
|
42
|
+
transaction_id: '1',
|
43
|
+
payment_type: '4',
|
44
|
+
rrn: '01',
|
45
|
+
wsb_signature: '62a2ab40e7ae1630883e1f8e2f284f8a'
|
46
|
+
}
|
47
|
+
|
48
|
+
response = webpay_client.response answer_params
|
49
|
+
|
50
|
+
expect(response.signature).to eq answer_params[:wsb_signature]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
describe 'Подтверждения об оплате банку' do
|
55
|
+
it 'ответ возвращает валидную электронную подпись' do
|
56
|
+
xml_response = <<-XML.gsub("\n", '').gsub(/\s{2,}/, '').gsub(' <', '<')
|
57
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
58
|
+
<wsb_api_response>
|
59
|
+
<version>1</version>
|
60
|
+
<command>get_transaction</command>
|
61
|
+
<status>success</status>
|
62
|
+
<fields>
|
63
|
+
<transaction_id>1</transaction_id>
|
64
|
+
<batch_timestamp>31231231</batch_timestamp>
|
65
|
+
<currency_id>BYN</currency_id>
|
66
|
+
<amount>100</amount>
|
67
|
+
<payment_method>cc</payment_method>
|
68
|
+
<payment_type>4</payment_type>
|
69
|
+
<order_id>item-1</order_id>
|
70
|
+
<order_num>1111</order_num>
|
71
|
+
<rrn>1</rrn>
|
72
|
+
<wsb_signature>7f0276873ce8e701c6fe36912fe5fb33</wsb_signature>
|
73
|
+
</fields>
|
74
|
+
</wsb_api_response>
|
75
|
+
XML
|
76
|
+
|
77
|
+
confirmation = webpay_client.confirmation(transaction_id: '1')
|
78
|
+
confirmation_response = WebpayBy::ConfirmationResponse.new(confirmation: confirmation, response: xml_response)
|
79
|
+
|
80
|
+
expect(confirmation_response.signature).to eq confirmation_response.response_fields[:wsb_signature]
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
# This file was generated by the `rspec --init` command. Conventionally, all
|
2
|
+
# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`.
|
3
|
+
# Require this file using `require "spec_helper"` to ensure that it is only
|
4
|
+
# loaded once.
|
5
|
+
#
|
6
|
+
# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration
|
7
|
+
RSpec.configure do |config|
|
8
|
+
config.treat_symbols_as_metadata_keys_with_true_values = true
|
9
|
+
config.run_all_when_everything_filtered = true
|
10
|
+
config.filter_run :focus
|
11
|
+
|
12
|
+
# Run specs in random order to surface order dependencies. If you find an
|
13
|
+
# order dependency and want to debug it, you can fix the order by providing
|
14
|
+
# the seed, which is printed after each run.
|
15
|
+
# --seed 1234
|
16
|
+
config.order = 'random'
|
17
|
+
end
|
metadata
ADDED
@@ -0,0 +1,101 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: webpay_by
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Bexeiitov Nursultan
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2019-01-18 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: activesupport
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
16
|
+
requirements:
|
17
|
+
- - ">="
|
18
|
+
- !ruby/object:Gem::Version
|
19
|
+
version: 4.1.0
|
20
|
+
type: :runtime
|
21
|
+
prerelease: false
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - ">="
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: 4.1.0
|
27
|
+
- !ruby/object:Gem::Dependency
|
28
|
+
name: builder
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
type: :runtime
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - ">="
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '2.0'
|
41
|
+
- !ruby/object:Gem::Dependency
|
42
|
+
name: rspec
|
43
|
+
requirement: !ruby/object:Gem::Requirement
|
44
|
+
requirements:
|
45
|
+
- - ">="
|
46
|
+
- !ruby/object:Gem::Version
|
47
|
+
version: '0'
|
48
|
+
type: :development
|
49
|
+
prerelease: false
|
50
|
+
version_requirements: !ruby/object:Gem::Requirement
|
51
|
+
requirements:
|
52
|
+
- - ">="
|
53
|
+
- !ruby/object:Gem::Version
|
54
|
+
version: '0'
|
55
|
+
description: Adds the ability to work with the payment system webpay.by.
|
56
|
+
email:
|
57
|
+
- bekseitov@mail.ru
|
58
|
+
executables: []
|
59
|
+
extensions: []
|
60
|
+
extra_rdoc_files: []
|
61
|
+
files:
|
62
|
+
- MIT-LICENSE
|
63
|
+
- README.md
|
64
|
+
- Rakefile
|
65
|
+
- lib/webpay_by.rb
|
66
|
+
- lib/webpay_by/client.rb
|
67
|
+
- lib/webpay_by/confirmation.rb
|
68
|
+
- lib/webpay_by/confirmation_response.rb
|
69
|
+
- lib/webpay_by/form.rb
|
70
|
+
- lib/webpay_by/item.rb
|
71
|
+
- lib/webpay_by/request.rb
|
72
|
+
- lib/webpay_by/response.rb
|
73
|
+
- lib/webpay_by/version.rb
|
74
|
+
- spec/models/webpay_by_spec.rb
|
75
|
+
- spec/spec_helper.rb
|
76
|
+
homepage: http://neoweb.kz
|
77
|
+
licenses: []
|
78
|
+
metadata: {}
|
79
|
+
post_install_message:
|
80
|
+
rdoc_options: []
|
81
|
+
require_paths:
|
82
|
+
- lib
|
83
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - ">="
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '2.3'
|
88
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
89
|
+
requirements:
|
90
|
+
- - ">="
|
91
|
+
- !ruby/object:Gem::Version
|
92
|
+
version: '0'
|
93
|
+
requirements: []
|
94
|
+
rubyforge_project:
|
95
|
+
rubygems_version: 2.6.12
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: webpay.by gem
|
99
|
+
test_files:
|
100
|
+
- spec/models/webpay_by_spec.rb
|
101
|
+
- spec/spec_helper.rb
|