Afip 0.8.2 → 0.8.5

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.
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