facturacr 0.1.4 → 1.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.
@@ -2,16 +2,18 @@ require 'base64'
2
2
 
3
3
  module FE
4
4
  class SignedDocument
5
- attr_accessor :document, :path, :base64, :payload
5
+ attr_accessor :document, :base64, :payload
6
6
 
7
- def initialize(document, path)
8
- @document = document
9
- @path = path
10
- @base64 = nil
11
- if File.exist?(@path)
12
- file = File.open(@path,"rb")
13
- @base64 = Base64.encode64(file.read).gsub("\n","");
7
+ def initialize(document, xml_provider)
8
+ # Backwards compatibility with v0.1.4
9
+ if xml_provider.is_a?(String)
10
+ raise ArgumentError, "File: #{xml_provider} does not exist" unless File.exists?(xml_provider)
11
+ xml_provider = Fe::DataProvider.new(:file, xml_provider)
14
12
  end
13
+ raise ArgumentError, "Invalid Argument" unless xml_provider.is_a?(FE::DataProvider)
14
+
15
+ @document = document
16
+ @base64 = Base64.encode64(xml_provider.contents).gsub("\n","");
15
17
  @payload = document.api_payload
16
18
  @payload[:comprobanteXml] = @base64
17
19
  end
@@ -5,7 +5,6 @@ require "rexml/xpath"
5
5
 
6
6
  module FE
7
7
  class Signer
8
- REXML::Document::entity_expansion_limit = 0
9
8
  C14N = "http://www.w3.org/TR/2001/REC-xml-c14n-20010315" #"http://www.w3.org/2001/10/xml-exc-c14n#"
10
9
  DSIG = "http://www.w3.org/2000/09/xmldsig#"
11
10
  NOKOGIRI_OPTIONS = Nokogiri::XML::ParseOptions::STRICT | Nokogiri::XML::ParseOptions::NONET | Nokogiri::XML::ParseOptions::NOENT
@@ -24,137 +23,180 @@ module FE
24
23
  XADES = "http://uri.etsi.org/01903/v1.3.2#"
25
24
  XADES141 = "http://uri.etsi.org/01903/v1.4.1#"
26
25
  SIGNATURE_POLICY = "https://tribunet.hacienda.go.cr/docs/esquemas/2016/v4/Resolucion%20Comprobantes%20Electronicos%20%20DGT-R-48-2016.pdf"
26
+
27
27
 
28
-
29
- def initialize(key_path, key_password,input_xml, output_path=nil)
30
- @doc = REXML::Document.new(File.read(input_xml))
31
- @doc.context[:attribute_quote] = :quote
32
- @doc << REXML::XMLDecl.new(REXML::XMLDecl::DEFAULT_VERSION,REXML::XMLDecl::DEFAULT_ENCODING, REXML::XMLDecl::DEFAULT_STANDALONE)
33
- @p12 = OpenSSL::PKCS12.new(File.read("tmp/pruebas.p12"),"8753")
28
+ def initialize(args = {})
29
+ document_provider = args[:xml_provider]
30
+ key_provider = args[:key_provider]
31
+ pin = args[:pin]
32
+ raise ArgumentError , "Los argumentos no son válidos" if document_provider.nil? || key_provider.nil? || pin.nil?
33
+ @doc = Nokogiri::XML(document_provider.contents) do |config|
34
+ config.options = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::NOENT | Nokogiri::XML::ParseOptions::NOENT
35
+ end
36
+ @p12 = OpenSSL::PKCS12.new(key_provider.contents,args[:pin])
34
37
  @x509 = @p12.certificate
35
- @output_path = output_path
38
+ @output_path = args[:output_path]
36
39
  end
37
-
40
+
38
41
  def sign
39
42
  #Build parts for Digest Calculation
40
43
  key_info = build_key_info_element
41
44
  signed_properties = build_signed_properties_element
42
45
  signed_info_element = build_signed_info_element(key_info,signed_properties)
46
+
43
47
  # Compute Signature
44
48
  signed_info_canon = canonicalize_document(signed_info_element)
45
49
  signature_value = compute_signature(@p12.key,algorithm(RSA_SHA256).new,signed_info_canon)
50
+
51
+ ds = Nokogiri::XML::Node.new("ds:Signature", @doc)
52
+ ds["xmlns:ds"] = DSIG
53
+ #ds["Id"] = SIGNATURE_ID#"xmldsig-#{uuid}"
54
+ ds["Id"] = "xmldsig-#{uuid}"
55
+ #ds.add_child(Nokogiri::XML(signed_info_without_ns).root)
56
+ ds.add_child(signed_info_element.root)
46
57
 
47
- # delete parts namespaces
48
- delete_namespaces(signed_info_element)
49
- delete_namespaces(key_info)
50
- delete_namespaces(signed_properties)
51
-
52
- # Created Signature element and add parts
53
- signature_element = REXML::Element.new("ds:Signature").add_namespace('ds', DSIG)
54
- signature_element.add_attribute("Id","xmldsig-#{uuid}")
58
+ sv = Nokogiri::XML::Node.new("ds:SignatureValue", @doc)
59
+ #sv["Id"] = SIGNATURE_VALUE#"xmldsig-#{uuid}-sigvalue"
60
+ sv["Id"] = "xmldsig-#{uuid}-sigvalue"
61
+ sv.content = signature_value
62
+ ds.add_child(sv)
55
63
 
56
- signature_element.add_element(signed_info_element)
57
- signature_element.add_element("ds:SignatureValue","Id"=>"xmldsig-#{uuid}-sigvalue").text = signature_value
58
- signature_element.add_element(key_info)
64
+ ds.add_child(key_info.root)
59
65
 
60
- object = signature_element.add_element("ds:Object")
61
- qualifying_properties = object.add_element("xades:QualifyingProperties", {"Target"=>"#xmldsig-#{uuid}"})
62
- qualifying_properties.add_namespace("xades", XADES)
63
- qualifying_properties.add_namespace("xades141", XADES141)
64
66
 
65
- qualifying_properties.add_element(signed_properties)
67
+ dsobj = Nokogiri::XML::Node.new("ds:Object",@doc)
68
+ dsobj["Id"] = "xades-obj-#{uuid}"#XADES_OBJECT_ID
69
+ qp = Nokogiri::XML::Node.new("xades:QualifyingProperties",@doc)
70
+ qp["xmlns:xades"] = XADES
71
+ #qp["Target"] = "##{SIGNATURE_ID}"#"#xmldsig-#{uuid}"
72
+ qp["Target"] = "#xmldsig-#{uuid}"
73
+ qp["Id"] = "QualifyingProperties-#{uuid}"
74
+ qp.add_child(signed_properties.root)
66
75
 
67
- @doc.root.add_element(signature_element)
76
+ dsobj.add_child(qp)
77
+ ds.add_child(dsobj)
78
+ @doc.root.add_child(ds)
68
79
 
69
- File.open(@output_path,"w"){|f| f.write(@doc.to_s)} if @output_path
80
+ File.open(@output_path,"w"){|f| f.write(@doc.to_xml(:save_with=>Nokogiri::XML::Node::SaveOptions::AS_XML).gsub(/\r|\n/,""))} if @output_path
70
81
 
71
- @doc
82
+ @doc.to_xml(:save_with=>Nokogiri::XML::Node::SaveOptions::AS_XML).gsub(/\r|\n/,"")
72
83
  end
73
84
 
85
+
74
86
  private
75
87
 
88
+ def build_key_info_element
89
+ builder = Nokogiri::XML::Builder.new
90
+ attributes = {
91
+ "xmlns" => "https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica",
92
+ "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
93
+ "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
94
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance",
95
+ "Id"=>"xmldsig-#{uuid}-keyinfo"
96
+ }
97
+
98
+ builder.send("ds:KeyInfo", attributes) do |ki|
99
+ ki.send("ds:X509Data") do |kd|
100
+ kd.send("ds:X509Certificate", @x509.to_pem.to_s.gsub("-----BEGIN CERTIFICATE-----","").gsub("-----END CERTIFICATE-----","").gsub(/\n|\r/, ""))
101
+ end
102
+ ki.send("ds:KeyValue") do |kv|
103
+ kv.send("ds:RSAKeyValue") do |rv|
104
+ rv.send("ds:Modulus", Base64.encode64(@x509.public_key.params["n"].to_s(2)).gsub("\n",""))
105
+ rv.send("ds:Exponent", Base64.encode64(@x509.public_key.params["e"].to_s(2)).gsub("\n",""))
106
+ end
107
+ end
108
+ end
109
+ builder.doc
110
+ end
111
+
76
112
  def build_signed_properties_element
77
113
  cert_digest = compute_digest(@x509.to_der,algorithm(SHA256))
78
114
  policy_digest = compute_digest(@x509.to_der,algorithm(SHA256))
79
- signing_time = DateTime.now
80
-
81
- element = REXML::Element.new("xades:SignedProperties",nil,{:attribute_quote=>:quote})
82
- element.add_namespace("https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica")
83
- element.add_namespace("ds","http://www.w3.org/2000/09/xmldsig#")
84
- element.add_namespace("xades","http://uri.etsi.org/01903/v1.3.2#")
85
- element.add_namespace("xsd","http://www.w3.org/2001/XMLSchema")
86
- element.add_namespace("xsi","http://www.w3.org/2001/XMLSchema-instance")
87
- element.add_namespace("xades141", XADES141)
88
- element.add_attribute("Id","xmldsig-#{uuid}-signedprops")
89
-
90
- element.add_element("xades:SigningTime").text = signing_time.rfc3339
91
- signing_certificate_elem = element.add_element("xades:SigningCertificate")
92
- cert_elem = signing_certificate_elem.add_element("xades:Cert")
93
- cert_digest_elem = cert_elem.add_element("xades:CertDigest")
94
- cert_digest_elem.add_element("ds:DigestMethod", {"Algorithm"=>SHA256})
95
- cert_digest_elem.add_element("ds:DigestValue").text = cert_digest
96
-
97
- issuer_serial = cert_elem.add_element("xades:IssuerSerial")
98
- issuer_serial.add_element("ds:X509IssuerName").text = @x509.issuer.to_a.reverse.map{|c| c[0..1].join("=")}.join(", ")
99
- issuer_serial.add_element("ds:X509SerialNumber").text = @x509.serial.to_s
100
-
101
- policy_elem = element.add_element("xades:SignaturePolicyIdentifier")
102
- policy_id_elem = policy_elem.add_element("xades:SignaturePolicyId")
103
- sig_policy_id = policy_id_elem.add_element("xades:SigPolicyId")
104
- sig_policy_id.add_element("xades:Identifier").text = SIGNATURE_POLICY
105
-
106
- policy_hash = sig_policy_id.add_element("xades:SigPolicyHash")
107
- policy_hash.add_element("ds:DigestMethod", {"Algorithm"=>"http://www.w3.org/2000/09/xmldsig#sha1"})
108
- policy_hash.add_element("ds:DigestValue").text = "V8lVVNGDCPen6VELRD1Ja8HARFk="#policy_digest
115
+ signing_time = DateTime.now.rfc3339
116
+ builder = Nokogiri::XML::Builder.new
117
+ attributes = {
118
+ "xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica",
119
+ "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
120
+ "xmlns:xades" => "http://uri.etsi.org/01903/v1.3.2#",
121
+ "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
122
+ "xmlns:xsi"=>"http://www.w3.org/2001/XMLSchema-instance",
123
+ "Id" => "xmldsig-#{uuid}-signedprops"
124
+ }
125
+ builder.send("xades:SignedProperties", attributes) do |sp|
126
+ sp.send("xades:SignedSignatureProperties") do |ssp|
127
+ ssp.send("xades:SigningTime", signing_time)
128
+ ssp.send("xades:SigningCertificate") do |sc|
129
+ sc.send("xades:Cert") do |c|
130
+ c.send("xades:CertDigest") do |xcd|
131
+ xcd.send("ds:DigestMethod", {"Algorithm"=>SHA256})
132
+ xcd.send("ds:DigestValue", cert_digest)
133
+ end
134
+ c.send("xades:IssuerSerial") do |is|
135
+ is.send("ds:X509IssuerName", @x509.issuer.to_a.reverse.map{|c| c[0..1].join("=")}.join(", "))
136
+ is.send("ds:X509SerialNumber", @x509.serial.to_s)
137
+ end
138
+ end
139
+ end
140
+
141
+ ssp.send("xades:SignaturePolicyIdentifier") do |spi|
142
+ spi.send("xades:SignaturePolicyId") do |spi2|
143
+ spi2.send("xades:SigPolicyId") do |spi3|
144
+ spi3.send("xades:Identifier", SIGNATURE_POLICY)
145
+ spi3.send("xades:Description")
146
+ end
147
+
148
+ spi2.send("xades:SigPolicyHash") do |sph|
149
+ sph.send("ds:DigestMethod", {"Algorithm"=>"http://www.w3.org/2000/09/xmldsig#sha1"})
150
+ sph.send("ds:DigestValue", "V8lVVNGDCPen6VELRD1Ja8HARFk=")
151
+ end
152
+ end
153
+ end
154
+
155
+ end
156
+ sp.send("xades:SignedDataObjectProperties") do |sdop|
157
+ sdop.send("xades:DataObjectFormat", {"ObjectReference"=>"#xmldsig-#{uuid}-ref0"}) do |dof|
158
+ dof.send("xades:MimeType","text/xml")
159
+ dof.send("xades:Encoding", "UTF-8")
160
+ end
161
+ end
162
+ end
109
163
 
110
- element
111
- end
112
-
113
- def build_key_info_element
114
- key_info = REXML::Element.new("ds:KeyInfo",nil,{:attribute_quote=>:quote})
115
- key_info.add_namespace("https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica")
116
- key_info.add_namespace("ds","http://www.w3.org/2000/09/xmldsig#")
117
- key_info.add_namespace("xsd","http://www.w3.org/2001/XMLSchema")
118
- key_info.add_namespace("xsi","http://www.w3.org/2001/XMLSchema-instance")
119
-
120
- key_info.add_attribute("Id","xmldsig-#{uuid}-keyinfo")
121
- x509_data = key_info.add_element("ds:X509Data")
122
- x509_data.add_element("ds:X509Certificate").text = @x509.to_pem.to_s.gsub("-----BEGIN CERTIFICATE-----","").gsub("-----END CERTIFICATE-----","").gsub(/\n|\r/, "")
123
- key_value = key_info.add_element("ds:KeyValue")
124
- rsa_value = key_value.add_element("ds:RSAKeyValue")
125
- rsa_value.add_element("ds:Modulus").text = Base64.encode64(@x509.public_key.params["n"].to_s(2)).gsub("\n","")
126
- rsa_value.add_element("ds:Exponent").text = Base64.encode64(@x509.public_key.params["e"].to_s(2)).gsub("\n","")
127
- key_info
164
+ builder.doc
128
165
  end
129
166
 
130
167
  def build_signed_info_element(key_info_element, signed_props_element)
131
168
 
132
- signed_info_element = REXML::Element.new("ds:SignedInfo",nil,{:attribute_quote=>:quote})
133
- signed_info_element.add_namespace("https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica")
134
- signed_info_element.add_namespace("ds","http://www.w3.org/2000/09/xmldsig#")
135
- signed_info_element.add_namespace("xsd","http://www.w3.org/2001/XMLSchema")
136
- signed_info_element.add_namespace("xsi","http://www.w3.org/2001/XMLSchema-instance")
137
- signed_info_element.add_element("ds:CanonicalizationMethod", { "Algorithm"=>C14N })
138
- signed_info_element.add_element("ds:SignatureMethod", {"Algorithm"=>RSA_SHA256})
139
-
140
- # Add Ref0
141
- ref0 = signed_info_element.add_element("ds:Reference",{"Id"=>"xmldsig-#{uuid}-ref0"})
142
- transforms = ref0.add_element("ds:Transforms")
143
- transform = transforms.add_element("ds:Transform", {"Algorithm"=>ENVELOPED_SIG})
144
- digest_method_element = ref0.add_element("ds:DigestMethod", {"Algorithm"=> SHA256})
145
- ref0.add_element("ds:DigestValue").text = digest_document(@doc,SHA256)
146
-
147
- # Add KeyInfo Ref
148
- ref_key_info = signed_info_element.add_element("ds:Reference",{"URI"=>"#xmldsig-#{uuid}-keyinfo"})
149
- ref_key_info.add_element("ds:DigestMethod", {"Algorithm"=> SHA256})
150
- ref_key_info.add_element("ds:DigestValue").text = digest_document(key_info_element, SHA256, true)
151
-
152
- # Add SignedProps Ref
153
- ref_props = signed_info_element.add_element("ds:Reference",{"URI"=>"#xmldsig-#{uuid}-signedprops", "Type"=>"http://uri.etsi.org/01903#SignedProperties"})
154
- ref_props.add_element("ds:DigestMethod", {"Algorithm"=> SHA256})
155
- ref_props.add_element("ds:DigestValue").text = digest_document(signed_props_element, SHA256,true)
169
+ builder = builder = Nokogiri::XML::Builder.new
170
+ attributes = {
171
+ "xmlns"=>"https://tribunet.hacienda.go.cr/docs/esquemas/2017/v4.2/facturaElectronica",
172
+ "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#",
173
+ "xmlns:xsd" => "http://www.w3.org/2001/XMLSchema",
174
+ "xmlns:xsi" => "http://www.w3.org/2001/XMLSchema-instance"
175
+ }
176
+ builder.send("ds:SignedInfo", attributes) do |si|
177
+ si.send("ds:CanonicalizationMethod", { "Algorithm"=>C14N })
178
+ si.send("ds:SignatureMethod", {"Algorithm"=>RSA_SHA256})
179
+
180
+ si.send("ds:Reference",{"Id"=>"xmldsig-#{uuid}-ref0", "URI"=>""}) do |r|
181
+ r.send("ds:Transforms") do |t|
182
+ t.send("ds:Transform", {"Algorithm"=>ENVELOPED_SIG})
183
+ end
184
+ r.send("ds:DigestMethod", {"Algorithm"=> SHA256})
185
+ r.send("ds:DigestValue", digest_document(@doc,SHA256))
186
+ end
187
+ si.send("ds:Reference",{"Id"=>"xmldsig-#{uuid}-ref1", "URI"=>"#xmldsig-#{uuid}-keyinfo"}) do |r|
188
+ r.send("ds:DigestMethod", {"Algorithm"=> SHA256})
189
+ r.send("ds:DigestValue", digest_document(key_info_element, SHA256, true))
190
+ end
191
+
192
+ si.send("ds:Reference",{"Type"=>"http://uri.etsi.org/01903#SignedProperties", "URI"=>"#xmldsig-#{uuid}-signedprops"}) do |r|
193
+ r.send("ds:DigestMethod", {"Algorithm"=> SHA256})
194
+ r.send("ds:DigestValue", digest_document(signed_props_element, SHA256, true))
195
+ end
196
+ end
156
197
 
157
- signed_info_element
198
+
199
+ builder.doc
158
200
  end
159
201
 
160
202
  def digest_document(doc, digest_algorithm=SHA256, strip=false)
@@ -162,21 +204,9 @@ module FE
162
204
  end
163
205
 
164
206
  def canonicalize_document(doc,strip=false)
165
- doc = doc.to_s if doc.is_a?(REXML::Element)
166
- doc.strip! if strip
167
- doc.encode("UTF-8")
168
- noko = Nokogiri::XML(doc) do |config|
169
- config.options = NOKOGIRI_OPTIONS
170
- end
171
-
172
- noko.canonicalize(canon_algorithm(C14N),NAMESPACES.split(" "))
207
+ doc.canonicalize(canon_algorithm(C14N),NAMESPACES.split(" "))
173
208
  end
174
209
 
175
- def delete_namespaces(element)
176
- NAMESPACES.split(" ").each do |ns|
177
- element.delete_namespace(ns)
178
- end
179
- end
180
210
 
181
211
  def uuid
182
212
  @uuid ||= SecureRandom.uuid
@@ -184,9 +214,7 @@ module FE
184
214
 
185
215
  def canon_algorithm(element)
186
216
  algorithm = element
187
- if algorithm.is_a?(REXML::Element)
188
- algorithm = element.attribute('Algorithm').value
189
- end
217
+
190
218
 
191
219
  case algorithm
192
220
  when "http://www.w3.org/TR/2001/REC-xml-c14n-20010315",
@@ -204,6 +232,8 @@ module FE
204
232
  algorithm = element
205
233
  if algorithm.is_a?(REXML::Element)
206
234
  algorithm = element.attribute("Algorithm").value
235
+ elsif algorithm.is_a?(Nokogiri::XML::Element)
236
+ algorithm = element.xpath("//@Algorithm", "xmlns:ds" => "http://www.w3.org/2000/09/xmldsig#").first.value
207
237
  end
208
238
 
209
239
  algorithm = algorithm && algorithm =~ /(rsa-)?sha(.*?)$/i && $2.to_i
@@ -218,14 +248,14 @@ module FE
218
248
  end
219
249
 
220
250
  def compute_signature(private_key, signature_algorithm, document)
221
- Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\n/, "")
251
+ Base64.encode64(private_key.sign(signature_algorithm, document)).gsub(/\r|\n/, "")
222
252
  end
223
253
 
224
254
  def compute_digest(document, digest_algorithm)
225
255
  digest = digest_algorithm.digest(document)
226
256
  Base64.encode64(digest).strip!
227
257
  end
228
-
258
+
229
259
  end
230
260
 
231
261
  class JavaSigner
@@ -1,3 +1,3 @@
1
1
  module FE
2
- VERSION = "0.1.4"
2
+ VERSION = "1.0"
3
3
  end
@@ -7,103 +7,108 @@ module FE
7
7
 
8
8
  attr_accessor :document, :root_tag
9
9
 
10
- def initialize(xml_path)
11
- if File.exist?(xml_path)
12
- @doc = Nokogiri::XML(File.open(xml_path,"rb")) do |config|
13
- config.options = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::NOENT
10
+ def initialize(xml_provider)
11
+ # Backwards compatibility with v0.1.4
12
+ if xml_provider.is_a?(String)
13
+ raise ArgumentError, "File: #{xml_provider} does not exist" unless File.exists?(xml_provider)
14
+ xml_provider = Fe::DataProvider.new(:file, xml_provider)
15
+ end
16
+ raise ArgumentError, "Invalid Argument" unless xml_provider.is_a?(FE::DataProvider)
17
+
18
+ @doc = Nokogiri::XML(xml_provider.contents) do |config|
19
+ config.options = Nokogiri::XML::ParseOptions::NOBLANKS | Nokogiri::XML::ParseOptions::NOENT
20
+ end
21
+ root_tag = @doc.elements.first.name
22
+
23
+ if root_tag.eql?('FacturaElectronica')
24
+ @document = FE::Invoice.new
25
+ elsif root_tag.eql?("NotaCreditoElectronica")
26
+ @document = FE::CreditNote.new
27
+ elsif root_tag.eql?("NotaDebitoElectronica")
28
+ @document = FE::DebitNote.new
29
+ elsif root_tag.eql?("TiqueteElectronico")
30
+ @document = FE::Ticket.new
31
+ elsif root_tag.eql?("MensajeReceptor")
32
+ @document = FE::ReceptionMessage.new
33
+ end
34
+
35
+ if @document.is_a?(FE::Document)
36
+ @document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmision").text)
37
+ @key = @doc.css("#{root_tag} Clave").text
38
+ @document.number = @key[31..40].to_i
39
+ @document.document_situation = @key[41]
40
+ @document.security_code = @key[42..-1]
41
+ @document.condition = @doc.css("#{root_tag} CondicionVenta").text
42
+ @document.credit_term = @doc.css("#{root_tag} PlazoCredito").text unless @doc.css("#{root_tag} PlazoCredito").empty?
43
+ @document.payment_type = @doc.css("#{root_tag} MedioPago").first.text
44
+
45
+ @issuer = FE::Document::Issuer.new
46
+ @issuer.identification_document = FE::Document::IdentificationDocument.new type: @doc.css("#{root_tag} Emisor Identificacion Tipo").text, number: @doc.css("#{root_tag} Emisor Identificacion Numero").text.to_i
47
+ @issuer.name = @doc.css("#{root_tag} Emisor Nombre").text
48
+ @issuer.comercial_name = @doc.css("#{root_tag} Emisor NombreComercial").text unless @doc.css("#{root_tag} Emisor NombreComercial").empty?
49
+ location = FE::Document::Location.new
50
+ location.province = @doc.css("#{root_tag} Emisor Ubicacion Provincia").text
51
+ location.county = @doc.css("#{root_tag} Emisor Ubicacion Canton").text
52
+ location.district = @doc.css("#{root_tag} Emisor Ubicacion Distrito").text
53
+ location.others = @doc.css("#{root_tag} Emisor Ubicacion OtrasSenas").text
54
+ @issuer.location = location
55
+
56
+ if !@doc.css("#{root_tag} Emisor Telefono").empty?
57
+ @issuer.phone = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text
14
58
  end
15
- root_tag = @doc.elements.first.name
16
-
17
-
18
- if root_tag.eql?('FacturaElectronica')
19
- @document = FE::Invoice.new
20
- elsif root_tag.eql?("NotaCreditoElectronica")
21
- @document = FE::CreditNote.new
22
- elsif root_tag.eql?("NotaDebitoElectronica")
23
- @document = FE::DebitNote.new
24
- elsif root_tag.eql?("TiqueteElectronico")
25
- @document = FE::Ticket.new
26
- elsif root_tag.eql?("MensajeReceptor")
27
- @document = FE::ReceptionMessage.new
59
+ if !@doc.css("#{root_tag} Emisor Fax").empty?
60
+ @issuer.fax = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text
28
61
  end
62
+ @issuer.email = @doc.css("#{root_tag} Emisor CorreoElectronico").text
63
+
64
+ unless @doc.css("#{root_tag} Receptor").empty?
65
+ @receiver = FE::Document::Receiver.new
66
+ @receiver.name = @doc.css("#{root_tag} Receptor Nombre").text
67
+ unless @doc.css("#{root_tag} Receptor Identificacion").empty?
68
+ @receiver.identification_document = FE::Document::IdentificationDocument.new type: @doc.css("#{root_tag} Receptor Identificacion Tipo").text, number: @doc.css("#{root_tag} Receptor Identificacion Numero").text.to_i
69
+ end
29
70
 
30
- if @document.is_a?(FE::Document)
31
- @document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmision").text)
32
- @key = @doc.css("#{root_tag} Clave").text
33
- @document.number = @key[31..40].to_i
34
- @document.document_situation = @key[41]
35
- @document.security_code = @key[42..-1]
36
- @document.condition = @doc.css("#{root_tag} CondicionVenta").text
37
- @document.credit_term = @doc.css("#{root_tag} PlazoCredito").text unless @doc.css("#{root_tag} PlazoCredito").empty?
38
- @document.payment_type = @doc.css("#{root_tag} MedioPago").first.text
39
-
40
- @issuer = FE::Document::Issuer.new
41
- @issuer.identification_document = FE::Document::IdentificationDocument.new type: @doc.css("#{root_tag} Emisor Identificacion Tipo").text, number: @doc.css("#{root_tag} Emisor Identificacion Numero").text.to_i
42
- @issuer.name = @doc.css("#{root_tag} Emisor Nombre").text
43
- @issuer.comercial_name = @doc.css("#{root_tag} Emisor NombreComercial").text unless @doc.css("#{root_tag} Emisor NombreComercial").empty?
44
- location = FE::Document::Location.new
45
- location.province = @doc.css("#{root_tag} Emisor Ubicacion Provincia").text
46
- location.county = @doc.css("#{root_tag} Emisor Ubicacion Canton").text
47
- location.district = @doc.css("#{root_tag} Emisor Ubicacion Distrito").text
48
- location.others = @doc.css("#{root_tag} Emisor Ubicacion OtrasSenas").text
49
- @issuer.location = location
50
-
51
- if !@doc.css("#{root_tag} Emisor Telefono").empty?
52
- @issuer.phone = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text
71
+ unless @doc.css("#{root_tag} Receptor IdentificacionExtranjero").empty?
72
+ @receiver.foreign_id_number = @doc.css("#{root_tag} Receptor IdentificacionExtranjero").text
53
73
  end
54
- if !@doc.css("#{root_tag} Emisor Fax").empty?
55
- @issuer.fax = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Emisor Telefono CodigoPais").text
74
+ @receiver.comercial_name = @doc.css("#{root_tag} Receptor NombreComercial").text unless @doc.css("#{root_tag} Receptor NombreComercial").empty?
75
+
76
+ unless @doc.css("#{root_tag} Receptor Ubicacion").empty?
77
+ location = FE::Document::Location.new
78
+ location.province = @doc.css("#{root_tag} Receptor Ubicacion Provincia").text
79
+ location.county = @doc.css("#{root_tag} Receptor Ubicacion Canton").text
80
+ location.district = @doc.css("#{root_tag} Receptor Ubicacion Distrito").text
81
+ location.others = @doc.css("#{root_tag} Receptor Ubicacion OtrasSenas").text
82
+ @receiver.location = location
56
83
  end
57
- @issuer.email = @doc.css("#{root_tag} Emisor CorreoElectronico").text
58
84
 
59
- unless @doc.css("#{root_tag} Receptor").empty?
60
- @receiver = FE::Document::Receiver.new
61
- @receiver.name = @doc.css("#{root_tag} Receptor Nombre").text
62
- unless @doc.css("#{root_tag} Receptor Identificacion").empty?
63
- @receiver.identification_document = FE::Document::IdentificationDocument.new type: @doc.css("#{root_tag} Receptor Identificacion Tipo").text, number: @doc.css("#{root_tag} Receptor Identificacion Numero").text.to_i
64
- end
65
-
66
- unless @doc.css("#{root_tag} Receptor IdentificacionExtranjero").empty?
67
- @receiver.foreign_id_number = @doc.css("#{root_tag} Receptor IdentificacionExtranjero").text
68
- end
69
- @receiver.comercial_name = @doc.css("#{root_tag} Receptor NombreComercial").text unless @doc.css("#{root_tag} Receptor NombreComercial").empty?
70
-
71
- unless @doc.css("#{root_tag} Receptor Ubicacion").empty?
72
- location = FE::Document::Location.new
73
- location.province = @doc.css("#{root_tag} Receptor Ubicacion Provincia").text
74
- location.county = @doc.css("#{root_tag} Receptor Ubicacion Canton").text
75
- location.district = @doc.css("#{root_tag} Receptor Ubicacion Distrito").text
76
- location.others = @doc.css("#{root_tag} Receptor Ubicacion OtrasSenas").text
77
- @receiver.location = location
78
- end
79
-
80
- if !@doc.css("#{root_tag} Receptor Telefono").empty?
81
- @issuer.phone = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text
82
- end
83
- if !@doc.css("#{root_tag} Receptor Fax").empty?
84
- @receiver.fax = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text
85
- end
86
- @receiver.email = @doc.css("#{root_tag} Receptor CorreoElectronico").text unless @doc.css("#{root_tag} Receptor CorreoElectronico").empty?
85
+ if !@doc.css("#{root_tag} Receptor Telefono").empty?
86
+ @issuer.phone = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text
87
87
  end
88
- @items = []
89
- @doc.css("#{root_tag} DetalleServicio LineaDetalle").each do |line|
90
- item = FE::Document::Item.new
91
- item.line_number = line.css("NumeroLinea").text.to_i
92
- item.code = line.css("Codigo Codigo").text
93
- item.quantity = line.css("Cantidad").text
94
- item.unit = line.css("UnidadMedida").text
95
- item.description = line.css("Detalle").text
96
- item.unit_price = line.css("PrecioUnitario").text.to_f
97
- item.total = line.css("MontoTotal").text.to_f
98
- item.discount = line.css("MontoDescuento").text.to_f unless line.css("MontoDescuento").empty?
99
- item.discount_reason = line.css("NaturalezaDescuento").text unless line.css("NaturalezaDescuento").empty?
100
- item.subtotal = line.css("SubTotal").text.to_f
101
- item.net_total = line.css("MontoTotalLinea").text.to_f
102
- item.taxes = []
103
- line.css("Impuesto").each do |tax|
104
- item.taxes << FE::Document::Tax.new(code: tax.css("Codigo").text, rate: tax.css("Tarifa").text.to_f, total: tax.css("Monto").text.to_f)
105
- end
106
- unless line.css("Exoneracion").empty?
88
+ if !@doc.css("#{root_tag} Receptor Fax").empty?
89
+ @receiver.fax = FE::Document::Phone.new country_code: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text, number: @doc.css("#{root_tag} Receptor Telefono CodigoPais").text
90
+ end
91
+ @receiver.email = @doc.css("#{root_tag} Receptor CorreoElectronico").text unless @doc.css("#{root_tag} Receptor CorreoElectronico").empty?
92
+ end
93
+ @items = []
94
+ @doc.css("#{root_tag} DetalleServicio LineaDetalle").each do |line|
95
+ item = FE::Document::Item.new
96
+ item.line_number = line.css("NumeroLinea").text.to_i
97
+ item.code = line.css("Codigo Codigo").text
98
+ item.quantity = line.css("Cantidad").text
99
+ item.unit = line.css("UnidadMedida").text
100
+ item.description = line.css("Detalle").text
101
+ item.unit_price = line.css("PrecioUnitario").text.to_f
102
+ item.total = line.css("MontoTotal").text.to_f
103
+ item.discount = line.css("MontoDescuento").text.to_f unless line.css("MontoDescuento").empty?
104
+ item.discount_reason = line.css("NaturalezaDescuento").text unless line.css("NaturalezaDescuento").empty?
105
+ item.subtotal = line.css("SubTotal").text.to_f
106
+ item.net_total = line.css("MontoTotalLinea").text.to_f
107
+ item.taxes = []
108
+ line.css("Impuesto").each do |tax|
109
+ exo = nil
110
+ t_args = {code: tax.css("Codigo").text, rate: tax.css("Tarifa").text.to_f, total: tax.css("Monto").text.to_f}
111
+ unless tax.css("Exoneracion").empty?
107
112
  exo = FE::Document::Exoneration.new
108
113
  exo.document_type = line.css("Exoneracion TipoDocumento").text
109
114
  exo.document_number = line.css("Exoneracion NumeroDocumento").text
@@ -111,71 +116,71 @@ module FE
111
116
  exo.date = DateTime.parse(line.css("Exoneracion FechaEmision").text)
112
117
  exo.total_tax = line.css("Exoneracion MontoImpuesto").text.to_f
113
118
  exo.percentage = line.css("Exoneracion PorcentajeCompra").text.to_i
114
- item.exoneration = exo
119
+ t_args[:exoneration] = exo
115
120
  end
116
- @items << item
121
+
122
+ item.taxes << FE::Document::Tax.new(t_args)
117
123
  end
124
+ @items << item
125
+ end
118
126
 
119
-
120
- @summary = FE::Document::Summary.new
121
- sum = @doc.css("#{root_tag} ResumenFactura")
122
- @summary.currency = sum.css("CodigoMoneda").text
123
- @summary.exchange_rate = sum.css("TipoCambio").text.to_f
124
- @summary.services_taxable_total = sum.css("TotalServGravados").text.to_f
125
- @summary.services_exent_total = sum.css("TotalServExentos").text.to_f
126
- @summary.goods_taxable_total = sum.css("TotalMercanciasGravadas").text.to_f
127
- @summary.goods_exent_total = sum.css("TotalMercanciasExentas").text.to_f
128
- @summary.taxable_total = sum.css("TotalGravado").text.to_f
129
- @summary.exent_total = sum.css("TotalExento").text.to_f
130
- @summary.subtotal = sum.css("TotalVenta").text.to_f
131
- @summary.discount_total = sum.css("TotalDescuentos").text.to_f
132
- @summary.gross_total = sum.css("TotalVentaNeta").text.to_f
133
- @summary.tax_total = sum.css("TotalImpuesto").text.to_f
134
- @summary.net_total = sum.css("TotalComprobante").text.to_f
135
-
136
- refs = @doc.css("#{root_tag} InformacionReferencia")
137
- @references = []
138
- unless refs.empty?
139
- refs.each do |ref|
140
- reference = FE::Document::Reference.new
141
- reference.document_type = ref.css("TipoDoc")
142
- reference.number = ref.css("Numero")
143
- reference.date = ref.css("FechaEmision")
144
- reference.code = ref.css("Codigo")
145
- reference.reason = ref.css("Razon")
146
- @references << reference
147
- end
127
+
128
+ @summary = FE::Document::Summary.new
129
+ sum = @doc.css("#{root_tag} ResumenFactura")
130
+ @summary.currency = sum.css("CodigoMoneda").text
131
+ @summary.exchange_rate = sum.css("TipoCambio").text.to_f
132
+ @summary.services_taxable_total = sum.css("TotalServGravados").text.to_f
133
+ @summary.services_exent_total = sum.css("TotalServExentos").text.to_f
134
+ @summary.goods_taxable_total = sum.css("TotalMercanciasGravadas").text.to_f
135
+ @summary.goods_exent_total = sum.css("TotalMercanciasExentas").text.to_f
136
+ @summary.taxable_total = sum.css("TotalGravado").text.to_f
137
+ @summary.exent_total = sum.css("TotalExento").text.to_f
138
+ @summary.subtotal = sum.css("TotalVenta").text.to_f
139
+ @summary.discount_total = sum.css("TotalDescuentos").text.to_f
140
+ @summary.gross_total = sum.css("TotalVentaNeta").text.to_f
141
+ @summary.tax_total = sum.css("TotalImpuesto").text.to_f
142
+ @summary.net_total = sum.css("TotalComprobante").text.to_f
143
+
144
+ refs = @doc.css("#{root_tag} InformacionReferencia")
145
+ @references = []
146
+ unless refs.empty?
147
+ refs.each do |ref|
148
+ reference = FE::Document::Reference.new
149
+ reference.document_type = ref.css("TipoDoc")
150
+ reference.number = ref.css("Numero")
151
+ reference.date = ref.css("FechaEmision")
152
+ reference.code = ref.css("Codigo")
153
+ reference.reason = ref.css("Razon")
154
+ @references << reference
148
155
  end
149
-
150
- reg = @doc.css("#{root_tag} Normativa")
151
- @regulation = FE::Document::Regulation.new
152
- @regulation.number = reg.css("NumeroResolucion").text
153
- @regulation.date = reg.css("FechaResolucion").text
154
-
155
-
156
- @document.issuer = @issuer
157
- @document.receiver = @receiver
158
- @document.items = @items
159
- @document.summary = @summary
160
- @document.references = @references
161
- @document.regulation = @regulation
162
- else
163
- @document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmisionDoc").text)
164
- @key = @doc.css("#{root_tag} Clave").text
165
- @document.key = @key
166
- @document.issuer_id_number = @doc.css("#{root_tag} NumeroCedulaEmisor").text
167
- @document.receiver_id_number = @doc.css("#{root_tag} NumeroCedulaReceptor").text
168
- @document.message = @doc.css("#{root_tag} Mensaje").text
169
- @document.details = @doc.css("#{root_tag} DetalleMensaje").text
170
- @document.number = @key[31..40].to_i
171
- @document.document_situation = @key[41]
172
- @document.security_code = @key[42..-1]
173
- @document.total = @doc.css("#{root_tag} TotalFactura").text
174
- @document.tax = @doc.css("#{root_tag} MontoTotalImpuesto").text
175
- end
156
+ end
157
+
158
+ reg = @doc.css("#{root_tag} Normativa")
159
+ @regulation = FE::Document::Regulation.new
160
+ @regulation.number = reg.css("NumeroResolucion").text
161
+ @regulation.date = reg.css("FechaResolucion").text
162
+
163
+
164
+ @document.issuer = @issuer
165
+ @document.receiver = @receiver
166
+ @document.items = @items
167
+ @document.summary = @summary
168
+ @document.references = @references
169
+ @document.regulation = @regulation
176
170
  else
177
- raise "#{xml_path} does not exist"
178
- end
171
+ @document.date = DateTime.parse(@doc.css("#{root_tag} FechaEmisionDoc").text)
172
+ @key = @doc.css("#{root_tag} Clave").text
173
+ @document.key = @key
174
+ @document.issuer_id_number = @doc.css("#{root_tag} NumeroCedulaEmisor").text
175
+ @document.receiver_id_number = @doc.css("#{root_tag} NumeroCedulaReceptor").text
176
+ @document.message = @doc.css("#{root_tag} Mensaje").text
177
+ @document.details = @doc.css("#{root_tag} DetalleMensaje").text
178
+ @document.number = @doc.css("#{root_tag} NumeroConsecutivoReceptor").text[10..-1].to_i
179
+ @document.document_situation = @key[41]
180
+ @document.security_code = @key[42..-1]
181
+ @document.total = @doc.css("#{root_tag} TotalFactura").text
182
+ @document.tax = @doc.css("#{root_tag} MontoTotalImpuesto").text
183
+ end
179
184
  end
180
185
  end
181
186
  end