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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 8b68db8ac9a599aaec681d348423ee776efea36d184a904f9440886b4c773536
4
- data.tar.gz: 27d579236d0d2945fae0d7b05fcc02b0328fb3f99ac76a445877d53f10f1a60c
3
+ metadata.gz: d28c167dc46546abce3862c5317ec267fd2f1ec76bbe2b1f9a6e1bfe79796fcd
4
+ data.tar.gz: be06129116933ca59561bd98d90a28b67c73e423ce2dfe5e87300636e8584f60
5
5
  SHA512:
6
- metadata.gz: e83cbc2f8be7731ff49ce72bd2224f6fb08e56172e431fe69a35533a9e67fa7125a8d88a30e4a3b7175baefab4cf7a7f82539175277bfcd208d9d203d33d475b
7
- data.tar.gz: 4cb2a5e374f723dd014416a6e82f8110a432aa1bbc38439303d0a7e596fda5ba130656d1355990df999860ea31eded3fb873afb16d1e534a86f5c5b76476247d
6
+ metadata.gz: 6a455f8be09447f37be8551fb9c301a8178263eab620e0855b78094c9450633240a3d7babba9eb1942b18a057daaf705d96630c286b9f616aa0362df50eb77ab
7
+ data.tar.gz: b001fb7155511edc76eb24b96c5075c3470eca806cd2f5da88e0abbcd714525cad7350c8556f1b2a75187ccf4b14e345a7300d62efa03b3d509f6dd5538068d8
data/.gitignore ADDED
@@ -0,0 +1,11 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+
10
+ # rspec failure tracking
11
+ .rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
data/.semver ADDED
@@ -0,0 +1,5 @@
1
+ ---
2
+ :major: 1
3
+ :minor: 1
4
+ :patch: 0
5
+ :special: ''
data/.travis.yml ADDED
@@ -0,0 +1,9 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.5.3
7
+ before_install: gem install bundler -v 2.0.1
8
+ services:
9
+ - redis-server
data/CHANGELOG.md ADDED
@@ -0,0 +1,10 @@
1
+ # Changelog
2
+
3
+ ## [1.1.0]
4
+ - Add log_enabled and logger configuration
5
+
6
+ ## [1.0.0]
7
+ - Working version
8
+
9
+ ## [0.1.0]
10
+ - Project initialization
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ source 'https://rubygems.org'
4
+
5
+ # Specify your gem's dependencies in fnsapi.gemspec
6
+ gemspec
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
+ ![Build Status](https://api.travis-ci.org/actie/fnsapi.svg?branch=master)
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
@@ -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
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
@@ -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/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
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Fnsapi
4
+ VERSION = '1.1.1'
5
+ 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.0
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-07-05 00:00:00.000000000 Z
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