Afip 0.8.2 → 0.8.5

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 423fa3ea9b764029af3e4c7910def5a4f5037125ddc481dd55fa3daa41ee66ae
4
- data.tar.gz: 04d2a8faaed0d63660f65f0c04ea19b8294cc1c1a21c0e2b9d8a2599b68798d9
3
+ metadata.gz: 1fe8d659a0e6df6e63828786c78261e24c6ff3161389dc68d4acdaad14c61174
4
+ data.tar.gz: 06a6cf1566d575a28facc865904892f8497cebc58c4fb20114f65de8c7d4a784
5
5
  SHA512:
6
- metadata.gz: f4533a6716f515077477e543a39079a6a84768553b9e65d52f4c49d5c88d6075abe84f9caebf790e3c7ec6c25c4281301f697d8009fa4d76c4901e0a8c3ddd62
7
- data.tar.gz: 560c2dea89ed78fc6d98a9e55dd1c60ed44e3c5c4e3df27c32b52c5bccc9261049f15a1eee28952867624c5ce6e599aabf972a4463f84e0190a858961daeea0e
6
+ metadata.gz: 11a01e92e4355c882abe6a908b378bf58edb2c35d6f096764bc3b2da9c5943aeec3429f0dea0027ff28adea88091dc62fe5e2ff6b2fc51c3b6da3b0f4c9646e0
7
+ data.tar.gz: ccec3d5eac35c165e8f8d15b7c9e6f0ca7a0d005d7afd257650d382cd21984799a7c1ca1cd802ba6cc67c4fec42c1de46dc5cb4c6b7e671b5d38eae10bf4c05c
data/.gitignore ADDED
@@ -0,0 +1,8 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /_yardoc/
4
+ /coverage/
5
+ /doc/
6
+ /pkg/
7
+ /spec/reports/
8
+ /tmp/
data/Afip-0.8.3.gem ADDED
Binary file
data/Afip-0.8.4.gem ADDED
Binary file
data/Afip.gemspec ADDED
@@ -0,0 +1,40 @@
1
+
2
+ lib = File.expand_path("../lib", __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require "Afip/version"
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "Afip"
8
+ spec.version = Afip::VERSION
9
+ spec.authors = ["Facundo A. Díaz Martínez"]
10
+ spec.email = ["facundo_diaz_martinez@hotmail.com"]
11
+
12
+ spec.summary = %q{Comunicacion con AFIP}
13
+ spec.description = %q{Gema para la comunicacion con los Web Services de AFIP.}
14
+ spec.homepage = "https://www.desideral.com"
15
+ spec.license = "MIT"
16
+
17
+ # Prevent pushing this gem to RubyGems.org. To allow pushes either set the 'allowed_push_host'
18
+ # to allow pushing to a single host or delete this section to allow pushing to any host.
19
+ if spec.respond_to?(:metadata)
20
+ spec.metadata["allowed_push_host"] = "https://rubygems.org"
21
+ else
22
+ raise "RubyGems 2.0 or newer is required to protect against " \
23
+ "public gem pushes."
24
+ end
25
+
26
+ # Specify which files should be added to the gem when it is released.
27
+ # The `git ls-files -z` loads the files in the RubyGem that have been added into git.
28
+ spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do
29
+ `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|features)/}) }
30
+ end
31
+
32
+ spec.bindir = "exe"
33
+ spec.executables = spec.files.grep(%r{^exe/}) { |f| File.basename(f) }
34
+ spec.require_paths = ["lib"]
35
+
36
+ spec.add_dependency "savon"
37
+ spec.add_dependency "httpi"
38
+ spec.add_development_dependency "bundler", "~> 1.16"
39
+ spec.add_development_dependency "rake", "~> 10.0"
40
+ end
data/Gemfile ADDED
@@ -0,0 +1,6 @@
1
+ source "https://rubygems.org"
2
+
3
+ git_source(:github) {|repo_name| "https://github.com/#{repo_name}" }
4
+
5
+ # Specify your gem's dependencies in Afip.gemspec
6
+ gemspec
data/Gemfile.lock ADDED
@@ -0,0 +1,48 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ Afip (0.8.4)
5
+ httpi
6
+ savon
7
+
8
+ GEM
9
+ remote: https://rubygems.org/
10
+ specs:
11
+ akami (1.3.1)
12
+ gyoku (>= 0.4.0)
13
+ nokogiri
14
+ builder (3.2.3)
15
+ gyoku (1.3.1)
16
+ builder (>= 2.1.2)
17
+ httpi (2.4.4)
18
+ rack
19
+ socksify
20
+ mini_portile2 (2.4.0)
21
+ nokogiri (1.9.1)
22
+ mini_portile2 (~> 2.4.0)
23
+ nori (2.6.0)
24
+ rack (2.0.6)
25
+ rake (10.5.0)
26
+ savon (2.12.0)
27
+ akami (~> 1.2)
28
+ builder (>= 2.1.2)
29
+ gyoku (~> 1.2)
30
+ httpi (~> 2.3)
31
+ nokogiri (>= 1.8.1)
32
+ nori (~> 2.4)
33
+ wasabi (~> 3.4)
34
+ socksify (1.7.1)
35
+ wasabi (3.5.0)
36
+ httpi (~> 2.0)
37
+ nokogiri (>= 1.4.2)
38
+
39
+ PLATFORMS
40
+ ruby
41
+
42
+ DEPENDENCIES
43
+ Afip!
44
+ bundler (~> 1.16)
45
+ rake (~> 10.0)
46
+
47
+ BUNDLED WITH
48
+ 1.17.1
data/LICENSE.txt ADDED
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2018 Facundo A. Díaz Martínez
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in
13
+ all copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21
+ THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,39 @@
1
+ # Afip
2
+
3
+ Welcome to your new gem! In this directory, you'll find the files you need to be able to package up your Ruby library into a gem. Put your Ruby code in the file `lib/Afip`. To experiment with that code, run `bin/console` for an interactive prompt.
4
+
5
+ TODO: Delete this and the text above, and describe your gem
6
+
7
+ ## Installation
8
+
9
+ Add this line to your application's Gemfile:
10
+
11
+ ```ruby
12
+ gem 'Afip'
13
+ ```
14
+
15
+ And then execute:
16
+
17
+ $ bundle
18
+
19
+ Or install it yourself as:
20
+
21
+ $ gem install Afip
22
+
23
+ ## Usage
24
+
25
+ TODO: Write usage instructions here
26
+
27
+ ## Development
28
+
29
+ After checking out the repo, run `bin/setup` to install dependencies. You can also run `bin/console` for an interactive prompt that will allow you to experiment.
30
+
31
+ To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org).
32
+
33
+ ## Contributing
34
+
35
+ Bug reports and pull requests are welcome on GitHub at https://github.com/[USERNAME]/Afip.
36
+
37
+ ## License
38
+
39
+ The gem is available as open source under the terms of the [MIT License](https://opensource.org/licenses/MIT).
data/Rakefile ADDED
@@ -0,0 +1,2 @@
1
+ require "bundler/gem_tasks"
2
+ task :default => :spec
data/bin/console ADDED
@@ -0,0 +1,14 @@
1
+ #!/usr/bin/env ruby
2
+
3
+ require "bundler/setup"
4
+ require "Afip"
5
+
6
+ # You can add fixtures and/or initialization code here to make experimenting
7
+ # with your gem easier. You can also use a different console, if you like.
8
+
9
+ # (If you use this, don't forget to add pry to your Gemfile!)
10
+ # require "pry"
11
+ # Pry.start
12
+
13
+ require "irb"
14
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ bundle install
7
+
8
+ # Do any other automated setup that you need to do here
@@ -0,0 +1,71 @@
1
+ module Afip
2
+
3
+ # This class handles authorization data
4
+ #
5
+ class AuthData
6
+
7
+ class << self
8
+
9
+ attr_accessor :environment, :todays_data_file_name
10
+
11
+ # Fetches WSAA Authorization Data to build the datafile for the day.
12
+ # It requires the private key file and the certificate to exist and
13
+ # to be configured as Afip.pkey and Afip.cert
14
+ #
15
+ def fetch(service = "wsfe")
16
+ unless File.exists?(Afip.pkey)
17
+ raise "Archivo de llave privada no encontrado en #{ Afip.pkey }"
18
+ end
19
+
20
+ unless File.exists?(Afip.cert)
21
+ raise "Archivo certificado no encontrado en #{ Afip.cert }"
22
+ end
23
+
24
+ unless File.exists?(todays_data_file_name)
25
+ Afip::Wsaa.login(service)
26
+ end
27
+
28
+ YAML.load_file(todays_data_file_name).each do |k, v|
29
+ Afip.const_set(k.to_s.upcase, v) unless Afip.const_defined?(k.to_s.upcase)
30
+ end
31
+ end
32
+
33
+ # Returns the authorization hash, containing the Token, Signature and Cuit
34
+ # @return [Hash]
35
+ #
36
+ def auth_hash(service = "wsfe")
37
+ fetch unless Afip.constants.include?(:TOKEN) && Afip.constants.include?(:SIGN)
38
+ case service
39
+ when "wsfe"
40
+ { 'Token' => Afip::TOKEN, 'Sign' => Afip::SIGN, 'Cuit' => Afip.cuit }
41
+ when "ws_sr_padron_a4"
42
+ { 'token' => Afip::TOKEN, 'sign' => Afip::SIGN, 'cuitRepresentado' => Afip.cuit }
43
+ when "wsctg"
44
+ { 'token' => Afip::TOKEN, 'sign' => Afip::SIGN, 'cuitRepresentado' => Afip.cuit }
45
+ end
46
+ end
47
+
48
+ # Returns the right wsaa url for the specific environment
49
+ # @return [String]
50
+ #
51
+ def wsaa_url
52
+ Afip::URLS[Afip.environment][:wsaa]
53
+ end
54
+
55
+ # Returns the right wsfe url for the specific environment
56
+ # @return [String]
57
+ #
58
+ def wsfe_url
59
+ raise 'Environment not sent to either :test or :production' unless Afip::URLS.keys.include? environment
60
+ Afip::URLS[Afip.environment][:wsfe]
61
+ end
62
+
63
+ # Creates the data file name for a cuit number and the current day
64
+ # @return [String]
65
+ #
66
+ def todays_data_file_name
67
+ @todays_data_file = "/tmp/#{environment.to_s}_Afip_#{ Afip.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml"
68
+ end
69
+ end
70
+ end
71
+ end
@@ -0,0 +1,10 @@
1
+ module Afip
2
+ class Authorizer
3
+ attr_reader :pkey, :cert
4
+
5
+ def initialize
6
+ @pkey = Afip.pkey
7
+ @cert = Afip.cert
8
+ end
9
+ end
10
+ end
data/lib/Afip/bill.rb ADDED
@@ -0,0 +1,197 @@
1
+ module Afip
2
+ class Bill
3
+ attr_reader :cbte_type, :body, :response, :fecha_emision, :total, :client
4
+ attr_accessor :net, :doc_num, :iva_cond, :documento, :concepto, :moneda,
5
+ :due_date, :fch_serv_desde, :fch_serv_hasta, :fch_emision,
6
+ :ivas, :sale_point
7
+
8
+ def initialize(attrs={})
9
+ @client = Bill.set_client
10
+ @sale_point = attrs[:sale_point]
11
+ @body = { "Auth" => Afip.auth_hash }
12
+ @net = attrs[:net] || 0
13
+ @documento = attrs[:documento] || Afip.default_documento
14
+ @moneda = attrs[:moneda] || Afip.default_moneda
15
+ @iva_cond = attrs[:iva_cond]
16
+ @concepto = attrs[:concepto] || Afip.default_concepto
17
+ @ivas = attrs[:ivas] || Array.new # [ 1, 100.00, 10.50 ], [ 2, 100.00, 21.00 ]
18
+ @fecha_emision = attrs[:fch_emision] || Time.new
19
+ @fch_serv_hasta = attrs[:fch_serv_hasta]
20
+ @fch_serv_desde = attrs[:fch_serv_desde]
21
+ @due_date = attrs[:due_date]
22
+ @cbte_type = Afip::BILL_TYPE[Afip.own_iva_cond][iva_cond]
23
+ @total = net.zero? ? 0 : net + iva_sum
24
+ end
25
+
26
+ def self.get_ptos_vta
27
+ client = set_client
28
+ body = { "Auth" => Afip.auth_hash }
29
+ response = client.call(:fe_param_get_ptos_venta, message: body)
30
+ if response.body[:fe_param_get_ptos_venta_response][:fe_param_get_ptos_venta_result][:errors].nil?
31
+ if response.body[:fe_param_get_ptos_venta_response][:fe_param_get_ptos_venta_result][:result_get][:pto_venta].is_a?(Hash)
32
+ response.body[:fe_param_get_ptos_venta_response][:fe_param_get_ptos_venta_result][:result_get][:pto_venta][:nro]
33
+ else
34
+ response.body[:fe_param_get_ptos_venta_response][:fe_param_get_ptos_venta_result][:result_get][:pto_venta].map{|r| r[:nro]}
35
+ end
36
+ else
37
+ []
38
+ end
39
+ end
40
+
41
+ def self.set_client
42
+ Afip::AuthData.fetch
43
+ @client = Savon.client(
44
+ wsdl: Afip.service_url,
45
+ namespaces: { "xmlns" => "http://ar.gov.afip.dif.FEV1/" },
46
+ log_level: :debug,
47
+ ssl_cert_key_file: Afip.pkey,
48
+ ssl_cert_file: Afip.cert,
49
+ ssl_verify_mode: :none,
50
+ read_timeout: 90,
51
+ open_timeout: 90,
52
+ headers: { "Accept-Encoding" => "gzip, deflate", "Connection" => "Keep-Alive" }
53
+ )
54
+ end
55
+
56
+ def authorize
57
+ body = setup_bill
58
+ response = client.call(:fecae_solicitar, message: body)
59
+ setup_response(response.to_hash)
60
+ authorized?
61
+ end
62
+
63
+ def setup_bill
64
+ array_ivas = Array.new
65
+ ivas.each{ |i|
66
+ array_ivas << {
67
+ "Id" => i[0],
68
+ "BaseImp" => i[1].round(2),
69
+ "Importe" => i[2].round(2) }
70
+ }
71
+
72
+ fecaereq = {
73
+ "FeCAEReq" => {
74
+ "FeCabReq" => Afip::Bill.header(cbte_type, sale_point),
75
+ "FeDetReq" => {
76
+ "FECAEDetRequest" => {
77
+ "Concepto" => Afip::CONCEPTOS[concepto],
78
+ "DocTipo" => Afip::DOCUMENTOS[documento],
79
+ "DocNro" => doc_num,
80
+ "CbteFch" => fecha_emision.strftime('%Y%m%d'),
81
+ "ImpTotConc" => 0.00,
82
+ "ImpNeto" => net.to_f,
83
+ "MonId" => Afip::MONEDAS[moneda][:codigo],
84
+ "MonCotiz" => exchange_rate,
85
+ "ImpOpEx" => 0.00,
86
+ "ImpTrib" => 0.00,
87
+ "ImpTotal" => (Afip.own_iva_cond == :responsable_monotributo ? net : total).to_f.round(2),
88
+ "CbteDesde" => next_bill_number,
89
+ "CbteHasta" => next_bill_number
90
+ }
91
+ }
92
+ }
93
+ }
94
+
95
+ detail = fecaereq["FeCAEReq"]["FeDetReq"]["FECAEDetRequest"]
96
+
97
+ if (Afip.own_iva_cond == :responsable_inscripto)
98
+ detail["ImpIVA"] = iva_sum
99
+ detail["Iva"] = { "AlicIva" => array_ivas }
100
+ end
101
+
102
+ unless concepto == "Productos" # En "Productos" ("01"), si se mandan estos parámetros la afip rechaza.
103
+ detail.merge!({"FchServDesde" => fch_serv_desde.strftime("%Y%m%d"),
104
+ "FchServHasta" => fch_serv_hasta.strftime("%Y%m%d"),
105
+ "FchVtoPago" => due_date.strftime("%Y%m%d")})
106
+ end
107
+
108
+ body.merge!(fecaereq)
109
+ pp body
110
+ end
111
+
112
+ def self.header(cbte_type, sale_point)
113
+ {"CantReg" => "1", "CbteTipo" => cbte_type, "PtoVta" => sale_point}
114
+ end
115
+
116
+ def exchange_rate
117
+ return 1 if moneda == :peso
118
+ response = client.call :fe_param_get_cotizacion do
119
+ message = body.merge!({"MonId" => Afip::MONEDAS[moneda][:codigo]})
120
+ end
121
+ response.to_hash[:fe_param_get_cotizacion_response][:fe_param_get_cotizacion_result][:result_get][:mon_cotiz].to_f
122
+ end
123
+
124
+ def iva_sum
125
+ iva_sum = 0.0
126
+ self.ivas.each{ |i|
127
+ iva_sum += i[2]
128
+ }
129
+ return iva_sum.round(2)
130
+ end
131
+
132
+ def next_bill_number
133
+ var = {"Auth" => Afip.auth_hash, "PtoVta" => sale_point, "CbteTipo" => cbte_type}
134
+ resp = client.call :fe_comp_ultimo_autorizado do
135
+ message(var)
136
+ end
137
+
138
+ resp.to_hash[:fe_comp_ultimo_autorizado_response][:fe_comp_ultimo_autorizado_result][:cbte_nro].to_i + 1
139
+ end
140
+
141
+ def setup_response(response)
142
+ # TODO: turn this into an all-purpose Response class
143
+
144
+ result = response[:fecae_solicitar_response][:fecae_solicitar_result]
145
+
146
+ if not result[:fe_det_resp] or not result[:fe_cab_resp] then
147
+ # Si no obtuvo respuesta ni cabecera ni detalle, evito hacer '[]' sobre algo indefinido.
148
+ # Ejemplo: Error con el token-sign de WSAA
149
+ keys, values = {
150
+ :errores => result[:errors],
151
+ :header_result => {:resultado => "X" },
152
+ :observaciones => nil
153
+ }.to_a.transpose
154
+ @response = (defined?(Struct::ResponseMal) ? Struct::ResponseMal : Struct.new("ResponseMal", *keys)).new(*values)
155
+ return
156
+ end
157
+
158
+ response_header = result[:fe_cab_resp]
159
+ response_detail = result[:fe_det_resp][:fecae_det_response]
160
+
161
+ request_header = body["FeCAEReq"]["FeCabReq"].underscore_keys.symbolize_keys
162
+ request_detail = body["FeCAEReq"]["FeDetReq"]["FECAEDetRequest"].underscore_keys.symbolize_keys
163
+
164
+ # Esto no funciona desde que se soportan múltiples alícuotas de iva simultáneas
165
+ # FIX ? TO-DO
166
+ # iva = request_detail.delete(:iva)["AlicIva"].underscore_keys.symbolize_keys
167
+ # request_detail.merge!(iva)
168
+
169
+ if result[:errors] then
170
+ response_detail.merge!( result[:errors] )
171
+ end
172
+
173
+ response_hash = {
174
+ :header_result => response_header.delete(:resultado),
175
+ :authorized_on => response_header.delete(:fch_proceso),
176
+ :detail_result => response_detail.delete(:resultado),
177
+ :cae_due_date => response_detail.delete(:cae_fch_vto),
178
+ :cae => response_detail.delete(:cae),
179
+ :iva_id => request_detail.delete(:id),
180
+ :iva_importe => request_detail.delete(:importe),
181
+ :moneda => request_detail.delete(:mon_id),
182
+ :cotizacion => request_detail.delete(:mon_cotiz),
183
+ :iva_base_imp => request_detail.delete(:base_imp),
184
+ :doc_num => request_detail.delete(:doc_nro),
185
+ :observaciones => response_detail.delete(:observaciones),
186
+ :errores => response_detail.delete(:err)
187
+ }.merge!(request_header).merge!(request_detail)
188
+
189
+ keys, values = response_hash.to_a.transpose
190
+ @response = (defined?(Struct::Response) ? Struct::Response : Struct.new("Response", *keys)).new(*values)
191
+ end
192
+
193
+ def authorized?
194
+ !response.nil? && response.header_result == "A" && response.detail_result == "A"
195
+ end
196
+ end
197
+ end
@@ -0,0 +1,80 @@
1
+ module Afip
2
+ CBTE_TIPO = {
3
+ "01"=>"Factura A",
4
+ "02"=>"Nota de Débito A",
5
+ "03"=>"Nota de Crédito A",
6
+ "06"=>"Factura B",
7
+ "07"=>"Nota de Debito B",
8
+ "08"=>"Nota de Credito B",
9
+ "11"=>"Factura C",
10
+ "12"=>"Nota de Debito C",
11
+ "13"=>"Nota de Credito C"
12
+ }
13
+
14
+ CONCEPTOS = {"Productos"=>"01", "Servicios"=>"02", "Productos y Servicios"=>"03"}
15
+
16
+ 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"}
17
+
18
+ MONEDAS = {
19
+ :peso => {:codigo => "PES", :nombre =>"Pesos Argentinos"},
20
+ :dolar => {:codigo => "DOL", :nombre =>"Dolar Estadounidense"},
21
+ :real => {:codigo => "012", :nombre =>"Real"},
22
+ :euro => {:codigo => "060", :nombre =>"Euro"},
23
+ :oro => {:codigo => "049", :nombre =>"Gramos de Oro Fino"}
24
+ }
25
+
26
+ ALIC_IVA = [["03", 0], ["04", 0.105], ["05", 0.21], ["06", 0.27]]
27
+
28
+ IVA_COND = ["Responsable Inscripto", "Responsable Monotributo"]
29
+
30
+ BILL_TYPE = {
31
+ :responsable_inscripto => {
32
+ :responsable_inscripto => "01",
33
+ :consumidor_final => "06",
34
+ :exento => "06",
35
+ :responsable_monotributo => "06",
36
+ :nota_credito_a => "03",
37
+ :nota_credito_b => "08",
38
+ :nota_debito_a => "02",
39
+ :nota_debito_b => "07"
40
+ },
41
+ :responsable_monotributo => {
42
+ :responsable_inscripto => "11",
43
+ :consumidor_final => "11",
44
+ :exento => "11",
45
+ :responsable_monotributo => "11",
46
+ :nota_credito_c => "13",
47
+ :nota_debito_c => "12"
48
+ }
49
+ }
50
+
51
+ AVAILABLE_TYPES = {
52
+ :responsable_inscripto => {
53
+ :responsable_inscripto => ["01", "02", "03"],
54
+ :consumidor_final => ["06", "07", "08"],
55
+ :exento => ["06", "07", "08"],
56
+ :responsable_monotributo => ["06", "07", "08"],
57
+ },
58
+ :responsable_monotributo => {
59
+ :responsable_inscripto => ["11", "12", "13"],
60
+ :consumidor_final => ["11", "12", "13"],
61
+ :exento => ["11", "12", "13"],
62
+ :responsable_monotributo => ["11", "12", "13"]
63
+ }
64
+ }
65
+
66
+ URLS =
67
+ {
68
+ :test => {
69
+ :wsaa => 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms',
70
+ :padron => "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA5?WSDL",
71
+ :wsfe => 'https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL'
72
+ },
73
+ :production => {
74
+ :wsaa => 'https://wsaa.afip.gov.ar/ws/services/LoginCms',
75
+ :padron => "https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA5?WSDL",
76
+ :wsfe => 'https://servicios1.afip.gov.ar/wsfev1/service.asmx'
77
+ }
78
+ }
79
+
80
+ 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,23 @@
1
+ class Hash
2
+ def symbolize_keys!
3
+ keys.each do |key|
4
+ self[(key.to_sym rescue key) || key] = delete(key)
5
+ end
6
+ self
7
+ end unless method_defined?(:symbolize_keys!)
8
+
9
+ def symbolize_keys
10
+ dup.symbolize_keys!
11
+ end unless method_defined?(:symbolize_keys)
12
+
13
+ def underscore_keys!
14
+ keys.each do |key|
15
+ self[(key.underscore rescue key) || key] = delete(key)
16
+ end
17
+ self
18
+ end unless method_defined?(:underscore_keys!)
19
+
20
+ def underscore_keys
21
+ dup.underscore_keys!
22
+ end unless method_defined?(:underscore_keys)
23
+ end
@@ -0,0 +1,12 @@
1
+ # Stolen from activesupport/lib/active_support/inflector/methods.rb, line 48
2
+ class String
3
+ def underscore
4
+ word = self.to_s.dup
5
+ word.gsub!(/::/, '/')
6
+ word.gsub!(/([A-Z]+)([A-Z][a-z])/,'\1_\2')
7
+ word.gsub!(/([a-z\d])([A-Z])/,'\1_\2')
8
+ word.tr!("-", "_")
9
+ word.downcase!
10
+ word
11
+ end
12
+ end
data/lib/Afip/ctg.rb ADDED
@@ -0,0 +1,234 @@
1
+ module Afip
2
+ class CTG
3
+ attr_reader :client, :base_imp, :total
4
+ attr_accessor :ctg_num, :cp_num, :cod_especie, :cuit_canjeador,:rccc, :cuit_destino, :cuit_destinatario, :localidad_origen, :localidad_destino,
5
+ :cosecha, :peso, :cuit_transportista, :horas, :patente, :km, :cuit_corredor, :remitente_com, :body
6
+
7
+ def initialize(attrs = {})
8
+ Afip::AuthData.fetch("wsctg")
9
+ @client = Savon.client(
10
+ ssl_cert_key_file: Afip.pkey,
11
+ ssl_cert_file: Afip.cert,
12
+ env_namespace: :soapenv,
13
+ namespace_identifier: :ctg,
14
+ log: true,
15
+ logger: Rails.logger,
16
+ log_level: :debug,
17
+ pretty_print_xml: true,
18
+ encoding: 'UTF-8',
19
+ ssl_version: :TLSv1,
20
+ wsdl: Afip.service_url
21
+ )
22
+
23
+ @ctg_num = attrs[:ctg_num]
24
+ @cp_num = attrs[:cp_num]
25
+ @cod_especie = attrs[:cod_especie]
26
+ @cuit_canjeador = attrs[:cuit_canjeador]
27
+ @rccc = attrs[:rccc]
28
+ @cuit_destino = attrs[:cuit_destino]
29
+ @cuit_destinatario = attrs[:cuit_destinatario]
30
+ @localidad_origen = attrs[:localidad_origen]
31
+ @localidad_destino = attrs[:localidad_destino]
32
+ @cosecha = attrs[:cosecha]
33
+ @peso = attrs[:peso]
34
+ @cuit_transportista = attrs[:cuit_transportista]
35
+ @horas = attrs[:horas]
36
+ @patente = attrs[:patente]
37
+ @km = attrs[:km]
38
+ @cuit_corredor = attrs[:cuit_corredor]
39
+ @remitente_com = attrs[:remitente_com]
40
+ @cuit_chofer = attrs[:cuit_chofer]
41
+ @cant_kilos_carta_porte = attrs[:cant_kilos_carta_porte]
42
+ @establecimiento = attrs[:establecimiento]
43
+ @cuit_solicitante = attrs[:cuit_solicitante]
44
+ @fecha_desde = attrs[:fecha_desde]
45
+ @fecha_hasta = attrs[:fecha_hasta]
46
+
47
+ @body = {"request" =>{"auth" => Afip.auth_hash("wsctg")}}
48
+ end
49
+
50
+ def solicitar_ctg_inicial
51
+ pp body = setup_ctg
52
+
53
+ pp response = client.call(:solicitar_ctg_inicial,message: body)
54
+
55
+ setup_response(response.to_hash)
56
+
57
+ self.authorized?
58
+ end
59
+
60
+ def setup_ctg
61
+
62
+ datos = {
63
+ "datosSolicitarCTGInicial" =>{
64
+ "cartaPorte" => @cp_num, #long
65
+ "codigoEspecie" => @cod_especie, #int
66
+ #"cuitCanjeador" => @cuit_canjeador.to_i, #long
67
+ #"remitenteComercialComoCanjeador" => @rccc,
68
+ "cuitDestino" => @cuit_destino, #long
69
+ "cuitDestinatario" => @cuit_destinatario, #long
70
+ "codigoLocalidadOrigen" => @localidad_origen, #int
71
+ "codigoLocalidadDestino" => @localidad_destino, #int
72
+ "codigoCosecha" => @cosecha, #string
73
+ "pesoNeto" => @peso.to_i, #long
74
+ #"cuitTransportista" => @cuit_transportista.to_i, #long
75
+ #"cantHoras" => @horas, #int
76
+ #"patente" => @patente, #string
77
+ "kmARecorrer" => @km, #unsignedint
78
+ #"cuitCorredor" => @cuit_corredor.to_i, #long
79
+ #"remitenteComercialcomoProductor" => @remitente_com
80
+ }
81
+ }
82
+
83
+ @body["request"].merge!(datos)
84
+ return @body
85
+ end
86
+
87
+ def authorized?
88
+ !response.nil?
89
+ end
90
+
91
+ def anular_ctg
92
+ request = {
93
+ "datosAnularCTG" =>{
94
+ "cartaPorte" => "@ctg_num",
95
+ "ctg" => "#{self.code}"
96
+ }
97
+ }
98
+ body["request"].merge(request)
99
+
100
+ response = client.call(:anular_ctg, message: body)
101
+ end
102
+
103
+ def cambiar_destino_detinatario_ctg_rechazado
104
+ request = {
105
+ "datosCambiarDestinoDestinatarioCTGRechazado" =>{
106
+ "cartaPorte" => "@ctg_num",
107
+ "ctg" => "#{code}",
108
+ "codigoLocalidadDestino" => @destino,
109
+ "codigoLocalidadDestinatario" => @destinatario,
110
+ "kmARecorrer" => @km
111
+ }
112
+ }
113
+ body["request"].merge(request)
114
+
115
+ response = client.call(:cambiar_destino_detinatario_ctg_rechazado, message: body)
116
+ end
117
+
118
+ def confirmar_arribo
119
+ request = {
120
+ "datosConfirmarArribo" =>{
121
+ "cartaPorte" => "@ctg_num",
122
+ "ctg" => "#{self.code}",
123
+ "cuitTransportista" => @cuit_transportista,
124
+ "cuitChofer" => @cuit_chofer,
125
+ "cantKilosCartaPorte" => @cant_kilos_carta_porte
126
+ }
127
+ }
128
+ body["request"].merge(request)
129
+
130
+ response = client.call(:confirmar_arribo, message: body)
131
+ end
132
+
133
+ def confirmar_definitivo
134
+ request = {
135
+ "datosConfirmarDefinitivo" =>{
136
+ "cartaPorte" => "@ctg_num",
137
+ "ctg" => "#{self.code}",
138
+ "establecimiento" => @establecimiento,
139
+ "codigoCosecha" => @cosecha,
140
+ "pesoNeto" => @peso
141
+ }
142
+ }
143
+ body["request"].merge(request)
144
+
145
+ response = client.call(:confirmar_definitivo, message: body)
146
+ end
147
+
148
+ def consultar_cosechas
149
+ response = client.call(:consultar_cosechas, message: body)
150
+ return response["arrayCosechas"]
151
+ end
152
+
153
+ def consultar_constancia_ctg_pdf
154
+ request = {
155
+ "ctg" => "#{code}"
156
+ }
157
+
158
+ body["request"].merge(request)
159
+
160
+ response = client.call(:consultar_constancia_ctg_pdf, message: body)
161
+ end
162
+
163
+ def consultar_ctg
164
+ request = {
165
+ "consultarCTGDatos" =>{
166
+ "cartaPorte" => "@ctg_num",
167
+ "ctg" => "#{self.code}",
168
+ "patente" => @patente,
169
+ "cuitSolicitante" => @cuit_solicitante,
170
+ "cuitDestino" => @destino,
171
+ "fechaEmisionDesde" => @fecha_desde,
172
+ "fechaEmisionHsta" => @fecha_hasta,
173
+ "cuitCorredor" => @cuit_corredor
174
+ }
175
+ }
176
+ body["request"].merge(request)
177
+
178
+ response = client.call(:consultar_ctg, message: body)
179
+ end
180
+
181
+ def consultar_ctg_rechazados
182
+ response = client.call(:consultar_ctg_rechazados, message: body)
183
+ return response.to_hash["response"]["arrayConsultarCTGRechazados"]
184
+ end
185
+
186
+ def consultar_detalle_ctg
187
+ request = {
188
+ "ctg" => "#{code}"
189
+ }
190
+
191
+ body["request"].merge(request)
192
+
193
+ response = client.call(:consultar_constancia_ctg_pdf, message: body)
194
+ return response.to_hash["response"]["consultarDetalleCTGDatos"]
195
+ end
196
+
197
+ def consultar_especies
198
+ response = client.call(:consultar_especies, message: body)
199
+ response.body[:consultar_especies_response][:response][:array_especies][:especie].map{|c| [c[:codigo],c[:descripcion]]}
200
+ end
201
+
202
+ def consultar_establecimientos
203
+ response = client.call(:consultar_establecimientos, message: body)
204
+ response.body[:consultar_especies_response][:response][:array_establecimientos][:establecimiento].map{|c| [c]}
205
+ end
206
+
207
+ def consultar_provincias
208
+ response = client.call(:consultar_provincias, message: body)
209
+ response.body[:consultar_provincias_response][:consultar_provincias_response][:array_provincias][:provincia].map{|c| [c[:codigo],c[:descripcion]]}
210
+ end
211
+
212
+ def consultar_cosechas
213
+ response = client.call(:consultar_cosechas, message: body)
214
+ response.body[:consultar_cosechas_response][:response][:array_cosechas][:cosecha].map{|c| [c[:codigo],c[:descripcion]]}
215
+ end
216
+
217
+ def consultar_localidades(city)
218
+ body["request"]["codigoProvincia"] = city
219
+ response = client.call(:consultar_localidades_por_provincia, message: body)
220
+ if response.body[:consultar_localidades_por_provincia_response][:response][:array_localidades][:localidad].class.name != "Array"
221
+ [response.body[:consultar_localidades_por_provincia_response][:response][:array_localidades][:localidad]].map{|c| [c[:codigo],c[:descripcion]]}
222
+ else
223
+ response.body[:consultar_localidades_por_provincia_response][:response][:array_localidades][:localidad].map{|c| [c[:codigo],c[:descripcion]]}
224
+ end
225
+ end
226
+
227
+ private
228
+
229
+ def setup_response(response)
230
+ # TODO: turn this into an all-purpose Response class
231
+ pp response
232
+ end
233
+ end
234
+ end
@@ -0,0 +1,183 @@
1
+ module Afip
2
+ class Padron
3
+ attr_reader :client, :body, :fault_code
4
+ attr_accessor :dni, :tipo, :data
5
+ def initialize(attrs = {})
6
+ Afip::AuthData.environment = Afip.environment || :production
7
+ url = Afip::AuthData.environment == :production ? "aws" : "awshomo"
8
+ Afip.service_url = "https://#{url}.afip.gov.ar/sr-padron/webservices/personaServiceA4?WSDL"
9
+ Afip.cuit ||= "20368642682"
10
+ Afip.cert ||= "#{Afip.root}/lib/Afip/certs/desideral_prod.crt"
11
+ Afip.pkey ||= "#{Afip.root}/lib/Afip/certs/desideral.key"
12
+ Afip::AuthData.fetch("ws_sr_padron_a4")
13
+
14
+ @client = Savon.client(
15
+ ssl_cert_key_file: Afip.pkey,
16
+ ssl_cert_file: Afip.cert,
17
+ env_namespace: :soapenv,
18
+ namespace_identifier: :a4,
19
+ encoding: 'UTF-8',
20
+ wsdl: Afip.service_url
21
+ )
22
+
23
+ @dni = attrs[:dni].rjust(8, "0")
24
+ @tipo = attrs[:tipo]
25
+ @cuit = get_cuit
26
+ end
27
+
28
+ def get_persona
29
+ body = setup_body
30
+
31
+ response = client.call(:get_persona,message: body)
32
+ rescue Savon::SOAPFault => error
33
+ if !error.blank?
34
+ @fault_code = error.to_hash[:fault][:faultstring]
35
+ else
36
+ @fault_code = nil
37
+ end
38
+ return response
39
+ end
40
+
41
+ def get_data
42
+ @data = get_persona
43
+ if fault_code.nil?
44
+ set_data
45
+ else
46
+ return nil
47
+ end
48
+
49
+ end
50
+
51
+ def set_data
52
+ pp data.body
53
+ if not data.body[:get_persona_response][:persona_return][:persona][:actividad].nil?
54
+ {
55
+ :last_name => data.body[:get_persona_response][:persona_return][:persona][:apellido],
56
+ :first_name => data.body[:get_persona_response][:persona_return][:persona][:nombre],
57
+ :cuit => data.body[:get_persona_response][:persona_return][:persona][:id_persona],
58
+ :cp => data.body[:get_persona_response][:persona_return][:persona][:domicilio].last[:cod_postal],
59
+ :address => data.body[:get_persona_response][:persona_return][:persona][:domicilio].last[:direccion],
60
+ :city_id => data.body[:get_persona_response][:persona_return][:persona][:domicilio].last[:id_provincia],
61
+ :city => PROVINCIAS[data.body[:get_persona_response][:persona_return][:persona][:domicilio].last[:id_provincia]],
62
+ :locality => data.body[:get_persona_response][:persona_return][:persona][:domicilio].last[:localidad],
63
+ :birthday => data.body[:get_persona_response][:persona_return][:persona][:fecha_nacimiento].to_date
64
+ }
65
+ else
66
+ {
67
+ :last_name => Padron.divide_name(data.body[:get_persona_response][:persona_return][:persona][:apellido])[0],
68
+ :first_name => Padron.divide_name(data.body[:get_persona_response][:persona_return][:persona][:apellido])[1],
69
+ :cuit => data.body[:get_persona_response][:persona_return][:persona][:id_persona],
70
+ :cp => data.body[:get_persona_response][:persona_return][:persona].try(:[], :domicilio).try(:[], :cod_postal),
71
+ :address => data.body[:get_persona_response][:persona_return][:persona].try(:[], :domicilio).try(:[], :direccion),
72
+ :city_id => data.body[:get_persona_response][:persona_return][:persona].try(:[], :domicilio).try(:[], :id_provincia),
73
+ :city => PROVINCIAS[data.body[:get_persona_response][:persona_return][:persona].try(:[], :domicilio).try(:[], :id_provincia)],
74
+ :locality => data.body[:get_persona_response][:persona_return][:persona].try(:[], :domicilio).try(:[], :localidad),
75
+ :birthday => data.body[:get_persona_response][:persona_return][:persona][:fecha_nacimiento].to_date
76
+ }
77
+ end
78
+ end
79
+
80
+ def self.divide_name(full_name)
81
+ full_name = full_name.strip.split(/\s+/)
82
+ last_name = ''
83
+ last = (full_name.count / 2) - 1
84
+ (0..last).each do |i|
85
+ if i != last
86
+ last_name += full_name[i] + ' '
87
+ else
88
+ last_name += full_name[i]
89
+ end
90
+ end
91
+ full_name = full_name - (last_name.strip.split(/\s+/))
92
+ first_name = full_name.join(", ").gsub(",","").split.map(&:capitalize).join(' ')
93
+ last_name = last_name.split.map(&:capitalize).join(' ')
94
+ return [last_name, first_name]
95
+ end
96
+
97
+ def get_cuit
98
+ if dni.length == 11
99
+ @cuit = @dni
100
+ else
101
+ @cuit = calculate_cuit
102
+ end
103
+ end
104
+
105
+ def calculate_cuit
106
+ multiplicador = "2345672345"
107
+
108
+ case tipo
109
+ when "F"
110
+ xy = 27
111
+ xy_dni = "27#{dni}"
112
+ when "M"
113
+ xy = 20
114
+ xy_dni = "20#{dni}"
115
+ end
116
+ verificador = 0
117
+ (0..9).each do |i|
118
+ verificador += (xy_dni.reverse[i].to_i * multiplicador[i].to_i)
119
+ end
120
+ verificador
121
+ z = verificador - (verificador / 11 * 11)
122
+
123
+ case z
124
+ when 0
125
+ z = 0
126
+ when 1
127
+ if tipo == "M"
128
+ z = 9
129
+ xy = 23
130
+ elsif tipo == "F"
131
+ z = 4
132
+ xy = 23
133
+ else
134
+ z = 11 - z
135
+ end
136
+ else
137
+ z = 11 - z
138
+ end
139
+
140
+ return "#{xy}#{dni}#{z}"
141
+ end
142
+
143
+ def setup_body
144
+ body = {
145
+ 'token' => Afip::TOKEN,
146
+ 'sign' => Afip::SIGN,
147
+ 'cuitRepresentada' => Afip.cuit,
148
+ 'idPersona' => @cuit.to_s
149
+ }
150
+ end
151
+
152
+ PROVINCIAS = {
153
+ "0" => 'CIUDAD AUTONOMA BUENOS AIRES',
154
+ "1" => 'BUENOS AIRES',
155
+ "2" => 'CATAMARCA',
156
+ "3" => 'CORDOBA',
157
+ "4" => 'CORRIENTES',
158
+ "5" => 'ENTRE RIOS',
159
+ "6" => 'JUJUY',
160
+ "7" => 'MENDOZA',
161
+ "8" => 'LA RIOJA',
162
+ "9" => 'SALTA',
163
+ "10" => 'SAN JUAN',
164
+ "11" => 'SAN LUIS',
165
+ "12" => 'SANTA FE',
166
+ "13" => 'SANTIAGO DEL ESTERO',
167
+ "14" => 'TUCUMAN',
168
+ "16" => 'CHACO',
169
+ "17" => 'CHUBUT',
170
+ "18" => 'FORMOSA',
171
+ "19" => 'MISIONES',
172
+ "20" => 'NEUQUEN',
173
+ "21" => 'LA PAMPA',
174
+ "22" => 'RIO NEGRO',
175
+ "23" => 'SANTA CRUZ',
176
+ "24" => 'TIERRA DEL FUEGO'
177
+ }
178
+ end
179
+ end
180
+
181
+
182
+
183
+
@@ -0,0 +1,3 @@
1
+ module Afip
2
+ VERSION = "0.8.5"
3
+ end
data/lib/Afip/wsaa.rb ADDED
@@ -0,0 +1,94 @@
1
+ module Afip
2
+ # Authorization class. Handles interactions wiht the WSAA, to provide
3
+ # valid key and signature that will last for a day.
4
+ #
5
+ class Wsaa
6
+ # Main method for authentication and authorization.
7
+ # When successful, produces the yaml file with auth data.
8
+ #
9
+ def self.login(service = "wsfe")
10
+ tra = build_tra(service)
11
+ cms = build_cms(tra)
12
+ req = build_request(cms)
13
+ auth = call_wsaa(req)
14
+
15
+ write_yaml(auth)
16
+ end
17
+
18
+ protected
19
+ # Builds the xml for the 'Ticket de Requerimiento de Acceso'
20
+ # @return [String] containing the request body
21
+ #
22
+ def self.build_tra service
23
+ @now = (Time.now) - 120
24
+ @from = @now.strftime('%FT%T%:z')
25
+ @to = (@now + ((12*60*60))).strftime('%FT%T%:z')
26
+ @id = @now.strftime('%s')
27
+ tra = <<-EOF
28
+ <?xml version="1.0" encoding="UTF-8"?>
29
+ <loginTicketRequest version="1.0">
30
+ <header>
31
+ <uniqueId>#{ @id }</uniqueId>
32
+ <generationTime>#{ @from }</generationTime>
33
+ <expirationTime>#{ @to }</expirationTime>
34
+ </header>
35
+ <service>#{service}</service>
36
+ </loginTicketRequest>
37
+ EOF
38
+ return tra
39
+ end
40
+
41
+ # Builds the CMS
42
+ # @return [String] cms
43
+ #
44
+ def self.build_cms(tra)
45
+ cms = `echo '#{ tra }' |
46
+ #{ Afip.openssl_bin } cms -sign -in /dev/stdin -signer #{ Afip.cert } -inkey #{ Afip.pkey } -nodetach \
47
+ -outform der |
48
+ #{ Afip.openssl_bin } base64 -e`
49
+ return cms
50
+ end
51
+
52
+ # Builds the CMS request to log in to the server
53
+ # @return [String] the cms body
54
+ #
55
+ def self.build_request(cms)
56
+ request = <<-XML
57
+ <?xml version="1.0" encoding="UTF-8"?>
58
+ <SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://wsaa.view.sua.dvadac.desein.afip.gov">
59
+ <SOAP-ENV:Body>
60
+ <ns1:loginCms>
61
+ <ns1:in0>
62
+ #{ cms }
63
+ </ns1:in0>
64
+ </ns1:loginCms>
65
+ </SOAP-ENV:Body>
66
+ </SOAP-ENV:Envelope>
67
+ XML
68
+ return request
69
+ end
70
+
71
+ # Calls the WSAA with the request built by build_request
72
+ # @return [Array] with the token and signature
73
+ #
74
+ def self.call_wsaa(req)
75
+ response = `echo '#{ req }' | curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Afip::AuthData.wsaa_url }`
76
+ pp response
77
+ response = CGI::unescapeHTML(response)
78
+ token = response.scan(/\<token\>(.+)\<\/token\>/).first.first
79
+ sign = response.scan(/\<sign\>(.+)\<\/sign\>/).first.first
80
+ return [token, sign]
81
+ end
82
+
83
+ # Writes the token and signature to a YAML file in the /tmp directory
84
+ #
85
+ def self.write_yaml(certs)
86
+ yml = <<-YML
87
+ token: #{certs[0]}
88
+ sign: #{certs[1]}
89
+ YML
90
+ `echo '#{ yml }' > /tmp/#{Afip::AuthData.environment.to_s}_Afip_#{ Afip.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml`
91
+ end
92
+
93
+ end
94
+ end
data/lib/Afip.rb ADDED
@@ -0,0 +1,59 @@
1
+ require "Afip/version"
2
+ require "bundler/setup"
3
+ require "Afip/constants"
4
+ require "savon"
5
+ require "Afip/core_ext/float"
6
+ require "Afip/core_ext/hash"
7
+ require "Afip/core_ext/string"
8
+
9
+ require 'net/http'
10
+ require 'net/https'
11
+
12
+ #require 'net/http'
13
+ require 'net/https'
14
+ module Afip
15
+
16
+ class NullOrInvalidAttribute < StandardError; end
17
+
18
+ def self.root
19
+ File.expand_path '../..', __FILE__
20
+ end
21
+
22
+ autoload :Authorizer, "Afip/authorizer"
23
+ autoload :AuthData, "Afip/auth_data"
24
+ autoload :Padron, "Afip/padron"
25
+ autoload :Wsaa, "Afip/wsaa"
26
+ autoload :Bill, "Afip/bill"
27
+ autoload :CTG, "Afip/ctg"
28
+
29
+
30
+ class << self
31
+ mattr_accessor :cuit, :pkey, :cert, :environment, :openssl_bin,
32
+ :default_concepto, :default_documento, :default_moneda, :own_iva_cond, :service_url, :auth_url
33
+ end
34
+
35
+ def self.setup(&block)
36
+ yield self
37
+ end
38
+
39
+ extend self
40
+
41
+ def auth_hash(service = "wsfe")
42
+ case service
43
+ when "wsfe"
44
+ { 'Token' => Afip::TOKEN, 'Sign' => Afip::SIGN, 'Cuit' => Afip.cuit }
45
+ when "ws_sr_padron_a4"
46
+ { 'token' => Afip::TOKEN, 'sign' => Afip::SIGN, 'cuitRepresentado' => Afip.cuit }
47
+ when "wsctg"
48
+ { 'token' => Afip::TOKEN, 'sign' => Afip::SIGN, 'cuitRepresentado' => Afip.cuit }
49
+ end
50
+ end
51
+
52
+ def log?
53
+ Afip.verbose || ENV["VERBOSE"]
54
+ end
55
+
56
+ def deleteToken
57
+ AuthData.deleteToken
58
+ end
59
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: Afip
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.8.2
4
+ version: 0.8.5
5
5
  platform: ruby
6
6
  authors:
7
7
  - Facundo A. Díaz Martínez
@@ -72,7 +72,30 @@ email:
72
72
  executables: []
73
73
  extensions: []
74
74
  extra_rdoc_files: []
75
- files: []
75
+ files:
76
+ - ".gitignore"
77
+ - Afip-0.8.3.gem
78
+ - Afip-0.8.4.gem
79
+ - Afip.gemspec
80
+ - Gemfile
81
+ - Gemfile.lock
82
+ - LICENSE.txt
83
+ - README.md
84
+ - Rakefile
85
+ - bin/console
86
+ - bin/setup
87
+ - lib/Afip.rb
88
+ - lib/Afip/auth_data.rb
89
+ - lib/Afip/authorizer.rb
90
+ - lib/Afip/bill.rb
91
+ - lib/Afip/constants.rb
92
+ - lib/Afip/core_ext/float.rb
93
+ - lib/Afip/core_ext/hash.rb
94
+ - lib/Afip/core_ext/string.rb
95
+ - lib/Afip/ctg.rb
96
+ - lib/Afip/padron.rb
97
+ - lib/Afip/version.rb
98
+ - lib/Afip/wsaa.rb
76
99
  homepage: https://www.desideral.com
77
100
  licenses:
78
101
  - MIT