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