solidus_sicoob 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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: 16bcb92b16e4692c8180cda602cbf14d706ed4965f8e5f6d0f941c1c42756548
4
+ data.tar.gz: f252140ff33eba6cd0d5dd45a825a90d757ac57228e549aa5aac18e44a51ece2
5
+ SHA512:
6
+ metadata.gz: e3662f372de478e75c7db09b629db666266cef75a99beda49f75b46338f0e48fe25d5cf898ac9490e591c70cff402b7b10ef9420e36705e5dfb569ca85e147be
7
+ data.tar.gz: f4c843009d70cca2963b1f8f13f14eea9df9c319d0b3a0177478dfbb413f5924a7403d6dcf71ce7e1e36c4ee90e2d07c68bf86e4ca56f00e3ea3665cdd654bb5
data/README.md ADDED
@@ -0,0 +1,16 @@
1
+ # Solidus Sicoob
2
+ <!-- Explain what your extension does. -->
3
+
4
+ ## Installation
5
+
6
+ Add solidus_sicoob to your Gemfile:
7
+
8
+ ```ruby
9
+ gem 'solidus_sicoob'
10
+ ```
11
+
12
+ Bundle your dependencies and run the installation generator:
13
+
14
+ ```shell
15
+ bin/rails generate solidus_sicoob:install
16
+ ```
@@ -0,0 +1,5 @@
1
+ module SolidusSicoob
2
+ class Account < ApplicationRecord
3
+ belongs_to :spree_payment_method, class_name: "Spree::PaymentMethod"
4
+ end
5
+ end
@@ -0,0 +1,39 @@
1
+ module SolidusSicoob
2
+ class Gateway
3
+ def initialize(options)
4
+ end
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
14
+ end
15
+
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)
19
+ end
20
+
21
+ private
22
+
23
+ def successful_response(message, transaction_id)
24
+ ActiveMerchant::Billing::Response.new(
25
+ true,
26
+ message,
27
+ {},
28
+ authorization: transaction_id
29
+ )
30
+ end
31
+
32
+ def failure_response(message)
33
+ ActiveMerchant::Billing::Response.new(
34
+ false,
35
+ message
36
+ )
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,40 @@
1
+ module SolidusSicoob
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
+ def expired?
20
+ expiration.past?
21
+ end
22
+
23
+ def active?
24
+ expiration.future?
25
+ end
26
+
27
+ def retrieve_from_api
28
+ payment_method.find_payment(txid)
29
+ end
30
+
31
+ def paid?
32
+ sicoob_payment = retrieve_from_api
33
+ sicoob_payment.paid?
34
+ end
35
+
36
+ def invalidate
37
+ payment_method.invalidate_payment(self)
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,183 @@
1
+ module SolidusSicoob
2
+ class SicoobPix < Spree::PaymentMethod
3
+ preference :client_id, :string
4
+ preference :chave_pix, :string
5
+ preference :crt, :text
6
+ preference :key, :text
7
+
8
+ def payment_source_class
9
+ PixPaymentSource
10
+ end
11
+
12
+ def gateway_class
13
+ Gateway
14
+ end
15
+
16
+ def supports?(source)
17
+ source.is_a?(payment_source_class)
18
+ end
19
+
20
+ def auto_capture?
21
+ true
22
+ end
23
+
24
+ def partial_name
25
+ "sicoob_pix"
26
+ end
27
+
28
+ def find_payment(txid)
29
+ client = set_api_client
30
+ client.get_payment(txid)
31
+ end
32
+
33
+ def webhook
34
+ client = set_api_client
35
+ client.get_webhook
36
+ end
37
+
38
+ def update_webhook(url)
39
+ client = set_api_client
40
+ client.update_webhook(url)
41
+ client.get_webhook
42
+ end
43
+
44
+ def create_payment(order)
45
+ existing_payment = find_existing_payment(order)
46
+ return existing_payment if payment_is_usable?(existing_payment, order)
47
+
48
+ payment = order.payments.new(amount: order.total, payment_method: self)
49
+ payment.source = init_source(order)
50
+ payment.save
51
+
52
+ sicoob_payment = create_sicoob_payment(payment.source)
53
+ process_payment_response(payment, sicoob_payment)
54
+ payment
55
+ end
56
+
57
+ def invalidate_payment(payment_source)
58
+ return false unless payment_source&.txid
59
+
60
+ sicoob_payment = find_payment(payment_source.txid)
61
+ return false if sicoob_payment.paid?
62
+
63
+ sicoob_payment.invalidate!
64
+ payment_source.payments[0].log_entries.create!(parsed_payment_response_details_with_fallback: failure_response("Pagamento cancelado"))
65
+ true
66
+ end
67
+
68
+ def purchase(money, source, options = {})
69
+ gateway.purchase(money, source, options)
70
+ end
71
+
72
+ def should_skip_processing?(source)
73
+ inter_payment = find_payment(source.txid)
74
+ !inter_payment.paid?
75
+ end
76
+
77
+ private
78
+
79
+ def create_sicoob_payment(payment_source)
80
+ client = set_api_client
81
+ client.create_payment(
82
+ amount: payment_source.amount,
83
+ payer_tax_id: payment_source.payer_tax_id,
84
+ payer_name: payment_source.payer_name,
85
+ expiration: 600
86
+ )
87
+ end
88
+
89
+ def find_existing_payment(order)
90
+ pix_payments = order.payments.checkout
91
+ raise "More than one valid payment for #{order.number}" if pix_payments.count > 1
92
+
93
+ pix_payments.first
94
+ end
95
+
96
+ def payment_is_usable?(payment, order)
97
+ return false unless payment
98
+
99
+ payment.source.active? && payment.amount == order.total
100
+ end
101
+
102
+ def process_payment_response(payment, sicoob_payment)
103
+ payment.update(response_code: sicoob_payment.txid)
104
+ payment.source.update(
105
+ txid: sicoob_payment.txid,
106
+ pix_code: sicoob_payment.copia_e_cola,
107
+ qr_code_svg: sicoob_payment.qr_code,
108
+ status: sicoob_payment.status,
109
+ expiration: sicoob_payment.expiracao
110
+ )
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
117
+ end
118
+
119
+ def update_payment_status(payment, inter_payment)
120
+ status = case inter_payment.status
121
+ when "ATIVA" then "pending"
122
+ end
123
+ payment.source.update(status: status)
124
+ end
125
+
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)
130
+ payment.log_entries.create(parsed_payment_response_details_with_fallback: response)
131
+ payment.source.update(internal_error: error_message, status: "error")
132
+ end
133
+
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
142
+
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
158
+ end
159
+
160
+ def temp_file(content)
161
+ t = Tempfile.new
162
+ t << content
163
+ t.close
164
+ t
165
+ end
166
+
167
+ def successful_response(message, transaction_id)
168
+ ActiveMerchant::Billing::Response.new(
169
+ true,
170
+ message,
171
+ {},
172
+ authorization: transaction_id
173
+ )
174
+ end
175
+
176
+ def failure_response(message)
177
+ ActiveMerchant::Billing::Response.new(
178
+ false,
179
+ message
180
+ )
181
+ end
182
+ end
183
+ end
@@ -0,0 +1,64 @@
1
+ <div class="content-wrapper">
2
+ <fieldset class="no-border-top">
3
+ <legend>
4
+ Pagamento Sicoob Pix
5
+ </legend>
6
+ <div class="row">
7
+ <div class="col-12 col-md-6">
8
+
9
+ <div class="field">
10
+ <%= label_tag :txid, "TXID" %>
11
+ <%= text_field_tag :txid, payment.source.txid, class:"fullwidth", disabled: true %>
12
+ </div>
13
+
14
+ <div class="field">
15
+ <%= label_tag :payer_name, "Nome" %>
16
+ <%= text_field_tag :payer_name, payment.source.payer_name, class:"fullwidth", disabled: true %>
17
+ </div>
18
+
19
+ <div class="field">
20
+ <%= label_tag :expiration, "Criação" %>
21
+ <%= text_field_tag :expiration, payment.source.created_at.in_time_zone('America/Sao_Paulo').strftime("%d/%m/%Y às %H:%M"), class:"fullwidth", disabled: true %>
22
+ </div>
23
+
24
+ <div class="field">
25
+ <%= label_tag :amount, "Valor" %>
26
+ <%= text_field_tag :amount, number_to_currency(payment.source.amount), class:"fullwidth", disabled: true %>
27
+ </div>
28
+ </div>
29
+
30
+ <div class="col-12 col-md-6">
31
+ <div class="field">
32
+ <%= label_tag :status, "Status" %>
33
+ <%= text_field_tag :status, payment.source.status, class:"fullwidth", disabled: true %>
34
+ </div>
35
+
36
+ <div class="field">
37
+ <%= label_tag :payer_tax_id, "Documento" %>
38
+ <%= text_field_tag :payer_tax_id, payment.source.payer_tax_id, class:"fullwidth", disabled: true %>
39
+ </div>
40
+
41
+ <div class="field">
42
+ <%= label_tag :expiration, "Expiração" %>
43
+ <%= text_field_tag :expiration, payment.source.expiration&.in_time_zone('America/Sao_Paulo')&.strftime("%d/%m/%Y às %H:%M"), class:"fullwidth", disabled: true %>
44
+ </div>
45
+
46
+ <div class="field">
47
+ <%= label_tag :e2e_id, "ID da Transação" %>
48
+ <%= text_field_tag :e2e_id, payment.source.e2e_id, class:"fullwidth", disabled: true %>
49
+ </div>
50
+ </div>
51
+
52
+ <div class="col-12">
53
+ <div class="field">
54
+ <%= label_tag :copia_cola, "Copia e Cola" %>
55
+ <%= text_field_tag :copia_cola, payment.source.pix_code, class:"fullwidth", disabled: true %>
56
+ </div>
57
+ <div class="field">
58
+ <div style="display: flex; justify-content: center; align-items: center;">
59
+ <%= payment.source.qr_code_svg.html_safe %>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ </fieldset>
64
+ </div>
@@ -0,0 +1 @@
1
+ json.call(payment_source, :id, :txid, :e2e_id, :status, :amount, :payer_name, :payer_tax_id)
@@ -0,0 +1,12 @@
1
+ class CreateSolidusSicoobAccounts < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :solidus_sicoob_accounts do |t|
4
+ t.string :chave_pix, index: {unique: true}
5
+ t.string :token
6
+ t.string :expires_at
7
+ t.string :spree_payment_method_id
8
+
9
+ t.timestamps
10
+ end
11
+ end
12
+ end
@@ -0,0 +1,20 @@
1
+ class CreateSolidusSicoobPixPaymentSources < ActiveRecord::Migration[7.1]
2
+ def change
3
+ create_table :solidus_sicoob_pix_payment_sources do |t|
4
+ t.string :txid
5
+ t.string :e2e_id
6
+ t.string :pix_code
7
+ t.string :qr_code_svg
8
+ t.string :status
9
+ t.decimal :amount
10
+ t.decimal :paid_amount
11
+ t.datetime :expiration
12
+ t.string :payer_name
13
+ t.string :payer_tax_id
14
+ t.string :internal_error
15
+ t.integer :payment_method_id
16
+
17
+ t.timestamps
18
+ end
19
+ end
20
+ end
@@ -0,0 +1,27 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusSicoob
4
+ module Generators
5
+ class InstallGenerator < Rails::Generators::Base
6
+ class_option :auto_run_migrations, type: :boolean, default: false
7
+ source_root File.expand_path("templates", __dir__)
8
+
9
+ def self.exit_on_failure?
10
+ true
11
+ end
12
+
13
+ def add_migrations
14
+ run "bin/rails railties:install:migrations FROM=solidus_sicoob"
15
+ end
16
+
17
+ def run_migrations
18
+ run_migrations = options[:auto_run_migrations] || ["", "y", "Y"].include?(ask("Would you like to run the migrations now? [Y/n]"))
19
+ if run_migrations
20
+ run "bin/rails db:migrate"
21
+ else
22
+ puts "Skipping bin/rails db:migrate, don't forget to run it!"
23
+ end
24
+ end
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solidus_core"
4
+ require "solidus_support"
5
+
6
+ module SolidusSicoob
7
+ class Engine < Rails::Engine
8
+ include SolidusSupport::EngineExtensions
9
+
10
+ isolate_namespace SolidusSicoob
11
+
12
+ engine_name "solidus_sicoob"
13
+
14
+ initializer "solidus_sicoob.add_payment_method", after: "spree.register.payment_methods" do |app|
15
+ app.config.spree.payment_methods << "SolidusSicoob::SicoobPix"
16
+ end
17
+
18
+ # use rspec for tests
19
+ config.generators do |g|
20
+ g.test_framework :rspec
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module SolidusSicoob
4
+ VERSION = "0.0.1"
5
+ end
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "solidus_sicoob/version"
4
+ require "solidus_sicoob/engine"
5
+ require "sicoob_api"
6
+
7
+ module SolidusSicoob
8
+ class Error < StandardError; end
9
+
10
+ class Configuration
11
+ end
12
+
13
+ def self.configure
14
+ yield(Configuration)
15
+ end
16
+ end
metadata ADDED
@@ -0,0 +1,114 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: solidus_sicoob
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Hamilton
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2025-01-27 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: solidus_core
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: solidus_support
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: sicoob_api
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: solidus_brazilian_adaptations
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :runtime
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description: ''
70
+ email: hamilton@todasessascoisas.com.br
71
+ executables: []
72
+ extensions: []
73
+ extra_rdoc_files: []
74
+ files:
75
+ - README.md
76
+ - app/models/solidus_sicoob/account.rb
77
+ - app/models/solidus_sicoob/gateway.rb
78
+ - app/models/solidus_sicoob/pix_payment_source.rb
79
+ - app/models/solidus_sicoob/sicoob_pix.rb
80
+ - app/views/spree/admin/payments/source_forms/_sicoob_pix.html.erb
81
+ - app/views/spree/admin/payments/source_views/_sicoob_pix.html.erb
82
+ - app/views/spree/api/payments/source_views/_sicoob_pix.jbuilder
83
+ - db/migrate/20250127141458_create_solidus_sicoob_accounts.rb
84
+ - db/migrate/20250127141733_create_solidus_sicoob_pix_payment_sources.rb
85
+ - lib/generators/solidus_sicoob/install/install_generator.rb
86
+ - lib/solidus_sicoob.rb
87
+ - lib/solidus_sicoob/engine.rb
88
+ - lib/solidus_sicoob/version.rb
89
+ homepage: https://github.com/todasessascoisas/solidus_sicoob
90
+ licenses: []
91
+ metadata: {}
92
+ post_install_message:
93
+ rdoc_options: []
94
+ require_paths:
95
+ - lib
96
+ required_ruby_version: !ruby/object:Gem::Requirement
97
+ requirements:
98
+ - - ">="
99
+ - !ruby/object:Gem::Version
100
+ version: '2.5'
101
+ - - "<"
102
+ - !ruby/object:Gem::Version
103
+ version: '4'
104
+ required_rubygems_version: !ruby/object:Gem::Requirement
105
+ requirements:
106
+ - - ">="
107
+ - !ruby/object:Gem::Version
108
+ version: '0'
109
+ requirements: []
110
+ rubygems_version: 3.5.10
111
+ signing_key:
112
+ specification_version: 4
113
+ summary: ''
114
+ test_files: []