Afip 0.1.1 → 0.1.2

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
  SHA1:
3
- metadata.gz: c44d1855f1f1844a4745cce85cdd919db4286ef4
4
- data.tar.gz: 0b3146d5b0fd7718f7298426dd599ecdc8f33142
3
+ metadata.gz: 3940933ea4642917aef2e693aa7c170052d19d4b
4
+ data.tar.gz: 9766eff2f7d9e62c270d977f24e1b5c3967bf45d
5
5
  SHA512:
6
- metadata.gz: f8f08e2292ce4cbf8c93b434a365625fd32b65664fbaf17b906cd5ca9dd3edc78a6864ed369f9a84c5d9ca085baa78dab75a61daa9cf58e8ca2c126a78d49080
7
- data.tar.gz: 2984ec6b14ed59738607ae81498bcea3db05a66205e130de2bb7d0dcb4fd83e9616eeffaa0c303fe11ed7324a6e72780a7677ed2010af53dc30d967dc52c9d9b
6
+ metadata.gz: cbda0d40344b3637967d267cb3272a2c7a0e2ec5e074d471e45605da7a8dabdd5aabf7a781a34a99d7e5fdc014c3010ab8d835bac62a41179b4aec8b4dcc8860
7
+ data.tar.gz: e203ce591b376f75f54fa62ea5bc83e22a8e7aeec9ddf4916db2a8e51dad0ea7f935f091c4bac838bde9665af0777669c2828960edfa373cb0d19c028a9bf270
data/.DS_Store ADDED
Binary file
data/Afip-0.1.0.gem ADDED
Binary file
data/Gemfile.lock ADDED
@@ -0,0 +1,20 @@
1
+ PATH
2
+ remote: .
3
+ specs:
4
+ Afip (0.1.1)
5
+
6
+ GEM
7
+ remote: https://rubygems.org/
8
+ specs:
9
+ rake (10.5.0)
10
+
11
+ PLATFORMS
12
+ ruby
13
+
14
+ DEPENDENCIES
15
+ Afip!
16
+ bundler (~> 1.16)
17
+ rake (~> 10.0)
18
+
19
+ BUNDLED WITH
20
+ 1.16.2
data/lib/.DS_Store ADDED
Binary file
@@ -0,0 +1,69 @@
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
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
+ end
44
+ end
45
+
46
+ # Returns the right wsaa url for the specific environment
47
+ # @return [String]
48
+ #
49
+ def wsaa_url
50
+ pp Afip::URLS[environment][:wsaa]
51
+ end
52
+
53
+ # Returns the right wsfe url for the specific environment
54
+ # @return [String]
55
+ #
56
+ def wsfe_url
57
+ raise 'Environment not sent to either :test or :production' unless Afip::URLS.keys.include? environment
58
+ Afip::URLS[environment][:wsfe]
59
+ end
60
+
61
+ # Creates the data file name for a cuit number and the current day
62
+ # @return [String]
63
+ #
64
+ def todays_data_file_name
65
+ @todays_data_file ||= "/tmp/#{environment.to_s}_Afip_#{ Afip.cuit }_#{ Time.new.strftime('%Y_%m_%d') }.yml"
66
+ end
67
+ end
68
+ end
69
+ 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,178 @@
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
+ 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
+ @cbte_type = Afip::BILL_TYPE[Afip.own_iva_cond][iva_cond] || raise(NullOrInvalidAttribute.new, "Please choose a valid document type.")
20
+ @total = net.zero? ? 0 : net + iva_sum
21
+ end
22
+
23
+ def set_client
24
+ Afip::AuthData.fetch
25
+ @client = Savon.client(
26
+ wsdl: Afip.service_url,
27
+ namespaces: { "xmlns" => "http://ar.gov.afip.dif.FEV1/" },
28
+ log_level: :debug,
29
+ ssl_cert_key_file: Afip.pkey,
30
+ ssl_cert_file: Afip.cert,
31
+ ssl_verify_mode: :none,
32
+ read_timeout: 90,
33
+ open_timeout: 90,
34
+ headers: { "Accept-Encoding" => "gzip, deflate", "Connection" => "Keep-Alive" }
35
+ )
36
+ end
37
+
38
+ def authorize
39
+ body = setup_bill
40
+ response = client.call(:fecae_solicitar, message: body)
41
+ setup_response(response.to_hash)
42
+ authorized?
43
+ end
44
+
45
+ def setup_bill
46
+ array_ivas = Array.new
47
+ ivas.each{ |i|
48
+ array_ivas << {
49
+ "Id" => Afip::ALIC_IVA[ i[0] ][0],
50
+ "BaseImp" => i[1] ,
51
+ "Importe" => i[2] }
52
+ }
53
+
54
+ fecaereq = {
55
+ "FeCAEReq" => {
56
+ "FeCabReq" => Afip::Bill.header(cbte_type, sale_point),
57
+ "FeDetReq" => {
58
+ "FECAEDetRequest" => {
59
+ "Concepto" => Afip::CONCEPTOS[concepto],
60
+ "DocTipo" => Afip::DOCUMENTOS[documento],
61
+ "DocNro" => doc_num,
62
+ "CbteFch" => fecha_emision.strftime('%Y%m%d'),
63
+ "ImpTotConc" => 0.00,
64
+ "ImpNeto" => net.to_f,
65
+ "MonId" => Afip::MONEDAS[moneda][:codigo],
66
+ "MonCotiz" => exchange_rate,
67
+ "ImpOpEx" => 0.00,
68
+ "ImpTrib" => 0.00,
69
+ "ImpTotal" => (Afip.own_iva_cond == :responsable_monotributo ? total : net).to_f.round(2),
70
+ "CbteDesde" => next_bill_number,
71
+ "CbteHasta" => next_bill_number
72
+ }
73
+ }
74
+ }
75
+ }
76
+
77
+ detail = fecaereq["FeCAEReq"]["FeDetReq"]["FECAEDetRequest"]
78
+
79
+ if (Afip.own_iva_cond == :responsable_monotributo)
80
+ detail["ImpIVA"] = iva_sum
81
+ detail["Iva"] = { "AlicIva" => array_ivas }
82
+ end
83
+
84
+ unless concepto == "Productos" # En "Productos" ("01"), si se mandan estos parámetros la afip rechaza.
85
+ detail.merge!({"FchServDesde" => fch_serv_desde,
86
+ "FchServHasta" => fch_serv_hasta,
87
+ "FchVtoPago" => due_date})
88
+ end
89
+
90
+ body.merge!(fecaereq)
91
+ end
92
+
93
+ def self.header(cbte_type, sale_point)
94
+ {"CantReg" => "1", "CbteTipo" => cbte_type, "PtoVta" => sale_point}
95
+ end
96
+
97
+ def exchange_rate
98
+ return 1 if moneda == :peso
99
+ response = client.call :fe_param_get_cotizacion do
100
+ message = body.merge!({"MonId" => Afip::MONEDAS[moneda][:codigo]})
101
+ end
102
+ response.to_hash[:fe_param_get_cotizacion_response][:fe_param_get_cotizacion_result][:result_get][:mon_cotiz].to_f
103
+ end
104
+
105
+ def iva_sum
106
+ iva_sum = 0.0
107
+ self.ivas.each{ |i|
108
+ iva_sum += i[1] * Afip::ALIC_IVA[ i[0] ][1]
109
+ }
110
+ return iva_sum.round(2)
111
+ end
112
+
113
+ def next_bill_number
114
+ var = {"Auth" => Afip.auth_hash, "PtoVta" => sale_point, "CbteTipo" => cbte_type}
115
+ resp = client.call :fe_comp_ultimo_autorizado do
116
+ message(var)
117
+ end
118
+
119
+ resp.to_hash[:fe_comp_ultimo_autorizado_response][:fe_comp_ultimo_autorizado_result][:cbte_nro].to_i + 1
120
+ end
121
+
122
+ def setup_response(response)
123
+ # TODO: turn this into an all-purpose Response class
124
+
125
+ result = response[:fecae_solicitar_response][:fecae_solicitar_result]
126
+
127
+ if not result[:fe_det_resp] or not result[:fe_cab_resp] then
128
+ # Si no obtuvo respuesta ni cabecera ni detalle, evito hacer '[]' sobre algo indefinido.
129
+ # Ejemplo: Error con el token-sign de WSAA
130
+ keys, values = {
131
+ :errores => result[:errors],
132
+ :header_result => {:resultado => "X" },
133
+ :observaciones => nil
134
+ }.to_a.transpose
135
+ @response = (defined?(Struct::ResponseMal) ? Struct::ResponseMal : Struct.new("ResponseMal", *keys)).new(*values)
136
+ return
137
+ end
138
+
139
+ response_header = result[:fe_cab_resp]
140
+ response_detail = result[:fe_det_resp][:fecae_det_response]
141
+
142
+ request_header = body["FeCAEReq"]["FeCabReq"].underscore_keys.symbolize_keys
143
+ request_detail = body["FeCAEReq"]["FeDetReq"]["FECAEDetRequest"].underscore_keys.symbolize_keys
144
+
145
+ # Esto no funciona desde que se soportan múltiples alícuotas de iva simultáneas
146
+ # FIX ? TO-DO
147
+ # iva = request_detail.delete(:iva)["AlicIva"].underscore_keys.symbolize_keys
148
+ # request_detail.merge!(iva)
149
+
150
+ if result[:errors] then
151
+ response_detail.merge!( result[:errors] )
152
+ end
153
+
154
+ response_hash = {
155
+ :header_result => response_header.delete(:resultado),
156
+ :authorized_on => response_header.delete(:fch_proceso),
157
+ :detail_result => response_detail.delete(:resultado),
158
+ :cae_due_date => response_detail.delete(:cae_fch_vto),
159
+ :cae => response_detail.delete(:cae),
160
+ :iva_id => request_detail.delete(:id),
161
+ :iva_importe => request_detail.delete(:importe),
162
+ :moneda => request_detail.delete(:mon_id),
163
+ :cotizacion => request_detail.delete(:mon_cotiz),
164
+ :iva_base_imp => request_detail.delete(:base_imp),
165
+ :doc_num => request_detail.delete(:doc_nro),
166
+ :observaciones => response_detail.delete(:observaciones),
167
+ :errores => response_detail.delete(:err)
168
+ }.merge!(request_header).merge!(request_detail)
169
+
170
+ keys, values = response_hash.to_a.transpose
171
+ @response = (defined?(Struct::Response) ? Struct::Response : Struct.new("Response", *keys)).new(*values)
172
+ end
173
+
174
+ def authorized?
175
+ !response.nil? && response.header_result == "A" && response.detail_result == "A"
176
+ end
177
+ end
178
+ end
@@ -0,0 +1,63 @@
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
+ BILL_TYPE = {
29
+ :responsable_inscripto => {
30
+ :responsable_inscripto => "01",
31
+ :consumidor_final => "06",
32
+ :exento => "06",
33
+ :responsable_monotributo => "06",
34
+ :nota_credito_a => "03",
35
+ :nota_credito_b => "08",
36
+ :nota_debito_a => "02",
37
+ :nota_debito_b => "07"
38
+ },
39
+ :responsable_monotributo => {
40
+ :responsable_inscripto => "11",
41
+ :consumidor_final => "11",
42
+ :exento => "11",
43
+ :responsable_monotributo => "11",
44
+ :nota_credito_c => "13",
45
+ :nota_debito_c => "12"
46
+ }
47
+ }
48
+
49
+ URLS =
50
+ {
51
+ :test => {
52
+ :wsaa => 'https://wsaahomo.afip.gov.ar/ws/services/LoginCms',
53
+ :padron => "https://awshomo.afip.gov.ar/sr-padron/webservices/personaServiceA5?WSDL",
54
+ :wsfe => 'https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL'
55
+ },
56
+ :production => {
57
+ :wsaa => 'https://wsaa.afip.gov.ar/ws/services/LoginCms',
58
+ :padron => "https://aws.afip.gov.ar/sr-padron/webservices/personaServiceA5?WSDL",
59
+ :wsfe => 'https://servicios1.afip.gov.ar/wsfev1/service.asmx'
60
+ }
61
+ }
62
+
63
+ 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
@@ -0,0 +1,181 @@
1
+ module Afip
2
+ class Padron
3
+ attr_reader :client, :body, :fault_code, :data
4
+ attr_accessor :dni, :tipo
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 = "#{Rails.root}/afip/desideral_prod.crt"
11
+ Afip.pkey = "#{Rails.root}/afip/desideral.key"
12
+ Afip::AuthData.fetch("ws_sr_padron_a4")
13
+
14
+ @client = Savon.client(
15
+ ssl_cert_key_file: "#{Rails.root}/afip/desideral.key",
16
+ ssl_cert_file: "#{Rails.root}/afip/desideral_prod.crt",
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] || "F" #F femenino M masculino E empresa
25
+ @cuit = get_cuit
26
+ end
27
+
28
+ def get_persona
29
+ body = setup_body
30
+
31
+ pp response = client.call(:get_persona,message: body)
32
+ rescue Savon::SOAPFault => error
33
+ if !error.blank?
34
+ pp @fault_code = error.to_hash[:fault][:faultstring]
35
+ end
36
+ return response
37
+ end
38
+
39
+ def get_data
40
+ @data = get_persona
41
+ if not fault_code
42
+ set_data
43
+ else
44
+ return nil
45
+ end
46
+
47
+ end
48
+
49
+ def set_data
50
+ pp data.body
51
+ if not data.body[:get_persona_response][:persona_return][:persona][:actividad].nil?
52
+ {
53
+ :last_name => data.body[:get_persona_response][:persona_return][:persona][:apellido],
54
+ :first_name => data.body[:get_persona_response][:persona_return][:persona][:nombre],
55
+ :cuit => data.body[:get_persona_response][:persona_return][:persona][:id_persona],
56
+ :cp => data.body[:get_persona_response][:persona_return][:persona][:domicilio].last[:cod_postal],
57
+ :address => data.body[:get_persona_response][:persona_return][:persona][:domicilio].last[:direccion],
58
+ :city_id => data.body[:get_persona_response][:persona_return][:persona][:domicilio].last[:id_provincia],
59
+ :city => PROVINCIAS[data.body[:get_persona_response][:persona_return][:persona][:domicilio].last[:id_provincia]],
60
+ :locality => data.body[:get_persona_response][:persona_return][:persona][:domicilio].last[:localidad],
61
+ :birthday => data.body[:get_persona_response][:persona_return][:persona][:fecha_nacimiento].to_date
62
+ }
63
+ else
64
+ {
65
+ :last_name => Padron.divide_name(data.body[:get_persona_response][:persona_return][:persona][:apellido])[0],
66
+ :first_name => Padron.divide_name(data.body[:get_persona_response][:persona_return][:persona][:apellido])[1],
67
+ :cuit => data.body[:get_persona_response][:persona_return][:persona][:id_persona],
68
+ :cp => data.body[:get_persona_response][:persona_return][:persona][:domicilio][:cod_postal],
69
+ :address => data.body[:get_persona_response][:persona_return][:persona][:domicilio][:direccion],
70
+ :city_id => data.body[:get_persona_response][:persona_return][:persona][:domicilio][:id_provincia],
71
+ :city => PROVINCIAS[data.body[:get_persona_response][:persona_return][:persona][:domicilio][:id_provincia]],
72
+ :locality => data.body[:get_persona_response][:persona_return][:persona][:domicilio][:localidad],
73
+ :birthday => data.body[:get_persona_response][:persona_return][:persona][:fecha_nacimiento].to_date
74
+ }
75
+ end
76
+ end
77
+
78
+ def self.divide_name(full_name)
79
+ full_name = full_name.strip.split(/\s+/)
80
+ last_name = ''
81
+ last = (full_name.count / 2) - 1
82
+ (0..last).each do |i|
83
+ if i != last
84
+ last_name += full_name[i] + ' '
85
+ else
86
+ last_name += full_name[i]
87
+ end
88
+ end
89
+ full_name = full_name - (last_name.strip.split(/\s+/))
90
+ first_name = full_name.join(", ").gsub(",","").split.map(&:capitalize).join(' ')
91
+ last_name = last_name.split.map(&:capitalize).join(' ')
92
+ return [last_name, first_name]
93
+ end
94
+
95
+ def get_cuit
96
+ if dni.length == 11
97
+ @cuit = @dni
98
+ else
99
+ @cuit = calculate_cuit
100
+ end
101
+ end
102
+
103
+ def calculate_cuit
104
+ multiplicador = "2345672345"
105
+
106
+ case tipo
107
+ when "F"
108
+ xy = 27
109
+ xy_dni = "27#{dni}"
110
+ when "M"
111
+ xy = 20
112
+ xy_dni = "20#{dni}"
113
+ end
114
+ verificador = 0
115
+ (0..9).each do |i|
116
+ verificador += (xy_dni.reverse[i].to_i * multiplicador[i].to_i)
117
+ end
118
+ pp verificador
119
+ z = verificador - (verificador / 11 * 11)
120
+
121
+ case z
122
+ when 0
123
+ z = 0
124
+ when 1
125
+ if tipo == "M"
126
+ z = 9
127
+ xy = 23
128
+ elsif tipo == "F"
129
+ z = 4
130
+ xy = 23
131
+ else
132
+ z = 11 - z
133
+ end
134
+ else
135
+ z = 11 - z
136
+ end
137
+
138
+ return "#{xy}#{dni}#{z}"
139
+ end
140
+
141
+ def setup_body
142
+ body = {
143
+ 'token' => Afip::TOKEN,
144
+ 'sign' => Afip::SIGN,
145
+ 'cuitRepresentada' => Afip.cuit,
146
+ 'idPersona' => @cuit.to_s
147
+ }
148
+ end
149
+
150
+ PROVINCIAS = {
151
+ "0" => 'CIUDAD AUTONOMA BUENOS AIRES',
152
+ "1" => 'BUENOS AIRES',
153
+ "2" => 'CATAMARCA',
154
+ "3" => 'CORDOBA',
155
+ "4" => 'CORRIENTES',
156
+ "5" => 'ENTRE RIOS',
157
+ "6" => 'JUJUY',
158
+ "7" => 'MENDOZA',
159
+ "8" => 'LA RIOJA',
160
+ "9" => 'SALTA',
161
+ "10" => 'SAN JUAN',
162
+ "11" => 'SAN LUIS',
163
+ "12" => 'SANTA FE',
164
+ "13" => 'SANTIAGO DEL ESTERO',
165
+ "14" => 'TUCUMAN',
166
+ "16" => 'CHACO',
167
+ "17" => 'CHUBUT',
168
+ "18" => 'FORMOSA',
169
+ "19" => 'MISIONES',
170
+ "20" => 'NEUQUEN',
171
+ "21" => 'LA PAMPA',
172
+ "22" => 'RIO NEGRO',
173
+ "23" => 'SANTA CRUZ',
174
+ "24" => 'TIERRA DEL FUEGO'
175
+ }
176
+ end
177
+ end
178
+
179
+
180
+
181
+
data/lib/Afip/version.rb CHANGED
@@ -1,3 +1,3 @@
1
1
  module Afip
2
- VERSION = "0.1.1"
2
+ VERSION = "0.1.2"
3
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 CHANGED
@@ -1,6 +1,6 @@
1
1
  require "Afip/version"
2
2
  require "bundler/setup"
3
- require "Afip/constants"
3
+ require "./Afip/constants"
4
4
  require "savon"
5
5
  require "Afip/core_ext/float"
6
6
  require "Afip/core_ext/hash"
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.1.1
4
+ version: 0.1.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Facundo A. Díaz Martínez
@@ -45,16 +45,29 @@ executables: []
45
45
  extensions: []
46
46
  extra_rdoc_files: []
47
47
  files:
48
+ - ".DS_Store"
48
49
  - ".gitignore"
50
+ - Afip-0.1.0.gem
49
51
  - Afip.gemspec
50
52
  - Gemfile
53
+ - Gemfile.lock
51
54
  - LICENSE.txt
52
55
  - README.md
53
56
  - Rakefile
54
57
  - bin/console
55
58
  - bin/setup
59
+ - lib/.DS_Store
56
60
  - lib/Afip.rb
61
+ - lib/Afip/auth_data.rb
62
+ - lib/Afip/authorizer.rb
63
+ - lib/Afip/bill.rb
64
+ - lib/Afip/constants.rb
65
+ - lib/Afip/core_ext/float.rb
66
+ - lib/Afip/core_ext/hash.rb
67
+ - lib/Afip/core_ext/string.rb
68
+ - lib/Afip/padron.rb
57
69
  - lib/Afip/version.rb
70
+ - lib/Afip/wsaa.rb
58
71
  homepage: https://www.desideral.com
59
72
  licenses:
60
73
  - MIT