fiscalizer 0.0.12 → 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +2 -1
- data/.rspec +2 -0
- data/Gemfile +0 -4
- data/LICENSE.txt +1 -1
- data/README.md +187 -171
- data/bin/console +14 -0
- data/bin/setup +7 -0
- data/doc/Tehnicka specifikacija za korisnike 1.4.pdf +0 -0
- data/fiscalizer.gemspec +14 -13
- data/lib/fiscalizer.rb +33 -4
- data/lib/fiscalizer/constants.rb +11 -0
- data/lib/fiscalizer/data_objects/echo.rb +10 -0
- data/lib/fiscalizer/data_objects/fee.rb +18 -0
- data/lib/fiscalizer/data_objects/invoice.rb +79 -0
- data/lib/fiscalizer/data_objects/office.rb +41 -0
- data/lib/fiscalizer/{tax.rb → data_objects/tax.rb} +3 -5
- data/lib/fiscalizer/deserializers/base.rb +58 -0
- data/lib/fiscalizer/deserializers/echo.rb +13 -0
- data/lib/fiscalizer/deserializers/invoice.rb +9 -0
- data/lib/fiscalizer/deserializers/office.rb +6 -0
- data/lib/fiscalizer/fiscalizer.rb +36 -152
- data/lib/fiscalizer/fiscalizers/base.rb +54 -0
- data/lib/fiscalizer/fiscalizers/echo.rb +13 -0
- data/lib/fiscalizer/fiscalizers/invoice.rb +24 -0
- data/lib/fiscalizer/fiscalizers/office.rb +15 -0
- data/lib/fiscalizer/serializers/base.rb +58 -0
- data/lib/fiscalizer/serializers/echo.rb +21 -0
- data/lib/fiscalizer/serializers/invoice.rb +92 -0
- data/lib/fiscalizer/serializers/office.rb +85 -0
- data/lib/fiscalizer/serializers/signature.rb +62 -0
- data/lib/fiscalizer/serializers/tax.rb +81 -0
- data/lib/fiscalizer/services/request_sender.rb +68 -0
- data/lib/fiscalizer/services/security_code_generator.rb +29 -0
- data/lib/fiscalizer/version.rb +1 -1
- data/spec/fiscalizer_spec.rb +119 -0
- data/spec/spec_helper.rb +9 -0
- metadata +67 -39
- data/doc/README.md +0 -3
- data/doc/Tehnicka_specifikacija_za_korisnike_1.2.pdf +0 -0
- data/example/README.md +0 -25
- data/example/echo_P12.rb +0 -13
- data/example/echo_public_and_private_keys.rb +0 -16
- data/example/invoice_fiscalization_passing_arguments.rb +0 -44
- data/example/invoice_fiscalization_passing_object.rb +0 -48
- data/example/office_fiscalization_passing_arguments.rb +0 -22
- data/example/office_fiscalization_passing_object.rb +0 -26
- data/lib/README.md +0 -0
- data/lib/fiscalizer/README.md +0 -9
- data/lib/fiscalizer/communication.rb +0 -305
- data/lib/fiscalizer/echo.rb +0 -11
- data/lib/fiscalizer/fee.rb +0 -20
- data/lib/fiscalizer/invoice.rb +0 -190
- data/lib/fiscalizer/office.rb +0 -66
- data/lib/fiscalizer/response.rb +0 -124
- data/test/README.md +0 -13
- data/test/test_echo.rb +0 -20
- data/test/test_fee +0 -64
- data/test/test_fiscalizer.rb +0 -222
- data/test/test_fiscalizer_communication.rb +0 -28
- data/test/test_invoice.rb +0 -11
- data/test/test_office.rb +0 -11
- data/test/test_tax.rb +0 -89
@@ -0,0 +1,54 @@
|
|
1
|
+
class Fiscalizer
|
2
|
+
module Fiscalizers
|
3
|
+
class Base
|
4
|
+
# rubocop:disable Metrics/ParameterLists
|
5
|
+
def initialize(app_cert_path, password, timeout, demo, ca_cert_path, object)
|
6
|
+
@app_cert_path = app_cert_path
|
7
|
+
@password = password
|
8
|
+
@timeout = timeout
|
9
|
+
@demo = demo
|
10
|
+
@ca_cert_path = ca_cert_path
|
11
|
+
@object = object
|
12
|
+
end
|
13
|
+
|
14
|
+
attr_reader :app_cert_path, :password, :timeout, :demo, :ca_cert_path, :object
|
15
|
+
|
16
|
+
def call
|
17
|
+
# check_echo
|
18
|
+
send_request
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def send_request
|
24
|
+
response = request_sender.send(request_message)
|
25
|
+
deserialize(response)
|
26
|
+
end
|
27
|
+
|
28
|
+
def request_message
|
29
|
+
serializer.new(object, app_private_key, app_public_key, demo).call
|
30
|
+
end
|
31
|
+
|
32
|
+
def deserialize(response)
|
33
|
+
deserializer.new(response.body, object)
|
34
|
+
end
|
35
|
+
|
36
|
+
def request_sender
|
37
|
+
@request_sender ||=
|
38
|
+
Fiscalizer::RequestSender.new(extracted_app_cert, password, timeout, demo, ca_cert_path)
|
39
|
+
end
|
40
|
+
|
41
|
+
def app_public_key
|
42
|
+
@app_public_key ||= OpenSSL::X509::Certificate.new(extracted_app_cert.certificate)
|
43
|
+
end
|
44
|
+
|
45
|
+
def app_private_key
|
46
|
+
@app_private_key ||= OpenSSL::PKey::RSA.new(extracted_app_cert.key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def extracted_app_cert
|
50
|
+
@extracted_app_cert ||= OpenSSL::PKCS12.new(File.read(app_cert_path), password)
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
class Fiscalizer
|
2
|
+
module Fiscalizers
|
3
|
+
class Invoice < Base
|
4
|
+
def call
|
5
|
+
generate_security_code
|
6
|
+
send_request
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def generate_security_code
|
12
|
+
SecurityCodeGenerator.new(object, app_private_key).call
|
13
|
+
end
|
14
|
+
|
15
|
+
def serializer
|
16
|
+
Serializers::Invoice
|
17
|
+
end
|
18
|
+
|
19
|
+
def deserializer
|
20
|
+
Deserializers::Invoice
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class Fiscalizer
|
2
|
+
module Serializers
|
3
|
+
class Base
|
4
|
+
include Constants
|
5
|
+
|
6
|
+
def initialize(object, private_key, public_key, demo)
|
7
|
+
@object = object
|
8
|
+
@private_key = private_key
|
9
|
+
@public_key = public_key
|
10
|
+
@demo = demo
|
11
|
+
end
|
12
|
+
|
13
|
+
attr_reader :object, :private_key, :public_key, :demo
|
14
|
+
|
15
|
+
def call
|
16
|
+
sign_xml
|
17
|
+
end
|
18
|
+
|
19
|
+
private
|
20
|
+
|
21
|
+
def sign_xml
|
22
|
+
document = Xmldsig_fiscalizer::SignedDocument.new(xml_with_soap_envelope.doc.root.to_xml)
|
23
|
+
signed_xml = document.sign(private_key)
|
24
|
+
signed_xml.sub!('<?xml version="1.0"?>', '')
|
25
|
+
signed_xml = signed_xml.gsub(/^$\n/, '')
|
26
|
+
object.generated_xml = signed_xml
|
27
|
+
signed_xml
|
28
|
+
end
|
29
|
+
|
30
|
+
def xml_with_soap_envelope
|
31
|
+
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
32
|
+
xml['soapenv'].Envelope('xmlns:soapenv' => 'http://schemas.xmlsoap.org/soap/envelope/') do
|
33
|
+
xml['soapenv'].Body do
|
34
|
+
xml << raw_xml.doc.root.to_xml
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def root_hash
|
41
|
+
{
|
42
|
+
'xmlns:tns' => TNS,
|
43
|
+
'xmlns:xsi' => XSI,
|
44
|
+
'xsi:schemaLocation' => SCHEMA_LOCATION,
|
45
|
+
'Id' => message_id
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def add_signature(xml)
|
50
|
+
Serializers::Signature.new(xml, "##{message_id}", public_key, cert_issuer).call
|
51
|
+
end
|
52
|
+
|
53
|
+
def cert_issuer
|
54
|
+
demo ? DEMO_CERT_ISSUER : PROD_CERT_ISSUER
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
class Fiscalizer
|
2
|
+
module Serializers
|
3
|
+
class Echo < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def message_id
|
7
|
+
'EchoRequest'
|
8
|
+
end
|
9
|
+
|
10
|
+
def raw_xml
|
11
|
+
@raw_xml ||= begin
|
12
|
+
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
13
|
+
xml['tns'].EchoRequest(root_hash) do
|
14
|
+
xml.text object.message
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
class Fiscalizer
|
2
|
+
module Serializers
|
3
|
+
class Invoice < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def message_id
|
7
|
+
'RacunZahtjev'
|
8
|
+
end
|
9
|
+
|
10
|
+
def raw_xml
|
11
|
+
@raw_xml ||= begin
|
12
|
+
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
13
|
+
xml['tns'].RacunZahtjev(root_hash) do
|
14
|
+
add_header(xml)
|
15
|
+
add_body(xml)
|
16
|
+
add_signature(xml)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_header(xml)
|
23
|
+
xml['tns'].Zaglavlje do
|
24
|
+
xml['tns'].IdPoruke object.uuid
|
25
|
+
xml['tns'].DatumVrijeme object.time_sent_str
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_body(xml)
|
30
|
+
xml['tns'].Racun do
|
31
|
+
add_general_invoice_info(xml)
|
32
|
+
add_invoice_number_info(xml)
|
33
|
+
add_invoice_tax_info(xml)
|
34
|
+
add_invoice_fee_info(xml)
|
35
|
+
add_invoice_summary(xml)
|
36
|
+
add_paragon_label(xml)
|
37
|
+
add_specific_purpose(xml)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
def add_general_invoice_info(xml)
|
42
|
+
xml['tns'].Oib object.pin
|
43
|
+
xml['tns'].USustPdv object.in_vat_system
|
44
|
+
xml['tns'].DatVrijeme object.time_issued_str
|
45
|
+
xml['tns'].OznSlijed object.consistance_mark
|
46
|
+
end
|
47
|
+
|
48
|
+
def add_invoice_number_info(xml)
|
49
|
+
xml['tns'].BrRac do
|
50
|
+
xml['tns'].BrOznRac object.issued_number.to_s
|
51
|
+
xml['tns'].OznPosPr object.issued_office.to_s
|
52
|
+
xml['tns'].OznNapUr object.issued_machine.to_s
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
def add_invoice_tax_info(xml)
|
57
|
+
Serializers::Tax.new(xml, object).call
|
58
|
+
end
|
59
|
+
|
60
|
+
def add_invoice_fee_info(xml)
|
61
|
+
return if object.fees.empty?
|
62
|
+
|
63
|
+
xml['tns'].Naknade do
|
64
|
+
object.fees.each do |fee|
|
65
|
+
xml['tns'].Naknada do
|
66
|
+
xml['tns'].NazivN fee.name
|
67
|
+
xml['tns'].IznosN fee.value
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
def add_invoice_summary(xml)
|
74
|
+
xml['tns'].IznosUkupno object.summed_total_str
|
75
|
+
xml['tns'].NacinPlac object.payment_method
|
76
|
+
xml['tns'].OibOper object.operator_pin
|
77
|
+
xml['tns'].ZastKod object.security_code
|
78
|
+
xml['tns'].NakDost object.subsequent_delivery
|
79
|
+
end
|
80
|
+
|
81
|
+
def add_paragon_label(xml)
|
82
|
+
return if object.paragon_label.nil?
|
83
|
+
xml['tns'].ParagonBrRac object.paragon_label
|
84
|
+
end
|
85
|
+
|
86
|
+
def add_specific_purpose(xml)
|
87
|
+
return if object.specific_purpose.nil?
|
88
|
+
xml['tns'].SpecNamj object.specific_purpose
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -0,0 +1,85 @@
|
|
1
|
+
class Fiscalizer
|
2
|
+
module Serializers
|
3
|
+
class Office < Base
|
4
|
+
private
|
5
|
+
|
6
|
+
def message_id
|
7
|
+
'PoslovniProstorZahtjev'
|
8
|
+
end
|
9
|
+
|
10
|
+
def raw_xml
|
11
|
+
@raw_xml ||= begin
|
12
|
+
Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
|
13
|
+
xml['tns'].PoslovniProstorZahtjev(root_hash) do
|
14
|
+
add_header(xml)
|
15
|
+
add_body(xml)
|
16
|
+
add_signature(xml)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
def add_header(xml)
|
23
|
+
xml['tns'].Zaglavlje do
|
24
|
+
xml['tns'].IdPoruke object.uuid
|
25
|
+
xml['tns'].DatumVrijeme object.time_sent_str
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_body(xml)
|
30
|
+
xml['tns'].PoslovniProstor do
|
31
|
+
add_general_info(xml)
|
32
|
+
add_address_info(xml)
|
33
|
+
add_time_info(xml)
|
34
|
+
add_closure_mark(xml)
|
35
|
+
add_specific_purpose(xml)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_general_info(xml)
|
40
|
+
xml['tns'].Oib object.pin
|
41
|
+
xml['tns'].OznPoslProstora object.office_label
|
42
|
+
end
|
43
|
+
|
44
|
+
def add_address_info(xml)
|
45
|
+
xml['tns'].AdresniPodatak do
|
46
|
+
add_address(xml)
|
47
|
+
add_other_address(xml)
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def add_address(xml)
|
52
|
+
return if object.adress_other
|
53
|
+
|
54
|
+
xml['tns'].Adresa do
|
55
|
+
xml['tns'].Ulica object.adress_street_name
|
56
|
+
xml['tns'].KucniBroj object.adress_house_num
|
57
|
+
xml['tns'].KucniBrojDodatak object.adress_house_num_addendum
|
58
|
+
xml['tns'].BrojPoste object.adress_post_num
|
59
|
+
xml['tns'].Naselje object.adress_settlement
|
60
|
+
xml['tns'].Opcina object.adress_township
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
def add_other_address(xml)
|
65
|
+
return if object.adress_other.nil?
|
66
|
+
xml['tns'].OstaliTipoviP object.adress_other
|
67
|
+
end
|
68
|
+
|
69
|
+
def add_time_info(xml)
|
70
|
+
xml['tns'].RadnoVrijeme object.office_time
|
71
|
+
xml['tns'].DatumPocetkaPrimjene object.take_effect_date_str
|
72
|
+
end
|
73
|
+
|
74
|
+
def add_closure_mark(xml)
|
75
|
+
return if object.closure_mark.nil?
|
76
|
+
xml['tns'].OznakaZatvaranja object.closure_mark
|
77
|
+
end
|
78
|
+
|
79
|
+
def add_specific_purpose(xml)
|
80
|
+
return if object.specific_purpose.nil?
|
81
|
+
xml['tns'].SpecNamj object.specific_purpose
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
@@ -0,0 +1,62 @@
|
|
1
|
+
class Fiscalizer
|
2
|
+
module Serializers
|
3
|
+
class Signature
|
4
|
+
def initialize(xml, reference, public_key, cert_issuer)
|
5
|
+
@xml = xml
|
6
|
+
@reference = reference
|
7
|
+
@public_key = public_key
|
8
|
+
@cert_issuer = cert_issuer
|
9
|
+
end
|
10
|
+
|
11
|
+
attr_reader :xml, :reference, :public_key, :cert_issuer
|
12
|
+
|
13
|
+
def call
|
14
|
+
xml.Signature('xmlns' => 'http://www.w3.org/2000/09/xmldsig#') do
|
15
|
+
add_signed_info
|
16
|
+
add_signature_value
|
17
|
+
add_key_info
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
def add_signed_info
|
24
|
+
xml.SignedInfo do
|
25
|
+
xml.CanonicalizationMethod('Algorithm' => 'http://www.w3.org/2001/10/xml-exc-c14n#')
|
26
|
+
xml.SignatureMethod('Algorithm' => 'http://www.w3.org/2000/09/xmldsig#rsa-sha1')
|
27
|
+
xml.Reference('URI' => reference) do
|
28
|
+
xml.Transforms do
|
29
|
+
xml.Transform('Algorithm' => 'http://www.w3.org/2000/09/xmldsig#enveloped-signature')
|
30
|
+
xml.Transform('Algorithm' => 'http://www.w3.org/2001/10/xml-exc-c14n#')
|
31
|
+
end
|
32
|
+
xml.DigestMethod('Algorithm' => 'http://www.w3.org/2000/09/xmldsig#sha1')
|
33
|
+
xml.DigestValue
|
34
|
+
end
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
def add_signature_value
|
39
|
+
xml.SignatureValue
|
40
|
+
end
|
41
|
+
|
42
|
+
def add_key_info
|
43
|
+
xml.KeyInfo do
|
44
|
+
xml.X509Data do
|
45
|
+
xml.X509Certificate public_key_string
|
46
|
+
xml.X509IssuerSerial do
|
47
|
+
xml.X509IssuerName cert_issuer
|
48
|
+
xml.X509SerialNumber '1053520622'
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
def public_key_string
|
55
|
+
public_key.to_s
|
56
|
+
.gsub('-----BEGIN CERTIFICATE-----', '')
|
57
|
+
.gsub('-----END CERTIFICATE-----', '')
|
58
|
+
.gsub("\n", '')
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
class Fiscalizer
|
2
|
+
module Serializers
|
3
|
+
class Tax
|
4
|
+
def initialize(xml, object)
|
5
|
+
@xml = xml
|
6
|
+
@object = object
|
7
|
+
end
|
8
|
+
|
9
|
+
attr_reader :xml, :object
|
10
|
+
|
11
|
+
def call
|
12
|
+
add_vat_tax
|
13
|
+
add_spending_tax
|
14
|
+
add_other_taxes
|
15
|
+
add_general_tax_info
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def add_vat_tax
|
21
|
+
return if object.tax_vat.empty?
|
22
|
+
|
23
|
+
xml['tns'].Pdv do
|
24
|
+
object.tax_vat.each do |tax|
|
25
|
+
add_tax(tax)
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
def add_spending_tax
|
31
|
+
return if object.tax_spending.empty?
|
32
|
+
|
33
|
+
xml['tns'].Pnp do
|
34
|
+
object.tax_spending.each do |tax|
|
35
|
+
add_tax(tax)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
def add_other_taxes
|
41
|
+
return if object.tax_other.empty?
|
42
|
+
|
43
|
+
xml['tns'].OstaliPor do
|
44
|
+
object.tax_other.each do |tax|
|
45
|
+
add_tax(tax, true)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
def add_tax(tax, include_name = false)
|
51
|
+
xml['tns'].Porez do
|
52
|
+
xml['tns'].Naziv tax.name if include_name
|
53
|
+
xml['tns'].Stopa tax.rate_str
|
54
|
+
xml['tns'].Osnovica tax.base_str
|
55
|
+
xml['tns'].Iznos tax.total_str
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def add_general_tax_info
|
60
|
+
add_tax_liberation
|
61
|
+
add_tax_margin
|
62
|
+
add_non_taxable
|
63
|
+
end
|
64
|
+
|
65
|
+
def add_tax_liberation
|
66
|
+
return if object.value_tax_liberation_str.nil?
|
67
|
+
xml['tns'].IznosOslobPdv object.value_tax_liberation_str
|
68
|
+
end
|
69
|
+
|
70
|
+
def add_tax_margin
|
71
|
+
return if object.value_tax_margin_str.nil?
|
72
|
+
xml['tns'].IznosMarza object.value_tax_margin_str
|
73
|
+
end
|
74
|
+
|
75
|
+
def add_non_taxable
|
76
|
+
return if object.value_non_taxable_str.nil?
|
77
|
+
xml['tns'].IznosNePodlOpor object.value_non_taxable_str
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|