Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion Gemfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
source "http://rubygems.org"
gem "savon", "~> 0.7.8"
gem "savon", "~> 2.0"
gem "wsaa-ruby", "~> 0.2"
gem "rack", "~> 2.0"

group :development, :test do
gem "rspec", "~> 3.0"
Expand Down
54 changes: 51 additions & 3 deletions Gemfile.lock
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,47 @@ GEM
specs:
addressable (2.9.0)
public_suffix (>= 2.0.2, < 8.0)
akami (1.3.2)
gyoku (>= 0.4.0)
nokogiri
bigdecimal (4.1.2)
builder (3.3.0)
crack (1.0.1)
bigdecimal
rexml
date (3.5.1)
diff-lcs (1.6.2)
gyoku (1.4.0)
builder (>= 2.1.2)
rexml (~> 3.0)
hashdiff (1.2.1)
httpi (3.0.1)
rack
logger (1.7.0)
mail (2.9.1)
logger
mini_mime (>= 0.1.1)
net-imap
net-pop
net-smtp
mini_mime (1.1.5)
mini_portile2 (2.8.9)
net-imap (0.4.25)
date
net-protocol
net-pop (0.1.2)
net-protocol
net-protocol (0.2.2)
timeout
net-smtp (0.5.1)
net-protocol
nokogiri (1.15.7)
mini_portile2 (~> 2.8.2)
racc (~> 1.4)
nori (2.6.0)
public_suffix (5.1.1)
racc (1.8.1)
rack (2.2.24)
rexml (3.4.4)
rspec (3.13.2)
rspec-core (~> 3.13.0)
Expand All @@ -25,24 +58,39 @@ GEM
diff-lcs (>= 1.2.0, < 2.0)
rspec-support (~> 3.13.0)
rspec-support (3.13.7)
savon (0.7.9)
savon (2.15.0)
akami (~> 1.2)
builder (>= 2.1.2)
crack (>= 0.1.4)
gyoku (~> 1.2)
httpi (>= 2.4.5, < 5)
mail (~> 2.5)
nokogiri (>= 1.8.1)
nori (~> 2.4)
wasabi (>= 3.7, < 6)
timeout (0.6.1)
vcr (6.4.0)
wasabi (4.0.0)
addressable
httpi (~> 3.0)
nokogiri (>= 1.13.9)
webmock (3.26.2)
addressable (>= 2.8.0)
crack (>= 0.3.2)
hashdiff (>= 0.4.0, < 2.0.0)
wsaa-ruby (0.2.0)
savon (~> 2.0)

PLATFORMS
ruby

DEPENDENCIES
bundler
rack (~> 2.0)
rspec (~> 3.0)
savon (~> 0.7.8)
savon (~> 2.0)
vcr (~> 6.0)
webmock (~> 3.0)
wsaa-ruby (~> 0.2)

BUNDLED WITH
2.4.22
3 changes: 1 addition & 2 deletions lib/bravo.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
require "bravo/version"
require "bravo/constants"
require "savon"
require "wsaa"
require "bravo/core_ext/float"
require "bravo/core_ext/hash"
require "bravo/core_ext/string"
Expand All @@ -23,6 +24,4 @@ class NullOrInvalidAttribute < StandardError; end
def auth_hash
{"Token" => Bravo::TOKEN, "Sign" => Bravo::SIGN, "Cuit" => Bravo.cuit}
end

Savon::Request.log = false unless (Bravo.verbose == "true") || (ENV["VERBOSE"] == true)
end
35 changes: 19 additions & 16 deletions lib/bravo/auth_data.rb
Original file line number Diff line number Diff line change
@@ -1,28 +1,31 @@
module Bravo
##
# Fetches authentication credentials from AFIP's WSAA service.
#
# Uses the wsaa-ruby gem to obtain TOKEN and SIGN credentials.
# Credentials are cached by the gem until expiration.
class AuthData

class << self
##
# Fetches authentication credentials and sets TOKEN/SIGN constants.
#
# @raise [Wsaa::ConfigurationError] If pkey or cert configuration is invalid.
# @raise [Wsaa::AuthenticationError] If authentication fails.
def fetch
unless File.exists?(Bravo.pkey)
raise "Archivo de llave privada no encontrado en #{Bravo.pkey}"
end
environment = Bravo.auth_url.include?("homo") ? :testing : :production

unless File.exists?(Bravo.cert)
raise "Archivo certificado no encontrado en #{Bravo.cert}"
Wsaa.configure do |config|
config.pkey = Bravo.pkey
config.cert = Bravo.cert
config.service = "wsfe"
config.environment = environment
end

todays_datafile = "/tmp/bravo_#{Time.new.strftime('%d_%m_%Y')}.yml"
opts = "-u #{Bravo.auth_url}"
opts += " -k #{Bravo.pkey}"
opts += " -c #{Bravo.cert}"

unless File.exists?(todays_datafile)
%x(#{File.dirname(__FILE__)}/../../wsaa-client.sh #{opts})
end
credentials = Wsaa.authenticate

@data = YAML.load_file(todays_datafile).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) unless Bravo.const_defined?(:TOKEN)
Bravo.const_set(:SIGN, credentials.sign) unless Bravo.const_defined?(:SIGN)
end
end
end
Expand Down
22 changes: 11 additions & 11 deletions lib/bravo/bill.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
module Bravo
##
# Represents an electronic invoice (factura electronica) for AFIP.
#
# Creates and authorizes invoices through the WSFE service.
class Bill
attr_reader :client, :base_imp, :total
attr_accessor :net, :doc_num, :iva_cond, :documento, :concepto, :moneda,
Expand All @@ -7,7 +11,7 @@ class Bill

def initialize(attrs = {})
Bravo::AuthData.fetch
@client = Savon::Client.new(Bravo.service_url)
@client = Savon.client(wsdl: Bravo.service_url, log: false)
@body = {"Auth" => Bravo.auth_hash}
@net = attrs[:net] || 0
self.documento = attrs[:documento] || Bravo.default_documento
Expand Down Expand Up @@ -45,10 +49,7 @@ def iva_sum

def authorize
setup_bill
response = client.fecae_solicitar do |soap|
soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
soap.body = body
end
response = client.call(:fecae_solicitar, message: body)

setup_response(response.to_hash)
self.authorized?
Expand Down Expand Up @@ -97,10 +98,11 @@ def setup_bill
end

def next_bill_number
resp = client.fe_comp_ultimo_autorizado do |s|
s.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
s.body = {"Auth" => Bravo.auth_hash, "PtoVta" => Bravo.sale_point, "CbteTipo" => cbte_type}
end
resp = client.call(:fe_comp_ultimo_autorizado, message: {
"Auth" => Bravo.auth_hash,
"PtoVta" => Bravo.sale_point,
"CbteTipo" => cbte_type
})

resp.to_hash[:fe_comp_ultimo_autorizado_response][:fe_comp_ultimo_autorizado_result][:cbte_nro].to_i + 1
end
Expand All @@ -118,8 +120,6 @@ def header(cbte_type)
end

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]
Expand Down
48 changes: 18 additions & 30 deletions lib/bravo/reference.rb
Original file line number Diff line number Diff line change
@@ -1,67 +1,54 @@
# encoding: utf-8
module Bravo
##
# Queries reference data from AFIP's WSFE service.
#
# Provides methods to fetch IVA rates, invoice types, and other parameters.
class Reference
attr_reader :client

def initialize
Bravo::AuthData.fetch
@client = Savon::Client.new(Bravo.service_url)
@client = Savon.client(wsdl: Bravo.service_url, log: false)
end

def alic_iva
body = {"Auth" => Bravo.auth_hash}

response = client.fe_param_get_tipos_iva do |soap|
soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
soap.body = body
end

response.to_hash
response = client.call(:fe_param_get_tipos_iva, message: {"Auth" => Bravo.auth_hash})
parse_fe_param_get_tipos_iva_response(response.to_hash)
end

def tipos_cbte
body = {"Auth" => Bravo.auth_hash}

response = client.fe_param_get_tipos_cbte do |soap|
soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
soap.body = body
end

response.to_hash
response = client.call(:fe_param_get_tipos_cbte, message: {"Auth" => Bravo.auth_hash})
parse_fe_param_get_tipos_cbte_response(response.to_hash)
end

def condicion_iva_receptor(clase_cmp = nil)
body = {"Auth" => Bravo.auth_hash}
body["ClaseCmp"] = clase_cmp if clase_cmp

response = client.fe_param_get_condicion_iva_receptor do |soap|
soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
soap.body = body
end

response = client.call(:fe_param_get_condicion_iva_receptor, message: body)
parse_condicion_iva_receptor_response(response.to_hash)
end

def exchange_rate(moneda)
return 1 if moneda == :peso

response = client.fe_param_get_cotizacion do |soap|
soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
soap.body = {"Auth" => Bravo.auth_hash, "MonId" => Bravo::MONEDAS[moneda][:codigo]}
end
response = client.call(:fe_param_get_cotizacion, message: {
"Auth" => Bravo.auth_hash,
"MonId" => Bravo::MONEDAS[moneda][:codigo]
})

response.to_hash[:fe_param_get_cotizacion_response][:fe_param_get_cotizacion_result][:result_get][:mon_cotiz].to_f
end

def ultimo_comprobante(cbte_tipo, pto_vta = nil)
pto_vta ||= Bravo.sale_point

response = client.fe_comp_ultimo_autorizado do |soap|
soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
soap.body = {"Auth" => Bravo.auth_hash, "PtoVta" => pto_vta, "CbteTipo" => cbte_tipo}
end
response = client.call(:fe_comp_ultimo_autorizado, message: {
"Auth" => Bravo.auth_hash,
"PtoVta" => pto_vta,
"CbteTipo" => cbte_tipo
})

response.to_hash[:fe_comp_ultimo_autorizado_response][:fe_comp_ultimo_autorizado_result][:cbte_nro].to_i
end
Expand Down Expand Up @@ -89,6 +76,7 @@ def parse_fe_param_get_tipos_iva_response(response_hash)
}
end
end

def parse_condicion_iva_receptor_response(response_hash)
result = response_hash[:fe_param_get_condicion_iva_receptor_response][:fe_param_get_condicion_iva_receptor_result]

Expand Down
10 changes: 7 additions & 3 deletions spec/bravo/authorizer_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,12 @@

describe "Authorizer" do
it "should read credentials on initialize" do
Bravo.pkey = '/path/to/pkey'
Bravo.cert = '/path/to/cert'

authorizer = Bravo::Authorizer.new
authorizer.pkey.should == 'spec/fixtures/pkey'
authorizer.cert.should == 'spec/fixtures/cert.crt'

authorizer.pkey.should == '/path/to/pkey'
authorizer.cert.should == '/path/to/cert'
end
end
end
Loading
Loading