Class: VerifactuRails::Transporte
- Inherits:
-
Object
- Object
- VerifactuRails::Transporte
- Defined in:
- lib/verifactu_rails/transporte.rb
Overview
Cliente HTTP con autenticación mutua TLS contra el servicio VERI*FACTU.
Sin Savon: el servicio es un único endpoint con un sobre SOAP fijo, así que Net::HTTP de la stdlib sobra. Nos ahorra httpi, wasabi, gyoku, akami y nori.
Constant Summary collapse
- ENDPOINTS =
Preproducción y producción se corresponden uno a uno: prewww1 <-> www1, prewww2 <-> www2, prewww10 <-> www10 (este último, el de certificado de sello). Fuente: portal de pruebas externas de la AEAT.
Aviso operativo del propio portal: preproducción es para pruebas puntuales, NO para pruebas masivas ni para validaciones integradas en procesos de producción. Un uso que consideren abusivo puede acabar en bloqueo.
{ [:pruebas, false] => 'https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP', [:pruebas, true] => 'https://prewww10.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP', [:produccion, false] => 'https://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP', [:produccion, true] => 'https://www10.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP' }.freeze
- SOAP_NS =
'http://schemas.xmlsoap.org/soap/envelope/'- DECLARACION_XML =
Una declaración solo puede ir al principio del documento, y Envio#to_xml emite la suya. Incrustarla tal cual dentro del Body producía un sobre MAL FORMADO con dos declaraciones, que la AEAT contestó con "Codigo.Error interno en el servidor": el fallo era de parseo, no de validación, así que el mensaje no orientaba en absoluto.
/\A\s*<\?xml[^>]*\?>\s*/
Instance Attribute Summary collapse
-
#certificado ⇒ Object
readonly
Returns the value of attribute certificado.
-
#entorno ⇒ Object
readonly
Returns the value of attribute entorno.
-
#url ⇒ Object
readonly
Returns the value of attribute url.
Instance Method Summary collapse
- #cliente ⇒ Object
-
#enviar(xml_registro) ⇒ Object
Envía el XML del registro ya construido.
- #envolver(xml_registro) ⇒ Object
-
#initialize(certificado:, entorno: :pruebas, sello: nil, url: nil, ca_file: nil, timeout: 30) ⇒ Transporte
constructor
A new instance of Transporte.
- #sello? ⇒ Boolean
Constructor Details
#initialize(certificado:, entorno: :pruebas, sello: nil, url: nil, ca_file: nil, timeout: 30) ⇒ Transporte
Returns a new instance of Transporte.
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 |
# File 'lib/verifactu_rails/transporte.rb', line 39 def initialize(certificado:, entorno: :pruebas, sello: nil, url: nil, ca_file: nil, timeout: 30) unless %i[pruebas produccion].include?(entorno) raise ValidacionError, "Entorno inválido: #{entorno.inspect} (usa :pruebas o :produccion)" end @certificado = Formato.objeto(certificado, 'certificado', Certificado) @entorno = entorno # Sin normalizar, un sello: 'S' llegaba a ENDPOINTS.fetch([entorno, 'S']) y # daba un KeyError, justo al lado de la comprobación de entorno que sí da # un error del dominio. @sello = sello.nil? ? @certificado.sello? : Formato.si_no(sello, 'sello') == 'S' @url = url || ENDPOINTS.fetch([entorno, @sello]) @ca_file = ca_file @timeout = timeout end |
Instance Attribute Details
#certificado ⇒ Object (readonly)
Returns the value of attribute certificado.
35 36 37 |
# File 'lib/verifactu_rails/transporte.rb', line 35 def certificado @certificado end |
#entorno ⇒ Object (readonly)
Returns the value of attribute entorno.
35 36 37 |
# File 'lib/verifactu_rails/transporte.rb', line 35 def entorno @entorno end |
#url ⇒ Object (readonly)
Returns the value of attribute url.
35 36 37 |
# File 'lib/verifactu_rails/transporte.rb', line 35 def url @url end |
Instance Method Details
#cliente ⇒ Object
94 95 96 97 98 99 100 101 102 103 104 105 106 107 |
# File 'lib/verifactu_rails/transporte.rb', line 94 def cliente http = Net::HTTP.new(uri.host, uri.port) http.use_ssl = true http.cert = certificado.certificado http.key = certificado.clave http.extra_chain_cert = certificado.cadena if certificado.cadena.any? # NUNCA VERIFY_NONE. Si falla la verificación, el arreglo es aportar la # cadena de la CA correcta en ca_file, no desactivar la comprobación. http.verify_mode = OpenSSL::SSL::VERIFY_PEER http.ca_file = @ca_file if @ca_file http.open_timeout = @timeout http.read_timeout = @timeout http end |
#enviar(xml_registro) ⇒ Object
Envía el XML del registro ya construido. Devuelve la respuesta cruda: el parseo es responsabilidad de la capa superior.
60 61 62 63 64 65 66 67 68 69 70 71 72 |
# File 'lib/verifactu_rails/transporte.rb', line 60 def enviar(xml_registro) peticion = Net::HTTP::Post.new(uri) peticion['Content-Type'] = 'text/xml; charset=utf-8' peticion['SOAPAction'] = '""' peticion.body = envolver(xml_registro) respuesta = cliente.request(peticion) { codigo: respuesta.code.to_i, cuerpo: respuesta.body } rescue OpenSSL::SSL::SSLError => e raise TransporteError, mensaje_ssl(e) rescue Net::OpenTimeout, Net::ReadTimeout => e raise TransporteError, "Timeout contra #{uri.host}: #{e.class}" end |
#envolver(xml_registro) ⇒ Object
81 82 83 84 85 86 87 88 89 90 91 92 |
# File 'lib/verifactu_rails/transporte.rb', line 81 def envolver(xml_registro) cuerpo = xml_registro.to_s.sub(DECLARACION_XML, '') # document/literal según el WSDL: el Body lleva directamente el elemento, # sin envoltorio de operación. <<~XML <?xml version="1.0" encoding="UTF-8"?> <soapenv:Envelope xmlns:soapenv="#{SOAP_NS}"> <soapenv:Header/> <soapenv:Body>#{cuerpo}</soapenv:Body> </soapenv:Envelope> XML end |
#sello? ⇒ Boolean
56 |
# File 'lib/verifactu_rails/transporte.rb', line 56 def sello? = @sello |