fnsapi 1.1.0 → 1.1.1

Sign up to get free protection for your applications and to get access to all the features.
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