sw_fac 0.3.55 → 0.3.58.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,736 +1,742 @@
1
1
 
2
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',
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
12
  # receptor_rfc: 'XAXX010101000',
13
13
  # forma_pago: '01',
14
14
  # total: 100.00,
15
- # time: '',
16
- # modena: '',
17
- # line_items: [
18
- # {
19
- # monto: 60.00,
20
- # moneda: '',
21
- # },
22
- # ]
23
- # }
24
-
25
- puts " Datos --------"
26
- puts "-- Total: #{params[:total]}"
27
- puts "--- Line items: "
28
- params[:line_items].each do |line|
29
- puts "--- #{line[:monto]}"
30
- end
31
- puts "-- Suma de line_items: #{params[:line_items].inject(0) {|sum, x| sum + x[:monto].to_f.round(2) }}"
32
-
33
- if (params[:line_items].inject(0) {|sum, x| sum + x[:monto].to_f }) > params[:total].to_f.round(2)
34
- raise 'Error - la suma de los complementos de pago es mayor al total reportado'
35
- end
36
-
37
- uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
38
- token = @production ? @production_token : @dev_token
39
- time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
40
-
41
-
42
- base_doc = %(<?xml version="1.0" encoding="UTF-8"?>
43
- <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" >
44
- <cfdi:Emisor />
45
- <cfdi:Receptor UsoCFDI="P01"/>
46
- <cfdi:Conceptos>
47
- <cfdi:Concepto ClaveProdServ="84111506" Cantidad="1" ClaveUnidad="ACT" Descripcion="Pago" ValorUnitario="0" Importe="0" />
48
- </cfdi:Conceptos>
49
- <cfdi:Complemento>
50
- <pago10:Pagos Version="1.0">
51
- <pago10:Pago>
52
- </pago10:Pago>
53
- </pago10:Pagos>
54
- </cfdi:Complemento>
55
- </cfdi:Comprobante>)
56
-
57
- base_doc.delete!("\n")
58
- base_doc.delete!("\t")
59
-
60
- xml = Nokogiri::XML(base_doc)
61
- comprobante = xml.at_xpath("//cfdi:Comprobante")
62
- comprobante['Serie'] = 'P'
63
- comprobante['Folio'] = params[:venta_folio].to_s
64
- comprobante['Fecha'] = time
65
- comprobante['LugarExpedicion'] = params[:cp].to_s
66
- comprobante['NoCertificado'] = @serial
67
- comprobante['Certificado'] = @cadena
68
- emisor = xml.at_xpath("//cfdi:Emisor")
69
- emisor['Rfc'] = @rfc
70
- emisor['Nombre'] = @razon
71
- emisor['RegimenFiscal'] = @regimen_fiscal
72
- receptor = xml.at_xpath("//cfdi:Receptor")
73
- receptor['Nombre'] = params[:receptor_razon].to_s
74
- receptor['Rfc'] = params[:receptor_rfc].to_s
75
-
76
- child_pago = xml.at_xpath("//pago10:Pago")
77
- child_pago['FechaPago'] = time
78
- child_pago['FormaDePagoP'] = params[:forma_pago].to_s
79
- child_pago['MonedaP'] = params.fetch(:moneda, 'MXN')
80
- child_pago['Monto'] = params[:total].round(2).to_s
81
-
82
- saldo_anterior = params[:total].to_s
83
-
84
- params[:line_items].each_with_index do |line, index|
85
- monto = line[:monto].to_f
86
- child_pago_relacionado = Nokogiri::XML::Node.new "pago10:DoctoRelacionado", xml
87
- child_pago_relacionado['IdDocumento'] = params[:uuid]
88
- child_pago_relacionado['MonedaDR'] = line.fetch(:moneda, 'MXN')
89
- child_pago_relacionado['MetodoDePagoDR'] = 'PPD'
90
- child_pago_relacionado['NumParcialidad'] = (index + 1).to_s
91
-
92
- child_pago_relacionado['ImpSaldoAnt'] = (saldo_anterior).round(2).to_s
93
- child_pago_relacionado['ImpPagado'] = monto.round(2).to_s
94
- child_pago_relacionado['ImpSaldoInsoluto'] = (saldo_anterior - monto).round(2).to_s
95
- saldo_anterior -= monto
96
-
97
- child_pago.add_child(child_pago_relacionado)
98
- end
99
-
100
- # puts '---------------- Xml resultante comprobante de pago -----------------------'
101
- # puts xml.to_xml
102
- # puts '--------------------------------------------------------'
103
-
104
- path = File.join(File.dirname(__FILE__), *%w[.. tmp])
105
- id = SecureRandom.hex
106
-
107
- FileUtils.mkdir_p(path) unless File.exist?(path)
108
- File.write("#{path}/tmp_c_#{id}.xml", xml.to_xml)
109
- xml_path = "#{path}/tmp_c_#{id}.xml"
110
- cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
111
-
112
- File.write("#{path}/pem_#{id}.pem", @pem)
113
- key_pem_url = "#{path}/pem_#{id}.pem"
114
- sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
115
- comprobante['Sello'] = sello
116
-
117
- File.delete("#{xml_path}")
118
- File.delete("#{key_pem_url}")
119
-
120
- # puts '------ comprobante de pago antes de timbre -------'
121
- # puts xml.to_xml
122
-
123
- base64_xml = Base64.encode64(xml.to_xml)
124
- request = Net::HTTP::Post.new(uri)
125
- request.basic_auth(token, "")
126
- request.content_type = "application/json"
127
- request["cache-control"] = 'no-cache'
128
- request.body = JSON.dump({
129
- "credentials" => {
130
- "id" => params[:venta_folio].to_s,
131
- "token" => token.to_s
132
- },
133
- "issuer" => {
134
- "rfc" => @rfc
135
- },
136
- "document" => {
137
- "ref-id": params[:venta_folio].to_s,
138
- "certificate-number": @serial,
139
- "section": "all",
140
- "format": "xml",
141
- "template": "letter",
142
- "type": "application/xml",
143
- "content": base64_xml
144
- }
145
- })
146
-
147
- req_options = {
148
- use_ssl: false,
149
- }
150
-
151
- json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
152
- http.request(request)
153
- end
154
- puts "-- #{json_response.code} --"
155
- puts "-- #{json_response.message} --"
156
- # puts "-- Body --"
157
- # puts json_response.body
158
- # puts '---'
159
- response = JSON.parse(json_response.body)
160
-
161
- if json_response.code == '200'
162
- decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
163
- timbre = decoded_xml.at_xpath("//cfdi:Complemento").children[1]
164
- response = {
165
- status: 200,
166
- message_error: '',
167
- xml: decoded_xml.to_xml,
168
- uuid: response['uuid'],
169
- fecha_timbrado: timbre['FechaTimbrado'],
170
- sello_cfd: timbre['SelloCFD'],
171
- sello_sat: timbre['SelloSAT'],
172
- no_certificado_sat: timbre['NoCertificadoSAT'],
173
- }
174
- return response
175
- else
176
- response = {
177
- status: json_response.code,
178
- message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
179
- xml: '',
180
- uuid: '',
181
- fecha_timbrado: '',
182
- sello_cfd: '',
183
- sello_sat: '',
184
- no_certificado_sat: '',
185
- }
186
- return response
187
- end
188
-
189
- end
190
-
191
- def nota_credito(params={})
192
- # Sample params
193
- # params = {
194
- # uuid_relacionado: '',
195
- # desc: '',
196
- # motivo: 'dev, mod',
197
- # series: '',
198
- # folio: '',
199
- # cp: '',
200
- # time: '',
201
- # receptor_razon: '',
202
- # receptor_rfc: '',
203
- # uso_cfdi: '',
204
- # }
205
-
206
- total = (params[:monto]).to_f
207
- subtotal = total / 1.16
208
- tax = total - subtotal
209
-
210
- uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
211
- token = @production ? @production_token : @dev_token
212
- time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
213
-
214
-
215
- base_doc = %(<?xml version="1.0" encoding="utf-8"?>
216
- <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">
217
- <cfdi:CfdiRelacionados TipoRelacion="01">
218
- <cfdi:CfdiRelacionado UUID="#{params[:uuid_relacionado]}" />
219
- </cfdi:CfdiRelacionados>
220
- <cfdi:Emisor Rfc="#{@rfc}" Nombre="#{@razon}" RegimenFiscal="#{@regimen_fiscal}" />
221
- <cfdi:Receptor Rfc="#{params[:receptor_rfc]}" Nombre="#{params[:receptor_razon]}" UsoCFDI="#{params.fetch(:uso_cfdi, 'G03')}" />
15
+ # time: '',
16
+ # modena: '',
17
+ # line_items: [
18
+ # {
19
+ # monto: 60.00,
20
+ # moneda: '',
21
+ # },
22
+ # ]
23
+ # }
24
+
25
+ puts " Datos --------"
26
+ puts "-- Total params: #{params[:total]}"
27
+ puts "--- Line items: "
28
+ params[:line_items].each do |line|
29
+ puts "--- #{line[:monto]}"
30
+ end
31
+ lines_total = params[:line_items].inject(0) {|sum, x| sum + x[:monto].to_f}
32
+
33
+ puts "-- Suma de line_items: #{lines_total.round(2)}"
34
+
35
+ if (lines_total.round(2) > params[:total].to_f)
36
+ raise 'Error SW - la suma de los complementos de pago es mayor al total reportado'
37
+ end
38
+
39
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
40
+ token = @production ? @production_token : @dev_token
41
+ time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
42
+
43
+
44
+ base_doc = %(<?xml version="1.0" encoding="UTF-8"?>
45
+ <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" >
46
+ <cfdi:Emisor />
47
+ <cfdi:Receptor UsoCFDI="P01"/>
222
48
  <cfdi:Conceptos>
223
- <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)}">
224
- <cfdi:Impuestos>
225
- <cfdi:Traslados>
226
- <cfdi:Traslado Base="#{subtotal.round(2)}" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="#{tax.round(2)}" />
227
- </cfdi:Traslados>
228
- </cfdi:Impuestos>
229
- </cfdi:Concepto>
49
+ <cfdi:Concepto ClaveProdServ="84111506" Cantidad="1" ClaveUnidad="ACT" Descripcion="Pago" ValorUnitario="0" Importe="0" />
230
50
  </cfdi:Conceptos>
231
- <cfdi:Impuestos TotalImpuestosTrasladados="#{tax.round(2)}">
232
- <cfdi:Traslados>
233
- <cfdi:Traslado Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="#{tax.round(2)}" />
234
- </cfdi:Traslados>
235
- </cfdi:Impuestos>
236
- </cfdi:Comprobante>
237
- )
51
+ <cfdi:Complemento>
52
+ <pago10:Pagos Version="1.0">
53
+ <pago10:Pago>
54
+ </pago10:Pago>
55
+ </pago10:Pagos>
56
+ </cfdi:Complemento>
57
+ </cfdi:Comprobante>)
238
58
 
239
59
  base_doc.delete!("\n")
240
- base_doc.delete!("\t")
60
+ base_doc.delete!("\t")
61
+
62
+ xml = Nokogiri::XML(base_doc)
63
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
64
+ comprobante['Serie'] = 'P'
65
+ comprobante['Folio'] = params[:venta_folio].to_s
66
+ comprobante['Fecha'] = time
67
+ comprobante['LugarExpedicion'] = params[:cp].to_s
68
+ comprobante['NoCertificado'] = @serial
69
+ comprobante['Certificado'] = @cadena
70
+ emisor = xml.at_xpath("//cfdi:Emisor")
71
+ emisor['Rfc'] = @rfc
72
+ emisor['Nombre'] = @razon
73
+ emisor['RegimenFiscal'] = @regimen_fiscal
74
+ receptor = xml.at_xpath("//cfdi:Receptor")
75
+ receptor['Nombre'] = params[:receptor_razon].to_s
76
+ receptor['Rfc'] = params[:receptor_rfc].to_s
77
+
78
+ child_pago = xml.at_xpath("//pago10:Pago")
79
+ child_pago['FechaPago'] = time
80
+ child_pago['FormaDePagoP'] = params[:forma_pago].to_s
81
+ child_pago['MonedaP'] = params.fetch(:moneda, 'MXN')
82
+ child_pago['Monto'] = params[:total].round(2).to_s
83
+
84
+ saldo_anterior = params[:total]
85
+
86
+ params[:line_items].each_with_index do |line, index|
87
+ monto = line[:monto].to_f
88
+ child_pago_relacionado = Nokogiri::XML::Node.new "pago10:DoctoRelacionado", xml
89
+ child_pago_relacionado['IdDocumento'] = params[:uuid]
90
+ child_pago_relacionado['MonedaDR'] = line.fetch(:moneda, 'MXN')
91
+ child_pago_relacionado['MetodoDePagoDR'] = 'PPD'
92
+ child_pago_relacionado['NumParcialidad'] = (index + 1).to_s
93
+
94
+ child_pago_relacionado['ImpSaldoAnt'] = (saldo_anterior).round(2).to_s
95
+ child_pago_relacionado['ImpPagado'] = monto.round(2).to_s
96
+ child_pago_relacionado['ImpSaldoInsoluto'] = (saldo_anterior - monto).round(2).to_s
97
+ saldo_anterior -= monto
98
+
99
+ child_pago.add_child(child_pago_relacionado)
100
+ end
101
+
102
+ # puts '---------------- Xml resultante comprobante de pago -----------------------'
103
+ # puts xml.to_xml
104
+ # puts '--------------------------------------------------------'
105
+
106
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
107
+ id = SecureRandom.hex
108
+
109
+ FileUtils.mkdir_p(path) unless File.exist?(path)
110
+ File.write("#{path}/tmp_c_#{id}.xml", xml.to_xml)
111
+ xml_path = "#{path}/tmp_c_#{id}.xml"
112
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
113
+
114
+ File.write("#{path}/pem_#{id}.pem", @pem)
115
+ key_pem_url = "#{path}/pem_#{id}.pem"
116
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
117
+ comprobante['Sello'] = sello
118
+
119
+ File.delete("#{xml_path}")
120
+ File.delete("#{key_pem_url}")
121
+
122
+ # puts '------ comprobante de pago antes de timbre -------'
123
+ # puts xml.to_xml
124
+
125
+ base64_xml = Base64.encode64(xml.to_xml)
126
+ request = Net::HTTP::Post.new(uri)
127
+ request.basic_auth(token, "")
128
+ request.content_type = "application/json"
129
+ request["cache-control"] = 'no-cache'
130
+ request.body = JSON.dump({
131
+ "credentials" => {
132
+ "id" => params[:venta_folio].to_s,
133
+ "token" => token.to_s
134
+ },
135
+ "issuer" => {
136
+ "rfc" => @rfc
137
+ },
138
+ "document" => {
139
+ "ref-id": params[:venta_folio].to_s,
140
+ "certificate-number": @serial,
141
+ "section": "all",
142
+ "format": "xml",
143
+ "template": "letter",
144
+ "type": "application/xml",
145
+ "content": base64_xml
146
+ }
147
+ })
148
+
149
+ req_options = {
150
+ use_ssl: false,
151
+ }
152
+
153
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
154
+ http.request(request)
155
+ end
156
+ puts "-- #{json_response.code} --"
157
+ puts "-- #{json_response.message} --"
158
+ # puts "-- Body --"
159
+ # puts json_response.body
160
+ # puts '---'
161
+ response = JSON.parse(json_response.body)
162
+
163
+ if json_response.code == '200'
164
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
165
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children[1]
166
+ response = {
167
+ status: 200,
168
+ message_error: '',
169
+ xml: decoded_xml.to_xml,
170
+ uuid: response['uuid'],
171
+ fecha_timbrado: timbre['FechaTimbrado'],
172
+ sello_cfd: timbre['SelloCFD'],
173
+ sello_sat: timbre['SelloSAT'],
174
+ no_certificado_sat: timbre['NoCertificadoSAT'],
175
+ }
176
+ return response
177
+ else
178
+ response = {
179
+ status: json_response.code,
180
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
181
+ xml: '',
182
+ uuid: '',
183
+ fecha_timbrado: '',
184
+ sello_cfd: '',
185
+ sello_sat: '',
186
+ no_certificado_sat: '',
187
+ }
188
+ return response
189
+ end
190
+
191
+ end
192
+
193
+ def nota_credito(params={})
194
+ # Sample params
195
+ # params = {
196
+ # uuid_relacionado: '',
197
+ # desc: '',
198
+ # motivo: 'dev, mod',
199
+ # series: '',
200
+ # folio: '',
201
+ # cp: '',
202
+ # time: '',
203
+ # receptor_razon: '',
204
+ # receptor_rfc: '',
205
+ # uso_cfdi: '',
206
+ # }
207
+
208
+ total = (params[:monto]).to_f
209
+ subtotal = total / 1.16
210
+ tax = total - subtotal
211
+
212
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
213
+ token = @production ? @production_token : @dev_token
214
+ time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
215
+
216
+
217
+ base_doc = %(<?xml version="1.0" encoding="utf-8"?>
218
+ <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">
219
+ <cfdi:CfdiRelacionados TipoRelacion="01">
220
+ <cfdi:CfdiRelacionado UUID="#{params[:uuid_relacionado]}" />
221
+ </cfdi:CfdiRelacionados>
222
+ <cfdi:Emisor Rfc="#{@rfc}" Nombre="#{@razon}" RegimenFiscal="#{@regimen_fiscal}" />
223
+ <cfdi:Receptor Rfc="#{params[:receptor_rfc]}" Nombre="#{params[:receptor_razon]}" UsoCFDI="#{params.fetch(:uso_cfdi, 'G03')}" />
224
+ <cfdi:Conceptos>
225
+ <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)}">
226
+ <cfdi:Impuestos>
227
+ <cfdi:Traslados>
228
+ <cfdi:Traslado Base="#{subtotal.round(2)}" Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="#{tax.round(2)}" />
229
+ </cfdi:Traslados>
230
+ </cfdi:Impuestos>
231
+ </cfdi:Concepto>
232
+ </cfdi:Conceptos>
233
+ <cfdi:Impuestos TotalImpuestosTrasladados="#{tax.round(2)}">
234
+ <cfdi:Traslados>
235
+ <cfdi:Traslado Impuesto="002" TipoFactor="Tasa" TasaOCuota="0.160000" Importe="#{tax.round(2)}" />
236
+ </cfdi:Traslados>
237
+ </cfdi:Impuestos>
238
+ </cfdi:Comprobante>
239
+ )
240
+
241
+ base_doc.delete!("\n")
242
+ base_doc.delete!("\t")
243
+
244
+ xml = Nokogiri::XML(base_doc)
245
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
246
+
247
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
248
+ id = SecureRandom.hex
249
+
250
+ FileUtils.mkdir_p(path) unless File.exist?(path)
251
+ File.write("#{path}/tmp_n_#{id}.xml", xml.to_xml)
252
+ xml_path = "#{path}/tmp_n_#{id}.xml"
253
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
254
+
255
+ File.write("#{path}/pem_#{id}.pem", @pem)
256
+ key_pem_url = "#{path}/pem_#{id}.pem"
257
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
258
+ comprobante['Sello'] = sello
259
+
260
+ File.delete("#{xml_path}")
261
+ File.delete("#{key_pem_url}")
262
+
263
+ puts '------ nota antes de timbre -------'
264
+ puts xml.to_xml
265
+
266
+ base64_xml = Base64.encode64(xml.to_xml)
267
+
268
+ request = Net::HTTP::Post.new(uri)
269
+ request.basic_auth(token, "")
270
+ request.content_type = "application/json"
271
+ request["cache-control"] = 'no-cache'
272
+ request.body = JSON.dump({
273
+ "credentials" => {
274
+ "id" => "#{params[:folio]}",
275
+ "token" => token
276
+ },
277
+ "issuer" => {
278
+ "rfc" => @rfc
279
+ },
280
+ "document" => {
281
+ "ref-id": "#{params[:folio]}",
282
+ "certificate-number": @serial,
283
+ "section": "all",
284
+ "format": "xml",
285
+ "template": "letter",
286
+ "type": "application/xml",
287
+ "content": base64_xml
288
+ }
289
+ })
290
+
291
+ req_options = {
292
+ use_ssl: false,
293
+ }
294
+
295
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
296
+ http.request(request)
297
+ end
298
+
299
+
300
+ puts "-- #{json_response.code} --"
301
+ puts "-- #{json_response.message} --"
302
+ # puts "-- Body --"
303
+ # puts json_response.body
304
+ # puts '---'
305
+ response = JSON.parse(json_response.body)
306
+
307
+ if json_response.code == '200'
308
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
309
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children.first
310
+
311
+ response = {
312
+ status: 200,
313
+ message_error: '',
314
+ xml: decoded_xml.to_xml,
315
+ uuid: response['uuid'],
316
+ fecha_timbrado: timbre['FechaTimbrado'],
317
+ sello_cfd: timbre['SelloCFD'],
318
+ sello_sat: timbre['SelloSAT'],
319
+ no_certificado_sat: timbre['NoCertificadoSAT'],
320
+ }
321
+ return response
322
+ else
323
+ response = {
324
+ status: json_response.code,
325
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
326
+ xml: '',
327
+ uuid: '',
328
+ fecha_timbrado: '',
329
+ sello_cfd: '',
330
+ sello_sat: '',
331
+ no_certificado_sat: '',
332
+ }
333
+ return response
334
+ end
335
+
336
+
337
+ end
338
+
339
+ def cancela_doc(params={})
340
+ # Sample params
341
+ # params = {
342
+ # uuid: '',
343
+ # rfc_emisor: '',
344
+ # key_password: '', # optional
345
+ # cer_cadena: '', # optional
346
+ # key_pem: '' # optional
347
+ # }
348
+
349
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/cancel/csd") : URI("#{SwFac::UrlDev}cfdi33/cancel/csd")
350
+ token = @production ? @production_token : @dev_token
351
+ # time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
352
+
353
+
354
+ request = Net::HTTP::Post.new(uri)
355
+ request["Authorization"] = "bearer #{token}"
356
+ request.content_type = "application/json"
357
+ request["Cache-Control"] = 'no-cache'
358
+ request["Postman-Token"] = '30b35bb8-534d-51c7-6a5c-e2c98a0c9395'
359
+ request.body = JSON.dump({
360
+ 'uuid': params[:uuid],
361
+ "password": params.fetch(:key_password, @key_pass),
362
+ "rfc": params.fetch(:rfc_emisor, @rfc),
363
+ "b64Cer": params.fetch(:cer_cadena, @cadena),
364
+ "b64Key": params.fetch(:key_pem, @pem_cadena)
365
+ })
366
+
367
+ req_options = {
368
+ use_ssl: false,
369
+ }
370
+
371
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
372
+ http.request(request)
373
+ end
374
+
375
+ puts "-- #{json_response.code} --"
376
+ puts "-- #{json_response.message} --"
377
+ # puts "-- Body --"
378
+ # puts json_response.body
379
+ # puts '---'
380
+ response = JSON.parse(json_response.body)
381
+
382
+ if json_response.code == '200'
383
+ decoded_xml = response['data']['acuse']
384
+
385
+ response = {
386
+ status: 200,
387
+ message_error: '',
388
+ xml: decoded_xml,
389
+ }
390
+
391
+ return response
392
+ else
393
+
394
+ response ={
395
+ status: json_response.code,
396
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
397
+ xml: '',
398
+ }
399
+
400
+ return response
401
+ end
402
+
403
+ end
404
+
405
+ def timbra_doc(params={})
406
+ ### sample params
407
+ #
408
+ # params = {
409
+ # moneda: 'MXN',
410
+ # series: 'FA',
411
+ # folio: '003',
412
+ # forma_pago: '',
413
+ # metodo_pago: 'PUE',
414
+ # cp: '47180',
415
+ # receptor_razon: 'Car zone',
416
+ # receptor_rfc: '',
417
+ # uso_cfdi: 'G03',
418
+ # time: "%Y-%m-%dT%H:%M:%S",
419
+ # line_items: [
420
+ # {
421
+ # clave_prod_serv: '78181500',
422
+ # clave_unidad: 'E48',
423
+ # unidad: 'Servicio',
424
+ # sku: 'serv001',
425
+ # cantidad: 1,
426
+ # descripcion: 'Servicio mano de obra',
427
+ # valor_unitario: 100.00,
428
+ # descuento: 0.00,
429
+ # tax_included: true,
430
+ # retencion_iva: 0, 6, 16
431
+ # # Optional parameters
432
+ # },
433
+ # ]
434
+
435
+ # }
436
+
437
+ puts "---- SwFacturacion:facturacion:timbra_doc"
438
+
439
+
440
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
441
+ token = @production ? @production_token : @dev_token
442
+ time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
443
+
444
+ xml = Nokogiri::XML(SwFac::DocBase)
445
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
446
+ comprobante['TipoCambio'] = '1'
447
+ comprobante['TipoDeComprobante'] = 'I'
448
+ comprobante['Serie'] = params.fetch(:series, 'FA').to_s
449
+ comprobante['Folio'] = params.fetch(:folio).to_s
450
+ comprobante['Fecha'] = time.to_s
451
+ comprobante['FormaPago'] = params.fetch(:forma_pago, '01')
452
+ comprobante['MetodoPago'] = params.fetch(:metodo_pago, 'PUE')
453
+ comprobante['LugarExpedicion'] = params.fetch(:cp, '')
454
+ comprobante['NoCertificado'] = @serial
455
+ comprobante['Certificado'] = @cadena
456
+
457
+ emisor = xml.at_xpath("//cfdi:Emisor")
458
+ emisor['Nombre'] = @razon
459
+ emisor['RegimenFiscal'] = @regimen_fiscal
460
+ emisor['Rfc'] = @rfc
461
+
462
+ receptor = xml.at_xpath("//cfdi:Receptor")
463
+ receptor['Nombre'] = params.fetch(:receptor_razon, '')
464
+ receptor['Rfc'] = params.fetch(:receptor_rfc, '')
465
+ receptor['UsoCFDI'] = params.fetch(:uso_cfdi, 'G03')
466
+
467
+ # retencion_iva = params.fetch(:retencion_iva, 0)
468
+
469
+ impuestos = xml.at_xpath("//cfdi:Impuestos")
470
+ traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
471
+
472
+
473
+ puts '--- sw_fac time -----'
474
+ puts time
475
+ puts '--------'
476
+
477
+ conceptos = xml.at_xpath("//cfdi:Conceptos")
478
+
479
+ line_items = params[:line_items]
480
+
481
+ suma_total = 0.00
482
+ subtotal = 0.00
483
+ suma_iva = 0.00
484
+ suma_ret = 0.00
485
+
486
+ line_items.each do |line|
487
+ ret_iva = line.fetch(:retencion_iva, 0)
488
+ puts ret_iva
489
+
490
+
491
+ ## revisando si la linea tiene iva 0
492
+ if line[:tax_included] == true
493
+ # if line[:tipo_impuesto] == '004'
494
+ # valor_unitario = (line[:valor_unitario].to_f)
495
+ # else
496
+ # end
497
+ valor_unitario = ((line[:valor_unitario]).to_f) / 1.16
498
+ else
499
+ valor_unitario = (line[:valor_unitario].to_f)
500
+ end
501
+
502
+ cantidad = line[:cantidad].to_f
503
+ total_line = cantidad * valor_unitario
504
+
505
+ # if line[:tipo_impuesto] == '004'
506
+ # total_acumulator = cantidad * valor_unitario
507
+ # else
508
+ # total_acumulator = cantidad * valor_unitario * 1.16
509
+ # end
510
+
511
+ total_acumulator = cantidad * valor_unitario * 1.16
512
+
513
+ importe_iva = total_acumulator - total_line
514
+ subtotal += total_line
515
+ suma_iva += importe_iva
516
+ suma_total += total_acumulator
517
+
518
+ puts "--- 01"
519
+ ## calculando retencion de IVA en caso de tener
520
+ if ret_iva > 0
521
+ if ret_iva == 6
522
+ importe_ret_linea = (total_line * 1.06) - total_line
523
+ elsif ret_iva == 16
524
+ importe_ret_linea = importe_iva
525
+ end
526
+ else
527
+ importe_ret_linea = 0
528
+ end
529
+ puts "--- 02"
530
+ suma_ret += importe_ret_linea
531
+
532
+
533
+ ## Creando y poblando CFDI:CONCEPTO
534
+ child_concepto = Nokogiri::XML::Node.new "cfdi:Concepto", xml
535
+ child_concepto['ClaveProdServ'] = line[:clave_prod_serv].to_s
536
+ child_concepto['NoIdentificacion'] = line[:sku].to_s
537
+ child_concepto['ClaveUnidad'] = line[:clave_unidad].to_s
538
+ child_concepto['Unidad'] = line[:unidad].to_s
539
+ child_concepto['Descripcion'] = line[:descripcion].to_s
540
+ child_concepto['Cantidad'] = cantidad.to_s
541
+ child_concepto['ValorUnitario'] = valor_unitario.round(4).to_s
542
+ child_concepto['Importe'] = total_line.round(4).to_s
543
+ # child_concepto['Descuento'] = line.fetch(:descuento, 0.00).round(6).to_s
544
+
545
+
546
+ ## Creando cdfi:Impuestos para cada linea
547
+ child_impuestos = Nokogiri::XML::Node.new "cfdi:Impuestos", xml
548
+
549
+ ## Creando cfdi:Traslados para cada linea
550
+ child_traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
551
+ child_traslado = Nokogiri::XML::Node.new "cfdi:Traslado", xml
552
+ child_traslado['Base'] = total_line.round(4).to_s
553
+ child_traslado['Impuesto'] = '002'
554
+ child_traslado['TipoFactor'] = "Tasa"
555
+ child_traslado['TasaOCuota'] = '0.160000'
556
+ child_traslado['Importe'] = importe_iva.round(4).to_s
557
+
558
+ # if line[:tipo_impuesto] == '004'
559
+ # child_traslado['TasaOCuota'] = '0.000000'
560
+ # else
561
+ # end
562
+
563
+
564
+ # Joining all up
565
+ child_traslados.add_child(child_traslado)
566
+ child_impuestos.add_child(child_traslados)
567
+ child_concepto.add_child(child_impuestos)
568
+ conceptos.add_child(child_concepto)
569
+
570
+ ## Creando cfdi:Retenciones para cada linea en caso de tener
571
+ if ret_iva > 0
572
+ child_retenciones = Nokogiri::XML::Node.new "cfdi:Retenciones", xml
573
+ child_retencion = Nokogiri::XML::Node.new "cfdi:Retencion", xml
574
+ child_retencion['Base'] = total_line.round(4).to_s
575
+ child_retencion['Impuesto'] = '002'
576
+ child_retencion['TipoFactor'] = "Tasa"
577
+
578
+ if ret_iva == 6
579
+ child_retencion['TasaOCuota'] = "0.060000"
580
+ elsif ret_iva == 16
581
+ child_retencion['TasaOCuota'] = "0.160000"
582
+ end
583
+
584
+ child_retencion['Importe'] = importe_ret_linea.round(4).to_s
585
+
586
+ child_retenciones.add_child(child_retencion)
587
+ child_impuestos.add_child(child_retenciones)
588
+ end
589
+
590
+
591
+ # ???
592
+ end
593
+
594
+ puts '------ Totales -----'
595
+ puts "Total suma = #{suma_total.round(2)}"
596
+ puts "SubTotal suma = #{subtotal.round(2)}"
597
+ puts "Suma iva = #{suma_iva.round(2)}"
598
+ puts "Suma restenciones = #{suma_ret.round(2)}"
599
+
600
+ comprobante['Moneda'] = params.fetch(:moneda, 'MXN')
601
+ comprobante['SubTotal'] = subtotal.round(2).to_s
241
602
 
242
- xml = Nokogiri::XML(base_doc)
243
- comprobante = xml.at_xpath("//cfdi:Comprobante")
244
603
 
245
- path = File.join(File.dirname(__FILE__), *%w[.. tmp])
246
- id = SecureRandom.hex
604
+ ## Poblanco cfdi:Impuestos
605
+ impuestos['TotalImpuestosRetenidos'] = suma_ret.round(2).to_s if suma_ret > 0
606
+ impuestos['TotalImpuestosTrasladados'] = suma_iva.round(2).to_s
247
607
 
248
- FileUtils.mkdir_p(path) unless File.exist?(path)
249
- File.write("#{path}/tmp_n_#{id}.xml", xml.to_xml)
250
- xml_path = "#{path}/tmp_n_#{id}.xml"
251
- cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
608
+ ## filling default retencion info
609
+ if suma_ret > 0
610
+ retenciones = Nokogiri::XML::Node.new "cfdi:Retenciones", xml
611
+ retencion_child = Nokogiri::XML::Node.new "cfdi:Retencion", xml
612
+ retencion_child['Impuesto'] = "002"
613
+ retencion_child['Importe'] = suma_ret.round(2).to_s
614
+ # retencion_child['TipoFactor'] = "Tasa"
252
615
 
253
- File.write("#{path}/pem_#{id}.pem", @pem)
254
- key_pem_url = "#{path}/pem_#{id}.pem"
255
- sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
256
- comprobante['Sello'] = sello
616
+ retenciones.add_child(retencion_child)
617
+ impuestos.add_child(retenciones)
618
+ comprobante['Total'] = (suma_total - suma_ret).round(2).to_s
619
+ else
620
+ comprobante['Total'] = suma_total.round(2).to_s
621
+ end
257
622
 
258
- File.delete("#{xml_path}")
259
- File.delete("#{key_pem_url}")
260
623
 
261
- puts '------ nota antes de timbre -------'
262
- puts xml.to_xml
624
+ ## filling traslado info
625
+ traslado_child = Nokogiri::XML::Node.new "cfdi:Traslado", xml
626
+ traslado_child['Impuesto'] = '002'
627
+ traslado_child['TipoFactor'] = 'Tasa'
628
+ traslado_child['TasaOCuota'] = '0.160000'
629
+ traslado_child['Importe'] = suma_iva.round(2).to_s
630
+ traslados.add_child(traslado_child)
631
+ impuestos.add_child(traslados)
632
+
633
+
634
+
635
+ # puts '------ Totales -----'
636
+ # puts "Total suma = #{comprobante['Total']}"
637
+ # puts "SubTotal suma = #{subtotal}"
638
+ # puts "Suma iva = #{suma_iva}"
639
+ # puts "Suma retenciones = #{impuestos['TotalImpuestosRetenidos']}" if suma_ret > 0
640
+
641
+
642
+
643
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
644
+ id = SecureRandom.hex
645
+
646
+ FileUtils.mkdir_p(path) unless File.exist?(path)
647
+ File.write("#{path}/tmp_#{id}.xml", xml.to_xml)
648
+ xml_path = "#{path}/tmp_#{id}.xml"
649
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
650
+
651
+ # puts File.read(cadena_path)
652
+ File.write("#{path}/pem_#{id}.pem", @pem)
653
+ key_pem_url = "#{path}/pem_#{id}.pem"
654
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
655
+ comprobante['Sello'] = sello
656
+
657
+ File.delete("#{xml_path}")
658
+ File.delete("#{key_pem_url}")
659
+
660
+ puts '---- SW GEM comprobante sin timbrar ------'
661
+ puts xml.to_xml
662
+ puts '-------------------------'
263
663
 
264
664
  base64_xml = Base64.encode64(xml.to_xml)
265
-
266
- request = Net::HTTP::Post.new(uri)
267
- request.basic_auth(token, "")
268
- request.content_type = "application/json"
269
- request["cache-control"] = 'no-cache'
270
- request.body = JSON.dump({
271
- "credentials" => {
272
- "id" => "#{params[:folio]}",
273
- "token" => token
274
- },
275
- "issuer" => {
276
- "rfc" => @rfc
277
- },
278
- "document" => {
279
- "ref-id": "#{params[:folio]}",
280
- "certificate-number": @serial,
281
- "section": "all",
282
- "format": "xml",
283
- "template": "letter",
284
- "type": "application/xml",
285
- "content": base64_xml
286
- }
287
- })
288
-
289
- req_options = {
290
- use_ssl: false,
291
- }
292
-
293
- json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
294
- http.request(request)
295
- end
296
-
297
-
298
- puts "-- #{json_response.code} --"
299
- puts "-- #{json_response.message} --"
300
- # puts "-- Body --"
301
- # puts json_response.body
302
- # puts '---'
303
- response = JSON.parse(json_response.body)
304
-
305
- if json_response.code == '200'
306
- decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
307
- timbre = decoded_xml.at_xpath("//cfdi:Complemento").children.first
308
-
309
- response = {
310
- status: 200,
311
- message_error: '',
312
- xml: decoded_xml.to_xml,
313
- uuid: response['uuid'],
314
- fecha_timbrado: timbre['FechaTimbrado'],
315
- sello_cfd: timbre['SelloCFD'],
316
- sello_sat: timbre['SelloSAT'],
317
- no_certificado_sat: timbre['NoCertificadoSAT'],
318
- }
319
- return response
320
- else
321
- response = {
322
- status: json_response.code,
323
- message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
324
- xml: '',
325
- uuid: '',
326
- fecha_timbrado: '',
327
- sello_cfd: '',
328
- sello_sat: '',
329
- no_certificado_sat: '',
330
- }
331
- return response
332
- end
333
-
334
-
335
- end
336
-
337
- def cancela_doc(params={})
338
- # Sample params
339
- # params = {
340
- # uuid: '',
341
- # rfc_emisor: '',
342
- # key_password: '', # optional
343
- # cer_cadena: '', # optional
344
- # key_pem: '' # optional
345
- # }
346
-
347
- uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/cancel/csd") : URI("#{SwFac::UrlDev}cfdi33/cancel/csd")
348
- token = @production ? @production_token : @dev_token
349
- # time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
350
-
351
-
352
- request = Net::HTTP::Post.new(uri)
353
- request["Authorization"] = "bearer #{token}"
354
- request.content_type = "application/json"
355
- request["Cache-Control"] = 'no-cache'
356
- request["Postman-Token"] = '30b35bb8-534d-51c7-6a5c-e2c98a0c9395'
357
- request.body = JSON.dump({
358
- 'uuid': params[:uuid],
359
- "password": params.fetch(:key_password, @key_pass),
360
- "rfc": params.fetch(:rfc_emisor, @rfc),
361
- "b64Cer": params.fetch(:cer_cadena, @cadena),
362
- "b64Key": params.fetch(:key_pem, @pem_cadena)
363
- })
364
-
365
- req_options = {
366
- use_ssl: false,
367
- }
368
-
369
- json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
370
- http.request(request)
371
- end
372
-
373
- puts "-- #{json_response.code} --"
374
- puts "-- #{json_response.message} --"
375
- # puts "-- Body --"
376
- # puts json_response.body
377
- # puts '---'
378
- response = JSON.parse(json_response.body)
379
-
380
- if json_response.code == '200'
381
- decoded_xml = response['data']['acuse']
382
-
383
- response = {
384
- status: 200,
385
- message_error: '',
386
- xml: decoded_xml,
387
- }
388
-
389
- return response
390
- else
391
-
392
- response ={
393
- status: json_response.code,
394
- message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
395
- xml: '',
396
- }
397
-
398
- return response
399
- end
400
-
401
- end
402
-
403
- def timbra_doc(params={})
404
- ### sample params
405
- #
406
- # params = {
407
- # moneda: 'MXN',
408
- # series: 'FA',
409
- # folio: '003',
410
- # forma_pago: '',
411
- # metodo_pago: 'PUE',
412
- # cp: '47180',
413
- # receptor_razon: 'Car zone',
414
- # receptor_rfc: '',
415
- # uso_cfdi: 'G03',
416
- # time: "%Y-%m-%dT%H:%M:%S",
417
- # line_items: [
418
- # {
419
- # clave_prod_serv: '78181500',
420
- # clave_unidad: 'E48',
421
- # unidad: 'Servicio',
422
- # sku: 'serv001',
423
- # cantidad: 1,
424
- # descripcion: 'Servicio mano de obra',
425
- # valor_unitario: 100.00,
426
- # descuento: 0.00,
427
- # tax_included: true,
428
- # retencion_iva: 0, 6, 16
429
- # # Optional parameters
430
- # },
431
- # ]
432
-
433
- # }
434
-
435
-
436
- uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
437
- token = @production ? @production_token : @dev_token
438
- time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
439
-
440
- xml = Nokogiri::XML(SwFac::DocBase)
441
- comprobante = xml.at_xpath("//cfdi:Comprobante")
442
- comprobante['TipoCambio'] = '1'
443
- comprobante['TipoDeComprobante'] = 'I'
444
- comprobante['Serie'] = params.fetch(:series, 'FA').to_s
445
- comprobante['Folio'] = params.fetch(:folio).to_s
446
- comprobante['Fecha'] = time.to_s
447
- comprobante['FormaPago'] = params.fetch(:forma_pago, '01')
448
- comprobante['MetodoPago'] = params.fetch(:metodo_pago, 'PUE')
449
- comprobante['LugarExpedicion'] = params.fetch(:cp, '')
450
- comprobante['NoCertificado'] = @serial
451
- comprobante['Certificado'] = @cadena
452
-
453
- emisor = xml.at_xpath("//cfdi:Emisor")
454
- emisor['Nombre'] = @razon
455
- emisor['RegimenFiscal'] = @regimen_fiscal
456
- emisor['Rfc'] = @rfc
457
-
458
- receptor = xml.at_xpath("//cfdi:Receptor")
459
- receptor['Nombre'] = params.fetch(:receptor_razon, '')
460
- receptor['Rfc'] = params.fetch(:receptor_rfc, '')
461
- receptor['UsoCFDI'] = params.fetch(:uso_cfdi, 'G03')
462
-
463
- # retencion_iva = params.fetch(:retencion_iva, 0)
464
-
465
- impuestos = xml.at_xpath("//cfdi:Impuestos")
466
- traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
467
-
468
-
469
- puts '--- sw_fac time -----'
470
- puts time
471
- puts '--------'
472
-
473
- conceptos = xml.at_xpath("//cfdi:Conceptos")
474
-
475
- line_items = params[:line_items]
476
-
477
- suma_total = 0.00
478
- subtotal = 0.00
479
- suma_iva = 0.00
480
- suma_ret = 0.00
481
-
482
-
483
-
484
- line_items.each do |line|
485
- ret_iva = line.fetch(:retencion_iva, 0)
486
- puts ret_iva
487
-
488
-
489
- ## revisando si la linea tiene iva 0
490
- if line[:tax_included] == true
491
- # if line[:tipo_impuesto] == '004'
492
- # valor_unitario = (line[:valor_unitario].to_f)
493
- # else
494
- # end
495
- valor_unitario = ((line[:valor_unitario]).to_f) / 1.16
496
- else
497
- valor_unitario = (line[:valor_unitario].to_f)
498
- end
499
-
500
- cantidad = line[:cantidad].to_f
501
- total_line = cantidad * valor_unitario
502
-
503
- # if line[:tipo_impuesto] == '004'
504
- # total_acumulator = cantidad * valor_unitario
505
- # else
506
- # total_acumulator = cantidad * valor_unitario * 1.16
507
- # end
508
-
509
- total_acumulator = cantidad * valor_unitario * 1.16
510
-
511
- importe_iva = total_acumulator - total_line
512
- subtotal += total_line
513
- suma_iva += importe_iva
514
- suma_total += total_acumulator
515
-
516
- puts "--- 01"
517
- ## calculando retencion de IVA en caso de tener
518
- if ret_iva > 0
519
- if ret_iva == 6
520
- importe_ret_linea = (total_line * 1.06) - total_line
521
- elsif ret_iva == 16
522
- importe_ret_linea = importe_iva
523
- end
524
- else
525
- importe_ret_linea = 0
526
- end
527
- puts "--- 02"
528
- suma_ret += importe_ret_linea
529
-
530
-
531
- ## Creando y poblando CFDI:CONCEPTO
532
- child_concepto = Nokogiri::XML::Node.new "cfdi:Concepto", xml
533
- child_concepto['ClaveProdServ'] = line[:clave_prod_serv].to_s
534
- child_concepto['NoIdentificacion'] = line[:sku].to_s
535
- child_concepto['ClaveUnidad'] = line[:clave_unidad].to_s
536
- child_concepto['Unidad'] = line[:unidad].to_s
537
- child_concepto['Descripcion'] = line[:descripcion].to_s
538
- child_concepto['Cantidad'] = cantidad.to_s
539
- child_concepto['ValorUnitario'] = valor_unitario.round(4).to_s
540
- child_concepto['Importe'] = total_line.round(4).to_s
541
- # child_concepto['Descuento'] = line.fetch(:descuento, 0.00).round(6).to_s
542
-
543
-
544
- ## Creando cdfi:Impuestos para cada linea
545
- child_impuestos = Nokogiri::XML::Node.new "cfdi:Impuestos", xml
546
-
547
- ## Creando cfdi:Traslados para cada linea
548
- child_traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
549
- child_traslado = Nokogiri::XML::Node.new "cfdi:Traslado", xml
550
- child_traslado['Base'] = total_line.round(4).to_s
551
- child_traslado['Impuesto'] = '002'
552
- child_traslado['TipoFactor'] = "Tasa"
553
- child_traslado['TasaOCuota'] = '0.160000'
554
- child_traslado['Importe'] = importe_iva.round(4).to_s
555
-
556
- # if line[:tipo_impuesto] == '004'
557
- # child_traslado['TasaOCuota'] = '0.000000'
558
- # else
559
- # end
560
-
561
-
562
- # Joining all up
563
- child_traslados.add_child(child_traslado)
564
- child_impuestos.add_child(child_traslados)
565
- child_concepto.add_child(child_impuestos)
566
- conceptos.add_child(child_concepto)
567
-
568
- ## Creando cfdi:Retenciones para cada linea en caso de tener
569
- if ret_iva > 0
570
- child_retenciones = Nokogiri::XML::Node.new "cfdi:Retenciones", xml
571
- child_retencion = Nokogiri::XML::Node.new "cfdi:Retencion", xml
572
- child_retencion['Base'] = total_line.round(4).to_s
573
- child_retencion['Impuesto'] = '002'
574
- child_retencion['TipoFactor'] = "Tasa"
575
-
576
- if ret_iva == 6
577
- child_retencion['TasaOCuota'] = "0.060000"
578
- elsif ret_iva == 16
579
- child_retencion['TasaOCuota'] = "0.160000"
580
- end
581
-
582
- child_retencion['Importe'] = importe_ret_linea.round(4).to_s
583
-
584
- child_retenciones.add_child(child_retencion)
585
- child_impuestos.add_child(child_retenciones)
586
- end
587
-
588
-
589
-
590
- end
591
-
592
- puts '------ Totales -----'
593
- puts "Total suma = #{suma_total.round(2)}"
594
- puts "SubTotal suma = #{subtotal.round(2)}"
595
- puts "Suma iva = #{suma_iva.round(2)}"
596
- puts "Suma restenciones = #{suma_ret.round(2)}"
597
-
598
- comprobante['Moneda'] = params.fetch(:moneda, 'MXN')
599
- comprobante['SubTotal'] = subtotal.round(2).to_s
600
-
601
-
602
- ## Poblanco cfdi:Impuestos
603
- impuestos['TotalImpuestosRetenidos'] = suma_ret.round(2).to_s if suma_ret > 0
604
- impuestos['TotalImpuestosTrasladados'] = suma_iva.round(2).to_s
605
-
606
- ## filling default retencion info
607
- if suma_ret > 0
608
- retenciones = Nokogiri::XML::Node.new "cfdi:Retenciones", xml
609
- retencion_child = Nokogiri::XML::Node.new "cfdi:Retencion", xml
610
- retencion_child['Impuesto'] = "002"
611
- retencion_child['Importe'] = suma_ret.round(2).to_s
612
- # retencion_child['TipoFactor'] = "Tasa"
613
-
614
- retenciones.add_child(retencion_child)
615
- impuestos.add_child(retenciones)
616
- comprobante['Total'] = (suma_total - suma_ret).round(2).to_s
617
- else
618
- comprobante['Total'] = suma_total.round(2).to_s
619
- end
620
-
621
-
622
- ## filling traslado info
623
- traslado_child = Nokogiri::XML::Node.new "cfdi:Traslado", xml
624
- traslado_child['Impuesto'] = '002'
625
- traslado_child['TipoFactor'] = 'Tasa'
626
- traslado_child['TasaOCuota'] = '0.160000'
627
- traslado_child['Importe'] = suma_iva.round(2).to_s
628
- traslados.add_child(traslado_child)
629
- impuestos.add_child(traslados)
630
-
631
-
632
-
633
- # puts '------ Totales -----'
634
- # puts "Total suma = #{comprobante['Total']}"
635
- # puts "SubTotal suma = #{subtotal}"
636
- # puts "Suma iva = #{suma_iva}"
637
- # puts "Suma retenciones = #{impuestos['TotalImpuestosRetenidos']}" if suma_ret > 0
638
-
639
-
640
-
641
- path = File.join(File.dirname(__FILE__), *%w[.. tmp])
642
- id = SecureRandom.hex
643
-
644
- FileUtils.mkdir_p(path) unless File.exist?(path)
645
- File.write("#{path}/tmp_#{id}.xml", xml.to_xml)
646
- xml_path = "#{path}/tmp_#{id}.xml"
647
- cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
648
-
649
- # puts File.read(cadena_path)
650
- File.write("#{path}/pem_#{id}.pem", @pem)
651
- key_pem_url = "#{path}/pem_#{id}.pem"
652
- sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
653
- comprobante['Sello'] = sello
654
-
655
- File.delete("#{xml_path}")
656
- File.delete("#{key_pem_url}")
657
-
658
- puts '---- SW GEM comprobante sin timbrar ------'
659
- puts xml.to_xml
660
- puts '-------------------------'
661
-
662
- base64_xml = Base64.encode64(xml.to_xml)
663
- request = Net::HTTP::Post.new(uri)
664
- request.basic_auth(token, "")
665
- request.content_type = "application/json"
666
- request["cache-control"] = 'no-cache'
667
- request.body = JSON.dump({
668
- "credentials" => {
669
- "id" => params.fetch(:folio).to_s,
670
- "token" => token
671
- },
672
- "issuer" => {
673
- "rfc" => emisor['Rfc']
674
- },
675
- "document" => {
676
- "ref-id": params.fetch(:folio).to_s,
677
- "certificate-number": comprobante['NoCertificado'],
678
- "section": "all",
679
- "format": "xml",
680
- "template": "letter",
681
- "type": "application/xml",
682
- "content": base64_xml
683
- }
684
- })
685
-
686
- req_options = {
687
- use_ssl: false,
688
- }
689
-
690
- json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
691
- http.request(request)
692
- end
693
-
694
- puts "-- #{json_response.code} --"
695
- puts "-- #{json_response.message} --"
696
- # puts "-- Body --"
697
- # puts json_response.body
698
- # puts '---'
699
- response = JSON.parse(json_response.body)
700
-
701
- if json_response.code == '200'
702
- decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
703
- timbre = decoded_xml.at_xpath("//cfdi:Complemento").children.first
704
-
705
- response = {
706
- status: 200,
707
- message_error: '',
708
- xml: decoded_xml.to_xml,
709
- uuid: response['uuid'],
710
- fecha_timbrado: timbre['FechaTimbrado'],
711
- sello_cfd: timbre['SelloCFD'],
712
- sello_sat: timbre['SelloSAT'],
713
- no_certificado_sat: timbre['NoCertificadoSAT'],
714
- }
715
-
716
- return response
717
- else
718
-
719
- response ={
720
- status: json_response.code,
721
- message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
722
- xml: '',
723
- uuid: '',
724
- fecha_timbrado: '',
725
- sello_cfd: '',
726
- sello_sat: '',
727
- no_certificado_sat: '',
728
- }
729
-
730
- return response
731
- end
732
-
733
- end
734
-
735
- end
665
+ request = Net::HTTP::Post.new(uri)
666
+ request.basic_auth(token, "")
667
+ request.content_type = "application/json"
668
+ request["cache-control"] = 'no-cache'
669
+ request.body = JSON.dump({
670
+ "credentials" => {
671
+ "id" => params.fetch(:folio).to_s,
672
+ "token" => token
673
+ },
674
+ "issuer" => {
675
+ "rfc" => emisor['Rfc']
676
+ },
677
+ "document" => {
678
+ "ref-id": params.fetch(:folio).to_s,
679
+ "certificate-number": comprobante['NoCertificado'],
680
+ "section": "all",
681
+ "format": "xml",
682
+ "template": "letter",
683
+ "type": "application/xml",
684
+ "content": base64_xml
685
+ }
686
+ })
687
+
688
+ req_options = {
689
+ use_ssl: false,
690
+ }
691
+
692
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
693
+ http.request(request)
694
+ end
695
+
696
+ puts "-- #{json_response.code} --"
697
+ puts "-- #{json_response.message} --"
698
+ # puts "-- Body --"
699
+ # puts json_response.body
700
+ # puts '---'
701
+ response = JSON.parse(json_response.body)
702
+
703
+ if json_response.code == '200'
704
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
705
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children.first
706
+
707
+ response = {
708
+ status: 200,
709
+ message_error: '',
710
+ xml: decoded_xml.to_xml,
711
+ uuid: response['uuid'],
712
+ fecha_timbrado: timbre['FechaTimbrado'],
713
+ sello_cfd: timbre['SelloCFD'],
714
+ sello_sat: timbre['SelloSAT'],
715
+ no_certificado_sat: timbre['NoCertificadoSAT'],
716
+ }
717
+
718
+ return response
719
+ else
720
+
721
+ response ={
722
+ status: json_response.code,
723
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
724
+ xml: '',
725
+ uuid: '',
726
+ fecha_timbrado: '',
727
+ sello_cfd: '',
728
+ sello_sat: '',
729
+ no_certificado_sat: '',
730
+ }
731
+
732
+ return response
733
+ end
734
+
735
+
736
+
737
+ end
738
+
739
+
740
+ end
741
+
736
742
  end