bravo 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,5 @@
1
+ lib/**/*.rb
2
+ bin/*
3
+ -
4
+ features/**/*.feature
5
+ LICENSE.txt
data/Gemfile ADDED
@@ -0,0 +1,17 @@
1
+ source "http://rubygems.org"
2
+ gem "savon", "~> 0.7.8"
3
+
4
+ group :development do
5
+ platforms :ruby_18 do
6
+ gem "ruby-debug"
7
+ end
8
+ platforms :ruby_19 do
9
+ gem 'ruby-debug-base19', "0.11.24"
10
+ gem 'ruby-debug19', "0.11.6"
11
+ end
12
+ gem "rspec", "~> 2.4.0"
13
+ gem "bundler", "~> 1.0.0"
14
+ gem "jeweler", "~> 1.5.1"
15
+ gem "rcov", ">= 0"
16
+ end
17
+
@@ -0,0 +1,57 @@
1
+ GEM
2
+ remote: http://rubygems.org/
3
+ specs:
4
+ archive-tar-minitar (0.5.2)
5
+ builder (3.0.0)
6
+ columnize (0.3.2)
7
+ crack (0.1.8)
8
+ diff-lcs (1.1.2)
9
+ git (1.2.5)
10
+ jeweler (1.5.2)
11
+ bundler (~> 1.0.0)
12
+ git (>= 1.2.5)
13
+ rake
14
+ linecache (0.43)
15
+ linecache19 (0.5.11)
16
+ ruby_core_source (>= 0.1.4)
17
+ rake (0.8.7)
18
+ rcov (0.9.9)
19
+ rspec (2.4.0)
20
+ rspec-core (~> 2.4.0)
21
+ rspec-expectations (~> 2.4.0)
22
+ rspec-mocks (~> 2.4.0)
23
+ rspec-core (2.4.0)
24
+ rspec-expectations (2.4.0)
25
+ diff-lcs (~> 1.1.2)
26
+ rspec-mocks (2.4.0)
27
+ ruby-debug (0.10.4)
28
+ columnize (>= 0.1)
29
+ ruby-debug-base (~> 0.10.4.0)
30
+ ruby-debug-base (0.10.4)
31
+ linecache (>= 0.3)
32
+ ruby-debug-base19 (0.11.24)
33
+ columnize (>= 0.3.1)
34
+ linecache19 (>= 0.5.11)
35
+ ruby_core_source (>= 0.1.4)
36
+ ruby-debug19 (0.11.6)
37
+ columnize (>= 0.3.1)
38
+ linecache19 (>= 0.5.11)
39
+ ruby-debug-base19 (>= 0.11.19)
40
+ ruby_core_source (0.1.4)
41
+ archive-tar-minitar (>= 0.5.2)
42
+ savon (0.7.9)
43
+ builder (>= 2.1.2)
44
+ crack (>= 0.1.4)
45
+
46
+ PLATFORMS
47
+ ruby
48
+
49
+ DEPENDENCIES
50
+ bundler (~> 1.0.0)
51
+ jeweler (~> 1.5.1)
52
+ rcov
53
+ rspec (~> 2.4.0)
54
+ ruby-debug
55
+ ruby-debug-base19 (= 0.11.24)
56
+ ruby-debug19 (= 0.11.6)
57
+ savon (~> 0.7.8)
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Leandro Marcucci & Vurbia Technologies International Inc
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,53 @@
1
+ h1. Bravo: la gema de facturación electrónica argentina
2
+
3
+ "-Bravo-":http://images.coveralia.com/audio/b/Bravo-Desierto_Sin_Amor-Frontal.jpg Bravo permite la obtención del "-C.A.E.-":http://www.muevamueva.com/masmusica/latina/cae/images/fotos.5.gif C.A.E. (Código de Autorización Electrónico) por medio del Web Service de Facturación Electrónica provisto por la AFIP.
4
+
5
+ h2. Instalación
6
+
7
+ <pre>
8
+ gem install bravo
9
+ </pre>
10
+
11
+ o
12
+
13
+ <pre>
14
+ gem 'bravo'
15
+ </pre>
16
+
17
+ en tu @Gemfile@
18
+
19
+
20
+ h2. Configuraci&oacute;n
21
+
22
+ Los servicios de AFIP requieren la utilizaci&oacute;n del Web Service de Autorizaci&oacute;n y Autenticaci&oacute;n ("wsaa readme":http://www.afip.gov.ar/ws/WSAA/README.txt)
23
+
24
+ Luego de cumplidos los pasos indicados en el readme, basta con configurar Bravo con la ruta a los archivos:
25
+
26
+ <pre>
27
+ Bravo.pkey = "spec/fixtures/pkey"
28
+ Bravo.cert = "spec/fixtures/cert.crt"
29
+ </pre>
30
+
31
+ y exportar la variable CUIT con el n&uacute;mero de cuit usado para obtener los certificados:
32
+
33
+ @export CUIT=_numerodecuit_@
34
+
35
+ Bravo acepta m&aacute;s opciones, para m&aacute;s detalles ver el "spec_helper":https://github.com/vurbia/Bravo/blob/master/spec/spec_helper.rb
36
+
37
+ h2. Uso
38
+
39
+ El uso de la gema se centra en el metodo @authorize@. Este m&eacute;todo invoca @FECAESolicitar@ y devuelve el resultado, que de ser exitoso incluye el CAE y su fecha de vencimento (ver "bill_spec":https://github.com/vurbia/Bravo/blob/master/spec/bravo/bill_spec.rb#L87)
40
+
41
+
42
+ h2. TODO list
43
+
44
+ * rdoc
45
+ * mensajes de error m&aacute;s completos
46
+
47
+
48
+
49
+ h2. Agradecimientos
50
+
51
+ * Emilio Tagua por sus consejos y contribuciones.
52
+
53
+ Copyright (c) 2010 Leandro Marcucci & Vurbia Technologies International Inc. See LICENSE.txt for further details.
@@ -0,0 +1,50 @@
1
+ require 'rubygems'
2
+ require 'bundler'
3
+ begin
4
+ Bundler.setup(:default, :development)
5
+ rescue Bundler::BundlerError => e
6
+ $stderr.puts e.message
7
+ $stderr.puts "Run `bundle install` to install missing gems"
8
+ exit e.status_code
9
+ end
10
+ require 'rake'
11
+
12
+ require 'jeweler'
13
+ Jeweler::Tasks.new do |gem|
14
+ # gem is a Gem::Specification... see http://docs.rubygems.org/read/chapter/20 for more options
15
+ gem.name = "bravo"
16
+ gem.homepage = "http://github.com/leanucci/bravo"
17
+ gem.license = "MIT"
18
+ gem.summary = "Adaptador AFIP wsfe."
19
+ gem.description = "Adaptador para el Web Service de Facturacion Electronica de AFIP"
20
+ gem.email = "leanucci@vurbia.com"
21
+ gem.authors = ["Leandro Marcucci"]
22
+ # Include your dependencies below. Runtime dependencies are required when using your gem,
23
+ # and development dependencies are only needed for development (ie running rake tasks, tests, etc)
24
+ # gem.add_runtime_dependency 'jabber4r', '> 0.1'
25
+ # gem.add_development_dependency 'rspec', '> 1.2.3'
26
+ end
27
+ Jeweler::RubygemsDotOrgTasks.new
28
+
29
+ require 'rspec/core'
30
+ require 'rspec/core/rake_task'
31
+ RSpec::Core::RakeTask.new(:spec) do |spec|
32
+ spec.pattern = FileList['spec/**/*_spec.rb']
33
+ end
34
+
35
+ RSpec::Core::RakeTask.new(:rcov) do |spec|
36
+ spec.pattern = 'spec/**/*_spec.rb'
37
+ spec.rcov = true
38
+ end
39
+
40
+ task :default => :spec
41
+
42
+ require 'rake/rdoctask'
43
+ Rake::RDocTask.new do |rdoc|
44
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
45
+
46
+ rdoc.rdoc_dir = 'rdoc'
47
+ rdoc.title = "bravo #{version}"
48
+ rdoc.rdoc_files.include('README*')
49
+ rdoc.rdoc_files.include('lib/**/*.rb')
50
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.1.1
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" } #added according to rspec2 book
@@ -0,0 +1,88 @@
1
+ # Generated by jeweler
2
+ # DO NOT EDIT THIS FILE DIRECTLY
3
+ # Instead, edit Jeweler::Tasks in Rakefile, and run 'rake gemspec'
4
+ # -*- encoding: utf-8 -*-
5
+
6
+ Gem::Specification.new do |s|
7
+ s.name = %q{bravo}
8
+ s.version = "0.1.0"
9
+
10
+ s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
+ s.authors = ["Leandro Marcucci"]
12
+ s.date = %q{2011-03-02}
13
+ s.description = %q{Adaptador para el Web Service de Facturacion Electronica de AFIP}
14
+ s.email = %q{leanucci@vurbia.com}
15
+ s.extra_rdoc_files = [
16
+ "LICENSE.txt",
17
+ "README.textile"
18
+ ]
19
+ s.files = [
20
+ ".document",
21
+ "Gemfile",
22
+ "Gemfile.lock",
23
+ "LICENSE.txt",
24
+ "README.textile",
25
+ "Rakefile",
26
+ "VERSION",
27
+ "autotest/discover.rb",
28
+ "bravo.gemspec",
29
+ "lib/bravo.rb",
30
+ "lib/bravo/auth_data.rb",
31
+ "lib/bravo/authorizer.rb",
32
+ "lib/bravo/bill.rb",
33
+ "lib/bravo/constants.rb",
34
+ "lib/bravo/core_ext/float.rb",
35
+ "lib/bravo/version.rb",
36
+ "spec/bravo/auth_data_spec.rb",
37
+ "spec/bravo/authorizer_spec.rb",
38
+ "spec/bravo/bill_spec.rb",
39
+ "spec/spec_helper.rb",
40
+ "wsaa-client.sh"
41
+ ]
42
+ s.homepage = %q{http://github.com/leanucci/bravo}
43
+ s.licenses = ["MIT"]
44
+ s.require_paths = ["lib"]
45
+ s.rubygems_version = %q{1.3.7}
46
+ s.summary = %q{Adaptador AFIP wsfe.}
47
+ s.test_files = [
48
+ "spec/bravo/auth_data_spec.rb",
49
+ "spec/bravo/authorizer_spec.rb",
50
+ "spec/bravo/bill_spec.rb",
51
+ "spec/spec_helper.rb"
52
+ ]
53
+
54
+ if s.respond_to? :specification_version then
55
+ current_version = Gem::Specification::CURRENT_SPECIFICATION_VERSION
56
+ s.specification_version = 3
57
+
58
+ if Gem::Version.new(Gem::VERSION) >= Gem::Version.new('1.2.0') then
59
+ s.add_runtime_dependency(%q<savon>, ["~> 0.7.8"])
60
+ s.add_development_dependency(%q<ruby-debug>, [">= 0"])
61
+ s.add_development_dependency(%q<ruby-debug-base19>, ["= 0.11.24"])
62
+ s.add_development_dependency(%q<ruby-debug19>, ["= 0.11.6"])
63
+ s.add_development_dependency(%q<rspec>, ["~> 2.4.0"])
64
+ s.add_development_dependency(%q<bundler>, ["~> 1.0.0"])
65
+ s.add_development_dependency(%q<jeweler>, ["~> 1.5.1"])
66
+ s.add_development_dependency(%q<rcov>, [">= 0"])
67
+ else
68
+ s.add_dependency(%q<savon>, ["~> 0.7.8"])
69
+ s.add_dependency(%q<ruby-debug>, [">= 0"])
70
+ s.add_dependency(%q<ruby-debug-base19>, ["= 0.11.24"])
71
+ s.add_dependency(%q<ruby-debug19>, ["= 0.11.6"])
72
+ s.add_dependency(%q<rspec>, ["~> 2.4.0"])
73
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
74
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
75
+ s.add_dependency(%q<rcov>, [">= 0"])
76
+ end
77
+ else
78
+ s.add_dependency(%q<savon>, ["~> 0.7.8"])
79
+ s.add_dependency(%q<ruby-debug>, [">= 0"])
80
+ s.add_dependency(%q<ruby-debug-base19>, ["= 0.11.24"])
81
+ s.add_dependency(%q<ruby-debug19>, ["= 0.11.6"])
82
+ s.add_dependency(%q<rspec>, ["~> 2.4.0"])
83
+ s.add_dependency(%q<bundler>, ["~> 1.0.0"])
84
+ s.add_dependency(%q<jeweler>, ["~> 1.5.1"])
85
+ s.add_dependency(%q<rcov>, [">= 0"])
86
+ end
87
+ end
88
+
@@ -0,0 +1,23 @@
1
+ require "bundler/setup"
2
+ require "bravo/version"
3
+ require "bravo/constants"
4
+ require "savon"
5
+ require "bravo/core_ext/float"
6
+ module Bravo
7
+
8
+ class NullOrInvalidAttribute < StandardError; end
9
+
10
+ autoload :Authorizer, "bravo/authorizer"
11
+ autoload :AuthData, "bravo/auth_data"
12
+ autoload :Bill, "bravo/bill"
13
+ autoload :Constants, "bravo/constants"
14
+
15
+
16
+ extend self
17
+ attr_accessor :cuit, :sale_point, :service_url, :default_documento, :pkey, :cert,
18
+ :default_concepto, :default_moneda, :own_iva_cond
19
+
20
+ def auth_hash
21
+ {"Token" => Bravo::TOKEN, "Sign" => Bravo::SIGN, "Cuit" => Bravo.cuit}
22
+ end
23
+ end
@@ -0,0 +1,22 @@
1
+ module Bravo
2
+ class AuthData
3
+
4
+ class << self
5
+ def fetch
6
+ todays_datafile = "/tmp/bravo_#{Time.new.strftime('%d_%m_%Y')}.yml"
7
+ opts = "-u https://wsaahomo.afip.gov.ar/ws/services/LoginCms"
8
+ keys_root = "/Users/leanucci/Xephstratus/afip/claves/"
9
+ opts += " -k #{Bravo.pkey}"
10
+ opts += " -c #{Bravo.cert}"
11
+
12
+ unless File.exists?(todays_datafile)
13
+ %x(#{File.dirname(__FILE__)}/../../wsaa-client.sh #{opts})
14
+ end
15
+
16
+ @data = YAML.load_file(todays_datafile).each do |k, v|
17
+ Bravo.const_set(k.to_s.upcase, v) unless Bravo.const_defined?(k.to_s.upcase)
18
+ end
19
+ end
20
+ end
21
+ end
22
+ end
@@ -0,0 +1,10 @@
1
+ module Bravo
2
+ class Authorizer
3
+ attr_reader :pkey, :cert
4
+
5
+ def initialize
6
+ @pkey = Bravo.pkey
7
+ @cert = Bravo.cert
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,122 @@
1
+ module Bravo
2
+ class Bill
3
+ attr_reader :client, :base_imp, :total
4
+ attr_accessor :net, :doc_num, :iva_cond, :documento, :concepto, :moneda,
5
+ :due_date, :from, :to, :aliciva_id, :fch_serv_desde, :fch_serv_hasta,
6
+ :body, :response
7
+
8
+ def initialize(attrs = {})
9
+ Bravo::AuthData.fetch
10
+ @client = Savon::Client.new(Bravo.service_url)
11
+ @body = {"Auth" => Bravo.auth_hash}
12
+ @net = attrs[:net] || 0
13
+ self.documento = attrs[:documento] || Bravo.default_documento
14
+ self.moneda = attrs[:moneda] || Bravo.default_moneda
15
+ self.iva_cond = attrs[:iva_cond]
16
+ self.concepto = attrs[:concepto] || Bravo.default_concepto
17
+ end
18
+
19
+ def cbte_type
20
+ Bravo::BILL_TYPE[Bravo.own_iva_cond][iva_cond] ||
21
+ raise(NullOrInvalidAttribute.new, "Please choose a valid document type.")
22
+ end
23
+
24
+ def exchange_rate
25
+ return 1 if moneda == :peso
26
+ response = client.fe_param_get_cotizacion do |soap|
27
+ soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
28
+ soap.body = body.merge!({"MonId" => Bravo::MONEDAS[moneda][:codigo]})
29
+ end
30
+ response.to_hash[:fe_param_get_cotizacion_response][:fe_param_get_cotizacion_result][:result_get][:mon_cotiz].to_f
31
+ end
32
+
33
+ def total
34
+ @total = net.zero? ? 0 : net + iva_sum
35
+ end
36
+
37
+ def iva_sum
38
+ @iva_sum = net * Bravo::ALIC_IVA[aliciva_id][1]
39
+ @iva_sum.round_up_with_precision(2)
40
+ end
41
+
42
+ def authorize
43
+ setup_bill
44
+ response = client.fecae_solicitar do |soap|
45
+ soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
46
+ soap.body = body
47
+ end
48
+
49
+ response = response.to_hash
50
+
51
+ setup_response(response)
52
+ self.authorized?
53
+ end
54
+
55
+ def setup_bill
56
+ today = Time.new.strftime('%Y%m%d')
57
+
58
+ fecaereq = {"FeCAEReq" => {
59
+ "FeCabReq" => Bravo::Bill.header(cbte_type),
60
+ "FeDetReq" => {
61
+ "FECAEDetRequest" => {
62
+ "Concepto" => Bravo::CONCEPTOS[concepto],
63
+ "DocTipo" => Bravo::DOCUMENTOS[documento],
64
+ "CbteFch" => today,
65
+ "ImpTotConc" => 0.00,
66
+ "MonId" => Bravo::MONEDAS[moneda][:codigo],
67
+ "MonCotiz" => exchange_rate,
68
+ "ImpOpEx" => 0.00,
69
+ "ImpTrib" => 0.00,
70
+ "Iva" => {
71
+ "AlicIva" => {
72
+ "Id" => "5",
73
+ "BaseImp" => net,
74
+ "Importe" => iva_sum}}}}}}
75
+
76
+ detail = fecaereq["FeCAEReq"]["FeDetReq"]["FECAEDetRequest"]
77
+
78
+ detail["DocNro"] = doc_num
79
+ detail["ImpNeto"] = net.to_f
80
+ detail["ImpIVA"] = iva_sum
81
+ detail["ImpTotal"] = total
82
+ detail["CbteDesde"] = detail["CbteHasta"] = next_bill_number
83
+
84
+ unless concepto == 0
85
+ detail.merge!({"FchServDesde" => fch_serv_desde || today,
86
+ "FchServHasta" => fch_serv_hasta || today,
87
+ "FchVtoPago" => due_date || today})
88
+ end
89
+
90
+ body.merge!(fecaereq)
91
+ end
92
+
93
+ def next_bill_number
94
+ resp = client.fe_comp_ultimo_autorizado do |s|
95
+ s.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
96
+ s.body = {"Auth" => Bravo.auth_hash, "PtoVta" => Bravo.sale_point, "CbteTipo" => "1"}
97
+ end
98
+
99
+ resp.to_hash[:fe_comp_ultimo_autorizado_response][:fe_comp_ultimo_autorizado_result][:cbte_nro].to_i + 1
100
+ end
101
+
102
+ def authorized?
103
+ !response.nil? && response.header_result == "A" && response.detail_result == "A"
104
+ end
105
+
106
+ private
107
+
108
+ class << self
109
+ def header(cbte_type)#todo sacado de la factura
110
+ {"CantReg" => "1", "CbteTipo" => cbte_type, "PtoVta" => "2"}
111
+ end
112
+ end
113
+
114
+ def setup_response(response)
115
+ detail_response = response[:fecae_solicitar_response][:fecae_solicitar_result][:fe_det_resp][:fecae_det_response]
116
+ header_response = response[:fecae_solicitar_response][:fecae_solicitar_result][:fe_cab_resp]
117
+
118
+ response_struct = Struct.new("Response", :header_result, :detail_result, :cae, :cae_due_date, :authorized_on)
119
+ self.response = response_struct.new(header_response[:resultado], detail_response[:resultado], detail_response[:cae], detail_response[:cae_fch_vto], header_response[:fch_proceso])
120
+ end
121
+ end
122
+ end
@@ -0,0 +1,42 @@
1
+ # encoding: utf-8
2
+ module Bravo
3
+ CBTE_TIPO = {
4
+ "01"=>"Factura A",
5
+ "02"=>"Nota de Débito A",
6
+ "03"=>"Nota de Crédito A",
7
+ "04"=>"Recibos A",
8
+ "05"=>"Notas de Venta al contado A",
9
+ "06"=>"Factura B",
10
+ "07"=>"Nota de Debito B",
11
+ "08"=>"Nota de Credito B",
12
+ "09"=>"Recibos B",
13
+ "10"=>"Notas de Venta al contado B",
14
+ "34"=>"Cbtes. A del Anexo I, Apartado A,inc.f),R.G.Nro. 1415",
15
+ "35"=>"Cbtes. B del Anexo I,Apartado A,inc. f),R.G. Nro. 1415",
16
+ "39"=>"Otros comprobantes A que cumplan con R.G.Nro. 1415",
17
+ "40"=>"Otros comprobantes B que cumplan con R.G.Nro. 1415",
18
+ "60"=>"Cta de Vta y Liquido prod. A",
19
+ "61"=>"Cta de Vta y Liquido prod. B",
20
+ "63"=>"Liquidacion A",
21
+ "64"=>"Liquidacion B"
22
+ }
23
+
24
+ CONCEPTOS = {"Productos"=>"01", "Servicios"=>"02", "Productos y Servicios"=>"03"}
25
+
26
+ 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"}
27
+
28
+ MONEDAS = {
29
+ :peso => {:codigo => "PES", :nombre =>"Pesos Argentinos"},
30
+ :dolar => {:codigo => "DOL", :nombre =>"Dolar Estadounidense"},
31
+ :real => {:codigo => "012", :nombre =>"Real"},
32
+ :euro => {:codigo => "060", :nombre =>"Euro"},
33
+ :oro => {:codigo => "049", :nombre =>"Gramos de Oro Fino"}
34
+ }
35
+
36
+ ALIC_IVA = [["03", 0],
37
+ ["04", 0.105],
38
+ ["05", 0.21],
39
+ ["06", 0.27]]
40
+
41
+ BILL_TYPE = {:responsable_inscripto => {:responsable_inscripto => "01", :consumidor_final => "06"}}
42
+ end
@@ -0,0 +1,8 @@
1
+ class Float
2
+ def round_with_precision(precision = nil)
3
+ precision.nil? ? round : (self * (10 ** precision)).round / (10 ** precision).to_f
4
+ end
5
+ def round_up_with_precision(precision = nil)
6
+ precision.nil? ? round : ((self * (10 ** precision)).round + 1) / (10 ** precision).to_f
7
+ end
8
+ end
@@ -0,0 +1,3 @@
1
+ module Bravo
2
+ VERSION = "0.1.1"
3
+ end
@@ -0,0 +1,12 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "AuthData" do
4
+ it "should create constants for todays data" do
5
+ Bravo::AuthData.fetch
6
+ if RUBY_VERSION >= "1.9"
7
+ Bravo.constants.should include(:TOKEN, :SIGN)
8
+ else
9
+ Bravo.constants.should include("TOKEN", "SIGN")
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,9 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Authorizer" do
4
+ it "should read credentials on initialize" do
5
+ authorizer = Bravo::Authorizer.new
6
+ authorizer.pkey.should == 'spec/fixtures/pkey'
7
+ authorizer.cert.should == 'spec/fixtures/cert.crt'
8
+ end
9
+ end
@@ -0,0 +1,102 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
2
+
3
+ describe "Bill" do
4
+ it "should setup a header hash" do
5
+ @header = Bravo::Bill.header(0)
6
+ @header.size.should == 3
7
+ ["CantReg", "CbteTipo", "PtoVta"].each do |key|
8
+ @header.has_key?(key).should == true
9
+ end
10
+ end
11
+
12
+ describe "instance" do
13
+ before(:each) {@bill = Bravo::Bill.new}
14
+
15
+ it "should initialize according to Bravo defaults" do
16
+ @bill.client.class.name.should == "Savon::Client"
17
+ ["Token", "Sign", "Cuit"].each do |key|
18
+ @bill.body["Auth"][key].should_not == nil
19
+ end
20
+ @bill.documento.should == Bravo.default_documento
21
+ @bill.moneda.should == Bravo.default_moneda
22
+ end
23
+
24
+ it "should calculate it's cbte_tipo for Responsable Inscripto" do
25
+ @bill.iva_cond = :responsable_inscripto
26
+ @bill.cbte_type.should == "01"
27
+ end
28
+
29
+ it "should calculate it's cbte_tipo for Consumidor Final" do
30
+ @bill.iva_cond = :consumidor_final
31
+ @bill.cbte_type.should == "06"
32
+ end
33
+
34
+ it "raise error on nil iva cond" do
35
+ @bill.iva_cond = 12
36
+ expect{@bill.cbte_type}.to raise_error(Bravo::NullOrInvalidAttribute)
37
+ end
38
+
39
+ it "should fetch non Peso currency's exchange rate" do
40
+ @bill.moneda = :dolar
41
+ @bill.exchange_rate.to_i.should be > 0
42
+ end
43
+
44
+ it "should return 1 for Peso currency" do
45
+ @bill.moneda = :peso
46
+ @bill.exchange_rate.should == 1
47
+ end
48
+
49
+ it "should calculate the IVA array values" do
50
+ @bill.iva_cond = :responsable_inscripto
51
+ @bill.moneda = :peso
52
+ @bill.net = 100.89
53
+ @bill.aliciva_id = 2
54
+
55
+ @bill.iva_sum.should be_within(0.05).of(21.18)
56
+ @bill.total.should be_within(0.05).of(122.07)
57
+ end
58
+
59
+ it "should use give due date an service dates, or todays date" do
60
+ @bill.net = 100
61
+ @bill.aliciva_id = 2
62
+ @bill.doc_num = "30710151543"
63
+ @bill.iva_cond = :responsable_inscripto
64
+ @bill.concepto = "Servicios"
65
+
66
+ @bill.setup_bill
67
+
68
+ detail = @bill.body["FeCAEReq"]["FeDetReq"]["FECAEDetRequest"]
69
+
70
+ detail["FchServDesde"].should == Time.new.strftime('%Y%m%d')
71
+ detail["FchServHasta"].should == Time.new.strftime('%Y%m%d')
72
+ detail["FchVtoPago"].should == Time.new.strftime('%Y%m%d')
73
+
74
+ @bill.due_date = Date.new(2011, 12, 10).strftime('%Y%m%d')
75
+ @bill.fch_serv_desde = Date.new(2011, 11, 01).strftime('%Y%m%d')
76
+ @bill.fch_serv_hasta = Date.new(2011, 11, 30).strftime('%Y%m%d')
77
+
78
+ @bill.setup_bill
79
+
80
+ detail = @bill.body["FeCAEReq"]["FeDetReq"]["FECAEDetRequest"]
81
+
82
+ detail["FchServDesde"].should == "20111101"
83
+ detail["FchServHasta"].should == "20111130"
84
+ detail["FchVtoPago"].should == "20111210"
85
+ end
86
+
87
+ it "should authorize a valid bill" do
88
+ @bill.net = 1000000
89
+ @bill.aliciva_id = 2
90
+ @bill.doc_num = "30710151543"
91
+ @bill.iva_cond = :responsable_inscripto
92
+ @bill.concepto = "Servicios"
93
+
94
+ @bill.authorized?.should == false
95
+
96
+ @bill.authorize.should == true
97
+
98
+ @bill.authorized?.should == true
99
+ @bill.response.cae.length.should == 14
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,24 @@
1
+ $:.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
2
+ require 'bravo'
3
+ require 'rspec'
4
+ require 'ruby-debug'
5
+
6
+ class SpecHelper
7
+ include Savon::Logger
8
+ end
9
+
10
+ # Requires supporting files with custom matchers and macros, etc,
11
+ # in ./support/ and its subdirectories.
12
+ Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
13
+
14
+ Savon::Request.log = false unless ENV["VERBOSE"] == "true"
15
+
16
+ Bravo.pkey = "spec/fixtures/pkey"
17
+ Bravo.cert = "spec/fixtures/cert.crt"
18
+ Bravo.cuit = ENV["CUIT"] || raise(Bravo::NullOrInvalidAttribute.new, "Please set CUIT env variable.")
19
+ Bravo.sale_point = "0002"
20
+ Bravo.service_url = "http://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL"
21
+ Bravo.default_concepto = "Productos y Servicios"
22
+ Bravo.default_documento = "CUIT"
23
+ Bravo.default_moneda = :peso
24
+ Bravo.own_iva_cond = :responsable_inscripto
@@ -0,0 +1,171 @@
1
+ #!/bin/bash
2
+ # FUNCTION: Bash script to get a TA from WSAA
3
+ # AUTHOR: Gerardo Fisanotti - AFIP/SDG-SI/DIITEC/DEARIN - 15-nov-2010
4
+ # Dependencies: curl, openssl >= 1.0, xmllint
5
+ #
6
+ # Modify following definitions according to your environment:
7
+ #
8
+ # URL=https://wsaahomo.afip.gov.ar/ws/services/LoginCms # WSAA URL
9
+ # KEY=spec/fixtures/pkey # file containing the private key in PEM format
10
+ # CRT=spec/fixtures/cert.crt # file containing the X.509 certificate in PEM format
11
+ TAFN="TA.xml" # file name of the output file
12
+ # modify next line if you need a proxy to get to the Internet or comment it out
13
+ # if you don't need a proxy
14
+ # export https_proxy="http://10.20.152.112:80"
15
+ #
16
+ # No further modifications should be needed below this line
17
+ #==============================================================================
18
+ function MakeTRA()
19
+ #
20
+ # Generate the XML containing the Access Ticket Request (TRA)
21
+ #
22
+ {
23
+ # FROM=$(date -j -f "%a %b %d %T %Z %Y" "`date -v0H -v0M -v0S`" "+%s")
24
+ # TO=$(date -j -f "%a %b %d %T %Z %Y" "`date -v23H -v59M -v59S`" "+%s")
25
+ FROM=$(date "+%Y-%m-%dT00:00:00-03:00")
26
+ TO=$(date "+%Y-%m-%dT23:59:59-03:00")
27
+ ID=$(date "+%s")
28
+ TRA=$(cat <<EOF
29
+ <?xml version="1.0" encoding="UTF-8"?>
30
+ <loginTicketRequest version="1.0">
31
+ <header>
32
+ <uniqueId>$ID</uniqueId>
33
+ <generationTime>$FROM</generationTime>
34
+ <expirationTime>$TO</expirationTime>
35
+ </header>
36
+ <service>wsfe</service>
37
+ </loginTicketRequest>
38
+ EOF
39
+ )
40
+ }
41
+ #------------------------------------------------------------------------------
42
+ function MakeCMS()
43
+ #
44
+ # Generate de CMS container (TRA + sign + certificate)
45
+ #
46
+ {
47
+ CMS=$(
48
+ echo "$TRA" |
49
+ /usr/local/ssl/bin/openssl cms -sign -in /dev/stdin -signer $CRT -inkey $KEY -nodetach \
50
+ -outform der |
51
+ /usr/local/ssl/bin/openssl base64 -e
52
+ )
53
+ }
54
+ #------------------------------------------------------------------------------
55
+ function MakeSOAPrequest()
56
+ #
57
+ # Generate the SOAP request XML
58
+ #
59
+ {
60
+ REQUEST=$(cat <<EOF
61
+ <?xml version="1.0" encoding="UTF-8"?>
62
+ <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://wsaa.view.sua.dvadac.desein.afip.gov">
63
+ <SOAP-ENV:Body>
64
+ <ns1:loginCms>
65
+ <ns1:in0>
66
+ $CMS
67
+ </ns1:in0>
68
+ </ns1:loginCms>
69
+ </SOAP-ENV:Body>
70
+ </SOAP-ENV:Envelope>
71
+ EOF
72
+ )
73
+ }
74
+ #------------------------------------------------------------------------------
75
+ function CallWSAA()
76
+ #
77
+ # Invoke WSAA sending SOAP request XML to LoginCMS method
78
+ #
79
+ {
80
+ RESPONSE=$(
81
+ echo "$REQUEST" |
82
+ curl -k -H 'Content-Type: application/soap+xml; action=""' -d @- $URL
83
+ )
84
+ echo "$REQUEST"
85
+ }
86
+ #------------------------------------------------------------------------------
87
+ function ParseTA()
88
+ #
89
+ # Try to parse the results obtained from WSAA
90
+ #
91
+ {
92
+ TOKEN=$(
93
+ echo "$RESPONSE" |
94
+ grep token |
95
+ sed -e 's/&lt;token&gt;//' |
96
+ sed -e 's/&lt;\/token&gt;//' |
97
+ sed -e 's/ //g'
98
+ )
99
+ SIGN=$(
100
+ echo "$RESPONSE" |
101
+ grep sign |
102
+ sed -e 's/&lt;sign&gt;//' |
103
+ sed -e 's/&lt;\/sign&gt;//' |
104
+ sed -e 's/ //g'
105
+ )
106
+ # If we did not get TOKEN, then it was a SOAP Fault, show the error message
107
+ # and exit
108
+ #
109
+ if [ "$TOKEN" == "" ]
110
+ then
111
+ echo "ERROR: "
112
+ echo "$(echo "$RESPONSE" | xmllint --format - | grep faultstring)"
113
+ exit 1
114
+ fi
115
+ }
116
+ #------------------------------------------------------------------------------
117
+ function WriteTA()
118
+ #
119
+ # Write the token and sign to the output file
120
+ #
121
+ {
122
+ cat <<EOF > $TAFN
123
+ <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
124
+ <loginTicketResponse version="1">
125
+ <credentials>
126
+ <token>$TOKEN</token>
127
+ <sign>$SIGN</sign>
128
+ </credentials>
129
+ </loginTicketResponse>
130
+ EOF
131
+ }
132
+
133
+ function WriteYAML()
134
+ {
135
+ cat <<EOF > /tmp/bravo_$(date +"%d_%m_%Y").yml
136
+ token: '$TOKEN'
137
+ sign: '$SIGN'
138
+ EOF
139
+ }
140
+ #------------------------------------------------------------------------------
141
+ #
142
+ # MAIN program
143
+ #
144
+ # If we were invoked with a service name in arg #1, use it
145
+ #[ $# -eq 1 ] && SERVICE=$1
146
+ # otherwise, ask for it
147
+ #[ $# -eq 0 ] && read -p "Service name: " SERVICE
148
+
149
+ # Parse commandline arguments
150
+ while getopts 'k:u:c:' OPTION
151
+ do
152
+ case $OPTION in
153
+ c) CRT=$OPTARG
154
+ ;;
155
+ k) KEY=$OPTARG
156
+ ;;
157
+ u) URL=$OPTARG
158
+ ;;
159
+ esac
160
+ done
161
+ shift $(($OPTIND - 1))
162
+ MakeTRA # Generate TRA
163
+ MakeCMS # Generate CMS (TRA + signature + certificate)
164
+ MakeSOAPrequest # Generate the SOAP request XML
165
+ CallWSAA # Invoke WSAA sending SOAP request
166
+ ParseTA # Parse the WSAA SOAP response, extract Token and Sign
167
+ # WriteTA # Write an abbreviated TA.xml with Token and Sign only
168
+ WriteYAML
169
+ echo "Access Ticket acquired, written to: $TAFN" # Inform success and exit
170
+ echo $REQUEST
171
+ echo $TRA
metadata ADDED
@@ -0,0 +1,214 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: bravo
3
+ version: !ruby/object:Gem::Version
4
+ hash: 27
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 1
9
+ - 0
10
+ version: 0.1.0
11
+ platform: ruby
12
+ authors:
13
+ - Leandro Marcucci
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2011-03-02 00:00:00 -03:00
19
+ default_executable:
20
+ dependencies:
21
+ - !ruby/object:Gem::Dependency
22
+ name: savon
23
+ prerelease: false
24
+ requirement: &id001 !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ hash: 19
30
+ segments:
31
+ - 0
32
+ - 7
33
+ - 8
34
+ version: 0.7.8
35
+ type: :runtime
36
+ version_requirements: *id001
37
+ - !ruby/object:Gem::Dependency
38
+ name: ruby-debug
39
+ prerelease: false
40
+ requirement: &id002 !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ">="
44
+ - !ruby/object:Gem::Version
45
+ hash: 3
46
+ segments:
47
+ - 0
48
+ version: "0"
49
+ type: :development
50
+ version_requirements: *id002
51
+ - !ruby/object:Gem::Dependency
52
+ name: ruby-debug-base19
53
+ prerelease: false
54
+ requirement: &id003 !ruby/object:Gem::Requirement
55
+ none: false
56
+ requirements:
57
+ - - "="
58
+ - !ruby/object:Gem::Version
59
+ hash: 3
60
+ segments:
61
+ - 0
62
+ - 11
63
+ - 24
64
+ version: 0.11.24
65
+ type: :development
66
+ version_requirements: *id003
67
+ - !ruby/object:Gem::Dependency
68
+ name: ruby-debug19
69
+ prerelease: false
70
+ requirement: &id004 !ruby/object:Gem::Requirement
71
+ none: false
72
+ requirements:
73
+ - - "="
74
+ - !ruby/object:Gem::Version
75
+ hash: 63
76
+ segments:
77
+ - 0
78
+ - 11
79
+ - 6
80
+ version: 0.11.6
81
+ type: :development
82
+ version_requirements: *id004
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ prerelease: false
86
+ requirement: &id005 !ruby/object:Gem::Requirement
87
+ none: false
88
+ requirements:
89
+ - - ~>
90
+ - !ruby/object:Gem::Version
91
+ hash: 31
92
+ segments:
93
+ - 2
94
+ - 4
95
+ - 0
96
+ version: 2.4.0
97
+ type: :development
98
+ version_requirements: *id005
99
+ - !ruby/object:Gem::Dependency
100
+ name: bundler
101
+ prerelease: false
102
+ requirement: &id006 !ruby/object:Gem::Requirement
103
+ none: false
104
+ requirements:
105
+ - - ~>
106
+ - !ruby/object:Gem::Version
107
+ hash: 23
108
+ segments:
109
+ - 1
110
+ - 0
111
+ - 0
112
+ version: 1.0.0
113
+ type: :development
114
+ version_requirements: *id006
115
+ - !ruby/object:Gem::Dependency
116
+ name: jeweler
117
+ prerelease: false
118
+ requirement: &id007 !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ~>
122
+ - !ruby/object:Gem::Version
123
+ hash: 1
124
+ segments:
125
+ - 1
126
+ - 5
127
+ - 1
128
+ version: 1.5.1
129
+ type: :development
130
+ version_requirements: *id007
131
+ - !ruby/object:Gem::Dependency
132
+ name: rcov
133
+ prerelease: false
134
+ requirement: &id008 !ruby/object:Gem::Requirement
135
+ none: false
136
+ requirements:
137
+ - - ">="
138
+ - !ruby/object:Gem::Version
139
+ hash: 3
140
+ segments:
141
+ - 0
142
+ version: "0"
143
+ type: :development
144
+ version_requirements: *id008
145
+ description: Adaptador para el Web Service de Facturacion Electronica de AFIP
146
+ email: leanucci@vurbia.com
147
+ executables: []
148
+
149
+ extensions: []
150
+
151
+ extra_rdoc_files:
152
+ - LICENSE.txt
153
+ - README.textile
154
+ files:
155
+ - .document
156
+ - Gemfile
157
+ - Gemfile.lock
158
+ - LICENSE.txt
159
+ - README.textile
160
+ - Rakefile
161
+ - VERSION
162
+ - autotest/discover.rb
163
+ - bravo.gemspec
164
+ - lib/bravo.rb
165
+ - lib/bravo/auth_data.rb
166
+ - lib/bravo/authorizer.rb
167
+ - lib/bravo/bill.rb
168
+ - lib/bravo/constants.rb
169
+ - lib/bravo/core_ext/float.rb
170
+ - lib/bravo/version.rb
171
+ - spec/bravo/auth_data_spec.rb
172
+ - spec/bravo/authorizer_spec.rb
173
+ - spec/bravo/bill_spec.rb
174
+ - spec/spec_helper.rb
175
+ - wsaa-client.sh
176
+ has_rdoc: true
177
+ homepage: http://github.com/leanucci/bravo
178
+ licenses:
179
+ - MIT
180
+ post_install_message:
181
+ rdoc_options: []
182
+
183
+ require_paths:
184
+ - lib
185
+ required_ruby_version: !ruby/object:Gem::Requirement
186
+ none: false
187
+ requirements:
188
+ - - ">="
189
+ - !ruby/object:Gem::Version
190
+ hash: 3
191
+ segments:
192
+ - 0
193
+ version: "0"
194
+ required_rubygems_version: !ruby/object:Gem::Requirement
195
+ none: false
196
+ requirements:
197
+ - - ">="
198
+ - !ruby/object:Gem::Version
199
+ hash: 3
200
+ segments:
201
+ - 0
202
+ version: "0"
203
+ requirements: []
204
+
205
+ rubyforge_project:
206
+ rubygems_version: 1.6.0
207
+ signing_key:
208
+ specification_version: 3
209
+ summary: Adaptador AFIP wsfe.
210
+ test_files:
211
+ - spec/bravo/auth_data_spec.rb
212
+ - spec/bravo/authorizer_spec.rb
213
+ - spec/bravo/bill_spec.rb
214
+ - spec/spec_helper.rb