module Afipws class WSAA attr_reader :key, :cert, :service, :ta, :cuit, :client WSDL = { development: 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl', production: 'https://wsaa.afip.gov.ar/ws/services/LoginCms?wsdl', test: Root + '/spec/fixtures/wsaa.wsdl' } def initialize options = {} @env = (options[:env] || :test).to_sym @key = options[:key] @cert = options[:cert] @service = options[:service] || 'wsfe' @ttl = options[:ttl] || 2400 @cuit = options[:cuit] @client = Client.new Hash(options[:savon]).reverse_merge(wsdl: WSDL[@env]) @ta_path = File.join(Dir.pwd, 'tmp', "#{@cuit}-#{@env}-#{@service}-ta.dump") end def generar_tra service, ttl xml = Builder::XmlMarkup.new indent: 2 xml.instruct! xml.loginTicketRequest version: 1 do xml.header do xml.uniqueId Time.now.to_i xml.generationTime xsd_datetime Time.now - ttl xml.expirationTime xsd_datetime Time.now + ttl end xml.service service end end def firmar_tra tra, key, crt key = OpenSSL::PKey::RSA.new key crt = OpenSSL::X509::Certificate.new crt OpenSSL::PKCS7.sign crt, key, tra end def codificar_tra pkcs7 pkcs7.to_pem.lines.to_a[1..-2].join end def tra key, cert, service, ttl codificar_tra firmar_tra(generar_tra(service, ttl), key, cert) end def login response = @client.request :login_cms, in0: tra(@key, @cert, @service, @ttl) ta = Nokogiri::XML(Nokogiri::XML(response.to_xml).text) { token: ta.css('token').text, sign: ta.css('sign').text, generation_time: from_xsd_datetime(ta.css('generationTime').text), expiration_time: from_xsd_datetime(ta.css('expirationTime').text) } rescue Savon::SOAPFault => f raise WSError, f.message end def auth ta = obtener_y_cachear_ta {token: ta[:token], sign: ta[:sign]} end private # Previene el error 'El CEE ya posee un TA valido para el acceso al WSN solicitado' que se genera cuando se pide el token varias veces en poco tiempo def obtener_y_cachear_ta @ta ||= restore_ta if ta_expirado? @ta @ta = login persist_ta @ta end @ta end def ta_expirado? ta ta.nil? || ta[:expiration_time] <= Time.now end def xsd_datetime time time.strftime('%Y-%m-%dT%H:%M:%S%z').sub /(\d{2})(\d{2})$/, '\1:\2' end def from_xsd_datetime str Time.parse(str) rescue nil end def restore_ta Marshal.load(File.read(@ta_path)) if File.exist?(@ta_path) end def persist_ta ta FileUtils.mkdir_p(File.dirname(@ta_path)) File.open(@ta_path, 'wb') { |f| f.write(Marshal.dump(ta)) } end end end