fnsapi 1.1.0 → 1.1.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 +4 -4
- data/.gitignore +11 -0
- data/.rspec +3 -0
- data/.semver +5 -0
- data/.travis.yml +9 -0
- data/CHANGELOG.md +10 -0
- data/Gemfile +6 -0
- data/Gemfile.lock +62 -0
- data/LICENSE.txt +21 -0
- data/README.md +93 -0
- data/Rakefile +6 -0
- data/bin/console +14 -0
- data/bin/setup +8 -0
- data/fnsapi.gemspec +34 -0
- data/lib/fnsapi.rb +32 -0
- data/lib/fnsapi/auth_service.rb +54 -0
- data/lib/fnsapi/base_service.rb +56 -0
- data/lib/fnsapi/configuration.rb +44 -0
- data/lib/fnsapi/get_message_service.rb +24 -0
- data/lib/fnsapi/kkt_concern.rb +19 -0
- data/lib/fnsapi/kkt_service.rb +93 -0
- data/lib/fnsapi/ticket.rb +34 -0
- data/lib/fnsapi/tmp_storage.rb +40 -0
- data/lib/fnsapi/version.rb +5 -0
- metadata +26 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: d28c167dc46546abce3862c5317ec267fd2f1ec76bbe2b1f9a6e1bfe79796fcd
|
|
4
|
+
data.tar.gz: be06129116933ca59561bd98d90a28b67c73e423ce2dfe5e87300636e8584f60
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 6a455f8be09447f37be8551fb9c301a8178263eab620e0855b78094c9450633240a3d7babba9eb1942b18a057daaf705d96630c286b9f616aa0362df50eb77ab
|
|
7
|
+
data.tar.gz: b001fb7155511edc76eb24b96c5075c3470eca806cd2f5da88e0abbcd714525cad7350c8556f1b2a75187ccf4b14e345a7300d62efa03b3d509f6dd5538068d8
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.semver
ADDED
data/.travis.yml
ADDED
data/CHANGELOG.md
ADDED
data/Gemfile
ADDED
data/Gemfile.lock
ADDED
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
PATH
|
|
2
|
+
remote: .
|
|
3
|
+
specs:
|
|
4
|
+
fnsapi (1.1.0)
|
|
5
|
+
savon
|
|
6
|
+
|
|
7
|
+
GEM
|
|
8
|
+
remote: https://rubygems.org/
|
|
9
|
+
specs:
|
|
10
|
+
akami (1.3.1)
|
|
11
|
+
gyoku (>= 0.4.0)
|
|
12
|
+
nokogiri
|
|
13
|
+
builder (3.2.3)
|
|
14
|
+
diff-lcs (1.3)
|
|
15
|
+
gyoku (1.3.1)
|
|
16
|
+
builder (>= 2.1.2)
|
|
17
|
+
httpi (2.4.4)
|
|
18
|
+
rack
|
|
19
|
+
socksify
|
|
20
|
+
mini_portile2 (2.4.0)
|
|
21
|
+
nokogiri (1.10.2)
|
|
22
|
+
mini_portile2 (~> 2.4.0)
|
|
23
|
+
nori (2.6.0)
|
|
24
|
+
rack (2.0.7)
|
|
25
|
+
rake (12.3.2)
|
|
26
|
+
rspec (3.8.0)
|
|
27
|
+
rspec-core (~> 3.8.0)
|
|
28
|
+
rspec-expectations (~> 3.8.0)
|
|
29
|
+
rspec-mocks (~> 3.8.0)
|
|
30
|
+
rspec-core (3.8.0)
|
|
31
|
+
rspec-support (~> 3.8.0)
|
|
32
|
+
rspec-expectations (3.8.2)
|
|
33
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
34
|
+
rspec-support (~> 3.8.0)
|
|
35
|
+
rspec-mocks (3.8.0)
|
|
36
|
+
diff-lcs (>= 1.2.0, < 2.0)
|
|
37
|
+
rspec-support (~> 3.8.0)
|
|
38
|
+
rspec-support (3.8.0)
|
|
39
|
+
savon (2.12.0)
|
|
40
|
+
akami (~> 1.2)
|
|
41
|
+
builder (>= 2.1.2)
|
|
42
|
+
gyoku (~> 1.2)
|
|
43
|
+
httpi (~> 2.3)
|
|
44
|
+
nokogiri (>= 1.8.1)
|
|
45
|
+
nori (~> 2.4)
|
|
46
|
+
wasabi (~> 3.4)
|
|
47
|
+
socksify (1.7.1)
|
|
48
|
+
wasabi (3.5.0)
|
|
49
|
+
httpi (~> 2.0)
|
|
50
|
+
nokogiri (>= 1.4.2)
|
|
51
|
+
|
|
52
|
+
PLATFORMS
|
|
53
|
+
ruby
|
|
54
|
+
|
|
55
|
+
DEPENDENCIES
|
|
56
|
+
bundler
|
|
57
|
+
fnsapi!
|
|
58
|
+
rake
|
|
59
|
+
rspec
|
|
60
|
+
|
|
61
|
+
BUNDLED WITH
|
|
62
|
+
1.16.6
|
data/LICENSE.txt
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2019 Alexey Naumov
|
|
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,93 @@
|
|
|
1
|
+
# Fnsapi
|
|
2
|
+
|
|
3
|
+
Gem implements API with Federal Tax services of Russia.
|
|
4
|
+
|
|
5
|
+
Гем реализуют взаимодействие с официальным апи проверки чеков Федеральной Налоговая службы России. Чтобы получить токен неоходимо подать заявку на сайте ФНС. [Документация для получения токена](https://www.nalog.ru/files/kkt/pdf/%D0%A2%D0%B5%D1%85%D0%BD%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%B8%D0%B5%20%D1%83%D1%81%D0%BB%D0%BE%D0%B2%D0%B8%D1%8F%20%D0%B8%D1%81%D0%BF%D0%BE%D0%BB%D1%8C%D0%B7%D0%BE%D0%B2%D0%B0%D0%BD%D0%B8%D1%8F.pdf).
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
[Недокументированное апи для работы с данными чеков ФНС](https://habr.com/ru/post/358966/) (при большом колиечестве проверок, работает не стабильно).
|
|
9
|
+
|
|
10
|
+

|
|
11
|
+
|
|
12
|
+
## Installation
|
|
13
|
+
|
|
14
|
+
Add this line to your application's Gemfile:
|
|
15
|
+
|
|
16
|
+
```ruby
|
|
17
|
+
gem 'fnsapi', github: 'actie/fnsapi'
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
Then:
|
|
21
|
+
|
|
22
|
+
$ bundle
|
|
23
|
+
|
|
24
|
+
## Configuration
|
|
25
|
+
|
|
26
|
+
You can configure this gem in `config/initializers/fnsapi.rb`:
|
|
27
|
+
|
|
28
|
+
```ruby
|
|
29
|
+
Fnsapi.configure do |config|
|
|
30
|
+
config.redis_url = ENV.fetch('REDIS_URL')
|
|
31
|
+
config.fnsapi_master_key = ENV.fetch('FNS_API_MASTER_KEY')
|
|
32
|
+
config.fnsapi_user_token = ENV.fetch('FNS_API_USER_TOKEN')
|
|
33
|
+
end
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
The only one parameter, which you must specify is `fnsapi_master_key`.
|
|
37
|
+
And if you want to store temporary credentials in redis specify `redis_url`. If you don't credentials will be stored in the tmp file.
|
|
38
|
+
|
|
39
|
+
The full parameters list for configuration with default values:
|
|
40
|
+
```
|
|
41
|
+
fns_host = 'https://openapi.nalog.ru'
|
|
42
|
+
fns_port = 8090
|
|
43
|
+
redis_key = :fnsapi_token
|
|
44
|
+
redis_url = nil
|
|
45
|
+
tmp_file_name = 'fnsapi_tmp_credentials'
|
|
46
|
+
fnsapi_master_key = nil
|
|
47
|
+
fnsapi_user_token = nil
|
|
48
|
+
get_message_timeout = 60
|
|
49
|
+
log_enabled = false
|
|
50
|
+
logger = Logger.new($stdout)
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### get message timeout
|
|
54
|
+
|
|
55
|
+
FNS provides us an asynchronous API. So, we need to make two requests: first to generate the message, and second to receive it. And there is a timeout on a server side. It's possible to download the message only within around the 60 seconds after request. We use the [exponential backoff algorithm](https://en.wikipedia.org/wiki/Exponential_backoff) with 60 seconds timeout. You can specify the different value but if it is too big, you'll just receive the TimeoutException from FNS backend.
|
|
56
|
+
|
|
57
|
+
### log_enabled
|
|
58
|
+
|
|
59
|
+
If this option id true, all SAVON logs will be written in logger.
|
|
60
|
+
|
|
61
|
+
### logger
|
|
62
|
+
|
|
63
|
+
By default it's a `stdout` stream but if you use this gem with Rails application, logger will be configurated as `Rails.logger` automaticaly.
|
|
64
|
+
|
|
65
|
+
## Usage
|
|
66
|
+
|
|
67
|
+
There are two methods:
|
|
68
|
+
```ruby
|
|
69
|
+
# Is check data correct?
|
|
70
|
+
Fnsapi.check_data(ticket, user_id) # true / false
|
|
71
|
+
# Give me full information about check: products, INN etc.
|
|
72
|
+
Fnsapi.get_data(ticket, user_id)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
`ticket` could be both an object which implements methods or a hash with the same keys:
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
fn - Fiscal number
|
|
79
|
+
fd - Fiscal document id
|
|
80
|
+
pfd - Fiscal signature
|
|
81
|
+
purchase_date - Ticket purchase date with time (we have tested for Moscow timezone but this point is not documented, and FNS API don't acept time with timezone, so I don't sure what timezone can you use.)
|
|
82
|
+
amount_cents - Ticket amount in cents (Integer)
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
`user_id` - is an optional parameter. You can send the ID for user in you system if you want to specify which person do this request. In other way it has a default value `'default_user'`.
|
|
86
|
+
|
|
87
|
+
## Contributing
|
|
88
|
+
|
|
89
|
+
Bug reports and pull requests are welcome on GitHub at https://github.com/actie/fnsapi.
|
|
90
|
+
|
|
91
|
+
## License
|
|
92
|
+
|
|
93
|
+
The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
|
data/Rakefile
ADDED
data/bin/console
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
|
|
3
|
+
require "bundler/setup"
|
|
4
|
+
require "fnsapi"
|
|
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(__FILE__)
|
data/bin/setup
ADDED
data/fnsapi.gemspec
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
lib = File.expand_path('lib', __dir__)
|
|
4
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
|
5
|
+
|
|
6
|
+
require 'fnsapi/version'
|
|
7
|
+
|
|
8
|
+
Gem::Specification.new do |spec|
|
|
9
|
+
spec.name = 'fnsapi'
|
|
10
|
+
spec.version = Fnsapi::VERSION
|
|
11
|
+
spec.authors = ['Fedor Koshel']
|
|
12
|
+
spec.email = ['alexsnaumov@gmail.com']
|
|
13
|
+
|
|
14
|
+
spec.summary = %(Ruby implementation for Russian FNS api)
|
|
15
|
+
spec.description = %(If you got approved for wirking with Russian FNS API, this gem will help you.)
|
|
16
|
+
spec.homepage = 'https://github.com/actie/fnsapi'
|
|
17
|
+
spec.license = 'MIT'
|
|
18
|
+
|
|
19
|
+
if spec.respond_to?(:metadata)
|
|
20
|
+
spec.metadata['homepage_uri'] = spec.homepage
|
|
21
|
+
spec.metadata['source_code_uri'] = 'https://github.com/actie/fnsapi'
|
|
22
|
+
spec.metadata['changelog_uri'] = 'https://github.com/actie/fnsapi/CHANGELOG.md'
|
|
23
|
+
else
|
|
24
|
+
raise 'RubyGems 2.0 or newer is required to protect against public gem pushes.'
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
spec.files = `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^spec/}) }
|
|
28
|
+
spec.require_paths = ['lib']
|
|
29
|
+
spec.add_runtime_dependency 'savon'
|
|
30
|
+
|
|
31
|
+
spec.add_development_dependency 'bundler'
|
|
32
|
+
spec.add_development_dependency 'rake'
|
|
33
|
+
spec.add_development_dependency 'rspec'
|
|
34
|
+
end
|
data/lib/fnsapi.rb
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'fnsapi/ticket'
|
|
5
|
+
require 'fnsapi/version'
|
|
6
|
+
require 'fnsapi/configuration'
|
|
7
|
+
require 'fnsapi/tmp_storage'
|
|
8
|
+
require 'fnsapi/base_service'
|
|
9
|
+
require 'fnsapi/auth_service'
|
|
10
|
+
require 'fnsapi/kkt_concern'
|
|
11
|
+
require 'fnsapi/get_message_service'
|
|
12
|
+
require 'fnsapi/kkt_service'
|
|
13
|
+
|
|
14
|
+
module Fnsapi
|
|
15
|
+
class << self
|
|
16
|
+
def configuration
|
|
17
|
+
@configuration ||= Configuration.new
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def configure
|
|
21
|
+
yield(configuration)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def check_data(ticket, user_id = 'default_user')
|
|
25
|
+
Fnsapi::KktService.new.check_data(ticket, user_id)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
def get_data(ticket, user_id = 'default_user')
|
|
29
|
+
Fnsapi::KktService.new.get_data(ticket, user_id)
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fnsapi
|
|
4
|
+
class AuthService < BaseService
|
|
5
|
+
def reset_credentials
|
|
6
|
+
result = client.call(:get_message, message: message_hash)
|
|
7
|
+
message = result.body.dig(:get_message_response, :message)
|
|
8
|
+
|
|
9
|
+
raise RequestError, message[:fault][:message] if message[:fault]
|
|
10
|
+
|
|
11
|
+
token = message.dig(:auth_response, :result, :token)
|
|
12
|
+
expired_at = Time.parse(message.dig(:auth_response, :result, :expire_time))
|
|
13
|
+
|
|
14
|
+
return if token.blank?
|
|
15
|
+
|
|
16
|
+
put_token!(token, expired_at)
|
|
17
|
+
token
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def namespaces
|
|
23
|
+
super.merge(
|
|
24
|
+
'xmlns:tns' => 'urn://x-artefacts-gnivc-ru/ais3/kkt/AuthService/types/1.0',
|
|
25
|
+
'targetNamespace' => 'urn://x-artefacts-gnivc-ru/ais3/kkt/AuthService/types/1.0'
|
|
26
|
+
)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def uri
|
|
30
|
+
'/open-api/AuthService/0.1?wsdl'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def message_hash
|
|
34
|
+
{
|
|
35
|
+
'Message' => {
|
|
36
|
+
'tns:AuthRequest' => {
|
|
37
|
+
'tns:AuthAppInfo' => {
|
|
38
|
+
'tns:MasterToken' => Fnsapi.configuration.fnsapi_master_key
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def put_token!(token, expired_at)
|
|
46
|
+
if redis
|
|
47
|
+
redis.set(Fnsapi.configuration.redis_key, token)
|
|
48
|
+
redis.expireat(Fnsapi.configuration.redis_key, expired_at.to_i)
|
|
49
|
+
else
|
|
50
|
+
tmp_storage.write_token(token, expired_at)
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'savon'
|
|
4
|
+
|
|
5
|
+
module Fnsapi
|
|
6
|
+
class RequestError < StandardError; end
|
|
7
|
+
class NotImplementedError < StandardError; end
|
|
8
|
+
|
|
9
|
+
class BaseService
|
|
10
|
+
def client(additional_params = {})
|
|
11
|
+
Savon.client(client_params(additional_params))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
private
|
|
15
|
+
|
|
16
|
+
def namespaces
|
|
17
|
+
{ 'xmlns:xs' => 'http://www.w3.org/2001/XMLSchema' }
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def client_params(additional_params = {})
|
|
21
|
+
{
|
|
22
|
+
wsdl: "#{fns_url}#{uri}",
|
|
23
|
+
namespaces: namespaces,
|
|
24
|
+
env_namespace: :soap,
|
|
25
|
+
log: Fnsapi.configuration.log_enabled,
|
|
26
|
+
logger: Fnsapi.configuration.logger
|
|
27
|
+
}.merge(additional_params)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def uri
|
|
31
|
+
raise NotImplementedError
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def redis
|
|
35
|
+
return false unless Fnsapi.configuration.redis_url
|
|
36
|
+
|
|
37
|
+
@redis ||= Redis.new(url: Fnsapi.configuration.redis_url)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def tmp_storage
|
|
41
|
+
@tmp_storage ||= TmpStorage.new
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def token
|
|
45
|
+
if redis
|
|
46
|
+
redis.get(Fnsapi.configuration.redis_key)
|
|
47
|
+
else
|
|
48
|
+
tmp_storage.token
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def fns_url
|
|
53
|
+
"#{Fnsapi.configuration.fns_host}:#{Fnsapi.configuration.fns_port}"
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fnsapi
|
|
4
|
+
class InvalidConfigurationError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class Configuration
|
|
7
|
+
attr_accessor :fns_host,
|
|
8
|
+
:fns_port,
|
|
9
|
+
:redis_key,
|
|
10
|
+
:redis_url,
|
|
11
|
+
:tmp_file_name,
|
|
12
|
+
:get_message_timeout,
|
|
13
|
+
:log_enabled,
|
|
14
|
+
:logger
|
|
15
|
+
|
|
16
|
+
attr_writer :fnsapi_user_token,
|
|
17
|
+
:fnsapi_master_key
|
|
18
|
+
|
|
19
|
+
def initialize
|
|
20
|
+
@fns_host = 'https://openapi.nalog.ru'
|
|
21
|
+
@fns_port = 8090
|
|
22
|
+
@redis_key = :fnsapi_token
|
|
23
|
+
@redis_url = nil
|
|
24
|
+
@tmp_file_name = 'fnsapi_tmp_credentials'
|
|
25
|
+
@fnsapi_master_key = nil
|
|
26
|
+
@fnsapi_user_token = nil
|
|
27
|
+
@get_message_timeout = 60
|
|
28
|
+
@logger = defined?(Rails) ? Rails.logger : Logger.new($stdout)
|
|
29
|
+
@log_enabled = false
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def fnsapi_user_token
|
|
33
|
+
return @fnsapi_user_token if @fnsapi_user_token
|
|
34
|
+
|
|
35
|
+
raise InvalidConfigurationError, 'fnsapi_user_token must be specified'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def fnsapi_master_key
|
|
39
|
+
return @fnsapi_master_key if @fnsapi_master_key
|
|
40
|
+
|
|
41
|
+
raise InvalidConfigurationError, 'fnsapi_master_key must be specified'
|
|
42
|
+
end
|
|
43
|
+
end
|
|
44
|
+
end
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fnsapi
|
|
4
|
+
class GetMessageService < BaseService
|
|
5
|
+
include KktConcern
|
|
6
|
+
|
|
7
|
+
def call(message_id, user_id)
|
|
8
|
+
result = client(auth_params(user_id)).call(:get_message, message: message_hash(message_id))
|
|
9
|
+
result.body.dig(:get_message_response)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def namespaces
|
|
15
|
+
super.merge(
|
|
16
|
+
'xmlns:tns' => 'urn://x-artefacts-gnivc-ru/inplat/servin/OpenApiAsyncMessageConsumerService/types/1.0'
|
|
17
|
+
)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def message_hash(message_id)
|
|
21
|
+
{ 'tns:MessageId' => message_id }
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fnsapi
|
|
4
|
+
module KktConcern
|
|
5
|
+
def auth_params(user_id)
|
|
6
|
+
refresh_credentials! unless token
|
|
7
|
+
|
|
8
|
+
{ headers: { 'FNS-OpenApi-Token' => token, 'FNS-OpenApi-UserToken' => Base64.strict_encode64(user_id.to_s) } }
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
def uri
|
|
12
|
+
'/open-api/ais3/KktService/0.1?wsdl'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def refresh_credentials!
|
|
16
|
+
AuthService.new.reset_credentials
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fnsapi
|
|
4
|
+
class KktService < BaseService
|
|
5
|
+
include KktConcern
|
|
6
|
+
|
|
7
|
+
def check_data(object, user_id = 'default_user')
|
|
8
|
+
ticket = Ticket.new(object)
|
|
9
|
+
result = client(auth_params(user_id)).call(:send_message, message: check_ticket_hash(ticket))
|
|
10
|
+
message_id = result.body.dig(:send_message_response, :message_id)
|
|
11
|
+
|
|
12
|
+
message = parse_message(message_id, user_id)
|
|
13
|
+
return unless message
|
|
14
|
+
|
|
15
|
+
code = message.dig(:check_ticket_response, :result, :code)
|
|
16
|
+
code == '200'
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def get_data(object, user_id = 'default_user')
|
|
20
|
+
ticket = Ticket.new(object)
|
|
21
|
+
result = client(auth_params(user_id)).call(:send_message, message: get_ticket_hash(ticket))
|
|
22
|
+
message_id = result.body.dig(:send_message_response, :message_id)
|
|
23
|
+
|
|
24
|
+
message = parse_message(message_id, user_id)
|
|
25
|
+
return unless message
|
|
26
|
+
|
|
27
|
+
code = message.dig(:get_ticket_response, :result, :code)
|
|
28
|
+
return code if code != '200'
|
|
29
|
+
|
|
30
|
+
JSON.parse(message.dig(:get_ticket_response, :result, :ticket))
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def parse_message(message_id, user_id)
|
|
36
|
+
wait_time = 0
|
|
37
|
+
i = 0
|
|
38
|
+
|
|
39
|
+
while true do
|
|
40
|
+
response = GetMessageService.new.call(message_id, user_id)
|
|
41
|
+
return response[:message] if response[:processing_status] == 'COMPLETED'
|
|
42
|
+
|
|
43
|
+
timeout = (2**i - 1)/2
|
|
44
|
+
wait_time += timeout
|
|
45
|
+
i += 1
|
|
46
|
+
|
|
47
|
+
break if wait_time > Fnsapi.configuration.get_message_timeout
|
|
48
|
+
|
|
49
|
+
sleep(timeout)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
raise RequestError, 'Timeout reached'
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
def namespaces
|
|
56
|
+
super.merge(
|
|
57
|
+
'xmlns:tns' => 'urn://x-artefacts-gnivc-ru/ais3/kkt/KktTicketService/types/1.0',
|
|
58
|
+
'targetNamespace' => 'urn://x-artefacts-gnivc-ru/ais3/kkt/KktTicketService/types/1.0'
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def check_ticket_hash(ticket)
|
|
63
|
+
{
|
|
64
|
+
'Message' => {
|
|
65
|
+
'tns:CheckTicketRequest' => {
|
|
66
|
+
'tns:CheckTicketInfo' => ticket_hash(ticket)
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
def get_ticket_hash(ticket)
|
|
73
|
+
{
|
|
74
|
+
'Message' => {
|
|
75
|
+
'tns:GetTicketRequest' => {
|
|
76
|
+
'tns:GetTicketInfo' => ticket_hash(ticket)
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def ticket_hash(ticket)
|
|
83
|
+
{
|
|
84
|
+
'tns:Fn' => ticket.fn,
|
|
85
|
+
'tns:FiscalDocumentId' => ticket.fd,
|
|
86
|
+
'tns:FiscalSign' => ticket.pfd,
|
|
87
|
+
'tns:Date' => ticket.purchase_date.strftime('%FT%T'),
|
|
88
|
+
'tns:Sum' => ticket.amount_cents,
|
|
89
|
+
'tns:TypeOperation' => 1
|
|
90
|
+
}
|
|
91
|
+
end
|
|
92
|
+
end
|
|
93
|
+
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fnsapi
|
|
4
|
+
class FieldNotSpecifiedError < StandardError; end
|
|
5
|
+
|
|
6
|
+
class Ticket
|
|
7
|
+
attr_reader :fn, :fd, :pfd, :purchase_date, :amount_cents
|
|
8
|
+
|
|
9
|
+
def initialize(object)
|
|
10
|
+
%i[fn fd pfd amount_cents].each do |field_name|
|
|
11
|
+
instance_variable_set("@#{field_name}", validated_field_value(object, field_name))
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
@purchase_date = validated_field_value(object, :purchase_date)
|
|
15
|
+
@purchase_date = DateTime.parse(@purchase_date) if @purchase_date.is_a?(String)
|
|
16
|
+
true
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
private
|
|
21
|
+
|
|
22
|
+
def validated_field_value(object, field)
|
|
23
|
+
value = if object.is_a?(Hash)
|
|
24
|
+
object[field] || object[field.to_s]
|
|
25
|
+
else
|
|
26
|
+
object.public_send(field)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
raise FieldNotSpecifiedError, "#{field} should be specified" if value.blank?
|
|
30
|
+
|
|
31
|
+
value
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Fnsapi
|
|
4
|
+
class TmpStorage
|
|
5
|
+
def initialize
|
|
6
|
+
@file = File.open(file_path, 'a+')
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def write_token(token, expire_at)
|
|
10
|
+
@file.truncate(0)
|
|
11
|
+
@file.write({ token: token, expire_at: expire_at }.to_json)
|
|
12
|
+
@file.rewind
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def token
|
|
16
|
+
data = JSON.parse(@file.read)
|
|
17
|
+
expired_at = Time.parse(data['expire_at'])
|
|
18
|
+
|
|
19
|
+
if expired_at < Time.now
|
|
20
|
+
@file.truncate(0)
|
|
21
|
+
return
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
data['token']
|
|
25
|
+
rescue JSON::ParserError
|
|
26
|
+
@file.truncate(0)
|
|
27
|
+
nil
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
private
|
|
31
|
+
|
|
32
|
+
def file_path
|
|
33
|
+
if defined?(Rails)
|
|
34
|
+
Rails.root.join('tmp', Fnsapi.configuration.tmp_file_name)
|
|
35
|
+
else
|
|
36
|
+
'tmp/' + Fnsapi.configuration.tmp_file_name
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: fnsapi
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 1.1.
|
|
4
|
+
version: 1.1.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Fedor Koshel
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2019-
|
|
11
|
+
date: 2019-08-20 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: savon
|
|
@@ -73,7 +73,30 @@ email:
|
|
|
73
73
|
executables: []
|
|
74
74
|
extensions: []
|
|
75
75
|
extra_rdoc_files: []
|
|
76
|
-
files:
|
|
76
|
+
files:
|
|
77
|
+
- ".gitignore"
|
|
78
|
+
- ".rspec"
|
|
79
|
+
- ".semver"
|
|
80
|
+
- ".travis.yml"
|
|
81
|
+
- CHANGELOG.md
|
|
82
|
+
- Gemfile
|
|
83
|
+
- Gemfile.lock
|
|
84
|
+
- LICENSE.txt
|
|
85
|
+
- README.md
|
|
86
|
+
- Rakefile
|
|
87
|
+
- bin/console
|
|
88
|
+
- bin/setup
|
|
89
|
+
- fnsapi.gemspec
|
|
90
|
+
- lib/fnsapi.rb
|
|
91
|
+
- lib/fnsapi/auth_service.rb
|
|
92
|
+
- lib/fnsapi/base_service.rb
|
|
93
|
+
- lib/fnsapi/configuration.rb
|
|
94
|
+
- lib/fnsapi/get_message_service.rb
|
|
95
|
+
- lib/fnsapi/kkt_concern.rb
|
|
96
|
+
- lib/fnsapi/kkt_service.rb
|
|
97
|
+
- lib/fnsapi/ticket.rb
|
|
98
|
+
- lib/fnsapi/tmp_storage.rb
|
|
99
|
+
- lib/fnsapi/version.rb
|
|
77
100
|
homepage: https://github.com/actie/fnsapi
|
|
78
101
|
licenses:
|
|
79
102
|
- MIT
|