sw_fac 0.1.0

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