sw_fac 0.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.
Files changed (48) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.travis.yml +5 -0
  4. data/Gemfile +6 -0
  5. data/Gemfile.lock +26 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +39 -0
  8. data/Rakefile +10 -0
  9. data/bin/console +14 -0
  10. data/bin/setup +8 -0
  11. data/lib/cadena/ComercioExterior11.xslt +181 -0
  12. data/lib/cadena/Pagos10.xslt +165 -0
  13. data/lib/cadena/TuristaPasajeroExtranjero.xslt +40 -0
  14. data/lib/cadena/aerolineas.xslt +50 -0
  15. data/lib/cadena/cadena33.xslt +349 -0
  16. data/lib/cadena/certificadodedestruccion.xslt +60 -0
  17. data/lib/cadena/cfdiregistrofiscal.xslt +19 -0
  18. data/lib/cadena/cfdv33.xsd +737 -0
  19. data/lib/cadena/consumodecombustibles.xslt +108 -0
  20. data/lib/cadena/detallista.xslt +42 -0
  21. data/lib/cadena/divisas.xslt +13 -0
  22. data/lib/cadena/donat11.xslt +13 -0
  23. data/lib/cadena/ecc11.xslt +102 -0
  24. data/lib/cadena/iedu.xslt +26 -0
  25. data/lib/cadena/implocal.xslt +39 -0
  26. data/lib/cadena/ine11.xslt +51 -0
  27. data/lib/cadena/leyendasFisc.xslt +28 -0
  28. data/lib/cadena/nomina12.xslt +412 -0
  29. data/lib/cadena/notariospublicos.xslt +301 -0
  30. data/lib/cadena/obrasarteantiguedades.xslt +33 -0
  31. data/lib/cadena/pagoenespecie.xslt +39 -0
  32. data/lib/cadena/pfic.xslt +13 -0
  33. data/lib/cadena/renovacionysustitucionvehiculos.xslt +152 -0
  34. data/lib/cadena/servicioparcialconstruccion.xslt +44 -0
  35. data/lib/cadena/terceros11.xslt +108 -0
  36. data/lib/cadena/utilerias.xslt +22 -0
  37. data/lib/cadena/valesdedespensa.xslt +70 -0
  38. data/lib/cadena/vehiculousado.xslt +63 -0
  39. data/lib/cadena/ventavehiculos11.xslt +53 -0
  40. data/lib/sw_fac/config.rb +69 -0
  41. data/lib/sw_fac/facturacion.rb +616 -0
  42. data/lib/sw_fac/tools.rb +105 -0
  43. data/lib/sw_fac/version.rb +3 -0
  44. data/lib/sw_fac.rb +18 -0
  45. data/sw_fac.gemspec +38 -0
  46. data/test/sw_fac_test.rb +11 -0
  47. data/test/test_helper.rb +4 -0
  48. metadata +146 -0
@@ -0,0 +1,616 @@
1
+
2
+ module SwFac
3
+ class Facturacion < Tools
4
+
5
+ def comp_pago(params={})
6
+ # Sample params
7
+ # params = {
8
+ # uuid: '',
9
+ # venta_folio: '',
10
+ # cp: '',
11
+ # receptor_razon: 'Car zone',
12
+ # receptor_rfc: 'XAXX010101000',
13
+ # forma_pago: '01',
14
+ # total: 100.00,
15
+ # time: '',
16
+ # modena: '',
17
+ # line_items: [
18
+ # {
19
+ # monto: 60.00,
20
+ # moneda: '',
21
+ # },
22
+ # ]
23
+ # }
24
+
25
+ raise 'Error - la suma de los complementos de pago es mayor al total reportado' if (params[:line_items].inject(0) {|sum, x| sum + x[:monto].to_f }) > params[:total].to_f
26
+
27
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
28
+ token = @production ? @production_token : @dev_token
29
+ time = params.fetch(:time, Time.now)
30
+
31
+ base_doc = %(<?xml version="1.0" encoding="UTF-8"?>
32
+ <cfdi:Comprobante xmlns:cfdi="http://www.sat.gob.mx/cfd/3" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:pago10="http://www.sat.gob.mx/Pagos" xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd http://www.sat.gob.mx/Pagos http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos10.xsd" Version="3.3" SubTotal="0" Total="0" Moneda="XXX" TipoDeComprobante="P" >
33
+ <cfdi:Emisor />
34
+ <cfdi:Receptor UsoCFDI="P01"/>
35
+ <cfdi:Conceptos>
36
+ <cfdi:Concepto ClaveProdServ="84111506" Cantidad="1" ClaveUnidad="ACT" Descripcion="Pago" ValorUnitario="0" Importe="0" />
37
+ </cfdi:Conceptos>
38
+ <cfdi:Complemento>
39
+ <pago10:Pagos Version="1.0">
40
+ <pago10:Pago>
41
+ </pago10:Pago>
42
+ </pago10:Pagos>
43
+ </cfdi:Complemento>
44
+ </cfdi:Comprobante>)
45
+
46
+ base_doc.delete!("\n")
47
+ base_doc.delete!("\t")
48
+
49
+ xml = Nokogiri::XML(base_doc)
50
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
51
+ comprobante['Serie'] = 'P'
52
+ comprobante['Folio'] = params[:venta_folio].to_s
53
+ comprobante['Fecha'] = time.strftime("%Y-%m-%dT%H:%M:%S")
54
+ comprobante['LugarExpedicion'] = params[:cp].to_s
55
+ comprobante['NoCertificado'] = @serial
56
+ comprobante['Certificado'] = @cadena
57
+ emisor = xml.at_xpath("//cfdi:Emisor")
58
+ emisor['Rfc'] = @rfc
59
+ emisor['Nombre'] = @razon
60
+ emisor['RegimenFiscal'] = @regimen_fiscal
61
+ receptor = xml.at_xpath("//cfdi:Receptor")
62
+ receptor['Nombre'] = params[:receptor_razon].to_s
63
+ receptor['Rfc'] = params[:receptor_rfc].to_s
64
+
65
+ child_pago = xml.at_xpath("//pago10:Pago")
66
+ child_pago['FechaPago'] = time.strftime("%Y-%m-%dT%H:%M:%S")
67
+ child_pago['FormaDePagoP'] = params[:forma_pago].to_s
68
+ child_pago['MonedaP'] = params.fetch(:moneda, 'MXN')
69
+ child_pago['Monto'] = params[:total].round(2).to_s
70
+
71
+ saldo_anterior = params[:total].to_f
72
+
73
+ params[:line_items].each_with_index do |line, index|
74
+ monto = line[:monto].to_f
75
+ child_pago_relacionado = Nokogiri::XML::Node.new "pago10:DoctoRelacionado", xml
76
+ child_pago_relacionado['IdDocumento'] = params[:uuid]
77
+ child_pago_relacionado['MonedaDR'] = line.fetch(:moneda, 'MXN')
78
+ child_pago_relacionado['MetodoDePagoDR'] = 'PPD'
79
+ child_pago_relacionado['NumParcialidad'] = (index + 1).to_s
80
+
81
+ child_pago_relacionado['ImpSaldoAnt'] = (saldo_anterior).to_s
82
+ child_pago_relacionado['ImpPagado'] = monto.round(2).to_s
83
+ child_pago_relacionado['ImpSaldoInsoluto'] = (saldo_anterior - monto).to_s
84
+ saldo_anterior -= monto
85
+
86
+ child_pago.add_child(child_pago_relacionado)
87
+ end
88
+
89
+ # puts '---------------- Xml resultante comprobante de pago -----------------------'
90
+ # puts xml.to_xml
91
+ # puts '--------------------------------------------------------'
92
+
93
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
94
+ id = SecureRandom.hex
95
+
96
+ FileUtils.mkdir_p(path) unless File.exist?(path)
97
+ File.write("#{path}/tmp_c_#{id}.xml", xml.to_xml)
98
+ xml_path = "#{path}/tmp_c_#{id}.xml"
99
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
100
+
101
+ File.write("#{path}/pem_#{id}.pem", @pem)
102
+ key_pem_url = "#{path}/pem_#{id}.pem"
103
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
104
+ comprobante['Sello'] = sello
105
+
106
+ File.delete("#{xml_path}")
107
+ File.delete("#{key_pem_url}")
108
+
109
+ # puts '------ comprobante de pago antes de timbre -------'
110
+ # puts xml.to_xml
111
+
112
+ base64_xml = Base64.encode64(xml.to_xml)
113
+ request = Net::HTTP::Post.new(uri)
114
+ request.basic_auth(token, "")
115
+ request.content_type = "application/json"
116
+ request["cache-control"] = 'no-cache'
117
+ request.body = JSON.dump({
118
+ "credentials" => {
119
+ "id" => params[:venta_folio].to_s,
120
+ "token" => token.to_s
121
+ },
122
+ "issuer" => {
123
+ "rfc" => @rfc
124
+ },
125
+ "document" => {
126
+ "ref-id": params[:venta_folio].to_s,
127
+ "certificate-number": @serial,
128
+ "section": "all",
129
+ "format": "xml",
130
+ "template": "letter",
131
+ "type": "application/xml",
132
+ "content": base64_xml
133
+ }
134
+ })
135
+
136
+ req_options = {
137
+ use_ssl: false,
138
+ }
139
+
140
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
141
+ http.request(request)
142
+ end
143
+ puts "-- #{json_response.code} --"
144
+ puts "-- #{json_response.message} --"
145
+ # puts "-- Body --"
146
+ # puts json_response.body
147
+ # puts '---'
148
+ response = JSON.parse(json_response.body)
149
+
150
+ if json_response.code == '200'
151
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
152
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children[1]
153
+ response = {
154
+ status: 200,
155
+ message_error: '',
156
+ xml: decoded_xml.to_xml,
157
+ uuid: response['uuid'],
158
+ fecha_timbrado: timbre['FechaTimbrado'],
159
+ sello_cfd: timbre['SelloCFD'],
160
+ sello_sat: timbre['SelloSAT'],
161
+ no_certificado_sat: timbre['NoCertificadoSAT'],
162
+ }
163
+ return response
164
+ else
165
+ response = {
166
+ status: json_response.code,
167
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
168
+ xml: '',
169
+ uuid: '',
170
+ fecha_timbrado: '',
171
+ sello_cfd: '',
172
+ sello_sat: '',
173
+ no_certificado_sat: '',
174
+ }
175
+ return response
176
+ end
177
+
178
+ end
179
+
180
+ def nota_credito(params={})
181
+ # Sample params
182
+ # params = {
183
+ # uuid_relacionado: '',
184
+ # desc: '',
185
+ # motivo: 'dev, mod',
186
+ # series: '',
187
+ # folio: '',
188
+ # cp: '',
189
+ # time: '',
190
+ # receptor_razon: '',
191
+ # receptor_rfc: '',
192
+ # uso_cfdi: '',
193
+ # }
194
+
195
+ total = (params[:monto]).to_f
196
+ subtotal = total / 1.16
197
+ tax = total - subtotal
198
+
199
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
200
+ token = @production ? @production_token : @dev_token
201
+ time = params.fetch(:time, Time.now)
202
+
203
+ base_doc = %(<?xml version="1.0" encoding="utf-8"?>
204
+ <cfdi:Comprobante xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sat.gob.mx/cfd/3 http://www.sat.gob.mx/sitio_internet/cfd/3/cfdv33.xsd" Version="3.3" Serie="#{params.fetch(:series, 'N')}" Folio="#{params[:folio]}" Fecha="#{time.strftime("%Y-%m-%dT%H:%M:%S")}" FormaPago="99" NoCertificado="#{@serial}" Certificado="#{@cadena}" SubTotal="#{subtotal.round(2)}" Moneda="#{params.fetch(:moneda, 'MXN')}" Total="#{total.round(2)}" TipoDeComprobante="E" MetodoPago="PUE" LugarExpedicion="#{params[:cp]}" xmlns:cfdi="http://www.sat.gob.mx/cfd/3">
205
+ <cfdi:CfdiRelacionados TipoRelacion="01">
206
+ <cfdi:CfdiRelacionado UUID="#{params[:uuid_relacionado]}" />
207
+ </cfdi:CfdiRelacionados>
208
+ <cfdi:Emisor Rfc="#{@rfc}" Nombre="#{@razon}" RegimenFiscal="#{@regimen_fiscal}" />
209
+ <cfdi:Receptor Rfc="#{params[:receptor_rfc]}" Nombre="#{params[:receptor_razon]}" UsoCFDI="#{params.fetch(:uso_cfdi, 'G03')}" />
210
+ <cfdi:Conceptos>
211
+ <cfdi:Concepto ClaveUnidad="ACT" ClaveProdServ="84111506" NoIdentificacion="C" Cantidad="1.00" Unidad="Pieza" Descripcion="#{params.fetch(:desc, 'DICTAMEN CC FACTURA ORIGEN 0')}" ValorUnitario="#{subtotal.round(2)}" Importe="#{subtotal.round(2)}">
212
+ <cfdi:Impuestos>
213
+ <cfdi:Traslados>
214
+ <cfdi:Traslado Base="#{subtotal.round(2)}" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="#{tax.round(2)}" />
215
+ </cfdi:Traslados>
216
+ </cfdi:Impuestos>
217
+ </cfdi:Concepto>
218
+ </cfdi:Conceptos>
219
+ <cfdi:Impuestos TotalImpuestosTrasladados="#{tax.round(2)}">
220
+ <cfdi:Traslados>
221
+ <cfdi:Traslado Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="#{tax.round(2)}" />
222
+ </cfdi:Traslados>
223
+ </cfdi:Impuestos>
224
+ </cfdi:Comprobante>
225
+ )
226
+
227
+ base_doc.delete!("\n")
228
+ base_doc.delete!("\t")
229
+
230
+ xml = Nokogiri::XML(base_doc)
231
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
232
+
233
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
234
+ id = SecureRandom.hex
235
+
236
+ FileUtils.mkdir_p(path) unless File.exist?(path)
237
+ File.write("#{path}/tmp_n_#{id}.xml", xml.to_xml)
238
+ xml_path = "#{path}/tmp_n_#{id}.xml"
239
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
240
+
241
+ File.write("#{path}/pem_#{id}.pem", @pem)
242
+ key_pem_url = "#{path}/pem_#{id}.pem"
243
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
244
+ comprobante['Sello'] = sello
245
+
246
+ File.delete("#{xml_path}")
247
+ File.delete("#{key_pem_url}")
248
+
249
+ puts '------ nota antes de timbre -------'
250
+ puts xml.to_xml
251
+
252
+ base64_xml = Base64.encode64(xml.to_xml)
253
+
254
+ request = Net::HTTP::Post.new(uri)
255
+ request.basic_auth(token, "")
256
+ request.content_type = "application/json"
257
+ request["cache-control"] = 'no-cache'
258
+ request.body = JSON.dump({
259
+ "credentials" => {
260
+ "id" => "#{params[:folio]}",
261
+ "token" => token
262
+ },
263
+ "issuer" => {
264
+ "rfc" => @rfc
265
+ },
266
+ "document" => {
267
+ "ref-id": "#{params[:folio]}",
268
+ "certificate-number": @serial,
269
+ "section": "all",
270
+ "format": "xml",
271
+ "template": "letter",
272
+ "type": "application/xml",
273
+ "content": base64_xml
274
+ }
275
+ })
276
+
277
+ req_options = {
278
+ use_ssl: false,
279
+ }
280
+
281
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
282
+ http.request(request)
283
+ end
284
+
285
+
286
+ puts "-- #{json_response.code} --"
287
+ puts "-- #{json_response.message} --"
288
+ # puts "-- Body --"
289
+ # puts json_response.body
290
+ # puts '---'
291
+ response = JSON.parse(json_response.body)
292
+
293
+ if json_response.code == '200'
294
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
295
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children.first
296
+
297
+ response = {
298
+ status: 200,
299
+ message_error: '',
300
+ xml: decoded_xml.to_xml,
301
+ uuid: response['uuid'],
302
+ fecha_timbrado: timbre['FechaTimbrado'],
303
+ sello_cfd: timbre['SelloCFD'],
304
+ sello_sat: timbre['SelloSAT'],
305
+ no_certificado_sat: timbre['NoCertificadoSAT'],
306
+ }
307
+ return response
308
+ else
309
+ response = {
310
+ status: json_response.code,
311
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
312
+ xml: '',
313
+ uuid: '',
314
+ fecha_timbrado: '',
315
+ sello_cfd: '',
316
+ sello_sat: '',
317
+ no_certificado_sat: '',
318
+ }
319
+ return response
320
+ end
321
+
322
+
323
+ end
324
+
325
+ def cancela_doc(params={})
326
+ # Sample params
327
+ # params = {
328
+ # uuid: '',
329
+ # rfc_emisor: '',
330
+ # key_password: '', # optional
331
+ # cer_cadena: '', # optional
332
+ # key_pem: '' # optional
333
+ # }
334
+
335
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/cancel/csd") : URI("#{SwFac::UrlDev}cfdi33/cancel/csd")
336
+ token = @production ? @production_token : @dev_token
337
+ time = Time.now
338
+
339
+ request = Net::HTTP::Post.new(uri)
340
+ request["Authorization"] = "bearer #{token}"
341
+ request.content_type = "application/json"
342
+ request["Cache-Control"] = 'no-cache'
343
+ request["Postman-Token"] = '30b35bb8-534d-51c7-6a5c-e2c98a0c9395'
344
+ request.body = JSON.dump({
345
+ 'uuid': params[:uuid],
346
+ "password": params.fetch(:key_password, @key_pass),
347
+ "rfc": params.fetch(:rfc_emisor, @rfc),
348
+ "b64Cer": params.fetch(:cer_cadena, @cadena),
349
+ "b64Key": params.fetch(:key_pem, @pem_cadena)
350
+ })
351
+
352
+ req_options = {
353
+ use_ssl: false,
354
+ }
355
+
356
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
357
+ http.request(request)
358
+ end
359
+
360
+ puts "-- #{json_response.code} --"
361
+ puts "-- #{json_response.message} --"
362
+ # puts "-- Body --"
363
+ # puts json_response.body
364
+ # puts '---'
365
+ response = JSON.parse(json_response.body)
366
+
367
+ if json_response.code == '200'
368
+ decoded_xml = response['data']['acuse']
369
+
370
+ response = {
371
+ status: 200,
372
+ message_error: '',
373
+ xml: decoded_xml,
374
+ }
375
+
376
+ return response
377
+ else
378
+
379
+ response ={
380
+ status: json_response.code,
381
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
382
+ xml: '',
383
+ }
384
+
385
+ return response
386
+ end
387
+
388
+ end
389
+
390
+ def timbra_doc(params={})
391
+ ### sample params
392
+ #
393
+ # params = {
394
+ # moneda: 'MXN',
395
+ # series: 'FA',
396
+ # folio: '003',
397
+ # forma_pago: '',
398
+ # metodo_pago: 'PUE',
399
+ # cp: '47180',
400
+ # receptor_razon: 'Car zone',
401
+ # receptor_rfc: '',
402
+ # uso_cfdi: 'G03',
403
+ # line_items: [
404
+ # {
405
+ # clave_prod_serv: '78181500',
406
+ # clave_unidad: 'E48',
407
+ # unidad: 'Servicio',
408
+ # sku: 'serv001',
409
+ # cantidad: 1,
410
+ # descripcion: 'Servicio mano de obra',
411
+ # valor_unitario: 100.00,
412
+ # descuento: 0.00,
413
+ # tax_included: true,
414
+ # # Optional parameters
415
+ # # tipo_impuesto: '002'
416
+ # },
417
+ # ]
418
+
419
+ # }
420
+
421
+
422
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
423
+ token = @production ? @production_token : @dev_token
424
+ time = Time.now
425
+
426
+ xml = Nokogiri::XML(SwFac::DocBase)
427
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
428
+ comprobante['TipoCambio'] = '1'
429
+ comprobante['TipoDeComprobante'] = 'I'
430
+ comprobante['Serie'] = params.fetch(:series, 'FA').to_s
431
+ comprobante['Folio'] = params.fetch(:folio).to_s
432
+ comprobante['Fecha'] = time.strftime("%Y-%m-%dT%H:%M:%S")
433
+ comprobante['FormaPago'] = params.fetch(:forma_pago, '01')
434
+ comprobante['MetodoPago'] = params.fetch(:metodo_pago, 'PUE')
435
+ comprobante['LugarExpedicion'] = params.fetch(:cp, '')
436
+ comprobante['NoCertificado'] = @serial
437
+ comprobante['Certificado'] = @cadena
438
+
439
+ emisor = xml.at_xpath("//cfdi:Emisor")
440
+ emisor['Nombre'] = @razon
441
+ emisor['RegimenFiscal'] = @regimen_fiscal
442
+ emisor['Rfc'] = @rfc
443
+
444
+ receptor = xml.at_xpath("//cfdi:Receptor")
445
+ receptor['Nombre'] = params.fetch(:receptor_razon, '')
446
+ receptor['Rfc'] = params.fetch(:receptor_rfc, '')
447
+ receptor['UsoCFDI'] = params.fetch(:uso_cfdi, 'G03')
448
+
449
+ impuestos = xml.at_xpath("//cfdi:Impuestos")
450
+ traslado = xml.at_xpath("//cfdi:Traslado")
451
+
452
+
453
+ conceptos = xml.at_xpath("//cfdi:Conceptos")
454
+
455
+ line_items = params[:line_items]
456
+
457
+ suma_total = 0.00
458
+ subtotal = 0.00
459
+ suma_iva = 0.00
460
+
461
+ line_items.each do |line|
462
+ descuento = line.fetch(:descuento, 0.00).to_f
463
+
464
+ if line[:tax_included] == true
465
+ unitario = ((line[:valor_unitario]).to_f - descuento) / 1.16
466
+ else
467
+ unitario = (line[:valor_unitario].to_f) - descuento
468
+ end
469
+
470
+ cantidad = line[:cantidad].to_f
471
+ total_line = cantidad * unitario
472
+ total_acumulator = cantidad * unitario * 1.16
473
+ importe_iva = total_acumulator - total_line
474
+
475
+ subtotal += total_line
476
+ suma_iva += importe_iva
477
+ suma_total += total_acumulator
478
+
479
+
480
+ child_concepto = Nokogiri::XML::Node.new "cfdi:Concepto", xml
481
+ child_concepto['ClaveProdServ'] = line[:clave_prod_serv].to_s
482
+ child_concepto['NoIdentificacion'] = line[:sku].to_s
483
+ child_concepto['ClaveUnidad'] = line[:clave_unidad].to_s
484
+ child_concepto['Unidad'] = line[:unidad].to_s
485
+ child_concepto['Descripcion'] = line[:descripcion].to_s
486
+ child_concepto['Cantidad'] = cantidad.to_s
487
+ child_concepto['ValorUnitario'] = unitario.round(6).to_s
488
+ child_concepto['Importe'] = total_line.round(6).to_s
489
+ # child_concepto['Descuento'] = line.fetch(:descuento, 0.00).round(6).to_s
490
+
491
+ child_impuestos = Nokogiri::XML::Node.new "cfdi:Impuestos", xml
492
+ child_traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
493
+ child_traslado = Nokogiri::XML::Node.new "cfdi:Traslado", xml
494
+ child_traslado['Base'] = total_line.round(6).to_s
495
+ child_traslado['Impuesto'] = line.fetch(:tipo_impuesto, '002')
496
+ child_traslado['TipoFactor'] = "Tasa"
497
+ child_traslado['TasaOCuota'] = '0.160000'
498
+ child_traslado['Importe'] = importe_iva.round(6).to_s
499
+
500
+ # Joining all up
501
+ child_traslados.add_child(child_traslado)
502
+ child_impuestos.add_child(child_traslados)
503
+ child_concepto.add_child(child_impuestos)
504
+
505
+ conceptos.add_child(child_concepto)
506
+
507
+ end
508
+
509
+ puts '------ Line -----'
510
+ puts "Total suma = #{suma_total}"
511
+ puts "SubTotal suma = #{subtotal}"
512
+ puts "Suma iva = #{suma_iva}"
513
+
514
+ comprobante['SubTotal'] = subtotal.round(2).to_s
515
+ comprobante['Total'] = suma_total.round(2).to_s
516
+ comprobante['Moneda'] = params.fetch(:moneda, 'MXN')
517
+
518
+ impuestos['TotalImpuestosTrasladados'] = suma_iva.round(2).to_s
519
+ traslado['Importe'] = suma_iva.round(2).to_s
520
+
521
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
522
+ id = SecureRandom.hex
523
+
524
+ FileUtils.mkdir_p(path) unless File.exist?(path)
525
+ File.write("#{path}/tmp_#{id}.xml", xml.to_xml)
526
+ xml_path = "#{path}/tmp_#{id}.xml"
527
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
528
+
529
+ # puts File.read(cadena_path)
530
+ File.write("#{path}/pem_#{id}.pem", @pem)
531
+ key_pem_url = "#{path}/pem_#{id}.pem"
532
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
533
+ comprobante['Sello'] = sello
534
+
535
+ File.delete("#{xml_path}")
536
+ File.delete("#{key_pem_url}")
537
+
538
+ # puts '---- comprobante sin timbrar------'
539
+ # puts xml.to_xml
540
+ # puts '-------------------------'
541
+
542
+ base64_xml = Base64.encode64(xml.to_xml)
543
+ request = Net::HTTP::Post.new(uri)
544
+ request.basic_auth(token, "")
545
+ request.content_type = "application/json"
546
+ request["cache-control"] = 'no-cache'
547
+ request.body = JSON.dump({
548
+ "credentials" => {
549
+ "id" => params.fetch(:folio).to_s,
550
+ "token" => token
551
+ },
552
+ "issuer" => {
553
+ "rfc" => emisor['Rfc']
554
+ },
555
+ "document" => {
556
+ "ref-id": params.fetch(:folio).to_s,
557
+ "certificate-number": comprobante['NoCertificado'],
558
+ "section": "all",
559
+ "format": "xml",
560
+ "template": "letter",
561
+ "type": "application/xml",
562
+ "content": base64_xml
563
+ }
564
+ })
565
+
566
+ req_options = {
567
+ use_ssl: false,
568
+ }
569
+
570
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
571
+ http.request(request)
572
+ end
573
+
574
+ puts "-- #{json_response.code} --"
575
+ puts "-- #{json_response.message} --"
576
+ # puts "-- Body --"
577
+ # puts json_response.body
578
+ # puts '---'
579
+ response = JSON.parse(json_response.body)
580
+
581
+ if json_response.code == '200'
582
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
583
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children.first
584
+
585
+ response = {
586
+ status: 200,
587
+ message_error: '',
588
+ xml: decoded_xml.to_xml,
589
+ uuid: response['uuid'],
590
+ fecha_timbrado: timbre['FechaTimbrado'],
591
+ sello_cfd: timbre['SelloCFD'],
592
+ sello_sat: timbre['SelloSAT'],
593
+ no_certificado_sat: timbre['NoCertificadoSAT'],
594
+ }
595
+
596
+ return response
597
+ else
598
+
599
+ response ={
600
+ status: json_response.code,
601
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
602
+ xml: '',
603
+ uuid: '',
604
+ fecha_timbrado: '',
605
+ sello_cfd: '',
606
+ sello_sat: '',
607
+ no_certificado_sat: '',
608
+ }
609
+
610
+ return response
611
+ end
612
+
613
+ end
614
+
615
+ end
616
+ end