fiscalizer 0.0.12 → 1.0.0
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 +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
|