bravo 0.4.0 → 1.0.0.alpha
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +34 -0
- data/.travis.yml +9 -0
- data/.yardopts +13 -0
- data/CHANGELOG +7 -3
- data/Gemfile +6 -14
- data/Guardfile +5 -0
- data/LICENSE.txt +4 -2
- data/README.md +52 -0
- data/Rakefile +7 -47
- data/VERSION +1 -1
- data/bin/bravo +59 -0
- data/bravo.gemspec +22 -86
- data/lib/bravo/auth_data.rb +45 -10
- data/lib/bravo/bill.rb +74 -52
- data/lib/bravo/constants.rb +70 -10
- data/lib/bravo/core_ext/float.rb +6 -0
- data/lib/bravo/core_ext/hash.rb +12 -0
- data/lib/bravo/core_ext/string.rb +4 -1
- data/lib/bravo/reference.rb +37 -0
- data/lib/bravo/version.rb +3 -1
- data/lib/bravo/wsaa.rb +96 -0
- data/lib/bravo.rb +13 -8
- data/spec/bravo/auth_data_spec.rb +7 -6
- data/spec/bravo/bill_spec.rb +85 -67
- data/spec/bravo/reference_spec.rb +12 -0
- data/spec/bravo/wsaa_spec.rb +43 -0
- data/spec/spec_helper.rb +30 -15
- metadata +101 -156
- data/Gemfile.lock +0 -57
- data/README.textile +0 -53
- data/lib/bravo/authorizer.rb +0 -10
- data/spec/bravo/authorizer_spec.rb +0 -9
- data/wsaa-client.sh +0 -171
data/lib/bravo/bill.rb
CHANGED
@@ -1,59 +1,80 @@
|
|
1
1
|
module Bravo
|
2
|
+
# The main class in Bravo. Handles WSFE method interactions.
|
3
|
+
# Subsequent implementations will be added here (maybe).
|
4
|
+
#
|
2
5
|
class Bill
|
3
|
-
|
6
|
+
# Returns the Savon::Client instance in charge of the interactions with WSFE API.
|
7
|
+
# (built on init)
|
8
|
+
#
|
9
|
+
attr_reader :client
|
10
|
+
|
4
11
|
attr_accessor :net, :doc_num, :iva_cond, :documento, :concepto, :moneda,
|
5
12
|
:due_date, :aliciva_id, :fch_serv_desde, :fch_serv_hasta,
|
6
|
-
:body, :response
|
13
|
+
:body, :response, :invoice_type
|
7
14
|
|
8
15
|
def initialize(attrs = {})
|
9
16
|
Bravo::AuthData.fetch
|
10
|
-
@client
|
11
|
-
@body
|
12
|
-
|
13
|
-
|
14
|
-
self.
|
15
|
-
self.
|
16
|
-
self.concepto
|
17
|
+
@client = Savon.client(wsdl: Bravo::AuthData.wsfe_url, log: false)
|
18
|
+
@body = { "Auth" => Bravo::AuthData.auth_hash }
|
19
|
+
self.iva_cond = attrs[:iva_cond]
|
20
|
+
@net = attrs[:net] || 0
|
21
|
+
self.documento = attrs[:documento] || Bravo.default_documento
|
22
|
+
self.moneda = attrs[:moneda] || Bravo.default_moneda
|
23
|
+
self.concepto = attrs[:concepto] || Bravo.default_concepto
|
24
|
+
self.invoice_type = attrs[:invoice_type] || :invoice
|
17
25
|
end
|
18
26
|
|
27
|
+
# Searches the corresponding invoice type according to the combination of
|
28
|
+
# the seller's IVA condition and the buyer's IVA condition
|
29
|
+
# @return [String] the document type string
|
30
|
+
#
|
19
31
|
def cbte_type
|
20
|
-
Bravo::BILL_TYPE[Bravo.own_iva_cond]
|
21
|
-
|
22
|
-
|
32
|
+
own_iva = Bravo::BILL_TYPE.has_key?(Bravo.own_iva_cond) ? Bravo::BILL_TYPE[Bravo.own_iva_cond] : raise(NullOrInvalidAttribute.new, "Own iva_cond is invalid.")
|
33
|
+
target_iva = own_iva.has_key?(iva_cond) ? own_iva[iva_cond] : raise(NullOrInvalidAttribute.new, "Target iva_cond is invalid.")
|
34
|
+
type = target_iva.has_key?(invoice_type) ? target_iva[invoice_type] : raise(NullOrInvalidAttribute.new, "Selected invoice_type is invalid.")
|
23
35
|
|
24
|
-
def exchange_rate
|
25
|
-
return 1 if moneda == :peso
|
26
|
-
response = client.fe_param_get_cotizacion do |soap|
|
27
|
-
soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
|
28
|
-
soap.body = body.merge!({"MonId" => Bravo::MONEDAS[moneda][:codigo]})
|
29
|
-
end
|
30
|
-
response.to_hash[:fe_param_get_cotizacion_response][:fe_param_get_cotizacion_result][:result_get][:mon_cotiz].to_f
|
31
36
|
end
|
32
37
|
|
38
|
+
# Calculates the total field for the invoice by adding
|
39
|
+
# net and iva_sum.
|
40
|
+
# @return [Float] the sum of both fields, or 0 if the net is 0.
|
41
|
+
#
|
33
42
|
def total
|
34
43
|
@total = net.zero? ? 0 : net + iva_sum
|
35
44
|
end
|
36
45
|
|
46
|
+
# Calculates the corresponding iva sum.
|
47
|
+
# This is performed by multiplying the net by the tax value
|
48
|
+
# @return [Float] the iva sum
|
49
|
+
#
|
50
|
+
# TODO: fix this
|
51
|
+
#
|
37
52
|
def iva_sum
|
38
53
|
@iva_sum = net * Bravo::ALIC_IVA[aliciva_id][1]
|
39
54
|
@iva_sum.round_up_with_precision(2)
|
40
55
|
end
|
41
56
|
|
57
|
+
# Files the authorization request to AFIP
|
58
|
+
# @return [Boolean] wether the request succeeded or not
|
59
|
+
#
|
42
60
|
def authorize
|
43
61
|
setup_bill
|
44
|
-
response = client.fecae_solicitar do |soap|
|
45
|
-
soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
|
46
|
-
soap.
|
62
|
+
response = client.call(:fecae_solicitar) do |soap|
|
63
|
+
# soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
|
64
|
+
soap.message body
|
47
65
|
end
|
48
66
|
|
49
67
|
setup_response(response.to_hash)
|
50
68
|
self.authorized?
|
51
69
|
end
|
52
70
|
|
71
|
+
# Sets up the request body for the authorisation
|
72
|
+
# @return [Hash] returns the request body as a hash
|
73
|
+
#
|
53
74
|
def setup_bill
|
54
75
|
today = Time.new.strftime('%Y%m%d')
|
55
76
|
|
56
|
-
fecaereq = {"FeCAEReq" => {
|
77
|
+
fecaereq = { "FeCAEReq" => {
|
57
78
|
"FeCabReq" => Bravo::Bill.header(cbte_type),
|
58
79
|
"FeDetReq" => {
|
59
80
|
"FECAEDetRequest" => {
|
@@ -62,14 +83,14 @@ module Bravo
|
|
62
83
|
"CbteFch" => today,
|
63
84
|
"ImpTotConc" => 0.00,
|
64
85
|
"MonId" => Bravo::MONEDAS[moneda][:codigo],
|
65
|
-
"MonCotiz" =>
|
86
|
+
"MonCotiz" => 1,
|
66
87
|
"ImpOpEx" => 0.00,
|
67
88
|
"ImpTrib" => 0.00,
|
68
89
|
"Iva" => {
|
69
90
|
"AlicIva" => {
|
70
91
|
"Id" => "5",
|
71
92
|
"BaseImp" => net,
|
72
|
-
"Importe" => iva_sum}}}}}}
|
93
|
+
"Importe" => iva_sum } } } } } }
|
73
94
|
|
74
95
|
detail = fecaereq["FeCAEReq"]["FeDetReq"]["FECAEDetRequest"]
|
75
96
|
|
@@ -77,26 +98,20 @@ module Bravo
|
|
77
98
|
detail["ImpNeto"] = net.to_f
|
78
99
|
detail["ImpIVA"] = iva_sum
|
79
100
|
detail["ImpTotal"] = total
|
80
|
-
detail["CbteDesde"] = detail["CbteHasta"] = next_bill_number
|
101
|
+
detail["CbteDesde"] = detail["CbteHasta"] = Bravo::Reference.next_bill_number(cbte_type)
|
81
102
|
|
82
103
|
unless concepto == 0
|
83
|
-
detail.merge!({"FchServDesde"
|
84
|
-
|
85
|
-
|
104
|
+
detail.merge!({ "FchServDesde" => fch_serv_desde || today,
|
105
|
+
"FchServHasta" => fch_serv_hasta || today,
|
106
|
+
"FchVtoPago" => due_date || today })
|
86
107
|
end
|
87
108
|
|
88
109
|
body.merge!(fecaereq)
|
89
110
|
end
|
90
111
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
s.body = {"Auth" => Bravo.auth_hash, "PtoVta" => Bravo.sale_point, "CbteTipo" => cbte_type}
|
95
|
-
end
|
96
|
-
|
97
|
-
resp.to_hash[:fe_comp_ultimo_autorizado_response][:fe_comp_ultimo_autorizado_result][:cbte_nro].to_i + 1
|
98
|
-
end
|
99
|
-
|
112
|
+
# Returns the result of the authorization operation
|
113
|
+
# @return [Boolean] the response result
|
114
|
+
#
|
100
115
|
def authorized?
|
101
116
|
!response.nil? && response.header_result == "A" && response.detail_result == "A"
|
102
117
|
end
|
@@ -104,11 +119,18 @@ module Bravo
|
|
104
119
|
private
|
105
120
|
|
106
121
|
class << self
|
107
|
-
|
108
|
-
|
122
|
+
# Sets the header hash for the request
|
123
|
+
# @return [Hash]
|
124
|
+
#
|
125
|
+
def header(cbte_type)
|
126
|
+
# todo sacado de la factura
|
127
|
+
{ "CantReg" => "1", "CbteTipo" => cbte_type, "PtoVta" => Bravo.sale_point }
|
109
128
|
end
|
110
129
|
end
|
111
130
|
|
131
|
+
# Response parser. Only works for the authorize method
|
132
|
+
# @return [Struct] a struct with key-value pairs with the response values
|
133
|
+
#
|
112
134
|
def setup_response(response)
|
113
135
|
# TODO: turn this into an all-purpose Response class
|
114
136
|
|
@@ -124,18 +146,18 @@ module Bravo
|
|
124
146
|
|
125
147
|
request_detail.merge!(iva)
|
126
148
|
|
127
|
-
response_hash = {:header_result => response_header.delete(:resultado),
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
149
|
+
response_hash = { :header_result => response_header.delete(:resultado),
|
150
|
+
:authorized_on => response_header.delete(:fch_proceso),
|
151
|
+
:detail_result => response_detail.delete(:resultado),
|
152
|
+
:cae_due_date => response_detail.delete(:cae_fch_vto),
|
153
|
+
:cae => response_detail.delete(:cae),
|
154
|
+
:iva_id => request_detail.delete(:id),
|
155
|
+
:iva_importe => request_detail.delete(:importe),
|
156
|
+
:moneda => request_detail.delete(:mon_id),
|
157
|
+
:cotizacion => request_detail.delete(:mon_cotiz),
|
158
|
+
:iva_base_imp => request_detail.delete(:base_imp),
|
159
|
+
:doc_num => request_detail.delete(:doc_nro)
|
160
|
+
}.merge!(request_header).merge!(request_detail)
|
139
161
|
|
140
162
|
keys, values = response_hash.to_a.transpose
|
141
163
|
self.response = (defined?(Struct::Response) ? Struct::Response : Struct.new("Response", *keys)).new(*values)
|
data/lib/bravo/constants.rb
CHANGED
@@ -1,5 +1,9 @@
|
|
1
1
|
# encoding: utf-8
|
2
|
+
# Here we define Hashes
|
3
|
+
#
|
2
4
|
module Bravo
|
5
|
+
# This constant contains the invoice types mappings between codes and names
|
6
|
+
# used by WSFE.
|
3
7
|
CBTE_TIPO = {
|
4
8
|
"01"=>"Factura A",
|
5
9
|
"02"=>"Nota de Débito A",
|
@@ -21,19 +25,75 @@ module Bravo
|
|
21
25
|
"64"=>"Liquidacion B"
|
22
26
|
}
|
23
27
|
|
24
|
-
|
28
|
+
# Name to code mapping for Sale types.
|
29
|
+
#
|
30
|
+
CONCEPTOS = { "Productos"=>"01", "Servicios"=>"02", "Productos y Servicios"=>"03" }
|
25
31
|
|
26
|
-
|
32
|
+
# Name to code mapping for types of documents.
|
33
|
+
#
|
34
|
+
DOCUMENTOS = {
|
35
|
+
"CUIT"=>"80",
|
36
|
+
"CUIL"=>"86",
|
37
|
+
"CDI"=>"87",
|
38
|
+
"LE"=>"89",
|
39
|
+
"LC"=>"90",
|
40
|
+
"CI Extranjera"=>"91",
|
41
|
+
"en tramite"=>"92",
|
42
|
+
"Acta Nacimiento"=>"93",
|
43
|
+
"CI Bs. As. RNP"=>"95",
|
44
|
+
"DNI"=>"96",
|
45
|
+
"Pasaporte"=>"94",
|
46
|
+
"Doc. (Otro)"=>"99" }
|
27
47
|
|
48
|
+
# Currency code and names hash identified by a symbol
|
49
|
+
#
|
28
50
|
MONEDAS = {
|
29
|
-
:peso => {:codigo => "PES", :nombre =>"Pesos Argentinos"},
|
30
|
-
:dolar => {:codigo => "DOL", :nombre =>"Dolar Estadounidense"},
|
31
|
-
:real => {:codigo => "012", :nombre =>"Real"},
|
32
|
-
:euro => {:codigo => "060", :nombre =>"Euro"},
|
33
|
-
:oro => {:codigo => "049", :nombre =>"Gramos de Oro Fino"}
|
34
|
-
|
51
|
+
:peso => { :codigo => "PES", :nombre =>"Pesos Argentinos" },
|
52
|
+
:dolar => { :codigo => "DOL", :nombre =>"Dolar Estadounidense" },
|
53
|
+
:real => { :codigo => "012", :nombre =>"Real" },
|
54
|
+
:euro => { :codigo => "060", :nombre =>"Euro" },
|
55
|
+
:oro => { :codigo => "049", :nombre =>"Gramos de Oro Fino" } }
|
56
|
+
|
35
57
|
|
58
|
+
# Tax percentage and codes according to each iva combination
|
59
|
+
#
|
36
60
|
ALIC_IVA = [["03", 0], ["04", 0.105], ["05", 0.21], ["06", 0.27]]
|
37
61
|
|
38
|
-
|
39
|
-
|
62
|
+
|
63
|
+
|
64
|
+
# This hash keeps the codes for A document types by operation
|
65
|
+
#
|
66
|
+
BILL_TYPE_A = {
|
67
|
+
:invoice => "01",
|
68
|
+
:debit => "02",
|
69
|
+
:credit => "03" }
|
70
|
+
|
71
|
+
# This hash keeps the codes for A document types by operation
|
72
|
+
#
|
73
|
+
BILL_TYPE_B = {
|
74
|
+
:invoice => "06",
|
75
|
+
:debit => "07",
|
76
|
+
:credit => "08" }
|
77
|
+
|
78
|
+
# This hash keeps the different buyer and invoice type mapping corresponding to
|
79
|
+
# the seller's iva condition and invoice kind.
|
80
|
+
# Usage:
|
81
|
+
# `BILL_TYPE[seller_iva_cond][buyer_iva_cond][invoice_type]` #=> invoice type as string
|
82
|
+
# `BILL_TYPE[:responsable_inscripto][:responsable_inscripto][:invoice]` #=> "01"
|
83
|
+
#
|
84
|
+
BILL_TYPE = {
|
85
|
+
:responsable_inscripto => {
|
86
|
+
:responsable_inscripto => BILL_TYPE_A,
|
87
|
+
:consumidor_final => BILL_TYPE_B,
|
88
|
+
:exento => BILL_TYPE_B,
|
89
|
+
:responsable_monotributo => BILL_TYPE_B } }
|
90
|
+
|
91
|
+
# This hash keeps the set of urls for wsaa and wsfe for production and testing envs
|
92
|
+
#
|
93
|
+
URLS = {
|
94
|
+
:test => { :wsaa => "https://wsaahomo.afip.gov.ar/ws/services/LoginCms",
|
95
|
+
:wsfe => "https://wswhomo.afip.gov.ar/wsfev1/service.asmx?WSDL" },
|
96
|
+
|
97
|
+
:production => { :wsaa => "https://wsaa.afip.gov.ar/ws/services/LoginCms",
|
98
|
+
:wsfe => "https://servicios1.afip.gov.ar/wsfev1/service.asmx" } }
|
99
|
+
end
|
data/lib/bravo/core_ext/float.rb
CHANGED
@@ -1,7 +1,13 @@
|
|
1
|
+
# Float monkeypatching.
|
2
|
+
#
|
1
3
|
class Float
|
4
|
+
# Should be removed.
|
5
|
+
#
|
2
6
|
def round_with_precision(precision = nil)
|
3
7
|
precision.nil? ? round : (self * (10 ** precision)).round / (10 ** precision).to_f
|
4
8
|
end
|
9
|
+
# Should be removed.
|
10
|
+
#
|
5
11
|
def round_up_with_precision(precision = nil)
|
6
12
|
precision.nil? ? round : ((self * (10 ** precision)).round + 1) / (10 ** precision).to_f
|
7
13
|
end
|
data/lib/bravo/core_ext/hash.rb
CHANGED
@@ -1,4 +1,8 @@
|
|
1
|
+
# Methods stolen from ActiveSupport, to avoid requiring the gem as a dependency
|
1
2
|
class Hash
|
3
|
+
# Alters the hash, converting it's keys to symbols
|
4
|
+
# @return [Hash]
|
5
|
+
#
|
2
6
|
def symbolize_keys!
|
3
7
|
keys.each do |key|
|
4
8
|
self[(key.to_sym rescue key) || key] = delete(key)
|
@@ -6,10 +10,16 @@ class Hash
|
|
6
10
|
self
|
7
11
|
end unless method_defined?(:symbolize_keys!)
|
8
12
|
|
13
|
+
# Returns a copy of the hash, with it's keys converted to symbols
|
14
|
+
# @return [Hash]
|
15
|
+
#
|
9
16
|
def symbolize_keys
|
10
17
|
dup.symbolize_keys!
|
11
18
|
end unless method_defined?(:symbolize_keys)
|
12
19
|
|
20
|
+
# Alters the hash, converting its keys to underscore strings
|
21
|
+
# @return [Hash]
|
22
|
+
#
|
13
23
|
def underscore_keys!
|
14
24
|
keys.each do |key|
|
15
25
|
self[(key.underscore rescue key) || key] = delete(key)
|
@@ -17,6 +27,8 @@ class Hash
|
|
17
27
|
self
|
18
28
|
end unless method_defined?(:underscore_keys!)
|
19
29
|
|
30
|
+
# Returns a copy of the hash, with it's keys converted to underscore strings
|
31
|
+
# @return [Hash]
|
20
32
|
def underscore_keys
|
21
33
|
dup.underscore_keys!
|
22
34
|
end unless method_defined?(:underscore_keys)
|
@@ -1,5 +1,8 @@
|
|
1
|
-
#
|
1
|
+
# Added to avoid requiring ActiveSupport as dependency
|
2
|
+
#
|
2
3
|
class String
|
4
|
+
# Stolen from activesupport/lib/active_support/inflector/methods.rb, line 48
|
5
|
+
#
|
3
6
|
def underscore
|
4
7
|
word = self.to_s.dup
|
5
8
|
word.gsub!(/::/, '/')
|
@@ -0,0 +1,37 @@
|
|
1
|
+
module Bravo
|
2
|
+
# Class in charge of issuing read requests on the api
|
3
|
+
#
|
4
|
+
class Reference
|
5
|
+
# Fetches the number for the next bill to be issued
|
6
|
+
# @return [Integer] the number for the next bill
|
7
|
+
#
|
8
|
+
def self.next_bill_number(cbte_type)
|
9
|
+
set_client
|
10
|
+
resp = @client.call(:fe_comp_ultimo_autorizado) do |soap|
|
11
|
+
# soap.namespaces["xmlns"] = "http://ar.gov.afip.dif.FEV1/"
|
12
|
+
soap.message "Auth" => Bravo::AuthData.auth_hash, "PtoVta" => Bravo.sale_point, "CbteTipo" => cbte_type
|
13
|
+
end
|
14
|
+
|
15
|
+
resp.to_hash[:fe_comp_ultimo_autorizado_response][:fe_comp_ultimo_autorizado_result][:cbte_nro].to_i + 1
|
16
|
+
end
|
17
|
+
|
18
|
+
# Fetches the possible document codes and names
|
19
|
+
# @return [Hash]
|
20
|
+
#
|
21
|
+
def self.get_custom(operation)
|
22
|
+
set_client
|
23
|
+
resp = @client.call(operation) do |soap|
|
24
|
+
soap.message "Auth" => Bravo::AuthData.auth_hash
|
25
|
+
end
|
26
|
+
resp.to_hash
|
27
|
+
end
|
28
|
+
|
29
|
+
# Sets up the cliet to perform consults to the api
|
30
|
+
#
|
31
|
+
#
|
32
|
+
def self.set_client
|
33
|
+
Bravo::AuthData.fetch
|
34
|
+
@client = Savon.client(wsdl: Bravo::AuthData.wsfe_url, log: false)
|
35
|
+
end
|
36
|
+
end
|
37
|
+
end
|
data/lib/bravo/version.rb
CHANGED
data/lib/bravo/wsaa.rb
ADDED
@@ -0,0 +1,96 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
module Bravo
|
3
|
+
# Authorization class. Handles interactions wiht the WSAA, to provide
|
4
|
+
# valid key and signature that will last for a day.
|
5
|
+
#
|
6
|
+
class Wsaa
|
7
|
+
# Main method for authentication and authorization.
|
8
|
+
# When successful, produces the yaml file with auth data.
|
9
|
+
#
|
10
|
+
def self.login
|
11
|
+
tra = build_tra
|
12
|
+
cms = build_cms(tra)
|
13
|
+
req = build_request(cms)
|
14
|
+
auth = call_wsaa(req)
|
15
|
+
|
16
|
+
write_yaml(auth)
|
17
|
+
end
|
18
|
+
|
19
|
+
protected
|
20
|
+
# Builds the xml for the "Ticket de Requerimiento de Acceso"
|
21
|
+
# @return [String] containing the request body
|
22
|
+
#
|
23
|
+
def self.build_tra
|
24
|
+
now = Time.now
|
25
|
+
from = now.strftime("%FT%T%:z")
|
26
|
+
to = ((now - 120) + (24*60*60)).strftime("%FT%T%:z") # make sure ti will last for 24hs - 2 mins
|
27
|
+
id = now.strftime("%s")
|
28
|
+
tra = <<-EOF
|
29
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
30
|
+
<loginTicketRequest version="1.0">
|
31
|
+
<header>
|
32
|
+
<uniqueId>#{ id }</uniqueId>
|
33
|
+
<generationTime>#{ from }</generationTime>
|
34
|
+
<expirationTime>#{ to }</expirationTime>
|
35
|
+
</header>
|
36
|
+
<service>wsfe</service>
|
37
|
+
</loginTicketRequest>
|
38
|
+
EOF
|
39
|
+
return tra
|
40
|
+
end
|
41
|
+
|
42
|
+
# Builds the CMS
|
43
|
+
# @return [String] cms
|
44
|
+
#
|
45
|
+
def self.build_cms(tra)
|
46
|
+
cms = `echo '#{ tra }' |
|
47
|
+
#{ Bravo.openssl_bin } cms -sign -in /dev/stdin -signer #{ Bravo.cert } -inkey #{ Bravo.pkey } -nodetach \
|
48
|
+
-outform der |
|
49
|
+
#{ Bravo.openssl_bin } base64 -e`
|
50
|
+
return cms
|
51
|
+
end
|
52
|
+
|
53
|
+
# Builds the CMS request to log in to the server
|
54
|
+
# @return [String] the cms body
|
55
|
+
#
|
56
|
+
def self.build_request(cms)
|
57
|
+
request = <<-XML
|
58
|
+
<?xml version="1.0" encoding="UTF-8"?>
|
59
|
+
<SOAP-ENV:Envelope xmlns:SOAP-ENV="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ns1="http://wsaa.view.sua.dvadac.desein.afip.gov">
|
60
|
+
<SOAP-ENV:Body>
|
61
|
+
<ns1:loginCms>
|
62
|
+
<ns1:in0>
|
63
|
+
#{ cms }
|
64
|
+
</ns1:in0>
|
65
|
+
</ns1:loginCms>
|
66
|
+
</SOAP-ENV:Body>
|
67
|
+
</SOAP-ENV:Envelope>
|
68
|
+
XML
|
69
|
+
return request
|
70
|
+
end
|
71
|
+
|
72
|
+
# Calls the WSAA with the request built by build_request
|
73
|
+
# @return [Array] with the token and signature
|
74
|
+
#
|
75
|
+
def self.call_wsaa(req)
|
76
|
+
response = `echo '#{ req }' |
|
77
|
+
curl -k -s -H 'Content-Type: application/soap+xml; action=""' -d @- #{ Bravo::AuthData.wsaa_url }`
|
78
|
+
|
79
|
+
response = CGI::unescapeHTML(response)
|
80
|
+
token = response.scan(/\<token\>(.+)\<\/token\>/).first.first
|
81
|
+
sign = response.scan(/\<sign\>(.+)\<\/sign\>/).first.first
|
82
|
+
return [token, sign]
|
83
|
+
end
|
84
|
+
|
85
|
+
# Writes the token and signature to a YAML file in the /tmp directory
|
86
|
+
#
|
87
|
+
def self.write_yaml(certs)
|
88
|
+
yml = <<-YML
|
89
|
+
token: #{certs[0]}
|
90
|
+
sign: #{certs[1]}
|
91
|
+
YML
|
92
|
+
`echo '#{ yml }' > /tmp/bravo_#{ Bravo.cuit }_#{ Time.new.strftime('%d_%m_%Y') }.yml`
|
93
|
+
end
|
94
|
+
|
95
|
+
end
|
96
|
+
end
|
data/lib/bravo.rb
CHANGED
@@ -5,23 +5,28 @@ require "savon"
|
|
5
5
|
require "bravo/core_ext/float"
|
6
6
|
require "bravo/core_ext/hash"
|
7
7
|
require "bravo/core_ext/string"
|
8
|
+
|
8
9
|
module Bravo
|
9
10
|
|
11
|
+
# Exception Class for missing or invalid attributes
|
12
|
+
#
|
10
13
|
class NullOrInvalidAttribute < StandardError; end
|
11
14
|
|
15
|
+
# Exception Clas for missing or invalid certifficate
|
16
|
+
#
|
17
|
+
class MissingCertificate < StandardError; end
|
18
|
+
|
12
19
|
autoload :Authorizer, "bravo/authorizer"
|
13
20
|
autoload :AuthData, "bravo/auth_data"
|
14
21
|
autoload :Bill, "bravo/bill"
|
15
22
|
autoload :Constants, "bravo/constants"
|
16
|
-
|
23
|
+
autoload :Wsaa, "bravo/wsaa"
|
24
|
+
autoload :Reference, "bravo/reference"
|
17
25
|
|
18
26
|
extend self
|
19
|
-
attr_accessor :cuit, :sale_point, :service_url, :default_documento, :pkey, :cert,
|
20
|
-
:default_concepto, :default_moneda, :own_iva_cond, :verbose, :auth_url
|
21
27
|
|
22
|
-
|
23
|
-
|
24
|
-
|
28
|
+
attr_accessor :cuit, :sale_point, :default_documento, :pkey, :cert,
|
29
|
+
:default_concepto, :default_moneda, :own_iva_cond,
|
30
|
+
:verbose, :openssl_bin
|
25
31
|
|
26
|
-
|
27
|
-
end
|
32
|
+
end
|
@@ -1,12 +1,13 @@
|
|
1
1
|
require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
|
2
2
|
|
3
3
|
describe "AuthData" do
|
4
|
-
|
5
|
-
|
6
|
-
|
4
|
+
describe ".fetch" do
|
5
|
+
it "creates constants for todays data" do
|
6
|
+
Bravo.constants.should_not include(:TOKEN, :SIGN)
|
7
|
+
|
8
|
+
Bravo::AuthData.fetch
|
9
|
+
|
7
10
|
Bravo.constants.should include(:TOKEN, :SIGN)
|
8
|
-
else
|
9
|
-
Bravo.constants.should include("TOKEN", "SIGN")
|
10
11
|
end
|
11
12
|
end
|
12
|
-
end
|
13
|
+
end
|