solidus_sicoob 0.0.2 → 2.0.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 3485ae76c32819dba0b23a83ec37015966711204fbd778d1087065a3911f428e
4
- data.tar.gz: a02fe345338d32472553b30b584e4ff7b518084b372867544d09f03d6a51aac1
3
+ metadata.gz: c7a4f9670199d754eaa022b29ea83a32fb129dfa21f2ed1f73e3f76801012154
4
+ data.tar.gz: 0aa13e2ac755ad8057db230b794a7d00a91b5d1d0b0a4e2788e97d13f4615aa5
5
5
  SHA512:
6
- metadata.gz: 54370e45afa0180e80de232d4f4d2d80e993b08af90f18b1ad000fa4d3335ccdba317ff2c84b43d21dd574c6338b2e80bdaba42d5043ac78e510e8045b7c3fcb
7
- data.tar.gz: 333743a5c8372761112f2917db9021237ae14b4d652c56eb8a3d184dd560ee88f8620b137466cc2e8b4b8a3f8462782a93b03832d4b5393cbea08e0db3ccff94
6
+ metadata.gz: c59f36b5cc30bc52b1bf7e1af12030b81a838b17a02df27a88c11bce71d84e54a82910465fec933d57c11d903fc8bfb4798f954137518a26bf931c6c8a5fdce1
7
+ data.tar.gz: 43c49c1ee24e96c535007c66bcd5f71108e5755cfd3a0c361824f45ec7f43a58e9f5fa9264e059b487410fb6addf1b27fbd44e2c6227459ddd730fe49e091169
@@ -3,23 +3,33 @@ module SolidusSicoob
3
3
  def initialize(options)
4
4
  end
5
5
 
6
- def purchase(_money, source, _options = {})
7
- sicoob_payment = source.retrieve_from_api
8
- if sicoob_payment.paid?
9
- source.update(status: "approved", paid_amount: sicoob_payment.valor_pago, e2e_id: sicoob_payment.end_to_end_id)
10
- successful_response("Pagamento realizado", sicoob_payment.txid)
11
- else
12
- failure_response(sicoob_payment.internal_error || "Ocorreu um erro no pagamento.")
13
- end
6
+ def void(transaction_id, options = {})
7
+ # Respondendo com falha pois atualmente todo pedido concluido ja teve seu pagamento processado
8
+ # e nesse caso o credit é mais adequado (Ao cancelar um pedido pela interface, o 'void' é chamado, se ele retornar falha o Solidus chama o 'credit')
9
+ failure_response("Pagamento processado")
14
10
  end
15
11
 
16
- def void(transaction_id, _options = {})
17
- # Respondendo sempre com successful_response para funcionar o botão de "Cancelar" do pedido. Reembolso deve ser feito por fora.
18
- successful_response("Pagamento cancelado. Se necessário, realize o reembolso.", transaction_id)
12
+ def credit(money, transaction_id, options = {})
13
+ payment = Spree::Payment.find_by(response_code: transaction_id)
14
+ payment_source = payment.source
15
+ refund = payment_source.refund!(amount: converted_money(money))
16
+ if refund.status == "DEVOLVIDO"
17
+ response = successful_response("Pagamento reembolsado (#{refund.status})", transaction_id)
18
+ payment.log_entries.create(parsed_payment_response_details_with_fallback: response)
19
+ successful_response("Reembolso realizado", refund.id)
20
+ else
21
+ response = successful_response("Pagamento não reembolsado - #{refund.status} - #{refund.try(:motivo)}", transaction_id)
22
+ payment.log_entries.create(parsed_payment_response_details_with_fallback: response)
23
+ successful_response("Erro ao reembolsar", refund.id)
24
+ end
19
25
  end
20
26
 
21
27
  private
22
28
 
29
+ def converted_money(money)
30
+ money / 100.0
31
+ end
32
+
23
33
  def successful_response(message, transaction_id)
24
34
  ActiveMerchant::Billing::Response.new(
25
35
  true,
@@ -29,10 +39,12 @@ module SolidusSicoob
29
39
  )
30
40
  end
31
41
 
32
- def failure_response(message)
42
+ def failure_response(message, transaction_id = nil)
33
43
  ActiveMerchant::Billing::Response.new(
34
44
  false,
35
- message
45
+ message,
46
+ {},
47
+ authorization: transaction_id
36
48
  )
37
49
  end
38
50
  end
@@ -1,21 +1,5 @@
1
1
  module SolidusSicoob
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
4
  expiration.nil? || expiration.past?
21
5
  end
@@ -29,12 +13,24 @@ module SolidusSicoob
29
13
  end
30
14
 
31
15
  def paid?
32
- sicoob_payment = retrieve_from_api
33
- sicoob_payment.paid?
16
+ payment_method.pix_paid?(payments.sole)
17
+ end
18
+
19
+ def void!
20
+ payment_method.invalidate_payment(payments.sole)
21
+ end
22
+
23
+ def refund!(amount: nil)
24
+ payment_method.refund_payment(payments.sole, amount: amount)
34
25
  end
35
26
 
36
- def invalidate
37
- payment_method.invalidate_payment(self)
27
+ # Definir para sempre false pois atualmente não implementados o void do Solidus
28
+ def can_void?(payment)
29
+ false
30
+ end
31
+
32
+ def can_credit?(payment)
33
+ payment.completed? && payment.credit_allowed > 0
38
34
  end
39
35
  end
40
36
  end
@@ -0,0 +1,54 @@
1
+ module SolidusSicoob
2
+ class SicoobClient
3
+ def self.for_payment_method(payment_method)
4
+ client_class = payment_method.preferred_test_mode ? SicoobClientSandbox : SicoobClientProduction
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
+ chave_pix: payment_method.preferred_chave_pix,
17
+ crt: temp_file(payment_method.preferred_crt).path,
18
+ key: temp_file(payment_method.preferred_key).path,
19
+ access_token: payment_method.preferred_access_token,
20
+ token_expires_at: payment_method.preferred_token_expires_at.to_datetime,
21
+ test_mode: payment_method.preferred_test_mode
22
+ )
23
+ end
24
+
25
+ def refresh_token
26
+ super
27
+ sync_payment_method_tokens
28
+ end
29
+
30
+ private
31
+
32
+ def sync_payment_method_tokens
33
+ payment_method.update!(
34
+ preferred_access_token: access_token,
35
+ preferred_token_expires_at: token_expires_at
36
+ )
37
+ end
38
+
39
+ def temp_file(content)
40
+ t = Tempfile.new
41
+ t << content
42
+ t.close
43
+ t
44
+ end
45
+ end
46
+
47
+ class SicoobClientSandbox < ::SicoobApi::ClientSandbox
48
+ include PaymentMethodSyncable
49
+ end
50
+
51
+ class SicoobClientProduction < ::SicoobApi::ClientProduction
52
+ include PaymentMethodSyncable
53
+ end
54
+ end
@@ -4,6 +4,8 @@ module SolidusSicoob
4
4
  preference :chave_pix, :string
5
5
  preference :crt, :text
6
6
  preference :key, :text
7
+ preference :access_token, :string
8
+ preference :token_expires_at, :string
7
9
 
8
10
  def payment_source_class
9
11
  PixPaymentSource
@@ -26,24 +28,22 @@ module SolidusSicoob
26
28
  end
27
29
 
28
30
  def find_payment(txid)
29
- client = set_api_client
30
- client.get_payment(txid)
31
+ sicoob_client.get_payment(txid)
31
32
  end
32
33
 
33
34
  def webhook
34
- client = set_api_client
35
- client.get_webhook
35
+ sicoob_client.get_webhook
36
36
  end
37
37
 
38
38
  def update_webhook(url)
39
- client = set_api_client
40
- client.update_webhook(url)
41
- client.get_webhook
39
+ sicoob_client.update_webhook(url)
40
+ sicoob_client.get_webhook
42
41
  end
43
42
 
44
43
  def create_payment(order)
45
44
  existing_payment = find_existing_payment(order)
46
45
  return existing_payment if payment_is_usable?(existing_payment, order)
46
+ invalidate_valid_payments(order, existing_payment&.id)
47
47
 
48
48
  payment = order.payments.new(amount: order.total, payment_method: self)
49
49
  payment.source = init_source(order)
@@ -54,31 +54,52 @@ module SolidusSicoob
54
54
  payment
55
55
  end
56
56
 
57
- def invalidate_payment(payment_source)
58
- return false unless payment_source&.txid
57
+ def purchase(money, source, _options = {})
58
+ sicoob_payment = source.retrieve_from_api
59
+ sync(source.payments.sole, sicoob_payment)
60
+ if sicoob_payment.status == "CONCLUIDA"
61
+ successful_response("Pagamento realizado", status: source.status, internal_detail: "")
62
+ else
63
+ failure_response("Pagamento não realizado", status: source.status, internal_detail: "")
64
+ end
65
+ end
59
66
 
60
- sicoob_payment = find_payment(payment_source.txid)
61
- return false if sicoob_payment.paid?
67
+ def invalidate_payment(payment)
68
+ txid = payment.source&.txid
69
+ return false if txid.nil?
70
+ sicoob_payment = find_payment(payment.source.txid)
71
+ sync(payment, sicoob_payment)
72
+ return false unless payment.checkout?
62
73
 
63
- sicoob_payment.invalidate!
64
- payment_source.payments[0].log_entries.create!(parsed_payment_response_details_with_fallback: failure_response("Pagamento cancelado"))
65
- true
74
+ sicoob_payment = sicoob_client.update_payment(payment_id: txid, status: "REMOVIDA_PELO_USUARIO_RECEBEDOR")
75
+ sync(payment, sicoob_payment)
76
+ end
77
+
78
+ def pix_paid? payment
79
+ return false unless payment.source&.txid
80
+ sicoob_payment = find_payment(payment.source.txid)
81
+ paid_amount = total_paid(sicoob_payment)
82
+ sicoob_payment.status == "CONCLUIDA" && paid_amount >= payment.amount
66
83
  end
67
84
 
68
- def purchase(money, source, options = {})
69
- gateway.purchase(money, source, options)
85
+ def refund_payment(payment, amount: nil, refund_nature: nil, description: nil)
86
+ amount ||= payment.amount
87
+ refund_nature ||= "ORIGINAL"
88
+ sicoob_client.refund_payment(end_to_end_id: payment.source.e2e_id, amount: amount, refund_nature: refund_nature, description: description)
70
89
  end
71
90
 
72
- def should_skip_processing?(source)
73
- inter_payment = find_payment(source.txid)
74
- !inter_payment.paid?
91
+ def puts_qrcode(payment)
92
+ puts RQRCode::QRCode.new(payment.pix_code).to_s(dark: "██", light: " ")
75
93
  end
76
94
 
77
95
  private
78
96
 
97
+ def sicoob_client
98
+ SicoobClient.for_payment_method(self)
99
+ end
100
+
79
101
  def create_sicoob_payment(payment_source)
80
- client = set_api_client
81
- client.create_payment(
102
+ sicoob_client.create_payment(
82
103
  amount: payment_source.amount,
83
104
  payer_tax_id: payment_source.payer_tax_id,
84
105
  payer_name: payment_source.payer_name,
@@ -87,7 +108,8 @@ module SolidusSicoob
87
108
  end
88
109
 
89
110
  def find_existing_payment(order)
90
- pix_payments = order.payments.checkout
111
+ available_payments = order.payments.where(state: ["checkout", "pending"])
112
+ pix_payments = available_payments.where(payment_method: self)
91
113
  raise "More than one valid payment for #{order.number}" if pix_payments.count > 1
92
114
 
93
115
  pix_payments.first
@@ -99,85 +121,105 @@ module SolidusSicoob
99
121
  payment.source.active? && payment.amount == order.total
100
122
  end
101
123
 
124
+ def init_source(order)
125
+ PixPaymentSource.new(
126
+ amount: order.total,
127
+ payer_name: order.ship_address.name,
128
+ payer_tax_id: order.tax_id,
129
+ payment_method: self
130
+ )
131
+ end
132
+
102
133
  def process_payment_response(payment, sicoob_payment)
103
134
  payment.update(response_code: sicoob_payment.txid)
104
135
  payment.source.update(
105
136
  txid: sicoob_payment.txid,
106
- pix_code: sicoob_payment.copia_e_cola,
107
- qr_code_svg: sicoob_payment.qr_code,
137
+ pix_code: sicoob_payment.brcode || sicoob_payment.qrCode,
138
+ qr_code_svg: qr_code(sicoob_payment),
108
139
  status: sicoob_payment.status,
109
- expiration: sicoob_payment.expiracao
140
+ expiration: expiration_date(sicoob_payment)
110
141
  )
111
-
112
- if sicoob_payment.internal_error
113
- handle_payment_error(payment, sicoob_payment)
114
- else
115
- update_payment_status(payment, sicoob_payment)
116
- end
142
+ payment.update(response_code: sicoob_payment.txid)
143
+ sync(payment, sicoob_payment)
117
144
  end
118
145
 
119
- def update_payment_status(payment, inter_payment)
120
- status = case inter_payment.status
121
- when "ATIVA" then "pending"
146
+ def sync(payment, sicoob_payment)
147
+ paid_amount = total_paid(sicoob_payment)
148
+ payment.source.update!(status: sicoob_payment.status, paid_amount: paid_amount, e2e_id: end_to_end(sicoob_payment))
149
+
150
+ if sicoob_payment.status == "CONCLUIDA" && paid_amount >= payment.amount
151
+ approve_payment(payment)
152
+ elsif sicoob_payment.status == "CONCLUIDA"
153
+ raise "Payment paid with value less than the created - #{sicoob_payment.txid}"
154
+ elsif sicoob_payment.status != "ATIVA" || payment.source.expired?
155
+ invalid_payment(payment)
122
156
  end
123
- payment.source.update(status: status)
124
157
  end
125
158
 
126
- def handle_payment_error(payment, inter_payment)
127
- payment.invalidate
128
- error_message = inter_payment.internal_error || "Erro ao criar o pagamento"
129
- response = failure_response(error_message)
159
+ def approve_payment(payment)
160
+ payment.complete
161
+ payment_source = payment.source
162
+ payment_source.update! status: payment.state
163
+ response = successful_response("Pagamento realizado", status: payment_source.status, internal_detail: "")
130
164
  payment.log_entries.create(parsed_payment_response_details_with_fallback: response)
131
- payment.source.update(internal_error: error_message, status: "error")
132
165
  end
133
166
 
134
- def init_source(order)
135
- PixPaymentSource.new(
136
- amount: order.total,
137
- payer_name: order.ship_address.name,
138
- payer_tax_id: order.tax_id,
139
- payment_method: self
140
- )
141
- end
167
+ def invalid_payment payment
168
+ return false unless payment.checkout?
169
+ if payment.checkout?
170
+ payment.invalidate!
171
+ else
172
+ payment.failure!
173
+ end
142
174
 
143
- def set_api_client
144
- account = SolidusSicoob::Account.find_by(chave_pix: preferences[:chave_pix])
145
- client = ::SicoobApi::Client.new(
146
- client_id: preferences[:client_id],
147
- chave_pix: preferences[:chave_pix],
148
- crt: temp_file(preferences[:crt]).path,
149
- key: temp_file(preferences[:key]).path,
150
- token: account&.token,
151
- token_expires_at: account&.expires_at
152
- )
153
- SolidusSicoob::Account.upsert(
154
- {token: client.token, expires_at: client.token_expires_at, chave_pix: client.chave_pix,
155
- spree_payment_method_id: id}, unique_by: :chave_pix
156
- )
157
- client
175
+ payment_source = payment.source
176
+ payment_source.update!(status: payment.state)
177
+ response = failure_response("Pagamento invalidado", status: payment_source.status, internal_detail: "")
178
+ payment.log_entries.create(parsed_payment_response_details_with_fallback: response)
158
179
  end
159
180
 
160
- def temp_file(content)
161
- t = Tempfile.new
162
- t << content
163
- t.close
164
- t
181
+ def invalidate_valid_payments order, existing_payment_id
182
+ available_payments = order.payments.where(state: ["checkout", "pending"])
183
+ available_payments.where.not(id: existing_payment_id).each do |payment|
184
+ payment.source.void!
185
+ end
165
186
  end
166
187
 
167
- def successful_response(message, transaction_id)
188
+ def successful_response message, status:, internal_detail:
189
+ full_message = message + " - " + status + ": " + internal_detail
168
190
  ActiveMerchant::Billing::Response.new(
169
191
  true,
170
- message,
171
- {},
172
- authorization: transaction_id
192
+ full_message
173
193
  )
174
194
  end
175
195
 
176
- def failure_response(message)
196
+ def failure_response message, status:, internal_detail:
197
+ full_message = message + " - " + status + ": " + internal_detail
177
198
  ActiveMerchant::Billing::Response.new(
178
199
  false,
179
- message
200
+ full_message
180
201
  )
181
202
  end
203
+
204
+ def qr_code(sicoob_payment, module_size: 4)
205
+ code = sicoob_payment.brcode || sicoob_payment.qrCode
206
+ qr = RQRCode::QRCode.new(code, size: 10, level: :l)
207
+ qr.as_svg(module_size: module_size)
208
+ end
209
+
210
+ def expiration_date(sicoob_payment)
211
+ return nil unless sicoob_payment.calendario.criacao
212
+ Time.new(sicoob_payment.calendario.criacao) + sicoob_payment.calendario.expiracao
213
+ end
214
+
215
+ def total_paid(sicoob_payment)
216
+ return nil unless sicoob_payment["pix"] && sicoob_payment.pix.any?
217
+ Float(sicoob_payment.pix[0].valor)
218
+ end
219
+
220
+ def end_to_end(sicoob_payment)
221
+ return nil unless sicoob_payment["pix"] && sicoob_payment.pix.any?
222
+ sicoob_payment.pix[0].endToEndId
223
+ end
182
224
  end
183
225
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidusSicoob
4
- VERSION = "0.0.2"
4
+ VERSION = "2.0.0"
5
5
  end
@@ -3,6 +3,7 @@
3
3
  require "solidus_sicoob/version"
4
4
  require "solidus_sicoob/engine"
5
5
  require "sicoob_api"
6
+ require "rqrcode"
6
7
 
7
8
  module SolidusSicoob
8
9
  class Error < StandardError; end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidus_sicoob
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.2
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hamilton
8
- autorequire:
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2025-01-28 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: solidus_core
@@ -66,6 +65,20 @@ dependencies:
66
65
  - - ">="
67
66
  - !ruby/object:Gem::Version
68
67
  version: '0'
68
+ - !ruby/object:Gem::Dependency
69
+ name: rqrcode
70
+ requirement: !ruby/object:Gem::Requirement
71
+ requirements:
72
+ - - ">="
73
+ - !ruby/object:Gem::Version
74
+ version: '0'
75
+ type: :runtime
76
+ prerelease: false
77
+ version_requirements: !ruby/object:Gem::Requirement
78
+ requirements:
79
+ - - ">="
80
+ - !ruby/object:Gem::Version
81
+ version: '0'
69
82
  description: ''
70
83
  email: hamilton@todasessascoisas.com.br
71
84
  executables: []
@@ -73,9 +86,9 @@ extensions: []
73
86
  extra_rdoc_files: []
74
87
  files:
75
88
  - README.md
76
- - app/models/solidus_sicoob/account.rb
77
89
  - app/models/solidus_sicoob/gateway.rb
78
90
  - app/models/solidus_sicoob/pix_payment_source.rb
91
+ - app/models/solidus_sicoob/sicoob_client.rb
79
92
  - app/models/solidus_sicoob/sicoob_pix.rb
80
93
  - app/views/spree/admin/payments/source_forms/_sicoob_pix.html.erb
81
94
  - app/views/spree/admin/payments/source_views/_sicoob_pix.html.erb
@@ -89,7 +102,6 @@ files:
89
102
  homepage: https://github.com/todasessascoisas/solidus_sicoob
90
103
  licenses: []
91
104
  metadata: {}
92
- post_install_message:
93
105
  rdoc_options: []
94
106
  require_paths:
95
107
  - lib
@@ -107,8 +119,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
107
119
  - !ruby/object:Gem::Version
108
120
  version: '0'
109
121
  requirements: []
110
- rubygems_version: 3.5.10
111
- signing_key:
122
+ rubygems_version: 3.6.9
112
123
  specification_version: 4
113
124
  summary: ''
114
125
  test_files: []
@@ -1,5 +0,0 @@
1
- module SolidusSicoob
2
- class Account < ApplicationRecord
3
- belongs_to :spree_payment_method, class_name: "Spree::PaymentMethod"
4
- end
5
- end