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