itau_shopline 0.0.1

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/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source "http://rubygems.org"
2
+
3
+ # Specify your gem's dependencies in itau_cripto.gemspec
4
+ gemspec
data/README.markdown ADDED
@@ -0,0 +1,157 @@
1
+ Itau Shopline (itau_cripto)
2
+ ------------
3
+
4
+ Gem para integração com Itau Shopline.
5
+
6
+ ### Observação
7
+
8
+ O código desta gem é muito ruim pois foi "extraído" de uma classe Java fornecido pelo banco,
9
+ em breve estarei melhorando isso inclusive adicionando testes unitários, caso você possa
10
+ contribuir entre em contato comigo.
11
+
12
+ COMO USAR
13
+ ---------
14
+
15
+
16
+ ### Configuração
17
+
18
+ O primeiro passo é instalar a gem.
19
+
20
+ ```ruby
21
+ gem install itau_shopline
22
+ ```
23
+
24
+ ou no Gemfile
25
+
26
+ ```ruby
27
+ gem itau_shopline
28
+ ```
29
+
30
+ Depois de instalar, você precisará informar seus dados criando o arquivo:
31
+
32
+ ### config/itau_shopline.yml
33
+
34
+ ```yaml
35
+ development:
36
+ codigo_empresa: CÓDIGO DA EMPRESA
37
+ chave: CHAVE FORNECIDA PELO BANCO
38
+
39
+ test:
40
+ codigo_empresa: CÓDIGO DA EMPRESA
41
+ chave: CHAVE FORNECIDA PELO BANCO
42
+
43
+ production:
44
+ codigo_empresa: CÓDIGO DA EMPRESA
45
+ chave: CHAVE FORNECIDA PELO BANCO
46
+
47
+ staging:
48
+ codigo_empresa: CÓDIGO DA EMPRESA
49
+ chave: CHAVE FORNECIDA PELO BANCO
50
+
51
+ ```
52
+
53
+ Efetuando uma transação
54
+ -----------------------
55
+
56
+ Para efetuar uma compra você pode usar a ```ItauCripto``` direto e seguir o manual do banco,
57
+ ou usar uma helper class chamada ```ItauShopline```, segue exemplo.
58
+
59
+ ```ruby
60
+
61
+ # gera_dados(invoice_id, total_price, full_name, city_name, state_uf, due_date=nil, return_url="")
62
+ # Atenção: Você deve passar somente o path de retorno,
63
+ # pois o banco ira concatenar isso ao endereço principal do site cadastrado.
64
+
65
+ @itau_dc = ItauShopline.new.gera_dados(1, 22.50, 'Fulano de Tal', 'Goiânia', 'GO', nil, 'faturamento/itau/aviso')
66
+
67
+ ```
68
+
69
+ Após isso vc deve chamar a tela para pagamentos.
70
+
71
+ ```html
72
+ <script type="text/javascript" charset="utf-8">
73
+ function carregaItauShopline() {
74
+ window.open('https://shopline.itau.com.br/shopline/shopline.aspx?DC=<%= @itau_dc %>', 'SHOPLINE', "toolbar=yes,menubar=yes,resizable=yes,status=no,scrollbars=yes,width=675,height=485");
75
+ }
76
+
77
+ $(function(){
78
+ carregaItauShopline();
79
+ });
80
+ </script>
81
+ <div id="itau-shopline">
82
+ <h1>Iniciando Itaú Shopline...</h1><br />
83
+ <%= link_to 'Caso o Itau Shopline não inicie automaticamente, clique aqui...', '#', :onclick => 'carregaItauShopline();' %>
84
+ </div>
85
+ ```
86
+
87
+ ### Recebendo retorno dos dados.
88
+
89
+ Após fazer o pagamento, o usuário vai ser direcionado para a URL de retorno informada, juntamente
90
+ com os dados da compra em um parâmetro chamado 'DC'
91
+
92
+ ```ruby
93
+ class BillingController < ApplicationController
94
+
95
+ def notify
96
+ notification = ItauShopline.new.dcript_transaction_notify(params['DC'])
97
+
98
+ # Pesquiso a fatura usando o código do pedido.
99
+ invoice = Invoice.find notification[:num_ped]
100
+
101
+ # Muda o status do pedido de acordo com os dados fornecidos.
102
+ invoice.update_by_itau_shopline
103
+
104
+ # Redireciona para uma página de pós compra.
105
+ redirect_to payment_confirmation_url
106
+ end
107
+ end
108
+
109
+ ```
110
+
111
+ ### Outras informação de retorno.
112
+
113
+ ```ruby
114
+
115
+ ItauShopline.new.dcript_transaction_notify(params['DC'])
116
+ => {"CodEmp"=>"NNNNNNNNNNNNNNN", "Pedido"=>"00010000", "Valor"=>"", "tipPag"=>:undefined, "sitPag"=>:pending, "dtPag"=>"", "codAut"=>"", "numId"=>"", "compVend"=>"", "tipCart"=>""}
117
+
118
+ ```
119
+ ## Pesquisando um pedido.
120
+
121
+ Caso seja necessário pesquisar por uma fatura:
122
+
123
+ ```ruby
124
+ shopline = ItauShopline.new
125
+ shopline.get_invoice_details(1) # Número do pedido.
126
+ => {"CodEmp"=>"NNNNNNNNNNNNNNN", "Pedido"=>"00000001", "Valor"=>"", "tipPag"=>:undefined, "sitPag"=>:pending, "dtPag"=>"", "codAut"=>"", "numId"=>"", "compVend"=>"", "tipCart"=>""}
127
+ end
128
+ ```
129
+
130
+ AUTOR:
131
+ ------
132
+
133
+ Bruno Frank Silva Cordeiro bfscordeiro (at) gmail (dot) com
134
+
135
+ LICENÇA:
136
+ --------
137
+
138
+ (The MIT License)
139
+
140
+ Permission is hereby granted, free of charge, to any person obtaining
141
+ a copy of this software and associated documentation files (the
142
+ 'Software'), to deal in the Software without restriction, including
143
+ without limitation the rights to use, copy, modify, merge, publish,
144
+ distribute, sublicense, and/or sell copies of the Software, and to
145
+ permit persons to whom the Software is furnished to do so, subject to
146
+ the following conditions:
147
+
148
+ The above copyright notice and this permission notice shall be
149
+ included in all copies or substantial portions of the Software.
150
+
151
+ THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND,
152
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
153
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
154
+ IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY
155
+ CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
156
+ TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
157
+ SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,23 @@
1
+ # -*- encoding: utf-8 -*-
2
+ $:.push File.expand_path("../lib", __FILE__)
3
+ require "itau_shopline/version"
4
+
5
+ Gem::Specification.new do |s|
6
+ s.name = "itau_shopline"
7
+ s.version = ItauShopline::VERSION
8
+ s.authors = ["Bruno Frank Cordeiro"]
9
+ s.email = ["bfscordeiro@gmail.com"]
10
+ s.homepage = ""
11
+ s.summary = %q{Integração com Itau Shopline}
12
+ s.description = %q{Gem para integração com Itau Shopline e implementação do itaucripto}
13
+
14
+
15
+ s.files = `git ls-files`.split("\n")
16
+ s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
17
+ s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
18
+ s.require_paths = ["lib"]
19
+
20
+ # specify any dependencies here; for example:
21
+ # s.add_development_dependency "rspec"
22
+ # s.add_runtime_dependency "rest-client"
23
+ end
@@ -0,0 +1,235 @@
1
+ # -*- encoding: utf-8 -*-
2
+
3
+ class ItauCripto
4
+
5
+ attr_reader :codEmp, :tipPag, :numPed
6
+
7
+ CHAVE_ITAU = "SEGUNDA12345ITAU"
8
+ TAM_COD_EMP = 26
9
+ TAM_CHAVE = 16
10
+
11
+ def initialize
12
+ @sbox = []
13
+ @key = []
14
+ @numPed = ''
15
+ @tipPag = ''
16
+ @codEmp = ''
17
+ @dados = nil
18
+ end
19
+
20
+ def gera_dados(param_string1, param_string2, param_string3, param_string4, param_string5, param_string6, param_string7, param_string8, param_string9, param_string10, param_string11, param_string12, param_string13, param_string14, param_string15, param_string16, param_string17, param_string18)
21
+ param_string1 = param_string1.upcase
22
+ param_string5 = param_string5.upcase
23
+
24
+
25
+ return "Erro: tamanho do codigo da empresa diferente de 26 posições." if param_string1.size != TAM_COD_EMP
26
+ return "Erro: tamanho da chave da chave diferente de 16 posições." if param_string5.size != TAM_CHAVE
27
+ return "Erro: número do pedido inválido." if param_string2.size < 1 || param_string2.size > 8
28
+
29
+ if is_numeric(param_string2)
30
+ param_string2 = preenche_zero(param_string2, 8)
31
+ else
32
+ return "Erro: numero do pedido não é numérico."
33
+ end
34
+
35
+ return "Erro: valor da compra inválido." if param_string3.size < 1 || param_string3.size > 11
36
+
37
+ i = param_string3 =~ /,/
38
+ if i
39
+ str3 = param_string3[(i + 1)..-1]
40
+
41
+ return "Erro: valor decimal não é numérico." if !is_numeric(str3)
42
+ return "Erro: valor decimal da compra deve possuir 2 posições após a virgula." if str3.size != 2
43
+
44
+ param_string3 = param_string3[0, (param_string3.size - 3)] + str3
45
+ else
46
+ return "Erro: valor da compra não é numérico." if !is_numeric(param_string3)
47
+ return "Erro: valor da compra deve possuir no máximo 8 posições antes da virgula." if param_string3.size > 8
48
+
49
+ param_string3 = param_string3 + "00"
50
+ end
51
+
52
+ param_string3 = preenche_zero(param_string3, 10)
53
+
54
+ param_string7 = param_string7.strip
55
+
56
+
57
+ return "Erro: código de inscrição inválido." if param_string7 != "02" && param_string7 != "01" && param_string7 != ""
58
+ return "Erro: número de inscrição inválido." if param_string8 != "" && !is_numeric(param_string8) && param_string8.size > 14
59
+ return "Erro: cep inválido." if param_string11 != "" && !is_numeric(param_string11) || param_string11.size != 8
60
+ return "Erro: data de vencimento inválida." if param_string14 != "" && !is_numeric(param_string14) || param_string14.size != 8
61
+ return "Erro: observação adicional 1 inválida." if param_string16.size > 60
62
+ return "Erro: observação adicional 2 inválida." if param_string17.size > 60
63
+ return "Erro: observação adicional 3 inválida." if param_string18.size > 60
64
+
65
+ param_string4 = preenche_branco(param_string4, 40)
66
+ param_string6 = preenche_branco(param_string6, 30)
67
+ param_string7 = preenche_branco(param_string7, 2)
68
+ param_string8 = preenche_branco(param_string8, 14)
69
+ param_string9 = preenche_branco(param_string9, 40)
70
+ param_string10 = preenche_branco(param_string10, 15)
71
+ param_string11 = preenche_branco(param_string11, 8)
72
+ param_string12 = preenche_branco(param_string12, 15)
73
+ param_string13 = preenche_branco(param_string13, 2)
74
+ param_string14 = preenche_branco(param_string14, 8)
75
+ param_string15 = preenche_branco(param_string15, 60)
76
+ param_string16 = preenche_branco(param_string16, 60)
77
+ param_string17 = preenche_branco(param_string17, 60)
78
+ param_string18 = preenche_branco(param_string18, 60)
79
+
80
+ str1 = algoritmo(param_string2 + param_string3 + param_string4 + param_string6 + param_string7 + param_string8 + param_string9 + param_string10 + param_string11 + param_string12 + param_string13 + param_string14 + param_string15 + param_string16 + param_string17 + param_string18, param_string5)
81
+ str2 = algoritmo(param_string1 + str1, CHAVE_ITAU)
82
+
83
+ converte(str2)
84
+ end
85
+
86
+ def gera_cripto(param_string1, param_string2, param_string3)
87
+ return "Erro: tamanho do codigo da empresa diferente de 26 posições." if param_string1.size != TAM_COD_EMP
88
+ return "Erro: tamanho da chave da chave diferente de 16 posições." if param_string3.size != TAM_CHAVE
89
+
90
+ param_string2 = param_string2.strip
91
+
92
+ return "Erro: código do sacado inválido." if param_string2 == ""
93
+
94
+ str1 = algoritmo(param_string2, param_string3)
95
+ str2 = algoritmo(param_string1 + str1, CHAVE_ITAU)
96
+
97
+ converte(str2)
98
+ end
99
+
100
+ def gera_consulta(param_string1, param_string2, param_string3, param_string4)
101
+ return "Erro: tamanho do codigo da empresa diferente de 26 posições." if param_string1.size != TAM_COD_EMP
102
+ return "Erro: tamanho da chave da chave diferente de 16 posições." if param_string4.size != TAM_CHAVE
103
+ return "Erro: número do pedido inválido." if param_string2.size < 1 || param_string2.size > 8
104
+
105
+ if is_numeric(param_string2)
106
+ param_string2 = preenche_zero(param_string2, 8)
107
+ else
108
+ return "Erro: numero do pedido não é numérico."
109
+ end
110
+
111
+ return "Erro: formato inválido." if param_string3 != "0" && param_string3 != "1"
112
+
113
+ str1 = algoritmo(param_string2 + param_string3, param_string4)
114
+
115
+ str2 = algoritmo(param_string1 + str1, CHAVE_ITAU)
116
+
117
+ converte(str2)
118
+ end
119
+
120
+ def decripto(param_string1, param_string2)
121
+ param_string1 = desconverte(param_string1)
122
+
123
+ str = algoritmo(param_string1, param_string2)
124
+
125
+ {:cod_emp => str[0, 26], :num_ped => str[26, 8], :tip_pag => str[34, 2]}
126
+ end
127
+
128
+ def gera_dados_generico(param_string1, param_string2, param_string3)
129
+ param_string1 = param_string1.upcase
130
+ param_string3 = param_string3.upcase
131
+
132
+
133
+ return "Erro: tamanho do codigo da empresa diferente de 26 posições." if param_string1.size != TAM_COD_EMP
134
+ return "Erro: tamanho da chave da chave diferente de 16 posições." if param_string3.size != TAM_CHAVE
135
+ return "Erro: sem dados." if param_string2.size < 1
136
+
137
+ str1 = algoritmo(param_string2, param_string3)
138
+
139
+ str2 = algoritmo(param_string1 + str1, CHAVE_ITAU)
140
+
141
+ converte(str2)
142
+ end
143
+
144
+ #private
145
+
146
+ def algoritmo(param_string1, param_string2)
147
+ k = 0
148
+ m = 0
149
+
150
+ str = ""
151
+ inicializa(param_string2)
152
+ for j in 1..param_string1.size do
153
+ k = (k + 1) % 256
154
+ m = (m + @sbox[k]) % 256
155
+ i = @sbox[k]
156
+ @sbox[k] = @sbox[m]
157
+ @sbox[m] = i
158
+
159
+ n = @sbox[((@sbox[k] + @sbox[m]) % 256)]
160
+
161
+ i1 = param_string1[j - 1].ord ^ n
162
+
163
+ str = str + i1.chr
164
+ end
165
+
166
+ str
167
+ end
168
+
169
+ def preenche_zero(param_string, param_int)
170
+ "%0#{param_int}d" % param_string
171
+ end
172
+
173
+ def is_numeric(param_string)
174
+ param_string =~ /^[0-9]+$/
175
+ end
176
+
177
+ def inicializa(param_string)
178
+ m = param_string.size
179
+ for j in 0..255 do
180
+ @key[j] = param_string[j % m].ord
181
+ @sbox[j] = j
182
+ end
183
+
184
+ k = 0
185
+ for j in 0..255 do
186
+ k = (k + @sbox[j] + @key[j]) % 256
187
+ i = @sbox[j]
188
+ @sbox[j] = @sbox[k]
189
+ @sbox[k] = i
190
+ end
191
+ end
192
+
193
+ def preenche_branco(param_string, param_int)
194
+ "%-#{param_int}s" % param_string
195
+ end
196
+
197
+ def converte(param_string)
198
+ c = (26.00 * rand + 65.00).to_i.chr
199
+ str = c
200
+
201
+ for i in 0..(param_string.size - 1) do
202
+
203
+ k = param_string[i].ord
204
+ j = k
205
+
206
+ str = str + j.to_s
207
+ c = (26.00 * rand + 65.00).to_i.chr
208
+ str = str + c
209
+ end
210
+
211
+ str
212
+ end
213
+
214
+ def desconverte(param_string)
215
+ str1 = ""
216
+ i = 0
217
+ while i < param_string.size do
218
+ str2 = ""
219
+
220
+ c = param_string[i]
221
+ puts c
222
+ while c =~ /[0-9]/ do
223
+ str2 = str2 + param_string[i]
224
+ i += 1
225
+ c = param_string[i]
226
+ end
227
+
228
+ str1 += str2.to_i.chr if str2 != ""
229
+
230
+ i += 1
231
+ end
232
+
233
+ str1
234
+ end
235
+ end
@@ -0,0 +1,112 @@
1
+ # -*- encoding: UTF-8 -*-
2
+
3
+ require "net/https"
4
+ require "uri"
5
+ require "rexml/document"
6
+ require "open-uri"
7
+
8
+ class ItauShopline
9
+ include ActionView::Helpers::NumberHelper
10
+
11
+ STATUS_TRANSLATE = {
12
+ :pending => 'Pendente',
13
+ :paid => 'Pago'
14
+ }
15
+
16
+ PAYMENT_TRANSLATE = {
17
+ :undefined => 'Não definido',
18
+ :online_transfer => 'Transferência',
19
+ :invoice => 'Boleto',
20
+ :credit_card => 'Cartão de crédito'
21
+ }
22
+
23
+ STATUS = {
24
+ :undefined => {
25
+ '01' => :pending,
26
+ '02' => :pending,
27
+ '03' => :pending
28
+ },
29
+ :online_transfer => {
30
+ '00' => :paid,
31
+ '01' => :pending,
32
+ '02' => :pending,
33
+ '03' => :pending
34
+ },
35
+ :invoice => {
36
+ '00' => :paid,
37
+ '01' => :pending,
38
+ '02' => :pending,
39
+ '03' => :pending,
40
+ '04' => :pending,
41
+ '05' => :paid,
42
+ '06' => :pending
43
+ },
44
+ :credit_card => {
45
+ '00' => :paid,
46
+ '01' => :pending,
47
+ '02' => :pending,
48
+ '03' => :pending
49
+ }
50
+ }
51
+
52
+ PAYMENT_METHODS = {
53
+ '00' => :undefined,
54
+ '01' => :online_transfer,
55
+ '02' => :invoice,
56
+ '03' => :credit_card
57
+ }
58
+
59
+ def gera_dados(invoice_id, total_price, full_name, city_name, state_uf, due_date=nil, return_url="")
60
+ cripto = ItauCripto.new
61
+ due_date ||= (Date.today + 3.days).strftime('%d%m%Y')
62
+ cripto.gera_dados(config['codigo_empresa'], invoice_id.to_s, number_to_currency(total_price, :unit => "", :separator => ",", :delimiter => ""), "", config['chave'], full_name, '01', '00000000000', '', '', "00000000", city_name[0,15], state_uf[0,2], due_date, return_url[0,60], '', '', '')
63
+ end
64
+
65
+ def dcript_transaction_notify(dados)
66
+ cripto = ItauCripto.new
67
+ cripto.decripto(dados, config['chave'])
68
+ end
69
+
70
+ def get_invoice_details(invoice_id)
71
+ cripto = ItauCripto.new
72
+ token = cripto.gera_consulta(config['codigo_empresa'], invoice_id.to_s, '1',config['chave'])
73
+ xml = open("https://shopline.itau.com.br/shopline/consulta.aspx?DC=#{token}").read
74
+
75
+ treat_itau_data xml
76
+ end
77
+
78
+ private
79
+ def treat_itau_data(dados)
80
+ treated_data = {}
81
+
82
+ response = REXML::Document.new(dados)
83
+ params = response.elements['consulta'].elements['PARAMETER']
84
+ params.elements.each do |param|
85
+ treated_data[param.attributes['ID']] = param.attributes['VALUE']
86
+ end
87
+
88
+ new_result = {}
89
+ treated_data.each do |field, value|
90
+ value = PAYMENT_METHODS[value] if field == 'tipPag'
91
+ value = STATUS[PAYMENT_METHODS[treated_data['tipPag']]][value] if field == 'sitPag'
92
+
93
+ new_result[field] = value
94
+ end
95
+
96
+ new_result
97
+ end
98
+
99
+ def config
100
+ # load file if is not loaded yet
101
+ @@config ||= YAML.load_file(Rails.root.join("config/itau_shopline.yml"))
102
+
103
+ # raise an exception if the environment hasn't been set
104
+ # or if file is empty
105
+ if @@config == false || !@@config[Rails.env]
106
+ raise MissingEnvironmentError, ":#{Rails.env} environment not set on #{config_file.inspect}"
107
+ end
108
+
109
+ # retrieve the environment settings
110
+ @@config[Rails.env]
111
+ end # Load configuration file.
112
+ end
@@ -0,0 +1,3 @@
1
+ module ItauShopline
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,7 @@
1
+ require "itau_shopline/version"
2
+ require "itau_shopline/itau_cripto"
3
+ require "itau_shopline/itau_shopline"
4
+
5
+ module ItauShopline
6
+ # Your code goes here...
7
+ end
metadata ADDED
@@ -0,0 +1,64 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: itau_shopline
3
+ version: !ruby/object:Gem::Version
4
+ prerelease:
5
+ version: 0.0.1
6
+ platform: ruby
7
+ authors:
8
+ - Bruno Frank Cordeiro
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+
13
+ date: 2012-01-18 00:00:00 -02:00
14
+ default_executable:
15
+ dependencies: []
16
+
17
+ description: "Gem para integra\xC3\xA7\xC3\xA3o com Itau Shopline e implementa\xC3\xA7\xC3\xA3o do itaucripto"
18
+ email:
19
+ - bfscordeiro@gmail.com
20
+ executables: []
21
+
22
+ extensions: []
23
+
24
+ extra_rdoc_files: []
25
+
26
+ files:
27
+ - Gemfile
28
+ - README.markdown
29
+ - Rakefile
30
+ - itau_shopline.gemspec
31
+ - lib/itau_shopline.rb
32
+ - lib/itau_shopline/itau_cripto.rb
33
+ - lib/itau_shopline/itau_shopline.rb
34
+ - lib/itau_shopline/version.rb
35
+ has_rdoc: true
36
+ homepage: ""
37
+ licenses: []
38
+
39
+ post_install_message:
40
+ rdoc_options: []
41
+
42
+ require_paths:
43
+ - lib
44
+ required_ruby_version: !ruby/object:Gem::Requirement
45
+ none: false
46
+ requirements:
47
+ - - ">="
48
+ - !ruby/object:Gem::Version
49
+ version: "0"
50
+ required_rubygems_version: !ruby/object:Gem::Requirement
51
+ none: false
52
+ requirements:
53
+ - - ">="
54
+ - !ruby/object:Gem::Version
55
+ version: "0"
56
+ requirements: []
57
+
58
+ rubyforge_project:
59
+ rubygems_version: 1.6.2
60
+ signing_key:
61
+ specification_version: 3
62
+ summary: "Integra\xC3\xA7\xC3\xA3o com Itau Shopline"
63
+ test_files: []
64
+