bravo 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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