fdis2 0.1.5

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