sw_fac 0.3.58 → 0.3.62

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,738 +1,945 @@
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',
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
- 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"/>
48
- <cfdi:Conceptos>
49
- <cfdi:Concepto ClaveProdServ="84111506" Cantidad="1" ClaveUnidad="ACT" Descripcion="Pago" ValorUnitario="0" Importe="0" />
50
- </cfdi:Conceptos>
51
- <cfdi:Complemento>
52
- <pago10:Pagos Version="1.0">
53
- <pago10:Pago>
54
- </pago10:Pago>
55
- </pago10:Pagos>
56
- </cfdi:Complemento>
57
- </cfdi:Comprobante>)
58
-
59
- base_doc.delete!("\n")
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')}" />
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
+ 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"/>
224
48
  <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>
49
+ <cfdi:Concepto ClaveProdServ="84111506" Cantidad="1" ClaveUnidad="ACT" Descripcion="Pago" ValorUnitario="0" Importe="0" />
232
50
  </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
- )
51
+ <cfdi:Complemento>
52
+ <pago10:Pagos Version="1.0">
53
+ <pago10:Pago>
54
+ </pago10:Pago>
55
+ </pago10:Pagos>
56
+ </cfdi:Complemento>
57
+ </cfdi:Comprobante>)
240
58
 
241
59
  base_doc.delete!("\n")
242
- 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
+ # motivo: '02',
345
+ # key_password: '', # optional
346
+ # cer_cadena: '', # optional
347
+ # key_pem: '' # optional
348
+ # }
349
+
350
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/cancel/csd") : URI("#{SwFac::UrlDev}cfdi33/cancel/csd")
351
+ token = @production ? @production_token : @dev_token
352
+ # time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
353
+
354
+
355
+ request = Net::HTTP::Post.new(uri)
356
+ request["Authorization"] = "bearer #{token}"
357
+ request.content_type = "application/json"
358
+ request["Cache-Control"] = 'no-cache'
359
+ request["Postman-Token"] = '30b35bb8-534d-51c7-6a5c-e2c98a0c9395'
360
+ request.body = JSON.dump({
361
+ 'uuid': params[:uuid],
362
+ "password": params.fetch(:key_password, @key_pass),
363
+ "rfc": params.fetch(:rfc_emisor, @rfc),
364
+ "motivo": '02',
365
+ "b64Cer": params.fetch(:cer_cadena, @cadena),
366
+ "b64Key": params.fetch(:key_pem, @pem_cadena)
367
+ })
368
+
369
+ req_options = {
370
+ use_ssl: false,
371
+ }
372
+
373
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
374
+ http.request(request)
375
+ end
376
+
377
+ puts "-- #{json_response.code} --"
378
+ puts "-- #{json_response.message} --"
379
+ # puts "-- Body --"
380
+ # puts json_response.body
381
+ # puts '---'
382
+ response = JSON.parse(json_response.body)
383
+
384
+ if json_response.code == '200'
385
+ decoded_xml = response['data']['acuse']
386
+
387
+ response = {
388
+ status: 200,
389
+ message_error: '',
390
+ xml: decoded_xml,
391
+ }
392
+
393
+ return response
394
+ else
395
+
396
+ response ={
397
+ status: json_response.code,
398
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
399
+ xml: '',
400
+ }
401
+
402
+ return response
403
+ end
404
+
405
+ end
406
+
407
+ def timbra_doc(params={})
408
+ ### sample params
409
+ #
410
+ # params = {
411
+ # moneda: 'MXN',
412
+ # series: 'FA',
413
+ # folio: '003',
414
+ # forma_pago: '',
415
+ # metodo_pago: 'PUE',
416
+ # cp: '47180',
417
+ # receptor_razon: 'Car zone',
418
+ # receptor_rfc: '',
419
+ # uso_cfdi: 'G03',
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_included: true,
432
+ # retencion_iva: 0, 6, 16
433
+ # # Optional parameters
434
+ # },
435
+ # ]
436
+
437
+ # }
438
+
439
+ puts "---- SwFacturacion:facturacion:timbra_doc"
440
+
441
+
442
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
443
+ token = @production ? @production_token : @dev_token
444
+ time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
445
+
446
+ xml = Nokogiri::XML(SwFac::DocBase)
447
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
448
+ comprobante['TipoCambio'] = '1'
449
+ comprobante['TipoDeComprobante'] = 'I'
450
+ comprobante['Serie'] = params.fetch(:series, 'FA').to_s
451
+ comprobante['Folio'] = params.fetch(:folio).to_s
452
+ comprobante['Fecha'] = time.to_s
453
+ comprobante['FormaPago'] = params.fetch(:forma_pago, '01')
454
+ comprobante['MetodoPago'] = params.fetch(:metodo_pago, 'PUE')
455
+ comprobante['LugarExpedicion'] = params.fetch(:cp, '')
456
+ comprobante['NoCertificado'] = @serial
457
+ comprobante['Certificado'] = @cadena
458
+
459
+ emisor = xml.at_xpath("//cfdi:Emisor")
460
+ emisor['Nombre'] = @razon
461
+ emisor['RegimenFiscal'] = @regimen_fiscal
462
+ emisor['Rfc'] = @rfc
463
+
464
+ receptor = xml.at_xpath("//cfdi:Receptor")
465
+ receptor['Nombre'] = params.fetch(:receptor_razon, '')
466
+ receptor['Rfc'] = params.fetch(:receptor_rfc, '')
467
+ receptor['UsoCFDI'] = params.fetch(:uso_cfdi, 'G03')
468
+
469
+ # retencion_iva = params.fetch(:retencion_iva, 0)
470
+
471
+ impuestos = xml.at_xpath("//cfdi:Impuestos")
472
+ traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
473
+
474
+
475
+ puts '--- sw_fac time -----'
476
+ puts time
477
+ puts '--------'
478
+
479
+ conceptos = xml.at_xpath("//cfdi:Conceptos")
480
+
481
+ line_items = params[:line_items]
482
+
483
+ suma_total = 0.00
484
+ subtotal = 0.00
485
+ suma_iva = 0.00
486
+ suma_ret = 0.00
487
+
488
+ line_items.each do |line|
489
+ ret_iva = line.fetch(:retencion_iva, 0)
490
+ puts ret_iva
491
+
492
+
493
+ ## revisando si la linea tiene iva 0
494
+ if line[:tax_included] == true
495
+ # if line[:tipo_impuesto] == '004'
496
+ # valor_unitario = (line[:valor_unitario].to_f)
497
+ # else
498
+ # end
499
+ valor_unitario = ((line[:valor_unitario]).to_f) / 1.16
500
+ else
501
+ valor_unitario = (line[:valor_unitario].to_f)
502
+ end
503
+
504
+ cantidad = line[:cantidad].to_f
505
+ total_line = cantidad * valor_unitario
506
+
507
+ # if line[:tipo_impuesto] == '004'
508
+ # total_acumulator = cantidad * valor_unitario
509
+ # else
510
+ # total_acumulator = cantidad * valor_unitario * 1.16
511
+ # end
512
+
513
+ total_acumulator = cantidad * valor_unitario * 1.16
514
+
515
+ importe_iva = total_acumulator - total_line
516
+ subtotal += total_line
517
+ suma_iva += importe_iva
518
+ suma_total += total_acumulator
519
+
520
+ puts "--- 01"
521
+ ## calculando retencion de IVA en caso de tener
522
+ if ret_iva > 0
523
+ if ret_iva == 6
524
+ importe_ret_linea = (total_line * 1.06) - total_line
525
+ elsif ret_iva == 16
526
+ importe_ret_linea = importe_iva
527
+ end
528
+ else
529
+ importe_ret_linea = 0
530
+ end
531
+ puts "--- 02"
532
+ suma_ret += importe_ret_linea
533
+
534
+
535
+ ## Creando y poblando CFDI:CONCEPTO
536
+ child_concepto = Nokogiri::XML::Node.new "cfdi:Concepto", xml
537
+ child_concepto['ClaveProdServ'] = line[:clave_prod_serv].to_s
538
+ child_concepto['NoIdentificacion'] = line[:sku].to_s
539
+ child_concepto['ClaveUnidad'] = line[:clave_unidad].to_s
540
+ child_concepto['Unidad'] = line[:unidad].to_s
541
+ child_concepto['Descripcion'] = line[:descripcion].to_s
542
+ child_concepto['Cantidad'] = cantidad.to_s
543
+ child_concepto['ValorUnitario'] = valor_unitario.round(4).to_s
544
+ child_concepto['Importe'] = total_line.round(4).to_s
545
+ # child_concepto['Descuento'] = line.fetch(:descuento, 0.00).round(6).to_s
546
+
547
+
548
+ ## Creando cdfi:Impuestos para cada linea
549
+ child_impuestos = Nokogiri::XML::Node.new "cfdi:Impuestos", xml
550
+
551
+ ## Creando cfdi:Traslados para cada linea
552
+ child_traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
553
+ child_traslado = Nokogiri::XML::Node.new "cfdi:Traslado", xml
554
+ child_traslado['Base'] = total_line.round(4).to_s
555
+ child_traslado['Impuesto'] = '002'
556
+ child_traslado['TipoFactor'] = "Tasa"
557
+ child_traslado['TasaOCuota'] = '0.160000'
558
+ child_traslado['Importe'] = importe_iva.round(4).to_s
559
+
560
+ # if line[:tipo_impuesto] == '004'
561
+ # child_traslado['TasaOCuota'] = '0.000000'
562
+ # else
563
+ # end
564
+
565
+
566
+ # Joining all up
567
+ child_traslados.add_child(child_traslado)
568
+ child_impuestos.add_child(child_traslados)
569
+ child_concepto.add_child(child_impuestos)
570
+ conceptos.add_child(child_concepto)
571
+
572
+ ## Creando cfdi:Retenciones para cada linea en caso de tener
573
+ if ret_iva > 0
574
+ child_retenciones = Nokogiri::XML::Node.new "cfdi:Retenciones", xml
575
+ child_retencion = Nokogiri::XML::Node.new "cfdi:Retencion", xml
576
+ child_retencion['Base'] = total_line.round(4).to_s
577
+ child_retencion['Impuesto'] = '002'
578
+ child_retencion['TipoFactor'] = "Tasa"
579
+
580
+ if ret_iva == 6
581
+ child_retencion['TasaOCuota'] = "0.060000"
582
+ elsif ret_iva == 16
583
+ child_retencion['TasaOCuota'] = "0.160000"
584
+ end
585
+
586
+ child_retencion['Importe'] = importe_ret_linea.round(4).to_s
587
+
588
+ child_retenciones.add_child(child_retencion)
589
+ child_impuestos.add_child(child_retenciones)
590
+ end
591
+
592
+
593
+ # ???
594
+ end
595
+
596
+ puts '------ Totales -----'
597
+ puts "Total suma = #{suma_total.round(2)}"
598
+ puts "SubTotal suma = #{subtotal.round(2)}"
599
+ puts "Suma iva = #{suma_iva.round(2)}"
600
+ puts "Suma restenciones = #{suma_ret.round(2)}"
601
+
602
+ comprobante['Moneda'] = params.fetch(:moneda, 'MXN')
603
+ comprobante['SubTotal'] = subtotal.round(2).to_s
604
+
605
+
606
+ ## Poblanco cfdi:Impuestos
607
+ impuestos['TotalImpuestosRetenidos'] = suma_ret.round(2).to_s if suma_ret > 0
608
+ impuestos['TotalImpuestosTrasladados'] = suma_iva.round(2).to_s
609
+
610
+ ## filling default retencion info
611
+ if suma_ret > 0
612
+ retenciones = Nokogiri::XML::Node.new "cfdi:Retenciones", xml
613
+ retencion_child = Nokogiri::XML::Node.new "cfdi:Retencion", xml
614
+ retencion_child['Impuesto'] = "002"
615
+ retencion_child['Importe'] = suma_ret.round(2).to_s
616
+ # retencion_child['TipoFactor'] = "Tasa"
617
+
618
+ retenciones.add_child(retencion_child)
619
+ impuestos.add_child(retenciones)
620
+ comprobante['Total'] = (suma_total - suma_ret).round(2).to_s
621
+ else
622
+ comprobante['Total'] = suma_total.round(2).to_s
623
+ end
624
+
243
625
 
244
- xml = Nokogiri::XML(base_doc)
245
- comprobante = xml.at_xpath("//cfdi:Comprobante")
626
+ ## filling traslado info
627
+ traslado_child = Nokogiri::XML::Node.new "cfdi:Traslado", xml
628
+ traslado_child['Impuesto'] = '002'
629
+ traslado_child['TipoFactor'] = 'Tasa'
630
+ traslado_child['TasaOCuota'] = '0.160000'
631
+ traslado_child['Importe'] = suma_iva.round(2).to_s
632
+ traslados.add_child(traslado_child)
633
+ impuestos.add_child(traslados)
246
634
 
247
- path = File.join(File.dirname(__FILE__), *%w[.. tmp])
248
- id = SecureRandom.hex
249
635
 
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
636
 
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
637
+ # puts '------ Totales -----'
638
+ # puts "Total suma = #{comprobante['Total']}"
639
+ # puts "SubTotal suma = #{subtotal}"
640
+ # puts "Suma iva = #{suma_iva}"
641
+ # puts "Suma retenciones = #{impuestos['TotalImpuestosRetenidos']}" if suma_ret > 0
259
642
 
260
- File.delete("#{xml_path}")
261
- File.delete("#{key_pem_url}")
262
643
 
263
- puts '------ nota antes de timbre -------'
264
- puts xml.to_xml
644
+
645
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
646
+ id = SecureRandom.hex
647
+
648
+ FileUtils.mkdir_p(path) unless File.exist?(path)
649
+ File.write("#{path}/tmp_#{id}.xml", xml.to_xml)
650
+ xml_path = "#{path}/tmp_#{id}.xml"
651
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
652
+
653
+ # puts File.read(cadena_path)
654
+ File.write("#{path}/pem_#{id}.pem", @pem)
655
+ key_pem_url = "#{path}/pem_#{id}.pem"
656
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
657
+ comprobante['Sello'] = sello
658
+
659
+ File.delete("#{xml_path}")
660
+ File.delete("#{key_pem_url}")
661
+
662
+ puts '---- SW GEM comprobante sin timbrar ------'
663
+ puts xml.to_xml
664
+ puts '-------------------------'
265
665
 
266
666
  base64_xml = Base64.encode64(xml.to_xml)
667
+ request = Net::HTTP::Post.new(uri)
668
+ request.basic_auth(token, "")
669
+ request.content_type = "application/json"
670
+ request["cache-control"] = 'no-cache'
671
+ request.body = JSON.dump({
672
+ "credentials" => {
673
+ "id" => params.fetch(:folio).to_s,
674
+ "token" => token
675
+ },
676
+ "issuer" => {
677
+ "rfc" => emisor['Rfc']
678
+ },
679
+ "document" => {
680
+ "ref-id": params.fetch(:folio).to_s,
681
+ "certificate-number": comprobante['NoCertificado'],
682
+ "section": "all",
683
+ "format": "xml",
684
+ "template": "letter",
685
+ "type": "application/xml",
686
+ "content": base64_xml
687
+ }
688
+ })
689
+
690
+ req_options = {
691
+ use_ssl: false,
692
+ }
693
+
694
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
695
+ http.request(request)
696
+ end
697
+
698
+ puts "-- #{json_response.code} --"
699
+ puts "-- #{json_response.message} --"
700
+ # puts "-- Body --"
701
+ # puts json_response.body
702
+ # puts '---'
703
+ response = JSON.parse(json_response.body)
704
+
705
+ if json_response.code == '200'
706
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
707
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children.first
708
+
709
+ response = {
710
+ status: 200,
711
+ message_error: '',
712
+ xml: decoded_xml.to_xml,
713
+ uuid: response['uuid'],
714
+ fecha_timbrado: timbre['FechaTimbrado'],
715
+ sello_cfd: timbre['SelloCFD'],
716
+ sello_sat: timbre['SelloSAT'],
717
+ no_certificado_sat: timbre['NoCertificadoSAT'],
718
+ }
719
+
720
+ return response
721
+ else
722
+
723
+ response ={
724
+ status: json_response.code,
725
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
726
+ xml: '',
727
+ uuid: '',
728
+ fecha_timbrado: '',
729
+ sello_cfd: '',
730
+ sello_sat: '',
731
+ no_certificado_sat: '',
732
+ }
733
+
734
+ return response
735
+ end
736
+
737
+
738
+
739
+ end
740
+
741
+ def timbra_doc_cero(params={})
742
+ puts "---- SwFacturacion:facturacion:timbra_doc_cero"
743
+ # params = {
744
+ # moneda: 'MXN',
745
+ # series: 'FA',
746
+ # folio: '003',
747
+ # forma_pago: '',
748
+ # metodo_pago: 'PUE',
749
+ # cp: '47180',
750
+ # receptor_razon: 'Car zone',
751
+ # receptor_rfc: '',
752
+ # uso_cfdi: 'G03',
753
+ # time: "%Y-%m-%dT%H:%M:%S",
754
+ # line_items: [
755
+ # {
756
+ # clave_prod_serv: '78181500',
757
+ # clave_unidad: 'E48',
758
+ # unidad: 'Servicio',
759
+ # sku: 'serv001',
760
+ # cantidad: 1,
761
+ # descripcion: 'Servicio mano de obra',
762
+ # valor_unitario: 100.00,
763
+ # # Optional parameters
764
+ # },
765
+ # ]
766
+
767
+ # }
768
+
769
+ uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
770
+ token = @production ? @production_token : @dev_token
771
+ time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
772
+
773
+ xml = Nokogiri::XML(SwFac::DocBaseCero)
774
+ comprobante = xml.at_xpath("//cfdi:Comprobante")
775
+ comprobante['TipoCambio'] = '1'
776
+ comprobante['TipoDeComprobante'] = 'I'
777
+ comprobante['Serie'] = params.fetch(:series, 'FA').to_s
778
+ comprobante['Folio'] = params.fetch(:folio, '1').to_s
779
+ comprobante['Fecha'] = time.to_s
780
+ comprobante['FormaPago'] = params.fetch(:forma_pago, '01')
781
+ comprobante['MetodoPago'] = params.fetch(:metodo_pago, 'PUE')
782
+ comprobante['LugarExpedicion'] = params.fetch(:cp, '55555')
783
+ comprobante['NoCertificado'] = @serial
784
+ comprobante['Certificado'] = @cadena
785
+
786
+ emisor = xml.at_xpath("//cfdi:Emisor")
787
+ emisor['Nombre'] = @razon
788
+ emisor['RegimenFiscal'] = @regimen_fiscal
789
+ emisor['Rfc'] = @rfc
790
+
791
+ receptor = xml.at_xpath("//cfdi:Receptor")
792
+ receptor['Nombre'] = params.fetch(:receptor_razon, '')
793
+ receptor['Rfc'] = params.fetch(:receptor_rfc, 'XAXX010101000')
794
+ receptor['UsoCFDI'] = params.fetch(:uso_cfdi, 'G03')
795
+
796
+
797
+ # impuestos = xml.at_xpath("//cfdi:Impuestos")
798
+ # traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
799
+
800
+
801
+ puts '--- sw_fac time -----'
802
+ puts time
803
+ puts '--------'
804
+
805
+ conceptos = xml.at_xpath("//cfdi:Conceptos")
806
+
807
+ line_items = params[:line_items]
808
+
809
+ suma_total = 0.00
810
+
811
+ line_items.each do |line|
812
+
813
+ valor_unitario = line[:valor_unitario].to_f
814
+ cantidad = line[:cantidad].to_f
815
+ total_line = cantidad * valor_unitario
816
+
817
+ suma_total += total_line
818
+
819
+ ## Creando y poblando CFDI:CONCEPTO
820
+ child_concepto = Nokogiri::XML::Node.new "cfdi:Concepto", xml
821
+ child_concepto['ClaveProdServ'] = line[:clave_prod_serv].to_s
822
+ child_concepto['NoIdentificacion'] = line[:sku].to_s
823
+ child_concepto['ClaveUnidad'] = line[:clave_unidad].to_s
824
+ child_concepto['Unidad'] = line[:unidad].to_s
825
+ child_concepto['Descripcion'] = line[:descripcion].to_s
826
+ child_concepto['Cantidad'] = cantidad.to_s
827
+ child_concepto['ValorUnitario'] = valor_unitario.round(4).to_s
828
+ child_concepto['Importe'] = total_line.round(4).to_s
829
+
830
+
831
+ # Joining all up
832
+ conceptos.add_child(child_concepto)
267
833
 
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
-
438
- uri = @production ? URI("#{SwFac::UrlProduction}cfdi33/stamp/customv1/b64") : URI("#{SwFac::UrlDev}cfdi33/stamp/customv1/b64")
439
- token = @production ? @production_token : @dev_token
440
- time = params.fetch(:time, (Time.now).strftime("%Y-%m-%dT%H:%M:%S"))
441
-
442
- xml = Nokogiri::XML(SwFac::DocBase)
443
- comprobante = xml.at_xpath("//cfdi:Comprobante")
444
- comprobante['TipoCambio'] = '1'
445
- comprobante['TipoDeComprobante'] = 'I'
446
- comprobante['Serie'] = params.fetch(:series, 'FA').to_s
447
- comprobante['Folio'] = params.fetch(:folio).to_s
448
- comprobante['Fecha'] = time.to_s
449
- comprobante['FormaPago'] = params.fetch(:forma_pago, '01')
450
- comprobante['MetodoPago'] = params.fetch(:metodo_pago, 'PUE')
451
- comprobante['LugarExpedicion'] = params.fetch(:cp, '')
452
- comprobante['NoCertificado'] = @serial
453
- comprobante['Certificado'] = @cadena
454
-
455
- emisor = xml.at_xpath("//cfdi:Emisor")
456
- emisor['Nombre'] = @razon
457
- emisor['RegimenFiscal'] = @regimen_fiscal
458
- emisor['Rfc'] = @rfc
459
-
460
- receptor = xml.at_xpath("//cfdi:Receptor")
461
- receptor['Nombre'] = params.fetch(:receptor_razon, '')
462
- receptor['Rfc'] = params.fetch(:receptor_rfc, '')
463
- receptor['UsoCFDI'] = params.fetch(:uso_cfdi, 'G03')
464
-
465
- # retencion_iva = params.fetch(:retencion_iva, 0)
466
-
467
- impuestos = xml.at_xpath("//cfdi:Impuestos")
468
- traslados = Nokogiri::XML::Node.new "cfdi:Traslados", xml
469
-
470
-
471
- puts '--- sw_fac time -----'
472
- puts time
473
- puts '--------'
474
-
475
- conceptos = xml.at_xpath("//cfdi:Conceptos")
476
-
477
- line_items = params[:line_items]
478
-
479
- suma_total = 0.00
480
- subtotal = 0.00
481
- suma_iva = 0.00
482
- suma_ret = 0.00
483
-
484
-
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
602
-
603
-
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
607
-
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"
615
-
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
622
-
623
-
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 '-------------------------'
663
-
664
- base64_xml = Base64.encode64(xml.to_xml)
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
- end
736
-
737
- end
834
+
835
+ end
836
+
837
+ puts '------ Totales -----'
838
+ puts "Subtotal = #{suma_total}"
839
+ puts "Total = #{suma_total}"
840
+
841
+ comprobante['Moneda'] = params.fetch(:moneda, 'MXN')
842
+ comprobante['SubTotal'] = suma_total.round(2).to_s
843
+ comprobante['Total'] = suma_total.round(2).to_s
844
+
845
+
846
+
847
+ path = File.join(File.dirname(__FILE__), *%w[.. tmp])
848
+ id = SecureRandom.hex
849
+
850
+ FileUtils.mkdir_p(path) unless File.exist?(path)
851
+ File.write("#{path}/tmp_#{id}.xml", xml.to_xml)
852
+ xml_path = "#{path}/tmp_#{id}.xml"
853
+ cadena_path = File.join(File.dirname(__FILE__), *%w[.. cadena cadena33.xslt])
854
+
855
+ # puts File.read(cadena_path)
856
+ File.write("#{path}/pem_#{id}.pem", @pem)
857
+ key_pem_url = "#{path}/pem_#{id}.pem"
858
+ sello = %x[xsltproc #{cadena_path} #{xml_path} | openssl dgst -sha256 -sign #{key_pem_url} | openssl enc -base64 -A]
859
+ comprobante['Sello'] = sello
860
+
861
+ File.delete("#{xml_path}")
862
+ File.delete("#{key_pem_url}")
863
+
864
+ puts '---- SW GEM comprobante sin timbrar ------'
865
+ puts xml.to_xml
866
+ puts '-------------------------'
867
+
868
+ base64_xml = Base64.encode64(xml.to_xml)
869
+ request = Net::HTTP::Post.new(uri)
870
+ request.basic_auth(token, "")
871
+ request.content_type = "application/json"
872
+ request["cache-control"] = 'no-cache'
873
+ request.body = JSON.dump({
874
+ "credentials" => {
875
+ "id" => params.fetch(:folio).to_s,
876
+ "token" => token
877
+ },
878
+ "issuer" => {
879
+ "rfc" => emisor['Rfc']
880
+ },
881
+ "document" => {
882
+ "ref-id": params.fetch(:folio).to_s,
883
+ "certificate-number": comprobante['NoCertificado'],
884
+ "section": "all",
885
+ "format": "xml",
886
+ "template": "letter",
887
+ "type": "application/xml",
888
+ "content": base64_xml
889
+ }
890
+ })
891
+
892
+ req_options = {
893
+ use_ssl: false,
894
+ }
895
+
896
+ json_response = Net::HTTP.start(uri.hostname, uri.port, req_options) do |http|
897
+ http.request(request)
898
+ end
899
+
900
+ puts "-- SW API reponse..."
901
+ puts "-- Response code: #{json_response.code} --"
902
+ puts "-- Response body: #{json_response.body} --"
903
+ puts "-- Response message: #{json_response.message} --"
904
+
905
+ response = JSON.parse(json_response.body)
906
+
907
+ if json_response.code == '200'
908
+ decoded_xml = Nokogiri::XML(Base64.decode64(response['content']))
909
+ timbre = decoded_xml.at_xpath("//cfdi:Complemento").children.first
910
+
911
+ response = {
912
+ status: 200,
913
+ message_error: '',
914
+ xml: decoded_xml.to_xml,
915
+ uuid: response['uuid'],
916
+ fecha_timbrado: timbre['FechaTimbrado'],
917
+ sello_cfd: timbre['SelloCFD'],
918
+ sello_sat: timbre['SelloSAT'],
919
+ no_certificado_sat: timbre['NoCertificadoSAT'],
920
+ }
921
+
922
+ return response
923
+ else
924
+
925
+ response ={
926
+ status: json_response.code,
927
+ message_error: "Error message: #{json_response.message}, #{response['message']} #{response['error_details']}",
928
+ xml: '',
929
+ uuid: '',
930
+ fecha_timbrado: '',
931
+ sello_cfd: '',
932
+ sello_sat: '',
933
+ no_certificado_sat: '',
934
+ }
935
+
936
+ return response
937
+ end
938
+
939
+
940
+ end
941
+
942
+
943
+ end
944
+
738
945
  end