fdis 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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