pscb_integration 0.9.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: 2b0871fac5b4d141f49b797dd11cab1360ecb81d
4
+ data.tar.gz: 177540f31376ea3c4aad1e3000ffd62a5b0cef30
5
+ SHA512:
6
+ metadata.gz: 5a224d86da1138a74e4f24062071e204b5e54dc062220e9b93eea1a58c8fb03bf86e2e57133e6d439cc494ea195e7e947528a8f9557b4d5d117a89352111f16c
7
+ data.tar.gz: 4eb961dd75a139fb4a633927c94fe81b858c2018a1f93ab52e3b0bd2f8a472d958cf7d0a263943ab7d52ed4b1fa72289038e5c217eeda63d09b607b60d128fb2
data/.gitignore ADDED
@@ -0,0 +1,14 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /spec/dummy/tmp/
10
+ /spec/dummy/public/assets
11
+ /spec/dummy/.sass-cache
12
+ /spec/dummy/log/*.log
13
+ /tmp/
14
+ *.sqlite3
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format progress
2
+ --color
3
+ --require rails_helper
data/.travis.yml ADDED
@@ -0,0 +1,5 @@
1
+ sudo: false
2
+ language: ruby
3
+ rvm:
4
+ - 2.3.1
5
+ before_install: gem install bundler -v 1.13.7
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in pscb_integration.gemspec
4
+ gemspec
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2017 Alex Emelyanov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,209 @@
1
+ # PscbIntegration
2
+
3
+ PSCB bank payment service integration (trade acquiring). Official documentation http://docs.pscb.ru/oos/, offer https://pscb.ru/corp/services/payment_service/platezhi-v-internete/.
4
+
5
+ ## Disclamer
6
+
7
+ Stay awhile and listen. Are you sure that you need this stuff?
8
+
9
+ ### Pro:
10
+
11
+ * 3% fee for Visa/MasterCard payments
12
+
13
+ ### Cons:
14
+
15
+ * During 1.5 years of our expirience PSCB lost all client's reccurent bingings twice. We lost a lot of revenue because of that.
16
+ * Very slow personal account web site, it takes from several second to several tens of seconds to load a page.
17
+ * API looks not solid and have a lack of consistency, it has several ways to return errors in response
18
+ * PSCB send demo environment payment callbacks to your production server, and if you don't handle them, they send you email like 'We have got invalid response for our HTTP-callbacks', so they can't completely split demo and production environments
19
+
20
+ ### Your choise
21
+
22
+ It's up to you. Probably Yandex.Kassa will be better option, its [API](https://tech.yandex.ru/money/doc/payment-solution/shop-config/intro-docpage/) looks much more solid and reliable (it's Yandex anyway) but it has higher fee 3.5% on [base plan](https://kassa.yandex.ru/fees) for bank cards. But if you revenue more than 1 million RUB per month then only 2.8%. I suppose the choice is obvious here.
23
+
24
+
25
+ ## Installation
26
+
27
+ I see you decided to try. Good luck and best wishes.
28
+
29
+ Add this line to your application's Gemfile:
30
+
31
+ ```ruby
32
+ gem 'pscb_integration'
33
+ ```
34
+
35
+ And then execute:
36
+
37
+ $ bundle
38
+
39
+ ## Usage
40
+
41
+ ### Configuration
42
+
43
+ Configure it in `<you app folder>/config/initializers/pscb.rb` with:
44
+
45
+ ```ruby
46
+ PscbIntegration.setup do |config|
47
+ config.host = 'https://oos.pscb.ru'
48
+ config.market_place = '<your market place id>'
49
+ config.secret_key = '<your secret key>'
50
+ config.demo_secret_key = '<your secret key for demo env>'
51
+ config.confirm_payment_callback = PaymentService.method(:confirm_pscb_payment_callback)
52
+ end
53
+ ```
54
+
55
+ If your application didn't setup `PscbIntegration` configuration with `setup` block then `PscbIntegration::ConfigurationError` will be raised during first attempt to call of any method.
56
+
57
+ ### Handling payment status notification
58
+
59
+ Mount engine in your `routes.rb` file as you wish, e.g.:
60
+
61
+ ```ruby
62
+ namespace :integration_api do
63
+ mount PscbIntegration::Engine => '/'
64
+ end
65
+ ```
66
+
67
+ Implement callback function assigned to `confirm_pscb_payment_callback`:
68
+
69
+ Arguments:
70
+ `payment` - hash with payment details from PSCB:
71
+
72
+ Property | Description
73
+ -------------------------------------------------------
74
+ `orderId` | Unique order id generated by merchant
75
+ `showOrderId` | Not uniqe order id generated by merchant to show it to customer
76
+ `paymentId` | Order id generated by PSCB
77
+ `account` | Customer id on merchant side
78
+ `marketPlace` | Merchant id on PSCB side
79
+ `paymentMethod` | Payment metho
80
+ `state` | [Payment state](http://docs.pscb.ru/oos/api.html#api-dopolnitelnyh-vozmozhnostej-merchanta-sostoyaniya-platezha)
81
+ `stateDate` | Date of last state changing ISO8601
82
+ `amount` | Order amount
83
+ `recurrencyToken` | Recurrency token
84
+
85
+ `is_demo` - if `true` then payment is from demo environment else from production.
86
+
87
+ Callback should return `true` if you system accepts and confirms payment, and `false` (`nil`) in case of rejecting. Example:
88
+
89
+ ```ruby
90
+ def confirm_pscb_payment_callback(payment, is_demo)
91
+ if (order = OrderModel.find_by(uid: payment['orderId']))
92
+ # Some state machine transition
93
+ order.apply_status(payment['state'])
94
+ end
95
+ end
96
+ ```
97
+
98
+ ### Build payment url
99
+
100
+ [PSCB API documentation](http://docs.pscb.ru/oos/api.html#api-pskb-onlajn-dlya-merchantov-api-platezhnoj-stranicy)
101
+
102
+ ```ruby
103
+ client = PscbIntegration::Client.new
104
+
105
+ url = client.build_payment_url(
106
+ nonce: SecureRandom.hex(5), # Salt to avoid replay attack
107
+ customerAccount: user.id, # Some user id
108
+ customerRating: 5, # Customer rating
109
+ customerEmail: user.email, # Customer email
110
+ customerPhone: user.phone, # Customer phone
111
+ orderId: '123456', # Unique order id
112
+ details: 'Some paymnet', # Payment details comment
113
+ amount: 500, # Amount in RUB
114
+ paymentMethod: 'ac', # Payment menthod
115
+ recurrentable: true, # Payment can be repeated by merchant
116
+ data: {
117
+ debug: 1, # show debug info in customer browser
118
+ }
119
+ )
120
+ ```
121
+
122
+ ### Pull order status
123
+
124
+ [PSCB API documentation](http://docs.pscb.ru/oos/api.html#api-dopolnitelnyh-vozmozhnostej-merchanta-zapros-sostoyaniya-platezha)
125
+
126
+ `Client` methods return result in [Either monad](https://github.com/bolshakov/fear#either-documentation) for helping handling errors on different level. Thank you [@bolshakov](https://github.com/bolshakov).
127
+
128
+ How it works:
129
+
130
+ * `Right` result means success
131
+ * `Left` result means PSCB returns some conscious error which can require special handling on our side.
132
+ * any exception means unexpected error (e.g. timeout, network) that we don't know how to handle, and probably best option is to log it and try again later.
133
+
134
+ Learn more about [Either monad usage](https://github.com/bolshakov/fear#either-documentation). Example:
135
+
136
+ ```ruby
137
+ client = PscbIntegration::Client.new
138
+
139
+ res = client.pull_order_status(order.id)
140
+
141
+ res.reduce(
142
+ # Left result is handled here
143
+ # @param error - PscbIntegration::BaseApiError
144
+ ->(error) {
145
+ # Some special error handling e.g.
146
+ if error.unknown_payment?
147
+ # Do something special
148
+ end
149
+ },
150
+
151
+ # Right result is handled here
152
+ # @param payment - payment hash from PSCB
153
+ ->(payment) {
154
+ # Update order status
155
+ }
156
+ )
157
+ ```
158
+
159
+ ### Recurring payment
160
+
161
+ [PSCB API documentation](http://docs.pscb.ru/oos/api.html#api-dopolnitelnyh-vozmozhnostej-merchanta-iniciaciya-povtornoj-oplaty)
162
+
163
+ Before this call you customer should successefully paid order with `recurrentable` flag. In callback or through status pulling `recurrency_token` will be returned.
164
+
165
+ ```ruby
166
+ client = PscbIntegration::Client.new
167
+
168
+ res = client.recurring_payment(
169
+ prev_order_uid: prev_order.id, # Previous recurrentable order id
170
+ new_order_uid: new_order.id, # New order id
171
+ token: recurrency_token, # Recurrency token from previous order
172
+ amount: 300, # Amount in RUB
173
+ )
174
+
175
+ res.reduce(
176
+ ->(error) { },
177
+ ->(payment) { },
178
+ )
179
+ ```
180
+
181
+ ### Refund order
182
+
183
+ [PSCB API documentation](http://docs.pscb.ru/oos/api.html#api-dopolnitelnyh-vozmozhnostej-merchanta-vozvrat-po-platezhu)
184
+
185
+ ```ruby
186
+ client = PscbIntegration::Client.new
187
+
188
+ res = client.refund_order(order.id)
189
+
190
+ res.reduce(
191
+ ->(error) { },
192
+ ->(payment) { },
193
+ )
194
+ ```
195
+
196
+ ## Development
197
+
198
+ After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
199
+
200
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
201
+
202
+ ## Contributing
203
+
204
+ Bug reports and pull requests are welcome on GitHub at https://github.com/holyketzer/pscb_integration.
205
+
206
+
207
+ ## License
208
+
209
+ The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,6 @@
1
+ require "bundler/gem_tasks"
2
+ require "rspec/core/rake_task"
3
+
4
+ RSpec::Core::RakeTask.new(:spec)
5
+
6
+ task :default => :spec
@@ -0,0 +1,59 @@
1
+ require 'json'
2
+
3
+ module PscbIntegration
4
+ class CallbackController < ActionController::Base
5
+ def payment_statuses
6
+ encrypted_body = request.body.read
7
+ log("PSCB payment_statuses encrypted params '#{encrypted_body.unpack('H*').first}'")
8
+
9
+ body, is_demo_env = *decrypt(encrypted_body)
10
+ log("PSCB payment_statuses binary params '#{body.unpack('H*').first}' demo_env=#{is_demo_env}")
11
+
12
+ json = JSON.parse(body)
13
+ log("PSCB payment_statuses params #{json.inspect}")
14
+
15
+ response = json['payments'].map do |payment|
16
+ confirmed = config.confirm_payment_callback.call(payment, is_demo_env)
17
+
18
+ {
19
+ orderId: payment['orderId'],
20
+ action: confirmed ? 'CONFIRM' : 'REJECT'
21
+ }
22
+ end
23
+
24
+ log("PSCB payment_statuses response #{response.inspect}")
25
+
26
+ render json: { payments: response }
27
+ end
28
+
29
+ private
30
+
31
+ def decrypt(encrypted_body)
32
+ [
33
+ client.decrypt(encrypted_body),
34
+ false,
35
+ ]
36
+ rescue OpenSSL::Cipher::CipherError
37
+ log("PSCB payment_statuses decryption with production key is failed, trying with demo key")
38
+
39
+ [
40
+ client.decrypt(encrypted_body, demo: true),
41
+ true,
42
+ ]
43
+ end
44
+
45
+ def log(line)
46
+ if defined?(Rails)
47
+ Rails.logger.info(line)
48
+ end
49
+ end
50
+
51
+ def client
52
+ @client ||= Client.new
53
+ end
54
+
55
+ def config
56
+ PscbIntegration.config
57
+ end
58
+ end
59
+ end
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "pscb_integration"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
data/config/routes.rb ADDED
@@ -0,0 +1,3 @@
1
+ PscbIntegration::Engine.routes.draw do
2
+ post 'pscb/payment_statuses' => 'callback#payment_statuses'
3
+ end
@@ -0,0 +1,29 @@
1
+ require 'pscb_integration/version'
2
+ require 'pscb_integration/client'
3
+ require 'pscb_integration/config'
4
+ require 'pscb_integration/engine'
5
+
6
+ module PscbIntegration
7
+ ConfigurationError = Class.new(StandardError)
8
+
9
+ class << self
10
+ attr_accessor :config
11
+
12
+ def config
13
+ @config ||= begin
14
+ if @setup_block
15
+ config = Config.new
16
+ @setup_block.call(config)
17
+ config
18
+ else
19
+ raise ConfigurationError
20
+ end
21
+ end
22
+ end
23
+
24
+ # Gets called within the initializer
25
+ def setup(&block)
26
+ @setup_block = block
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,31 @@
1
+ module PscbIntegration
2
+ class ApiError < BaseApiError
3
+ ERROR_CODES = {
4
+ 'NOT_AUTHORIZED' => 'запрос не авторизован',
5
+ 'ILLEGAL_REQUEST' => 'некорректный запрос',
6
+ 'ILLEGAL_ARGUMENTS' => 'передан некорректный набор аргументов',
7
+ 'UNKNOWN_PAYMENT' => 'указанный платёж не обнаружен',
8
+ 'ILLEGAL_ACTION' => 'невозможно совершить требуемое действие',
9
+ 'ILLEGAL_PAYMENT_STATE' => 'невозможно совершить требуемое действие, т.к. платёж находится в неподходящем статусе',
10
+ 'FAILED' => 'невозможно совершить требуемое действие (причина в описании ошибки)',
11
+ 'REPEAT_REQUEST' => 'операция завершена с неопределённым результатом, требуется повторить запрос',
12
+ 'PROCESSING' => 'операция продолжается',
13
+ 'SERVER_ERROR' => 'произошла ошибка на сервере. При возникновении данной ошибки рекомендуется выполнить запрос состояния платежа, чтобы уточнить текущий статус платежа',
14
+ }.freeze
15
+
16
+ attr_reader :body
17
+
18
+ def initialize(error_code:, body: nil)
19
+ @error_code = error_code
20
+ @body = body
21
+ end
22
+
23
+ def to_s
24
+ "#{error_code} #{ERROR_CODES[error_code]} #{body}"
25
+ end
26
+
27
+ def unknown_payment?
28
+ 'UNKNOWN_PAYMENT' == error_code
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,34 @@
1
+ module PscbIntegration
2
+ class BaseApiError
3
+ ERROR_CODES = {
4
+ timeout: 'timeout error',
5
+ connection_failed: 'connection failed error',
6
+ }.freeze
7
+
8
+ attr_reader :error_code
9
+
10
+ def initialize(error_code)
11
+ @error_code = error_code
12
+ end
13
+
14
+ def to_s
15
+ ERROR_CODES[error_code]
16
+ end
17
+
18
+ def message
19
+ to_s
20
+ end
21
+
22
+ def timeout?
23
+ :timeout == error_code
24
+ end
25
+
26
+ def connection_failed?
27
+ :connection_failed == error_code
28
+ end
29
+
30
+ def unknown_payment?
31
+ false
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,140 @@
1
+ require 'base64'
2
+ require 'faraday'
3
+ require 'faraday_middleware'
4
+ require 'fear'
5
+ require 'pscb_integration/base_api_error'
6
+ require 'pscb_integration/api_error'
7
+ require 'pscb_integration/extended_api_error'
8
+
9
+ module PscbIntegration
10
+ class Client
11
+ include Fear::Either::Mixin
12
+
13
+ attr_reader :config
14
+
15
+ def initialize(explicit_config = nil)
16
+ @config = explicit_config || PscbIntegration.config
17
+
18
+ @client = Faraday.new(url: config.host) do |faraday|
19
+ faraday.request :json # form-encode POST params
20
+ faraday.response :json
21
+
22
+ if defined?(Rails)
23
+ faraday.response :logger, Rails.logger, bodies: true
24
+ end
25
+
26
+ faraday.adapter Faraday.default_adapter # make requests with Net::HTTP
27
+ end
28
+ end
29
+
30
+ def build_payment_url(message)
31
+ json = message.to_json
32
+
33
+ params = {
34
+ marketPlace: config.market_place,
35
+ message: Base64.urlsafe_encode64(json),
36
+ signature: signature(json),
37
+ }
38
+
39
+ @client.build_url('pay', params).to_s
40
+ end
41
+
42
+ def recurring_payment(prev_order_uid:, new_order_uid:, token:, amount:)
43
+ body = {
44
+ orderId: prev_order_uid,
45
+ newOrderId: new_order_uid,
46
+ marketPlace: config.market_place,
47
+ token: token,
48
+ amount: amount,
49
+ }.to_json
50
+
51
+ handle_response(
52
+ post('merchantApi/payRecurrent', body)
53
+ )
54
+ end
55
+
56
+ def pull_order_status(order_uid)
57
+ body = {
58
+ orderId: order_uid,
59
+ marketPlace: config.market_place,
60
+ }.to_json
61
+
62
+ handle_response(
63
+ post('merchantApi/checkPayment', body)
64
+ )
65
+ end
66
+
67
+ def refund_order(order_uid)
68
+ body = {
69
+ orderId: order_uid,
70
+ marketPlace: config.market_place,
71
+ partialRefund: false,
72
+ }.to_json
73
+
74
+ handle_response(
75
+ post('merchantApi/refundPayment', body)
76
+ )
77
+ end
78
+
79
+ def decrypt(encrypted, demo: false)
80
+ secret_key = demo ? config.demo_secret_key : config.secret_key
81
+
82
+ decipher = OpenSSL::Cipher::AES.new(128, :ECB)
83
+ decipher.decrypt
84
+ decipher.key = Digest::MD5.digest(secret_key.to_s)
85
+
86
+ plain = decipher.update(encrypted) + decipher.final
87
+ plain.force_encoding('utf-8')
88
+ end
89
+
90
+ def encrypt(plain)
91
+ cipher = OpenSSL::Cipher::AES.new(128, :ECB)
92
+ cipher.encrypt
93
+ cipher.key = Digest::MD5.digest(config.secret_key.to_s)
94
+
95
+ cipher.update(plain) + cipher.final
96
+ end
97
+
98
+ private
99
+
100
+ def signature(str)
101
+ Digest::SHA256.new.hexdigest(str + config.secret_key.to_s)
102
+ end
103
+
104
+ # @return [Either<Faraday::Response, BaseApiError>]
105
+ def post(path, body)
106
+ response = @client.post(path) do |request|
107
+ request.headers['Signature'] = signature(body)
108
+ request.body = body
109
+ end
110
+
111
+ Right(response.body)
112
+ rescue Faraday::TimeoutError
113
+ Left(BaseApiError.new(:timeout))
114
+ rescue Faraday::Error::ConnectionFailed
115
+ Left(BaseApiError.new(:connection_failed))
116
+ end
117
+
118
+ # @return [Either<Hash, BaseApiError>]
119
+ def handle_response(response_body)
120
+ response_body.flat_map do |body|
121
+ if body && body['status'] == 'STATUS_SUCCESS'
122
+ Right(body['payment'])
123
+ elsif body && (error_code = body['errorCode'])
124
+ Left(ApiError.new(error_code: error_code, body: body))
125
+ elsif body && (error = body['paymentSystemError'] || body.dig('payment', 'lastError'))
126
+ Left(
127
+ ExtendedApiError.new(
128
+ error_code: error['code'],
129
+ error_sub_code: error['subCode'],
130
+ details: error['details'],
131
+ body: body,
132
+ )
133
+ )
134
+ else
135
+ Left(ApiError.new(error_code: 'Payment system error', body: body))
136
+ end
137
+ end
138
+ end
139
+ end
140
+ end
@@ -0,0 +1,11 @@
1
+ module PscbIntegration
2
+ class Config
3
+ attr_accessor :host, :market_place, :secret_key, :demo_secret_key, :confirm_payment_callback
4
+
5
+ def initialize(attrs = {})
6
+ attrs.each do |name, value|
7
+ public_send("#{name}=", value)
8
+ end
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,5 @@
1
+ module PscbIntegration
2
+ class Engine < ::Rails::Engine
3
+ isolate_namespace PscbIntegration
4
+ end
5
+ end
@@ -0,0 +1,81 @@
1
+ module PscbIntegration
2
+ class ExtendedApiError < ApiError
3
+ ERROR_CODES = {
4
+ 0 => 'Платеж обработан успешно',
5
+ 1 => 'Платеж находится в обработке',
6
+ 2 => 'Платеж ожидает подтверждения одноразовым паролем',
7
+ 3 => 'Для завершения привязки рекуррентного платежа необходимо передать сумму, заблокированную на карте Клиента',
8
+ -1 => 'Транзакция отвергнута ПЦ Требуется анализ subCode',
9
+ -2 => 'Транзакция отвергнута СИСТЕМОЙ Требуется анализ subCode',
10
+ -3 => 'Неверные параметры платежа, платеж не прошел проверку у поставщика услуги',
11
+ -4 => 'Карта не привязана: возникает, если карта, с которой пытаются сделать оплату, не привязана к веб-кошельку или услуге, а это требуется, согласно настройке услуги',
12
+ -5 => 'Неизвестная ошибка, транзакция отвергнута',
13
+ -14 => 'Не верная SMS подтверждения платежа для Веб-кошелька',
14
+ -15 => 'Рекуррентные платежи не поддерживаются',
15
+ -16 => 'Некорректные параметры для рекуррентного платежа',
16
+ -17 => 'Подпись не верна',
17
+ -18 => 'Нарушение лимитов СИСТЕМЫ',
18
+ -19 => 'Попытка фрода',
19
+ }.freeze
20
+
21
+ ERROR_SUB_CODES = {
22
+ 100 => 'Сервис недоступен',
23
+ 101 => 'Регламентные работы',
24
+ 102 => 'Недоступен шлюз в МПС',
25
+ 103 => 'Технический сбой при обработке платежа, пользователь пытался задвоить транзакцию (нажал F5 в браузере)',
26
+ 104 => 'Технический сбой при обработке платежа, разрушилась сессия на веб-сервере',
27
+ 105 => 'Не прошла валидация полей',
28
+ 106 => 'Не передан телефон (для услуги, оплачиваемой через веб-кошелек)',
29
+ -20 => 'Expired transaction',
30
+ -19 => 'Authentication failed',
31
+ -17 => 'Access denied',
32
+ -16 => 'Terminal is locked, please try again',
33
+ -9 => 'Error in card expiration date field',
34
+ -4 => 'Server is not responding',
35
+ -3 => 'No or Invalid response received',
36
+ -2 => 'Bad CGI request',
37
+ 0 => 'Approved',
38
+ 1 => 'Call your bank',
39
+ 3 => 'Invalid merchant',
40
+ 4 => 'Your card is restricted',
41
+ 5 => 'Transaction declined',
42
+ 6 => 'Error - retry',
43
+ 12 => 'Invalid transaction',
44
+ 13 => 'Invalid amount',
45
+ 14 => 'No such card',
46
+ 15 => 'No such card/issuer',
47
+ 19 => 'Re-enter transaction',
48
+ 20 => 'Invalid response',
49
+ 30 => 'Format error',
50
+ 41 => 'Lost card',
51
+ 43 => 'Stolen card',
52
+ 51 => 'Not sufficient funds',
53
+ 54 => 'Expired card',
54
+ 55 => 'Incorrect PIN',
55
+ 57 => 'Not permitted to client',
56
+ 58 => 'Not permitted to merchant',
57
+ 61 => 'Exceeds amount limit',
58
+ 62 => 'Restricted card',
59
+ 65 => 'Exceeds frequency limit',
60
+ 75 => 'PIN tries exceeded',
61
+ 78 => 'Reserved',
62
+ 82 => 'Time-out at issuer',
63
+ 89 => 'Authentication failure',
64
+ 91 => 'Issuer unavailable',
65
+ 93 => 'Violation of law',
66
+ 96 => 'System malfunction',
67
+ }.freeze
68
+
69
+ attr_reader :error_sub_code, :description
70
+
71
+ def initialize(error_code:, error_sub_code:, description:, body:)
72
+ super(error_code: error_code, body: body)
73
+ @error_sub_code = error_sub_code
74
+ @description = description
75
+ end
76
+
77
+ def to_s
78
+ "#{description} #{ERROR_CODES[error_code]} #{ERROR_SUB_CODES[error_sub_code]} #{body}"
79
+ end
80
+ end
81
+ end
@@ -0,0 +1,3 @@
1
+ module PscbIntegration
2
+ VERSION = '0.9.0'
3
+ end
@@ -0,0 +1,39 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'pscb_integration/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "pscb_integration"
8
+ spec.version = PscbIntegration::VERSION
9
+ spec.authors = ['Alex Emelyanov']
10
+ spec.email = ['holyketzer@gmail.com']
11
+
12
+ spec.summary = 'PSCB payment gateway integration'
13
+ spec.description = 'If you not sure, think a little about using of Yandex Kassa instead :)'
14
+ spec.homepage = 'https://github.com/holyketzer/pscb_integration'
15
+ spec.license = 'MIT'
16
+
17
+ spec.files = `git ls-files -z`.split("\x0").reject do |f|
18
+ f.match(%r{^(test|spec|features)/})
19
+ end
20
+
21
+ spec.bindir = "exe"
22
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
23
+ spec.require_paths = ["lib"]
24
+
25
+ spec.add_dependency 'faraday', '~> 0.9.1'
26
+ spec.add_dependency 'faraday_middleware', '~> 0.9'
27
+ spec.add_dependency 'fear', '~> 0.5.0'
28
+ spec.add_dependency 'rails', '~> 4.2'
29
+
30
+ spec.add_development_dependency 'addressable', '~> 2.5'
31
+ spec.add_development_dependency 'bundler', '~> 1.13'
32
+ spec.add_development_dependency 'fear-rspec', '~> 0.2.0'
33
+ spec.add_development_dependency 'rake', '~> 10.0'
34
+ spec.add_development_dependency 'rspec', '~> 3.0'
35
+ spec.add_development_dependency 'rspec-rails', '~> 3.0'
36
+ spec.add_development_dependency 'sqlite3'
37
+ spec.add_development_dependency 'vcr', '~> 2.9'
38
+ spec.add_development_dependency 'webmock', '~> 1.17'
39
+ end
metadata ADDED
@@ -0,0 +1,246 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: pscb_integration
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.9.0
5
+ platform: ruby
6
+ authors:
7
+ - Alex Emelyanov
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-04-18 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: faraday
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: 0.9.1
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: 0.9.1
27
+ - !ruby/object:Gem::Dependency
28
+ name: faraday_middleware
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.9'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.9'
41
+ - !ruby/object:Gem::Dependency
42
+ name: fear
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: 0.5.0
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: 0.5.0
55
+ - !ruby/object:Gem::Dependency
56
+ name: rails
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '4.2'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '4.2'
69
+ - !ruby/object:Gem::Dependency
70
+ name: addressable
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '2.5'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '2.5'
83
+ - !ruby/object:Gem::Dependency
84
+ name: bundler
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '1.13'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '1.13'
97
+ - !ruby/object:Gem::Dependency
98
+ name: fear-rspec
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - "~>"
102
+ - !ruby/object:Gem::Version
103
+ version: 0.2.0
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - "~>"
109
+ - !ruby/object:Gem::Version
110
+ version: 0.2.0
111
+ - !ruby/object:Gem::Dependency
112
+ name: rake
113
+ requirement: !ruby/object:Gem::Requirement
114
+ requirements:
115
+ - - "~>"
116
+ - !ruby/object:Gem::Version
117
+ version: '10.0'
118
+ type: :development
119
+ prerelease: false
120
+ version_requirements: !ruby/object:Gem::Requirement
121
+ requirements:
122
+ - - "~>"
123
+ - !ruby/object:Gem::Version
124
+ version: '10.0'
125
+ - !ruby/object:Gem::Dependency
126
+ name: rspec
127
+ requirement: !ruby/object:Gem::Requirement
128
+ requirements:
129
+ - - "~>"
130
+ - !ruby/object:Gem::Version
131
+ version: '3.0'
132
+ type: :development
133
+ prerelease: false
134
+ version_requirements: !ruby/object:Gem::Requirement
135
+ requirements:
136
+ - - "~>"
137
+ - !ruby/object:Gem::Version
138
+ version: '3.0'
139
+ - !ruby/object:Gem::Dependency
140
+ name: rspec-rails
141
+ requirement: !ruby/object:Gem::Requirement
142
+ requirements:
143
+ - - "~>"
144
+ - !ruby/object:Gem::Version
145
+ version: '3.0'
146
+ type: :development
147
+ prerelease: false
148
+ version_requirements: !ruby/object:Gem::Requirement
149
+ requirements:
150
+ - - "~>"
151
+ - !ruby/object:Gem::Version
152
+ version: '3.0'
153
+ - !ruby/object:Gem::Dependency
154
+ name: sqlite3
155
+ requirement: !ruby/object:Gem::Requirement
156
+ requirements:
157
+ - - ">="
158
+ - !ruby/object:Gem::Version
159
+ version: '0'
160
+ type: :development
161
+ prerelease: false
162
+ version_requirements: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ - !ruby/object:Gem::Dependency
168
+ name: vcr
169
+ requirement: !ruby/object:Gem::Requirement
170
+ requirements:
171
+ - - "~>"
172
+ - !ruby/object:Gem::Version
173
+ version: '2.9'
174
+ type: :development
175
+ prerelease: false
176
+ version_requirements: !ruby/object:Gem::Requirement
177
+ requirements:
178
+ - - "~>"
179
+ - !ruby/object:Gem::Version
180
+ version: '2.9'
181
+ - !ruby/object:Gem::Dependency
182
+ name: webmock
183
+ requirement: !ruby/object:Gem::Requirement
184
+ requirements:
185
+ - - "~>"
186
+ - !ruby/object:Gem::Version
187
+ version: '1.17'
188
+ type: :development
189
+ prerelease: false
190
+ version_requirements: !ruby/object:Gem::Requirement
191
+ requirements:
192
+ - - "~>"
193
+ - !ruby/object:Gem::Version
194
+ version: '1.17'
195
+ description: If you not sure, think a little about using of Yandex Kassa instead :)
196
+ email:
197
+ - holyketzer@gmail.com
198
+ executables: []
199
+ extensions: []
200
+ extra_rdoc_files: []
201
+ files:
202
+ - ".gitignore"
203
+ - ".rspec"
204
+ - ".travis.yml"
205
+ - Gemfile
206
+ - LICENSE.txt
207
+ - README.md
208
+ - Rakefile
209
+ - app/controllers/pscb_integration/callback_controller.rb
210
+ - bin/console
211
+ - bin/setup
212
+ - config/routes.rb
213
+ - lib/pscb_integration.rb
214
+ - lib/pscb_integration/api_error.rb
215
+ - lib/pscb_integration/base_api_error.rb
216
+ - lib/pscb_integration/client.rb
217
+ - lib/pscb_integration/config.rb
218
+ - lib/pscb_integration/engine.rb
219
+ - lib/pscb_integration/extended_api_error.rb
220
+ - lib/pscb_integration/version.rb
221
+ - pscb_integration.gemspec
222
+ homepage: https://github.com/holyketzer/pscb_integration
223
+ licenses:
224
+ - MIT
225
+ metadata: {}
226
+ post_install_message:
227
+ rdoc_options: []
228
+ require_paths:
229
+ - lib
230
+ required_ruby_version: !ruby/object:Gem::Requirement
231
+ requirements:
232
+ - - ">="
233
+ - !ruby/object:Gem::Version
234
+ version: '0'
235
+ required_rubygems_version: !ruby/object:Gem::Requirement
236
+ requirements:
237
+ - - ">="
238
+ - !ruby/object:Gem::Version
239
+ version: '0'
240
+ requirements: []
241
+ rubyforge_project:
242
+ rubygems_version: 2.6.8
243
+ signing_key:
244
+ specification_version: 4
245
+ summary: PSCB payment gateway integration
246
+ test_files: []