fdis 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (41) hide show
  1. checksums.yaml +7 -0
  2. data/README.md +51 -0
  3. data/lib/cadena/ComercioExterior11.xslt +181 -0
  4. data/lib/cadena/Pagos10.xslt +165 -0
  5. data/lib/cadena/Pagos20.xsd +532 -0
  6. data/lib/cadena/Pagos20.xslt +233 -0
  7. data/lib/cadena/TuristaPasajeroExtranjero.xslt +40 -0
  8. data/lib/cadena/aerolineas.xslt +50 -0
  9. data/lib/cadena/cadena33.xslt +349 -0
  10. data/lib/cadena/cadenaoriginal_4_0.xslt +405 -0
  11. data/lib/cadena/catCFDI.xsd +162331 -0
  12. data/lib/cadena/certificadodedestruccion.xslt +60 -0
  13. data/lib/cadena/cfdiregistrofiscal.xslt +19 -0
  14. data/lib/cadena/cfdv33.xsd +737 -0
  15. data/lib/cadena/cfdv40.xsd +850 -0
  16. data/lib/cadena/consumodecombustibles.xslt +108 -0
  17. data/lib/cadena/detallista.xslt +42 -0
  18. data/lib/cadena/divisas.xslt +13 -0
  19. data/lib/cadena/donat11.xslt +13 -0
  20. data/lib/cadena/ecc11.xslt +102 -0
  21. data/lib/cadena/iedu.xslt +26 -0
  22. data/lib/cadena/implocal.xslt +39 -0
  23. data/lib/cadena/ine11.xslt +51 -0
  24. data/lib/cadena/leyendasFisc.xslt +28 -0
  25. data/lib/cadena/nomina12.xslt +412 -0
  26. data/lib/cadena/notariospublicos.xslt +301 -0
  27. data/lib/cadena/obrasarteantiguedades.xslt +33 -0
  28. data/lib/cadena/pagoenespecie.xslt +39 -0
  29. data/lib/cadena/pfic.xslt +13 -0
  30. data/lib/cadena/renovacionysustitucionvehiculos.xslt +152 -0
  31. data/lib/cadena/servicioparcialconstruccion.xslt +44 -0
  32. data/lib/cadena/terceros11.xslt +108 -0
  33. data/lib/cadena/utilerias.xslt +22 -0
  34. data/lib/cadena/valesdedespensa.xslt +70 -0
  35. data/lib/cadena/vehiculousado.xslt +63 -0
  36. data/lib/cadena/ventavehiculos11.xslt +53 -0
  37. data/lib/fdis/config.rb +94 -0
  38. data/lib/fdis/facturacion.rb +1028 -0
  39. data/lib/fdis/version.rb +3 -0
  40. data/lib/fdis.rb +17 -0
  41. metadata +139 -0
@@ -0,0 +1,1028 @@
1
+
2
+ module Fdis
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
+ # iva_id: 16,8,0,
16
+ # forma_pago: '01',
17
+ # total: 100.00,
18
+ # time_pago: '',
19
+ # time_now: '',
20
+ # modena: '',
21
+ # line_items: [
22
+ # {
23
+ # monto: 60.00,
24
+ # moneda: '',
25
+ # id: ,
26
+ # },
27
+ # ]
28
+ # }
29
+
30
+ puts " Datos --------"
31
+ puts "-- Total params: #{params[:total]}"
32
+ puts "--- Line items: "
33
+ params[:line_items].each do |line|
34
+ puts "--- #{line[:monto]}"
35
+ end
36
+ lines_total = params[:line_items].inject(0) {|sum, x| sum + x[:monto].to_f}
37
+
38
+ puts "-- Suma de line_items: #{lines_total.round(2)}"
39
+
40
+ if (lines_total.round(2) > params[:total].to_f)
41
+ raise 'Error Fdis - la suma de los complementos de pago es mayor al total reportado'
42
+ end
43
+
44
+ unless params[:time_pago] and params[:time_pago].size > 0
45
+ raise "Error Fdis - la fecha de timbrado debe de estar presente"
46
+ end
47
+
48
+
49
+
50
+
51
+ time_now = params.fetch(:time_now, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
52
+ time_pago = params[:time_pago]
53
+
54
+
55
+ base_doc = %(<?xml version="1.0" encoding="UTF-8"?>
56
+ <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">
57
+
58
+ <cfdi:Emisor Rfc="" Nombre="" RegimenFiscal="" />
59
+ <cfdi:Receptor Rfc="" Nombre="" UsoCFDI="CP01" DomicilioFiscalReceptor="" RegimenFiscalReceptor="616" />
60
+
61
+ <cfdi:Conceptos>
62
+ <cfdi:Concepto ClaveProdServ="84111506" Cantidad="1" ClaveUnidad="ACT" Descripcion="Pago" ValorUnitario="0" Importe="0" ObjetoImp="01" />
63
+ </cfdi:Conceptos>
64
+
65
+ <cfdi:Complemento>
66
+
67
+ <pago20:Pagos Version="2.0">
68
+ <pago20:Totales MontoTotalPagos="" />
69
+
70
+ <pago20:Pago FechaPago="" FormaDePagoP="01" MonedaP="MXN" Monto="">
71
+ </pago20:Pago>
72
+
73
+ </pago10:Pagos>
74
+
75
+ </cfdi:Complemento>
76
+
77
+ </cfdi:Comprobante>)
78
+
79
+ base_doc.delete!("\n")
80
+ base_doc.delete!("\t")
81
+
82
+ xml = Nokogiri::XML(base_doc)
83
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
84
+ comprobante['Serie'] = 'P'
85
+ comprobante['Folio'] = params[:folio].to_s
86
+ comprobante['Fecha'] = time_now
87
+ comprobante['LugarExpedicion'] = params[:cp].to_s
88
+ comprobante['NoCertificado'] = @serial
89
+ comprobante['Certificado'] = @cadena
90
+
91
+ # Emisor datos
92
+ emisor = xml.at_xpath("//cfdi:Emisor")
93
+ emisor['Rfc'] = @rfc
94
+ emisor['Nombre'] = @razon
95
+ emisor['RegimenFiscal'] = @regimen_fiscal
96
+
97
+ # Receptor datos
98
+ receptor = xml.at_xpath("//cfdi:Receptor")
99
+ receptor['Nombre'] = params[:receptor_razon].to_s
100
+ receptor['Rfc'] = params[:receptor_rfc].to_s
101
+ receptor['DomicilioFiscalReceptor'] = params[:receptor_cp].to_s
102
+ receptor['RegimenFiscalReceptor'] = params[:receptor_regimen].to_s
103
+
104
+ # totales
105
+ total = params[:total].to_f
106
+ iva_id = params.fetch(:iva_id, 16)
107
+
108
+ pago_totales = xml.at_xpath("//pago20:Totales")
109
+ pago_totales['MontoTotalPagos'] = total.round(2).to_s # total
110
+
111
+
112
+ if iva_id == 0
113
+ subtotal = total
114
+ iva = 0.00
115
+
116
+ pago_totales['TotalTrasladosBaseIVA0'] = subtotal.round(2).to_s # subtotal
117
+ pago_totales['TotalTrasladosImpuestoIVA0'] = iva.round(2).to_s # iva
118
+
119
+ elsif iva_id == 8
120
+ subtotal = total / 1.08
121
+ iva = total - subtotal
122
+
123
+ pago_totales['TotalTrasladosBaseIVA8'] = subtotal.round(2).to_s # subtotal
124
+ pago_totales['TotalTrasladosImpuestoIVA8'] = iva.round(2).to_s # iva
125
+ else
126
+ subtotal = total / 1.16
127
+ iva = total - subtotal
128
+
129
+ pago_totales['TotalTrasladosBaseIVA16'] = subtotal.round(2).to_s # subtotal
130
+ pago_totales['TotalTrasladosImpuestoIVA16'] = iva.round(2).to_s # iva
131
+ end
132
+
133
+ # pago
134
+ child_pago = xml.at_xpath("//pago20:Pago")
135
+ child_pago['FechaPago'] = time_pago
136
+ child_pago['FormaDePagoP'] = params[:forma_pago].to_s
137
+ child_pago['MonedaP'] = params.fetch(:moneda, 'MXN')
138
+ child_pago['Monto'] = total.round(2).to_s
139
+
140
+ saldo_anterior = total
141
+
142
+ params[:line_items].each_with_index do |line, index|
143
+ monto = line[:monto].to_f
144
+ child_pago_relacionado = Nokogiri::XML::Node.new "pago20:DoctoRelacionado", xml
145
+ child_pago_relacionado['IdDocumento'] = params[:uuid]
146
+ child_pago_relacionado['Serie'] = "Depo"
147
+ child_pago_relacionado['Folio'] = line[:id].to_s
148
+
149
+ child_pago_relacionado['MonedaDR'] = line.fetch(:moneda, 'MXN')
150
+ child_pago_relacionado['MetodoDePagoDR'] = 'PPD'
151
+ child_pago_relacionado['NumParcialidad'] = (index + 1).to_s
152
+
153
+ child_pago_relacionado['ImpSaldoAnt'] = (saldo_anterior).round(2).to_s
154
+ child_pago_relacionado['ImpPagado'] = monto.round(2).to_s
155
+ child_pago_relacionado['ImpSaldoInsoluto'] = (saldo_anterior - monto).round(2).to_s
156
+ saldo_anterior -= monto
157
+
158
+ child_pago.add_child(child_pago_relacionado)
159
+ end
160
+
161
+ # puts '---------------- Xml resultante comprobante de pago -----------------------'
162
+ # puts xml.to_xml
163
+ # puts '--------------------------------------------------------'
164
+
165
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
166
+ id = SecureRandom.hex
167
+
168
+ FileUtils.mkdir_p(path) unless File.exist?(path)
169
+ File.write("#{path}/tmp_c_#{id}.xml", xml.to_xml)
170
+ xml_path = "#{path}/tmp_c_#{id}.xml"
171
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadenaoriginal_4_0.xslt])
172
+
173
+ File.write("#{path}/pem_#{id}.pem", @pem)
174
+ key_pem_url = "#{path}/pem_#{id}.pem"
175
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
176
+ comprobante['Sello'] = sello
177
+
178
+ File.delete("#{xml_path}")
179
+ File.delete("#{key_pem_url}")
180
+
181
+ puts '------ Fdis: comprobante de pago antes de timbre -------'
182
+ puts xml.to_xml
183
+ base64_xml = Base64.encode64(xml.to_xml)
184
+
185
+ # haciendo llamada a API
186
+ uri = URI("#{Fdis::UrlPro}/Timbrar40")
187
+ request = Net::HTTP::Post.new(uri)
188
+ # request.basic_auth(token, "")
189
+ request.content_type = "application/json"
190
+ # request["cache-control"] = 'no-cache'
191
+ request.body = JSON.dump({
192
+ "testMode": !@production,
193
+ "idServicio": @id_servicio,
194
+ "base64XmlFile": base64_xml,
195
+ })
196
+
197
+ req_options = {
198
+ use_ssl: uri.scheme == "https",
199
+ }
200
+
201
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
202
+ http.request(request)
203
+ end
204
+ puts "-- #{json_response.code} --"
205
+ puts "-- #{json_response.message} --"
206
+ # puts "-- Body --"
207
+ # puts json_response.body
208
+ # puts '---'
209
+ response = JSON.parse(json_response.body)
210
+
211
+ case json_response
212
+ when Net::HTTPSuccess, Net::HTTPRedirection
213
+ if response['success'] == true
214
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['base64XmlFile']))
215
+ # timbre = decoded_xml.at_xpath("//cfdi:Complemento").children[1]
216
+ timbre = decoded_xml.at_xpath("//tfd:TimbreFiscalDigital")
217
+ response = {
218
+ status: 200,
219
+ message_error: '',
220
+ xml: decoded_xml.to_xml,
221
+ uuid: timbre['UUID'],
222
+ fecha_timbrado: timbre['FechaTimbrado'],
223
+ sello_cfd: timbre['SelloCFD'],
224
+ sello_sat: timbre['SelloSAT'],
225
+ no_certificado_sat: timbre['NoCertificadoSAT'],
226
+ }
227
+ return response
228
+ else
229
+ response = {
230
+ status: 400,
231
+ message_error: "Error: #{response['errorMessages']}",
232
+ xml: '',
233
+ uuid: '',
234
+ fecha_timbrado: '',
235
+ sello_cfd: '',
236
+ sello_sat: '',
237
+ no_certificado_sat: '',
238
+ }
239
+ return response
240
+
241
+ end
242
+ else
243
+ response = {
244
+ status: json_response.code,
245
+ message_error: "Error: #{json_response.message}",
246
+ xml: '',
247
+ uuid: '',
248
+ fecha_timbrado: '',
249
+ sello_cfd: '',
250
+ sello_sat: '',
251
+ no_certificado_sat: '',
252
+ }
253
+ return response
254
+
255
+ end
256
+
257
+
258
+ end
259
+
260
+ # def nota_credito(params={})
261
+ # # Sample params
262
+ # # params = {
263
+ # # uuid_relacionado: '',
264
+ # # desc: '',
265
+ # # motivo: 'dev, mod',
266
+ # # series: '',
267
+ # # folio: '',
268
+ # # cp: '',
269
+ # # time: '',
270
+ # # receptor_razon: '',
271
+ # # receptor_rfc: '',
272
+ # # uso_cfdi: '',
273
+ # # }
274
+
275
+ # total = (params[:monto]).to_f
276
+ # subtotal = total / 1.16
277
+ # tax = total - subtotal
278
+
279
+ # uri = @production ? URI("#{Fdis::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{Fdis::UrlDev}cfdi33/stamp/customv1/b64")
280
+ # token = @production ? @production_token : @dev_token
281
+ # time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
282
+
283
+
284
+ # base_doc = %(<?xml version="1.0" encoding="utf-8"?>
285
+ # <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}" 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">
286
+ # <cfdi:CfdiRelacionados TipoRelacion="01">
287
+ # <cfdi:CfdiRelacionado UUID="#{params[:uuid_relacionado]}" />
288
+ # </cfdi:CfdiRelacionados>
289
+ # <cfdi:Emisor Rfc="#{@rfc}" Nombre="#{@razon}" RegimenFiscal="#{@regimen_fiscal}" />
290
+ # <cfdi:Receptor Rfc="#{params[:receptor_rfc]}" Nombre="#{params[:receptor_razon]}" UsoCFDI="#{params.fetch(:uso_cfdi, 'G03')}" />
291
+ # <cfdi:Conceptos>
292
+ # <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)}">
293
+ # <cfdi:Impuestos>
294
+ # <cfdi:Traslados>
295
+ # <cfdi:Traslado Base="#{subtotal.round(2)}" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="#{tax.round(2)}" />
296
+ # </cfdi:Traslados>
297
+ # </cfdi:Impuestos>
298
+ # </cfdi:Concepto>
299
+ # </cfdi:Conceptos>
300
+ # <cfdi:Impuestos TotalImpuestosTrasladados="#{tax.round(2)}">
301
+ # <cfdi:Traslados>
302
+ # <cfdi:Traslado Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="#{tax.round(2)}" />
303
+ # </cfdi:Traslados>
304
+ # </cfdi:Impuestos>
305
+ # </cfdi:Comprobante>
306
+ # )
307
+
308
+ # base_doc.delete!("\n")
309
+ # base_doc.delete!("\t")
310
+
311
+ # xml = Nokogiri::XML(base_doc)
312
+ # comprobante = xml.at_xpath("//cfdi:Comprobante")
313
+
314
+ # path = File.join(File.dirname(__FILE__), *%w[.. tmp])
315
+ # id = SecureRandom.hex
316
+
317
+ # FileUtils.mkdir_p(path) unless File.exist?(path)
318
+ # File.write("#{path}/tmp_n_#{id}.xml", xml.to_xml)
319
+ # xml_path = "#{path}/tmp_n_#{id}.xml"
320
+ # cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
321
+
322
+ # File.write("#{path}/pem_#{id}.pem", @pem)
323
+ # key_pem_url = "#{path}/pem_#{id}.pem"
324
+ # sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
325
+ # comprobante['Sello'] = sello
326
+
327
+ # File.delete("#{xml_path}")
328
+ # File.delete("#{key_pem_url}")
329
+
330
+ # puts '------ nota antes de timbre -------'
331
+ # puts xml.to_xml
332
+
333
+ # base64_xml = Base64.encode64(xml.to_xml)
334
+
335
+ # request = Net::HTTP::Post.new(uri)
336
+ # request.basic_auth(token, "")
337
+ # request.content_type = "application/json"
338
+ # request["cache-control"] = 'no-cache'
339
+ # request.body = JSON.dump({
340
+ # "credentials" => {
341
+ # "id" => "#{params[:folio]}",
342
+ # "token" => token
343
+ # },
344
+ # "issuer" => {
345
+ # "rfc" => @rfc
346
+ # },
347
+ # "document" => {
348
+ # "ref-id": "#{params[:folio]}",
349
+ # "certificate-number": @serial,
350
+ # "section": "all",
351
+ # "format": "xml",
352
+ # "template": "letter",
353
+ # "type": "application/xml",
354
+ # "content": base64_xml
355
+ # }
356
+ # })
357
+
358
+ # req_options = {
359
+ # use_ssl: false,
360
+ # }
361
+
362
+ # json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
363
+ # http.request(request)
364
+ # end
365
+
366
+
367
+ # puts "-- #{json_response.code} --"
368
+ # puts "-- #{json_response.message} --"
369
+ # # puts "-- Body --"
370
+ # # puts json_response.body
371
+ # # puts '---'
372
+ # response = JSON.parse(json_response.body)
373
+
374
+ # if json_response.code == '200'
375
+ # decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
376
+ # timbre = decoded_xml.at_xpath("//cfdi:Complemento").children.first
377
+
378
+ # response = {
379
+ # status: 200,
380
+ # message_error: '',
381
+ # xml: decoded_xml.to_xml,
382
+ # uuid: response['uuid'],
383
+ # fecha_timbrado: timbre['FechaTimbrado'],
384
+ # sello_cfd: timbre['SelloCFD'],
385
+ # sello_sat: timbre['SelloSAT'],
386
+ # no_certificado_sat: timbre['NoCertificadoSAT'],
387
+ # }
388
+ # return response
389
+ # else
390
+ # response = {
391
+ # status: json_response.code,
392
+ # message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
393
+ # xml: '',
394
+ # uuid: '',
395
+ # fecha_timbrado: '',
396
+ # sello_cfd: '',
397
+ # sello_sat: '',
398
+ # no_certificado_sat: '',
399
+ # }
400
+ # return response
401
+ # end
402
+
403
+
404
+ # end
405
+
406
+ def cancela_doc(params={})
407
+ # Sample params
408
+ # params = {
409
+ # uuid: '',
410
+ # rfc_emisor: '',
411
+ # motivo: '02',
412
+ # key_password: '', # optional
413
+ # cer_cadena: '', # optional
414
+ # key_pem: '' # optional
415
+ # }
416
+
417
+ uri = @production ? URI("#{Fdis::UrlProduction}cfdi33/cancel/csd") : URI("#{Fdis::UrlDev}cfdi33/cancel/csd")
418
+ token = @production ? @production_token : @dev_token
419
+ # time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
420
+
421
+
422
+ request = Net::HTTP::Post.new(uri)
423
+ request["Authorization"] = "bearer #{token}"
424
+ request.content_type = "application/json"
425
+ request["Cache-Control"] = 'no-cache'
426
+ request["Postman-Token"] = '30b35bb8-534d-51c7-6a5c-e2c98a0c9395'
427
+ request.body = JSON.dump({
428
+ 'uuid': params[:uuid],
429
+ "password": params.fetch(:key_password, @key_pass),
430
+ "rfc": params.fetch(:rfc_emisor, @rfc),
431
+ "motivo": '02',
432
+ "b64Cer": params.fetch(:cer_cadena, @cadena),
433
+ "b64Key": params.fetch(:key_pem, @pem_cadena)
434
+ })
435
+
436
+ req_options = {
437
+ use_ssl: uri.scheme == "https",
438
+ }
439
+
440
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
441
+ http.request(request)
442
+ end
443
+
444
+ puts "-- #{json_response.code} --"
445
+ puts "-- #{json_response.message} --"
446
+ # puts "-- Body --"
447
+ # puts json_response.body
448
+ # puts '---'
449
+ response = JSON.parse(json_response.body)
450
+
451
+ if json_response.code == '200'
452
+ decoded_xml = response['data']['acuse']
453
+
454
+ response = {
455
+ status: 200,
456
+ message_error: '',
457
+ xml: decoded_xml,
458
+ }
459
+
460
+ return response
461
+ else
462
+
463
+ response ={
464
+ status: json_response.code,
465
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
466
+ xml: '',
467
+ }
468
+
469
+ return response
470
+ end
471
+
472
+ end
473
+
474
+
475
+ def timbra_doc(params={})
476
+ ### sample params
477
+ #
478
+ # params = {
479
+ # moneda: 'MXN',
480
+ # series: 'FA',
481
+ # folio: '003',
482
+ # forma_pago: '',
483
+ # metodo_pago: 'PUE',
484
+ # cp: '47180',
485
+ # receptor_cp: '47180',
486
+ # receptor_razon: 'Car zone',
487
+ # receptor_rfc: '',
488
+ # uso_cfdi: 'G03',
489
+ # time: "%Y-%m-%dT%H:%M:%S",
490
+ # line_items: [
491
+ # {
492
+ # clave_prod_serv: '78181500',
493
+ # clave_unidad: 'E48',
494
+ # unidad: 'Servicio',
495
+ # sku: 'serv001',
496
+ # cantidad: 1,
497
+ # descripcion: 'Servicio mano de obra',
498
+ # valor_unitario: 100.00,
499
+ # descuento: 0.00,
500
+ # tax_included: true,
501
+ # retencion_iva: 0, 6, 16
502
+ # # Optional parameters
503
+ # },
504
+ # ]
505
+
506
+ # }
507
+
508
+ puts "---- Fdis:facturacion:timbra_doc"
509
+
510
+
511
+ time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
512
+
513
+
514
+ base_doc = %(<?xml version="1.0" encoding="utf-8"?>
515
+ <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="" >
516
+
517
+ <cfdi:Emisor Rfc="" Nombre="" RegimenFiscal="" />
518
+ <cfdi:Receptor Rfc="" Nombre="" DomicilioFiscalReceptor="" RegimenFiscalReceptor="" UsoCFDI="" />
519
+
520
+ <cfdi:Conceptos></cfdi:Conceptos>
521
+ <cfdi:Impuestos></cfdi:Impuestos>
522
+
523
+ </cfdi:Comprobante>)
524
+
525
+ base_doc.delete!("\n")
526
+ base_doc.delete!("\t")
527
+
528
+ xml = Nokogiri::XML(base_doc)
529
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
530
+ comprobante['TipoCambio'] = '1'
531
+ comprobante['TipoDeComprobante'] = 'I'
532
+ comprobante['Serie'] = params.fetch(:series, 'FA').to_s
533
+ comprobante['Folio'] = params.fetch(:folio).to_s
534
+ comprobante['Fecha'] = time.to_s
535
+ comprobante['FormaPago'] = params.fetch(:forma_pago, '01')
536
+ comprobante['MetodoPago'] = params.fetch(:metodo_pago, 'PUE')
537
+ comprobante['LugarExpedicion'] = params.fetch(:cp, '47180')
538
+ comprobante['NoCertificado'] = @serial
539
+ comprobante['Certificado'] = @cadena
540
+
541
+ # emisor
542
+ emisor = xml.at_xpath("//cfdi:Emisor")
543
+ emisor['Nombre'] = @razon
544
+ emisor['RegimenFiscal'] = @regimen_fiscal
545
+ emisor['Rfc'] = @rfc
546
+
547
+ # receptor
548
+ receptor = xml.at_xpath("//cfdi:Receptor")
549
+ receptor['Rfc'] = params.fetch(:receptor_rfc, '')
550
+ receptor['Nombre'] = params.fetch(:receptor_razon, '')
551
+ receptor['DomicilioFiscalReceptor'] = params.fetch(:receptor_cp, '47180')
552
+ receptor['UsoCFDI'] = params.fetch(:uso_cfdi, 'G03')
553
+
554
+ # retencion_iva = params.fetch(:retencion_iva, 0)
555
+
556
+ impuestos = xml.at_xpath("//cfdi:Impuestos")
557
+ traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
558
+
559
+
560
+ puts '--- Fdis time -----'
561
+ puts time
562
+ puts '--------'
563
+
564
+ conceptos = xml.at_xpath("//cfdi:Conceptos")
565
+
566
+ line_items = params[:line_items]
567
+
568
+ suma_total = 0.00
569
+ subtotal = 0.00
570
+ suma_iva = 0.00
571
+ suma_ret = 0.00
572
+
573
+ line_items.each do |line|
574
+ ret_iva = line.fetch(:retencion_iva, 0)
575
+ puts ret_iva
576
+
577
+
578
+ ## revisando si la linea tiene iva 0
579
+ if line[:tax_included] == true
580
+ # if line[:tipo_impuesto] == '004'
581
+ # valor_unitario = (line[:valor_unitario].to_f)
582
+ # else
583
+ # end
584
+ valor_unitario = ((line[:valor_unitario]).to_f) / 1.16
585
+ else
586
+ valor_unitario = (line[:valor_unitario].to_f)
587
+ end
588
+
589
+ cantidad = line[:cantidad].to_f
590
+ total_line = cantidad * valor_unitario
591
+
592
+ # if line[:tipo_impuesto] == '004'
593
+ # total_acumulator = cantidad * valor_unitario
594
+ # else
595
+ # total_acumulator = cantidad * valor_unitario * 1.16
596
+ # end
597
+
598
+ total_acumulator = cantidad * valor_unitario * 1.16
599
+
600
+ importe_iva = total_acumulator - total_line
601
+ subtotal += total_line
602
+ suma_iva += importe_iva
603
+ suma_total += total_acumulator
604
+
605
+ puts "--- 01"
606
+ ## calculando retencion de IVA en caso de tener
607
+ if ret_iva > 0
608
+ if ret_iva == 6
609
+ importe_ret_linea = (total_line * 1.06) - total_line
610
+ elsif ret_iva == 16
611
+ importe_ret_linea = importe_iva
612
+ end
613
+ else
614
+ importe_ret_linea = 0
615
+ end
616
+ puts "--- 02"
617
+ suma_ret += importe_ret_linea
618
+
619
+
620
+ ## Creando y poblando CFDI:CONCEPTO
621
+ child_concepto = Nokogiri::XML::Node.new "cfdi:Concepto", xml
622
+ child_concepto['ClaveProdServ'] = line[:clave_prod_serv].to_s
623
+ child_concepto['NoIdentificacion'] = line[:sku].to_s
624
+ child_concepto['ClaveUnidad'] = line[:clave_unidad].to_s
625
+ child_concepto['Unidad'] = line[:unidad].to_s
626
+ child_concepto['Descripcion'] = line[:descripcion].to_s
627
+ child_concepto['Cantidad'] = cantidad.to_s
628
+ child_concepto['ValorUnitario'] = valor_unitario.round(4).to_s
629
+ child_concepto['Importe'] = total_line.round(4).to_s
630
+ child_concepto['ObjetoImp'] = '02'
631
+
632
+
633
+ ## Creando cdfi:Impuestos para cada linea
634
+ child_impuestos = Nokogiri::XML::Node.new "cfdi:Impuestos", xml
635
+
636
+ ## Creando cfdi:Traslados para cada linea
637
+ child_traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
638
+ child_traslado = Nokogiri::XML::Node.new "cfdi:Traslado", xml
639
+ child_traslado['Base'] = total_line.round(4).to_s
640
+ child_traslado['Importe'] = importe_iva.round(4).to_s
641
+ child_traslado['Impuesto'] = '002'
642
+ child_traslado['TasaOCuota'] = '0.160000'
643
+ child_traslado['TipoFactor'] = "Tasa"
644
+
645
+ # if line[:tipo_impuesto] == '004'
646
+ # child_traslado['TasaOCuota'] = '0.000000'
647
+ # else
648
+ # end
649
+
650
+
651
+ # Joining all up
652
+ child_traslados.add_child(child_traslado)
653
+ child_impuestos.add_child(child_traslados)
654
+ child_concepto.add_child(child_impuestos)
655
+ conceptos.add_child(child_concepto)
656
+
657
+ ## Creando cfdi:Retenciones para cada linea en caso de tener
658
+ if ret_iva > 0
659
+ child_retenciones = Nokogiri::XML::Node.new "cfdi:Retenciones", xml
660
+ child_retencion = Nokogiri::XML::Node.new "cfdi:Retencion", xml
661
+ child_retencion['Base'] = total_line.round(4).to_s
662
+ child_retencion['Impuesto'] = '002'
663
+ child_retencion['TipoFactor'] = "Tasa"
664
+
665
+ if ret_iva == 6
666
+ child_retencion['TasaOCuota'] = "0.060000"
667
+ elsif ret_iva == 16
668
+ child_retencion['TasaOCuota'] = "0.160000"
669
+ end
670
+
671
+ child_retencion['Importe'] = importe_ret_linea.round(4).to_s
672
+
673
+ child_retenciones.add_child(child_retencion)
674
+ child_impuestos.add_child(child_retenciones)
675
+ end
676
+
677
+
678
+ end
679
+
680
+ puts '------ Totales -----'
681
+ puts "Total suma = #{suma_total.round(2)}"
682
+ puts "SubTotal suma = #{subtotal.round(2)}"
683
+ puts "Suma iva = #{suma_iva.round(2)}"
684
+ puts "Suma restenciones = #{suma_ret.round(2)}"
685
+
686
+ comprobante['Moneda'] = params.fetch(:moneda, 'MXN')
687
+ comprobante['SubTotal'] = subtotal.round(2).to_s
688
+
689
+
690
+ ## Poblanco cfdi:Impuestos
691
+ impuestos['TotalImpuestosRetenidos'] = suma_ret.round(2).to_s if suma_ret > 0
692
+ impuestos['TotalImpuestosTrasladados'] = suma_iva.round(2).to_s
693
+
694
+ ## filling default retencion info
695
+ if suma_ret > 0
696
+ retenciones = Nokogiri::XML::Node.new "cfdi:Retenciones", xml
697
+ retencion_child = Nokogiri::XML::Node.new "cfdi:Retencion", xml
698
+ retencion_child['Impuesto'] = "002"
699
+ retencion_child['Importe'] = suma_ret.round(2).to_s
700
+ # retencion_child['TipoFactor'] = "Tasa"
701
+
702
+ retenciones.add_child(retencion_child)
703
+ impuestos.add_child(retenciones)
704
+ comprobante['Total'] = (suma_total - suma_ret).round(2).to_s
705
+ else
706
+ comprobante['Total'] = suma_total.round(2).to_s
707
+ end
708
+
709
+
710
+ ## filling traslado info
711
+ traslado_child = Nokogiri::XML::Node.new "cfdi:Traslado", xml
712
+ traslado_child['Base'] = subtotal.round(2)
713
+ traslado_child['Impuesto'] = '002'
714
+ traslado_child['Importe'] = suma_iva.round(2).to_s
715
+ traslado_child['TipoFactor'] = 'Tasa'
716
+ traslado_child['TasaOCuota'] = '0.160000'
717
+ traslados.add_child(traslado_child)
718
+ impuestos.add_child(traslados)
719
+
720
+
721
+
722
+ # puts '------ Totales -----'
723
+ # puts "Total suma = #{comprobante['Total']}"
724
+ # puts "SubTotal suma = #{subtotal}"
725
+ # puts "Suma iva = #{suma_iva}"
726
+ # puts "Suma retenciones = #{impuestos['TotalImpuestosRetenidos']}" if suma_ret > 0
727
+
728
+
729
+
730
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
731
+ id = SecureRandom.hex
732
+
733
+ FileUtils.mkdir_p(path) unless File.exist?(path)
734
+ File.write("#{path}/tmp_#{id}.xml", xml.to_xml)
735
+ xml_path = "#{path}/tmp_#{id}.xml"
736
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadenaoriginal_4_0.xslt])
737
+
738
+ # puts File.read(cadena_path)
739
+ File.write("#{path}/pem_#{id}.pem", @pem)
740
+ key_pem_url = "#{path}/pem_#{id}.pem"
741
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
742
+ comprobante['Sello'] = sello
743
+
744
+ File.delete("#{xml_path}")
745
+ File.delete("#{key_pem_url}")
746
+
747
+ puts '---- Fdis: comprobante sin timbrar ------'
748
+ puts xml.to_xml
749
+ puts '-------------------------'
750
+
751
+ base64_xml = Base64.encode64(xml.to_xml)
752
+
753
+ # haciendo llamada a API
754
+ uri = URI("#{Fdis::UrlPro}/Timbrar40")
755
+ request = Net::HTTP::Post.new(uri)
756
+ request.content_type = "application/json"
757
+ request.body = JSON.dump({
758
+ "testMode": !@production,
759
+ "idServicio": @id_servicio,
760
+ "base64XmlFile": base64_xml,
761
+ })
762
+
763
+ req_options = {
764
+ use_ssl: uri.scheme == "https",
765
+ }
766
+
767
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
768
+ http.request(request)
769
+ end
770
+ puts "-- #{json_response.code} --"
771
+ puts "-- #{json_response.message} --"
772
+
773
+ response = JSON.parse(json_response.body)
774
+
775
+ case json_response
776
+ when Net::HTTPSuccess, Net::HTTPRedirection
777
+ if response['success'] == true
778
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['base64XmlFile']))
779
+ # timbre = decoded_xml.at_xpath("//cfdi:Complemento").children[1]
780
+ timbre = decoded_xml.at_xpath("//tfd:TimbreFiscalDigital")
781
+ response = {
782
+ status: 200,
783
+ message_error: '',
784
+ xml: decoded_xml.to_xml,
785
+ uuid: timbre['UUID'],
786
+ fecha_timbrado: timbre['FechaTimbrado'],
787
+ sello_cfd: timbre['SelloCFD'],
788
+ sello_sat: timbre['SelloSAT'],
789
+ no_certificado_sat: timbre['NoCertificadoSAT'],
790
+ }
791
+ return response
792
+ else
793
+ response = {
794
+ status: 400,
795
+ message_error: "Error: #{response['errorMessages']}",
796
+ xml: '',
797
+ uuid: '',
798
+ fecha_timbrado: '',
799
+ sello_cfd: '',
800
+ sello_sat: '',
801
+ no_certificado_sat: '',
802
+ }
803
+ return response
804
+
805
+ end
806
+ else
807
+ response = {
808
+ status: json_response.code,
809
+ message_error: "Error: #{json_response.message}",
810
+ xml: '',
811
+ uuid: '',
812
+ fecha_timbrado: '',
813
+ sello_cfd: '',
814
+ sello_sat: '',
815
+ no_certificado_sat: '',
816
+ }
817
+ return response
818
+
819
+ end
820
+
821
+
822
+ end
823
+
824
+ def timbra_doc_cero(params={})
825
+ puts "---- Fdis:facturacion:timbra_doc_cero"
826
+ # params = {
827
+ # moneda: 'MXN',
828
+ # series: 'FA',
829
+ # folio: '003',
830
+ # forma_pago: '',
831
+ # metodo_pago: 'PUE',
832
+ # cp: '47180',
833
+ # receptor_razon: 'Car zone',
834
+ # receptor_rfc: '',
835
+ # uso_cfdi: 'G03',
836
+ # time: "%Y-%m-%dT%H:%M:%S",
837
+ # line_items: [
838
+ # {
839
+ # clave_prod_serv: '78181500',
840
+ # clave_unidad: 'E48',
841
+ # unidad: 'Servicio',
842
+ # sku: 'serv001',
843
+ # cantidad: 1,
844
+ # descripcion: 'Servicio mano de obra',
845
+ # valor_unitario: 100.00,
846
+ # # Optional parameters
847
+ # },
848
+ # ]
849
+
850
+ # }
851
+
852
+ uri = @production ? URI("#{Fdis::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{Fdis::UrlDev}cfdi33/stamp/customv1/b64")
853
+ token = @production ? @production_token : @dev_token
854
+ time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
855
+
856
+ xml = Nokogiri::XML(Fdis::DocBase)
857
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
858
+ comprobante['TipoCambio'] = '1'
859
+ comprobante['TipoDeComprobante'] = 'I'
860
+ comprobante['Serie'] = params.fetch(:series, 'FA').to_s
861
+ comprobante['Folio'] = params.fetch(:folio, '1').to_s
862
+ comprobante['Fecha'] = time.to_s
863
+ comprobante['FormaPago'] = params.fetch(:forma_pago, '01')
864
+ comprobante['MetodoPago'] = params.fetch(:metodo_pago, 'PUE')
865
+ comprobante['LugarExpedicion'] = params.fetch(:cp, '55555')
866
+ comprobante['NoCertificado'] = @serial
867
+ comprobante['Certificado'] = @cadena
868
+
869
+ emisor = xml.at_xpath("//cfdi:Emisor")
870
+ emisor['Nombre'] = @razon
871
+ emisor['RegimenFiscal'] = @regimen_fiscal
872
+ emisor['Rfc'] = @rfc
873
+
874
+ receptor = xml.at_xpath("//cfdi:Receptor")
875
+ receptor['Nombre'] = params.fetch(:receptor_razon, '')
876
+ receptor['Rfc'] = params.fetch(:receptor_rfc, 'XAXX010101000')
877
+ receptor['UsoCFDI'] = params.fetch(:uso_cfdi, 'G03')
878
+
879
+
880
+ # impuestos = xml.at_xpath("//cfdi:Impuestos")
881
+ # traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
882
+
883
+
884
+ puts '--- Fdis time -----'
885
+ puts time
886
+ puts '--------'
887
+
888
+ conceptos = xml.at_xpath("//cfdi:Conceptos")
889
+
890
+ line_items = params[:line_items]
891
+
892
+ suma_total = 0.00
893
+
894
+ line_items.each do |line|
895
+
896
+ valor_unitario = line[:valor_unitario].to_f
897
+ cantidad = line[:cantidad].to_f
898
+ total_line = cantidad * valor_unitario
899
+
900
+ suma_total += total_line
901
+
902
+ ## Creando y poblando CFDI:CONCEPTO
903
+ child_concepto = Nokogiri::XML::Node.new "cfdi:Concepto", xml
904
+ child_concepto['ClaveProdServ'] = line[:clave_prod_serv].to_s
905
+ child_concepto['NoIdentificacion'] = line[:sku].to_s
906
+ child_concepto['ClaveUnidad'] = line[:clave_unidad].to_s
907
+ child_concepto['Unidad'] = line[:unidad].to_s
908
+ child_concepto['Descripcion'] = line[:descripcion].to_s
909
+ child_concepto['Cantidad'] = cantidad.to_s
910
+ child_concepto['ValorUnitario'] = valor_unitario.round(4).to_s
911
+ child_concepto['Importe'] = total_line.round(4).to_s
912
+
913
+
914
+ # Joining all up
915
+ conceptos.add_child(child_concepto)
916
+
917
+
918
+ end
919
+
920
+ puts '------ Totales -----'
921
+ puts "Subtotal = #{suma_total}"
922
+ puts "Total = #{suma_total}"
923
+
924
+ comprobante['Moneda'] = params.fetch(:moneda, 'MXN')
925
+ comprobante['SubTotal'] = suma_total.round(2).to_s
926
+ comprobante['Total'] = suma_total.round(2).to_s
927
+
928
+
929
+
930
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
931
+ id = SecureRandom.hex
932
+
933
+ FileUtils.mkdir_p(path) unless File.exist?(path)
934
+ File.write("#{path}/tmp_#{id}.xml", xml.to_xml)
935
+ xml_path = "#{path}/tmp_#{id}.xml"
936
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
937
+
938
+ # puts File.read(cadena_path)
939
+ File.write("#{path}/pem_#{id}.pem", @pem)
940
+ key_pem_url = "#{path}/pem_#{id}.pem"
941
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
942
+ comprobante['Sello'] = sello
943
+
944
+ File.delete("#{xml_path}")
945
+ File.delete("#{key_pem_url}")
946
+
947
+ puts '---- Fdis GEM comprobante sin timbrar ------'
948
+ puts xml.to_xml
949
+ puts '-------------------------'
950
+
951
+ base64_xml = Base64.encode64(xml.to_xml)
952
+ request = Net::HTTP::Post.new(uri)
953
+ request.basic_auth(token, "")
954
+ request.content_type = "application/json"
955
+ request["cache-control"] = 'no-cache'
956
+ request.body = JSON.dump({
957
+ "credentials" => {
958
+ "id" => params.fetch(:folio).to_s,
959
+ "token" => token
960
+ },
961
+ "issuer" => {
962
+ "rfc" => emisor['Rfc']
963
+ },
964
+ "document" => {
965
+ "ref-id": params.fetch(:folio).to_s,
966
+ "certificate-number": comprobante['NoCertificado'],
967
+ "section": "all",
968
+ "format": "xml",
969
+ "template": "letter",
970
+ "type": "application/xml",
971
+ "content": base64_xml
972
+ }
973
+ })
974
+
975
+ req_options = {
976
+ use_ssl: false,
977
+ }
978
+
979
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
980
+ http.request(request)
981
+ end
982
+
983
+ puts "-- Fdis API reponse..."
984
+ puts "-- Response code: #{json_response.code} --"
985
+ puts "-- Response body: #{json_response.body} --"
986
+ puts "-- Response message: #{json_response.message} --"
987
+
988
+ response = JSON.parse(json_response.body)
989
+
990
+ if json_response.code == '200'
991
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
992
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children.first
993
+
994
+ response = {
995
+ status: 200,
996
+ message_error: '',
997
+ xml: decoded_xml.to_xml,
998
+ uuid: response['uuid'],
999
+ fecha_timbrado: timbre['FechaTimbrado'],
1000
+ sello_cfd: timbre['SelloCFD'],
1001
+ sello_sat: timbre['SelloSAT'],
1002
+ no_certificado_sat: timbre['NoCertificadoSAT'],
1003
+ }
1004
+
1005
+ return response
1006
+ else
1007
+
1008
+ response ={
1009
+ status: json_response.code,
1010
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
1011
+ xml: '',
1012
+ uuid: '',
1013
+ fecha_timbrado: '',
1014
+ sello_cfd: '',
1015
+ sello_sat: '',
1016
+ no_certificado_sat: '',
1017
+ }
1018
+
1019
+ return response
1020
+ end
1021
+
1022
+
1023
+ end
1024
+
1025
+
1026
+ end
1027
+
1028
+ end