arca.rb 1.0.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +4 -0
- data/Gemfile.lock +1 -1
- data/LICENSE +1 -1
- data/README.md +6 -1
- data/lib/arca/version.rb +1 -1
- data/lib/arca/wsaa.rb +46 -1
- data/test/arca/wsaa_test.rb +57 -0
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: dd4d89db0b9d4d77abb2b5e6eb0e03d3169893d511a0e2b066f12e3c1d613e2c
|
|
4
|
+
data.tar.gz: 49b8a96793606351fa4a9ca0b137cab1f7efc2459ef5604d9efffe0a20c5b54f
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 4b6060fe8e15d82e5ca2cf8cfa5f7651899ae10661df16a389b6cb43ddcfbed79f6303b17a126b654d40e1b5830ba78263f6a20f7af6d11a1fe36f89eee8c459
|
|
7
|
+
data.tar.gz: 02242344d53082a0b5b7f3b6aba1f004184b8aa8ac64e91ac9ed07b7f623a95191fe82582f0acd14256d616d5b2928f59b07471c73351887dcf7fb4b8af7342f
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Arca Changelog
|
|
2
2
|
|
|
3
|
+
## [1.1.0]
|
|
4
|
+
|
|
5
|
+
- **WSAA:** Store opcional para TA. Podés pasar `store:` con un objeto que implemente `read(cuit:, env:, service:)` y `write(cuit:, env:, service:, ta:, expires_at:)` para guardar el token en tu backend (p. ej. DB por tenant con cifrado). Sin `store`, se sigue usando el archivo JSON en `tmp/`.
|
|
6
|
+
|
|
3
7
|
## [1.0.2] - 2025-02-09
|
|
4
8
|
|
|
5
9
|
- **Errores:** En WSAA, `persist_ta` ahora encapsula fallos en `Arca::ServerError` en lugar de re-levantar excepciones crudas (mejor encapsulación y menos riesgo de filtrar datos sensibles en logs).
|
data/Gemfile.lock
CHANGED
data/LICENSE
CHANGED
data/README.md
CHANGED
|
@@ -75,7 +75,7 @@ Opción del constructor: `env: :development` o `env: :production` (symbol o stri
|
|
|
75
75
|
|
|
76
76
|
## Uso
|
|
77
77
|
|
|
78
|
-
Opciones comunes del constructor: `env`, `cuit`, `key`, `cert`. Opcionales: `savon` (hash pasado a Savon), `ta_path` (WSAA), etc.
|
|
78
|
+
Opciones comunes del constructor: `env`, `cuit`, `key`, `cert`. Opcionales: `savon` (hash pasado a Savon), `ta_path` (WSAA, path del archivo TA), `store` (WSAA, objeto con `read`/`write` para guardar TA en backend propio), etc.
|
|
79
79
|
|
|
80
80
|
### Cliente de bajo nivel (`Arca::Client`)
|
|
81
81
|
|
|
@@ -112,6 +112,11 @@ wsaa = Arca::WSAA.new(env: :development, cuit: '20123456789', key: key, cert: ce
|
|
|
112
112
|
auth = wsaa.auth # => { token: '...', sign: '...' }
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
Opcionalmente podés pasar un **store** para guardar el TA en tu propio backend (por ejemplo DB por tenant con cifrado). Sin `store`, se usa un archivo JSON en `tmp/`. El store debe implementar:
|
|
116
|
+
|
|
117
|
+
- **`read(cuit:, env:, service:)`** — devuelve `nil` o un Hash con claves simbólicas: `:token`, `:sign`, `:generation_time`, `:expiration_time` (Time o string ISO8601).
|
|
118
|
+
- **`write(cuit:, env:, service:, ta:, expires_at:)`** — se invoca después de un `#login` exitoso; `ta` es el hash del TA y `expires_at` el Time de vencimiento.
|
|
119
|
+
|
|
115
120
|
### WSFE (Factura Electrónica)
|
|
116
121
|
```ruby
|
|
117
122
|
ws = Arca::WSFE.new(env: :development, cuit: '20123456789', key: key, cert: cert)
|
data/lib/arca/version.rb
CHANGED
data/lib/arca/wsaa.rb
CHANGED
|
@@ -4,7 +4,7 @@ require "json"
|
|
|
4
4
|
|
|
5
5
|
module Arca
|
|
6
6
|
class WSAA
|
|
7
|
-
attr_reader :key, :cert, :service, :ta, :cuit, :client, :env
|
|
7
|
+
attr_reader :key, :cert, :service, :ta, :cuit, :client, :env, :store
|
|
8
8
|
|
|
9
9
|
WSDL = {
|
|
10
10
|
development: "https://wsaahomo.afip.gov.ar/ws/services/LoginCms?wsdl",
|
|
@@ -20,6 +20,7 @@ module Arca
|
|
|
20
20
|
@ttl = options[:ttl] || 2400
|
|
21
21
|
@cuit = options[:cuit]
|
|
22
22
|
@client = Client.new Hash(options[:savon]).reverse_merge(wsdl: WSDL[@env])
|
|
23
|
+
@store = options[:store]
|
|
23
24
|
@ta_path = options[:ta_path] || default_ta_path
|
|
24
25
|
end
|
|
25
26
|
|
|
@@ -79,6 +80,10 @@ module Arca
|
|
|
79
80
|
end
|
|
80
81
|
|
|
81
82
|
def restore_ta
|
|
83
|
+
if @store
|
|
84
|
+
return restore_ta_from_store
|
|
85
|
+
end
|
|
86
|
+
|
|
82
87
|
return nil unless File.exist?(@ta_path) && !File.empty?(@ta_path)
|
|
83
88
|
|
|
84
89
|
data = JSON.parse(File.read(@ta_path))
|
|
@@ -92,6 +97,27 @@ module Arca
|
|
|
92
97
|
nil
|
|
93
98
|
end
|
|
94
99
|
|
|
100
|
+
def restore_ta_from_store
|
|
101
|
+
h = @store.read(cuit: @cuit, env: @env, service: @service)
|
|
102
|
+
return nil if h.nil? || !h.is_a?(Hash)
|
|
103
|
+
|
|
104
|
+
{
|
|
105
|
+
token: h[:token],
|
|
106
|
+
sign: h[:sign],
|
|
107
|
+
generation_time: normalize_ta_time(h[:generation_time]),
|
|
108
|
+
expiration_time: normalize_ta_time(h[:expiration_time])
|
|
109
|
+
}
|
|
110
|
+
end
|
|
111
|
+
|
|
112
|
+
def normalize_ta_time(value)
|
|
113
|
+
return nil if value.nil?
|
|
114
|
+
return value if value.is_a?(Time)
|
|
115
|
+
|
|
116
|
+
Time.parse(value.to_s)
|
|
117
|
+
rescue ArgumentError
|
|
118
|
+
nil
|
|
119
|
+
end
|
|
120
|
+
|
|
95
121
|
def ta_expirado?(ta)
|
|
96
122
|
if ta.nil?
|
|
97
123
|
true
|
|
@@ -103,6 +129,25 @@ module Arca
|
|
|
103
129
|
end
|
|
104
130
|
|
|
105
131
|
def persist_ta(ta)
|
|
132
|
+
if @store
|
|
133
|
+
begin
|
|
134
|
+
@store.write(
|
|
135
|
+
cuit: @cuit,
|
|
136
|
+
env: @env,
|
|
137
|
+
service: @service,
|
|
138
|
+
ta: ta,
|
|
139
|
+
expires_at: ta[:expiration_time]
|
|
140
|
+
)
|
|
141
|
+
rescue StandardError => e
|
|
142
|
+
raise ServerError, e
|
|
143
|
+
end
|
|
144
|
+
return
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
persist_ta_to_file(ta)
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def persist_ta_to_file(ta)
|
|
106
151
|
dir = File.dirname(@ta_path)
|
|
107
152
|
FileUtils.mkdir_p(dir)
|
|
108
153
|
|
data/test/arca/wsaa_test.rb
CHANGED
|
@@ -86,5 +86,62 @@ module Arca
|
|
|
86
86
|
assert_equal Errno::EACCES, error.cause.class
|
|
87
87
|
assert_match(/Permission denied/, error.cause.message)
|
|
88
88
|
end
|
|
89
|
+
|
|
90
|
+
def test_auth_con_store_usa_read_y_no_llama_login_si_ta_valido
|
|
91
|
+
Time.stubs(:now).returns(Time.local(2010, 1, 1))
|
|
92
|
+
ta = { token: "stored-token", sign: "stored-sign", generation_time: Time.now, expiration_time: Time.now + 3600 }
|
|
93
|
+
store = Object.new
|
|
94
|
+
store.expects(:read).with(cuit: "20123456789", env: :test, service: "wsfe").returns(ta)
|
|
95
|
+
|
|
96
|
+
ws = WSAA.new(cuit: "20123456789", env: :test, service: "wsfe", wsdl: Arca::WSFE::WSDL[:test], store: store)
|
|
97
|
+
ws.expects(:login).never
|
|
98
|
+
|
|
99
|
+
assert_equal({ token: "stored-token", sign: "stored-sign" }, ws.auth)
|
|
100
|
+
end
|
|
101
|
+
|
|
102
|
+
def test_auth_con_store_llama_login_y_write_cuando_read_devuelve_nil
|
|
103
|
+
Time.stubs(:now).returns(Time.local(2010, 1, 1))
|
|
104
|
+
ta = { token: "new-token", sign: "new-sign", generation_time: Time.now, expiration_time: Time.now + 60 }
|
|
105
|
+
store = Object.new
|
|
106
|
+
store.expects(:read).with(cuit: "20123456789", env: :test, service: "wsfe").returns(nil)
|
|
107
|
+
store.expects(:write).with(cuit: "20123456789", env: :test, service: "wsfe", ta: ta, expires_at: ta[:expiration_time]).returns(nil)
|
|
108
|
+
|
|
109
|
+
ws = WSAA.new(cuit: "20123456789", env: :test, service: "wsfe", wsdl: Arca::WSFE::WSDL[:test], store: store)
|
|
110
|
+
ws.expects(:login).returns(ta)
|
|
111
|
+
|
|
112
|
+
ws.auth
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def test_auth_con_store_normaliza_tiempos_iso8601_de_read
|
|
116
|
+
Time.stubs(:now).returns(Time.local(2010, 1, 1))
|
|
117
|
+
gen_str = "2010-01-01T00:00:00-03:00"
|
|
118
|
+
exp_str = "2010-01-01T02:00:00-03:00"
|
|
119
|
+
store = Object.new
|
|
120
|
+
store.expects(:read).with(cuit: "20123456789", env: :test, service: "wsfe").returns({ token: "t", sign: "s", generation_time: gen_str, expiration_time: exp_str })
|
|
121
|
+
|
|
122
|
+
ws = WSAA.new(cuit: "20123456789", env: :test, service: "wsfe", wsdl: Arca::WSFE::WSDL[:test], store: store)
|
|
123
|
+
ws.expects(:login).never
|
|
124
|
+
|
|
125
|
+
ws.auth
|
|
126
|
+
assert_instance_of(Time, ws.ta[:generation_time])
|
|
127
|
+
assert_instance_of(Time, ws.ta[:expiration_time])
|
|
128
|
+
assert_equal(Time.parse(gen_str), ws.ta[:generation_time])
|
|
129
|
+
assert_equal(Time.parse(exp_str), ws.ta[:expiration_time])
|
|
130
|
+
end
|
|
131
|
+
|
|
132
|
+
def test_persist_ta_con_store_encapsula_errores_en_server_error
|
|
133
|
+
ta = { token: "t", sign: "s", generation_time: Time.now, expiration_time: Time.now + 60 }
|
|
134
|
+
store = Class.new do
|
|
135
|
+
def read(*) nil end
|
|
136
|
+
def write(*) raise Errno::EACCES, "Permission denied" end
|
|
137
|
+
end.new
|
|
138
|
+
|
|
139
|
+
ws = WSAA.new(cuit: "1", env: :test, service: "wsfe", wsdl: Arca::WSFE::WSDL[:test], store: store)
|
|
140
|
+
assert_same store, ws.store
|
|
141
|
+
|
|
142
|
+
error = assert_raises(ServerError) { ws.send(:persist_ta, ta) }
|
|
143
|
+
assert_equal Errno::EACCES, error.cause.class
|
|
144
|
+
assert_match(/Permission denied/, error.cause.message)
|
|
145
|
+
end
|
|
89
146
|
end
|
|
90
147
|
end
|