sw_fac 0.3.58 → 0.3.62

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