solidus_sicoob 1.0.0 → 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: c7fcf5044fe4d6d1f222b2b32e21b1c1e303095db40abc0649bed5c90f4584db
4
- data.tar.gz: 19343e81cf137feeeca2ad983693c1546e73c0bcf24e80af503307eb0dfdf6a7
3
+ metadata.gz: c7a4f9670199d754eaa022b29ea83a32fb129dfa21f2ed1f73e3f76801012154
4
+ data.tar.gz: 0aa13e2ac755ad8057db230b794a7d00a91b5d1d0b0a4e2788e97d13f4615aa5
5
5
  SHA512:
6
- metadata.gz: 55fab3f1438a118eb526f579045796a84f25687e293fb834dc5177e979ee2827213e6630b3b056c8422cee19e5665fe6a4948b251d8ffdb51c31b6bb6112c0fe
7
- data.tar.gz: d4d6342d73b071398c580d9fb33603bad64dc339c1ea4fab6e5ece96603b10a01cba25214a9b3da493649d38b58719e069431aa206cfcdaf03fbbabf59cce925
6
+ metadata.gz: c59f36b5cc30bc52b1bf7e1af12030b81a838b17a02df27a88c11bce71d84e54a82910465fec933d57c11d903fc8bfb4798f954137518a26bf931c6c8a5fdce1
7
+ data.tar.gz: 43c49c1ee24e96c535007c66bcd5f71108e5755cfd3a0c361824f45ec7f43a58e9f5fa9264e059b487410fb6addf1b27fbd44e2c6227459ddd730fe49e091169
@@ -3,21 +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
- 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")
12
10
  end
13
11
 
14
- def void(transaction_id, _options = {})
15
- # Respondendo sempre com successful_response para funcionar o botão de "Cancelar" do pedido. Reembolso deve ser feito por fora.
16
- 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
17
25
  end
18
26
 
19
27
  private
20
28
 
29
+ def converted_money(money)
30
+ money / 100.0
31
+ end
32
+
21
33
  def successful_response(message, transaction_id)
22
34
  ActiveMerchant::Billing::Response.new(
23
35
  true,
@@ -27,10 +39,12 @@ module SolidusSicoob
27
39
  )
28
40
  end
29
41
 
30
- def failure_response(message)
42
+ def failure_response(message, transaction_id = nil)
31
43
  ActiveMerchant::Billing::Response.new(
32
44
  false,
33
- message
45
+ message,
46
+ {},
47
+ authorization: transaction_id
34
48
  )
35
49
  end
36
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,73 +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
- update_payment_status(payment, sicoob_payment)
142
+ payment.update(response_code: sicoob_payment.txid)
143
+ sync(payment, sicoob_payment)
113
144
  end
114
145
 
115
- def update_payment_status(payment, inter_payment)
116
- status = case inter_payment.status
117
- 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)
118
156
  end
119
- payment.source.update(status: status)
120
157
  end
121
158
 
122
- def init_source(order)
123
- PixPaymentSource.new(
124
- amount: order.total,
125
- payer_name: order.ship_address.name,
126
- payer_tax_id: order.tax_id,
127
- payment_method: self
128
- )
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: "")
164
+ payment.log_entries.create(parsed_payment_response_details_with_fallback: response)
129
165
  end
130
166
 
131
- def set_api_client
132
- account = SolidusSicoob::Account.find_by(chave_pix: preferences[:chave_pix])
133
- client = ::SicoobApi::Client.new(
134
- client_id: preferences[:client_id],
135
- chave_pix: preferences[:chave_pix],
136
- crt: temp_file(preferences[:crt]).path,
137
- key: temp_file(preferences[:key]).path,
138
- access_token: account&.token,
139
- token_expires_at: account&.expires_at
140
- )
141
- SolidusSicoob::Account.upsert(
142
- {token: client.access_token, expires_at: client.token_expires_at, chave_pix: client.chave_pix,
143
- spree_payment_method_id: id}, unique_by: :chave_pix
144
- )
145
- client
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
174
+
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)
146
179
  end
147
180
 
148
- def temp_file(content)
149
- t = Tempfile.new
150
- t << content
151
- t.close
152
- 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
153
186
  end
154
187
 
155
- def successful_response(message, transaction_id)
188
+ def successful_response message, status:, internal_detail:
189
+ full_message = message + " - " + status + ": " + internal_detail
156
190
  ActiveMerchant::Billing::Response.new(
157
191
  true,
158
- message,
159
- {},
160
- authorization: transaction_id
192
+ full_message
161
193
  )
162
194
  end
163
195
 
164
- def failure_response(message)
196
+ def failure_response message, status:, internal_detail:
197
+ full_message = message + " - " + status + ": " + internal_detail
165
198
  ActiveMerchant::Billing::Response.new(
166
199
  false,
167
- message
200
+ full_message
168
201
  )
169
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
170
224
  end
171
225
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module SolidusSicoob
4
- VERSION = "1.0.0"
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,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: solidus_sicoob
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 2.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Hamilton
@@ -65,6 +65,20 @@ dependencies:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
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'
68
82
  description: ''
69
83
  email: hamilton@todasessascoisas.com.br
70
84
  executables: []
@@ -72,9 +86,9 @@ extensions: []
72
86
  extra_rdoc_files: []
73
87
  files:
74
88
  - README.md
75
- - app/models/solidus_sicoob/account.rb
76
89
  - app/models/solidus_sicoob/gateway.rb
77
90
  - app/models/solidus_sicoob/pix_payment_source.rb
91
+ - app/models/solidus_sicoob/sicoob_client.rb
78
92
  - app/models/solidus_sicoob/sicoob_pix.rb
79
93
  - app/views/spree/admin/payments/source_forms/_sicoob_pix.html.erb
80
94
  - app/views/spree/admin/payments/source_views/_sicoob_pix.html.erb
@@ -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