From ed05a9d9c8f569ef5c2b20ed9dc22951a7915145 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Tue, 16 Sep 2014 21:40:47 -0300 Subject: [PATCH 01/35] Refactored style issues. --- Gemfile | 2 +- Guardfile | 2 +- autotest/discover.rb | 2 +- bin/bravo | 22 ++++--- bravo.gemspec | 10 ++-- examples/invoices.rb | 3 +- lib/bravo.rb | 12 ++-- lib/bravo/auth_data.rb | 23 ++++---- lib/bravo/bill.rb | 83 +++++++++++++------------- lib/bravo/constants.rb | 111 ++++++++++++++++++----------------- lib/bravo/core_ext/string.rb | 8 +-- lib/bravo/reference.rb | 3 +- spec/bravo/auth_data_spec.rb | 2 +- spec/bravo/bill_spec.rb | 52 ++++++++-------- spec/bravo/reference_spec.rb | 2 +- spec/bravo/wsaa_spec.rb | 2 +- spec/spec_helper.rb | 14 ++--- 17 files changed, 178 insertions(+), 175 deletions(-) diff --git a/Gemfile b/Gemfile index 8021c6c..cd26fbf 100644 --- a/Gemfile +++ b/Gemfile @@ -6,4 +6,4 @@ group :test do gem 'debugger', '~> 1.3.0' end -gemspec \ No newline at end of file +gemspec diff --git a/Guardfile b/Guardfile index 70b4a18..4a0ef87 100644 --- a/Guardfile +++ b/Guardfile @@ -2,4 +2,4 @@ guard 'rspec', cli: '--color --format nested' do watch(%r{^spec/.+_spec\.rb$}) watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } watch('spec/spec_helper.rb') { "spec" } -end \ No newline at end of file +end diff --git a/autotest/discover.rb b/autotest/discover.rb index 42aea33..87dbf24 100644 --- a/autotest/discover.rb +++ b/autotest/discover.rb @@ -1 +1 @@ -Autotest.add_discovery { "rspec2" } #added according to rspec2 book \ No newline at end of file +Autotest.add_discovery { "rspec2" } # added according to rspec2 book diff --git a/bin/bravo b/bin/bravo index fa439cf..696f5a1 100755 --- a/bin/bravo +++ b/bin/bravo @@ -10,11 +10,16 @@ module Bravo desc 'gencsr', 'Crea el Certificate Signature Request' method_option :bin, type: :string, required: true, desc: 'El path completo al binario de openssl' - method_option :pkey, type: :string, desc: 'Path a una clave privada preexistente. Si se omite, se crea una clave en --out' - method_option :sn, type: :string, required: true, desc: 'Nombre del servidor. Sin uso práctico, es requerido por AFIP' - method_option :cn, type: :string, required: true, desc: 'Nombre de la compañía. Sin uso práctico, es requerido por AFIP' - method_option :cuit, type: :numeric, required: true, desc: 'Número de CUIT sin guiones. Ejemplo: 20876543217' - method_option :out, type: :string, default: 'bravo-certs', desc: 'Directorio de destino para los archivos creados. Si se omite, se crea el directorio bravo-certs en pwd' + method_option :pkey, type: :string, + desc: 'Path a una clave privada preexistente. Si se omite, se crea una clave en --out' + method_option :sn, type: :string, required: true, + desc: 'Nombre del servidor. Sin uso práctico, es requerido por AFIP' + method_option :cn, type: :string, required: true, + desc: 'Nombre de la compañía. Sin uso práctico, es requerido por AFIP' + method_option :cuit, type: :numeric, required: true, + desc: 'Número de CUIT sin guiones. Ejemplo: 20876543217' + method_option :out, type: :string, default: 'bravo-certs', + desc: 'Directorio de destino para los archivos creados. Default: "./bravo-certs"' # Certificate Signature Request wrapper for bravo. # @@ -28,7 +33,7 @@ module Bravo cuit = options[:cuit] out = options[:out] - Dir.mkdir(out) unless File.exists?(out) + Dir.mkdir(out) unless File.exist?(out) out_path = "#{ Dir.pwd }/#{ out }/" @@ -45,9 +50,10 @@ module Bravo end protected + # Creates a new private key # - def create_pkey(bin,out_path) + def create_pkey(bin, out_path) say('Creando pkey', :cyan) `#{ bin } genrsa -out #{ out_path }pkey 1024` say('Hecho!\n\n', :green) @@ -56,4 +62,4 @@ module Bravo end Bravo.start -end \ No newline at end of file +end diff --git a/bravo.gemspec b/bravo.gemspec index ccf1963..d0e8a31 100644 --- a/bravo.gemspec +++ b/bravo.gemspec @@ -8,14 +8,14 @@ Gem::Specification.new do |gem| gem.version = Bravo::VERSION gem.authors = ["Leandro Marcucci"] gem.email = ["leanucci@gmail.com"] - gem.description = %q{Adaptador para el Web Service de Facturacion Electrónica de AFIP} - gem.summary = %q{Adaptador WSFE} + gem.description = 'Adaptador para el Web Service de Facturacion Electrónica de AFIP' + gem.summary = 'Adaptador WSFE' gem.homepage = "https://github.com/leanucci/bravo#readme" - gem.date = %q(2011-03-14) + gem.date = '2011-03-14' gem.files = `git ls-files`.split($/) - gem.files.reject! { |f| f.include? 'vcr' } - gem.executables = gem.files.grep(%r{^bin/}).map{ |f| File.basename(f) } + gem.files.reject! {|f| f.include? 'vcr' } + gem.executables = gem.files.grep(%r{^bin/}).map{|f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = ["lib", "bin"] diff --git a/examples/invoices.rb b/examples/invoices.rb index 5b2d110..d0a2cd8 100644 --- a/examples/invoices.rb +++ b/examples/invoices.rb @@ -14,7 +14,7 @@ # Let's issue a Factura for 1200 ARS to a Responsable Inscripto bill_a = Bravo::Bill.new(iva_condition: :responsable_inscripto, net: 1200, invoice_type: :invoice) -bill_a.document_number = '30710151543' +bill_a.document_number = '30710151543' bill_a.document_type = 'CUIT' bill_a.authorize @@ -33,4 +33,3 @@ puts "Authorization result = #{ bill_b.authorized? }" puts "Authorization response." pp bill_b.response - diff --git a/lib/bravo.rb b/lib/bravo.rb index d06d62b..06cc1c4 100644 --- a/lib/bravo.rb +++ b/lib/bravo.rb @@ -16,7 +16,6 @@ class NullOrInvalidAttribute < StandardError; end # class MissingCertificate < StandardError; end - # This class handles the logging options # class Logger < Struct.new(:log, :pretty_xml, :level) @@ -25,13 +24,13 @@ class Logger < Struct.new(:log, :pretty_xml, :level) def initialize(opts = {}) self.log = opts[:log] || false - self.pretty_xml = opts[:pretty_xml] || self.log + self.pretty_xml = opts[:pretty_xml] || log self.level = opts[:level] || :debug end # @return [Hash] returns a hash with the proper logging optios for Savon. def logger_options - { log: self.log, pretty_print_xml: self.pretty_xml, log_level: self.level } + { log: log, pretty_print_xml: pretty_xml, log_level: level } end end @@ -44,9 +43,8 @@ def logger_options extend self - attr_accessor :cuit, :sale_point, :default_documento, :pkey, :cert, - :default_concepto, :default_moneda, :own_iva_cond, - :openssl_bin + attr_accessor :cuit, :sale_point, :default_documento, :pkey, :cert, :default_concepto, :default_moneda, + :own_iva_cond, :openssl_bin class << self # Receiver of the logging configuration options. @@ -69,7 +67,7 @@ def logger_options end def own_iva_cond=(iva_cond_symbol) - if Bravo::BILL_TYPE.has_key?(iva_cond_symbol) + if Bravo::BILL_TYPE.key?(iva_cond_symbol) @own_iva_cond = iva_cond_symbol else raise(NullOrInvalidAttribute.new, "El valor de own_iva_cond: (#{ iva_cond_symbol }) es inválido.") diff --git a/lib/bravo/auth_data.rb b/lib/bravo/auth_data.rb index 754ac7a..770da70 100644 --- a/lib/bravo/auth_data.rb +++ b/lib/bravo/auth_data.rb @@ -13,17 +13,10 @@ class << self # to be configured as Bravo.pkey and Bravo.cert # def fetch - unless File.exists?(Bravo.pkey) - raise "Archivo de llave privada no encontrado en #{ Bravo.pkey }" - end - - unless File.exists?(Bravo.cert) - raise "Archivo certificado no encontrado en #{ Bravo.cert }" - end + raise "Archivo de llave privada no encontrado en #{ Bravo.pkey }" unless File.exist?(Bravo.pkey) + raise "Archivo certificado no encontrado en #{ Bravo.cert }" unless File.exist?(Bravo.cert) - unless File.exists?(todays_data_file_name) - Bravo::Wsaa.login - end + Bravo::Wsaa.login unless File.exist?(todays_data_file_name) YAML.load_file(todays_data_file_name).each do |k, v| Bravo.const_set(k.to_s.upcase, v) unless Bravo.const_defined?(k.to_s.upcase) @@ -35,14 +28,14 @@ def fetch # def auth_hash fetch unless Bravo.constants.include?(:TOKEN) && Bravo.constants.include?(:SIGN) - { 'Token' => Bravo::TOKEN, 'Sign' => Bravo::SIGN, 'Cuit' => Bravo.cuit } + { 'Token' => Bravo::TOKEN, 'Sign' => Bravo::SIGN, 'Cuit' => Bravo.cuit } end # Returns the right wsaa url for the specific environment # @return [String] # def wsaa_url - raise 'Environment not sent to either :test or :production' unless Bravo::URLS.keys.include? environment + check_environment! Bravo::URLS[environment][:wsaa] end @@ -50,7 +43,7 @@ def wsaa_url # @return [String] # def wsfe_url - raise 'Environment not sent to either :test or :production' unless Bravo::URLS.keys.include? environment + check_environment! Bravo::URLS[environment][:wsfe] end @@ -60,6 +53,10 @@ def wsfe_url def todays_data_file_name @todays_data_file ||= "/tmp/bravo_#{ Bravo.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml" end + + def check_environment! + raise 'Environment not set.' unless Bravo::URLS.keys.include? environment + end end end end diff --git a/lib/bravo/bill.rb b/lib/bravo/bill.rb index 9a8f0e2..63e0844 100644 --- a/lib/bravo/bill.rb +++ b/lib/bravo/bill.rb @@ -9,9 +9,8 @@ class Bill # attr_reader :client - attr_accessor :net, :document_number, :iva_condition, :document_type, :concept, - :currency, :due_date, :aliciva_id, :date_from, :date_to, :body, :response, - :invoice_type + attr_accessor :net, :document_number, :iva_condition, :document_type, :concept, :currency, :due_date, + :aliciva_id, :date_from, :date_to, :body, :response, :invoice_type def initialize(attrs = {}) opts = { wsdl: Bravo::AuthData.wsfe_url }.merge! Bravo.logger_options @@ -70,25 +69,7 @@ def authorize # @return [Hash] returns the request body as a hash # def setup_bill - today = Time.new.strftime('%Y%m%d') - - fecaereq = { 'FeCAEReq' => { - 'FeCabReq' => Bravo::Bill.header(bill_type), - 'FeDetReq' => { - 'FECAEDetRequest' => { - 'Concepto' => Bravo::CONCEPTOS[concept], - 'DocTipo' => Bravo::DOCUMENTOS[document_type], - 'CbteFch' => today, - 'ImpTotConc' => 0.00, - 'MonId' => Bravo::MONEDAS[currency][:codigo], - 'MonCotiz' => 1, - 'ImpOpEx' => 0.00, - 'ImpTrib' => 0.00, - 'Iva' => { - 'AlicIva' => { - 'Id' => applicable_iva_code, - 'BaseImp' => net.round(2), - 'Importe' => iva_sum } } } } } } + fecaereq = setup_request_structure detail = fecaereq['FeCAEReq']['FeDetReq']['FECAEDetRequest'] @@ -99,9 +80,9 @@ def setup_bill detail['CbteDesde'] = detail['CbteHasta'] = Bravo::Reference.next_bill_number(bill_type) unless concept == 0 - detail.merge!({ 'FchServDesde' => date_from || today, - 'FchServHasta' => date_to || today, - 'FchVtoPago' => due_date || today }) + detail.merge!('FchServDesde' => date_from || today, + 'FchServHasta' => date_to || today, + 'FchVtoPago' => due_date || today) end body.merge!(fecaereq) @@ -121,7 +102,7 @@ class << self # @return [Hash] # def header(bill_type) - # todo sacado de la factura + # toodo sacado de la factura { 'CantReg' => '1', 'CbteTipo' => bill_type, 'PtoVta' => Bravo.sale_point } end end @@ -131,7 +112,6 @@ def header(bill_type) # def setup_response(response) # TODO: turn this into an all-purpose Response class - result = response[:fecae_solicitar_response][:fecae_solicitar_result] response_header = result[:fe_cab_resp] @@ -144,22 +124,24 @@ def setup_response(response) request_detail.merge!(iva) - response_hash = { :header_result => response_header.delete(:resultado), - :authorized_on => response_header.delete(:fch_proceso), - :detail_result => response_detail.delete(:resultado), - :cae_due_date => response_detail.delete(:cae_fch_vto), - :cae => response_detail.delete(:cae), - :iva_id => request_detail.delete(:id), - :iva_importe => request_detail.delete(:importe), - :moneda => request_detail.delete(:mon_id), - :cotizacion => request_detail.delete(:mon_cotiz), - :iva_base_imp => request_detail.delete(:base_imp), - :doc_num => request_detail.delete(:doc_nro) - }.merge!(request_header).merge!(request_detail) + response_hash = { header_result: response_header.delete(:resultado), + authorized_on: response_header.delete(:fch_proceso), + + detail_result: response_detail.delete(:resultado), + cae_due_date: response_detail.delete(:cae_fch_vto), + cae: response_detail.delete(:cae), - keys, values = response_hash.to_a.transpose + iva_id: request_detail.delete(:id), + iva_importe: request_detail.delete(:importe), + moneda: request_detail.delete(:mon_id), + cotizacion: request_detail.delete(:mon_cotiz), + iva_base_imp: request_detail.delete(:base_imp), + doc_num: request_detail.delete(:doc_nro) + }.merge!(request_header).merge!(request_detail) - self.response = (defined?(Struct::Response) ? Struct::Response : Struct.new('Response', *keys)).new(*values) + keys, values = response_hash.to_a.transpose + + self.response = Struct.new('Response', *keys).new(*values) end def applicable_iva @@ -181,7 +163,7 @@ def validate_iva_condition(iva_cond) iva_cond else raise(NullOrInvalidAttribute.new, - "El valor de iva_condition debe estar incluído en #{ valid_conditions }") + "El valor de iva_condition debe estar incluído en #{ valid_conditions }") end end @@ -193,5 +175,22 @@ def validate_invoice_type(type) #{ Bravo::BILL_TYPE_A.keys }") end end + + def setup_request_structure + { 'FeCAEReq' => + { 'FeCabReq' => Bravo::Bill.header(bill_type), + 'FeDetReq' => + { 'FECAEDetRequest' => + { 'Concepto' => Bravo::CONCEPTOS[concept], 'DocTipo' => Bravo::DOCUMENTOS[document_type], + 'CbteFch' => today, 'ImpTotConc' => 0.00, 'MonId' => Bravo::MONEDAS[currency][:codigo], + 'MonCotiz' => 1, 'ImpOpEx' => 0.00, 'ImpTrib' => 0.00, + 'Iva' => + { 'AlicIva' => { 'Id' => applicable_iva_code, 'BaseImp' => net.round(2), + 'Importe' => iva_sum } } } } } } + end + + def today + Time.new.strftime('%Y%m%d') + end end end diff --git a/lib/bravo/constants.rb b/lib/bravo/constants.rb index 310d5c7..67b8bd6 100644 --- a/lib/bravo/constants.rb +++ b/lib/bravo/constants.rb @@ -5,55 +5,56 @@ module Bravo # This constant contains the invoice types mappings between codes and names # used by WSFE. CBTE_TIPO = { - '01'=>'Factura A', - '02'=>'Nota de Débito A', - '03'=>'Nota de Crédito A', - '04'=>'Recibos A', - '05'=>'Notas de Venta al contado A', - '06'=>'Factura B', - '07'=>'Nota de Debito B', - '08'=>'Nota de Credito B', - '09'=>'Recibos B', - '10'=>'Notas de Venta al contado B', - '34'=>'Cbtes. A del Anexo I, Apartado A,inc.f),R.G.Nro. 1415', - '35'=>'Cbtes. B del Anexo I,Apartado A,inc. f),R.G. Nro. 1415', - '39'=>'Otros comprobantes A que cumplan con R.G.Nro. 1415', - '40'=>'Otros comprobantes B que cumplan con R.G.Nro. 1415', - '60'=>'Cta de Vta y Liquido prod. A', - '61'=>'Cta de Vta y Liquido prod. B', - '63'=>'Liquidacion A', - '64'=>'Liquidacion B' + '01' => 'Factura A', + '02' => 'Nota de Débito A', + '03' => 'Nota de Crédito A', + '04' => 'Recibos A', + '05' => 'Notas de Venta al contado A', + '06' => 'Factura B', + '07' => 'Nota de Debito B', + '08' => 'Nota de Credito B', + '09' => 'Recibos B', + '10' => 'Notas de Venta al contado B', + '34' => 'Cbtes. A del Anexo I, Apartado A,inc.f),R.G.Nro. 1415', + '35' => 'Cbtes. B del Anexo I,Apartado A,inc. f),R.G. Nro. 1415', + '39' => 'Otros comprobantes A que cumplan con R.G.Nro. 1415', + '40' => 'Otros comprobantes B que cumplan con R.G.Nro. 1415', + '60' => 'Cta de Vta y Liquido prod. A', + '61' => 'Cta de Vta y Liquido prod. B', + '63' => 'Liquidacion A', + '64' => 'Liquidacion B' } # Name to code mapping for Sale types. # - CONCEPTOS = { 'Productos'=>'01', 'Servicios'=>'02', 'Productos y Servicios'=>'03' } + CONCEPTOS = { 'Productos' => '01', 'Servicios' => '02', 'Productos y Servicios' => '03' } # Name to code mapping for types of documents. # DOCUMENTOS = { - 'CUIT'=>'80', - 'CUIL'=>'86', - 'CDI'=>'87', - 'LE'=>'89', - 'LC'=>'90', - 'CI Extranjera'=>'91', - 'en tramite'=>'92', - 'Acta Nacimiento'=>'93', - 'CI Bs. As. RNP'=>'95', - 'DNI'=>'96', - 'Pasaporte'=>'94', - 'Doc. (Otro)'=>'99' } + 'CUIT' => '80', + 'CUIL' => '86', + 'CDI' => '87', + 'LE' => '89', + 'LC' => '90', + 'CI Extranjera' => '91', + 'en tramite' => '92', + 'Acta Nacimiento' => '93', + 'CI Bs. As. RNP' => '95', + 'DNI' => '96', + 'Pasaporte' => '94', + 'Doc. (Otro)' => '99' + } # Currency code and names hash identified by a symbol # MONEDAS = { - :peso => { :codigo => 'PES', :nombre =>'Pesos Argentinos' }, - :dolar => { :codigo => 'DOL', :nombre =>'Dolar Estadounidense' }, - :real => { :codigo => '012', :nombre =>'Real' }, - :euro => { :codigo => '060', :nombre =>'Euro' }, - :oro => { :codigo => '049', :nombre =>'Gramos de Oro Fino' } } - + peso: { codigo: 'PES', nombre: 'Pesos Argentinos' }, + dolar: { codigo: 'DOL', nombre: 'Dolar Estadounidense' }, + real: { codigo: '012', nombre: 'Real' }, + euro: { codigo: '060', nombre: 'Euro' }, + oro: { codigo: '049', nombre: 'Gramos de Oro Fino' } + } # Tax percentage and codes according to each iva combination # @@ -62,7 +63,7 @@ module Bravo # Applicable tax according to buyer and seller's iva condition. # APPLICABLE_IVA = { - :responsable_inscripto => { + responsable_inscripto: { responsable_inscripto: 02, consumidor_final: 00, exento: 00, @@ -73,16 +74,18 @@ module Bravo # This hash keeps the codes for A document types by operation # BILL_TYPE_A = { - :invoice => '01', - :debit => '02', - :credit => '03' } + invoice: '01', + debit: '02', + credit: '03' + } # This hash keeps the codes for A document types by operation # BILL_TYPE_B = { - :invoice => '06', - :debit => '07', - :credit => '08' } + invoice: '06', + debit: '07', + credit: '08' + } # This hash keeps the different buyer and invoice type mapping corresponding to # the seller's iva condition and invoice kind. @@ -91,18 +94,20 @@ module Bravo # `BILL_TYPE[:responsable_inscripto][:responsable_inscripto][:invoice]` #=> '01' # BILL_TYPE = { - :responsable_inscripto => { - :responsable_inscripto => BILL_TYPE_A, - :consumidor_final => BILL_TYPE_B, - :exento => BILL_TYPE_B, - :responsable_monotributo => BILL_TYPE_B } } + responsable_inscripto: { + responsable_inscripto: BILL_TYPE_A, + consumidor_final: BILL_TYPE_B, + exento: BILL_TYPE_B, + responsable_monotributo: BILL_TYPE_B } + } # This hash keeps the set of urls for wsaa and wsfe for production and testing envs # URLS = { - :test => { :wsaa => 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms', - :wsfe => 'https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL' }, + test: { wsaa: 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms', + wsfe: 'https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL' }, - :production => { :wsaa => 'https://wsaa.afip.gov.ar/ws/services/LoginCms', - :wsfe => 'https://servicios1.afip.gov.ar/wsfev1/service.asmx' } } + production: { wsaa: 'https://wsaa.afip.gov.ar/ws/services/LoginCms', + wsfe: 'https://servicios1.afip.gov.ar/wsfev1/service.asmx' } + } end diff --git a/lib/bravo/core_ext/string.rb b/lib/bravo/core_ext/string.rb index 5bc8bbc..42ee333 100644 --- a/lib/bravo/core_ext/string.rb +++ b/lib/bravo/core_ext/string.rb @@ -4,12 +4,12 @@ class String # Stolen from activesupport/lib/active_support/inflector/methods.rb, line 48 # def underscore - word = self.to_s.dup + word = to_s.dup word.gsub!(/::/, '/') - word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2') - word.gsub!(/([a-z\d])([A-Z])/,'\1_\2') + word.gsub!(/([A-Z]+)([A-Z][a-z])/, '\1_\2') + word.gsub!(/([a-z\d])([A-Z])/, '\1_\2') word.tr!('-', '_') word.downcase! word end -end \ No newline at end of file +end diff --git a/lib/bravo/reference.rb b/lib/bravo/reference.rb index 346cc41..f5b575a 100644 --- a/lib/bravo/reference.rb +++ b/lib/bravo/reference.rb @@ -9,7 +9,8 @@ def self.next_bill_number(cbte_type) set_client resp = @client.call(:fe_comp_ultimo_autorizado) do |soap| # soap.namespaces['xmlns'] = 'http://ar.gov.afip.dif.FEV1/' - soap.message 'Auth' => Bravo::AuthData.auth_hash, 'PtoVta' => Bravo.sale_point, 'CbteTipo' => cbte_type + soap.message 'Auth' => Bravo::AuthData.auth_hash, 'PtoVta' => Bravo.sale_point, + 'CbteTipo' => cbte_type end resp.to_hash[:fe_comp_ultimo_autorizado_response][:fe_comp_ultimo_autorizado_result][:cbte_nro].to_i + 1 diff --git a/spec/bravo/auth_data_spec.rb b/spec/bravo/auth_data_spec.rb index 3ba4784..1d281e7 100644 --- a/spec/bravo/auth_data_spec.rb +++ b/spec/bravo/auth_data_spec.rb @@ -10,4 +10,4 @@ Bravo.constants.should include(:TOKEN, :SIGN) end end -end \ No newline at end of file +end diff --git a/spec/bravo/bill_spec.rb b/spec/bravo/bill_spec.rb index 058637a..6523dd7 100644 --- a/spec/bravo/bill_spec.rb +++ b/spec/bravo/bill_spec.rb @@ -7,22 +7,22 @@ describe '.header' do it 'sets up the header hash' do @header = Bravo::Bill.header(0) - @header.size.should == 3 - ['CantReg', 'CbteTipo', 'PtoVta'].each do |key| - @header.has_key?(key).should == true + @header.size.should eql 3 + %w[CantReg CbteTipo PtoVta].each do |key| + @header.key?(key).should == true end end end describe '.initialize' do it 'applies Bravos defaults' do - bill.client.class.name.should == 'Savon::Client' + bill.client.class.name.should eql 'Savon::Client' - ['Token', 'Sign', 'Cuit'].each do |key| - bill.body['Auth'][key].should_not == nil + %w[Token Sign Cuit].each do |key| + bill.body['Auth'][key].should_not eql nil end - bill.document_type.should == Bravo.default_documento + bill.document_type.should eql Bravo.default_documento bill.currency.should == Bravo.default_moneda end end @@ -63,14 +63,14 @@ bill.concept = 'Servicios' end - it 'uses today dates when due and service dates are ommitted', vcr: { cassette_name: 'setup_bill_ommitted_date' } do + it 'uses today dates when due and service dates are null', vcr: { cassette_name: 'setup_bill_ommitted_date' } do bill.setup_bill detail = bill.body['FeCAEReq']['FeDetReq']['FECAEDetRequest'] - detail['FchServDesde'].should == Time.new.strftime('%Y%m%d') - detail['FchServHasta'].should == Time.new.strftime('%Y%m%d') - detail['FchVtoPago'].should == Time.new.strftime('%Y%m%d') + detail['FchServDesde'].should eql Time.new.strftime('%Y%m%d') + detail['FchServHasta'].should eql Time.new.strftime('%Y%m%d') + detail['FchVtoPago'].should eql Time.new.strftime('%Y%m%d') end it 'uses given due and service dates', vcr: { cassette_name: 'setup_bill_given_date' } do @@ -82,34 +82,34 @@ detail = bill.body['FeCAEReq']['FeDetReq']['FECAEDetRequest'] - detail['FchServDesde'].should == '20111101' - detail['FchServHasta'].should == '20111130' - detail['FchVtoPago'].should == '20111210' + detail['FchServDesde'].should eql '20111101' + detail['FchServHasta'].should eql '20111130' + detail['FchVtoPago'].should eql '20111210' end end describe '#authorize' do describe 'for facturas' do Bravo::BILL_TYPE[Bravo.own_iva_cond].keys.each do |target_iva_cond| - describe "issued to #{ target_iva_cond.to_s }" do + describe "issued to #{ target_iva_cond }" do Bravo::BILL_TYPE[Bravo.own_iva_cond][target_iva_cond].keys.each do |bill_type| - vcr_options = { cassette_name: "#{ target_iva_cond.to_s }_and_#{ bill_type }" } + vcr_options = { cassette_name: "#{ target_iva_cond }_and_#{ bill_type }" } it "authorizes bill type #{ bill_type }", vcr: vcr_options do - bill.net = 10000.88 - bill.aliciva_id = 2 - bill.document_number = '30710151543' - bill.iva_condition = target_iva_cond - bill.concept = 'Servicios' + bill.net = 10_000.88 + bill.aliciva_id = 2 + bill.document_number = '30710151543' + bill.iva_condition = target_iva_cond + bill.concept = 'Servicios' bill.invoice_type = bill_type - bill.authorized?.should == false - bill.authorize.should == true - bill.authorized?.should == true + bill.authorized?.should eql false + bill.authorize.should eql true + bill.authorized?.should eql true response = bill.response - response.length.should == 28 - response.cae.length.should == 14 + response.length.should eql 28 + response.cae.length.should eql 14 end end end diff --git a/spec/bravo/reference_spec.rb b/spec/bravo/reference_spec.rb index 7942975..2e0833b 100644 --- a/spec/bravo/reference_spec.rb +++ b/spec/bravo/reference_spec.rb @@ -9,4 +9,4 @@ # # # end -end \ No newline at end of file +end diff --git a/spec/bravo/wsaa_spec.rb b/spec/bravo/wsaa_spec.rb index ffb97a9..bbd0824 100644 --- a/spec/bravo/wsaa_spec.rb +++ b/spec/bravo/wsaa_spec.rb @@ -4,7 +4,7 @@ before do @now = (Time.now) - 120 @from = @now.strftime('%FT%T%:z') - @to = (@now + ((12*60*60))).strftime('%FT%T%:z') + @to = (@now + ((12 * 60 * 60))).strftime('%FT%T%:z') @id = @now.strftime('%s') @tra = <<-EOF diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index cd50de1..e3299fa 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -1,4 +1,4 @@ -$:.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) +$LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib')) require 'bravo' require 'rspec' require 'vcr' @@ -8,6 +8,7 @@ begin require 'debugger' rescue LoadError + puts 'debugger not found' end VCR.configure do |c| @@ -18,7 +19,7 @@ RSpec.configure do |config| config.treat_symbols_as_metadata_keys_with_true_values = true - config.filter_run :focus => true + config.filter_run focus: true config.run_all_when_everything_filtered = true end @@ -35,12 +36,9 @@ Bravo::AuthData.environment = :test # TODO: refactor into actual validations -unless Bravo.cuit - raise(Bravo::NullOrInvalidAttribute.new, 'Please set CUIT env variable.') -end + +raise(Bravo::NullOrInvalidAttribute.new, 'Please set CUIT env variable.') unless Bravo.cuit [Bravo.pkey, Bravo.cert].each do |file| - unless File.exists?("#{ file }") - raise(Bravo::MissingCertificate.new, "No existe #{ file }") - end + raise(Bravo::MissingCertificate.new, "No existe #{ file }") unless File.exist?("#{ file }") end From a84fc2c75a13a55251d917eab365629a34945633 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Tue, 16 Sep 2014 21:49:12 -0300 Subject: [PATCH 02/35] Do not test 1.9.3. Test 2.1.2. --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 8c01670..5236101 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,7 +1,7 @@ language: ruby rvm: - - 1.9.3 - 2.0.0 + - 2.1.2 bundler_args: --without test branches: only: From f7782d66a72faa855a5fc7c8e3e7c9a57434cbf8 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Mon, 20 Oct 2014 15:06:49 -0300 Subject: [PATCH 03/35] Minor fixes to README. --- README.md | 48 +++++++++++++++++++++++++++--------------------- 1 file changed, 27 insertions(+), 21 deletions(-) diff --git a/README.md b/README.md index 821bf25..034ce24 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -# Bravo +# Bravo ![Travis status](https://travis-ci.org/leanucci/bravo.png) [![Gem Version](https://badge.fury.io/rb/bravo.png)](http://badge.fury.io/rb/bravo) [![Code Climate](https://codeclimate.com/repos/5292a01e89af7e473304513a/badges/4a29fbaff3d74a23e634/gpa.png)](https://codeclimate.com/repos/5292a01e89af7e473304513a/feed) @@ -53,20 +53,23 @@ Bravo no asume valores por defecto, por lo cual hay que configurar de forma expl Ejemplo de configuración tomado del spec_helper de Bravo: +```ruby - require 'bravo' +require 'bravo' - Bravo.pkey = 'spec/fixtures/certs/pkey' - Bravo.cert = 'spec/fixtures/certs/cert.crt' - Bravo.cuit = '20287740027' - Bravo.sale_point = '0002' - Bravo.default_concepto = 'Productos y Servicios' - Bravo.default_documento = 'CUIT' - Bravo.default_moneda = :peso - Bravo.own_iva_cond = :responsable_inscripto - Bravo.verbose = 'true' - Bravo.openssl_bin = '/usr/local/Cellar/openssl/1.0.1e/bin/openssl' - Bravo::AuthData.environment = :test +Bravo.pkey = 'spec/fixtures/certs/pkey' +Bravo.cert = 'spec/fixtures/certs/cert.crt' +Bravo.cuit = '20287740027' +Bravo.sale_point = '0002' +Bravo.default_concepto = 'Productos y Servicios' +Bravo.default_documento = 'CUIT' +Bravo.default_moneda = :peso +Bravo.own_iva_cond = :responsable_inscripto +Bravo.verbose = 'true' +Bravo.openssl_bin = '/usr/local/Cellar/openssl/1.0.1e/bin/openssl' +Bravo::AuthData.environment = :test + +``` ### Emisión de comprobantes @@ -88,18 +91,21 @@ Luego de configurar Bravo, autorizamos una factura: Código de ejemplo para la configuración anterior: +```ruby + +bill = Bravo::Bill.new - factura = Bravo::Bill.new +bill.net = 100.00 # el neto de la factura, total para Consumidor final +bill.aliciva_id = 2 # define la alicuota de iva a utilizar, ver archivo constants. +bill.iva_cond = :consumidor_final # la condición ante el iva del comprador +bill.concepto = 'Servicios' # concepto de la factura +bill.invoice_type = :invoice # el tipo de comprobante a emitir, en este caso factura. - factura.net = 100.00 # el neto de la factura, total para Consumidor final - factura.aliciva_id = 2 # define la alicuota de iva a utilizar, ver archivo constants. - factura.iva_cond = :consumidor_final # la condición ante el iva del comprador - factura.concepto = 'Servicios' # concepto de la factura - factura.invoice_type = :invoice # el tipo de comprobante a emitir, en este caso factura. +bill.authorize - bill.authorize +bill.response.cae # contiene el cae para este comprobante. - bill.response.cae # contiene el cae para este comprobante. +``` ## TODO list From cb88c57b2aa91faa58bf0a3b538ba6b1722bdc88 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Tue, 21 Oct 2014 12:53:25 -0300 Subject: [PATCH 04/35] Updated specs. --- README.md | 10 +++++----- spec/bravo/bill_spec.rb | 43 +++++++++++++++++++++-------------------- spec/spec_helper.rb | 2 +- 3 files changed, 28 insertions(+), 27 deletions(-) diff --git a/README.md b/README.md index 034ce24..ec295e0 100644 --- a/README.md +++ b/README.md @@ -95,15 +95,15 @@ Código de ejemplo para la configuración anterior: bill = Bravo::Bill.new -bill.net = 100.00 # el neto de la factura, total para Consumidor final -bill.aliciva_id = 2 # define la alicuota de iva a utilizar, ver archivo constants. +bill.net = 100.00 # el neto de la factura, total para Consumidor final +bill.aliciva_id = 2010 # define la alicuota de iva a utilizar, ver archivo constants. bill.iva_cond = :consumidor_final # la condición ante el iva del comprador -bill.concepto = 'Servicios' # concepto de la factura -bill.invoice_type = :invoice # el tipo de comprobante a emitir, en este caso factura. +bill.concepto = 'Servicios' # concepto de la factura +bill.invoice_type = :invoice # el tipo de comprobante a emitir, en este caso factura. bill.authorize -bill.response.cae # contiene el cae para este comprobante. +bill.response.cae # contiene el cae para este comprobante. ``` diff --git a/spec/bravo/bill_spec.rb b/spec/bravo/bill_spec.rb index 6523dd7..8fb6df7 100644 --- a/spec/bravo/bill_spec.rb +++ b/spec/bravo/bill_spec.rb @@ -7,23 +7,23 @@ describe '.header' do it 'sets up the header hash' do @header = Bravo::Bill.header(0) - @header.size.should eql 3 + expect(@header.size).to be 3 %w[CantReg CbteTipo PtoVta].each do |key| - @header.key?(key).should == true + expect(@header.key?(key)).to be_true end end end describe '.initialize' do it 'applies Bravos defaults' do - bill.client.class.name.should eql 'Savon::Client' + expect(bill.client).to be_a Savon::Client %w[Token Sign Cuit].each do |key| - bill.body['Auth'][key].should_not eql nil + expect(bill.body['Auth'].fetch(key, nil)).not_to be_nil end - bill.document_type.should eql Bravo.default_documento - bill.currency.should == Bravo.default_moneda + expect(bill.document_type).to be Bravo.default_documento + expect(bill.currency).to be Bravo.default_moneda end end @@ -32,13 +32,13 @@ it 'returns the bill type for Responsable Inscripto' do bill.iva_condition = :responsable_inscripto - bill.bill_type.should == '01' + expect(bill.bill_type).to eq '01' end it 'returns the bill type for Consumidor Final' do bill.iva_condition = :consumidor_final - bill.bill_type.should == '06' + expect(bill.bill_type).to eq '06' end end @@ -49,8 +49,8 @@ bill.net = 100.89 bill.aliciva_id = 2 - bill.iva_sum.should be_within(0.005).of(21.19) - bill.total.should be_within(0.005).of(122.08) + expect(bill.iva_sum).to be_within(0.005).of(21.19) + expect(bill.total).to be_within(0.005).of(122.08) end end @@ -68,9 +68,9 @@ detail = bill.body['FeCAEReq']['FeDetReq']['FECAEDetRequest'] - detail['FchServDesde'].should eql Time.new.strftime('%Y%m%d') - detail['FchServHasta'].should eql Time.new.strftime('%Y%m%d') - detail['FchVtoPago'].should eql Time.new.strftime('%Y%m%d') + expect(detail['FchServDesde']).to eq Time.new.strftime('%Y%m%d') + expect(detail['FchServHasta']).to eq Time.new.strftime('%Y%m%d') + expect(detail['FchVtoPago']).to eq Time.new.strftime('%Y%m%d') end it 'uses given due and service dates', vcr: { cassette_name: 'setup_bill_given_date' } do @@ -82,9 +82,9 @@ detail = bill.body['FeCAEReq']['FeDetReq']['FECAEDetRequest'] - detail['FchServDesde'].should eql '20111101' - detail['FchServHasta'].should eql '20111130' - detail['FchVtoPago'].should eql '20111210' + expect(detail['FchServDesde']).to eq '20111101' + expect(detail['FchServHasta']).to eq '20111130' + expect(detail['FchVtoPago']).to eq '20111210' end end @@ -102,14 +102,15 @@ bill.concept = 'Servicios' bill.invoice_type = bill_type - bill.authorized?.should eql false - bill.authorize.should eql true - bill.authorized?.should eql true + expect(bill.authorized?).to be_false + + expect(bill.authorize).to be_true + expect(bill.authorized?).to be_true response = bill.response - response.length.should eql 28 - response.cae.length.should eql 14 + expect(response.length).to eql 28 + expect(response.cae.length).to eql 14 end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index e3299fa..01f9333 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,7 +32,7 @@ Bravo.default_moneda = :peso Bravo.own_iva_cond = :responsable_inscripto # Bravo.logger = { log: true, level: :critical } -Bravo.openssl_bin = ENV["TRAVIS"] ? 'openssl' : '/usr/local/Cellar/openssl/1.0.1e/bin/openssl' +Bravo.openssl_bin = 'openssl' Bravo::AuthData.environment = :test # TODO: refactor into actual validations From cb08856114d7f4e07094e3fe261e0586685cabb8 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Tue, 21 Oct 2014 15:24:33 -0300 Subject: [PATCH 05/35] Rubocop. --- .rubocop.yml | 163 ++++++++++++++++++++++++++++++++++++++++ .travis.yml | 3 + bin/bravo | 3 +- bravo.gemspec | 25 +++--- lib/bravo/bill.rb | 6 +- lib/bravo/wsaa.rb | 35 ++++----- spec/bravo/bill_spec.rb | 3 +- spec/spec_helper.rb | 4 +- 8 files changed, 205 insertions(+), 37 deletions(-) create mode 100644 .rubocop.yml diff --git a/.rubocop.yml b/.rubocop.yml new file mode 100644 index 0000000..e67404f --- /dev/null +++ b/.rubocop.yml @@ -0,0 +1,163 @@ +--- +AllCops: + Exclude: + - 'doc/' + - 'coverage/' + - 'pkg/' + - 'tmp/' + +Metrics/CyclomaticComplexity: + Severity: error + Max: 8 + +Metrics/LineLength: + Max: 110 + Severity: error + +Metrics/ClassLength: + Max: 150 + Severity: error + +Metrics/MethodLength: + Max: 15 + Severity: error + +Metrics/ParameterLists: + Max: 5 + Severity: error + +Metrics/PerceivedComplexity: + Max: 10 + Severity: error + +Lint/EndAlignment: + AlignWith: variable + +Lint/UselessAssignment: + Severity: error + +Lint/ShadowingOuterLocalVariable: + Severity: convention + +Style/CaseEquality: + Enabled: false + +Style/Documentation: + Enabled: false + Severity: error + +Style/IfUnlessModifier: + MaxLineLength: 80 + +Style/GuardClause: + MinBodyLength: 3 + +Style/Lambda: + Enabled: false + +Style/EmptyLinesAroundBody: + Enabled: false + +Style/EmptyLineBetweenDefs: + AllowAdjacentOneLineDefs: true + +Style/ClassAndModuleChildren: + Enabled: false + +Style/AndOr: + EnforcedStyle: conditionals + +# This one is usefull for class attr_* +Style/TrivialAccessors: + AllowDSLWriters: true + +Style/AlignParameters: + EnforcedStyle: with_fixed_indentation + +Style/AlignHash: + EnforcedHashRocketStyle: key + EnforcedLastArgumentHashStyle: always_ignore + +Style/ModuleFunction: + Enabled: false + +Style/RegexpLiteral: + # The maximum number of (escaped) slashes that a slash-delimited regexp is + # allowed to have. If there are more slashes, a %r regexp shall be used. + MaxSlashes: 0 + +Style/FormatString: + Enabled: false + +Style/SingleLineBlockParams: + Enabled: false + +Style/CaseIndentation: + IndentWhenRelativeTo: end + +Style/PredicateName: + Severity: error + +Style/IndentHash: + EnforcedStyle: consistent + +Style/MultilineBlockChain: + Severity: error + +Lint/AssignmentInCondition: + Enabled: false + +Style/Alias: + Enabled: false + +Style/StringLiterals: + Enabled: false + +Style/SpaceInsideBlockBraces: + SpaceBeforeBlockParameters: true + +Style/PercentLiteralDelimiters: + PreferredDelimiters: + '%i': '[]' + '%w': '[]' + '%W': '[]' + '%': '{}' + +Style/CollectionMethods: + PreferredMethods: + reduce: inject + collect: map + collect!: 'map!' + detect: find + detect!: 'find!' + find_all: select + find_all!: 'select!' + +Style/DoubleNegation: + Severity: error + +Style/SignalException: + EnforcedStyle: only_raise + +Style/DotPosition: + EnforcedStyle: trailing + +Style/SingleLineMethods: + AllowIfMethodIsEmpty: true + +# It doesnt handle cases when we want to align multiple methods call into a table like +Style/SingleSpaceBeforeFirstArg: + Enabled: false + +# replaces $1 x Regexp.last_match[1] +Style/PerlBackrefs: + Enabled: false + +# There are valid cases (eg. Date.parse(date) rescue nil) +Style/RescueModifier: + Enabled: false + +Style/Blocks: + Severity: error + Exclude: + - '**/spec/**/*_spec.rb' diff --git a/.travis.yml b/.travis.yml index 5236101..e810ca2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -6,3 +6,6 @@ bundler_args: --without test branches: only: - master +script: + - bundle exec rspec spec + - bundle exec rubocop diff --git a/bin/bravo b/bin/bravo index 696f5a1..6b807f4 100755 --- a/bin/bravo +++ b/bin/bravo @@ -2,12 +2,11 @@ # -*- encoding: utf-8 -*- require 'thor' - +# base module module Bravo # Bravo executable for certificate request generation # class Bravo < Thor - desc 'gencsr', 'Crea el Certificate Signature Request' method_option :bin, type: :string, required: true, desc: 'El path completo al binario de openssl' method_option :pkey, type: :string, diff --git a/bravo.gemspec b/bravo.gemspec index d0e8a31..54d4225 100644 --- a/bravo.gemspec +++ b/bravo.gemspec @@ -13,19 +13,20 @@ Gem::Specification.new do |gem| gem.homepage = "https://github.com/leanucci/bravo#readme" gem.date = '2011-03-14' - gem.files = `git ls-files`.split($/) - gem.files.reject! {|f| f.include? 'vcr' } - gem.executables = gem.files.grep(%r{^bin/}).map{|f| File.basename(f) } + gem.files = `git ls-files`.split($RS) + gem.files.reject! { |f| f.include? 'vcr' } + gem.executables = gem.files.grep(%r{^bin/}).map { |f| File.basename(f) } gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) - gem.require_paths = ["lib", "bin"] + gem.require_paths = %w[lib bin] - gem.add_runtime_dependency(%q, ["~> 2.3.0"]) - gem.add_runtime_dependency(%q, ["~> 0.17.0"]) + gem.add_runtime_dependency(%{savon}, ["~> 2.3.0"]) + gem.add_runtime_dependency(%{thor}, ["~> 0.17.0"]) - gem.add_development_dependency(%q, ["~> 2.14.0"]) - gem.add_development_dependency(%q, ["~> 2.14.0"]) - gem.add_development_dependency(%q, ["~> 10.0.0"]) - gem.add_development_dependency(%q, ["~> 2.4.0"]) - gem.add_development_dependency(%q, ["~> 0.7.0"]) - gem.add_development_dependency(%q, ["~> 1.3.0"]) + gem.add_development_dependency(%{rspec}, ["~> 2.14.0"]) + gem.add_development_dependency(%{rspec-mocks}, ["~> 2.14.0"]) + gem.add_development_dependency(%{rake}, ["~> 10.0.0"]) + gem.add_development_dependency(%{vcr}, ["~> 2.4.0"]) + gem.add_development_dependency(%{simplecov}, ["~> 0.7.0"]) + gem.add_development_dependency(%{fakeweb}, ["~> 1.3.0"]) + gem.add_development_dependency(%{rubocop}, ["~> 0.26.1"]) end diff --git a/lib/bravo/bill.rb b/lib/bravo/bill.rb index 63e0844..bebddc6 100644 --- a/lib/bravo/bill.rb +++ b/lib/bravo/bill.rb @@ -110,6 +110,7 @@ def header(bill_type) # Response parser. Only works for the authorize method # @return [Struct] a struct with key-value pairs with the response values # + # rubocop:disable Metrics/MethodLength def setup_response(response) # TODO: turn this into an all-purpose Response class result = response[:fecae_solicitar_response][:fecae_solicitar_result] @@ -120,9 +121,7 @@ def setup_response(response) request_header = body['FeCAEReq']['FeCabReq'].underscore_keys.symbolize_keys request_detail = body['FeCAEReq']['FeDetReq']['FECAEDetRequest'].underscore_keys.symbolize_keys - iva = request_detail.delete(:iva)['AlicIva'].underscore_keys.symbolize_keys - - request_detail.merge!(iva) + request_detail.merge!(request_detail.delete(:iva)['AlicIva'].underscore_keys.symbolize_keys) response_hash = { header_result: response_header.delete(:resultado), authorized_on: response_header.delete(:fch_proceso), @@ -143,6 +142,7 @@ def setup_response(response) self.response = Struct.new('Response', *keys).new(*values) end + # rubocop:enable Metrics/MethodLength def applicable_iva index = Bravo::APPLICABLE_IVA[Bravo.own_iva_cond][iva_condition] diff --git a/lib/bravo/wsaa.rb b/lib/bravo/wsaa.rb index ad56c50..a138392 100644 --- a/lib/bravo/wsaa.rb +++ b/lib/bravo/wsaa.rb @@ -16,15 +16,15 @@ def self.login write_yaml(auth) end - protected # Builds the xml for the 'Ticket de Requerimiento de Acceso' # @return [String] containing the request body # + # rubocop:disable Metrics/MethodLength def self.build_tra - @now = (Time.now) - 120 - @from = @now.strftime('%FT%T%:z') - @to = (@now + ((12*60*60))).strftime('%FT%T%:z') - @id = @now.strftime('%s') + now = (Time.now) - 120 + @from = now.strftime('%FT%T%:z') + @to = (now + ((12 * 60 * 60))).strftime('%FT%T%:z') + @id = now.strftime('%s') tra = <<-EOF @@ -36,24 +36,24 @@ def self.build_tra wsfe EOF - return tra + tra end - + # rubocop:enable Metrics/MethodLength # Builds the CMS # @return [String] cms # def self.build_cms(tra) - cms = `echo '#{ tra }' | - #{ Bravo.openssl_bin } cms -sign -in /dev/stdin -signer #{ Bravo.cert } -inkey #{ Bravo.pkey } -nodetach \ - -outform der | + `echo '#{ tra }' | + #{ Bravo.openssl_bin } cms -sign -in /dev/stdin -signer #{ Bravo.cert } -inkey #{ Bravo.pkey } \ + -nodetach -outform der | #{ Bravo.openssl_bin } base64 -e` - return cms end # Builds the CMS request to log in to the server # @return [String] the cms body # def self.build_request(cms) + # rubocop:disable Metrics/LineLength request = <<-XML @@ -66,8 +66,9 @@ def self.build_request(cms) XML - return request + request end + # rubocop:enable Metrics/LineLength # Calls the WSAA with the request built by build_request # @return [Array] with the token and signature @@ -76,10 +77,10 @@ def self.call_wsaa(req) response = `echo '#{ req }' | curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Bravo::AuthData.wsaa_url }` - response = CGI::unescapeHTML(response) - token = response.scan(/\(.+)\<\/token\>/).first.first - sign = response.scan(/\(.+)\<\/sign\>/).first.first - return [token, sign] + response = CGI::Util.unescape_html(response) + token = response.scan(%r{\(.+)\<\/token\>}).first.first + sign = response.scan(%r{\(.+)\<\/sign\>}).first.first + [token, sign] end # Writes the token and signature to a YAML file in the /tmp directory @@ -89,7 +90,7 @@ def self.write_yaml(certs) token: #{certs[0]} sign: #{certs[1]} YML - `echo '#{ yml }' > /tmp/bravo_#{ Bravo.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml` + `echo '#{ yml }' > /tmp/bravo_#{ Bravo.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml` end end diff --git a/spec/bravo/bill_spec.rb b/spec/bravo/bill_spec.rb index 8fb6df7..6f82e43 100644 --- a/spec/bravo/bill_spec.rb +++ b/spec/bravo/bill_spec.rb @@ -63,7 +63,8 @@ bill.concept = 'Servicios' end - it 'uses today dates when due and service dates are null', vcr: { cassette_name: 'setup_bill_ommitted_date' } do + it 'uses today dates when due and service dates are null', + vcr: { cassette_name: 'setup_bill_ommitted_date' } do bill.setup_bill detail = bill.body['FeCAEReq']['FeDetReq']['FECAEDetRequest'] diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 01f9333..bb0ead0 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -31,9 +31,9 @@ Bravo.default_documento = 'CUIT' Bravo.default_moneda = :peso Bravo.own_iva_cond = :responsable_inscripto -# Bravo.logger = { log: true, level: :critical } +Bravo.logger = { log: false, level: :debug } Bravo.openssl_bin = 'openssl' -Bravo::AuthData.environment = :test +Bravo::AuthData.environment = :test # TODO: refactor into actual validations From 7b58df213c899da96d761f3aa7ad5062bb0c7717 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Tue, 21 Oct 2014 15:44:34 -0300 Subject: [PATCH 06/35] Do the usual call. --- lib/bravo/wsaa.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bravo/wsaa.rb b/lib/bravo/wsaa.rb index a138392..cab6c79 100644 --- a/lib/bravo/wsaa.rb +++ b/lib/bravo/wsaa.rb @@ -77,7 +77,7 @@ def self.call_wsaa(req) response = `echo '#{ req }' | curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Bravo::AuthData.wsaa_url }` - response = CGI::Util.unescape_html(response) + response = CGI::unescapeHTML(response) token = response.scan(%r{\(.+)\<\/token\>}).first.first sign = response.scan(%r{\(.+)\<\/sign\>}).first.first [token, sign] From ee5cf279de7f2038838c74f67301b71f0a73a5b9 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Fri, 5 Dec 2014 16:19:24 -0300 Subject: [PATCH 07/35] Added missing ?WSDL to production url. --- lib/bravo/constants.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/bravo/constants.rb b/lib/bravo/constants.rb index 67b8bd6..9129ebf 100644 --- a/lib/bravo/constants.rb +++ b/lib/bravo/constants.rb @@ -79,7 +79,7 @@ module Bravo credit: '03' } - # This hash keeps the codes for A document types by operation + # This hash keeps the codes for B document types by operation # BILL_TYPE_B = { invoice: '06', @@ -108,6 +108,6 @@ module Bravo wsfe: 'https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL' }, production: { wsaa: 'https://wsaa.afip.gov.ar/ws/services/LoginCms', - wsfe: 'https://servicios1.afip.gov.ar/wsfev1/service.asmx' } + wsfe: 'https://servicios1.afip.gov.ar/wsfev1/service.asmx?WSDL' } } end From 95c40767491853258934b578af664f78b560d96e Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sat, 18 Apr 2015 19:46:20 -0300 Subject: [PATCH 08/35] Updated Thor. --- Gemfile | 2 +- bravo.gemspec | 2 +- spec/spec_helper.rb | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Gemfile b/Gemfile index cd26fbf..40c2e23 100644 --- a/Gemfile +++ b/Gemfile @@ -3,7 +3,7 @@ source 'http://rubygems.org' group :test do gem 'guard-rspec', '~> 2.4.0' gem 'rb-fsevent', '~> 0.9.1' - gem 'debugger', '~> 1.3.0' + gem 'byebug' end gemspec diff --git a/bravo.gemspec b/bravo.gemspec index 54d4225..87ebfca 100644 --- a/bravo.gemspec +++ b/bravo.gemspec @@ -20,7 +20,7 @@ Gem::Specification.new do |gem| gem.require_paths = %w[lib bin] gem.add_runtime_dependency(%{savon}, ["~> 2.3.0"]) - gem.add_runtime_dependency(%{thor}, ["~> 0.17.0"]) + gem.add_runtime_dependency(%{thor}, ["~> 0.19.0"]) gem.add_development_dependency(%{rspec}, ["~> 2.14.0"]) gem.add_development_dependency(%{rspec-mocks}, ["~> 2.14.0"]) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index bb0ead0..a38a935 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,7 +32,7 @@ Bravo.default_moneda = :peso Bravo.own_iva_cond = :responsable_inscripto Bravo.logger = { log: false, level: :debug } -Bravo.openssl_bin = 'openssl' +Bravo.openssl_bin = '/usr/local/Cellar/openssl/1.0.1j/bin/openssl' Bravo::AuthData.environment = :test # TODO: refactor into actual validations From ae24a44602602196da5493f6ce197b89a9188161 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sat, 18 Apr 2015 19:49:43 -0300 Subject: [PATCH 09/35] Updated savon. --- bravo.gemspec | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bravo.gemspec b/bravo.gemspec index 87ebfca..2c10664 100644 --- a/bravo.gemspec +++ b/bravo.gemspec @@ -19,7 +19,7 @@ Gem::Specification.new do |gem| gem.test_files = gem.files.grep(%r{^(test|spec|features)/}) gem.require_paths = %w[lib bin] - gem.add_runtime_dependency(%{savon}, ["~> 2.3.0"]) + gem.add_runtime_dependency(%{savon}, ["~> 2.9.0"]) gem.add_runtime_dependency(%{thor}, ["~> 0.19.0"]) gem.add_development_dependency(%{rspec}, ["~> 2.14.0"]) From f77298cbb1047935c6e6e0200aec249c9617105a Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Wed, 29 Apr 2015 11:36:46 -0300 Subject: [PATCH 10/35] Restore travis openssl config. --- spec/spec_helper.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index a38a935..960b93b 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -32,7 +32,7 @@ Bravo.default_moneda = :peso Bravo.own_iva_cond = :responsable_inscripto Bravo.logger = { log: false, level: :debug } -Bravo.openssl_bin = '/usr/local/Cellar/openssl/1.0.1j/bin/openssl' +Bravo.openssl_bin = ENV["TRAVIS"] ? 'openssl' : '/usr/local/Cellar/openssl/1.0.2a-1/bin/openssl' Bravo::AuthData.environment = :test # TODO: refactor into actual validations From e1c9b7ffe3b53c97e851b67acbb6fa71015d6e72 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sun, 17 May 2015 23:55:50 -0300 Subject: [PATCH 11/35] Added inspect method. --- lib/bravo/bill.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/lib/bravo/bill.rb b/lib/bravo/bill.rb index bebddc6..66988be 100644 --- a/lib/bravo/bill.rb +++ b/lib/bravo/bill.rb @@ -24,6 +24,23 @@ def initialize(attrs = {}) @invoice_type = validate_invoice_type(attrs[:invoice_type]) end + def inspect + %{#} + end + + def to_hash + { net: net, document_number: document_number, iva_condition: iva_condition, invoice_type: invoice_type, + document_type: document_type, concept: concept, currency: currency, due_date: due_date, + aliciva_id: aliciva_id, date_from: date_from, date_to: date_to, body: body } + end + + def to_yaml + to_hash.to_yaml + end + # Searches the corresponding invoice type according to the combination of # the seller's IVA condition and the buyer's IVA condition # @return [String] the document type string From 47d37077e0459b7703e58a668bb42944f5b1b1c0 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sun, 17 May 2015 23:56:09 -0300 Subject: [PATCH 12/35] Removed class name for response since it varies. --- lib/bravo/bill.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/bravo/bill.rb b/lib/bravo/bill.rb index 66988be..4371e48 100644 --- a/lib/bravo/bill.rb +++ b/lib/bravo/bill.rb @@ -157,7 +157,7 @@ def setup_response(response) keys, values = response_hash.to_a.transpose - self.response = Struct.new('Response', *keys).new(*values) + self.response = Struct.new(*keys).new(*values) end # rubocop:enable Metrics/MethodLength From 200d5e72b6694970247b04c20ba6eff8b25bc5d0 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Fri, 20 Feb 2015 00:34:37 -0300 Subject: [PATCH 13/35] Removed guard. --- Gemfile | 6 ------ Guardfile | 5 ----- 2 files changed, 11 deletions(-) delete mode 100644 Guardfile diff --git a/Gemfile b/Gemfile index 40c2e23..d65e2a6 100644 --- a/Gemfile +++ b/Gemfile @@ -1,9 +1,3 @@ source 'http://rubygems.org' -group :test do - gem 'guard-rspec', '~> 2.4.0' - gem 'rb-fsevent', '~> 0.9.1' - gem 'byebug' -end - gemspec diff --git a/Guardfile b/Guardfile deleted file mode 100644 index 4a0ef87..0000000 --- a/Guardfile +++ /dev/null @@ -1,5 +0,0 @@ -guard 'rspec', cli: '--color --format nested' do - watch(%r{^spec/.+_spec\.rb$}) - watch(%r{^lib/(.+)\.rb$}) { |m| "spec/lib/#{m[1]}_spec.rb" } - watch('spec/spec_helper.rb') { "spec" } -end From 89b331d9c6287f2ad485a9e6ebddb1ebf2c40b95 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Fri, 20 Feb 2015 00:34:55 -0300 Subject: [PATCH 14/35] Updated gems. --- bravo.gemspec | 13 ++++++------- spec/bravo/auth_data_spec.rb | 4 ++-- spec/bravo/bill_spec.rb | 8 ++++---- spec/bravo/wsaa_spec.rb | 9 +++++---- spec/spec_helper.rb | 5 ++--- 5 files changed, 19 insertions(+), 20 deletions(-) diff --git a/bravo.gemspec b/bravo.gemspec index 2c10664..a199a6f 100644 --- a/bravo.gemspec +++ b/bravo.gemspec @@ -22,11 +22,10 @@ Gem::Specification.new do |gem| gem.add_runtime_dependency(%{savon}, ["~> 2.9.0"]) gem.add_runtime_dependency(%{thor}, ["~> 0.19.0"]) - gem.add_development_dependency(%{rspec}, ["~> 2.14.0"]) - gem.add_development_dependency(%{rspec-mocks}, ["~> 2.14.0"]) - gem.add_development_dependency(%{rake}, ["~> 10.0.0"]) - gem.add_development_dependency(%{vcr}, ["~> 2.4.0"]) - gem.add_development_dependency(%{simplecov}, ["~> 0.7.0"]) - gem.add_development_dependency(%{fakeweb}, ["~> 1.3.0"]) - gem.add_development_dependency(%{rubocop}, ["~> 0.26.1"]) + gem.add_development_dependency(%{rspec}, ["~> 3.2.0"]) + gem.add_development_dependency(%{rake}, ["~> 10.4.0"]) + gem.add_development_dependency(%{vcr}, ["~> 2.9.0"]) + gem.add_development_dependency(%{simplecov}, ["~> 0.9.0"]) + gem.add_development_dependency(%{rubocop}, ["~> 0.29.0"]) + gem.add_development_dependency(%{webmock}, ["~> 1.18.0"]) end diff --git a/spec/bravo/auth_data_spec.rb b/spec/bravo/auth_data_spec.rb index 1d281e7..5526db0 100644 --- a/spec/bravo/auth_data_spec.rb +++ b/spec/bravo/auth_data_spec.rb @@ -3,11 +3,11 @@ describe 'AuthData' do describe '.fetch' do it 'creates constants for todays data' do - Bravo.constants.should_not include(:TOKEN, :SIGN) + expect(Bravo.constants).not_to include(:TOKEN, :SIGN) Bravo::AuthData.fetch - Bravo.constants.should include(:TOKEN, :SIGN) + expect(Bravo.constants).to include(:TOKEN, :SIGN) end end end diff --git a/spec/bravo/bill_spec.rb b/spec/bravo/bill_spec.rb index 6f82e43..c3f9746 100644 --- a/spec/bravo/bill_spec.rb +++ b/spec/bravo/bill_spec.rb @@ -9,7 +9,7 @@ @header = Bravo::Bill.header(0) expect(@header.size).to be 3 %w[CantReg CbteTipo PtoVta].each do |key| - expect(@header.key?(key)).to be_true + expect(@header.key?(key)).to be_truthy end end end @@ -103,10 +103,10 @@ bill.concept = 'Servicios' bill.invoice_type = bill_type - expect(bill.authorized?).to be_false + expect(bill.authorized?).to be_falsey - expect(bill.authorize).to be_true - expect(bill.authorized?).to be_true + expect(bill.authorize).to be_truthy + expect(bill.authorized?).to be_truthy response = bill.response diff --git a/spec/bravo/wsaa_spec.rb b/spec/bravo/wsaa_spec.rb index bbd0824..fc1d4c6 100644 --- a/spec/bravo/wsaa_spec.rb +++ b/spec/bravo/wsaa_spec.rb @@ -21,19 +21,20 @@ describe '.build_tra' do it 'sets the body for the ticket request' do - Bravo::Wsaa.build_tra.should == @tra + expect(Bravo::Wsaa.build_tra).to eq @tra end end describe '.build_cms' do - it 'returns the cms with the tra in it' do + xit 'returns the cms with the tra in it' do pending 'find a proper way to stub openssl' end end describe '.login' do - xit 'should work', vcr: { cassette_name: 'login' } do - Bravo::Wsaa.login.should be_true + it 'writes the auth file', vcr: { cassette_name: 'login' } do + expect(File).to receive(:write) + Bravo::Wsaa.login end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 960b93b..2c05153 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -13,12 +13,11 @@ VCR.configure do |c| c.cassette_library_dir = 'spec/fixtures/vcr_cassettes' - c.hook_into :fakeweb + c.hook_into :webmock c.configure_rspec_metadata! end RSpec.configure do |config| - config.treat_symbols_as_metadata_keys_with_true_values = true config.filter_run focus: true config.run_all_when_everything_filtered = true end @@ -31,7 +30,7 @@ Bravo.default_documento = 'CUIT' Bravo.default_moneda = :peso Bravo.own_iva_cond = :responsable_inscripto -Bravo.logger = { log: false, level: :debug } +Bravo.logger = { log: false, level: :info } Bravo.openssl_bin = ENV["TRAVIS"] ? 'openssl' : '/usr/local/Cellar/openssl/1.0.2a-1/bin/openssl' Bravo::AuthData.environment = :test From fd309cea6be6ce0200ed5047404c523f6f0db328 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Fri, 20 Feb 2015 00:48:02 -0300 Subject: [PATCH 15/35] Bravo auth filename refactored. --- lib/bravo.rb | 5 +++++ lib/bravo/wsaa.rb | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/bravo.rb b/lib/bravo.rb index 06cc1c4..57a506e 100644 --- a/lib/bravo.rb +++ b/lib/bravo.rb @@ -73,5 +73,10 @@ def own_iva_cond=(iva_cond_symbol) raise(NullOrInvalidAttribute.new, "El valor de own_iva_cond: (#{ iva_cond_symbol }) es inválido.") end end + + def auth_filename + "/tmp/bravo_#{ Bravo.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml" + end + end end diff --git a/lib/bravo/wsaa.rb b/lib/bravo/wsaa.rb index cab6c79..c7fad7c 100644 --- a/lib/bravo/wsaa.rb +++ b/lib/bravo/wsaa.rb @@ -90,7 +90,7 @@ def self.write_yaml(certs) token: #{certs[0]} sign: #{certs[1]} YML - `echo '#{ yml }' > /tmp/bravo_#{ Bravo.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml` + File.write(Bravo.auth_filename, yml) end end From 2d52e915813af5db07116d6199519510a982d2f1 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Fri, 20 Feb 2015 00:48:10 -0300 Subject: [PATCH 16/35] Bill cleanup. --- lib/bravo/bill.rb | 50 ++++++++++++++++++++++++----------------------- 1 file changed, 26 insertions(+), 24 deletions(-) diff --git a/lib/bravo/bill.rb b/lib/bravo/bill.rb index 4371e48..b8f5dca 100644 --- a/lib/bravo/bill.rb +++ b/lib/bravo/bill.rb @@ -4,18 +4,19 @@ module Bravo # Subsequent implementations will be added here (maybe). # class Bill - # Returns the Savon::Client instance in charge of the interactions with WSFE API. - # (built on init) + # Returns the Savon::Client instance in charge of the interactions + # with WSFE API (built on init) # attr_reader :client - attr_accessor :net, :document_number, :iva_condition, :document_type, :concept, :currency, :due_date, - :aliciva_id, :date_from, :date_to, :body, :response, :invoice_type + attr_accessor :net, :document_number, :iva_condition, :document_type, + :concept, :currency, :due_date, :aliciva_id, :date_from, :date_to, :body, + :response, :invoice_type def initialize(attrs = {}) - opts = { wsdl: Bravo::AuthData.wsfe_url }.merge! Bravo.logger_options + opts = { wsdl: AuthData.wsfe_url }.merge! Bravo.logger_options @client ||= Savon.client(opts) - @body = { 'Auth' => Bravo::AuthData.auth_hash } + @body = { 'Auth' => AuthData.auth_hash } @iva_condition = validate_iva_condition(attrs[:iva_condition]) @net = attrs[:net] || 0 @document_type = attrs[:document_type] || Bravo.default_documento @@ -46,7 +47,7 @@ def to_yaml # @return [String] the document type string # def bill_type - Bravo::BILL_TYPE[Bravo.own_iva_cond][iva_condition][invoice_type] + BILL_TYPE[Bravo.own_iva_cond][iva_condition][invoice_type] end # Calculates the total field for the invoice by adding @@ -94,7 +95,7 @@ def setup_bill detail['ImpNeto'] = net.to_f detail['ImpIVA'] = iva_sum detail['ImpTotal'] = total - detail['CbteDesde'] = detail['CbteHasta'] = Bravo::Reference.next_bill_number(bill_type) + detail['CbteDesde'] = detail['CbteHasta'] = Reference.next_bill_number(bill_type) unless concept == 0 detail.merge!('FchServDesde' => date_from || today, @@ -109,7 +110,7 @@ def setup_bill # @return [Boolean] the response result # def authorized? - !response.nil? && response.header_result == 'A' && response.detail_result == 'A' + response && response.header_result == 'A' && response.detail_result == 'A' end private @@ -120,7 +121,10 @@ class << self # def header(bill_type) # toodo sacado de la factura - { 'CantReg' => '1', 'CbteTipo' => bill_type, 'PtoVta' => Bravo.sale_point } + { 'CantReg' => '1', + 'CbteTipo' => bill_type, + 'PtoVta' => Bravo.sale_point + } end end @@ -130,13 +134,13 @@ def header(bill_type) # rubocop:disable Metrics/MethodLength def setup_response(response) # TODO: turn this into an all-purpose Response class - result = response[:fecae_solicitar_response][:fecae_solicitar_result] + result = response[:fecae_solicitar_response][:fecae_solicitar_result] response_header = result[:fe_cab_resp] response_detail = result[:fe_det_resp][:fecae_det_response] - request_header = body['FeCAEReq']['FeCabReq'].underscore_keys.symbolize_keys - request_detail = body['FeCAEReq']['FeDetReq']['FECAEDetRequest'].underscore_keys.symbolize_keys + request_header = body['FeCAEReq']['FeCabReq'].underscore_keys.symbolize_keys + request_detail = body['FeCAEReq']['FeDetReq']['FECAEDetRequest'].underscore_keys.symbolize_keys request_detail.merge!(request_detail.delete(:iva)['AlicIva'].underscore_keys.symbolize_keys) @@ -162,8 +166,8 @@ def setup_response(response) # rubocop:enable Metrics/MethodLength def applicable_iva - index = Bravo::APPLICABLE_IVA[Bravo.own_iva_cond][iva_condition] - Bravo::ALIC_IVA[index] + index = APPLICABLE_IVA[Bravo.own_iva_cond][iva_condition] + ALIC_IVA[index] end def applicable_iva_code @@ -175,7 +179,7 @@ def applicable_iva_multiplier end def validate_iva_condition(iva_cond) - valid_conditions = Bravo::BILL_TYPE[Bravo.own_iva_cond].keys + valid_conditions = BILL_TYPE[Bravo.own_iva_cond].keys if valid_conditions.include? iva_cond iva_cond else @@ -185,21 +189,19 @@ def validate_iva_condition(iva_cond) end def validate_invoice_type(type) - if Bravo::BILL_TYPE_A.keys.include? type - type - else - raise(NullOrInvalidAttribute.new, "invoice_type debe estar incluido en \ + return type if BILL_TYPE_A.keys.include? type + + raise(NullOrInvalidAttribute.new, "invoice_type debe estar incluido en \ #{ Bravo::BILL_TYPE_A.keys }") - end end def setup_request_structure { 'FeCAEReq' => - { 'FeCabReq' => Bravo::Bill.header(bill_type), + { 'FeCabReq' => Bill.header(bill_type), 'FeDetReq' => { 'FECAEDetRequest' => - { 'Concepto' => Bravo::CONCEPTOS[concept], 'DocTipo' => Bravo::DOCUMENTOS[document_type], - 'CbteFch' => today, 'ImpTotConc' => 0.00, 'MonId' => Bravo::MONEDAS[currency][:codigo], + { 'Concepto' => CONCEPTOS[concept], 'DocTipo' => DOCUMENTOS[document_type], + 'CbteFch' => today, 'ImpTotConc' => 0.00, 'MonId' => MONEDAS[currency][:codigo], 'MonCotiz' => 1, 'ImpOpEx' => 0.00, 'ImpTrib' => 0.00, 'Iva' => { 'AlicIva' => { 'Id' => applicable_iva_code, 'BaseImp' => net.round(2), From ffacb4fc3348494a763e33342660e21545054cbb Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Fri, 20 Feb 2015 02:09:38 -0300 Subject: [PATCH 17/35] Cleaned up bill generation. --- lib/bravo.rb | 13 +++++----- lib/bravo/bill.rb | 61 +++++++++++++++++--------------------------- lib/bravo/request.rb | 44 ++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+), 43 deletions(-) create mode 100644 lib/bravo/request.rb diff --git a/lib/bravo.rb b/lib/bravo.rb index 57a506e..b11790f 100644 --- a/lib/bravo.rb +++ b/lib/bravo.rb @@ -34,12 +34,13 @@ def logger_options end end - autoload :Authorizer, 'bravo/authorizer' - autoload :AuthData, 'bravo/auth_data' - autoload :Bill, 'bravo/bill' - autoload :Constants, 'bravo/constants' - autoload :Wsaa, 'bravo/wsaa' - autoload :Reference, 'bravo/reference' + autoload :Authorizer, 'bravo/authorizer' + autoload :AuthData, 'bravo/auth_data' + autoload :Bill, 'bravo/bill' + autoload :Constants, 'bravo/constants' + autoload :Wsaa, 'bravo/wsaa' + autoload :Reference, 'bravo/reference' + autoload :Request, 'bravo/request' extend self diff --git a/lib/bravo/bill.rb b/lib/bravo/bill.rb index b8f5dca..bd2af5a 100644 --- a/lib/bravo/bill.rb +++ b/lib/bravo/bill.rb @@ -18,10 +18,10 @@ def initialize(attrs = {}) @client ||= Savon.client(opts) @body = { 'Auth' => AuthData.auth_hash } @iva_condition = validate_iva_condition(attrs[:iva_condition]) - @net = attrs[:net] || 0 - @document_type = attrs[:document_type] || Bravo.default_documento - @currency = attrs[:currency] || Bravo.default_moneda - @concept = attrs[:concept] || Bravo.default_concepto + @net = attrs[:net].to_f.round(2) || 0 + @document_type = attrs.fetch(:document_type, Bravo.default_documento) + @currency = attrs.fetch(:currency, Bravo.default_moneda) + @concept = attrs.fetch(:concept, Bravo.default_concepto) @invoice_type = validate_invoice_type(attrs[:invoice_type]) end @@ -55,7 +55,7 @@ def bill_type # @return [Float] the sum of both fields, or 0 if the net is 0. # def total - @total = net.zero? ? 0 : net + iva_sum + @total = net.zero? ? 0.0 : net + iva_sum end # Calculates the corresponding iva sum. @@ -75,35 +75,35 @@ def iva_sum def authorize setup_bill response = client.call(:fecae_solicitar) do |soap| - # soap.namespaces['xmlns'] = 'http://ar.gov.afip.dif.FEV1/' soap.message body end setup_response(response.to_hash) - self.authorized? + authorized? end # Sets up the request body for the authorisation # @return [Hash] returns the request body as a hash # def setup_bill - fecaereq = setup_request_structure - - detail = fecaereq['FeCAEReq']['FeDetReq']['FECAEDetRequest'] - - detail['DocNro'] = document_number - detail['ImpNeto'] = net.to_f - detail['ImpIVA'] = iva_sum - detail['ImpTotal'] = total - detail['CbteDesde'] = detail['CbteHasta'] = Reference.next_bill_number(bill_type) - - unless concept == 0 - detail.merge!('FchServDesde' => date_from || today, - 'FchServHasta' => date_to || today, - 'FchVtoPago' => due_date || today) - end - - body.merge!(fecaereq) + request = Request.new + request.header = Bill.header(bill_type) + request.concept = CONCEPTOS[concept] + request.document_type = DOCUMENTOS[document_type] + request.date = today + request.currency_id = MONEDAS[currency][:codigo] + request.iva_code = applicable_iva_code + request.net_amount = net.to_f + request.iva_amount = iva_sum + request.document_number = document_number + request.total = total + request.from = request.to = Reference.next_bill_number(bill_type) + + request.date_from = date_from || today + request.date_to = date_to || today + request.due_on = due_date || today + + body.merge!(request.to_hash) end # Returns the result of the authorization operation @@ -195,19 +195,6 @@ def validate_invoice_type(type) #{ Bravo::BILL_TYPE_A.keys }") end - def setup_request_structure - { 'FeCAEReq' => - { 'FeCabReq' => Bill.header(bill_type), - 'FeDetReq' => - { 'FECAEDetRequest' => - { 'Concepto' => CONCEPTOS[concept], 'DocTipo' => DOCUMENTOS[document_type], - 'CbteFch' => today, 'ImpTotConc' => 0.00, 'MonId' => MONEDAS[currency][:codigo], - 'MonCotiz' => 1, 'ImpOpEx' => 0.00, 'ImpTrib' => 0.00, - 'Iva' => - { 'AlicIva' => { 'Id' => applicable_iva_code, 'BaseImp' => net.round(2), - 'Importe' => iva_sum } } } } } } - end - def today Time.new.strftime('%Y%m%d') end diff --git a/lib/bravo/request.rb b/lib/bravo/request.rb new file mode 100644 index 0000000..90509a3 --- /dev/null +++ b/lib/bravo/request.rb @@ -0,0 +1,44 @@ +module Bravo + class Request + attr_accessor :concept, :document_type, :date, :currency_id, :iva_code, + :net_amount, :iva_amount, :document_number, :total, :from, :to, + :date_from, :date_to, :due_on, :header + + def to_hash + { 'FeCAEReq' => { 'FeCabReq' => header, 'FeDetReq' => build_details } } + end + + def build_details + hsh = { + 'FECAEDetRequest' => { + 'Concepto' => concept, + 'DocTipo' => document_type, + 'CbteFch' => date, + 'ImpTotConc' => 0.00, + 'MonId' => currency_id, + 'MonCotiz' => 1, + 'ImpOpEx' => 0.00, + 'ImpTrib' => 0.00, + 'DocNro' => document_number, + 'ImpNeto' => net_amount, + 'ImpIVA' => iva_amount, + 'ImpTotal' => total, + 'CbteDesde' => from, + 'CbteHasta' => to, + 'FchServDesde' => date_from, + 'FchServHasta' => date_to, + 'FchVtoPago' => due_on, + + 'Iva' => { + 'AlicIva' => { + 'Id' => iva_code, + 'BaseImp' => net_amount, + 'Importe' => iva_amount + } + } + } + } + hsh.reject { |k, _v| hsh[k].nil? } + end + end +end From 1c0d7f81696a25fa79626d42cafac15979d85ef3 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Mon, 18 May 2015 00:30:18 -0300 Subject: [PATCH 18/35] Run with progress format in travis --- .travis.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index e810ca2..f7631bc 100644 --- a/.travis.yml +++ b/.travis.yml @@ -7,5 +7,5 @@ branches: only: - master script: - - bundle exec rspec spec + - bundle exec rspec spec --format=progress - bundle exec rubocop From 21f00e8c773d6a0ce3be775fac83b9d433e027a7 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Mon, 18 May 2015 00:31:02 -0300 Subject: [PATCH 19/35] Test all branches. Added ruby 2.2.0. --- .travis.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.travis.yml b/.travis.yml index f7631bc..4af132a 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,10 +2,8 @@ language: ruby rvm: - 2.0.0 - 2.1.2 + - 2.2.0 bundler_args: --without test -branches: - only: - - master script: - bundle exec rspec spec --format=progress - bundle exec rubocop From 373a34f9e8baca2e34daeefb216bfa61d848a34f Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Mon, 18 May 2015 00:31:15 -0300 Subject: [PATCH 20/35] Switched xit to pending in wsaa spec. --- spec/bravo/wsaa_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/bravo/wsaa_spec.rb b/spec/bravo/wsaa_spec.rb index fc1d4c6..ad3a967 100644 --- a/spec/bravo/wsaa_spec.rb +++ b/spec/bravo/wsaa_spec.rb @@ -26,8 +26,8 @@ end describe '.build_cms' do - xit 'returns the cms with the tra in it' do - pending 'find a proper way to stub openssl' + pending 'returns the cms with the tra in it' do + expect(false).to be_truthy end end From 3c9da68577872c7a5a87bea9248f0c9eff20dfb5 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Mon, 18 May 2015 01:12:53 -0300 Subject: [PATCH 21/35] Rubocop fixes --- .rubocop.yml | 22 ++++++---------------- bin/bravo | 4 +--- bravo.gemspec | 2 +- lib/bravo.rb | 6 ++---- lib/bravo/auth_data.rb | 3 --- lib/bravo/bill.rb | 21 +++++++++++---------- lib/bravo/wsaa.rb | 3 +-- 7 files changed, 22 insertions(+), 39 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index e67404f..5d2bf24 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -12,15 +12,15 @@ Metrics/CyclomaticComplexity: Metrics/LineLength: Max: 110 - Severity: error + Severity: refactor Metrics/ClassLength: Max: 150 - Severity: error + Severity: refactor Metrics/MethodLength: Max: 15 - Severity: error + Severity: refactor Metrics/ParameterLists: Max: 5 @@ -30,6 +30,9 @@ Metrics/PerceivedComplexity: Max: 10 Severity: error +Metrics/AbcSize: + Severity: refactor + Lint/EndAlignment: AlignWith: variable @@ -55,9 +58,6 @@ Style/GuardClause: Style/Lambda: Enabled: false -Style/EmptyLinesAroundBody: - Enabled: false - Style/EmptyLineBetweenDefs: AllowAdjacentOneLineDefs: true @@ -81,11 +81,6 @@ Style/AlignHash: Style/ModuleFunction: Enabled: false -Style/RegexpLiteral: - # The maximum number of (escaped) slashes that a slash-delimited regexp is - # allowed to have. If there are more slashes, a %r regexp shall be used. - MaxSlashes: 0 - Style/FormatString: Enabled: false @@ -156,8 +151,3 @@ Style/PerlBackrefs: # There are valid cases (eg. Date.parse(date) rescue nil) Style/RescueModifier: Enabled: false - -Style/Blocks: - Severity: error - Exclude: - - '**/spec/**/*_spec.rb' diff --git a/bin/bravo b/bin/bravo index 6b807f4..1e679ef 100755 --- a/bin/bravo +++ b/bin/bravo @@ -40,9 +40,7 @@ module Bravo say("Creando CSR en #{ out_path } con CUIT=#{ cuit }, o=#{ sn } y cn=#{ cn }", :cyan) - `#{bin} req -new \ - -key #{ pkey } \ - -subj "/C=AR/O=#{ sn }/CN=#{ cn }/serialNumber=CUIT #{ cuit }"\ + `#{bin} req -new -key #{ pkey } -subj "/C=AR/O=#{ sn }/CN=#{ cn }/serialNumber=CUIT #{ cuit }"\ -out #{ out_path }pedido-#{ cuit }` say('Hecho!', :green) diff --git a/bravo.gemspec b/bravo.gemspec index a199a6f..3023220 100644 --- a/bravo.gemspec +++ b/bravo.gemspec @@ -26,6 +26,6 @@ Gem::Specification.new do |gem| gem.add_development_dependency(%{rake}, ["~> 10.4.0"]) gem.add_development_dependency(%{vcr}, ["~> 2.9.0"]) gem.add_development_dependency(%{simplecov}, ["~> 0.9.0"]) - gem.add_development_dependency(%{rubocop}, ["~> 0.29.0"]) + gem.add_development_dependency(%{rubocop}, ["~> 0.31.0"]) gem.add_development_dependency(%{webmock}, ["~> 1.18.0"]) end diff --git a/lib/bravo.rb b/lib/bravo.rb index b11790f..cf7f8c2 100644 --- a/lib/bravo.rb +++ b/lib/bravo.rb @@ -7,17 +7,15 @@ require 'bravo/core_ext/string' module Bravo - # Exception Class for missing or invalid attributes # class NullOrInvalidAttribute < StandardError; end - # Exception Class for missing or invalid certifficate # class MissingCertificate < StandardError; end - # This class handles the logging options # + # rubocop:disable Style/StructInheritance class Logger < Struct.new(:log, :pretty_xml, :level) # @param opts [Hash] receives a hash with keys `log`, `pretty_xml` (both # boolean) or the desired log level as `level` @@ -33,6 +31,7 @@ def logger_options { log: log, pretty_print_xml: pretty_xml, log_level: level } end end + # rubocop:enable Style/StructInheritance autoload :Authorizer, 'bravo/authorizer' autoload :AuthData, 'bravo/auth_data' @@ -78,6 +77,5 @@ def own_iva_cond=(iva_cond_symbol) def auth_filename "/tmp/bravo_#{ Bravo.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml" end - end end diff --git a/lib/bravo/auth_data.rb b/lib/bravo/auth_data.rb index 770da70..1c24f20 100644 --- a/lib/bravo/auth_data.rb +++ b/lib/bravo/auth_data.rb @@ -1,11 +1,8 @@ module Bravo - # This class handles authorization data # class AuthData - class << self - attr_accessor :environment, :todays_data_file_name # Fetches WSAA Authorization Data to build the datafile for the day. diff --git a/lib/bravo/bill.rb b/lib/bravo/bill.rb index bd2af5a..fe07ec5 100644 --- a/lib/bravo/bill.rb +++ b/lib/bravo/bill.rb @@ -15,7 +15,7 @@ class Bill def initialize(attrs = {}) opts = { wsdl: AuthData.wsfe_url }.merge! Bravo.logger_options - @client ||= Savon.client(opts) + @client ||= Savon.client(opts) @body = { 'Auth' => AuthData.auth_hash } @iva_condition = validate_iva_condition(attrs[:iva_condition]) @net = attrs[:net].to_f.round(2) || 0 @@ -87,17 +87,18 @@ def authorize # def setup_bill request = Request.new - request.header = Bill.header(bill_type) - request.concept = CONCEPTOS[concept] + request.header = Bill.header(bill_type) + request.concept = CONCEPTOS[concept] request.document_type = DOCUMENTOS[document_type] - request.date = today - request.currency_id = MONEDAS[currency][:codigo] - request.iva_code = applicable_iva_code - request.net_amount = net.to_f - request.iva_amount = iva_sum - request.document_number = document_number - request.total = total + request.date = today + request.currency_id = MONEDAS[currency][:codigo] + request.iva_code = applicable_iva_code + request.net_amount = net.to_f + request.iva_amount = iva_sum + request.total = total + request.from = request.to = Reference.next_bill_number(bill_type) + request.document_number = document_number request.date_from = date_from || today request.date_to = date_to || today diff --git a/lib/bravo/wsaa.rb b/lib/bravo/wsaa.rb index c7fad7c..c89be22 100644 --- a/lib/bravo/wsaa.rb +++ b/lib/bravo/wsaa.rb @@ -77,7 +77,7 @@ def self.call_wsaa(req) response = `echo '#{ req }' | curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Bravo::AuthData.wsaa_url }` - response = CGI::unescapeHTML(response) + response = CGI.unescapeHTML(response) token = response.scan(%r{\(.+)\<\/token\>}).first.first sign = response.scan(%r{\(.+)\<\/sign\>}).first.first [token, sign] @@ -92,6 +92,5 @@ def self.write_yaml(certs) YML File.write(Bravo.auth_filename, yml) end - end end From 4e894872f9344099b2d5a0165c5f865ac1bfda5c Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Tue, 19 May 2015 01:45:49 -0300 Subject: [PATCH 22/35] Refactored AuthData --- CHANGELOG | 3 +++ Gemfile | 1 + lib/bravo.rb | 4 ---- lib/bravo/auth_data.rb | 39 ++++++++++++++++++++++++++++-------- lib/bravo/wsaa.rb | 18 ++++++++--------- spec/bravo/auth_data_spec.rb | 20 +++++++++++------- spec/bravo/wsaa_spec.rb | 2 +- spec/spec_helper.rb | 9 ++------- 8 files changed, 60 insertions(+), 36 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index a64d9a4..0ec1c13 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,6 @@ +*Bravo 1.0.0 (june 01, 2015)* +* Refactored auth data to expire after 12 hours + *Bravo 1.0.0.rc1 (November 25, 2013)* * Added full support for Savon logging options diff --git a/Gemfile b/Gemfile index d65e2a6..87a7951 100644 --- a/Gemfile +++ b/Gemfile @@ -1,3 +1,4 @@ source 'http://rubygems.org' +gem 'byebug', group: :test gemspec diff --git a/lib/bravo.rb b/lib/bravo.rb index cf7f8c2..ae07e97 100644 --- a/lib/bravo.rb +++ b/lib/bravo.rb @@ -73,9 +73,5 @@ def own_iva_cond=(iva_cond_symbol) raise(NullOrInvalidAttribute.new, "El valor de own_iva_cond: (#{ iva_cond_symbol }) es inválido.") end end - - def auth_filename - "/tmp/bravo_#{ Bravo.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml" - end end end diff --git a/lib/bravo/auth_data.rb b/lib/bravo/auth_data.rb index 1c24f20..e6fd423 100644 --- a/lib/bravo/auth_data.rb +++ b/lib/bravo/auth_data.rb @@ -9,22 +9,21 @@ class << self # It requires the private key file and the certificate to exist and # to be configured as Bravo.pkey and Bravo.cert # - def fetch + def create raise "Archivo de llave privada no encontrado en #{ Bravo.pkey }" unless File.exist?(Bravo.pkey) raise "Archivo certificado no encontrado en #{ Bravo.cert }" unless File.exist?(Bravo.cert) - Bravo::Wsaa.login unless File.exist?(todays_data_file_name) + Bravo::Wsaa.login(current_data_file) unless authorized_data? - YAML.load_file(todays_data_file_name).each do |k, v| - Bravo.const_set(k.to_s.upcase, v) unless Bravo.const_defined?(k.to_s.upcase) - end + Bravo.const_set(:TOKEN, credentials[:token]) + Bravo.const_set(:SIGN, credentials[:sign]) end # Returns the authorization hash, containing the Token, Signature and Cuit # @return [Hash] # def auth_hash - fetch unless Bravo.constants.include?(:TOKEN) && Bravo.constants.include?(:SIGN) + create unless Bravo.constants.include?(:TOKEN) && Bravo.constants.include?(:SIGN) { 'Token' => Bravo::TOKEN, 'Sign' => Bravo::SIGN, 'Cuit' => Bravo.cuit } end @@ -47,10 +46,34 @@ def wsfe_url # Creates the data file name for a cuit number and the current day # @return [String] # - def todays_data_file_name - @todays_data_file ||= "/tmp/bravo_#{ Bravo.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml" + def current_data_file + "/tmp/bravo_#{ Bravo.cuit }.yml" end + # Checks the auth file exists and contains valid current data + # @return [Boolean] + # + def currently_authorized? + File.exist?(current_data_file) && authorized_data? + end + + # Checks credentials are valid + # @return [Boolean] + # + def authorized_data? + DateTime.now < DateTime.parse(credentials[:expires_on]) + end + + # Reads current data file + # @return [Hash] + # + def credentials + YAML.load_file(current_data_file) + end + + # Validates Bravo.environment is set and valid + # @return [Boolean] + # def check_environment! raise 'Environment not set.' unless Bravo::URLS.keys.include? environment end diff --git a/lib/bravo/wsaa.rb b/lib/bravo/wsaa.rb index c89be22..8f657b4 100644 --- a/lib/bravo/wsaa.rb +++ b/lib/bravo/wsaa.rb @@ -7,13 +7,13 @@ class Wsaa # Main method for authentication and authorization. # When successful, produces the yaml file with auth data. # - def self.login + def self.login(filename) tra = build_tra cms = build_cms(tra) req = build_request(cms) auth = call_wsaa(req) - write_yaml(auth) + write_yaml(auth, filename) end # Builds the xml for the 'Ticket de Requerimiento de Acceso' @@ -78,19 +78,19 @@ def self.call_wsaa(req) curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Bravo::AuthData.wsaa_url }` response = CGI.unescapeHTML(response) + token = response.scan(%r{\(.+)\<\/token\>}).first.first sign = response.scan(%r{\(.+)\<\/sign\>}).first.first - [token, sign] + created_at = response.scan(%r{\(.+)\<\/generationTime\>}).first.first + expires_at = response.scan(%r{\(.+)\<\/expirationTime\>}).first.first + + { token: token, sign: sign, created_at: created_at, expires_at: expires_at } end # Writes the token and signature to a YAML file in the /tmp directory # - def self.write_yaml(certs) - yml = <<-YML -token: #{certs[0]} -sign: #{certs[1]} -YML - File.write(Bravo.auth_filename, yml) + def self.write_yaml(credentials, filename) + File.write(filename, credentials.to_yaml) end end end diff --git a/spec/bravo/auth_data_spec.rb b/spec/bravo/auth_data_spec.rb index 5526db0..ec98684 100644 --- a/spec/bravo/auth_data_spec.rb +++ b/spec/bravo/auth_data_spec.rb @@ -1,13 +1,19 @@ -require File.expand_path(File.dirname(__FILE__) + '/../spec_helper') +require 'spec_helper' -describe 'AuthData' do - describe '.fetch' do - it 'creates constants for todays data' do - expect(Bravo.constants).not_to include(:TOKEN, :SIGN) +module Bravo + describe AuthData do + context 'when new credentials are necessary' do + before do + allow(described_class).to receive(:authorized_data?).and_return(false) + end - Bravo::AuthData.fetch + it 'creates constants for todays data' do + expect(Bravo.constants).not_to include(:TOKEN, :SIGN) - expect(Bravo.constants).to include(:TOKEN, :SIGN) + Bravo::AuthData.create + + expect(Bravo.constants).to include(:TOKEN, :SIGN) + end end end end diff --git a/spec/bravo/wsaa_spec.rb b/spec/bravo/wsaa_spec.rb index ad3a967..95480aa 100644 --- a/spec/bravo/wsaa_spec.rb +++ b/spec/bravo/wsaa_spec.rb @@ -34,7 +34,7 @@ describe '.login' do it 'writes the auth file', vcr: { cassette_name: 'login' } do expect(File).to receive(:write) - Bravo::Wsaa.login + Bravo::Wsaa.login('/tmp/bravo_test.yml') end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 2c05153..0be0862 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -3,14 +3,9 @@ require 'rspec' require 'vcr' require 'simplecov' +require 'byebug' # SimpleCov.start -begin - require 'debugger' -rescue LoadError - puts 'debugger not found' -end - VCR.configure do |c| c.cassette_library_dir = 'spec/fixtures/vcr_cassettes' c.hook_into :webmock @@ -30,7 +25,7 @@ Bravo.default_documento = 'CUIT' Bravo.default_moneda = :peso Bravo.own_iva_cond = :responsable_inscripto -Bravo.logger = { log: false, level: :info } +Bravo.logger = { log: true, level: :debug } Bravo.openssl_bin = ENV["TRAVIS"] ? 'openssl' : '/usr/local/Cellar/openssl/1.0.2a-1/bin/openssl' Bravo::AuthData.environment = :test From ec804f703cd9575c90abe8851cd16012f3d2e1f7 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Tue, 19 May 2015 20:44:16 -0300 Subject: [PATCH 23/35] Rubocop refactor. --- .rubocop.yml | 30 +++--- lib/bravo/auth_data.rb | 20 ++-- lib/bravo/bill.rb | 20 +++- lib/bravo/request.rb | 4 + lib/bravo/wsaa.rb | 3 +- spec/bravo/auth_data_spec.rb | 7 +- spec/bravo/bill_spec.rb | 178 ++++++++++++++++++----------------- spec/spec_helper.rb | 2 +- 8 files changed, 143 insertions(+), 121 deletions(-) diff --git a/.rubocop.yml b/.rubocop.yml index 5d2bf24..02b58e9 100644 --- a/.rubocop.yml +++ b/.rubocop.yml @@ -1,13 +1,10 @@ --- AllCops: Exclude: - - 'doc/' - - 'coverage/' - - 'pkg/' - - 'tmp/' + - 'bin/*' Metrics/CyclomaticComplexity: - Severity: error + Severity: refactor Max: 8 Metrics/LineLength: @@ -24,13 +21,10 @@ Metrics/MethodLength: Metrics/ParameterLists: Max: 5 - Severity: error + Severity: refactor Metrics/PerceivedComplexity: Max: 10 - Severity: error - -Metrics/AbcSize: Severity: refactor Lint/EndAlignment: @@ -47,7 +41,7 @@ Style/CaseEquality: Style/Documentation: Enabled: false - Severity: error + Severity: refactor Style/IfUnlessModifier: MaxLineLength: 80 @@ -81,6 +75,11 @@ Style/AlignHash: Style/ModuleFunction: Enabled: false +Style/RegexpLiteral: + # The maximum number of (escaped) slashes that a slash-delimited regexp is + # allowed to have. If there are more slashes, a %r regexp shall be used. + MaxSlashes: 0 + Style/FormatString: Enabled: false @@ -91,13 +90,13 @@ Style/CaseIndentation: IndentWhenRelativeTo: end Style/PredicateName: - Severity: error + Severity: refactor Style/IndentHash: EnforcedStyle: consistent Style/MultilineBlockChain: - Severity: error + Severity: refactor Lint/AssignmentInCondition: Enabled: false @@ -129,7 +128,7 @@ Style/CollectionMethods: find_all!: 'select!' Style/DoubleNegation: - Severity: error + Severity: refactor Style/SignalException: EnforcedStyle: only_raise @@ -151,3 +150,8 @@ Style/PerlBackrefs: # There are valid cases (eg. Date.parse(date) rescue nil) Style/RescueModifier: Enabled: false + +Style/Blocks: + Severity: refactor + Exclude: + - '**/spec/**/*_spec.rb' diff --git a/lib/bravo/auth_data.rb b/lib/bravo/auth_data.rb index e6fd423..f26eed0 100644 --- a/lib/bravo/auth_data.rb +++ b/lib/bravo/auth_data.rb @@ -10,21 +10,16 @@ class << self # to be configured as Bravo.pkey and Bravo.cert # def create - raise "Archivo de llave privada no encontrado en #{ Bravo.pkey }" unless File.exist?(Bravo.pkey) - raise "Archivo certificado no encontrado en #{ Bravo.cert }" unless File.exist?(Bravo.cert) - - Bravo::Wsaa.login(current_data_file) unless authorized_data? - - Bravo.const_set(:TOKEN, credentials[:token]) - Bravo.const_set(:SIGN, credentials[:sign]) + validate_certificates + Wsaa.login(current_data_file) unless currently_authorized? end # Returns the authorization hash, containing the Token, Signature and Cuit # @return [Hash] # def auth_hash - create unless Bravo.constants.include?(:TOKEN) && Bravo.constants.include?(:SIGN) - { 'Token' => Bravo::TOKEN, 'Sign' => Bravo::SIGN, 'Cuit' => Bravo.cuit } + create + { 'Token' => credentials[:token], 'Sign' => credentials[:sign], 'Cuit' => Bravo.cuit } end # Returns the right wsaa url for the specific environment @@ -61,7 +56,7 @@ def currently_authorized? # @return [Boolean] # def authorized_data? - DateTime.now < DateTime.parse(credentials[:expires_on]) + DateTime.now < DateTime.parse(credentials[:expires_at]) end # Reads current data file @@ -77,6 +72,11 @@ def credentials def check_environment! raise 'Environment not set.' unless Bravo::URLS.keys.include? environment end + + def validate_certificates + raise "Archivo de llave privada no encontrado en #{ Bravo.pkey }" unless File.exist?(Bravo.pkey) + raise "Archivo certificado no encontrado en #{ Bravo.cert }" unless File.exist?(Bravo.cert) + end end end end diff --git a/lib/bravo/bill.rb b/lib/bravo/bill.rb index fe07ec5..08d91e5 100644 --- a/lib/bravo/bill.rb +++ b/lib/bravo/bill.rb @@ -10,20 +10,20 @@ class Bill attr_reader :client attr_accessor :net, :document_number, :iva_condition, :document_type, - :concept, :currency, :due_date, :aliciva_id, :date_from, :date_to, :body, + :concept, :currency, :due_date, :aliciva_id, :date_from, :date_to, :response, :invoice_type + # rubocop:disable Metrics/AbcSize def initialize(attrs = {}) - opts = { wsdl: AuthData.wsfe_url }.merge! Bravo.logger_options - @client ||= Savon.client(opts) - @body = { 'Auth' => AuthData.auth_hash } - @iva_condition = validate_iva_condition(attrs[:iva_condition]) + @client ||= Savon.client({ wsdl: AuthData.wsfe_url }.merge! Bravo.logger_options) @net = attrs[:net].to_f.round(2) || 0 @document_type = attrs.fetch(:document_type, Bravo.default_documento) @currency = attrs.fetch(:currency, Bravo.default_moneda) @concept = attrs.fetch(:concept, Bravo.default_concepto) + @iva_condition = validate_iva_condition(attrs[:iva_condition]) @invoice_type = validate_invoice_type(attrs[:invoice_type]) end + # rubocop:enable Metrics/AbcSize def inspect %{# AuthData.auth_hash } + end + # Searches the corresponding invoice type according to the combination of # the seller's IVA condition and the buyer's IVA condition # @return [String] the document type string @@ -85,6 +89,8 @@ def authorize # Sets up the request body for the authorisation # @return [Hash] returns the request body as a hash # + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength def setup_bill request = Request.new request.header = Bill.header(bill_type) @@ -106,6 +112,8 @@ def setup_bill body.merge!(request.to_hash) end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength # Returns the result of the authorization operation # @return [Boolean] the response result @@ -133,6 +141,7 @@ def header(bill_type) # @return [Struct] a struct with key-value pairs with the response values # # rubocop:disable Metrics/MethodLength + # rubocop:disable Metrics/AbcSize def setup_response(response) # TODO: turn this into an all-purpose Response class result = response[:fecae_solicitar_response][:fecae_solicitar_result] @@ -165,6 +174,7 @@ def setup_response(response) self.response = Struct.new(*keys).new(*values) end # rubocop:enable Metrics/MethodLength + # rubocop:enable Metrics/AbcSize def applicable_iva index = APPLICABLE_IVA[Bravo.own_iva_cond][iva_condition] diff --git a/lib/bravo/request.rb b/lib/bravo/request.rb index 90509a3..745303a 100644 --- a/lib/bravo/request.rb +++ b/lib/bravo/request.rb @@ -8,6 +8,8 @@ def to_hash { 'FeCAEReq' => { 'FeCabReq' => header, 'FeDetReq' => build_details } } end + # rubocop:disable Metrics/AbcSize + # rubocop:disable Metrics/MethodLength def build_details hsh = { 'FECAEDetRequest' => { @@ -40,5 +42,7 @@ def build_details } hsh.reject { |k, _v| hsh[k].nil? } end + # rubocop:enable Metrics/AbcSize + # rubocop:enable Metrics/MethodLength end end diff --git a/lib/bravo/wsaa.rb b/lib/bravo/wsaa.rb index 8f657b4..aa14878 100644 --- a/lib/bravo/wsaa.rb +++ b/lib/bravo/wsaa.rb @@ -73,12 +73,12 @@ def self.build_request(cms) # Calls the WSAA with the request built by build_request # @return [Array] with the token and signature # + # rubocop:disable Metrics/AbcSize def self.call_wsaa(req) response = `echo '#{ req }' | curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Bravo::AuthData.wsaa_url }` response = CGI.unescapeHTML(response) - token = response.scan(%r{\(.+)\<\/token\>}).first.first sign = response.scan(%r{\(.+)\<\/sign\>}).first.first created_at = response.scan(%r{\(.+)\<\/generationTime\>}).first.first @@ -86,6 +86,7 @@ def self.call_wsaa(req) { token: token, sign: sign, created_at: created_at, expires_at: expires_at } end + # rubocop:enable Metrics/AbcSize # Writes the token and signature to a YAML file in the /tmp directory # diff --git a/spec/bravo/auth_data_spec.rb b/spec/bravo/auth_data_spec.rb index ec98684..36eae39 100644 --- a/spec/bravo/auth_data_spec.rb +++ b/spec/bravo/auth_data_spec.rb @@ -4,15 +4,12 @@ module Bravo describe AuthData do context 'when new credentials are necessary' do before do - allow(described_class).to receive(:authorized_data?).and_return(false) + allow(described_class).to receive(:currently_authorized?).and_return(false) end it 'creates constants for todays data' do - expect(Bravo.constants).not_to include(:TOKEN, :SIGN) - + expect(Wsaa).to receive(:login) Bravo::AuthData.create - - expect(Bravo.constants).to include(:TOKEN, :SIGN) end end end diff --git a/spec/bravo/bill_spec.rb b/spec/bravo/bill_spec.rb index c3f9746..0129f89 100644 --- a/spec/bravo/bill_spec.rb +++ b/spec/bravo/bill_spec.rb @@ -1,117 +1,123 @@ # encoding: utf-8 require 'spec_helper' -describe 'Bill' do - let(:bill) { @bill = Bravo::Bill.new(iva_condition: :consumidor_final, invoice_type: :invoice) } - - describe '.header' do - it 'sets up the header hash' do - @header = Bravo::Bill.header(0) - expect(@header.size).to be 3 - %w[CantReg CbteTipo PtoVta].each do |key| - expect(@header.key?(key)).to be_truthy +module Bravo + describe Bill do + let(:bill) { Bravo::Bill.new(iva_condition: :consumidor_final, invoice_type: :invoice) } + + describe '.header' do + subject(:header) { described_class.header(0) } + it 'sets up the header hash' do + expect(header.size).to be 3 + %w[CantReg CbteTipo PtoVta].each do |key| + expect(header.key?(key)).to be_truthy + end end end - end - describe '.initialize' do - it 'applies Bravos defaults' do - expect(bill.client).to be_a Savon::Client + describe '.initialize' do + it 'applies Bravos defaults' do + expect(bill.client).to be_a Savon::Client - %w[Token Sign Cuit].each do |key| - expect(bill.body['Auth'].fetch(key, nil)).not_to be_nil + expect(bill.document_type).to be Bravo.default_documento + expect(bill.currency).to be Bravo.default_moneda end + end - expect(bill.document_type).to be Bravo.default_documento - expect(bill.currency).to be Bravo.default_moneda + describe '.body' do + it 'sets up a body if there is none' do + %w[Token Sign Cuit].each do |key| + expect(bill.body['Auth'].fetch(key, nil)).not_to be_nil + end + end end - end - describe '#bill_type' do - before { bill.invoice_type = :invoice } - it 'returns the bill type for Responsable Inscripto' do - bill.iva_condition = :responsable_inscripto + describe '#bill_type' do + before { bill.invoice_type = :invoice } + it 'returns the bill type for Responsable Inscripto' do + bill.iva_condition = :responsable_inscripto - expect(bill.bill_type).to eq '01' - end + expect(bill.bill_type).to eq '01' + end - it 'returns the bill type for Consumidor Final' do - bill.iva_condition = :consumidor_final + it 'returns the bill type for Consumidor Final' do + bill.iva_condition = :consumidor_final - expect(bill.bill_type).to eq '06' + expect(bill.bill_type).to eq '06' + end end - end - describe '#iva_sum and #total' do - it 'calculate the IVA array values' do - bill.iva_condition = :responsable_inscripto - bill.currency = :peso - bill.net = 100.89 - bill.aliciva_id = 2 + describe '#iva_sum and #total' do + it 'calculate the IVA array values' do + bill.iva_condition = :responsable_inscripto + bill.currency = :peso + bill.net = 100.89 + bill.aliciva_id = 2 - expect(bill.iva_sum).to be_within(0.005).of(21.19) - expect(bill.total).to be_within(0.005).of(122.08) + expect(bill.iva_sum).to be_within(0.005).of(21.19) + expect(bill.total).to be_within(0.005).of(122.08) + end end - end - describe '#setup_bill' do - before do - bill.net = 100 - bill.aliciva_id = 2 - bill.document_number = '30710151543' - bill.iva_condition = :responsable_inscripto - bill.concept = 'Servicios' - end + describe '#setup_bill' do + before do + bill.net = 100 + bill.aliciva_id = 2 + bill.document_number = '30710151543' + bill.iva_condition = :responsable_inscripto + bill.concept = 'Servicios' + end - it 'uses today dates when due and service dates are null', - vcr: { cassette_name: 'setup_bill_ommitted_date' } do - bill.setup_bill + it 'uses today dates when due and service dates are null', + vcr: { cassette_name: 'setup_bill_ommitted_date' } do + bill.setup_bill - detail = bill.body['FeCAEReq']['FeDetReq']['FECAEDetRequest'] + detail = bill.body['FeCAEReq']['FeDetReq']['FECAEDetRequest'] - expect(detail['FchServDesde']).to eq Time.new.strftime('%Y%m%d') - expect(detail['FchServHasta']).to eq Time.new.strftime('%Y%m%d') - expect(detail['FchVtoPago']).to eq Time.new.strftime('%Y%m%d') - end + expect(detail['FchServDesde']).to eq Time.new.strftime('%Y%m%d') + expect(detail['FchServHasta']).to eq Time.new.strftime('%Y%m%d') + expect(detail['FchVtoPago']).to eq Time.new.strftime('%Y%m%d') + end - it 'uses given due and service dates', vcr: { cassette_name: 'setup_bill_given_date' } do - bill.due_date = Date.new(2011, 12, 10).strftime('%Y%m%d') - bill.date_from = Date.new(2011, 11, 01).strftime('%Y%m%d') - bill.date_to = Date.new(2011, 11, 30).strftime('%Y%m%d') + it 'uses given due and service dates', vcr: { cassette_name: 'setup_bill_given_date' } do + bill.due_date = Date.new(2011, 12, 10).strftime('%Y%m%d') + bill.date_from = Date.new(2011, 11, 01).strftime('%Y%m%d') + bill.date_to = Date.new(2011, 11, 30).strftime('%Y%m%d') - bill.setup_bill + bill.setup_bill - detail = bill.body['FeCAEReq']['FeDetReq']['FECAEDetRequest'] + detail = bill.body['FeCAEReq']['FeDetReq']['FECAEDetRequest'] - expect(detail['FchServDesde']).to eq '20111101' - expect(detail['FchServHasta']).to eq '20111130' - expect(detail['FchVtoPago']).to eq '20111210' + expect(detail['FchServDesde']).to eq '20111101' + expect(detail['FchServHasta']).to eq '20111130' + expect(detail['FchVtoPago']).to eq '20111210' + end end - end - describe '#authorize' do - describe 'for facturas' do - Bravo::BILL_TYPE[Bravo.own_iva_cond].keys.each do |target_iva_cond| - describe "issued to #{ target_iva_cond }" do - Bravo::BILL_TYPE[Bravo.own_iva_cond][target_iva_cond].keys.each do |bill_type| - vcr_options = { cassette_name: "#{ target_iva_cond }_and_#{ bill_type }" } - it "authorizes bill type #{ bill_type }", vcr: vcr_options do - bill.net = 10_000.88 - bill.aliciva_id = 2 - bill.document_number = '30710151543' - bill.iva_condition = target_iva_cond - bill.concept = 'Servicios' - bill.invoice_type = bill_type - - expect(bill.authorized?).to be_falsey - - expect(bill.authorize).to be_truthy - expect(bill.authorized?).to be_truthy - - response = bill.response - - expect(response.length).to eql 28 - expect(response.cae.length).to eql 14 + describe '#authorize' do + describe 'for facturas' do + Bravo::BILL_TYPE[Bravo.own_iva_cond].keys.each do |target_iva_cond| + describe "issued to #{ target_iva_cond }" do + Bravo::BILL_TYPE[Bravo.own_iva_cond][target_iva_cond].keys.each do |bill_type| + vcr_options = { cassette_name: "#{ target_iva_cond }_and_#{ bill_type }" } + it "authorizes bill type #{ bill_type }", vcr: vcr_options do + bill.net = 10_000.88 + bill.aliciva_id = 2 + bill.document_number = '30710151543' + bill.iva_condition = target_iva_cond + bill.concept = 'Servicios' + bill.invoice_type = bill_type + + expect(bill.authorized?).to be_falsey + + expect(bill.authorize).to be_truthy + expect(bill.authorized?).to be_truthy + + response = bill.response + + expect(response.length).to eql 28 + expect(response.cae.length).to eql 14 + end end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 0be0862..626ca37 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -25,7 +25,7 @@ Bravo.default_documento = 'CUIT' Bravo.default_moneda = :peso Bravo.own_iva_cond = :responsable_inscripto -Bravo.logger = { log: true, level: :debug } +Bravo.logger = { log: false, level: :info } Bravo.openssl_bin = ENV["TRAVIS"] ? 'openssl' : '/usr/local/Cellar/openssl/1.0.2a-1/bin/openssl' Bravo::AuthData.environment = :test From 38e069e279e89f27ab043e927e33c6dc7cedff20 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Tue, 19 May 2015 20:49:51 -0300 Subject: [PATCH 24/35] Bundle with test. --- .travis.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index 4af132a..925e671 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,7 +3,6 @@ rvm: - 2.0.0 - 2.1.2 - 2.2.0 -bundler_args: --without test script: - bundle exec rspec spec --format=progress - bundle exec rubocop From 28f760a8e22d625d041ace1c3d84578253649d5e Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sat, 23 May 2015 11:40:52 -0300 Subject: [PATCH 25/35] Debugging travis and ruby 2.0.0 --- lib/bravo/wsaa.rb | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/bravo/wsaa.rb b/lib/bravo/wsaa.rb index aa14878..376eaf0 100644 --- a/lib/bravo/wsaa.rb +++ b/lib/bravo/wsaa.rb @@ -79,8 +79,10 @@ def self.call_wsaa(req) curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Bravo::AuthData.wsaa_url }` response = CGI.unescapeHTML(response) - token = response.scan(%r{\(.+)\<\/token\>}).first.first - sign = response.scan(%r{\(.+)\<\/sign\>}).first.first + require 'pp' + pp response + token = response.scan(%r{\(.+)\<\/token\>}).flatten.first + sign = response.scan(%r{\(.+)\<\/sign\>}).flatten.first created_at = response.scan(%r{\(.+)\<\/generationTime\>}).first.first expires_at = response.scan(%r{\(.+)\<\/expirationTime\>}).first.first @@ -95,3 +97,4 @@ def self.write_yaml(credentials, filename) end end end + From 07a6e984286f1fe290dd287fa5896d335a84fadf Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sat, 23 May 2015 11:48:44 -0300 Subject: [PATCH 26/35] Fixedh scan compatibility issue with ruby 2.0.0 --- lib/bravo/wsaa.rb | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/bravo/wsaa.rb b/lib/bravo/wsaa.rb index 376eaf0..e616bd3 100644 --- a/lib/bravo/wsaa.rb +++ b/lib/bravo/wsaa.rb @@ -79,12 +79,10 @@ def self.call_wsaa(req) curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Bravo::AuthData.wsaa_url }` response = CGI.unescapeHTML(response) - require 'pp' - pp response token = response.scan(%r{\(.+)\<\/token\>}).flatten.first sign = response.scan(%r{\(.+)\<\/sign\>}).flatten.first - created_at = response.scan(%r{\(.+)\<\/generationTime\>}).first.first - expires_at = response.scan(%r{\(.+)\<\/expirationTime\>}).first.first + created_at = response.scan(%r{\(.+)\<\/generationTime\>}).flatten.first + expires_at = response.scan(%r{\(.+)\<\/expirationTime\>}).flatten.first { token: token, sign: sign, created_at: created_at, expires_at: expires_at } end @@ -97,4 +95,3 @@ def self.write_yaml(credentials, filename) end end end - From 8699792ac397d43ab277e6106244ad1217eb6616 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sat, 23 May 2015 14:18:22 -0300 Subject: [PATCH 27/35] Using Bigdecimal instead of Float for calculations --- lib/bravo/bill.rb | 22 ++++++++++++---------- spec/bravo/bill_spec.rb | 6 +++--- 2 files changed, 15 insertions(+), 13 deletions(-) diff --git a/lib/bravo/bill.rb b/lib/bravo/bill.rb index 08d91e5..03aa85d 100644 --- a/lib/bravo/bill.rb +++ b/lib/bravo/bill.rb @@ -1,4 +1,7 @@ # encoding: utf-8 +require 'bigdecimal' +require 'bigdecimal/util' + module Bravo # The main class in Bravo. Handles WSFE method interactions. # Subsequent implementations will be added here (maybe). @@ -7,7 +10,7 @@ class Bill # Returns the Savon::Client instance in charge of the interactions # with WSFE API (built on init) # - attr_reader :client + attr_reader :client, :iva_sum, :total attr_accessor :net, :document_number, :iva_condition, :document_type, :concept, :currency, :due_date, :aliciva_id, :date_from, :date_to, @@ -16,7 +19,7 @@ class Bill # rubocop:disable Metrics/AbcSize def initialize(attrs = {}) @client ||= Savon.client({ wsdl: AuthData.wsfe_url }.merge! Bravo.logger_options) - @net = attrs[:net].to_f.round(2) || 0 + @net = attrs.fetch(:net, 0).to_d @document_type = attrs.fetch(:document_type, Bravo.default_documento) @currency = attrs.fetch(:currency, Bravo.default_moneda) @concept = attrs.fetch(:concept, Bravo.default_concepto) @@ -58,8 +61,8 @@ def bill_type # net and iva_sum. # @return [Float] the sum of both fields, or 0 if the net is 0. # - def total - @total = net.zero? ? 0.0 : net + iva_sum + def calculate_total + @total = net + calculate_iva_sum end # Calculates the corresponding iva sum. @@ -68,9 +71,8 @@ def total # # TODO: fix this # - def iva_sum - @iva_sum = net * applicable_iva_multiplier - @iva_sum.round(2) + def calculate_iva_sum + @iva_sum ||= (net * applicable_iva_multiplier).round(2) end # Files the authorization request to AFIP @@ -99,9 +101,9 @@ def setup_bill request.date = today request.currency_id = MONEDAS[currency][:codigo] request.iva_code = applicable_iva_code - request.net_amount = net.to_f - request.iva_amount = iva_sum - request.total = total + request.net_amount = net.to_d + request.iva_amount = calculate_iva_sum + request.total = calculate_total request.from = request.to = Reference.next_bill_number(bill_type) request.document_number = document_number diff --git a/spec/bravo/bill_spec.rb b/spec/bravo/bill_spec.rb index 0129f89..f55196f 100644 --- a/spec/bravo/bill_spec.rb +++ b/spec/bravo/bill_spec.rb @@ -48,14 +48,14 @@ module Bravo end describe '#iva_sum and #total' do - it 'calculate the IVA array values' do + it 'calculates the IVA array values' do bill.iva_condition = :responsable_inscripto bill.currency = :peso bill.net = 100.89 bill.aliciva_id = 2 - expect(bill.iva_sum).to be_within(0.005).of(21.19) - expect(bill.total).to be_within(0.005).of(122.08) + expect(bill.calculate_iva_sum).to be_within(0.005).of(21.19) + expect(bill.calculate_total).to be_within(0.005).of(122.08) end end From 4ace56a9ac2ea69039e83a7a4fb043589a057e18 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sat, 23 May 2015 14:22:08 -0300 Subject: [PATCH 28/35] Clean cassettes and auth data after ruby version --- .travis.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.travis.yml b/.travis.yml index 925e671..0141ece 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,5 +4,7 @@ rvm: - 2.1.2 - 2.2.0 script: + - rm -rf spec/fixtures/vcr_cassettes/ + - if [ -e /tmp/bravo* ]; then rm /tmp/bravo*; fi - bundle exec rspec spec --format=progress - bundle exec rubocop From f7c4516b55e9c29196dff425de70e80fd13a7846 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sat, 23 May 2015 17:41:31 -0300 Subject: [PATCH 29/35] Logger is poro now --- lib/bravo.rb | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/bravo.rb b/lib/bravo.rb index ae07e97..8e6991b 100644 --- a/lib/bravo.rb +++ b/lib/bravo.rb @@ -15,15 +15,15 @@ class NullOrInvalidAttribute < StandardError; end class MissingCertificate < StandardError; end # This class handles the logging options # - # rubocop:disable Style/StructInheritance - class Logger < Struct.new(:log, :pretty_xml, :level) + class Logger # @param opts [Hash] receives a hash with keys `log`, `pretty_xml` (both # boolean) or the desired log level as `level` + attr_accessor :log, :pretty_xml, :level def initialize(opts = {}) - self.log = opts[:log] || false - self.pretty_xml = opts[:pretty_xml] || log - self.level = opts[:level] || :debug + self.log = opts.fetch(:log, false) + self.pretty_xml = opts.fetch(:pretty_xml, log) + self.level = opts.fetch(:level, :debug) end # @return [Hash] returns a hash with the proper logging optios for Savon. @@ -31,7 +31,6 @@ def logger_options { log: log, pretty_print_xml: pretty_xml, log_level: level } end end - # rubocop:enable Style/StructInheritance autoload :Authorizer, 'bravo/authorizer' autoload :AuthData, 'bravo/auth_data' @@ -51,7 +50,7 @@ class << self # @param opts [Hash] pass a hash with `log`, `pretty_xml` and `level` keys to set # them. def logger=(opts) - @logger ||= Logger.new(opts) + @logger = Logger.new(opts) end # Sets the logger options to the default values or returns the previously set From 50082c69836d43bdb7d4ddb2875ffb37f6965028 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sat, 23 May 2015 17:59:47 -0300 Subject: [PATCH 30/35] Ignored vcr cassettes --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 008b9c3..6bbafcb 100644 --- a/.gitignore +++ b/.gitignore @@ -26,7 +26,7 @@ tmtags log/ log/*.log ./fixtures/ -./fixtures/vcr_cassettes/ +*/**/vcr_cassettes/ *.gem tmp/ bin/bravo-certs/ From 9a43e36e1017d376e66561c466eefc6169a74c52 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sat, 23 May 2015 18:25:47 -0300 Subject: [PATCH 31/35] removed class << self block --- lib/bravo.rb | 44 +++++++++++++++++++++----------------------- 1 file changed, 21 insertions(+), 23 deletions(-) diff --git a/lib/bravo.rb b/lib/bravo.rb index 8e6991b..5eb594c 100644 --- a/lib/bravo.rb +++ b/lib/bravo.rb @@ -45,32 +45,30 @@ def logger_options attr_accessor :cuit, :sale_point, :default_documento, :pkey, :cert, :default_concepto, :default_moneda, :own_iva_cond, :openssl_bin - class << self - # Receiver of the logging configuration options. - # @param opts [Hash] pass a hash with `log`, `pretty_xml` and `level` keys to set - # them. - def logger=(opts) - @logger = Logger.new(opts) - end + # Receiver of the logging configuration options. + # @param opts [Hash] pass a hash with `log`, `pretty_xml` and `level` keys to set + # them. + def self.logger=(opts) + @logger = Logger.new(opts) + end - # Sets the logger options to the default values or returns the previously set - # logger options - # @return [Logger] - def logger - @logger ||= Logger.new - end + # Sets the logger options to the default values or returns the previously set + # logger options + # @return [Logger] + def self.logger + @logger ||= Logger.new + end - # Returs the formatted logger options to be used by Savon. - def logger_options - logger.logger_options - end + # Returs the formatted logger options to be used by Savon. + def self.logger_options + logger.logger_options + end - def own_iva_cond=(iva_cond_symbol) - if Bravo::BILL_TYPE.key?(iva_cond_symbol) - @own_iva_cond = iva_cond_symbol - else - raise(NullOrInvalidAttribute.new, "El valor de own_iva_cond: (#{ iva_cond_symbol }) es inválido.") - end + def self.own_iva_cond=(iva_cond_symbol) + if Bravo::BILL_TYPE.key?(iva_cond_symbol) + @own_iva_cond = iva_cond_symbol + else + raise(NullOrInvalidAttribute.new, "El valor de own_iva_cond: (#{ iva_cond_symbol }) es inválido.") end end end From 89797693d758bcd8ea02e866d60280cb75225df5 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sat, 23 May 2015 19:39:46 -0300 Subject: [PATCH 32/35] Added Authorization class. --- lib/bravo.rb | 17 ++++++++++------- lib/bravo/authorization.rb | 11 +++++++++++ spec/bravo/authorization_spec.rb | 18 ++++++++++++++++++ 3 files changed, 39 insertions(+), 7 deletions(-) create mode 100644 lib/bravo/authorization.rb create mode 100644 spec/bravo/authorization_spec.rb diff --git a/lib/bravo.rb b/lib/bravo.rb index 5eb594c..fde73e5 100644 --- a/lib/bravo.rb +++ b/lib/bravo.rb @@ -13,6 +13,8 @@ class NullOrInvalidAttribute < StandardError; end # Exception Class for missing or invalid certifficate # class MissingCertificate < StandardError; end + + class MissingCredentials < StandardError; end # This class handles the logging options # class Logger @@ -32,13 +34,14 @@ def logger_options end end - autoload :Authorizer, 'bravo/authorizer' - autoload :AuthData, 'bravo/auth_data' - autoload :Bill, 'bravo/bill' - autoload :Constants, 'bravo/constants' - autoload :Wsaa, 'bravo/wsaa' - autoload :Reference, 'bravo/reference' - autoload :Request, 'bravo/request' + autoload :Authorizer, 'bravo/authorizer' + autoload :AuthData, 'bravo/auth_data' + autoload :Bill, 'bravo/bill' + autoload :Constants, 'bravo/constants' + autoload :Authorization, 'bravo/authorization' + autoload :Wsaa, 'bravo/wsaa' + autoload :Reference, 'bravo/reference' + autoload :Request, 'bravo/request' extend self diff --git a/lib/bravo/authorization.rb b/lib/bravo/authorization.rb new file mode 100644 index 0000000..93ab9a2 --- /dev/null +++ b/lib/bravo/authorization.rb @@ -0,0 +1,11 @@ +module Bravo + class Authorization + @authorized_cuits = [] + + def self.for(cuit) + credentials = @authorized_cuits.find { |authorization| authorization.cuit == cuit } + raise ::Bravo::MissingCredentials.new, "missing credentials for #{ cuit }" unless credentials + credentials + end + end +end diff --git a/spec/bravo/authorization_spec.rb b/spec/bravo/authorization_spec.rb new file mode 100644 index 0000000..484cbb4 --- /dev/null +++ b/spec/bravo/authorization_spec.rb @@ -0,0 +1,18 @@ +require 'spec_helper' + +module Bravo + describe Authorization do + context 'when requested for credentials for a cuit' do + context 'and there are none' do + before do + described_class.instance_variable_set("@authorized_cuits", []) + end + + it 'raises an error' do + expect { described_class.for('some_cuit') }.to raise_exception(::Bravo::MissingCredentials, + 'missing credentials for some_cuit') + end + end + end + end +end From a8409a3af8ddfc8f334142bee70c6029238c1376 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sun, 7 Jun 2015 19:36:40 -0300 Subject: [PATCH 33/35] deleted Auth data class --- lib/bravo/auth_data.rb | 82 ------------------------------------ spec/bravo/auth_data_spec.rb | 16 ------- 2 files changed, 98 deletions(-) delete mode 100644 lib/bravo/auth_data.rb delete mode 100644 spec/bravo/auth_data_spec.rb diff --git a/lib/bravo/auth_data.rb b/lib/bravo/auth_data.rb deleted file mode 100644 index f26eed0..0000000 --- a/lib/bravo/auth_data.rb +++ /dev/null @@ -1,82 +0,0 @@ -module Bravo - # This class handles authorization data - # - class AuthData - class << self - attr_accessor :environment, :todays_data_file_name - - # Fetches WSAA Authorization Data to build the datafile for the day. - # It requires the private key file and the certificate to exist and - # to be configured as Bravo.pkey and Bravo.cert - # - def create - validate_certificates - Wsaa.login(current_data_file) unless currently_authorized? - end - - # Returns the authorization hash, containing the Token, Signature and Cuit - # @return [Hash] - # - def auth_hash - create - { 'Token' => credentials[:token], 'Sign' => credentials[:sign], 'Cuit' => Bravo.cuit } - end - - # Returns the right wsaa url for the specific environment - # @return [String] - # - def wsaa_url - check_environment! - Bravo::URLS[environment][:wsaa] - end - - # Returns the right wsfe url for the specific environment - # @return [String] - # - def wsfe_url - check_environment! - Bravo::URLS[environment][:wsfe] - end - - # Creates the data file name for a cuit number and the current day - # @return [String] - # - def current_data_file - "/tmp/bravo_#{ Bravo.cuit }.yml" - end - - # Checks the auth file exists and contains valid current data - # @return [Boolean] - # - def currently_authorized? - File.exist?(current_data_file) && authorized_data? - end - - # Checks credentials are valid - # @return [Boolean] - # - def authorized_data? - DateTime.now < DateTime.parse(credentials[:expires_at]) - end - - # Reads current data file - # @return [Hash] - # - def credentials - YAML.load_file(current_data_file) - end - - # Validates Bravo.environment is set and valid - # @return [Boolean] - # - def check_environment! - raise 'Environment not set.' unless Bravo::URLS.keys.include? environment - end - - def validate_certificates - raise "Archivo de llave privada no encontrado en #{ Bravo.pkey }" unless File.exist?(Bravo.pkey) - raise "Archivo certificado no encontrado en #{ Bravo.cert }" unless File.exist?(Bravo.cert) - end - end - end -end diff --git a/spec/bravo/auth_data_spec.rb b/spec/bravo/auth_data_spec.rb deleted file mode 100644 index 36eae39..0000000 --- a/spec/bravo/auth_data_spec.rb +++ /dev/null @@ -1,16 +0,0 @@ -require 'spec_helper' - -module Bravo - describe AuthData do - context 'when new credentials are necessary' do - before do - allow(described_class).to receive(:currently_authorized?).and_return(false) - end - - it 'creates constants for todays data' do - expect(Wsaa).to receive(:login) - Bravo::AuthData.create - end - end - end -end From 1066c129998900ec3c15373195eabc9d12885583 Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Sun, 7 Jun 2015 19:37:51 -0300 Subject: [PATCH 34/35] added multiple authorized cuits support --- README.md | 23 +++++-- lib/bravo.rb | 16 ++++- lib/bravo/authorization.rb | 84 +++++++++++++++++++++++-- lib/bravo/bill.rb | 28 +++------ lib/bravo/reference.rb | 10 +-- lib/bravo/request.rb | 4 +- lib/bravo/wsaa.rb | 19 ++++-- spec/bravo/authorization_spec.rb | 104 ++++++++++++++++++++++++++++++- spec/bravo/bill_spec.rb | 25 +++++--- spec/bravo/wsaa_spec.rb | 3 +- spec/spec_helper.rb | 13 +--- 11 files changed, 261 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index ec295e0..8f10416 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,12 @@ Para poder autorizar comprobantes mediante el WSFE, AFIP requiere de ciertos pas * Generar un CSR (Certificate Signing Request) utilizando el número de CUIT que emitirá los comprobantes y la clave privada del paso anterior. Se deberá enviar a AFIP el CSR para obtener el Certificado X.509 que se utilizará en el proceso de autorización de comprobantes. * Para el entorno de Testing, se debe enviar el X.509 por email a _webservices@afip.gov.ar_. * Para el entorno de Producción, el trámite se hace a través del portal [AFIP](http://www.afip.gov.ar) -* El certificado X.509 y la clave privada son utilizados por Bravo para obtener el token y signature a incluir en el header de autenticacion en cada request que hagamaos a los servicios de AFIP. +* El certificado X.509 y la clave privada son utilizados por el WSAA para identificarse frente a los servicios de AFIP. El WSAA permite obtener el token y signature a incluir en el header de autenticacion en cada request que hagamaos a los servicios de AFIP. ### OpenSSL -Para cumplir con los requisitos de encriptación del [Web Service de Autenticación y Autorización](http://www.afip.gov.ar/ws/WSAA/README.txt) (WSAA), Bravo requiere [OpenSSL](http://openssl.org) en cualquier versión posterior a la 1.0.0a. +Para cumplir con los requisitos de encriptación del [Web Service de Autenticación y Autorización](http://www.afip.gov.ar/ws/WSAA/README.txt) (WSAA), Bravo requiere [OpenSSL](http://openssl.org) en cualquier versión igual o posterior a la 1.0.0a. Como regla general, basta correr desde la línea de comandos ```openssl cms``` @@ -26,7 +26,7 @@ Si el comando ```cms``` no está disponible, se debe actualizar OpenSSL. ### Certificados -AFIP exige para acceder a sus Web Services, la utilización del WSAA. Este servicio se encarga de la autorización y autenticación de cada request hecho al web service. +AFIP exige estar identificado para acceder a sus Web Services, mediante la utilización del WSAA. Este servicio se encarga de la autorización y autenticación de cada request hecho al web service. Para esto, hay que generar un mensaje encriptado, que se utiliza para el login en el WSAA. Si este login es exitoso, se obtienen credenciales válidas por 12 horas. Una vez instalada la version correcta de OpenSSL, podemos generar la clave privada y el CSR. @@ -38,9 +38,22 @@ Una vez instalada la version correcta de OpenSSL, podemos generar la clave priva Luego de haber obtenido el certificado X.509, podemos comenzar a utilizar Bravo en el entorno para el cual sirve el certificado. -### Configuración +### Autorización + +Cada mensajeLa clase Authorizations es la encargada de manejar las credenciales con las que se autentican los mensajes +enviados a afip. + +El primer paso, es crear una nueva instancia de Bravo::Authorization con el número de cuit y la ruta a los +archivos de certificado y clave privada: + +```ruby +authorization = Bravo::Authorization.new +authorization.cuit = '2028774002' +authorization.pkey_path = 'path/al/pkey' +authorization.cert_path = 'path/al/cert' +``` + -Bravo no asume valores por defecto, por lo cual hay que configurar de forma explícita todos los parámetros: * ```pkey``` ruta a la clave privada * ```cert``` ruta al certificado X.509 diff --git a/lib/bravo.rb b/lib/bravo.rb index fde73e5..66a96ce 100644 --- a/lib/bravo.rb +++ b/lib/bravo.rb @@ -35,7 +35,6 @@ def logger_options end autoload :Authorizer, 'bravo/authorizer' - autoload :AuthData, 'bravo/auth_data' autoload :Bill, 'bravo/bill' autoload :Constants, 'bravo/constants' autoload :Authorization, 'bravo/authorization' @@ -45,9 +44,22 @@ def logger_options extend self - attr_accessor :cuit, :sale_point, :default_documento, :pkey, :cert, :default_concepto, :default_moneda, + attr_accessor :sale_point, :default_documento, :default_concepto, :default_moneda, :own_iva_cond, :openssl_bin + attr_reader :environment + + # Used to set the environment. Validates the value with the existing environments + # + def self.environment=(env) + env = env.to_sym + if Bravo::URLS.keys.include?(env) + @environment = env + else + raise "invalid environment: #{ env }. Choose one from [#{ Bravo::URLS.keys.join(', ') }]" + end + end + # Receiver of the logging configuration options. # @param opts [Hash] pass a hash with `log`, `pretty_xml` and `level` keys to set # them. diff --git a/lib/bravo/authorization.rb b/lib/bravo/authorization.rb index 93ab9a2..dc49541 100644 --- a/lib/bravo/authorization.rb +++ b/lib/bravo/authorization.rb @@ -1,11 +1,87 @@ module Bravo + Credentials = Struct.new(:authorizations) do + def find(cuit) + authorizations.find { |authorization| authorization.cuit == cuit } + end + + def store(new_auth) + authorizations.reject! { |authorization| authorization.cuit == new_auth.cuit } + authorizations << new_auth + end + end + class Authorization - @authorized_cuits = [] + attr_accessor :cuit, :pkey_path, :cert_path, :token, :sign, :created_at, :expires_at + + def initialize(cuit, pkey, cert) + @cuit = cuit + @pkey_path = validate_path(pkey) + @cert_path = validate_path(cert) + self + end + + # Returns the WSFE url for the specified environment + # @return [String] + # + def self.wsfe_url + raise 'environment not set' unless Bravo.environment + Bravo::URLS[Bravo.environment][:wsfe] + end + + # Returns the WSAA url for the specified environment + # @return [String] + # + def self.wsaa_url + raise 'environment not set' unless Bravo.environment + Bravo::URLS[Bravo.environment][:wsaa] + end + + def self.build(cuit, pkey_path, cert_path) + authorization = new(cuit, pkey_path, cert_path) + credentials.store(authorization) + authorization + end + + def self.credentials + @credentials ||= Credentials.new([]) + end def self.for(cuit) - credentials = @authorized_cuits.find { |authorization| authorization.cuit == cuit } - raise ::Bravo::MissingCredentials.new, "missing credentials for #{ cuit }" unless credentials - credentials + credentials_for_cuit = credentials.find(cuit) + raise ::Bravo::MissingCredentials.new, "missing credentials for #{ cuit }" unless credentials_for_cuit + credentials_for_cuit + end + + def self.create(cuit:, pkey_path:, cert_path:) + authorization = build(cuit, pkey_path, cert_path) + authorization.authorize! + end + + def authorized? + !token.nil? && !sign.nil? && !expires_at.nil? && (expires_at > Time.new) + end + + def auth_hash + authorize! unless self.authorized? + { 'Cuit' => cuit, 'Sign' => sign, 'Token' => token } + end + + def authorize! + authorization_data = Wsaa.login(pkey_path, cert_path) + + self.token = authorization_data[:token] + self.sign = authorization_data[:sign] + self.expires_at = Time.parse(authorization_data[:expires_at]) + self.created_at = Time.parse(authorization_data[:created_at]) + + self + end + + private + + def validate_path(path) + raise(ArgumentError.new, "#{ path } does not exist") unless File.exist? path + path end end end diff --git a/lib/bravo/bill.rb b/lib/bravo/bill.rb index 03aa85d..9266af0 100644 --- a/lib/bravo/bill.rb +++ b/lib/bravo/bill.rb @@ -17,8 +17,10 @@ class Bill :response, :invoice_type # rubocop:disable Metrics/AbcSize - def initialize(attrs = {}) - @client ||= Savon.client({ wsdl: AuthData.wsfe_url }.merge! Bravo.logger_options) + def initialize(cuit, attrs = {}) + @cuit = cuit + + @client ||= Savon.client({ wsdl: Authorization.wsfe_url }.merge! Bravo.logger_options) @net = attrs.fetch(:net, 0).to_d @document_type = attrs.fetch(:document_type, Bravo.default_documento) @currency = attrs.fetch(:currency, Bravo.default_moneda) @@ -28,25 +30,9 @@ def initialize(attrs = {}) end # rubocop:enable Metrics/AbcSize - def inspect - %{#} - end - - def to_hash - { net: net, document_number: document_number, iva_condition: iva_condition, invoice_type: invoice_type, - document_type: document_type, concept: concept, currency: currency, due_date: due_date, - aliciva_id: aliciva_id, date_from: date_from, date_to: date_to, body: body } - end - - def to_yaml - to_hash.to_yaml - end - + # @private def body - @body ||= { 'Auth' => AuthData.auth_hash } + @body ||= { 'Auth' => Authorization.for(@cuit).auth_hash } end # Searches the corresponding invoice type according to the combination of @@ -105,7 +91,7 @@ def setup_bill request.iva_amount = calculate_iva_sum request.total = calculate_total - request.from = request.to = Reference.next_bill_number(bill_type) + request.from = request.to = Reference.next_bill_number(@cuit, bill_type) request.document_number = document_number request.date_from = date_from || today diff --git a/lib/bravo/reference.rb b/lib/bravo/reference.rb index f5b575a..e6e67f3 100644 --- a/lib/bravo/reference.rb +++ b/lib/bravo/reference.rb @@ -5,11 +5,11 @@ class Reference # Fetches the number for the next bill to be issued # @return [Integer] the number for the next bill # - def self.next_bill_number(cbte_type) + def self.next_bill_number(cuit, cbte_type) set_client resp = @client.call(:fe_comp_ultimo_autorizado) do |soap| # soap.namespaces['xmlns'] = 'http://ar.gov.afip.dif.FEV1/' - soap.message 'Auth' => Bravo::AuthData.auth_hash, 'PtoVta' => Bravo.sale_point, + soap.message 'Auth' => Authorization.for(cuit).auth_hash, 'PtoVta' => Bravo.sale_point, 'CbteTipo' => cbte_type end @@ -19,10 +19,10 @@ def self.next_bill_number(cbte_type) # Fetches the possible document codes and names # @return [Hash] # - def self.get_custom(operation) + def self.get_custom(cuit, operation) set_client resp = @client.call(operation) do |soap| - soap.message 'Auth' => Bravo::AuthData.auth_hash + soap.message 'Auth' => Authorization.for(cuit).auth_hash end resp.to_hash end @@ -31,7 +31,7 @@ def self.get_custom(operation) # # def self.set_client - opts = { wsdl: Bravo::AuthData.wsfe_url }.merge! Bravo.logger_options + opts = { wsdl: Authorization.wsfe_url }.merge! Bravo.logger_options @client = Savon.client(opts) end end diff --git a/lib/bravo/request.rb b/lib/bravo/request.rb index 745303a..7569b2b 100644 --- a/lib/bravo/request.rb +++ b/lib/bravo/request.rb @@ -11,7 +11,7 @@ def to_hash # rubocop:disable Metrics/AbcSize # rubocop:disable Metrics/MethodLength def build_details - hsh = { + details = { 'FECAEDetRequest' => { 'Concepto' => concept, 'DocTipo' => document_type, @@ -40,7 +40,7 @@ def build_details } } } - hsh.reject { |k, _v| hsh[k].nil? } + details.reject { |_k, v| v.nil? } end # rubocop:enable Metrics/AbcSize # rubocop:enable Metrics/MethodLength diff --git a/lib/bravo/wsaa.rb b/lib/bravo/wsaa.rb index e616bd3..b285bf3 100644 --- a/lib/bravo/wsaa.rb +++ b/lib/bravo/wsaa.rb @@ -7,13 +7,17 @@ class Wsaa # Main method for authentication and authorization. # When successful, produces the yaml file with auth data. # - def self.login(filename) + def self.login(pkey_path, cert_path) tra = build_tra - cms = build_cms(tra) + cms = build_cms(tra, pkey_path, cert_path) req = build_request(cms) - auth = call_wsaa(req) + call_wsaa(req) + end + def self.login_to_file(filename, pkey_path, cert_path) + auth = login(pkey_path, cert_path) write_yaml(auth, filename) + auth end # Builds the xml for the 'Ticket de Requerimiento de Acceso' @@ -42,9 +46,9 @@ def self.build_tra # Builds the CMS # @return [String] cms # - def self.build_cms(tra) + def self.build_cms(tra, pkey_path, cert_path) `echo '#{ tra }' | - #{ Bravo.openssl_bin } cms -sign -in /dev/stdin -signer #{ Bravo.cert } -inkey #{ Bravo.pkey } \ + #{ Bravo.openssl_bin } cms -sign -in /dev/stdin -signer #{ cert_path } -inkey #{ pkey_path } \ -nodetach -outform der | #{ Bravo.openssl_bin } base64 -e` end @@ -75,10 +79,13 @@ def self.build_request(cms) # # rubocop:disable Metrics/AbcSize def self.call_wsaa(req) + # XXX: a request made too soon after a successful one throws an error. deal with it response = `echo '#{ req }' | - curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Bravo::AuthData.wsaa_url }` + curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Authorization.wsaa_url }` response = CGI.unescapeHTML(response) + puts response + # ns1:coe.alreadyAuthenticated grepear esto para evitar errores token = response.scan(%r{\(.+)\<\/token\>}).flatten.first sign = response.scan(%r{\(.+)\<\/sign\>}).flatten.first created_at = response.scan(%r{\(.+)\<\/generationTime\>}).flatten.first diff --git a/spec/bravo/authorization_spec.rb b/spec/bravo/authorization_spec.rb index 484cbb4..1a2b82f 100644 --- a/spec/bravo/authorization_spec.rb +++ b/spec/bravo/authorization_spec.rb @@ -2,10 +2,44 @@ module Bravo describe Authorization do + context 'on initialize' do + context 'with bad path' do + let(:subject) { described_class.new('20287740027', 'bad/pkey/path', 'bad/cert/path') } + + it 'requires cuit, pkey_path and cert_path' do + expect { subject }.to raise_exception(ArgumentError) + end + end + + context 'with correct attributes' do + let(:authorization) do + described_class.new('20287740027', 'spec/fixtures/certs/pkey', 'spec/fixtures/certs/cert.crt') + end + + it 'returns a valid instance' do + expect(authorization).to be_a(described_class) + end + + it 'sets cuit, pkey_path and cert_path instance variables' do + expect(authorization.cuit).to eq '20287740027' + expect(authorization.pkey_path).to eq "spec/fixtures/certs/pkey" + expect(authorization.cert_path).to eq "spec/fixtures/certs/cert.crt" + end + end + end + + context 'when building a new set of credentials' do + it 'stores the new auth in the authorizations instance variable' do + expect do + described_class.build('20287740027', 'spec/fixtures/certs/pkey', 'spec/fixtures/certs/cert.crt') + end.to change(described_class.credentials.authorizations, :count).by(1) + end + end + context 'when requested for credentials for a cuit' do - context 'and there are none' do + context 'when there are none for that cuit' do before do - described_class.instance_variable_set("@authorized_cuits", []) + described_class.credentials.authorizations = [] end it 'raises an error' do @@ -13,6 +47,72 @@ module Bravo 'missing credentials for some_cuit') end end + + context 'when there are credentials for that cuit' do + let(:authorization) { valid_authorization } + + it 'returns the authorization' do + expect(described_class.for(authorization.cuit)).to eq(authorization) + end + end + end + + context 'a valid instance' do + subject(:authorization) { valid_authorization } + + context 'when checking if its authorized' do + it 'returns false for unauthorized credentials' do + expect(authorization.authorized?).to be_falsey + end + end + + context 'when calling the login method' do + it 'sets the login data attributes', vcr: { cassette_name: 'valid_login' } do + expect(authorization.authorize!).to be_truthy + + expect(authorization.token).to be_truthy + expect(authorization.sign).to be_truthy + expect(authorization.created_at).to be_truthy + expect(authorization.expires_at).to be_truthy + + expect(authorization.authorized?).to be_truthy + end + end + end + + context 'when credentials are current' do + subject(:authorization) { described_class.for('20287740027') } + + before do + timestamp = Time.new + + described_class.build('20287740027', 'spec/fixtures/certs/pkey', 'spec/fixtures/certs/cert.crt') + + subject.token = 'token' + subject.sign = 'sign' + subject.expires_at = timestamp + 3600 + subject.created_at = timestamp - 7200 + + described_class.credentials.store subject + end + + it 'builds the auth hash from memory' do + expect { subject.auth_hash }.not_to change(subject, :created_at) + end + end + + context 'when credentials are expired' do + subject(:authorization) do + described_class.build('20287740027', 'spec/fixtures/certs/pkey', 'spec/fixtures/certs/cert.crt') + end + + it 'renews the credentials', vcr: { cassette_name: 'valid_login' }do + expect { subject.auth_hash }.to change(subject, :created_at) + end + end + + def valid_authorization + described_class.build('20287740027', 'spec/fixtures/certs/pkey', 'spec/fixtures/certs/cert.crt') end end end diff --git a/spec/bravo/bill_spec.rb b/spec/bravo/bill_spec.rb index f55196f..7c530c8 100644 --- a/spec/bravo/bill_spec.rb +++ b/spec/bravo/bill_spec.rb @@ -3,7 +3,7 @@ module Bravo describe Bill do - let(:bill) { Bravo::Bill.new(iva_condition: :consumidor_final, invoice_type: :invoice) } + let(:bill) { Bravo::Bill.new('20287740027', iva_condition: :consumidor_final, invoice_type: :invoice) } describe '.header' do subject(:header) { described_class.header(0) } @@ -25,7 +25,11 @@ module Bravo end describe '.body' do - it 'sets up a body if there is none' do + before do + create_auth + end + + it 'sets up a body if there is none', vcr: { cassette_name: 'valid_login' } do %w[Token Sign Cuit].each do |key| expect(bill.body['Auth'].fetch(key, nil)).not_to be_nil end @@ -61,11 +65,11 @@ module Bravo describe '#setup_bill' do before do - bill.net = 100 + bill.net = 100 bill.aliciva_id = 2 - bill.document_number = '30710151543' - bill.iva_condition = :responsable_inscripto - bill.concept = 'Servicios' + bill.document_number = '30710151543' + bill.iva_condition = :responsable_inscripto + bill.concept = 'Servicios' end it 'uses today dates when due and service dates are null', @@ -95,7 +99,7 @@ module Bravo end describe '#authorize' do - describe 'for facturas' do + describe 'for invoices' do Bravo::BILL_TYPE[Bravo.own_iva_cond].keys.each do |target_iva_cond| describe "issued to #{ target_iva_cond }" do Bravo::BILL_TYPE[Bravo.own_iva_cond][target_iva_cond].keys.each do |bill_type| @@ -110,7 +114,7 @@ module Bravo expect(bill.authorized?).to be_falsey - expect(bill.authorize).to be_truthy + expect(bill.authorize).to be_truthy expect(bill.authorized?).to be_truthy response = bill.response @@ -123,5 +127,10 @@ module Bravo end end end + + def create_auth + Authorization.create(cuit: '20287740027', pkey_path: 'spec/fixtures/certs/pkey', + cert_path: 'spec/fixtures/certs/cert.crt') + end end end diff --git a/spec/bravo/wsaa_spec.rb b/spec/bravo/wsaa_spec.rb index 95480aa..6e263bc 100644 --- a/spec/bravo/wsaa_spec.rb +++ b/spec/bravo/wsaa_spec.rb @@ -34,7 +34,8 @@ describe '.login' do it 'writes the auth file', vcr: { cassette_name: 'login' } do expect(File).to receive(:write) - Bravo::Wsaa.login('/tmp/bravo_test.yml') + Bravo::Wsaa.login_to_file('/tmp/bravo_test.yml', 'spec/fixtures/certs/pkey', + 'spec/fixtures/certs/cert.crt') end end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 626ca37..14c28ff 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -17,9 +17,6 @@ config.run_all_when_everything_filtered = true end -Bravo.pkey = 'spec/fixtures/certs/pkey' -Bravo.cert = 'spec/fixtures/certs/cert.crt' -Bravo.cuit = ENV['CUIT'] || '20287740027' Bravo.sale_point = ENV['SALE'] || '0002' Bravo.default_concepto = 'Productos y Servicios' Bravo.default_documento = 'CUIT' @@ -27,12 +24,4 @@ Bravo.own_iva_cond = :responsable_inscripto Bravo.logger = { log: false, level: :info } Bravo.openssl_bin = ENV["TRAVIS"] ? 'openssl' : '/usr/local/Cellar/openssl/1.0.2a-1/bin/openssl' -Bravo::AuthData.environment = :test - -# TODO: refactor into actual validations - -raise(Bravo::NullOrInvalidAttribute.new, 'Please set CUIT env variable.') unless Bravo.cuit - -[Bravo.pkey, Bravo.cert].each do |file| - raise(Bravo::MissingCertificate.new, "No existe #{ file }") unless File.exist?("#{ file }") -end +Bravo.environment = :test From 98e51681f45d83fc847d2f1bbfab8895ada9964c Mon Sep 17 00:00:00 2001 From: Leandro Marcucci Date: Thu, 18 Aug 2016 23:41:23 -0300 Subject: [PATCH 35/35] Progress --- README.md | 10 +-------- bravo.gemspec | 1 - lib/bravo/authorization.rb | 1 - spec/fixtures/certs/cert.crt | 41 ++++++++++++++++++------------------ spec/spec_helper.rb | 4 +--- 5 files changed, 23 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 8f10416..7d77774 100644 --- a/README.md +++ b/README.md @@ -7,15 +7,7 @@ ## Requisitos -Para poder autorizar comprobantes mediante el WSFE, AFIP requiere de ciertos pasos detallados a continuación: - -* Generar una clave privada para la aplicación. -* Generar un CSR (Certificate Signing Request) utilizando el número de CUIT que emitirá los comprobantes y la clave privada del paso anterior. Se deberá enviar a AFIP el CSR para obtener el Certificado X.509 que se utilizará en el proceso de autorización de comprobantes. - * Para el entorno de Testing, se debe enviar el X.509 por email a _webservices@afip.gov.ar_. - * Para el entorno de Producción, el trámite se hace a través del portal [AFIP](http://www.afip.gov.ar) -* El certificado X.509 y la clave privada son utilizados por el WSAA para identificarse frente a los servicios de AFIP. El WSAA permite obtener el token y signature a incluir en el header de autenticacion en cada request que hagamaos a los servicios de AFIP. - - +Guía para obtener certificados WSAA [aquí](http://www.afip.gov.ar/ws/WSAA/cert-req-howto.txt) ### OpenSSL Para cumplir con los requisitos de encriptación del [Web Service de Autenticación y Autorización](http://www.afip.gov.ar/ws/WSAA/README.txt) (WSAA), Bravo requiere [OpenSSL](http://openssl.org) en cualquier versión igual o posterior a la 1.0.0a. diff --git a/bravo.gemspec b/bravo.gemspec index 3023220..49beba4 100644 --- a/bravo.gemspec +++ b/bravo.gemspec @@ -25,7 +25,6 @@ Gem::Specification.new do |gem| gem.add_development_dependency(%{rspec}, ["~> 3.2.0"]) gem.add_development_dependency(%{rake}, ["~> 10.4.0"]) gem.add_development_dependency(%{vcr}, ["~> 2.9.0"]) - gem.add_development_dependency(%{simplecov}, ["~> 0.9.0"]) gem.add_development_dependency(%{rubocop}, ["~> 0.31.0"]) gem.add_development_dependency(%{webmock}, ["~> 1.18.0"]) end diff --git a/lib/bravo/authorization.rb b/lib/bravo/authorization.rb index dc49541..e47cb01 100644 --- a/lib/bravo/authorization.rb +++ b/lib/bravo/authorization.rb @@ -17,7 +17,6 @@ def initialize(cuit, pkey, cert) @cuit = cuit @pkey_path = validate_path(pkey) @cert_path = validate_path(cert) - self end # Returns the WSFE url for the specified environment diff --git a/spec/fixtures/certs/cert.crt b/spec/fixtures/certs/cert.crt index 130a426..f3f901d 100755 --- a/spec/fixtures/certs/cert.crt +++ b/spec/fixtures/certs/cert.crt @@ -1,22 +1,23 @@ -----BEGIN CERTIFICATE----- -MIIDpjCCAo6gAwIBAgIIMKwSjJcsnxgwDQYJKoZIhvcNAQEFBQAwQzElMCMGA1UE +MIIDqDCCApCgAwIBAgIIAbB+njdEwWUwDQYJKoZIhvcNAQEFBQAwQzElMCMGA1UE AwwcQUZJUCBUZXN0aW5nIENvbXB1dGFkb3JlcyBDQTENMAsGA1UECgwEQUZJUDEL -MAkGA1UEBhMCQVIwHhcNMTMwMjAxMTMwNDA4WhcNMTUxMDI5MTMwNDA4WjBEMQ0w -CwYDVQQDDARMZWFuMRkwFwYDVQQFExBDVUlUIDIwMjg3NzQwMDI3MQswCQYDVQQK -DAJObzELMAkGA1UEBhMCQVIwgZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBAKQE -xPczGh5ou0LAUWa7SnScc+ojF0cDYyYXeRRNZ3r69IRjfmNjQQ9EczW+PC1h/HNv -Z2ZAu0401uO0ySAF4ncShUrwwNj4da2B9WXEcTMZ8DixApwevTQ2AKQXJ1XOzGGJ -fhyxbexDrF6EOjBno30h4NfCMQjzFqOeeXhryEwnAgMBAAGjggEfMIIBGzAMBgNV -HRMBAf8EAjAAMA4GA1UdDwEB/wQEAwIF4DAdBgNVHQ4EFgQUfmtL+JcFml+AhNjA -X9GN4QDCAM0wHwYDVR0jBBgwFoAURHTutJwm31bhwQ3rVwuQGTY9lgEwgboGA1Ud -IASBsjCBrzCBrAYOKwYBBAGBu2MBAgECAQEwgZkwgZYGCCsGAQUFBwICMIGJHoGG -AEMAZQByAHQAaQBmAGkAYwBhAGQAbwAgAHAAYQByAGEAIABjAG8AbQBwAHUAdABh -AGQAbwByAGUAcwAgAHMAbwBsAG8AIAB2AGEAbABpAGQAbwAgAGUAbgAgAGUAbgB0 -AG8AcgBuAG8AcwAgAGQAZQAgAGQAZQBzAGEAcgByAG8AbABsAG8wDQYJKoZIhvcN -AQEFBQADggEBAEW7IiTcq58vccBhfxfu2eV1UTV7/It5royRwrXwIeNqoC76KoB8 -XsX3GeV+INlNyqDZ1fibfbCLsT7Vm3lkCIHX3ELKjm/hQSO/m0rIdMg4DknwsFYw -gUOjRsXAsChcCMiXgKnv080PehtvOa2AviLabp4Db6N9ghLMTT6gHkumqu8joKY5 -Qkldrf+ENK5SDE+oDdU11+eMykx2rRAHg2riffUEWaPnlu+RVThsokiz3ieDqY51 -i+KBfNWrnCvgh+Iz+5GosMH4neI9NXtPZo1ZrFef8aV/I/vnIBUD3/jHIUnyK62d -rPNCRze2CLIc4qT9YeUpD6NbbQ0giSPZ8Dc= ------END CERTIFICATE----- \ No newline at end of file +MAkGA1UEBhMCQVIwHhcNMTUxMTA1MTYyOTQyWhcNMTgwODAxMTYyOTQyWjBGMQ0w +CwYDVQQDDARsZWFuMRkwFwYDVQQFExBDVUlUIDIwMjg3NzQwMDI3MQ0wCwYDVQQK +DARsZWFuMQswCQYDVQQGEwJBUjCBnzANBgkqhkiG9w0BAQEFAAOBjQAwgYkCgYEA +pATE9zMaHmi7QsBRZrtKdJxz6iMXRwNjJhd5FE1nevr0hGN+Y2NBD0RzNb48LWH8 +c29nZkC7TjTW47TJIAXidxKFSvDA2Ph1rYH1ZcRxMxnwOLECnB69NDYApBcnVc7M +YYl+HLFt7EOsXoQ6MGejfSHg18IxCPMWo555eGvITCcCAwEAAaOCAR8wggEbMAwG +A1UdEwEB/wQCMAAwDgYDVR0PAQH/BAQDAgXgMB0GA1UdDgQWBBR+a0v4lwWaX4CE +2MBf0Y3hAMIAzTAfBgNVHSMEGDAWgBREdO60nCbfVuHBDetXC5AZNj2WATCBugYD +VR0gBIGyMIGvMIGsBg4rBgEEAYG7YwECAQIBATCBmTCBlgYIKwYBBQUHAgIwgYke +gYYAQwBlAHIAdABpAGYAaQBjAGEAZABvACAAcABhAHIAYQAgAGMAbwBtAHAAdQB0 +AGEAZABvAHIAZQBzACAAcwBvAGwAbwAgAHYAYQBsAGkAZABvACAAZQBuACAAZQBu +AHQAbwByAG4AbwBzACAAZABlACAAZABlAHMAYQByAHIAbwBsAGwAbzANBgkqhkiG +9w0BAQUFAAOCAQEAKuoAl73cac55OcPNHTNCuBhAkKg4mXo5YLCvuqVRw5Icnm1s +VwloGLSpJnz5dhanoDnKF1XP5aBaovl5brD8mCaALHQhpf5oGDHO4cZkdsQ1kEhL +Rjk6hxq2q2a/JZhnBc9M2ckSkuBOfPdLIMIgR8HebwheQW9oEcm0o1p71Dk8cXpX +5z77Ce5ONnS+EKylmZ1bIT95C7eY0WLadg8qTOq4D59GJvho0Z6//r+cXg4u+UAz +KDPZMto9rbxYiUr5G5LteSVIrqT2SkYmjM41i3URZRD0aKxsuloChRN0K2tsx5Qk +w+pbIKcQOEah1sHvgLpRKJDZxCDp7oU5cLgDuA== +-----END CERTIFICATE----- + diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 14c28ff..e25f4bc 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -2,9 +2,7 @@ require 'bravo' require 'rspec' require 'vcr' -require 'simplecov' require 'byebug' -# SimpleCov.start VCR.configure do |c| c.cassette_library_dir = 'spec/fixtures/vcr_cassettes' @@ -23,5 +21,5 @@ Bravo.default_moneda = :peso Bravo.own_iva_cond = :responsable_inscripto Bravo.logger = { log: false, level: :info } -Bravo.openssl_bin = ENV["TRAVIS"] ? 'openssl' : '/usr/local/Cellar/openssl/1.0.2a-1/bin/openssl' +Bravo.openssl_bin = ENV["TRAVIS"] ? 'openssl' : '/usr/local/Cellar/openssl/1.0.2d_1/bin/openssl' Bravo.environment = :test