fm_adapter 0.0.1
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.
- checksums.yaml +7 -0
- data/README.md +77 -0
- data/Rakefile +6 -0
- data/fm_adapter.gemspec +23 -0
- data/lib/fm_adapter/fm_cfdi.rb +31 -0
- data/lib/fm_adapter/fm_cfdi_parser.rb +28 -0
- data/lib/fm_adapter/fm_cliente.rb +69 -0
- data/lib/fm_adapter/fm_informacion_cfdi.rb +26 -0
- data/lib/fm_adapter/fm_respuesta.rb +105 -0
- data/lib/fm_adapter/fm_respuesta_cancelacion.rb +28 -0
- data/lib/fm_adapter/fm_timbre.rb +78 -0
- data/lib/fm_adapter/version.rb +3 -0
- data/lib/fm_adapter.rb +87 -0
- metadata +100 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: ef34784c30c539aa5ce36d7bd36e07b64bf1a84eaf36736d10eb6354263a78d2
|
4
|
+
data.tar.gz: bc1771e6d3257dd802e408f224234f3c7cf7f5dc64a427015e40c814ee66a7a4
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: '09b8b5e915e865f947b7f890cc0b5dc06ad85475ec70ee6c66caa7f1806937000cabb29deb57846f7d8176c5bbbf34131309dc3052a13a363025f895bcd41667'
|
7
|
+
data.tar.gz: 972db0557f4311404022232f144ebc23ad1d5037246e9841a8745ab46628649d21bdf772c6ab145f96ed573a2f72960a51d26695182a14812aa65b47bd3cf8df
|
data/README.md
ADDED
@@ -0,0 +1,77 @@
|
|
1
|
+
# FmTimbradoCfdi
|
2
|
+
[](https://travis-ci.org/LogicalBricks/fm_timbrado_cfdi)
|
3
|
+
[](https://codeclimate.com/github/LogicalBricks/fm_timbrado_cfdi)
|
4
|
+
[](https://coveralls.io/r/LogicalBricks/fm_timbrado_cfdi)
|
5
|
+
|
6
|
+
Implementa la conexión con el servicio de timbrado cfdi con el PAC Facturación Moderna [Guía de Desarrollo de FM](http://developers.facturacionmoderna.com).
|
7
|
+
|
8
|
+
No incluye ninguna funcionalidad de sellado.
|
9
|
+
|
10
|
+
## Installation
|
11
|
+
|
12
|
+
Agrega esto al Gemfile de tu aplicación:
|
13
|
+
|
14
|
+
gem 'fm_timbrado_cfdi'
|
15
|
+
|
16
|
+
Y ejecuta:
|
17
|
+
|
18
|
+
$ bundle
|
19
|
+
|
20
|
+
o instala de forma independiente:
|
21
|
+
|
22
|
+
$ gem install fm_timbrado_cfdi
|
23
|
+
|
24
|
+
## Uso
|
25
|
+
|
26
|
+
Para usar la gema es necesario realizar la configuración con los valores de conexión:
|
27
|
+
|
28
|
+
```ruby
|
29
|
+
FmTimbradoCfdi.configurar do |config|
|
30
|
+
config.user_id = 'user_id'
|
31
|
+
config.user_pass = 'password'
|
32
|
+
config.namespace = 'http://serviciondetimrado...'
|
33
|
+
config.endpoint = 'http://serviciondetimrado...'
|
34
|
+
config.fm_wsdl = 'http://serviciondetimrado...'
|
35
|
+
config.log = 'path_to_log'
|
36
|
+
config.ssl_verify_mode = true
|
37
|
+
end # configurar
|
38
|
+
```
|
39
|
+
|
40
|
+
Y realizar la petición de timbrado:
|
41
|
+
|
42
|
+
```ruby
|
43
|
+
respuesta = FmTimbradoCfdi.timbra_cfdi_layout rfc, 'layout_file', false
|
44
|
+
# => Petición sin generación del CBB
|
45
|
+
respuesta = FmTimbradoCfdi.timbra_cfdi_layout rfc, 'layout_file', true
|
46
|
+
# => Petición con generación del CBB
|
47
|
+
```
|
48
|
+
|
49
|
+
Si cuentas con el XML sellado puedes hacer lo siguiente:
|
50
|
+
|
51
|
+
```ruby
|
52
|
+
respuesta = FmTimbradoCfdi.timbra_cfdi_xml 'archivo_xml', false
|
53
|
+
# => Petición sin generación del CBB
|
54
|
+
respuesta = FmTimbradoCfdi.timbra_cfdi_xml 'archivo_xml', true
|
55
|
+
# => Petición con generación del CBB
|
56
|
+
```
|
57
|
+
|
58
|
+
Para un método más general de timbrado que se encuentre más acorde a la documentación de [Facturación Moderna](http://developers.facturacionmoderna.com):
|
59
|
+
|
60
|
+
```ruby
|
61
|
+
respuesta = FmTimbradoCfdi.timbrar rfc_emisor, 'archivo_xml_o_layout', 'generarCBB' => false, 'generarPDF' => true, 'generarTXT' => false
|
62
|
+
# => Generar la respuesta con formato PDF, pero sin formato CBB ni TXT
|
63
|
+
```
|
64
|
+
|
65
|
+
|
66
|
+
El archivo de layout y el XML son string.
|
67
|
+
|
68
|
+
|
69
|
+
## Contribuciones
|
70
|
+
|
71
|
+
1. 'Forkea' el repositorio
|
72
|
+
2. Crea una rama con tu funcionalidad (`git checkout -b my-new-feature`)
|
73
|
+
3. Envía tus cambios (`git commit -am 'Add some feature'`)
|
74
|
+
4. 'Pushea' a la rama (`git push origin my-new-feature`)
|
75
|
+
5. Crea un 'Pull request'
|
76
|
+
|
77
|
+
Esta gema fue creada por LogicalBricks Solutions.
|
data/Rakefile
ADDED
data/fm_adapter.gemspec
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
lib = File.expand_path('../lib', __FILE__)
|
2
|
+
$LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
|
3
|
+
require 'fm_adapter/version'
|
4
|
+
|
5
|
+
Gem::Specification.new do |gem|
|
6
|
+
gem.name = 'fm_adapter'
|
7
|
+
gem.version = FmTimbradoCfdi::VERSION
|
8
|
+
gem.authors = ['Gabriel Edera', 'Facundo Condori', 'Franco Sanchez']
|
9
|
+
gem.email = ['gab.edera@gmail.com']
|
10
|
+
gem.homepage = 'https://github.com/gedera/fm_adapter'
|
11
|
+
gem.summary = %q{Implementación en ruby de la conexión con el servicio de timbrado de cfdi con el PAC Facturación Moderna}
|
12
|
+
gem.description = 'Implementación en Ruby de la Conexión con el Servicio de Timbrado de CFDI con el PAC: Facturación Moderna'
|
13
|
+
|
14
|
+
gem.files = `git ls-files`.split($/)
|
15
|
+
gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) }
|
16
|
+
gem.test_files = gem.files.grep(%r{^(test|spec|features)/})
|
17
|
+
gem.require_paths = ['lib']
|
18
|
+
|
19
|
+
gem.add_dependency('nokogiri', ['~> 1.10.9'])
|
20
|
+
gem.add_dependency('savon', ['~> 2.12.0'])
|
21
|
+
|
22
|
+
gem.add_development_dependency 'rspec'
|
23
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module FmTimbradoCfdi
|
2
|
+
class FmCfdi
|
3
|
+
attr_reader :xml
|
4
|
+
|
5
|
+
def initialize(response)
|
6
|
+
@doc = Nokogiri::XML(response)
|
7
|
+
@xml = obtener_xml(@doc)
|
8
|
+
end
|
9
|
+
|
10
|
+
def version_3_2?
|
11
|
+
obtener_version('version') == '3.2'
|
12
|
+
end
|
13
|
+
|
14
|
+
def version_3_3?
|
15
|
+
obtener_version('Version') == '3.3'
|
16
|
+
end
|
17
|
+
|
18
|
+
private
|
19
|
+
|
20
|
+
def obtener_version(version)
|
21
|
+
factura_xml = Nokogiri::XML(xml)
|
22
|
+
factura_xml.xpath("//cfdi:Comprobante").attribute(version).value rescue nil
|
23
|
+
end
|
24
|
+
|
25
|
+
def obtener_xml(doc)
|
26
|
+
return nil if doc.xpath("//xml").empty?
|
27
|
+
|
28
|
+
Base64.decode64(doc.xpath("//xml")[0].content)
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
|
3
|
+
module FmTimbradoCfdi
|
4
|
+
class FmCfdiParser
|
5
|
+
def initialize(nodo_timbre)
|
6
|
+
parse(nodo_timbre)
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
|
11
|
+
def parse(nodo_timbre)
|
12
|
+
xml = Nokogiri::XML(nodo_timbre)
|
13
|
+
ns = generar_namespaces(xml)
|
14
|
+
atributos.each do |variable|
|
15
|
+
instance_variable_set("@#{variable}", send("obtener_#{variable}", xml, ns))
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def generar_namespaces(xml)
|
20
|
+
namespaces = xml.collect_namespaces
|
21
|
+
ns = {}
|
22
|
+
namespaces.each_pair do |key, value|
|
23
|
+
ns[key.sub(/^xmlns:/, '')] = value
|
24
|
+
end
|
25
|
+
ns
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
require 'savon'
|
2
|
+
require 'fm_adapter/fm_respuesta'
|
3
|
+
require 'fm_adapter/fm_respuesta_cancelacion'
|
4
|
+
|
5
|
+
module FmTimbradoCfdi
|
6
|
+
class FmCliente
|
7
|
+
attr_accessor :user_id, :user_pass, :namespace, :fm_wsdl, :endpoint, :ssl_verify_mode, :log, :log_level, :logger
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
# La configuracion por default es la del ambiente de pruebas de FM
|
11
|
+
# Datos de acceso al webservice
|
12
|
+
@user_id = 'UsuarioPruebasWS'
|
13
|
+
@user_pass = 'b9ec2afa3361a59af4b4d102d3f704eabdf097d4'
|
14
|
+
# Datos del webservise de prueba
|
15
|
+
@namespace = 'https://t2demo.facturacionmoderna.com/timbrado/soap'
|
16
|
+
@endpoint = 'https://t2demo.facturacionmoderna.com/timbrado/soap'
|
17
|
+
@fm_wsdl = 'https://t2demo.facturacionmoderna.com/timbrado/wsdl'
|
18
|
+
|
19
|
+
#Opciones adicionales
|
20
|
+
@log = false
|
21
|
+
@log_level = :error
|
22
|
+
@ssl_verify_mode = :none
|
23
|
+
@logger = nil
|
24
|
+
end
|
25
|
+
|
26
|
+
def timbrar(rfc, documento, opciones = {})
|
27
|
+
text_to_cfdi = Base64.encode64( documento )
|
28
|
+
# Realizamos la peticion
|
29
|
+
respuesta = webservice_call(:request_timbrar_cfdi, rfc, {'text2CFDI' => text_to_cfdi}.merge(opciones))
|
30
|
+
FmRespuesta.new(respuesta)
|
31
|
+
end
|
32
|
+
|
33
|
+
def subir_certificado(rfc, certificado, llave, password, opciones = {})
|
34
|
+
parametros = { 'archivoCer' => Base64.encode64(certificado),
|
35
|
+
'archivoKey' => Base64.encode64(llave),
|
36
|
+
'clave' => password }
|
37
|
+
respuesta = webservice_call(:activar_cancelacion, rfc, parametros.merge(opciones))
|
38
|
+
FmRespuestaCancelacion.new(respuesta)
|
39
|
+
end
|
40
|
+
|
41
|
+
def cancelar(rfc, uuid, opciones = {})
|
42
|
+
respuesta = webservice_call(:request_cancelar_cfdi, rfc, {uuid: uuid}.merge(opciones))
|
43
|
+
FmRespuestaCancelacion.new(respuesta)
|
44
|
+
end
|
45
|
+
|
46
|
+
private
|
47
|
+
|
48
|
+
def webservice_call(accion, rfc, opciones)
|
49
|
+
configurar_cliente
|
50
|
+
parametros= { 'param0' => { 'UserPass' => user_pass, 'UserID' => user_id, 'emisorRFC' => rfc }.merge(opciones) }
|
51
|
+
@client.call(accion, message: parametros)
|
52
|
+
end
|
53
|
+
|
54
|
+
def configurar_cliente
|
55
|
+
@client = Savon.client do |globals|
|
56
|
+
globals.ssl_verify_mode ssl_verify_mode
|
57
|
+
globals.wsdl fm_wsdl
|
58
|
+
globals.endpoint endpoint
|
59
|
+
globals.raise_errors false
|
60
|
+
globals.log_level log_level
|
61
|
+
globals.log log
|
62
|
+
globals.logger logger if logger
|
63
|
+
globals.open_timeout 15
|
64
|
+
globals.read_timeout 15
|
65
|
+
end
|
66
|
+
end
|
67
|
+
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'fm_adapter/fm_cfdi_parser'
|
3
|
+
|
4
|
+
module FmTimbradoCfdi
|
5
|
+
class FmInformacionCfdi < FmCfdiParser
|
6
|
+
attr_reader :total, :subtotal, :descuento
|
7
|
+
|
8
|
+
private
|
9
|
+
|
10
|
+
def atributos
|
11
|
+
['total', 'subtotal', 'descuento']
|
12
|
+
end
|
13
|
+
|
14
|
+
def obtener_total(xml, ns)
|
15
|
+
xml.xpath('//cfdi:Comprobante', ns).attribute('Total').value rescue nil
|
16
|
+
end
|
17
|
+
|
18
|
+
def obtener_subtotal(xml, ns)
|
19
|
+
xml.xpath('//cfdi:Comprobante', ns).attribute('SubTotal').value rescue nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def obtener_descuento(xml, ns)
|
23
|
+
xml.xpath('//cfdi:Comprobante', ns).attribute('Descuento').value rescue nil
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'savon'
|
3
|
+
require 'fm_adapter/fm_timbre'
|
4
|
+
|
5
|
+
module FmTimbradoCfdi
|
6
|
+
class FmRespuesta
|
7
|
+
attr_reader :errors, :pdf, :xml, :cbb, :timbre, :no_csd_emisor, :raw
|
8
|
+
|
9
|
+
def initialize(response)
|
10
|
+
@errors = []
|
11
|
+
if response.is_a? String
|
12
|
+
@raw = response
|
13
|
+
procesar_respuesta(response)
|
14
|
+
else
|
15
|
+
parse_savon(response)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
def parse_savon(savon_response)
|
20
|
+
if savon_response.success?
|
21
|
+
@raw = savon_response.to_xml
|
22
|
+
procesar_respuesta(savon_response.to_xml)
|
23
|
+
else
|
24
|
+
@errors << savon_response.soap_fault.to_s if savon_response.soap_fault?
|
25
|
+
@doc = @xml = @no_csd_emisor = @timbre = @pdf = @cbb = nil
|
26
|
+
end
|
27
|
+
rescue Exception => e
|
28
|
+
@errors << "No se ha podido realizar el parseo de la respuesta. #{e.message}"
|
29
|
+
end
|
30
|
+
|
31
|
+
def valid?
|
32
|
+
@errors.empty?
|
33
|
+
end
|
34
|
+
|
35
|
+
def xml?
|
36
|
+
!!@xml
|
37
|
+
end
|
38
|
+
|
39
|
+
def cbb?
|
40
|
+
!!@cbb
|
41
|
+
end
|
42
|
+
|
43
|
+
def pdf?
|
44
|
+
!!@pdf
|
45
|
+
end
|
46
|
+
|
47
|
+
def timbre?
|
48
|
+
!!@timbre
|
49
|
+
end
|
50
|
+
|
51
|
+
def no_csd_emisor?
|
52
|
+
!!@no_csd_emisor
|
53
|
+
end
|
54
|
+
|
55
|
+
alias xml_present? xml?
|
56
|
+
alias cbb_present? cbb?
|
57
|
+
alias pdf_present? pdf?
|
58
|
+
alias timbre_present? timbre?
|
59
|
+
alias no_csd_emisor_present? no_csd_emisor?
|
60
|
+
|
61
|
+
private
|
62
|
+
|
63
|
+
def procesar_respuesta(respuesta_xml)
|
64
|
+
@doc = Nokogiri::XML(respuesta_xml)
|
65
|
+
@xml = obtener_xml(@doc)
|
66
|
+
@no_csd_emisor = obtener_no_csd_emisor(@xml) if @xml
|
67
|
+
@timbre = obtener_timbre(@xml)
|
68
|
+
@pdf = obtener_pdf(@doc)
|
69
|
+
@cbb = obtener_cbb(@doc)
|
70
|
+
end
|
71
|
+
|
72
|
+
def obtener_xml(doc)
|
73
|
+
return sin_nodo_xml if doc.xpath('//xml').empty?
|
74
|
+
|
75
|
+
Base64.decode64(doc.xpath('//xml')[0].content)
|
76
|
+
end
|
77
|
+
|
78
|
+
def sin_nodo_xml
|
79
|
+
@errors << 'No se ha encontrado el nodo xml'
|
80
|
+
nil
|
81
|
+
end
|
82
|
+
|
83
|
+
def obtener_timbre(xml)
|
84
|
+
FmTimbre.new(xml) if xml
|
85
|
+
end
|
86
|
+
|
87
|
+
def obtener_pdf(doc)
|
88
|
+
Base64.decode64(doc.xpath('//pdf')[0].content) unless doc.xpath('//pdf').empty?
|
89
|
+
end
|
90
|
+
|
91
|
+
def obtener_cbb(doc)
|
92
|
+
Base64.decode64(doc.xpath('//png')[0].content) unless doc.xpath('//png').empty?
|
93
|
+
end
|
94
|
+
|
95
|
+
def obtener_no_csd_emisor(xml)
|
96
|
+
factura_xml = Nokogiri::XML(xml)
|
97
|
+
if factura_xml.xpath('//cfdi:Comprobante').attribute('NoCertificado')
|
98
|
+
factura_xml.xpath('//cfdi:Comprobante').attribute('NoCertificado').value
|
99
|
+
else
|
100
|
+
@errors << 'No se ha podido obtener el CSD del emisor'
|
101
|
+
nil
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,28 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'savon'
|
3
|
+
|
4
|
+
module FmTimbradoCfdi
|
5
|
+
class FmRespuestaCancelacion
|
6
|
+
attr_reader :error, :xml
|
7
|
+
|
8
|
+
def initialize(savon_response)
|
9
|
+
@respuesta = savon_response
|
10
|
+
unless valid?
|
11
|
+
@error = @respuesta.to_s if @respuesta.soap_fault?
|
12
|
+
@xml = @respuesta.to_xml
|
13
|
+
end
|
14
|
+
end
|
15
|
+
|
16
|
+
def codigo
|
17
|
+
@error.match(/\((.*)\).*/)[0] if @error
|
18
|
+
end
|
19
|
+
|
20
|
+
def mensaje
|
21
|
+
@error
|
22
|
+
end
|
23
|
+
|
24
|
+
def valid?
|
25
|
+
@respuesta.success?
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
@@ -0,0 +1,78 @@
|
|
1
|
+
require 'nokogiri'
|
2
|
+
require 'fm_adapter/fm_cfdi_parser'
|
3
|
+
|
4
|
+
module FmTimbradoCfdi
|
5
|
+
class FmTimbre < FmCfdiParser
|
6
|
+
attr_reader :no_certificado_sat, :no_certificado, :fecha_timbrado, :uuid, :sello_sat, :sello_cfd,
|
7
|
+
:fecha_comprobante, :serie, :folio, :trans_id, :version, :rfc_provedor_certificacion
|
8
|
+
|
9
|
+
def cadena_original
|
10
|
+
"||#{version}|#{uuid}|#{fecha_timbrado}|#{sello_cfd}|#{no_certificado_sat}||"
|
11
|
+
end
|
12
|
+
|
13
|
+
private
|
14
|
+
|
15
|
+
def atributos
|
16
|
+
['trans_id',
|
17
|
+
'version',
|
18
|
+
'no_certificado_sat',
|
19
|
+
'no_certificado',
|
20
|
+
'fecha_timbrado',
|
21
|
+
'uuid',
|
22
|
+
'sello_sat',
|
23
|
+
'sello_cfd',
|
24
|
+
'fecha_comprobante',
|
25
|
+
'serie',
|
26
|
+
'rfc_provedor_certificacion',
|
27
|
+
'folio']
|
28
|
+
end
|
29
|
+
|
30
|
+
def obtener_version(xml, ns)
|
31
|
+
xml.xpath('//tfd:TimbreFiscalDigital', ns).attribute('Version').value rescue nil
|
32
|
+
end
|
33
|
+
|
34
|
+
def obtener_no_certificado(xml, ns)
|
35
|
+
xml.xpath('//cfdi:Comprobante',ns).attribute('NoCertificado').value rescue nil
|
36
|
+
end
|
37
|
+
|
38
|
+
def obtener_trans_id(xml, ns)
|
39
|
+
xml.xpath('//cfdi:Comprobante',ns).attribute('TransID').value rescue nil
|
40
|
+
end
|
41
|
+
|
42
|
+
def obtener_no_certificado_sat(xml, ns)
|
43
|
+
xml.xpath('//tfd:TimbreFiscalDigital', ns).attribute('NoCertificadoSAT').value rescue nil
|
44
|
+
end
|
45
|
+
|
46
|
+
def obtener_uuid(xml, ns)
|
47
|
+
xml.xpath('//tfd:TimbreFiscalDigital', ns).attribute('UUID').value rescue nil
|
48
|
+
end
|
49
|
+
|
50
|
+
def obtener_fecha_timbrado(xml, ns)
|
51
|
+
xml.xpath('//tfd:TimbreFiscalDigital', ns).attribute('FechaTimbrado').value rescue nil
|
52
|
+
end
|
53
|
+
|
54
|
+
def obtener_sello_sat(xml, ns)
|
55
|
+
xml.xpath('//tfd:TimbreFiscalDigital', ns).attribute('SelloSAT').value rescue nil
|
56
|
+
end
|
57
|
+
|
58
|
+
def obtener_fecha_comprobante(xml, ns)
|
59
|
+
xml.xpath('//cfdi:Comprobante', ns).attribute('Fecha').value rescue nil
|
60
|
+
end
|
61
|
+
|
62
|
+
def obtener_sello_cfd(xml, ns)
|
63
|
+
xml.xpath('//tfd:TimbreFiscalDigital', ns).attribute('SelloCFD').value rescue nil
|
64
|
+
end
|
65
|
+
|
66
|
+
def obtener_serie(xml, ns)
|
67
|
+
xml.xpath('//cfdi:Comprobante', ns).attribute('Serie').value rescue nil
|
68
|
+
end
|
69
|
+
|
70
|
+
def obtener_folio(xml, ns)
|
71
|
+
xml.xpath('//cfdi:Comprobante', ns).attribute('Folio').value rescue nil
|
72
|
+
end
|
73
|
+
|
74
|
+
def obtener_rfc_provedor_certificacion(xml, ns)
|
75
|
+
xml.xpath('//tfd:TimbreFiscalDigital', ns).attribute('RfcProvCertif').value rescue nil
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
data/lib/fm_adapter.rb
ADDED
@@ -0,0 +1,87 @@
|
|
1
|
+
require 'fm_adapter/version'
|
2
|
+
require 'fm_adapter/fm_cliente'
|
3
|
+
require 'fm_adapter/fm_informacion_cfdi'
|
4
|
+
require 'fm_adapter/fm_cfdi'
|
5
|
+
require 'nokogiri'
|
6
|
+
require 'base64'
|
7
|
+
|
8
|
+
module FmTimbradoCfdi
|
9
|
+
extend self
|
10
|
+
|
11
|
+
def configurar
|
12
|
+
yield cliente
|
13
|
+
end
|
14
|
+
|
15
|
+
def cliente
|
16
|
+
@cliente ||= FmCliente.new
|
17
|
+
end
|
18
|
+
|
19
|
+
# Public: Envía un archivo al PAC para ser timbrado en formato xml
|
20
|
+
#
|
21
|
+
# xml - Es el archivo XML sellado como un string
|
22
|
+
# generar_cbb - Es una bandera que indica si debe generarse el código cbb, por default es false
|
23
|
+
#
|
24
|
+
# Regresa un objeto tipo FMRespuesta que contiene el xml certificado, el timbre y la representación en pdf o el cbb en png
|
25
|
+
def timbra_cfdi_xml (xml, generar_cbb = false)
|
26
|
+
factura_xml = Nokogiri::XML(xml)
|
27
|
+
#procesar rfc del emisor
|
28
|
+
emisor = factura_xml.xpath('//cfdi:Emisor')
|
29
|
+
rfc = emisor[0]['rfc']
|
30
|
+
cliente.timbrar rfc, factura_xml.to_s, 'generarCBB' => generar_cbb
|
31
|
+
end
|
32
|
+
|
33
|
+
# Public: Envía un archivo al PAC para ser timbrado en formato layout
|
34
|
+
#
|
35
|
+
# rfc - Es el RFC del emisor
|
36
|
+
# layout - Es el archivo layout a ser timbrado como un string
|
37
|
+
# generar_cbb - Es una bandera que indica si debe generarse el código cbb, por default es false
|
38
|
+
#
|
39
|
+
# Regresa un objeto tipo FMRespuesta que contiene el xml certificado, el timbre y la representación en pdf o el cbb en png
|
40
|
+
def timbra_cfdi_layout (rfc, layout, generar_cbb = false)
|
41
|
+
cliente.timbrar rfc, layout, 'generarCBB' => generar_cbb
|
42
|
+
end
|
43
|
+
|
44
|
+
# Public: Envía un archivo al PAC para ser timbrado tanto en formato layout como en formato XML
|
45
|
+
#
|
46
|
+
# rfc - Es el RFC del emisor
|
47
|
+
# archivo - Es el archivo a ser timbrado como un string
|
48
|
+
# opciones - Es un hash de opciones que deben coincidir con los parámetros recibidos por Facturación Moderna para el timbrado
|
49
|
+
#
|
50
|
+
# Regresa un objeto tipo FMRespuesta que contiene el xml certificado, el timbre y la representación en pdf o el cbb en png
|
51
|
+
def timbrar (rfc, archivo, opciones= {})
|
52
|
+
cliente.timbrar rfc, archivo, opciones
|
53
|
+
end
|
54
|
+
|
55
|
+
# Public: Envía CSD para que lo almacene el PAC
|
56
|
+
#
|
57
|
+
# rfc - Es el RFC del emisor
|
58
|
+
# certificado - El contenido del archivo de certificado
|
59
|
+
# llave - La llave privada del certificado
|
60
|
+
# password - Contraseña de la llave privada
|
61
|
+
#
|
62
|
+
# Regresa un objeto de tipo FmRespuestaCancelacion
|
63
|
+
def activar_cancelacion(rfc, certificado, llave, password)
|
64
|
+
cliente.subir_certificado rfc, certificado, llave, password
|
65
|
+
end
|
66
|
+
alias :subir_certificado :activar_cancelacion
|
67
|
+
|
68
|
+
# Public: Envía una petición de cancelación de factura
|
69
|
+
#
|
70
|
+
# rfc - Es el RFC del emisor
|
71
|
+
# uuid - Es el identificador de la factura a cancelar
|
72
|
+
#
|
73
|
+
# Regresa una respuesta SOAP
|
74
|
+
#
|
75
|
+
# en opciones se debe enviar:
|
76
|
+
# { 'Motivo' => op1,
|
77
|
+
# 'FolioSustitucion' => Folio Fiscal del comprobante que lo sustituye (solo si Motivo es 01) }
|
78
|
+
#
|
79
|
+
# Motivo puede ser:
|
80
|
+
# 01 - Comprobantes emitidos con errores con relación
|
81
|
+
# 02 - Comprobantes emitidos con errores sin relación
|
82
|
+
# 03 - No se llevó a cabo la operación
|
83
|
+
# 04 - Operación nominativa relacionada en una factura global
|
84
|
+
def cancelar(rfc, uuid, opciones)
|
85
|
+
cliente.cancelar(rfc, uuid, opciones)
|
86
|
+
end
|
87
|
+
end
|
metadata
ADDED
@@ -0,0 +1,100 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: fm_adapter
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.1
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Gabriel Edera
|
8
|
+
- Facundo Condori
|
9
|
+
- Franco Sanchez
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2022-04-18 00:00:00.000000000 Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: nokogiri
|
17
|
+
requirement: !ruby/object:Gem::Requirement
|
18
|
+
requirements:
|
19
|
+
- - "~>"
|
20
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: 1.10.9
|
22
|
+
type: :runtime
|
23
|
+
prerelease: false
|
24
|
+
version_requirements: !ruby/object:Gem::Requirement
|
25
|
+
requirements:
|
26
|
+
- - "~>"
|
27
|
+
- !ruby/object:Gem::Version
|
28
|
+
version: 1.10.9
|
29
|
+
- !ruby/object:Gem::Dependency
|
30
|
+
name: savon
|
31
|
+
requirement: !ruby/object:Gem::Requirement
|
32
|
+
requirements:
|
33
|
+
- - "~>"
|
34
|
+
- !ruby/object:Gem::Version
|
35
|
+
version: 2.12.0
|
36
|
+
type: :runtime
|
37
|
+
prerelease: false
|
38
|
+
version_requirements: !ruby/object:Gem::Requirement
|
39
|
+
requirements:
|
40
|
+
- - "~>"
|
41
|
+
- !ruby/object:Gem::Version
|
42
|
+
version: 2.12.0
|
43
|
+
- !ruby/object:Gem::Dependency
|
44
|
+
name: rspec
|
45
|
+
requirement: !ruby/object:Gem::Requirement
|
46
|
+
requirements:
|
47
|
+
- - ">="
|
48
|
+
- !ruby/object:Gem::Version
|
49
|
+
version: '0'
|
50
|
+
type: :development
|
51
|
+
prerelease: false
|
52
|
+
version_requirements: !ruby/object:Gem::Requirement
|
53
|
+
requirements:
|
54
|
+
- - ">="
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '0'
|
57
|
+
description: 'Implementación en Ruby de la Conexión con el Servicio de Timbrado de
|
58
|
+
CFDI con el PAC: Facturación Moderna'
|
59
|
+
email:
|
60
|
+
- gab.edera@gmail.com
|
61
|
+
executables: []
|
62
|
+
extensions: []
|
63
|
+
extra_rdoc_files: []
|
64
|
+
files:
|
65
|
+
- README.md
|
66
|
+
- Rakefile
|
67
|
+
- fm_adapter.gemspec
|
68
|
+
- lib/fm_adapter.rb
|
69
|
+
- lib/fm_adapter/fm_cfdi.rb
|
70
|
+
- lib/fm_adapter/fm_cfdi_parser.rb
|
71
|
+
- lib/fm_adapter/fm_cliente.rb
|
72
|
+
- lib/fm_adapter/fm_informacion_cfdi.rb
|
73
|
+
- lib/fm_adapter/fm_respuesta.rb
|
74
|
+
- lib/fm_adapter/fm_respuesta_cancelacion.rb
|
75
|
+
- lib/fm_adapter/fm_timbre.rb
|
76
|
+
- lib/fm_adapter/version.rb
|
77
|
+
homepage: https://github.com/gedera/fm_adapter
|
78
|
+
licenses: []
|
79
|
+
metadata: {}
|
80
|
+
post_install_message:
|
81
|
+
rdoc_options: []
|
82
|
+
require_paths:
|
83
|
+
- lib
|
84
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
85
|
+
requirements:
|
86
|
+
- - ">="
|
87
|
+
- !ruby/object:Gem::Version
|
88
|
+
version: '0'
|
89
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
90
|
+
requirements:
|
91
|
+
- - ">="
|
92
|
+
- !ruby/object:Gem::Version
|
93
|
+
version: '0'
|
94
|
+
requirements: []
|
95
|
+
rubygems_version: 3.3.11
|
96
|
+
signing_key:
|
97
|
+
specification_version: 4
|
98
|
+
summary: Implementación en ruby de la conexión con el servicio de timbrado de cfdi
|
99
|
+
con el PAC Facturación Moderna
|
100
|
+
test_files: []
|