fdis2 0.1.5

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 (46) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +51 -0
  3. data/lib/cadena/CartaPorte20.xslt +615 -0
  4. data/lib/cadena/ComercioExterior11.xslt +181 -0
  5. data/lib/cadena/GastosHidrocarburos10.xslt +171 -0
  6. data/lib/cadena/IngresosHidrocarburos.xslt +39 -0
  7. data/lib/cadena/Pagos10.xslt +165 -0
  8. data/lib/cadena/Pagos20.xsd +532 -0
  9. data/lib/cadena/Pagos20.xslt +233 -0
  10. data/lib/cadena/TuristaPasajeroExtranjero.xslt +40 -0
  11. data/lib/cadena/aerolineas.xslt +50 -0
  12. data/lib/cadena/cadena33.xslt +349 -0
  13. data/lib/cadena/cadenaoriginal_4_0.xslt +405 -0
  14. data/lib/cadena/catCFDI.xsd +162331 -0
  15. data/lib/cadena/certificadodedestruccion.xslt +60 -0
  16. data/lib/cadena/cfdiregistrofiscal.xslt +19 -0
  17. data/lib/cadena/cfdv33.xsd +737 -0
  18. data/lib/cadena/cfdv40.xsd +850 -0
  19. data/lib/cadena/consumodeCombustibles11.xslt +94 -0
  20. data/lib/cadena/consumodecombustibles.xslt +108 -0
  21. data/lib/cadena/detallista.xslt +42 -0
  22. data/lib/cadena/divisas.xslt +13 -0
  23. data/lib/cadena/donat11.xslt +13 -0
  24. data/lib/cadena/ecc11.xslt +102 -0
  25. data/lib/cadena/ecc12.xslt +99 -0
  26. data/lib/cadena/iedu.xslt +26 -0
  27. data/lib/cadena/implocal.xslt +39 -0
  28. data/lib/cadena/ine11.xslt +51 -0
  29. data/lib/cadena/leyendasFisc.xslt +28 -0
  30. data/lib/cadena/nomina12.xslt +412 -0
  31. data/lib/cadena/notariospublicos.xslt +301 -0
  32. data/lib/cadena/obrasarteantiguedades.xslt +33 -0
  33. data/lib/cadena/pagoenespecie.xslt +39 -0
  34. data/lib/cadena/pfic.xslt +13 -0
  35. data/lib/cadena/renovacionysustitucionvehiculos.xslt +152 -0
  36. data/lib/cadena/servicioparcialconstruccion.xslt +44 -0
  37. data/lib/cadena/terceros11.xslt +108 -0
  38. data/lib/cadena/utilerias.xslt +22 -0
  39. data/lib/cadena/valesdedespensa.xslt +70 -0
  40. data/lib/cadena/vehiculousado.xslt +63 -0
  41. data/lib/cadena/ventavehiculos11.xslt +53 -0
  42. data/lib/fdis2/config.rb +101 -0
  43. data/lib/fdis2/facturacion.rb +800 -0
  44. data/lib/fdis2/version.rb +3 -0
  45. data/lib/fdis2.rb +17 -0
  46. metadata +144 -0
@@ -0,0 +1,800 @@
1
+
2
+ module Fdis2
3
+ class Facturacion < Config
4
+
5
+ def comp_pago(params={})
6
+ # Sample params
7
+ # params = {
8
+ # uuid: '',
9
+ # folio: '',
10
+ # cp: '',
11
+ # receptor_razon: 'Car zone',
12
+ # receptor_rfc: 'XAXX010101000',
13
+ # receptor_cp: '47180',
14
+ # receptor_regimen: '616',
15
+ # tasa_iva: 0, 16, se toma tasa iva de factura madre
16
+ # forma_pago: '01',
17
+ # total: 100.00,
18
+ # monto_pago: 50.0,
19
+ # saldo_anterior: 100.0,
20
+ # num_parcialidad: '1',
21
+ # time_pago: '',
22
+ # time_now: '',
23
+ # modena: '',
24
+ # line_items: [
25
+ # {
26
+ # monto: 60.00,
27
+ # moneda: '',
28
+ # id: ,
29
+ # },
30
+ # ]
31
+ # }
32
+
33
+ puts "----- Fdis: Facturacion::com_pago"
34
+ puts "--- Fdis: Total venta: #{params[:total]}"
35
+ puts "--- Fdis: Monto de pago a procesar: #{params[:monto_pago]}"
36
+ puts "--- Fdis: Saldo insoluto anterior: #{params[:saldo_anterior]}"
37
+ puts "--- Fdis: Line items: "
38
+ params[:line_items].each do |line|
39
+ puts "-- #{line[:monto]}"
40
+ end
41
+ lines_total = params[:line_items].inject(0) {|sum, x| sum + x[:monto].to_f}
42
+
43
+ puts "--- Fdis: Suma de lineas: #{lines_total}"
44
+
45
+ if (lines_total > params[:total].to_f)
46
+ raise 'Error Fdis - la suma de los complementos de pago es mayor al total de la venta'
47
+ end
48
+
49
+ unless params[:time_pago] and params[:time_pago].size > 0
50
+ raise "Error Fdis - la fecha de timbrado debe de estar presente"
51
+ end
52
+
53
+ if params[:num_parcialidad].empty?
54
+ raise "Error Fdis - Se debe registrar el número de parcialidad que corresponde al pago"
55
+ end
56
+
57
+
58
+
59
+
60
+ time_now = params.fetch(:time_now, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
61
+ time_pago = params[:time_pago]
62
+
63
+
64
+ base_doc = %(<?xml version="1.0" encoding="UTF-8"?>
65
+ <cfdi:Comprobante xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:pago20="http://www.sat.gob.mx/Pagos20" xsi:schemaLocation="http://www.sat.gob.mx/Pagos20 http://www.sat.gob.mx/sitio_internet/cfd/Pagos/Pagos20.xsd http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd" Version="4.0" Serie="" Folio="" Fecha="" Sello="" NoCertificado="" Certificado="" SubTotal="0" Moneda="XXX" Total="0" TipoDeComprobante="P" Exportacion="01" LugarExpedicion="" xmlns:cfdi="http://www.sat.gob.mx/cfd/4">
66
+
67
+ <cfdi:Emisor Rfc="" Nombre="" RegimenFiscal="" />
68
+ <cfdi:Receptor Rfc="" Nombre="" UsoCFDI="CP01" DomicilioFiscalReceptor="" RegimenFiscalReceptor="616" />
69
+
70
+ <cfdi:Conceptos>
71
+ <cfdi:Concepto ClaveProdServ="84111506" Cantidad="1" ClaveUnidad="ACT" Descripcion="Pago" ValorUnitario="0" Importe="0" ObjetoImp="01" />
72
+ </cfdi:Conceptos>
73
+
74
+ <cfdi:Complemento>
75
+
76
+ <pago20:Pagos Version="2.0">
77
+ <pago20:Totales MontoTotalPagos="" />
78
+
79
+ <pago20:Pago FechaPago="" FormaDePagoP="" MonedaP="MXN" Monto="" TipoCambioP="1">
80
+
81
+ <pago20:DoctoRelacionado IdDocumento="" MonedaDR="MXN" NumParcialidad="" ImpSaldoAnt="" ImpPagado="" ImpSaldoInsoluto="" ObjetoImpDR="02" EquivalenciaDR="1">
82
+
83
+
84
+ </pago20:DoctoRelacionado>
85
+
86
+ </pago20:Pago>
87
+
88
+ </pago10:Pagos>
89
+
90
+ </cfdi:Complemento>
91
+
92
+ </cfdi:Comprobante>)
93
+
94
+ base_doc.delete!("\n")
95
+ base_doc.delete!("\t")
96
+
97
+ xml = Nokogiri::XML(base_doc)
98
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
99
+ comprobante['Serie'] = 'P'
100
+ comprobante['Folio'] = params[:folio].to_s
101
+ comprobante['Fecha'] = time_now
102
+ comprobante['LugarExpedicion'] = params[:cp].to_s
103
+ comprobante['NoCertificado'] = @serial
104
+ comprobante['Certificado'] = @cadena
105
+
106
+ # Emisor datos
107
+ emisor = xml.at_xpath("//cfdi:Emisor")
108
+ emisor['Rfc'] = @rfc
109
+ emisor['Nombre'] = @razon
110
+ emisor['RegimenFiscal'] = @regimen_fiscal
111
+
112
+ # Receptor datos
113
+ receptor = xml.at_xpath("//cfdi:Receptor")
114
+ receptor['Nombre'] = params[:receptor_razon].to_s
115
+ receptor['Rfc'] = params[:receptor_rfc].to_s
116
+ receptor['DomicilioFiscalReceptor'] = params.fetch(:receptor_cp, '47180')
117
+ receptor['RegimenFiscalReceptor'] = params.fetch(:receptor_regimen, '616')
118
+
119
+
120
+ # totales
121
+ total = params[:monto_pago].to_f.abs
122
+ iva_id = params.fetch(:tasa_iva, 16)
123
+
124
+ pago_totales = xml.at_xpath("//pago20:Totales")
125
+ pago_totales['MontoTotalPagos'] = total.round(2).to_s # total
126
+
127
+
128
+
129
+
130
+ if iva_id == 0
131
+ subtotal = total
132
+ iva = 0.00
133
+
134
+ pago_totales['TotalTrasladosBaseIVA0'] = subtotal.round(2).to_s # subtotal
135
+ pago_totales['TotalTrasladosImpuestoIVA0'] = iva.round(2).to_s # iva
136
+
137
+ else
138
+ subtotal = total / 1.16
139
+ iva = total - subtotal
140
+
141
+ pago_totales['TotalTrasladosBaseIVA16'] = subtotal.round(2).to_s # subtotal
142
+ pago_totales['TotalTrasladosImpuestoIVA16'] = iva.round(2).to_s # iva
143
+ end
144
+
145
+
146
+ # pagos = xml.at_xpath("//pago20:Pagos")
147
+
148
+ # pago
149
+ child_pago = xml.at_xpath("//pago20:Pago")
150
+
151
+ child_pago['FechaPago'] = time_pago
152
+ child_pago['FormaDePagoP'] = params[:forma_pago].to_s
153
+ child_pago['MonedaP'] = params.fetch(:moneda, 'MXN')
154
+ child_pago['Monto'] = total.round(2).to_s
155
+ child_pago['TipoCambioP'] = "1"
156
+
157
+ child_pago_relacionado = xml.at_xpath("//pago20:DoctoRelacionado")
158
+
159
+ child_pago_relacionado['IdDocumento'] = params[:uuid]
160
+ child_pago_relacionado['MonedaDR'] = 'MXN'
161
+ child_pago_relacionado['NumParcialidad'] = params[:num_parcialidad]
162
+ child_pago_relacionado['Serie'] = "Depo"
163
+ child_pago_relacionado['Folio'] = params[:folio]
164
+ child_pago_relacionado['EquivalenciaDR'] = "1"
165
+
166
+ saldo_anterior = params[:saldo_anterior].to_f.abs
167
+
168
+ child_pago_relacionado['ImpSaldoAnt'] = saldo_anterior.round(2).to_s
169
+ child_pago_relacionado['ImpPagado'] = total.round(2).to_s
170
+ child_pago_relacionado['ImpSaldoInsoluto'] = (saldo_anterior - total).round(2).abs.to_s
171
+ child_pago_relacionado['ObjetoImpDR'] = '02'
172
+
173
+ impuestos_dr = Nokogiri::XML::Node.new "pago20:ImpuestosDR", xml
174
+ traslados_dr = Nokogiri::XML::Node.new "pago20:TrasladosDR", xml
175
+ traslado = Nokogiri::XML::Node.new "pago20:TrasladoDR", xml
176
+
177
+ impuestos_p = Nokogiri::XML::Node.new "pago20:ImpuestosP", xml
178
+ traslados_p = Nokogiri::XML::Node.new "pago20:TrasladosP", xml
179
+ traslado_p = Nokogiri::XML::Node.new "pago20:TrasladoP", xml
180
+
181
+ traslado['TipoFactorDR'] = 'Tasa'
182
+ traslado['ImpuestoDR'] = '002'
183
+
184
+ traslado['BaseDR'] = subtotal.round(2).to_s #subtotal
185
+ traslado['ImporteDR'] = iva.round(2).to_s # tax
186
+
187
+ traslado_p['BaseP'] = subtotal.round(2).to_s
188
+ traslado_p['ImpuestoP'] = '002'
189
+ traslado_p['TipoFactorP'] = 'Tasa'
190
+ traslado_p['ImporteP'] = iva.round(2).to_s # tax
191
+
192
+ if iva_id == 16
193
+ traslado['TasaOCuotaDR'] = '0.160000'
194
+ # t_subtotal = total / 1.16
195
+ # t_tax = total - t_subtotal
196
+
197
+ traslado_p['TasaOCuotaP'] = '0.160000'
198
+
199
+ else
200
+ traslado['TasaOCuotaDR'] = '0.000000'
201
+ traslado_p['TasaOCuotaP'] = '0.000000'
202
+ end
203
+
204
+ traslados_dr.add_child(traslado)
205
+ impuestos_dr.add_child(traslados_dr)
206
+ child_pago_relacionado.add_child(impuestos_dr)
207
+
208
+ traslados_p.add_child(traslado_p)
209
+ impuestos_p.add_child(traslados_p)
210
+ child_pago.add_child(impuestos_p)
211
+
212
+
213
+
214
+ # puts '---------------- Xml resultante comprobante de pago -----------------------'
215
+ # puts xml.to_xml
216
+ # puts '--------------------------------------------------------'
217
+
218
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
219
+ id = SecureRandom.hex
220
+
221
+ FileUtils.mkdir_p(path) unless File.exist?(path)
222
+ File.write("#{path}/tmp_c_#{id}.xml", xml.to_xml)
223
+ xml_path = "#{path}/tmp_c_#{id}.xml"
224
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadenaoriginal_4_0.xslt])
225
+
226
+ File.write("#{path}/pem_#{id}.pem", @pem)
227
+ key_pem_url = "#{path}/pem_#{id}.pem"
228
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
229
+ comprobante['Sello'] = sello
230
+
231
+ File.delete("#{xml_path}")
232
+ File.delete("#{key_pem_url}")
233
+
234
+ puts '------ Fdis: comprobante de pago antes de timbre -------'
235
+ puts xml.to_xml
236
+ base64_xml = Base64.encode64(xml.to_xml)
237
+
238
+ # haciendo llamada a API
239
+ uri = URI("#{Fdis2::UrlPro}/Timbrar40")
240
+ request = Net::HTTP::Post.new(uri)
241
+ # request.basic_auth(token, "")
242
+ request.content_type = "application/json"
243
+ # request["cache-control"] = 'no-cache'
244
+ request.body = JSON.dump({
245
+ "testMode": !@production,
246
+ "idServicio": @id_servicio,
247
+ "base64XmlFile": base64_xml,
248
+ })
249
+
250
+ req_options = {
251
+ use_ssl: uri.scheme == "https",
252
+ }
253
+
254
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
255
+ http.request(request)
256
+ end
257
+
258
+ puts "---- Fdis: request"
259
+ puts "-- body: #{request.body} --"
260
+
261
+ puts "---- Fdis: Respuesta"
262
+ puts "-- Codigo: #{json_response.code} --"
263
+ puts "-- Mensaje: #{json_response.message} --"
264
+ puts "-- Body: "
265
+ p json_response.body
266
+ response = JSON.parse(json_response.body)
267
+
268
+ case json_response
269
+ when Net::HTTPSuccess, Net::HTTPRedirection
270
+ if response['success'] == true
271
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['base64XmlFile']))
272
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children[0]
273
+ response = {
274
+ status: 200,
275
+ message_error: '',
276
+ xml: decoded_xml.to_xml,
277
+ uuid: timbre['UUID'],
278
+ fecha_timbrado: timbre['FechaTimbrado'],
279
+ sello_cfd: timbre['SelloCFD'],
280
+ sello_sat: timbre['SelloSAT'],
281
+ no_certificado_sat: timbre['NoCertificadoSAT'],
282
+ }
283
+ return response
284
+ else
285
+ response = {
286
+ status: 400,
287
+ message_error: "Error: #{response['errorMessages']}",
288
+ xml: '',
289
+ uuid: '',
290
+ fecha_timbrado: '',
291
+ sello_cfd: '',
292
+ sello_sat: '',
293
+ no_certificado_sat: '',
294
+ }
295
+ return response
296
+
297
+ end
298
+ else
299
+ response = {
300
+ status: json_response.code,
301
+ message_error: "Error: #{response['errorMessages']}",
302
+ xml: '',
303
+ uuid: '',
304
+ fecha_timbrado: '',
305
+ sello_cfd: '',
306
+ sello_sat: '',
307
+ no_certificado_sat: '',
308
+ }
309
+ return response
310
+
311
+ end
312
+
313
+
314
+ end
315
+
316
+
317
+
318
+ def cancela_doc(params={})
319
+ puts "---- Fdis:facturacion:cancela_doc"
320
+
321
+ # Sample params
322
+ # params = {
323
+ # uuid: '',
324
+ # rfcReceptor: 'XAXX010101000',
325
+ # total_sale: 100.0,
326
+ # motivo: '02',
327
+ # uuid_sustituye: '',
328
+ # key_password: '', # optional
329
+ # cer_cadena: '', # optional
330
+ # key_pem: '' # optional
331
+ # }
332
+
333
+
334
+ uri = URI(Fdis2::UrlCancel)
335
+ request = Net::HTTP::Post.new(uri)
336
+ request.content_type = "application/json"
337
+
338
+ request.body = JSON.dump({
339
+ rfcEmisor: @rfc,
340
+ rfcReceptor: params[:rfcReceptor],
341
+ keyPassword: @key_pass,
342
+ totalCfdi: params[:total_sale],
343
+ uuid: params[:uuid],
344
+ motivoCancelacion: params.fetch(:motivo, '02'),
345
+ FolioFiscalSustituye: params.fetch(:uuid_sustituye, SecureRandom.uuid.upcase),
346
+ testMode: !@production,
347
+ base64CerFile: params.fetch(:cer_cadena, @cadena),
348
+ base64KeyFile: params.fetch(:key_pem, @pem_cadena),
349
+ })
350
+
351
+ req_options = {
352
+ use_ssl: uri.scheme == "https",
353
+ }
354
+
355
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
356
+ http.request(request)
357
+ end
358
+
359
+ puts "---- Fdis: request"
360
+ puts "-- body: #{request.body} --"
361
+
362
+ puts "---- Fdis: Respuesta"
363
+ puts "-- Codigo: #{json_response.code} --"
364
+ puts "-- Mensaje: #{json_response.message} --"
365
+ puts "-- Body: "
366
+ p json_response.body
367
+
368
+ response = JSON.parse(json_response.body)
369
+
370
+ case json_response
371
+ when Net::HTTPSuccess, Net::HTTPRedirection
372
+ if response['success'] == true
373
+ acuse = response['acuseCancelacion']
374
+ response = {
375
+ status: 200,
376
+ message_error: '',
377
+ xml: acuse,
378
+ }
379
+
380
+ return response
381
+ else
382
+ response = {
383
+ status: 400,
384
+ message_error: "Error: #{response['errors']}",
385
+ xml: '',
386
+
387
+ }
388
+ return response
389
+ end
390
+
391
+ else
392
+ response = {
393
+ status: 400,
394
+ message_error: "Error: #{response['errors']}",
395
+ xml: '',
396
+
397
+ }
398
+ return response
399
+ end
400
+
401
+ end
402
+
403
+
404
+ def timbra_doc(params={})
405
+ ### sample params
406
+ #
407
+ # params = {
408
+ # moneda: 'MXN',
409
+ # series: 'FA',
410
+ # folio: '003',
411
+ # forma_pago: '',
412
+ # metodo_pago: 'PUE',
413
+ # cp: '47180',
414
+ # receptor_cp: '47180',
415
+ # receptor_razon: 'Car zone',
416
+ # receptor_rfc: '',
417
+ # receptor_regimen: '',
418
+ # uso_cfdi: 'G03',
419
+ # tasa_iva: 0, 16,
420
+ # time: "%Y-%m-%dT%H:%M:%S",
421
+ # line_items: [
422
+ # {
423
+ # clave_prod_serv: '78181500',
424
+ # clave_unidad: 'E48',
425
+ # unidad: 'Servicio',
426
+ # sku: 'serv001',
427
+ # cantidad: 1,
428
+ # descripcion: 'Servicio mano de obra',
429
+ # valor_unitario: 100.00,
430
+ # descuento: 0.00,
431
+ # tax: 16.0 o 0.0,
432
+ # retencion_iva: 0, 6, 16
433
+ # # Optional parameters
434
+ # },
435
+ # ]
436
+
437
+ # }
438
+
439
+ puts "---- Fdis:facturacion:timbra_doc"
440
+
441
+ puts "--- Fdis: Datos --------"
442
+ puts "--- Fdis: Line items: "
443
+ params[:line_items].each do |line|
444
+ puts "----- valor unitario: #{line[:valor_unitario]}"
445
+ puts "----- cantidad: #{line[:cantidad]}"
446
+ end
447
+
448
+ time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
449
+
450
+
451
+ base_doc = %(<?xml version="1.0" encoding="utf-8"?>
452
+ <cfdi:Comprobante xsi:schemaLocation="http://www.sat.gob.mx/cfd/4 http://www.sat.gob.mx/sitio_internet/cfd/4/cfdv40.xsd" Version="4.0" xmlns:cfdi="http://www.sat.gob.mx/cfd/4" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" Fecha="" Serie="" Folio="" FormaPago="" SubTotal="" Moneda="MXN" Total="" TipoDeComprobante="I" MetodoPago="" LugarExpedicion="" Certificado="" NoCertificado="" Sello="" Exportacion="01">
453
+
454
+ <cfdi:Emisor Rfc="" Nombre="" RegimenFiscal="" />
455
+ <cfdi:Receptor Rfc="" Nombre="" DomicilioFiscalReceptor="" RegimenFiscalReceptor="" UsoCFDI="" />
456
+
457
+ <cfdi:Conceptos></cfdi:Conceptos>
458
+ <cfdi:Impuestos></cfdi:Impuestos>
459
+
460
+ </cfdi:Comprobante>)
461
+
462
+ base_doc.delete!("\n")
463
+ base_doc.delete!("\t")
464
+
465
+ xml = Nokogiri::XML(base_doc)
466
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
467
+ comprobante['TipoCambio'] = '1'
468
+ comprobante['TipoDeComprobante'] = 'I'
469
+ comprobante['Serie'] = params.fetch(:series, 'FA').to_s
470
+ comprobante['Folio'] = params.fetch(:folio).to_s
471
+ comprobante['Fecha'] = time.to_s
472
+ comprobante['MetodoPago'] = params.fetch(:metodo_pago, 'PUE')
473
+ comprobante['FormaPago'] = params.fetch(:forma_pago, '01')
474
+
475
+ if comprobante['MetodoPago'] == 'PPD'
476
+ comprobante['FormaPago'] = '99'
477
+ end
478
+
479
+
480
+ comprobante['LugarExpedicion'] = params.fetch(:cp, '47180')
481
+ comprobante['NoCertificado'] = @serial
482
+ comprobante['Certificado'] = @cadena
483
+
484
+ # emisor
485
+ emisor = xml.at_xpath("//cfdi:Emisor")
486
+ emisor['Nombre'] = @razon
487
+ emisor['RegimenFiscal'] = @regimen_fiscal
488
+ emisor['Rfc'] = @rfc
489
+
490
+ # receptor
491
+ receptor = xml.at_xpath("//cfdi:Receptor")
492
+ receptor['Rfc'] = params.fetch(:receptor_rfc, '')
493
+ receptor['Nombre'] = params.fetch(:receptor_razon, '')
494
+ receptor['DomicilioFiscalReceptor'] = params.fetch(:receptor_cp, '47180')
495
+ if params[:receptor_rfc] == 'XAXX010101000'
496
+ receptor['UsoCFDI'] = 'S01'
497
+ receptor['RegimenFiscalReceptor'] = '616'
498
+ else
499
+ receptor['UsoCFDI'] = params.fetch(:uso_cfdi, 'G03')
500
+ # receptor['RegimenFiscalReceptor'] = params.fetch(:receptor_regimen, '616')
501
+ receptor['RegimenFiscalReceptor'] = params[:receptor_regimen]
502
+ end
503
+
504
+ # retencion_iva = params.fetch(:retencion_iva, 0)
505
+
506
+ impuestos = xml.at_xpath("//cfdi:Impuestos")
507
+ traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
508
+
509
+
510
+ puts '--- Fdis time -----'
511
+ puts time
512
+ puts '--------'
513
+
514
+ conceptos = xml.at_xpath("//cfdi:Conceptos")
515
+
516
+ line_items = params[:line_items]
517
+
518
+ suma_total = 0.00
519
+ subtotal = 0.00
520
+ suma_iva = 0.00
521
+ suma_ret = 0.00
522
+
523
+
524
+ line_items.each do |line|
525
+ iva_id = line.fetch(:tax, 16.0)
526
+
527
+ ret_iva = line.fetch(:retencion_iva, 0)
528
+
529
+ cantidad = line[:cantidad].to_f
530
+
531
+
532
+
533
+ # if line[:tipo_impuesto] == '004'
534
+ # total_acumulator = cantidad * valor_unitario
535
+ # else
536
+ # total_acumulator = cantidad * valor_unitario * 1.16
537
+ # end
538
+
539
+ ## TODO: ajustar todo a facturacion con iva cero
540
+
541
+ total_acumulator = cantidad * line[:valor_unitario].to_f
542
+
543
+ if iva_id > 0
544
+ tax_factor = (iva_id / 100) + 1
545
+ valor_unitario = (line[:valor_unitario].to_f) / tax_factor
546
+ else
547
+ valor_unitario = line[:valor_unitario].to_f
548
+ end
549
+
550
+ # if iva_id == 16
551
+ # valor_unitario = (line[:valor_unitario].to_f) / 1.16
552
+ # else
553
+ # valor_unitario = line[:valor_unitario].to_f
554
+ # end
555
+
556
+ subtotal_line = cantidad * valor_unitario
557
+ importe_iva = total_acumulator - subtotal_line
558
+
559
+ subtotal += subtotal_line
560
+ suma_iva += importe_iva
561
+ suma_total += total_acumulator
562
+
563
+ puts "--- 01"
564
+ ## calculando retencion de IVA en caso de tener
565
+ if ret_iva > 0
566
+ if ret_iva == 6
567
+ importe_ret_linea = (subtotal_line * 1.06) - subtotal_line
568
+ elsif ret_iva == 16
569
+ importe_ret_linea = importe_iva
570
+ end
571
+ else
572
+ importe_ret_linea = 0
573
+ end
574
+ puts "--- 02"
575
+ suma_ret += importe_ret_linea
576
+
577
+
578
+ ## Creando y poblando CFDI:CONCEPTO
579
+ child_concepto = Nokogiri::XML::Node.new "cfdi:Concepto", xml
580
+ child_concepto['ClaveProdServ'] = line[:clave_prod_serv].to_s
581
+ child_concepto['NoIdentificacion'] = line[:sku].to_s
582
+ child_concepto['ClaveUnidad'] = line[:clave_unidad].to_s
583
+ child_concepto['Unidad'] = line[:unidad].to_s
584
+ child_concepto['Descripcion'] = line[:descripcion].to_s
585
+ child_concepto['Cantidad'] = cantidad.to_s
586
+ child_concepto['ValorUnitario'] = valor_unitario.round(4).to_s
587
+ child_concepto['Importe'] = subtotal_line.round(4).to_s
588
+ child_concepto['ObjetoImp'] = '02'
589
+
590
+
591
+ ## Creando cdfi:Impuestos para cada linea
592
+ child_impuestos = Nokogiri::XML::Node.new "cfdi:Impuestos", xml
593
+
594
+ ## Creando cfdi:Traslados para cada linea
595
+ child_traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
596
+
597
+
598
+ child_traslado = Nokogiri::XML::Node.new "cfdi:Traslado", xml
599
+ child_traslado['Impuesto'] = '002'
600
+ child_traslado['TipoFactor'] = "Tasa"
601
+ child_traslado['Base'] = subtotal_line.round(4).to_s
602
+
603
+ if iva_id > 0
604
+ tasa_cuota = (iva_id / 100).round(6)
605
+ child_traslado['Importe'] = importe_iva.round(4).to_s
606
+ child_traslado['TasaOCuota'] = tasa_cuota.to_s
607
+ else
608
+ child_traslado['Importe'] = "0.00"
609
+ child_traslado['TasaOCuota'] = '0.000000'
610
+ end
611
+
612
+
613
+ # Mezclando todo lo anterios
614
+ child_traslados.add_child(child_traslado)
615
+ child_impuestos.add_child(child_traslados)
616
+ child_concepto.add_child(child_impuestos)
617
+ conceptos.add_child(child_concepto)
618
+
619
+ ## Creando cfdi:Retenciones para cada linea en caso de tener
620
+ if ret_iva > 0
621
+ child_retenciones = Nokogiri::XML::Node.new "cfdi:Retenciones", xml
622
+ child_retencion = Nokogiri::XML::Node.new "cfdi:Retencion", xml
623
+ child_retencion['Base'] = subtotal_line.round(4).to_s
624
+ child_retencion['Impuesto'] = '002'
625
+ child_retencion['TipoFactor'] = "Tasa"
626
+
627
+ if ret_iva == 6
628
+ child_retencion['TasaOCuota'] = "0.060000"
629
+ elsif ret_iva == 16
630
+ child_retencion['TasaOCuota'] = "0.160000"
631
+ end
632
+
633
+ child_retencion['Importe'] = importe_ret_linea.round(4).to_s
634
+
635
+ child_retenciones.add_child(child_retencion)
636
+ child_impuestos.add_child(child_retenciones)
637
+ end
638
+
639
+
640
+ end
641
+
642
+ puts '------ Totales -----'
643
+ puts "Total suma = #{suma_total.round(2)}"
644
+ puts "SubTotal suma = #{subtotal.round(2)}"
645
+ puts "Suma iva = #{suma_iva.round(2)}"
646
+ puts "Suma restenciones = #{suma_ret.round(2)}"
647
+
648
+ comprobante['Moneda'] = params.fetch(:moneda, 'MXN')
649
+ comprobante['SubTotal'] = subtotal.round(2).to_s
650
+
651
+
652
+ ## Poblando cfdi:Impuestos
653
+ impuestos['TotalImpuestosRetenidos'] = suma_ret.round(2).to_s if suma_ret > 0
654
+
655
+ if suma_iva > 0
656
+ impuestos['TotalImpuestosTrasladados'] = suma_iva.round(2).to_s
657
+ else
658
+ impuestos['TotalImpuestosTrasladados'] = "0.00"
659
+ end
660
+
661
+
662
+ ## filling default retencion info
663
+ if suma_ret > 0
664
+ retenciones = Nokogiri::XML::Node.new "cfdi:Retenciones", xml
665
+ retencion_child = Nokogiri::XML::Node.new "cfdi:Retencion", xml
666
+ retencion_child['Impuesto'] = "002"
667
+ retencion_child['Importe'] = suma_ret.round(2).to_s
668
+ # retencion_child['TipoFactor'] = "Tasa"
669
+
670
+ retenciones.add_child(retencion_child)
671
+ impuestos.add_child(retenciones)
672
+ comprobante['Total'] = (suma_total - suma_ret).round(2).to_s
673
+ else
674
+ comprobante['Total'] = suma_total.round(2).to_s
675
+ end
676
+
677
+
678
+ ## filling traslado info
679
+ traslado_child = Nokogiri::XML::Node.new "cfdi:Traslado", xml
680
+ traslado_child['Impuesto'] = '002'
681
+ traslado_child['TipoFactor'] = 'Tasa'
682
+ traslado_child['Base'] = subtotal.round(2)
683
+
684
+ if suma_iva > 0
685
+ traslado_child['Importe'] = suma_iva.round(2).to_s
686
+ traslado_child['TasaOCuota'] = '0.160000'
687
+ else
688
+ traslado_child['Importe'] = "0.00"
689
+ traslado_child['TasaOCuota'] = '0.000000'
690
+ end
691
+
692
+ traslados.add_child(traslado_child)
693
+ impuestos.add_child(traslados)
694
+
695
+
696
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
697
+ id = SecureRandom.hex
698
+
699
+ FileUtils.mkdir_p(path) unless File.exist?(path)
700
+ File.write("#{path}/tmp_#{id}.xml", xml.to_xml)
701
+ xml_path = "#{path}/tmp_#{id}.xml"
702
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadenaoriginal_4_0.xslt])
703
+
704
+ # puts File.read(cadena_path)
705
+ File.write("#{path}/pem_#{id}.pem", @pem)
706
+ key_pem_url = "#{path}/pem_#{id}.pem"
707
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
708
+ comprobante['Sello'] = sello
709
+
710
+ File.delete("#{xml_path}")
711
+ File.delete("#{key_pem_url}")
712
+
713
+ puts '---- Fdis: comprobante sin timbrar ------'
714
+ puts xml.to_xml
715
+ puts '-------------------------'
716
+
717
+ base64_xml = Base64.encode64(xml.to_xml)
718
+
719
+ # haciendo llamada a API
720
+ uri = URI("#{Fdis2::UrlPro}/Timbrar40")
721
+ request = Net::HTTP::Post.new(uri)
722
+ request.content_type = "application/json"
723
+ request.body = JSON.dump({
724
+ "testMode": !@production,
725
+ "idServicio": @id_servicio,
726
+ "base64XmlFile": base64_xml,
727
+ })
728
+
729
+ req_options = {
730
+ use_ssl: uri.scheme == "https",
731
+ }
732
+
733
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
734
+ http.request(request)
735
+ end
736
+
737
+ puts "---- Fdis: request"
738
+ puts "-- body: #{request.body} --"
739
+
740
+ puts "---- Fdis: Respuesta"
741
+ puts "-- Codigo: #{json_response.code} --"
742
+ puts "-- Mensaje: #{json_response.message} --"
743
+ puts "-- Body: "
744
+ p json_response.body
745
+
746
+ response = JSON.parse(json_response.body)
747
+
748
+ case json_response
749
+ when Net::HTTPSuccess, Net::HTTPRedirection
750
+ if response['success'] == true
751
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['base64XmlFile']))
752
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children[0]
753
+ response = {
754
+ status: 200,
755
+ message_error: '',
756
+ xml: decoded_xml.to_xml,
757
+ uuid: timbre['UUID'],
758
+ fecha_timbrado: timbre['FechaTimbrado'],
759
+ sello_cfd: timbre['SelloCFD'],
760
+ sello_sat: timbre['SelloSAT'],
761
+ no_certificado_sat: timbre['NoCertificadoSAT'],
762
+ }
763
+ return response
764
+ else
765
+ response = {
766
+ status: 400,
767
+ message_error: "Error: #{response['errorMessages']}",
768
+ xml: '',
769
+ uuid: '',
770
+ fecha_timbrado: '',
771
+ sello_cfd: '',
772
+ sello_sat: '',
773
+ no_certificado_sat: '',
774
+ }
775
+ return response
776
+
777
+ end
778
+ else
779
+ response = {
780
+ status: json_response.code,
781
+ message_error: "Error: #{response['errorMessages']}",
782
+ xml: '',
783
+ uuid: '',
784
+ fecha_timbrado: '',
785
+ sello_cfd: '',
786
+ sello_sat: '',
787
+ no_certificado_sat: '',
788
+ }
789
+ return response
790
+
791
+ end
792
+
793
+
794
+ end
795
+
796
+
797
+
798
+ end
799
+
800
+ end