solidus_inter 2.0.0 → 3.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f26d5961715115c68b93397ab535656b57c57d69005409c9a62a21f67ee735ff
4
- data.tar.gz: a781c0a4133a46af79b2a92c930f3fd5165bbe582efa61698d93c6449a81e5cc
3
+ metadata.gz: 68978582e05c3a6391cc622865892b77ca8b28e2d98388213bf6d58a489ef446
4
+ data.tar.gz: 7759c7ac13c686895ed6a8848f33875d2bac0fd4f0c1045fafa3ae487ab5b807
5
5
  SHA512:
6
- metadata.gz: 68ded17e92681541c0feadd0149edd8daa29594184c404af8c5c043d751101043721ba93816b420d329106b5930cc5b5f5d6a384698226923bf924d54b7304b4
7
- data.tar.gz: 28a3d4bf964f59cc37a2cadcbac9ca9913823dc4c2944f1aaa31901986db7b58eb8bba321fbefbb0a30615191df81572111c8ec76c97fb04ed1c700ec3465813
6
+ metadata.gz: 3508d43d52924bbdde45b088cd81c44dd65d8c7697535f1bb8c8e39a6a98f98e97c8470eb6ed81d1a34d1c715b79e93134df2940ad9950f7546f8764586ca593
7
+ data.tar.gz: d6851bf2cd670b75074668824d9bdba9ae9522b073fb58abe51496864f7816d9cfcd671f1b7a8a59fe4135df1fc3c62b7bc5846f582fb2fbc7eb0ab5569b10b9
data/README.md CHANGED
@@ -1,16 +1,152 @@
1
- # Solidus Inter
2
- <!-- Explain what your extension does. -->
1
+ # SolidusInter
3
2
 
4
- ## Installation
3
+ Integração do Banco Inter com o Solidus para pagamentos via PIX.
5
4
 
6
- Add solidus_inter to your Gemfile:
5
+ ## Sobre
6
+
7
+ SolidusInter é uma extensão para o Solidus que permite integração com a API do Banco Inter para processamento de pagamentos via PIX. A gem oferece uma solução completa para gerar códigos PIX, QR codes e processar pagamentos em tempo real.
8
+
9
+ ## Funcionalidades
10
+
11
+ - ✅ Geração automática de códigos PIX
12
+ - ✅ Criação de QR codes para pagamento
13
+ - ✅ Validação de pagamentos em tempo real
14
+ - ✅ Integração completa com o admin do Solidus
15
+ - ✅ Suporte para diferentes chaves PIX
16
+ - ✅ Interface responsiva para mobile e desktop
17
+ - ✅ Webhooks para confirmação automática de pagamentos
18
+
19
+ ## Instalação
20
+
21
+ Adicione esta linha ao Gemfile da sua aplicação:
7
22
 
8
23
  ```ruby
9
24
  gem 'solidus_inter'
10
25
  ```
11
26
 
12
- Bundle your dependencies and run the installation generator:
27
+ Execute:
13
28
 
14
- ```shell
15
- bin/rails generate solidus_inter:install
29
+ ```bash
30
+ $ bundle install
16
31
  ```
32
+
33
+ Execute o gerador de instalação:
34
+
35
+ ```bash
36
+ $ rails generate solidus_inter:install
37
+ ```
38
+
39
+ Isso irá:
40
+ - Copiar as migrações necessárias
41
+ - Executar as migrações (se você confirmar)
42
+
43
+ ## Configuração
44
+
45
+ ### 1. Credenciais do Banco Inter
46
+
47
+ Você precisará das seguintes credenciais do Banco Inter:
48
+
49
+ - **Client ID**: ID do cliente fornecido pelo Banco Inter
50
+ - **Client Secret**: Chave secreta fornecida pelo Banco Inter
51
+ - **Chave PIX**: Sua chave PIX cadastrada no Banco Inter
52
+ - **Conta Corrente**: Número da conta corrente
53
+ - **Certificados**: Certificado (.crt) e chave privada (.key) fornecidos pelo Banco Inter
54
+
55
+ ### 2. Configuração do Payment Method
56
+
57
+ No admin do Solidus, vá para:
58
+
59
+ 1. `Settings` → `Payment Methods`
60
+ 2. Clique em `New Payment Method`
61
+ 3. Selecione `SolidusInter::InterPix` como tipo
62
+ 4. Configure as preferências:
63
+ - **Client ID**: Seu client ID do Banco Inter
64
+ - **Client Secret**: Seu client secret do Banco Inter
65
+ - **Chave PIX**: Sua chave PIX
66
+ - **Conta Corrente**: Número da sua conta corrente
67
+ - **CRT**: Conteúdo do certificado (.crt)
68
+ - **Key**: Conteúdo da chave privada (.key)
69
+
70
+ ## Uso
71
+
72
+ ### Fluxo de Pagamento
73
+
74
+ 1. **Checkout**: O cliente seleciona PIX como método de pagamento
75
+ 2. **Geração**: A gem gera automaticamente o código PIX e QR code
76
+ 3. **Pagamento**: O cliente usa o app do banco para pagar
77
+ 4. **Confirmação**: O pagamento é confirmado via webhook ou polling
78
+ 5. **Finalização**: O pedido é processado automaticamente
79
+
80
+ ### Códigos de Estado
81
+
82
+ A gem controla automaticamente os seguintes estados de pagamento:
83
+
84
+ - `pending`: Pagamento aguardando confirmação
85
+ - `paid`: Pagamento confirmado
86
+ - `expired`: Pagamento expirado
87
+ - `cancelled`: Pagamento cancelado
88
+
89
+ ## API
90
+
91
+ ### Principais Classes
92
+
93
+ #### `SolidusInter::InterPix`
94
+
95
+ Classe principal do método de pagamento que herda de `Spree::PaymentMethod`:
96
+
97
+ ```ruby
98
+ payment_method = SolidusInter::InterPix.new
99
+ payment_method.create_payment(order) # Cria pagamento PIX
100
+ payment_method.find_payment(txid) # Busca pagamento por txid
101
+ ```
102
+
103
+ #### `SolidusInter::PixPaymentSource`
104
+
105
+ Representa a fonte de pagamento PIX:
106
+
107
+ ```ruby
108
+ source = SolidusInter::PixPaymentSource.new
109
+ source.paid? # Verifica se foi pago
110
+ source.expired? # Verifica se expirou
111
+ source.active? # Verifica se está ativo
112
+ ```
113
+
114
+ #### `SolidusInter::InterClient`
115
+
116
+ Cliente para comunicação com a API do Banco Inter:
117
+
118
+ ```ruby
119
+ client = SolidusInter::InterClient.new(payment_method)
120
+ client.get_payment(txid) # Busca pagamento
121
+ client.create_payment(data) # Cria novo pagamento
122
+ ```
123
+
124
+ ## Personalização
125
+
126
+ ### Views
127
+
128
+ As views podem ser customizadas copiando-as para sua aplicação:
129
+
130
+ ```
131
+ app/views/spree/admin/payments/source_forms/_inter_pix.html.erb
132
+ app/views/spree/admin/payments/source_views/_inter_pix.html.erb
133
+ app/views/spree/api/payments/source_views/_inter_pix.jbuilder
134
+ ```
135
+
136
+ ## Desenvolvimento
137
+
138
+ ### Configuração do Ambiente
139
+
140
+ ```bash
141
+ git clone https://github.com/todasessascoisas/solidus_inter.git
142
+ cd solidus_inter
143
+ bundle install
144
+ ```
145
+
146
+ ## Dependências
147
+
148
+ - **Ruby**: >= 2.5, < 4
149
+ - **Solidus**: Compatível com versões atuais
150
+ - **[inter_api](https://github.com/todasessascoisas/inter_api)**: API client para o Banco Inter
151
+ - **[solidus_brazilian_adaptations](https://github.com/todasessascoisas/solidus_brazilian_adaptations)**: Adaptações brasileiras para Solidus
152
+ - **rqrcode**: Geração de QR codes
@@ -3,14 +3,6 @@ module SolidusInter
3
3
  def initialize(options)
4
4
  end
5
5
 
6
- def purchase(_money, source, _options = {})
7
- inter_payment = source.retrieve_from_api
8
- if inter_payment.paid?
9
- source.update(status: "approved", paid_amount: inter_payment.valor_pago, e2e_id: inter_payment.end_to_end_id)
10
- successful_response("Pagamento realizado", inter_payment.txid)
11
- end
12
- end
13
-
14
6
  def void(transaction_id, _options = {})
15
7
  # Respondendo sempre com successful_response para funcionar o botão de "Cancelar" do pedido. Reembolso deve ser feito por fora.
16
8
  successful_response("Pagamento cancelado. Se necessário, realize o reembolso.", transaction_id)
@@ -0,0 +1,56 @@
1
+ module SolidusInter
2
+ class InterClient
3
+ def self.for_payment_method(payment_method)
4
+ client_class = payment_method.preferred_test_mode ? InterClientSandbox : InterClientProduction
5
+ client_class.new(payment_method)
6
+ end
7
+ end
8
+
9
+ module PaymentMethodSyncable
10
+ attr_accessor :payment_method
11
+
12
+ def initialize(payment_method)
13
+ @payment_method = payment_method
14
+ super(
15
+ client_id: payment_method.preferred_client_id,
16
+ client_secret: payment_method.preferred_client_secret,
17
+ chave_pix: payment_method.preferred_chave_pix,
18
+ conta_corrente: payment_method.preferred_conta_corrente,
19
+ crt: temp_file(payment_method.preferred_crt).path,
20
+ key: temp_file(payment_method.preferred_key).path,
21
+ access_token: payment_method.preferred_access_token,
22
+ token_expires_at: payment_method.preferred_token_expires_at.to_datetime,
23
+ test_mode: payment_method.preferred_test_mode
24
+ )
25
+ end
26
+
27
+ def refresh_token
28
+ super
29
+ sync_payment_method_tokens
30
+ end
31
+
32
+ private
33
+
34
+ def sync_payment_method_tokens
35
+ payment_method.update!(
36
+ preferred_access_token: access_token,
37
+ preferred_token_expires_at: token_expires_at
38
+ )
39
+ end
40
+
41
+ def temp_file(content)
42
+ t = Tempfile.new
43
+ t << content
44
+ t.close
45
+ t
46
+ end
47
+ end
48
+
49
+ class InterClientSandbox < ::InterApi::ClientSandbox
50
+ include PaymentMethodSyncable
51
+ end
52
+
53
+ class InterClientProduction < ::InterApi::ClientProduction
54
+ include PaymentMethodSyncable
55
+ end
56
+ end
@@ -6,6 +6,8 @@ module SolidusInter
6
6
  preference :conta_corrente, :string
7
7
  preference :crt, :text
8
8
  preference :key, :text
9
+ preference :access_token, :string
10
+ preference :token_expires_at, :string
9
11
 
10
12
  def payment_source_class
11
13
  PixPaymentSource
@@ -28,13 +30,13 @@ module SolidusInter
28
30
  end
29
31
 
30
32
  def find_payment(txid)
31
- client = set_api_client
32
- client.get_payment(txid)
33
+ inter_client.get_payment(txid)
33
34
  end
34
35
 
35
36
  def create_payment(order)
36
37
  existing_payment = find_existing_payment(order)
37
38
  return existing_payment if payment_is_usable?(existing_payment, order)
39
+ invalidate_valid_payments(order, existing_payment&.id)
38
40
 
39
41
  payment = order.payments.new(amount: order.total, payment_method: self)
40
42
  payment.source = init_source(order)
@@ -45,38 +47,61 @@ module SolidusInter
45
47
  payment
46
48
  end
47
49
 
48
- def invalidate_payment(payment_source)
49
- return false unless payment_source&.txid
50
+ def invalidate_payment(payment)
51
+ txid = payment.source&.txid
52
+ return false if txid.nil?
53
+ inter_payment = find_payment(payment.source.txid)
54
+ sync(payment, inter_payment)
55
+ return false unless payment.checkout?
50
56
 
51
- inter_payment = find_payment(payment_source.txid)
52
- return false if inter_payment.paid?
53
-
54
- inter_payment.invalidate!
55
- payment_source.payments[0].log_entries.create!(parsed_payment_response_details_with_fallback: failure_response("Pagamento cancelado"))
56
- true
57
+ inter_payment = inter_client.update_payment(payment_id: txid, status: "REMOVIDA_PELO_USUARIO_RECEBEDOR")
58
+ sync(payment, inter_payment)
57
59
  end
58
60
 
59
- def pay_test_payment(payment_source)
60
- return false unless payment_source&.txid
61
+ def pay_test_payment(payment, amount: nil)
62
+ return false unless inter_client.class == InterClientSandbox
63
+
64
+ amount ||= payment.amount
65
+ txid = payment.source&.txid
66
+ return false if txid.nil?
67
+ inter_payment = find_payment(payment.source.txid)
68
+ sync(payment, inter_payment)
69
+ return false unless payment.checkout?
61
70
 
62
- inter_payment = find_payment(payment_source.txid)
63
- return false if inter_payment.paid?
71
+ inter_client.pay_pix(payment_id: payment.source.txid, amount: amount)
72
+ sync(payment, inter_payment)
73
+ end
64
74
 
65
- client = set_api_client
66
- client.pay_pix(inter_payment.txid, inter_payment.valor_original)
75
+ def purchase(money, source, _options = {})
76
+ inter_payment = source.retrieve_from_api
77
+ sync(source.payments.sole, inter_payment)
78
+ if inter_payment.status == "CONCLUIDA"
79
+ successful_response("Pagamento realizado", status: source.status, internal_detail: "")
80
+ else
81
+ failure_response("Pagamento não realizado", status: source.status, internal_detail: "")
82
+ end
67
83
  end
68
84
 
69
- def purchase(money, source, options = {})
70
- gateway.purchase(money, source, options)
85
+ # Não da pra chamar o sync aqui pq ele da payment.complete e isso faz nao chamar o purchase
86
+ def pix_paid? payment
87
+ return false unless payment.source&.txid
88
+ inter_payment = find_payment(payment.source.txid)
89
+ paid_amount = total_paid(inter_payment)
90
+ inter_payment.status == "CONCLUIDA" && paid_amount >= payment.amount
71
91
  end
72
92
 
73
- def should_skip_processing?(source)
74
- inter_payment = find_payment(source.txid)
75
- !inter_payment.paid?
93
+ def payment_valid? payment
94
+ return false unless payment.source&.txid
95
+ purchase(payment)
96
+ payment.checkout?
76
97
  end
77
98
 
78
99
  private
79
100
 
101
+ def inter_client
102
+ InterClient.for_payment_method(self)
103
+ end
104
+
80
105
  def init_source(order)
81
106
  PixPaymentSource.new(
82
107
  amount: order.total,
@@ -87,8 +112,7 @@ module SolidusInter
87
112
  end
88
113
 
89
114
  def create_inter_payment(payment_source)
90
- client = set_api_client
91
- client.create_payment(
115
+ inter_client.create_payment(
92
116
  amount: payment_source.amount,
93
117
  payer_tax_id: payment_source.payer_tax_id,
94
118
  payer_name: payment_source.payer_name,
@@ -110,66 +134,93 @@ module SolidusInter
110
134
  end
111
135
 
112
136
  def process_payment_response(payment, inter_payment)
113
- payment.update(response_code: inter_payment.txid)
114
137
  payment.source.update(
115
138
  txid: inter_payment.txid,
116
- pix_code: inter_payment.copia_e_cola,
117
- qr_code_svg: inter_payment.qr_code,
139
+ pix_code: inter_payment.pixCopiaECola,
140
+ qr_code_svg: qr_code(inter_payment),
118
141
  status: inter_payment.status,
119
- expiration: inter_payment.expiracao
142
+ expiration: expiration_date(inter_payment)
120
143
  )
144
+ payment.update(response_code: inter_payment.txid)
145
+ sync(payment, inter_payment)
146
+ end
147
+
148
+ def sync(payment, inter_payment)
149
+ paid_amount = total_paid(inter_payment)
150
+ payment.source.update!(status: inter_payment.status, paid_amount: paid_amount, e2e_id: end_to_end(inter_payment))
151
+ if inter_payment.status == "CONCLUIDA" && paid_amount >= payment.amount
152
+ approve_payment(payment)
153
+ elsif inter_payment.status == "CONCLUIDA"
154
+ raise "Payment paid with value less than the created - #{inter_payment.txid}"
155
+ elsif inter_payment.status != "ATIVA" || payment.source.expired?
156
+ invalid_payment(payment)
157
+ end
158
+ end
121
159
 
122
- update_payment_status(payment, inter_payment)
160
+ def approve_payment(payment)
161
+ payment.complete
162
+ payment_source = payment.source
163
+ payment_source.update! status: payment.state
164
+ response = successful_response("Pagamento realizado", status: payment_source.status, internal_detail: "")
165
+ payment.log_entries.create(parsed_payment_response_details_with_fallback: response)
123
166
  end
124
167
 
125
- def update_payment_status(payment, inter_payment)
126
- status = case inter_payment.status
127
- when "ATIVA" then "pending"
168
+ def invalid_payment payment
169
+ return false unless payment.checkout?
170
+ if payment.checkout?
171
+ payment.invalidate!
172
+ else
173
+ payment.failure!
128
174
  end
129
- payment.source.update(status: status)
130
- end
131
-
132
- def set_api_client
133
- account = SolidusInter::Account.find_by(chave_pix: preferences[:chave_pix])
134
- client = ::InterApi::Client.new(
135
- client_id: preferences[:client_id],
136
- client_secret: preferences[:client_secret],
137
- chave_pix: preferences[:chave_pix],
138
- conta_corrente: preferences[:conta_corrente],
139
- crt: temp_file(preferences[:crt]).path,
140
- key: temp_file(preferences[:key]).path,
141
- access_token: account&.token,
142
- token_expires_at: account&.expires_at,
143
- test_mode: preferences[:test_mode]
144
- )
145
- SolidusInter::Account.upsert(
146
- {token: client.access_token, expires_at: client.token_expires_at, chave_pix: client.chave_pix,
147
- spree_payment_method_id: id}, unique_by: :chave_pix
148
- )
149
- client
175
+
176
+ payment_source = payment.source
177
+ payment_source.update!(status: payment.state)
178
+ response = failure_response("Pagamento invalidado", status: payment_source.status, internal_detail: "")
179
+ payment.log_entries.create(parsed_payment_response_details_with_fallback: response)
150
180
  end
151
181
 
152
- def temp_file(content)
153
- t = Tempfile.new
154
- t << content
155
- t.close
156
- t
182
+ def invalidate_valid_payments order, existing_payment_id
183
+ available_payments = order.payments.where(state: ["checkout", "pending"])
184
+ available_payments.where.not(id: existing_payment_id).each do |payment|
185
+ payment.source.void!
186
+ end
157
187
  end
158
188
 
159
- def successful_response(message, transaction_id)
189
+ def successful_response message, status:, internal_detail:
190
+ full_message = message + " - " + status + ": " + internal_detail
160
191
  ActiveMerchant::Billing::Response.new(
161
192
  true,
162
- message,
163
- {},
164
- authorization: transaction_id
193
+ full_message
165
194
  )
166
195
  end
167
196
 
168
- def failure_response(message)
197
+ def failure_response message, status:, internal_detail:
198
+ full_message = message + " - " + status + ": " + internal_detail
169
199
  ActiveMerchant::Billing::Response.new(
170
200
  false,
171
- message
201
+ full_message
172
202
  )
173
203
  end
204
+
205
+ def qr_code(inter_payment, module_size: 4)
206
+ return unless inter_payment.pixCopiaECola
207
+ qr = RQRCode::QRCode.new(inter_payment.pixCopiaECola, size: 10, level: :l)
208
+ qr.as_svg(module_size: module_size)
209
+ end
210
+
211
+ def expiration_date(inter_payment)
212
+ return unless inter_payment.calendario.criacao
213
+ Time.new(inter_payment.calendario.criacao) + inter_payment.calendario.expiracao
214
+ end
215
+
216
+ def total_paid(inter_payment)
217
+ return unless inter_payment["pix"] && inter_payment.pix.any?
218
+ Float(inter_payment.pix[0].valor)
219
+ end
220
+
221
+ def end_to_end(inter_payment)
222
+ return unless inter_payment["pix"] && inter_payment.pix.any?
223
+ inter_payment.pix[0].endToEndId
224
+ end
174
225
  end
175
226
  end
@@ -1,27 +1,11 @@
1
1
  module SolidusInter
2
2
  class PixPaymentSource < Spree::PaymentSource
3
- def actions
4
- %w[]
5
- end
6
-
7
- def can_capture?(payment)
8
- payment.pending? || payment.checkout?
9
- end
10
-
11
- def can_void?(payment)
12
- payment.can_void?
13
- end
14
-
15
- def can_credit?(payment)
16
- payment.completed? && payment.credit_allowed > 0
17
- end
18
-
19
3
  def expired?
20
- expiration.past?
4
+ expiration.nil? || expiration.past?
21
5
  end
22
6
 
23
7
  def active?
24
- expiration.future?
8
+ expiration.present? && expiration.future?
25
9
  end
26
10
 
27
11
  def retrieve_from_api
@@ -29,12 +13,11 @@ module SolidusInter
29
13
  end
30
14
 
31
15
  def paid?
32
- inter_payment = retrieve_from_api
33
- inter_payment.paid?
16
+ payment_method.pix_paid?(payments.sole)
34
17
  end
35
18
 
36
- def invalidate
37
- payment_method.invalidate_payment(self)
19
+ def void!
20
+ payment_method.invalidate_payment(payments.sole)
38
21
  end
39
22
  end
40
23
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidusInter
4
- VERSION = "2.0.0"
4
+ VERSION = "3.0.1"
5
5
  end
data/lib/solidus_inter.rb CHANGED
@@ -3,6 +3,7 @@
3
3
  require "solidus_inter/version"
4
4
  require "solidus_inter/engine"
5
5
  require "inter_api"
6
+ require "rqrcode"
6
7
 
7
8
  module SolidusInter
8
9
  class Error < StandardError; end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidus_inter
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.0.0
4
+ version: 3.0.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - ulysses
@@ -37,15 +37,31 @@ dependencies:
37
37
  - - ">="
38
38
  - !ruby/object:Gem::Version
39
39
  version: '0'
40
- description: ''
40
+ - !ruby/object:Gem::Dependency
41
+ name: rqrcode
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :runtime
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ description: Extensão para o Solidus que permite integração completa com a API do
55
+ Banco Inter para processamento de pagamentos via PIX, incluindo geração de QR codes
56
+ e validação automática de pagamentos.
41
57
  email: ulyssesh.20@gmail.com
42
58
  executables: []
43
59
  extensions: []
44
60
  extra_rdoc_files: []
45
61
  files:
46
62
  - README.md
47
- - app/models/solidus_inter/account.rb
48
63
  - app/models/solidus_inter/gateway.rb
64
+ - app/models/solidus_inter/inter_client.rb
49
65
  - app/models/solidus_inter/inter_pix.rb
50
66
  - app/models/solidus_inter/pix_payment_source.rb
51
67
  - app/views/spree/admin/payments/source_forms/_inter_pix.html.erb
@@ -81,5 +97,5 @@ required_rubygems_version: !ruby/object:Gem::Requirement
81
97
  requirements: []
82
98
  rubygems_version: 3.6.9
83
99
  specification_version: 4
84
- summary: ''
100
+ summary: Integração do Banco Inter com Solidus para pagamentos via PIX
85
101
  test_files: []
@@ -1,5 +0,0 @@
1
- module SolidusInter
2
- class Account < ApplicationRecord
3
- belongs_to :spree_payment_method, class_name: "Spree::PaymentMethod"
4
- end
5
- end