orangedata 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 9f3099e2e11880ab05a035b7174a3b51d4f23568
4
+ data.tar.gz: c48823de2b7073f0e19c60903e3c6d574b08b2b9
5
+ SHA512:
6
+ metadata.gz: e8449d4a55cebf3da41ec3cae035fccdd16c62a363359ee3b5cc35b2511e84b9ebed8b4147a2d3604ca47096df09a65de39cd35f7c24363cda62402c68013949
7
+ data.tar.gz: bcf0803eaecc27e05bb5a34305cf3da9092c792309630147e093c084c89bfd01aaa78fd0860a290190c9b7f606c7a310b59d6c0d039909d9888f6d593a1d63a2
@@ -0,0 +1,9 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
9
+ /.rspec_status
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --format documentation
2
+ --color
3
+ --require spec_helper
@@ -0,0 +1,7 @@
1
+ ---
2
+ sudo: false
3
+ language: ruby
4
+ cache: bundler
5
+ rvm:
6
+ - 2.3.1
7
+ before_install: gem install bundler -v 1.16.6
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # All dependencies are in orangedata.gemspec
6
+ gemspec
@@ -0,0 +1,72 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ orangedata (0.0.1)
5
+ faraday (>= 0.15)
6
+ faraday_middleware
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ addressable (2.5.2)
12
+ public_suffix (>= 2.0.2, < 4.0)
13
+ ast (2.4.0)
14
+ crack (0.4.3)
15
+ safe_yaml (~> 1.0.0)
16
+ diff-lcs (1.3)
17
+ faraday (0.15.2)
18
+ multipart-post (>= 1.2, < 3)
19
+ faraday_middleware (0.12.2)
20
+ faraday (>= 0.7.4, < 1.0)
21
+ hashdiff (0.3.7)
22
+ jaro_winkler (1.5.1)
23
+ multipart-post (2.0.0)
24
+ parallel (1.12.1)
25
+ parser (2.5.1.0)
26
+ ast (~> 2.4.0)
27
+ powerpack (0.1.2)
28
+ public_suffix (3.0.3)
29
+ rainbow (3.0.0)
30
+ rake (10.5.0)
31
+ rspec (3.7.0)
32
+ rspec-core (~> 3.7.0)
33
+ rspec-expectations (~> 3.7.0)
34
+ rspec-mocks (~> 3.7.0)
35
+ rspec-core (3.7.1)
36
+ rspec-support (~> 3.7.0)
37
+ rspec-expectations (3.7.0)
38
+ diff-lcs (>= 1.2.0, < 2.0)
39
+ rspec-support (~> 3.7.0)
40
+ rspec-mocks (3.7.0)
41
+ diff-lcs (>= 1.2.0, < 2.0)
42
+ rspec-support (~> 3.7.0)
43
+ rspec-support (3.7.1)
44
+ rubocop (0.57.2)
45
+ jaro_winkler (~> 1.5.1)
46
+ parallel (~> 1.10)
47
+ parser (>= 2.5)
48
+ powerpack (~> 0.1)
49
+ rainbow (>= 2.2.2, < 4.0)
50
+ ruby-progressbar (~> 1.7)
51
+ unicode-display_width (~> 1.0, >= 1.0.1)
52
+ ruby-progressbar (1.9.0)
53
+ safe_yaml (1.0.4)
54
+ unicode-display_width (1.4.0)
55
+ webmock (3.4.2)
56
+ addressable (>= 2.3.6)
57
+ crack (>= 0.3.2)
58
+ hashdiff
59
+
60
+ PLATFORMS
61
+ ruby
62
+
63
+ DEPENDENCIES
64
+ bundler (~> 1.16)
65
+ orangedata!
66
+ rake (~> 10.0)
67
+ rspec
68
+ rubocop
69
+ webmock
70
+
71
+ BUNDLED WITH
72
+ 1.16.6
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Vasily Fedoseyev
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.
@@ -0,0 +1,120 @@
1
+ # Orangedata Client
2
+
3
+ A ruby client for orangedata.ru service.
4
+ Target service is pretty local to RU, so parts of readme will be in russian.
5
+
6
+ Note: This is a Work-in-progress. API might change in the future.
7
+
8
+ Умеет:
9
+ - собственно транспорт с подписью запросов
10
+ - сгенерировать ключ сразу в нужном виде
11
+ - планируется DSL для чеков
12
+
13
+ ## Установка
14
+
15
+ Все стандартно. Пишем в Gemfile:
16
+
17
+ ```ruby
18
+ gem 'orangedata'
19
+ ```
20
+
21
+ И давим:
22
+
23
+ $ bundle
24
+
25
+ Либо руками:
26
+
27
+ $ gem install orangedata
28
+
29
+ ## Использование
30
+
31
+ Для тестового окружения ключики в комплекте - [credentials_test.yml](lib/credentials_test.yml), собрано из родного `File_for_test.zip`, доступны как `OrangeData::Credentials.default_test`
32
+
33
+ ```ruby
34
+ transport = OrangeData::Transport.new("https://apip.orangedata.ru:2443/api/v2/", OrangeData::Credentials.default_test)
35
+
36
+ receipt = {
37
+ id: SecureRandom.uuid,
38
+ inn: '1234567890', key:'1234567890',
39
+ content: { # тут собрать данные можно по официальному мануалу
40
+ type: 1,
41
+ positions:[{ quantity: 1, price: 0.01, tax: 4, text: "Товар на копейку"}],
42
+ checkClose:{
43
+ payments: [{ type:2, amount:'0.01' }],
44
+ taxationSystem: 1
45
+ }
46
+ }
47
+ }
48
+ transport.post_document(receipt)
49
+ # wait some time, then
50
+ transport.get_document("1234567890", receipt[:id])
51
+ # =>
52
+ # {
53
+ # "id"=>"a88b6b30-20ab-47ea-95ca-f12f22ef03d3",
54
+ # "deviceSN"=>"1400000000001033",
55
+ # "deviceRN"=>"0000000400054952",
56
+ # "fsNumber"=>"9999078900001341",
57
+ # "ofdName"=>"ООО \"Ярус\" (\"ОФД-Я\")",
58
+ # "ofdWebsite"=>"www.ofd-ya.ru",
59
+ # "ofdinn"=>"7728699517",
60
+ # "fnsWebsite"=>"www.nalog.ru",
61
+ # "companyINN"=>"1234567890",
62
+ # "companyName"=>"Тест",
63
+ # "documentNumber"=>5548,
64
+ # "shiftNumber"=>6072,
65
+ # "documentIndex"=>3045,
66
+ # "processedAt"=>"2018-10-22T19:36:00",
67
+ # "content"=>
68
+ # {
69
+ # "type"=>1,
70
+ # "positions"=>[{"quantity"=>1.0, "price"=>0.01, "tax"=>4, "text"=>"Товар на копейку"}],
71
+ # "checkClose"=>{
72
+ # "payments"=>[{"type"=>2, "amount"=>0.01}],
73
+ # "taxationSystem"=>1
74
+ # }
75
+ # },
76
+ # "change"=>0.0,
77
+ # "fp"=>"787980846"
78
+ # }
79
+ ```
80
+
81
+ ### Получаем сертификаты
82
+
83
+ Предполагается, что всякие договоры и прочая фискализация уже успешно пройдена и у вас есть доступ
84
+ к ЛК orangedata.
85
+
86
+ Генерируем себе приватный ключ:
87
+
88
+ ```ruby
89
+ require 'yaml'
90
+ c = OrangeData::Credentials.new title:'My production'
91
+ c.generate_signature_key!(2048) # параметр - длина ключа
92
+ #=> возвращает публичный ключ в том виде, который хочет ЛК OrangeData:
93
+ # "<RSAKeyValue><Modulus>(многабукв)==</Modulus><Exponent>AQAB</Exponent></RSAKeyValue>"
94
+
95
+ File.open("my_production.yml", "wt"){|f| f.write YAML.dump(c.to_hash) }
96
+ # (на выходе - yml с приватным ключом и паролем к нему, который надо сохранить и беречь)
97
+
98
+ # повторно взять публичный ключ можно так:
99
+ credentials = OrangeData::Credentials.from_hash(YAML.load_file('my_production.yml'))
100
+ credentials.signature_public_xml
101
+ ```
102
+
103
+ После чего публичный ключ (xml c `RSAKeyValue`) кормим в ЛК. Значение поля `Название ключа` с этого шага отправляется в `signature_key_name`.
104
+ Далее там выпускаем себе сертификаты и из полученного архива вставляем содержимое `<ИНН>.crt` и `<ИНН>.key`(сертификат и ключ к нему) в yml-файлик аналогично примеру.
105
+
106
+ Если все прошло гладко - теперь у вас есть файлик `my_production.yml` со всеми реквизитами доступа к продакшн-кассе. Обращаться с ним стоит как и с любой другой очень чувствительной информацией, например не стоит коммитить его (ну или как минимум, убрать из него поля `signature_key_pass` и `certificate_key_pass` и хранить отдельно)
107
+
108
+ ## Разработка
109
+
110
+ 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.
111
+
112
+ 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).
113
+
114
+ ## Contributing
115
+
116
+ Bug reports and pull requests are welcome on GitHub at https://github.com/Vasfed/orangedata.
117
+
118
+ ## License
119
+
120
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT). Copyright (c) 2018 Vasily Fedoseyev
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'bundler/gem_tasks'
4
+ require 'rspec/core/rake_task'
5
+
6
+ RSpec::Core::RakeTask.new(:spec)
7
+
8
+ task default: :spec
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "orange_data"
5
+ require "irb"
6
+
7
+ using OrangeData::Credentials::KeyEncoding
8
+ IRB.start(__FILE__)
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "orange_data/version"
4
+ require "orange_data/credentials"
5
+ require "orange_data/transport"
6
+
7
+ module OrangeData
8
+
9
+
10
+ # QR: t=20180518T220500&s=975.88&fn=8710000101125654&i=99456&fp=1250448795&n=1
11
+ #
12
+
13
+ # - t=<date/time - дата и время осуществления расчета в формате ГГГГММДДТЧЧММ>
14
+ # - s=<сумма расчета в рублях и копейках, разделенных точкой>
15
+ # - fn=<заводской номер фискального накопителя>
16
+ # - i=<порядковый номер фискального документа, нулями не дополняется>
17
+ # - fp=<фискальный признак документа, нулями не дополняется>
18
+ # - n=<признак расчета>.
19
+ # Пример строки QR-кода: t=20150720T1638&s=9999999.00&fn=000110000105&i=12345678&fp=123456&n=2
20
+
21
+ end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "openssl"
4
+ require "base64"
5
+ require "securerandom"
6
+
7
+ # wrapper for keys/certs used for connection auth
8
+ module OrangeData
9
+ class Credentials
10
+
11
+ # nodoc
12
+ module KeyEncoding
13
+ refine OpenSSL::PKey::RSA do
14
+ def to_xml
15
+ h_params = to_hash
16
+ h = { 'Modulus' => :n, 'Exponent' => :e }
17
+ if private?
18
+ h.merge!({
19
+ 'P' => :p, 'Q' => :q, 'DP' => :dmp1, 'DQ' => :dmq1, 'InverseQ' => :iqmp, 'D' => :d
20
+ })
21
+ end
22
+ "<RSAKeyValue>#{h.map{|(k,v)| "<#{k}>#{h_params[v.to_s]}</#{k}>"}.join('')}</RSAKeyValue>"
23
+ end
24
+
25
+ def to_hash
26
+ params.map{|k,v| v != 0 && [k, Base64.strict_encode64(v.to_s(2))] || nil}.compact.to_h
27
+ end
28
+ end
29
+
30
+ refine OpenSSL::PKey::RSA.singleton_class do
31
+ def from_xml(xml)
32
+ require "rexml/document"
33
+ kv = REXML::Document.new(xml).elements['RSAKeyValue']
34
+ raise ArgumentError, 'no RSAKeyValue in xml' unless kv&.name == 'RSAKeyValue'
35
+
36
+ mapping = {
37
+ "Modulus"=>:n, "Exponent"=>:e,
38
+ "D"=>:d, "P"=>:p, "Q"=>:q,
39
+ "DP"=>:dmp1, "DQ"=>:dmq1, "InverseQ"=>:iqmp
40
+ }
41
+ from_hash(
42
+ kv.elements.each_with_object({}){|k,h| h[mapping[k.name]] = k.text if mapping[k.name] }
43
+ )
44
+ end
45
+
46
+ def from_hash hash
47
+ OpenSSL::PKey::RSA.new.tap do |key|
48
+ key.params.keys.each{|param|
49
+ if(v = hash[param] || hash[param.to_sym])
50
+ key.send(:"#{param}=", OpenSSL::BN.new(Base64.decode64(v), 2))
51
+ end
52
+ }
53
+ end
54
+ end
55
+ end
56
+ end
57
+
58
+ using KeyEncoding
59
+
60
+ attr_accessor :signature_key_name, :signature_key, :certificate, :certificate_key, :title
61
+
62
+ def initialize signature_key_name:nil,signature_key:nil,certificate:nil,certificate_key:nil,title:nil
63
+ raise ArgumentError, "Signature key should be a private key" if signature_key && !signature_key.private?
64
+ raise ArgumentError, "Certificate key should be a private key" if certificate_key && !certificate_key.private?
65
+ @signature_key_name = signature_key_name
66
+ @signature_key = signature_key
67
+ @certificate = certificate
68
+ @certificate_key = certificate_key
69
+ @title = title
70
+ end
71
+
72
+ def valid?
73
+ signature_key_name &&
74
+ signature_key && signature_key.private? &&
75
+ certificate && certificate_key && certificate_key.private?
76
+ end
77
+
78
+ def self.from_hash creds
79
+ key = nil
80
+ if creds[:signature_key]
81
+ key = if creds[:signature_key].is_a?(Hash)
82
+ OpenSSL::PKey::RSA.from_hash(creds[:signature_key])
83
+ elsif creds[:signature_key].start_with?('<')
84
+ OpenSSL::PKey::RSA.from_xml(creds[:signature_key])
85
+ else
86
+ OpenSSL::PKey::RSA.new(creds[:signature_key], creds[:signature_key_pass])
87
+ end
88
+ end
89
+ new(
90
+ signature_key_name: creds[:signature_key_name],
91
+ signature_key: key,
92
+ certificate: creds[:certificate] && OpenSSL::X509::Certificate.new(creds[:certificate]),
93
+ certificate_key: creds[:certificate_key] && OpenSSL::PKey::RSA.new(creds[:certificate_key], creds[:certificate_key_pass]),
94
+ title: creds[:title]
95
+ )
96
+ end
97
+
98
+ def to_hash(key_pass:nil, save_pass:false)
99
+ if key_pass.nil?
100
+ key_pass = SecureRandom.hex
101
+ save_pass = true
102
+ elsif key_pass == false
103
+ key_pass = nil
104
+ end
105
+
106
+ {
107
+ title: title,
108
+ signature_key_name: signature_key_name,
109
+ signature_key: signature_key&.to_pem(OpenSSL::Cipher.new("aes-128-cbc"), key_pass),
110
+ certificate: certificate&.to_pem,
111
+ certificate_key: certificate_key&.to_pem(OpenSSL::Cipher.new("aes-128-cbc"), key_pass),
112
+ }.tap do |h|
113
+ h.delete(:title) if !title || title == ''
114
+ if save_pass
115
+ h[:certificate_key_pass] = key_pass
116
+ h[:signature_key_pass] = key_pass
117
+ end
118
+ end
119
+ end
120
+
121
+ def self.from_json json
122
+ require 'json'
123
+ from_hash(JSON.parse(json, symbolize_names: true))
124
+ end
125
+
126
+ def to_json(key_pass:nil, save_pass:false)
127
+ to_hash(key_pass:key_pass, save_pass:save_pass).to_json
128
+ end
129
+
130
+ def inspect
131
+ info_fields = {
132
+ title: (title || 'untitled').inspect,
133
+ key_name: signature_key_name.inspect,
134
+ certificate: (certificate&.subject.to_a.select{|ent| ent.first == 'O'}.dig(0, 1)).inspect,
135
+ }.map{|(k,v)| "#{k}=#{v}"}.join(' ')
136
+
137
+ "#<#{self.class.name}:#{object_id} #{info_fields}>"
138
+ end
139
+
140
+ def generate_signature_key!(key_length=2048)
141
+ self.signature_key = OpenSSL::PKey::RSA.new(key_length)
142
+ signature_public_xml
143
+ end
144
+
145
+ # публичная часть ключа подписи в формате пригодном для отдачи в ЛК
146
+ def signature_public_xml
147
+ signature_key.public_key.to_xml
148
+ end
149
+
150
+ # ключи для тествого окружения
151
+ def self.default_test
152
+ require 'yaml'
153
+ from_hash(YAML.load_file(File.expand_path('../credentials_test.yml', __FILE__)))
154
+ end
155
+
156
+ end
157
+ end
@@ -0,0 +1,101 @@
1
+ ---
2
+ # Credentials for test environment at https://apip.orangedata.ru:2443/api/v2/
3
+ :title: 'Test credentials'
4
+ :signature_key_name: '1234567890'
5
+ :signature_key: |
6
+ <RSAKeyValue>
7
+ <Modulus>
8
+ t8nC/Eth8UabQbXu8pdro3v7NqUanV8Y+g92YgT7z1xqkBLRHXZ1guml3Pxr
9
+ qjNX9AvOmu8R+qaKOyHfJW0PcRDLzCoIUcHNAwpDO/E5j6WAaLIv7gAjTtyr
10
+ 9kJB9rfJaparViJNZu3RSUYGTvVznOmXMf7LTOTMR6HP/5H1TP5n1g4+BbLm
11
+ C9EhjUf2eNFqwZBqPtzybBb6jaHBRaJ0XdE3lh2OeE9/OF0BtLwiYPDKsVTx
12
+ IekbNf7l/DREy+YbUOxQLceeHXrvbYLiGWecP0a7CqHGj9ZNY1oJThK3AwrS
13
+ d4yHa9Wnx/GaZUNtWud1BaP9g3sVX+sRV9xtnI96dw==
14
+ </Modulus>
15
+ <Exponent>AQAB</Exponent>
16
+ <P>
17
+ 3WSb72a1erb6jcLkyZA2Y21VNIipGz+ta1RP+iacs3xnktFsxgTYgqWyt6SW
18
+ Z2rStp0u4vb/IAHyKhgJPNTUSi2u0G44MOsRxMC/FWTF8zdyrDF4BjPBM4j8
19
+ 4nAmE/FQYv5F8ldDkakc96zEPiTk5Fka3MPeN8mMk6/OA59JdF0=
20
+ </P>
21
+ <Q>
22
+ 1IRVid5SsDrOwJQAEKkdT436XEb0sVWe9AcU8JyaCEEMj0NPzownNbIrebPo
23
+ fMYdDHikopQpr2XqxZYDbb7AneoHkhEV26TfpPVbN4wBJFXih3lAP2n5hqhg
24
+ qHGp5Wq2Lu7jUS376Ruw3bhwW+MiWpXv1xhMTZ8AtDfnZFFNvOM=
25
+ </Q>
26
+ <DP>
27
+ Fo5KiNCJCtCbpFfH4XVM5UJdXPXTbNBHBdlYMJ9AddTl5IJrt50ExgLFu4oM
28
+ PMsYXryS61LI2WT5XCqIvmbcnhYbambgWLOKYuZUUYSr2kS67So5FUCunWaG
29
+ hTdx2bRLQVqwm6kiXDPDnMRAViiCHXWqk/VsrXheVymhLqNK440=
30
+ </DP>
31
+ <DQ>
32
+ mowSWMzhfV+G8+2tjnAt7KjnpSvEzyHhEr4DsGdybQZBR/4/j4nFCfukOkFn
33
+ lTXN8j/aGpF9Lx0C+uX5YFoUYcLL9qGOL8lbCu+TgnXCbtY2gybeXj+HQzI3
34
+ +MeQMlLEYqU/ks3KIOAOY2+55ljrpszbOqVk+B3luSnekMm/qtk=
35
+ </DQ>
36
+ <InverseQ>
37
+ aP5e5F1j6s82Pm7dCpH3mRZWnfZIKqoNQIq2BO8vA9/WrdFI2C27uNhxCp2Z
38
+ DMulRdBZcoeHcwJjnyDzg4I4gBZ2nSKkVdlN1REoTjLBBdlHi8XKiXzxvpIt
39
+ c2wjNC2AKHaJqj/dnh3bbTAQD1iUAxPmmLJYYkhfZ2i1IrTVxZE=
40
+ </InverseQ>
41
+ <D>
42
+ PUfM+Aq6kZSVWAetsL3EajKAxOuwQCDhVx+ovW4j+DQ8Y+WiTEyfShNV9qVD
43
+ 0PBltz3omch1GjpFhQn6OaRvraeIDH9HXttb3FOjr2zzYG4yrrYbPSRWoYj6
44
+ 3ZWiIP2O7zdl0caGQHezfNcYa2N0NTG99DGc3/q6EnhlvjWQsSbiEjmxcPx8
45
+ fmV1i4DoflMQ383nsixAFapgrROUAtCgMvhWn1kSeoojKd+e4eKZxa/SNYul
46
+ sBJWNFkmo1CZH4YTqlPM+IwYeDUOnOUGNxGurRZ3qQdWs2N2ZQhnrvlh+zpz
47
+ urD2hwAz6gQXP7mxxMR1xHtAD8XQ+w4OiJK6VWjoIQ==
48
+ </D>
49
+ </RSAKeyValue>
50
+ :certificate: |
51
+ -----BEGIN CERTIFICATE-----
52
+ MIIDYjCCAkoCAQAwDQYJKoZIhvcNAQELBQAwcTELMAkGA1UEBhMCUlUxDzANBgNV
53
+ BAgMBk1vc2NvdzEPMA0GA1UEBwwGTW9zY293MRMwEQYDVQQKDApPcmFuZ2VkYXRh
54
+ MQ8wDQYDVQQLDAZOZWJ1bGExGjAYBgNVBAMMEXd3dy5vcmFuZ2VkYXRhLnJ1MB4X
55
+ DTE4MDMxNTE2NDYwMVoXDTI4MDMxMjE2NDYwMVowfTELMAkGA1UEBhMCUlUxDzAN
56
+ BgNVBAgMBk1vc2NvdzEPMA0GA1UEBwwGTW9zY293MR8wHQYDVQQKDBZPcmFuZ2Vk
57
+ YXRhIHRlc3QgY2xpZW50MRMwEQYDVQQLDApFLWNvbW1lcmNlMRYwFAYDVQQDDA1v
58
+ cmFuZ2VkYXRhLnJ1MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAo7XZ
59
+ +VUUo9p+Q0zPmlt1eThA8NmVVAgNXkVDZoz3umyEnnm2d4R5Voxf4y6fuesW3Za8
60
+ /ImKWLbQ3/S/pHZKWiz75ElSfpnYJfMRuLAaqqs0eFfxmHbHi8Mgg9zjAMdILpR6
61
+ eEaP7qeCNRom3Zb6ziYoWEmDC2ZFFu9995rjkn7CtV3noWZveOCGExjM7WTkql8L
62
+ v1PX3ee3fXaEC7Kefxl4O/4w7agEceKRHlc0l3iwVJaKittQwAQd3ieUwoqsxzPH
63
+ dRwB4IU9aI6IjfqteyD51s7xd+ayM/O4j+aJ/HBhJajDHBcGWKytxv0f6YpqPUAc
64
+ 25fRAXVa0Gsei6eY/QIDAQABMA0GCSqGSIb3DQEBCwUAA4IBAQCv/Vcxh2lMt8RV
65
+ Al0V9xIst0ZdjH22yTOUCOiH9PZgeagqrjTLT3ycWAdbZZUpzcFSdOmPUsgQ7Eqz
66
+ +TpcY5lmYFInLwJK/Afjqsb5LK2irGKT254p5qzD9rSRlM42wxRzQTA0BWX3mmhi
67
+ zwdrfLAvyCw1gHBbUZNf3eemBCY+8RRGPRAqD2XbyIya1bX0AHLXbx5dBe9EIOG/
68
+ F46WbTlrkR7kc06eiacTiGYwNdcywJ2KOcvmnXPup8Os6KOWe197CIathDHeiG2C
69
+ mQlsQDF/d7W4G/+l6Q66BhfRtuhp99gkT8P8j82X6ChrwbgQ5+vya3SytJ0wmIg2
70
+ 67jOKmGK
71
+ -----END CERTIFICATE-----
72
+ :certificate_key: |
73
+ -----BEGIN PRIVATE KEY-----
74
+ MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCjtdn5VRSj2n5D
75
+ TM+aW3V5OEDw2ZVUCA1eRUNmjPe6bISeebZ3hHlWjF/jLp+56xbdlrz8iYpYttDf
76
+ 9L+kdkpaLPvkSVJ+mdgl8xG4sBqqqzR4V/GYdseLwyCD3OMAx0gulHp4Ro/up4I1
77
+ GibdlvrOJihYSYMLZkUW7333muOSfsK1XeehZm944IYTGMztZOSqXwu/U9fd57d9
78
+ doQLsp5/GXg7/jDtqARx4pEeVzSXeLBUloqK21DABB3eJ5TCiqzHM8d1HAHghT1o
79
+ joiN+q17IPnWzvF35rIz87iP5on8cGElqMMcFwZYrK3G/R/pimo9QBzbl9EBdVrQ
80
+ ax6Lp5j9AgMBAAECggEAL5qkrKT54H+bcZR3Vco8iag68g5DJvFEeeIoLDzXmGUP
81
+ 10lLLsvdwLYG9/fJyHU86+h2QfT4vr1CVa1EwN0I19n20TYk/91ahgZ9Y7gJuREZ
82
+ q9jeztfTRKfT36Quej54ldrlFe5m0h3xdeGJ5auOeL2Nw8Z0ja8KbhXsCkEG5cTx
83
+ ZvXB0XlFoAJOp8AZvU3ZNBpmpItFlcl2aBXwRCb72DUjLkpnZf2kFDNorc1wFZ2e
84
+ DO/pujT6EtQ1r5qb2kUuj4GpCaHffOB/ukz3dg3bBhompTYdhax0RlZs2vNsUusm
85
+ 6oYsUS5nWmJfnrh32Te03Fdzc2U8/XUflJzKL/0QvQKBgQDOpNQvCCxwvthZXART
86
+ q0fl9NY0fxlSqUpxd1BB4DYCg6Sg5kVvfwf7rdb5bbP4aNCC/9m4MgXTD0DGfEhM
87
+ FnYPVNKTzwLMBftBQdzDN6766j5lI49evwnh855EFAR5GyaIWh2n7tT3NUOstogp
88
+ kpwhzsPGH1WkEO1QLcBDyzPI3wKBgQDKz94V8au1EVKuRBR+c5gNJpF+zmUu2t2C
89
+ ZlPtYIuWaxMbqitmeCmNBQQZK+oLQdSUMkgMvYVpKriPk6AgnY7+1F+OOeg+ezPU
90
+ G+J4Vi8Yx/kZPhXoBuW745twux+q8WOBwEj2WeMy5p1F/V3qlu70HA3kbsrXdB+R
91
+ 0bFVAxCtowKBgFTtq4M08cbYuORpDCIzGBarvMnQnuC5US43IlYgxzHbVvMGEO2V
92
+ IPvQY7UZ4EitE11zt9CbRoeLEk1BURlsddMxQmabQwQFRVF5tzjIjvLzCPfaWJdR
93
+ Hsetr5M9QuVfQkPx/ZRCdWawjoLSdj3X0rGWYCHySOloR5CXbRiv0DWzAoGAF3XW
94
+ Ldmn0Ckx1EDB0iLS+up0OCPt5m6g4v2tRa8+VmcKbc/Qd2j8/XgQEk1XJHg3+/CZ
95
+ Dwg5T4IGmW0tP7iaGvY8G3qtV9TumOGk3+CwUACJ2xaoeA+cMZDRoUe0ERUdOpwg
96
+ lIavVmsA1GDLpWBSQeCg5sS+KBAhur9z8O6K1lsCgYEAj7TLLE0jLNXRRfkfWzy5
97
+ RsJezMCQS9fjtJrLGB3BbYxqtebP2owp1qjmKMQioW5QjRxRCOyT2KrHjb31hRsp
98
+ Hk3Wi0OKOEuKNwmAZczbjcPH4caPZPeL6LMDtFFMsFX2BW7TnC8FcoVr2KPO/FG/
99
+ xs4KtXC9j5rrvBowJ0LbJ2U=
100
+ -----END PRIVATE KEY-----
101
+ :certificate_key_pass: '1234'
@@ -0,0 +1,77 @@
1
+ # frozen_string_literal: true
2
+
3
+ module OrangeData
4
+
5
+ class Receipt
6
+ attr_accessor :id, :inn, :group, :key_name
7
+
8
+ def initialize id:SecureRandom.uuid, inn:, group:nil, key_name:nil
9
+ @id = id
10
+ @inn = inn
11
+ @group = group
12
+ @key_name = key_name
13
+ yield self if block_given?
14
+ end
15
+ end
16
+
17
+ class ReceiptContent
18
+
19
+ module AgentTypeSerializer
20
+ AGENT_TYPE_BITS = { # 1057 (в чеках/БСО должно соответствовать отчету о (пере)регистрации ККТ)
21
+ bank_payment_agent: (1 << 0), # банковский платежный агент
22
+ bank_payment_subagent: (1 << 1), # банковский платежный субагент
23
+ payment_agent: (1 << 2), # платежный агент
24
+ payment_subagent: (1 << 3), # платежный субагент
25
+ attorney: (1 << 4), # поверенный
26
+ commission_agent: (1 << 5), # комиссионер
27
+ other_agent: (1 << 6), # иной агент
28
+ }.freeze
29
+
30
+ def self.load data
31
+ data = data.to_i
32
+ AGENT_TYPE_BITS.select{|(k,v)| (data & v) > 0}.map(&:first)
33
+ end
34
+
35
+ def self.dump val
36
+ val = [val] unless val.is_a?(Array)
37
+ val.map{|v| AGENT_TYPE_BITS[v] || raise "unknown agent_type #{v}"}.reduce(:|)
38
+ end
39
+ end
40
+
41
+
42
+ RECEIPT_TYPES = { # 1054:
43
+
44
+ }.freeze
45
+
46
+ FIELDS = {
47
+ type: {
48
+ name: 'Признак расчета',
49
+ tag_num: 1054,
50
+ mapper: :enum,
51
+ enum_values: {
52
+ income: 1, # Приход
53
+ return_income: 2, # Возврат прихода
54
+ expense: 3, # Расход
55
+ return_expense: 4 # Возврат расхода
56
+ }
57
+ }
58
+ }
59
+
60
+
61
+
62
+ def initialize type
63
+ @positions = []
64
+ @payments = []
65
+ end
66
+
67
+ def agent_type
68
+ AgentTypeSerializer.load(@agent_type)
69
+ end
70
+
71
+ def agent_type= val
72
+ @agent_type = AgentTypeSerializer.dump(val)
73
+ end
74
+
75
+ end
76
+
77
+ end
@@ -0,0 +1,134 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'openssl'
4
+ require 'base64'
5
+ require 'faraday'
6
+ require 'faraday_middleware'
7
+
8
+ # handles low-level http requests to orangedata, including auth
9
+ module OrangeData
10
+ class Transport
11
+
12
+ def initialize api_url=default_api_url, credentials=Credentials.default_test
13
+ raise ArgumentError, "Need full credentials for connection" unless credentials.valid?
14
+ @credentials = credentials
15
+ @api_url = api_url
16
+ end
17
+
18
+ def default_api_url
19
+ # production: https://api.orangedata.ru:12003/api/v2/
20
+ # test: https://apip.orangedata.ru:2443/api/v2/
21
+ "https://apip.orangedata.ru:2443/api/v2/"
22
+ end
23
+
24
+ class RequestSignatureMiddleware < Faraday::Middleware
25
+ def initialize(app, signature_key)
26
+ @app = app
27
+ @signature_key = signature_key
28
+ end
29
+
30
+ def call(env)
31
+ if env.body
32
+ signature = @signature_key.sign(OpenSSL::Digest::SHA256.new, env.body)
33
+ env.request_headers['X-Signature'] = Base64.strict_encode64(signature)
34
+ end
35
+ @app.call(env)
36
+ end
37
+ end
38
+
39
+ def transport
40
+ @transport ||= Faraday.new(
41
+ url: @api_url,
42
+ ssl: {
43
+ client_cert: @credentials.certificate,
44
+ client_key: @credentials.certificate_key,
45
+ }
46
+ ) do |conn|
47
+ conn.headers['User-Agent'] = "OrangeDataRuby/#{OrangeData::VERSION}"
48
+ conn.headers['Accept'] = "application/json"
49
+ conn.request(:json)
50
+ conn.use(RequestSignatureMiddleware, @credentials.signature_key)
51
+ conn.response :json, content_type: /\bjson$/
52
+ conn.adapter(Faraday.default_adapter)
53
+ end
54
+ end
55
+
56
+ def raw_post method, data
57
+ transport.post(method, data)
58
+ end
59
+
60
+ def post_entity sub_url, data
61
+ res = raw_post(sub_url, data)
62
+
63
+ case res.status
64
+ when 201
65
+ return true
66
+ when 401
67
+ raise 'Unauthorized'
68
+ when 409
69
+ raise "Conflict"
70
+ when 400
71
+ raise "Invalid doc: #{res.body}"
72
+ else
73
+ raise "Unknown code from OD: #{res.status} #{res.reason_phrase} #{res.body}"
74
+ end
75
+ end
76
+
77
+ def get_entity sub_url
78
+ res = transport.get(sub_url)
79
+
80
+ case res.status
81
+ when 200
82
+ return res.body
83
+ when 400
84
+ raise "Cannot get doc: #{res.body}"
85
+ when 401
86
+ raise 'Unauthorized'
87
+ end
88
+ end
89
+
90
+ # Below actual methods from api
91
+
92
+ def ping
93
+ res = transport.get(''){|r|
94
+ r.headers['Accept'] = 'text/plain'
95
+ }
96
+
97
+ return res.status == 200 && res.body == "Nebula.Api v2"
98
+ rescue StandardError => e
99
+ return false
100
+ end
101
+
102
+ def post_document_validate data
103
+ res = raw_post 'validateDocument', data
104
+
105
+ case res.status
106
+ when 200
107
+ return true
108
+ when 400
109
+ return res.body
110
+ when 401
111
+ raise 'Unauthorized'
112
+ else
113
+ raise "Unexpected response: #{res.status} #{res.reason_phrase}"
114
+ end
115
+ end
116
+
117
+ def post_document data
118
+ post_entity 'documents', data
119
+ end
120
+
121
+ def get_document inn, document_id
122
+ get_entity "documents/#{inn}/status/#{document_id}"
123
+ end
124
+
125
+ def post_correction data
126
+ post_entity 'corrections', data
127
+ end
128
+
129
+ def get_correction inn, document_id
130
+ get_entity "corrections/#{inn}/status/#{document_id}"
131
+ end
132
+
133
+ end
134
+ end
@@ -0,0 +1,3 @@
1
+ module OrangeData
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1 @@
1
+ require_relative 'orange_data'
@@ -0,0 +1,33 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "orange_data/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "orangedata"
8
+ spec.version = OrangeData::VERSION
9
+ spec.authors = ["Vasily Fedoseyev"]
10
+ spec.email = ["vasilyfedoseyev@gmail.com"]
11
+
12
+ spec.summary = %q{Ruby client for orangedata.ru service}
13
+ spec.description = %q{Ruby client for orangedata.ru service}
14
+ spec.homepage = "https://github.com/Vasfed/orangedata"
15
+ spec.license = "MIT"
16
+
17
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
18
+ `git ls-files -z`.split("\x0").reject { |f| 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.15"
26
+ spec.add_dependency "faraday_middleware"
27
+
28
+ spec.add_development_dependency "bundler", "~> 1.16"
29
+ spec.add_development_dependency "rake", "~> 10.0"
30
+ spec.add_development_dependency "rspec"
31
+ spec.add_development_dependency "webmock"
32
+ spec.add_development_dependency "rubocop"
33
+ end
metadata ADDED
@@ -0,0 +1,160 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: orangedata
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Vasily Fedoseyev
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2018-10-22 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.15'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0.15'
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'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: bundler
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '1.16'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '1.16'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '10.0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '10.0'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rspec
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - ">="
74
+ - !ruby/object:Gem::Version
75
+ version: '0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - ">="
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: webmock
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - ">="
88
+ - !ruby/object:Gem::Version
89
+ version: '0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - ">="
95
+ - !ruby/object:Gem::Version
96
+ version: '0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: rubocop
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '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'
111
+ description: Ruby client for orangedata.ru service
112
+ email:
113
+ - vasilyfedoseyev@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - Gemfile.lock
123
+ - LICENSE.txt
124
+ - README.md
125
+ - Rakefile
126
+ - bin/console
127
+ - bin/setup
128
+ - lib/orange_data.rb
129
+ - lib/orange_data/credentials.rb
130
+ - lib/orange_data/credentials_test.yml
131
+ - lib/orange_data/receipt.rb
132
+ - lib/orange_data/transport.rb
133
+ - lib/orange_data/version.rb
134
+ - lib/orangedata.rb
135
+ - orangedata.gemspec
136
+ homepage: https://github.com/Vasfed/orangedata
137
+ licenses:
138
+ - MIT
139
+ metadata: {}
140
+ post_install_message:
141
+ rdoc_options: []
142
+ require_paths:
143
+ - lib
144
+ required_ruby_version: !ruby/object:Gem::Requirement
145
+ requirements:
146
+ - - ">="
147
+ - !ruby/object:Gem::Version
148
+ version: '0'
149
+ required_rubygems_version: !ruby/object:Gem::Requirement
150
+ requirements:
151
+ - - ">="
152
+ - !ruby/object:Gem::Version
153
+ version: '0'
154
+ requirements: []
155
+ rubyforge_project:
156
+ rubygems_version: 2.6.3
157
+ signing_key:
158
+ specification_version: 4
159
+ summary: Ruby client for orangedata.ru service
160
+ test_files: []