bravo 0.4.0 → 1.0.0.alpha
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.
- 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
|