solidus_mercado_pago 1.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 +7 -0
- data/app/assets/javascripts/spree/backend/solidus_mercado_pago.js +0 -0
- data/app/assets/javascripts/spree/frontend/solidus_mercado_pago.js +25 -0
- data/app/assets/stylesheets/spree/backend/solidus_mercado_pago.css +1 -0
- data/app/assets/stylesheets/spree/frontend/solidus_mercado_pago.css +1 -0
- data/app/controllers/spree/mercado_pago_controller.rb +68 -0
- data/app/models/mercado_pago/client.rb +62 -0
- data/app/models/mercado_pago/client/api.rb +44 -0
- data/app/models/mercado_pago/client/authentication.rb +36 -0
- data/app/models/mercado_pago/client/preferences.rb +20 -0
- data/app/models/mercado_pago/notification.rb +8 -0
- data/app/models/mercado_pago/order_preferences_builder.rb +65 -0
- data/app/models/spree/payment_method/mercado_pago.rb +46 -0
- data/app/services/mercado_pago/handle_received_notification.rb +18 -0
- data/app/services/mercado_pago/process_notification.rb +56 -0
- data/app/views/spree/admin/payments/source_forms/_mercadopago.html.erb +1 -0
- data/app/views/spree/admin/payments/source_views/_mercadopago.html.erb +1 -0
- data/app/views/spree/checkout/mercado_pago_error.html.erb +0 -0
- data/app/views/spree/checkout/payment/_mercadopago.html.erb +15 -0
- data/config/locales/en.yml +4 -0
- data/config/locales/es.yml +4 -0
- data/config/locales/pt-BR.yml +4 -0
- data/config/routes.rb +6 -0
- data/db/migrate/20141201204026_create_solidus_mercado_pago_notifications.rb +9 -0
- data/lib/generators/solidus_mercado_pago/install/install_generator.rb +46 -0
- data/lib/solidus_mercado_pago.rb +3 -0
- data/lib/solidus_mercado_pago/engine.rb +35 -0
- data/lib/solidus_mercado_pago/version.rb +3 -0
- data/lib/tasks/mercado_user.rake +5 -0
- data/spec/controllers/spree/mercado_pago_controller_spec.rb +30 -0
- data/spec/fixtures/authenticated.json +7 -0
- data/spec/fixtures/preferences_created.json +41 -0
- data/spec/lib/generators/solidus_mercado_pago/install/install_generator_spec.rb +33 -0
- data/spec/lib/solidus_mercado_pago/version_spec.rb +5 -0
- data/spec/models/mercado_pago/client_spec.rb +130 -0
- data/spec/models/mercado_pago/notification_spec.rb +17 -0
- data/spec/models/mercado_pago/order_preferences_builder_spec.rb +58 -0
- data/spec/models/spree/payment_method/mercado_pago_spec.rb +34 -0
- data/spec/services/mercado_pago/process_notification_spec.rb +82 -0
- data/spec/spec_helper.rb +133 -0
- metadata +200 -0
@@ -0,0 +1 @@
|
|
1
|
+
<!-- Source form -->
|
@@ -0,0 +1 @@
|
|
1
|
+
<!-- Source view -->
|
File without changes
|
@@ -0,0 +1,15 @@
|
|
1
|
+
<script>
|
2
|
+
MercadoPago.paymentMethodID = "<%= payment_method.id %>";
|
3
|
+
</script>
|
4
|
+
|
5
|
+
<div class="row">
|
6
|
+
<div class="twelve rows alpha omega">
|
7
|
+
<img src="http://imgmp.mlstatic.com/org-img/banners/ar/medios/575X40.jpg" title="MercadoPago - Medios de pago" alt="MercadoPago - Medios de pago" width="575" height="40"/>
|
8
|
+
</div>
|
9
|
+
</div>
|
10
|
+
|
11
|
+
<div class="row">
|
12
|
+
<div class="twelve rows alpha omega">
|
13
|
+
<%= link_to I18n.t(:make_payment, scope: :mercado_pago), mercado_pago_checkout_path(payment_method_id: payment_method.id), method: :post, class: "button primary" %>
|
14
|
+
</div>
|
15
|
+
</div>
|
data/config/routes.rb
ADDED
@@ -0,0 +1,6 @@
|
|
1
|
+
Spree::Core::Engine.routes.draw do
|
2
|
+
post '/mercado_pago/checkout', to: 'mercado_pago#checkout', as: :mercado_pago_checkout
|
3
|
+
get '/mercado_pago/success', to: 'mercado_pago#success', as: :mercado_pago_success
|
4
|
+
get '/mercado_pago/failure', to: 'mercado_pago#failure', as: :mercado_pago_failure
|
5
|
+
post '/mercado_pago/ipn', to: 'mercado_pago#ipn', as: :mercado_pago_ipn
|
6
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# rubocop:disable Style/RegexpLiteral
|
2
|
+
# rubocop:disable Rails/Output
|
3
|
+
|
4
|
+
module SolidusMercadoPago
|
5
|
+
module Generators
|
6
|
+
class InstallGenerator < Rails::Generators::Base
|
7
|
+
class_option :auto_run_migrations, type: :boolean, default: false
|
8
|
+
class_option :auto_skip_migrations, type: :boolean, default: false
|
9
|
+
|
10
|
+
def add_javascripts
|
11
|
+
append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/solidus_mercado_pago\n"
|
12
|
+
append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_mercado_pago\n"
|
13
|
+
end
|
14
|
+
|
15
|
+
def add_stylesheets
|
16
|
+
frontend_css_file = 'vendor/assets/stylesheets/spree/frontend/all.css'
|
17
|
+
backend_css_file = 'vendor/assets/stylesheets/spree/backend/all.css'
|
18
|
+
|
19
|
+
return unless File.exist?(backend_css_file) && File.exist?(frontend_css_file)
|
20
|
+
|
21
|
+
inject_into_file frontend_css_file, " *= require spree/frontend/solidus_mercado_pago\n", before: /\*\//, verbose: true
|
22
|
+
inject_into_file backend_css_file, " *= require spree/backend/solidus_mercado_pago\n", before: /\*\//, verbose: true
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_migrations
|
26
|
+
if !options[:auto_skip_migrations]
|
27
|
+
run 'bundle exec rake solidus_mercado_pago:install:migrations'
|
28
|
+
else
|
29
|
+
puts 'Skipping rake solidus_mercado_pago:install:migrations, don\'t forget to run it!'
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
def run_migrations
|
34
|
+
run_migrations = options[:auto_skip_migrations] ||
|
35
|
+
options[:auto_run_migrations] ||
|
36
|
+
['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
|
37
|
+
|
38
|
+
if run_migrations && !options[:auto_skip_migrations]
|
39
|
+
run 'bundle exec rake db:migrate'
|
40
|
+
else
|
41
|
+
puts 'Skipping rake db:migrate, don\'t forget to run it!'
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module SolidusMercadoPago
|
2
|
+
class Engine < Rails::Engine
|
3
|
+
require 'spree/core'
|
4
|
+
isolate_namespace Spree
|
5
|
+
engine_name 'solidus_mercado_pago'
|
6
|
+
|
7
|
+
# use rspec for tests
|
8
|
+
config.generators do |g|
|
9
|
+
g.test_framework :rspec
|
10
|
+
end
|
11
|
+
|
12
|
+
initializer 'spree_payment_network.register.payment_methods' do |app|
|
13
|
+
app.config.spree.payment_methods << Spree::PaymentMethod::MercadoPago
|
14
|
+
end
|
15
|
+
|
16
|
+
def self.activate
|
17
|
+
load_decorators
|
18
|
+
load_overrides
|
19
|
+
end
|
20
|
+
|
21
|
+
def self.load_decorators
|
22
|
+
Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
|
23
|
+
Rails.application.config.cache_classes ? require(c) : load(c)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def self.load_overrides
|
28
|
+
Dir.glob(File.join(File.dirname(__FILE__), '../../app/overrides/*.rb')) do |c|
|
29
|
+
Rails.application.config.cache_classes ? require(c) : load(c)
|
30
|
+
end
|
31
|
+
end
|
32
|
+
|
33
|
+
config.to_prepare(&method(:activate).to_proc)
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,30 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
RSpec.describe Spree::MercadoPagoController, type: :controller do
|
4
|
+
describe '#ipn' do
|
5
|
+
let(:operation_id) { 'op123' }
|
6
|
+
|
7
|
+
context 'for valid notifications' do
|
8
|
+
let(:use_case) { double('use_case') }
|
9
|
+
|
10
|
+
it 'handles notification and returns success' do
|
11
|
+
allow(MercadoPago::HandleReceivedNotification).to receive(:new).and_return(use_case)
|
12
|
+
expect(use_case).to receive(:process!)
|
13
|
+
|
14
|
+
post :ipn, params: { id: operation_id, topic: 'payment', format: :json }
|
15
|
+
expect(response).to be_success
|
16
|
+
|
17
|
+
notification = MercadoPago::Notification.order(:created_at).last
|
18
|
+
expect(notification.topic).to eq('payment')
|
19
|
+
expect(notification.operation_id).to eq(operation_id)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
context 'for invalid notification' do
|
24
|
+
it 'responds with invalid request' do
|
25
|
+
post :ipn, params: { id: operation_id, topic: 'nonexistent_topic', format: :raw }
|
26
|
+
expect(response).to be_bad_request
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
@@ -0,0 +1,41 @@
|
|
1
|
+
{
|
2
|
+
"external_reference": "Reference_1234",
|
3
|
+
"items": [
|
4
|
+
{
|
5
|
+
"id": "Código",
|
6
|
+
"title": "Título de lo que estás pagando",
|
7
|
+
"description": "Descripción",
|
8
|
+
"quantity": 1,
|
9
|
+
"unit_price": 50.5,
|
10
|
+
"currency_id": "Tipo de moneda",
|
11
|
+
"picture_url": "https://www.mercadopago.com/org-img/MP3/home/logomp3.gif"
|
12
|
+
}
|
13
|
+
],
|
14
|
+
"date_created": "2011-08-16T21:28:42.606-04:00",
|
15
|
+
"id": "identificador_de_la_preferencia",
|
16
|
+
"collector_id": "collector identifier",
|
17
|
+
"init_point": "https://www.mercadopago.com/checkout/pay?pref_id=identificador_de_la_preferencia",
|
18
|
+
"payer": {
|
19
|
+
"email": "payer@email.com",
|
20
|
+
"name": "payer-name",
|
21
|
+
"surname": "payer-surname"
|
22
|
+
},
|
23
|
+
"back_urls": {
|
24
|
+
"success": "https://www.success.com",
|
25
|
+
"failure": "http://www.failure.com",
|
26
|
+
"pending": "http://www.pending.com"
|
27
|
+
},
|
28
|
+
"payment_methods": {
|
29
|
+
"excluded_payment_methods": [
|
30
|
+
{
|
31
|
+
"id": "amex"
|
32
|
+
}
|
33
|
+
],
|
34
|
+
"excluded_payment_types": [
|
35
|
+
{
|
36
|
+
"id": "ticket"
|
37
|
+
}
|
38
|
+
],
|
39
|
+
"installments": 12
|
40
|
+
}
|
41
|
+
}
|
@@ -0,0 +1,33 @@
|
|
1
|
+
# rubocop:disable Style/RegexpLiteral
|
2
|
+
# rubocop:disable RSpec/FilePath
|
3
|
+
|
4
|
+
require 'spec_helper'
|
5
|
+
require 'generator_spec'
|
6
|
+
require 'generators/solidus_mercado_pago/install/install_generator'
|
7
|
+
|
8
|
+
describe SolidusMercadoPago::Generators::InstallGenerator, type: :generator do
|
9
|
+
destination File.expand_path('../../../../tmp', __FILE__)
|
10
|
+
|
11
|
+
let(:dest) do
|
12
|
+
File.expand_path('../../../../tmp', __FILE__)
|
13
|
+
end
|
14
|
+
|
15
|
+
before do
|
16
|
+
prepare_destination
|
17
|
+
FileUtils.mkdir_p "#{dest}/vendor/assets/javascripts/spree/frontend"
|
18
|
+
FileUtils.mkdir_p "#{dest}/vendor/assets/javascripts/spree/backend"
|
19
|
+
FileUtils.touch "#{dest}/vendor/assets/javascripts/spree/frontend/all.js"
|
20
|
+
FileUtils.touch "#{dest}/vendor/assets/javascripts/spree/backend/all.js"
|
21
|
+
run_generator %w[--auto_skip_migrations]
|
22
|
+
end
|
23
|
+
|
24
|
+
it 'creates a test initializer' do
|
25
|
+
assert_file "#{dest}/vendor/assets/javascripts/spree/frontend/all.js", /^\/\/= require spree\/frontend\/solidus_mercado_pago/
|
26
|
+
end
|
27
|
+
|
28
|
+
it { assert_file "#{dest}/vendor/assets/javascripts/spree/backend/all.js", /^\/\/= require spree\/backend\/solidus_mercado_pago/ }
|
29
|
+
|
30
|
+
after do
|
31
|
+
FileUtils.rm_rf destination_root
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
RSpec.describe MercadoPago::Client, type: :model do
|
5
|
+
SPEC_ROOT = File.expand_path('../', File.dirname(__FILE__))
|
6
|
+
|
7
|
+
let(:payment_method) { double('payment_method', preferred_client_id: 1, preferred_client_secret: 1) }
|
8
|
+
|
9
|
+
let(:order) { double('order', payment_method: payment_method, number: 'testorder', line_items: [], ship_total: 1000) }
|
10
|
+
let(:url_callbacks) { { success: 'url', failure: 'url', pending: 'url' } }
|
11
|
+
let(:payment_method) { double :payment_method, id: 1, preferred_client_id: 'app id', preferred_client_secret: 'app secret' }
|
12
|
+
let(:payment) { double :payment, payment_method: payment_method, id: 1, identifier: 'fruta' }
|
13
|
+
|
14
|
+
let(:login_json_response) { File.open("#{SPEC_ROOT}/../fixtures/authenticated.json").read }
|
15
|
+
let(:preferences_json_response) { File.open("#{SPEC_ROOT}/../fixtures/preferences_created.json").read }
|
16
|
+
let(:client) { MercadoPago::Client.new(payment_method) }
|
17
|
+
|
18
|
+
describe '#initialize' do
|
19
|
+
it "doesn't raise error with all params" do
|
20
|
+
expect { client }.not_to raise_error
|
21
|
+
end
|
22
|
+
end
|
23
|
+
|
24
|
+
describe '#authenticate' do
|
25
|
+
context 'On success' do
|
26
|
+
let(:http_response) do
|
27
|
+
response = double('response')
|
28
|
+
allow(response).to receive(:code).and_return 200
|
29
|
+
allow(response).to receive(:to_str).and_return login_json_response
|
30
|
+
response
|
31
|
+
end
|
32
|
+
let(:js_response) { JSON.parse(http_response) }
|
33
|
+
|
34
|
+
before do
|
35
|
+
allow(RestClient).to receive(:post).and_return(http_response)
|
36
|
+
end
|
37
|
+
|
38
|
+
it 'returns a response object' do
|
39
|
+
expect(client.authenticate).to eq(js_response)
|
40
|
+
end
|
41
|
+
|
42
|
+
it '#errors returns empty array' do
|
43
|
+
client.authenticate
|
44
|
+
expect(client.errors).to be_empty
|
45
|
+
end
|
46
|
+
|
47
|
+
it 'sets the access token' do
|
48
|
+
client.authenticate
|
49
|
+
expect(client.auth_response['access_token']).to eq('TU_ACCESS_TOKEN')
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
context 'On failure' do
|
54
|
+
let(:bad_request_response) do
|
55
|
+
response = double('response')
|
56
|
+
allow(response).to receive(:code).and_return 400
|
57
|
+
allow(response).to receive(:to_str).and_return ''
|
58
|
+
response
|
59
|
+
end
|
60
|
+
|
61
|
+
before do
|
62
|
+
allow(RestClient).to receive(:post).and_raise(RestClient::Exception.new('foo'))
|
63
|
+
end
|
64
|
+
|
65
|
+
it 'raise exception on invalid authentication' do
|
66
|
+
expect { client.authenticate }.to raise_error(RuntimeError) do |_error|
|
67
|
+
expect(client.errors).to include(I18n.t(:authentication_error, scope: :mercado_pago))
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
|
73
|
+
describe '#check_payment_status' do
|
74
|
+
let(:collection) { {} }
|
75
|
+
let(:expected_response) { { results: [collection: collection] } }
|
76
|
+
|
77
|
+
before do
|
78
|
+
allow(subject).to receive(:send_search_request).with(external_reference: payment.id).and_return(expected_response)
|
79
|
+
allow(subject).to receive(:check_status).with(payment, {})
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
describe '#create_preferences' do
|
84
|
+
context 'On success' do
|
85
|
+
let(:preferences) { { foo: 'bar' } }
|
86
|
+
|
87
|
+
before do
|
88
|
+
response = double('response')
|
89
|
+
allow(response).to receive(:code).and_return(200, 201)
|
90
|
+
allow(response).to receive(:to_str).and_return(login_json_response, preferences_json_response)
|
91
|
+
allow(RestClient).to receive(:post).exactly(2).times { response }
|
92
|
+
|
93
|
+
client.authenticate
|
94
|
+
end
|
95
|
+
|
96
|
+
it 'return value should not be nil' do
|
97
|
+
response = client.create_preferences(preferences)
|
98
|
+
expect(response).to be_truthy
|
99
|
+
end
|
100
|
+
|
101
|
+
it '#redirect_url returns offsite checkout url' do
|
102
|
+
client.create_preferences(preferences)
|
103
|
+
expect(client.redirect_url).to be_present
|
104
|
+
expect(client.redirect_url).to eq('https://www.mercadopago.com/checkout/pay?pref_id=identificador_de_la_preferencia')
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
context 'on failure' do
|
109
|
+
before do
|
110
|
+
expect(RestClient).to receive(:post).exactly(2).times do
|
111
|
+
if !@is_second_time
|
112
|
+
@is_second_time = true
|
113
|
+
'{}'
|
114
|
+
else
|
115
|
+
raise RestClient::Exception, 'foo'
|
116
|
+
end
|
117
|
+
end
|
118
|
+
client.authenticate
|
119
|
+
end
|
120
|
+
|
121
|
+
let(:preferences) { { foo: 'bar' } }
|
122
|
+
|
123
|
+
it 'throws exception and populate errors' do
|
124
|
+
expect { client.create_preferences(preferences) }.to raise_error(RuntimeError) do |_variable|
|
125
|
+
expect(client.errors).to include(I18n.t(:authentication_error, scope: :mercado_pago))
|
126
|
+
end
|
127
|
+
end
|
128
|
+
end
|
129
|
+
end
|
130
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module MercadoPago
|
4
|
+
describe Notification do
|
5
|
+
describe 'without basic parameters' do
|
6
|
+
it { expect(Notification.new).not_to be_valid }
|
7
|
+
end
|
8
|
+
|
9
|
+
describe 'with unknown topic' do
|
10
|
+
it { expect(Notification.new(topic: 'foo', operation_id: 'op123')).not_to be_valid }
|
11
|
+
end
|
12
|
+
|
13
|
+
describe 'with correct parameters' do
|
14
|
+
it { expect(Notification.new(topic: 'payment', operation_id: 'op123')).to be_valid }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe 'OrderPreferencesBuilder' do
|
4
|
+
# Factory order_with_line_items is incredibly slow..
|
5
|
+
let(:order) do
|
6
|
+
order = create(:order)
|
7
|
+
create_list(:line_item, 2, order: order)
|
8
|
+
order.line_items.reload
|
9
|
+
order
|
10
|
+
end
|
11
|
+
|
12
|
+
let(:payment) { create(:payment) }
|
13
|
+
let(:callback_urls) { { success: 'http://example.com/success', pending: 'http://example.com/pending', failure: 'http://example.com/failure' } }
|
14
|
+
let(:payer_data) { { email: 'jmperez@devartis.com' } }
|
15
|
+
|
16
|
+
include ActionView::Helpers::TextHelper
|
17
|
+
include ActionView::Helpers::SanitizeHelper
|
18
|
+
include Spree::ProductsHelper
|
19
|
+
|
20
|
+
context 'Calling preferences_hash' do
|
21
|
+
let(:subject) { MercadoPago::OrderPreferencesBuilder.new(order, payment, callback_urls, payer_data).preferences_hash }
|
22
|
+
|
23
|
+
it 'returns external reference' do
|
24
|
+
end
|
25
|
+
|
26
|
+
it 'sets callback urls' do
|
27
|
+
expect(subject).to include(back_urls: callback_urls)
|
28
|
+
end
|
29
|
+
|
30
|
+
it 'sets payer data if brought' do
|
31
|
+
expect(subject).to include(payer: payer_data)
|
32
|
+
end
|
33
|
+
|
34
|
+
it 'sets an item for every line item' do
|
35
|
+
expect(subject).to include(:items)
|
36
|
+
|
37
|
+
order.line_items.each do |line_item|
|
38
|
+
expect(subject[:items]).to include(title: line_item_description_text(line_item.variant.product.name),
|
39
|
+
unit_price: line_item.price.to_f,
|
40
|
+
quantity: line_item.quantity.to_f)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'for order with adjustments' do
|
45
|
+
let!(:adjustment) { Spree::Adjustment.create!(adjustable: order, order: order, label: 'Descuento', amount: -10.0) }
|
46
|
+
|
47
|
+
it 'sets its adjustments as items' do
|
48
|
+
expect(subject[:items]).to include(title: line_item_description_text(adjustment.label),
|
49
|
+
unit_price: adjustment.amount.to_f,
|
50
|
+
quantity: 1)
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'onlies have line items and adjustments in items' do
|
54
|
+
expect(subject[:items]).to have(order.line_items.count + order.adjustments.count).items
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|