sefaz 0.2.0 → 1.3.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/CHANGELOG.md +115 -0
- data/Gemfile.lock +31 -17
- data/README.md +449 -17
- data/Rakefile +1 -0
- data/docs/CFe-SAT/Especificacao_SAT_v_ER_2_30_03.pdf +0 -0
- data/docs/CFe-SAT/Manual_Orientacao_SAT_v_MO_2_19_04.pdf +0 -0
- data/docs/CFe-SAT/Manual_do_Emulador_SAT-CF-e_Offline_2015_09_10.pdf +0 -0
- data/docs/CFe-SAT/_CANCEL_dataset.md +14 -0
- data/docs/CFe-SAT/_SALE_dataset.md +171 -0
- data/docs/CFe-SAT/emulador_off_line_v2_9_4.zip +0 -0
- data/docs/NFe-NFCe/ANEXO I - Leiaute e Regra de Valida/303/247/303/243o - NF-e e NFC-e.pdf +0 -0
- data/docs/NFe-NFCe/ANEXO II -Manual Especifica/303/247/303/265esT/303/251cnicas - Danfe-C/303/263digo-Barras.pdf +0 -0
- data/docs/NFe-NFCe/Anexo III - Manual de Conting/303/252ncia - NF-e.pdf +0 -0
- data/docs/NFe-NFCe/Anexo IV - Manual de Conting/303/252ncia - NFC-e.pdf +0 -0
- data/docs/NFe-NFCe/Manual de Orienta/303/247/303/243o ao Contribuinte - MOC - vers/303/243o 7.0 - NF-e e NFC-e.pdf +0 -0
- data/docs/NFe-NFCe/_dataset.md +819 -0
- data/lib/sefaz/base.rb +11 -0
- data/lib/sefaz/configuration.rb +58 -0
- data/lib/sefaz/exception.rb +14 -0
- data/lib/sefaz/refinement.rb +91 -0
- data/lib/sefaz/utils/connection.rb +35 -0
- data/lib/sefaz/utils/prawn_helper.rb +47 -0
- data/lib/sefaz/utils/signer.rb +122 -0
- data/lib/sefaz/version.rb +1 -1
- data/lib/sefaz/webservice/base.rb +13 -0
- data/lib/sefaz/webservice/nfe/auditor.rb +35 -0
- data/lib/sefaz/webservice/nfe/client.rb +426 -0
- data/lib/sefaz/webservice/nfe/connection.rb +17 -0
- data/lib/sefaz/webservice/nfe/dataset.rb +451 -0
- data/lib/sefaz/webservice/nfe/validator.rb +63 -0
- data/lib/sefaz/webservice/nfe/wsdl.rb +117 -0
- data/lib/sefaz/webservice/sat/client.rb +44 -0
- data/lib/sefaz/webservice/sat/dataset/cancel.rb +34 -0
- data/lib/sefaz/webservice/sat/dataset/sale.rb +176 -0
- data/lib/sefaz/webservice/sat/templates/base.rb +54 -0
- data/lib/sefaz/webservice/sat/templates/cupom_fiscal_55mm.rb +307 -0
- data/lib/sefaz/webservice/sat/templates/cupom_fiscal_80mm.rb +307 -0
- data/lib/sefaz.rb +45 -3
- data/sefaz.gemspec +5 -3
- metadata +80 -6
data/lib/sefaz/base.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
# Classe base para parametrização da biblioteca
|
5
|
+
class Configuration
|
6
|
+
|
7
|
+
# Configuração da formatação de valores monetários
|
8
|
+
attr_accessor :currency_format
|
9
|
+
|
10
|
+
# CF-e (Cupom Fiscal Eletrônico)
|
11
|
+
attr_accessor :cfe_qr_code_aplicativo
|
12
|
+
attr_accessor :cfe_cMP_cod_desc
|
13
|
+
|
14
|
+
# 55mm
|
15
|
+
attr_accessor :cfe_page_width_55mm
|
16
|
+
attr_accessor :cfe_margin_55mm
|
17
|
+
|
18
|
+
# 80mm
|
19
|
+
attr_accessor :cfe_page_width_80mm
|
20
|
+
attr_accessor :cfe_margin_80mm
|
21
|
+
|
22
|
+
def initialize
|
23
|
+
@currency_format = { delimiter: ".", separator: ",", unit: "", precision: 2, position: "before" }
|
24
|
+
@cfe_qr_code_aplicativo = "De olho na nota"
|
25
|
+
@cfe_cMP_cod_desc = {
|
26
|
+
"01" => "Dinheiro",
|
27
|
+
"02" => "Cheque",
|
28
|
+
"03" => "Cartão de Crédito",
|
29
|
+
"04" => "Cartão de Débito",
|
30
|
+
"05" => "Crédito Loja",
|
31
|
+
"10" => "Vale Alimentação",
|
32
|
+
"11" => "Vale Refeição",
|
33
|
+
"12" => "Vale Presente",
|
34
|
+
"13" => "Vale Combustível",
|
35
|
+
"15" => "Boleto Bancário",
|
36
|
+
"16" => "Depósito Bancário",
|
37
|
+
"17" => "Pagamento Instantâneo (PIX)",
|
38
|
+
"18" => "Transferência bancária, Carteira Digital",
|
39
|
+
"19" => "Programa de fidelidade, Cashback, Crédito Virtual",
|
40
|
+
"90" => "Sem pagamento",
|
41
|
+
"99" => "Outros"
|
42
|
+
}
|
43
|
+
|
44
|
+
# 50mm
|
45
|
+
@cfe_page_width_55mm = 55.mm
|
46
|
+
@cfe_margin_55mm = 5.mm
|
47
|
+
|
48
|
+
# 80mm
|
49
|
+
@cfe_page_width_80mm = 80.mm
|
50
|
+
@cfe_margin_80mm = 5.mm
|
51
|
+
end
|
52
|
+
|
53
|
+
def self.default
|
54
|
+
@default ||= Configuration.new
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,91 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
# Módulo base de refinamento da biblioteca
|
5
|
+
module Refinement
|
6
|
+
|
7
|
+
module String
|
8
|
+
def to_hash!
|
9
|
+
Nori.new(convert_tags_to: lambda { |key| key.to_sym }).parse(self)
|
10
|
+
end
|
11
|
+
|
12
|
+
def compress!
|
13
|
+
doc = Nokogiri::XML::Document.parse(self, nil, "utf-8", Nokogiri::XML::ParseOptions::NOBLANKS)
|
14
|
+
doc.search('*').each { |node| node.remove if node.content.strip.blank? }
|
15
|
+
doc.canonicalize
|
16
|
+
end
|
17
|
+
|
18
|
+
def mask!(mask)
|
19
|
+
index = -1
|
20
|
+
mask.gsub('#') { self[index += 1] }
|
21
|
+
end
|
22
|
+
|
23
|
+
def to_number(options={})
|
24
|
+
return self.gsub(/,/, '.').to_f if self.numeric?
|
25
|
+
nil
|
26
|
+
end
|
27
|
+
|
28
|
+
def numeric?
|
29
|
+
self =~ /^(|-)?[0-9]+((\.|,)[0-9]+)?$/ ? true : false
|
30
|
+
end
|
31
|
+
|
32
|
+
def to_currency(options = {})
|
33
|
+
return self.to_number.to_currency(options) if self.numeric?
|
34
|
+
nil
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
module Hash
|
39
|
+
def to_xml!
|
40
|
+
Gyoku.xml(self, key_converter: :none)
|
41
|
+
end
|
42
|
+
|
43
|
+
def compress!
|
44
|
+
self.to_xml!.compress!.to_hash!
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
module Currency
|
49
|
+
def to_currency(options = {})
|
50
|
+
number = self
|
51
|
+
default = Configuration.default.currency_format
|
52
|
+
options = default.merge(options)
|
53
|
+
precision = options[:precision] || default[:precision]
|
54
|
+
unit = options[:unit] || default[:unit]
|
55
|
+
position = options[:position] || default[:position]
|
56
|
+
separator = precision > 0 ? options[:separator] || default[:separator] : ""
|
57
|
+
delimiter = options[:delimiter] || default[:delimiter]
|
58
|
+
|
59
|
+
begin
|
60
|
+
parts = number.with_precision(precision).split('.')
|
61
|
+
number = parts[0].to_i.with_delimiter(delimiter) + separator + parts[1].to_s
|
62
|
+
position == "before" ? unit + number : number + unit
|
63
|
+
rescue
|
64
|
+
number
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
def with_delimiter(delimiter = ",", separator = ".")
|
69
|
+
number = self
|
70
|
+
begin
|
71
|
+
parts = number.to_s.split(separator)
|
72
|
+
parts[0].gsub!(/(\d)(?=(\d\d\d)+(?!\d))/, "\\1#{delimiter}")
|
73
|
+
parts.join separator
|
74
|
+
rescue
|
75
|
+
self
|
76
|
+
end
|
77
|
+
end
|
78
|
+
|
79
|
+
def with_precision(precision = 3)
|
80
|
+
number = self
|
81
|
+
"%01.#{precision}f" % number
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
String.class_eval { include SEFAZ::Refinement::String }
|
89
|
+
Hash.class_eval { include SEFAZ::Refinement::Hash }
|
90
|
+
Integer.class_eval { include SEFAZ::Refinement::Currency }
|
91
|
+
Float.class_eval { include SEFAZ::Refinement::Currency }
|
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Utils
|
5
|
+
# Classe base de conexão SOAP da biblioteca
|
6
|
+
class Connection
|
7
|
+
|
8
|
+
def initialize(pkcs12, wsdl, soap_header)
|
9
|
+
@conn = Savon.client(
|
10
|
+
wsdl: wsdl,
|
11
|
+
ssl_verify_mode: :none,
|
12
|
+
ssl_cert: pkcs12.certificate,
|
13
|
+
ssl_cert_key: pkcs12.key,
|
14
|
+
soap_header: soap_header,
|
15
|
+
soap_version: 2,
|
16
|
+
convert_request_keys_to: :none,
|
17
|
+
convert_response_tags_to: lambda { |key| key.to_sym },
|
18
|
+
namespace_identifier: nil
|
19
|
+
)
|
20
|
+
end
|
21
|
+
|
22
|
+
def connected?; (@conn.operations.length > 0) end
|
23
|
+
def operations; (@conn.operations) end
|
24
|
+
|
25
|
+
def build(operation, hash)
|
26
|
+
@conn.build_request(operation, message: hash)
|
27
|
+
end
|
28
|
+
|
29
|
+
def call(operation, hash)
|
30
|
+
@conn.call(operation, message: hash)
|
31
|
+
end
|
32
|
+
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Utils
|
5
|
+
# Módulo com recursos adicionais para os templates baseados na Prawn
|
6
|
+
module PrawnHelper
|
7
|
+
|
8
|
+
def dash(doc, gap:, space:, line_width:, x1:, x2:, y:)
|
9
|
+
doc.dash(gap, space: space)
|
10
|
+
doc.line_width line_width
|
11
|
+
doc.stroke_horizontal_line x1, x2, at: y
|
12
|
+
doc.move_down 2
|
13
|
+
end
|
14
|
+
|
15
|
+
def barcode_128c(doc, value:, x:, y:, xdim: 1, height: 20, margin: 0, unbleed: 0, color: '000000')
|
16
|
+
barcode = Barby::Code128C.new(value)
|
17
|
+
outputter = Barby::PrawnOutputter.new(barcode)
|
18
|
+
outputter.x = x
|
19
|
+
outputter.y = (y - height)
|
20
|
+
outputter.xdim = xdim
|
21
|
+
outputter.height = height
|
22
|
+
outputter.margin = margin
|
23
|
+
outputter.unbleed = unbleed
|
24
|
+
outputter.color = color
|
25
|
+
|
26
|
+
doc.move_down height
|
27
|
+
outputter.annotate_pdf doc
|
28
|
+
end
|
29
|
+
|
30
|
+
def qrcode(doc, value:, x:, y:, xdim: 1, margin: 0, unbleed: 0, color: '000000')
|
31
|
+
qrcode = Barby::QrCode.new(value)
|
32
|
+
outputter = Barby::PrawnOutputter.new(qrcode)
|
33
|
+
height = (outputter.boolean_groups.length * xdim)
|
34
|
+
outputter.x = x
|
35
|
+
outputter.y = (y - height)
|
36
|
+
outputter.xdim = xdim
|
37
|
+
outputter.margin = margin
|
38
|
+
outputter.unbleed = unbleed
|
39
|
+
outputter.color = color
|
40
|
+
|
41
|
+
doc.move_down height
|
42
|
+
outputter.annotate_pdf doc
|
43
|
+
end
|
44
|
+
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,122 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Utils
|
5
|
+
# Classe base de assinatura de XML da biblioteca
|
6
|
+
class Signer
|
7
|
+
|
8
|
+
def initialize(xml)
|
9
|
+
@doc = Nokogiri::XML::Document.parse(xml, nil, "utf-8", Nokogiri::XML::ParseOptions::NOBLANKS)
|
10
|
+
end
|
11
|
+
|
12
|
+
def sign!(cert, key)
|
13
|
+
@cert = cert
|
14
|
+
@key = key
|
15
|
+
@doc.root.add_child(signature)
|
16
|
+
end
|
17
|
+
|
18
|
+
def to_xml
|
19
|
+
return @doc.canonicalize
|
20
|
+
end
|
21
|
+
|
22
|
+
private
|
23
|
+
|
24
|
+
def signature
|
25
|
+
# Create Signature
|
26
|
+
node = Nokogiri::XML::Node.new('Signature', @doc)
|
27
|
+
node.default_namespace = 'http://www.w3.org/2000/09/xmldsig#'
|
28
|
+
node.add_child signature_info
|
29
|
+
node.add_child signature_value
|
30
|
+
node.add_child key_info
|
31
|
+
node
|
32
|
+
end
|
33
|
+
|
34
|
+
def signature_info
|
35
|
+
# Create SignatureInfo
|
36
|
+
node = Nokogiri::XML::Node.new('SignedInfo', @doc)
|
37
|
+
|
38
|
+
# Add CanonicalizationMethod
|
39
|
+
child_node = Nokogiri::XML::Node.new('CanonicalizationMethod', @doc)
|
40
|
+
child_node['Algorithm'] = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
|
41
|
+
node.add_child child_node
|
42
|
+
|
43
|
+
# Add SignatureMethod
|
44
|
+
child_node = Nokogiri::XML::Node.new('SignatureMethod', @doc)
|
45
|
+
child_node['Algorithm'] = 'http://www.w3.org/2000/09/xmldsig#rsa-sha1'
|
46
|
+
node.add_child child_node
|
47
|
+
|
48
|
+
# Add Reference
|
49
|
+
node.add_child reference
|
50
|
+
node
|
51
|
+
end
|
52
|
+
|
53
|
+
def signature_value
|
54
|
+
# Sign Signature
|
55
|
+
xml = Nokogiri::XML(signature_info.to_xml, &:noblanks)
|
56
|
+
xml.root["xmlns"] = 'http://www.w3.org/2000/09/xmldsig#'
|
57
|
+
sign_canon = xml.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0)
|
58
|
+
signature_hash = @key.sign(OpenSSL::Digest::SHA1.new, sign_canon)
|
59
|
+
|
60
|
+
# Add SignatureValue
|
61
|
+
node = Nokogiri::XML::Node.new('SignatureValue', @doc)
|
62
|
+
node.content = Base64.encode64(signature_hash).gsub("\n", '')
|
63
|
+
node
|
64
|
+
end
|
65
|
+
|
66
|
+
def key_info
|
67
|
+
# Create KeyInfo
|
68
|
+
node = Nokogiri::XML::Node.new('KeyInfo', @doc)
|
69
|
+
|
70
|
+
# Add X509 Data and Certificate
|
71
|
+
x509_data = Nokogiri::XML::Node.new('X509Data', @doc)
|
72
|
+
x509_certificate = Nokogiri::XML::Node.new('X509Certificate', @doc)
|
73
|
+
x509_certificate.content = @cert.to_pem.gsub(/\-\-\-\-\-[A-Z]+ CERTIFICATE\-\-\-\-\-/, "").gsub(/\n/,"")
|
74
|
+
|
75
|
+
x509_data.add_child x509_certificate
|
76
|
+
node.add_child x509_data
|
77
|
+
node
|
78
|
+
end
|
79
|
+
|
80
|
+
def reference
|
81
|
+
inf = @doc.at_xpath("//*[@Id]")
|
82
|
+
|
83
|
+
# Calculate digest
|
84
|
+
xml_canon = inf.canonicalize(Nokogiri::XML::XML_C14N_EXCLUSIVE_1_0)
|
85
|
+
xml_digest = Base64.encode64(OpenSSL::Digest::SHA1.digest(xml_canon)).strip
|
86
|
+
|
87
|
+
# Create Reference
|
88
|
+
node = Nokogiri::XML::Node.new('Reference', @doc)
|
89
|
+
node['URI'] = "##{inf['Id']}"
|
90
|
+
node.add_child transforms
|
91
|
+
|
92
|
+
# Add Digest
|
93
|
+
child_node = Nokogiri::XML::Node.new('DigestMethod', @doc)
|
94
|
+
child_node['Algorithm'] = 'http://www.w3.org/2000/09/xmldsig#sha1'
|
95
|
+
node.add_child child_node
|
96
|
+
|
97
|
+
# Add DigestValue
|
98
|
+
child_node = Nokogiri::XML::Node.new('DigestValue', @doc)
|
99
|
+
child_node.content = xml_digest
|
100
|
+
node.add_child child_node
|
101
|
+
node
|
102
|
+
end
|
103
|
+
|
104
|
+
def transforms
|
105
|
+
# Add Transforms
|
106
|
+
node = Nokogiri::XML::Node.new('Transforms', @doc)
|
107
|
+
|
108
|
+
# Add Transform
|
109
|
+
child_node = Nokogiri::XML::Node.new('Transform', @doc)
|
110
|
+
child_node['Algorithm'] = 'http://www.w3.org/2000/09/xmldsig#enveloped-signature'
|
111
|
+
node.add_child child_node
|
112
|
+
|
113
|
+
# Add Transform
|
114
|
+
child_node = Nokogiri::XML::Node.new('Transform', @doc)
|
115
|
+
child_node['Algorithm'] = 'http://www.w3.org/TR/2001/REC-xml-c14n-20010315'
|
116
|
+
node.add_child child_node
|
117
|
+
node
|
118
|
+
end
|
119
|
+
|
120
|
+
end
|
121
|
+
end
|
122
|
+
end
|
data/lib/sefaz/version.rb
CHANGED
@@ -0,0 +1,35 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module SEFAZ
|
4
|
+
module Webservice
|
5
|
+
module NFE
|
6
|
+
# Auditor de XML pelo validador público da Tecnospeed
|
7
|
+
class Auditor
|
8
|
+
|
9
|
+
def initialize(xml)
|
10
|
+
@xml = xml
|
11
|
+
end
|
12
|
+
|
13
|
+
def exec(openTimeout, readTimeout)
|
14
|
+
uri = URI("https://validadornfe.tecnospeed.com.br/validar")
|
15
|
+
http = Net::HTTP.new(uri.host, uri.port)
|
16
|
+
http.use_ssl = true
|
17
|
+
http.open_timeout = openTimeout
|
18
|
+
http.read_timeout = readTimeout
|
19
|
+
content = Net::HTTP::Post.new(uri.request_uri)
|
20
|
+
content.set_form_data(txtXML: @xml)
|
21
|
+
response = http.request(content)
|
22
|
+
if response.code == "200"
|
23
|
+
url = JSON(response.body)["url"]
|
24
|
+
url_esc = url.to_s.sub("{", "%7B").sub("}", "%7D")
|
25
|
+
txt_uri = URI(url_esc)
|
26
|
+
body = CGI.unescape(Net::HTTP.get(txt_uri))
|
27
|
+
return [:ok, JSON(body)]
|
28
|
+
end
|
29
|
+
return [:error, {}]
|
30
|
+
end
|
31
|
+
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|