solidus_mercadopago 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/LICENSE +26 -0
- data/README.md +68 -0
- data/Rakefile +31 -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/mercadopago_controller.rb +70 -0
- data/app/models/mercadopago/client.rb +64 -0
- data/app/models/mercadopago/client/api.rb +46 -0
- data/app/models/mercadopago/client/authentication.rb +38 -0
- data/app/models/mercadopago/client/preferences.rb +22 -0
- data/app/models/mercadopago/notification.rb +10 -0
- data/app/models/mercadopago/order_preferences_builder.rb +67 -0
- data/app/models/spree/payment_method/mercadopago.rb +48 -0
- data/app/services/mercadopago/handle_received_notification.rb +20 -0
- data/app/services/mercadopago/process_notification.rb +58 -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 +8 -0
- data/db/migrate/20141201204026_create_solidus_mercadopago_notifications.rb +11 -0
- data/lib/generators/solidus_mercadopago/install/install_generator.rb +46 -0
- data/lib/solidus_mercadopago.rb +6 -0
- data/lib/solidus_mercadopago/engine.rb +21 -0
- data/lib/solidus_mercadopago/factories.rb +8 -0
- data/lib/solidus_mercadopago/version.rb +5 -0
- data/lib/tasks/mercado_user.rake +7 -0
- metadata +227 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: d1603f3a8a1fb150b29457dcf870d703db1736b7313d8204efcbfd73a9cc40c6
|
4
|
+
data.tar.gz: 9ecf2ed9ce55e859cd87d723d828bdfd1b05374058f0528a0407eff0ffa005eb
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: e91e4dadda35074f45ec10bd5c301b148fe9c14e4ae1ed7ff5ec02ee58978646df244449167a1ed1972afa7be04d2afa76576d3e349600f16237bc66ecf73217
|
7
|
+
data.tar.gz: '017930045ab6b1773109dacbae1c62930c98a27e45ece144cc14fc40963684eddf20feeef0cb85ca37dc949905b113568766e29b04af9d4401cd0c26b2d50e9e'
|
data/LICENSE
ADDED
@@ -0,0 +1,26 @@
|
|
1
|
+
Copyright (c) 2012 Manuel Barros Reyes
|
2
|
+
All rights reserved.
|
3
|
+
|
4
|
+
Redistribution and use in source and binary forms, with or without modification,
|
5
|
+
are permitted provided that the following conditions are met:
|
6
|
+
|
7
|
+
* Redistributions of source code must retain the above copyright notice,
|
8
|
+
this list of conditions and the following disclaimer.
|
9
|
+
* Redistributions in binary form must reproduce the above copyright notice,
|
10
|
+
this list of conditions and the following disclaimer in the documentation
|
11
|
+
and/or other materials provided with the distribution.
|
12
|
+
* Neither the name Spree nor the names of its contributors may be used to
|
13
|
+
endorse or promote products derived from this software without specific
|
14
|
+
prior written permission.
|
15
|
+
|
16
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
17
|
+
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
18
|
+
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
19
|
+
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
20
|
+
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
21
|
+
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
22
|
+
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
23
|
+
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
24
|
+
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
25
|
+
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
26
|
+
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
data/README.md
ADDED
@@ -0,0 +1,68 @@
|
|
1
|
+
Solidus Mercado Pago Payment Method.
|
2
|
+
=================================
|
3
|
+
|
4
|
+
[](https://circleci.com/gh/magma-labs/solidus_mercadopago/tree/master)
|
5
|
+
|
6
|
+
This project is a fork from [spree version maintained by ngelx](https://github.com/ngelx/solidus_mercadopago).
|
7
|
+
|
8
|
+
|
9
|
+
```
|
10
|
+
gem 'solidus_mercadopago', git: "git@github.com:magma-labs/solidus_mercadopago.git"
|
11
|
+
```
|
12
|
+
|
13
|
+
You should run inside your project
|
14
|
+
|
15
|
+
```
|
16
|
+
bundle exec rails g solidus_mercadopago:install
|
17
|
+
```
|
18
|
+
|
19
|
+
This will import assets and migrations
|
20
|
+
|
21
|
+
Basic Setup
|
22
|
+
-----
|
23
|
+
|
24
|
+
## Retrieve Mercado pago account details
|
25
|
+
You'll need the following account details:
|
26
|
+
|
27
|
+
- Client ID
|
28
|
+
- Client secret
|
29
|
+
- Sandbox (A boolean value to indicate if we're using the sandbox or not)
|
30
|
+
|
31
|
+
These values can be obtained by logging in to your Mercado Pago account, going to `Credentials -> Basic checkout`
|
32
|
+
|
33
|
+
## Create a new payment method
|
34
|
+
|
35
|
+
Payment methods can accept preferences either directly entered in admin.
|
36
|
+
|
37
|
+
1. Visit `/admin/payment_methods/new`
|
38
|
+
2. Set `Mercadopago` as provider.
|
39
|
+
3. Save to update the form
|
40
|
+
4. Set your credentials into the corresponding fields
|
41
|
+
|
42
|
+
By default, your preferences can be gained from your environment vars (`MERCADOPAGO_CLIENT_ID`, `MERCADOPAGO_CLIENT_SECRET`), except by `sandbox`; its default value is `true`.
|
43
|
+
|
44
|
+
|
45
|
+
Other option is create the payment method from admin interface:
|
46
|
+
|
47
|
+
|
48
|
+
Usage
|
49
|
+
-----
|
50
|
+
|
51
|
+
- Add a new payment method in the admin panel of type Spree::PaymentMethod::Mercadopago
|
52
|
+
- After adding the payment method you will be able to configure your Client ID and Client Secret (provided by Mercado Pago).
|
53
|
+
|
54
|
+
IPN
|
55
|
+
---
|
56
|
+
|
57
|
+
For IPN you need to configure the notification URL in Mercado Pago's site. The notification URL will be `http[s]://[your_domain]/mercado_pago/ipn`. Please review Mercado Pago's documentation at http://developers.Mercadopago.com/ for the correct place where to configure IPN notification URLs.
|
58
|
+
|
59
|
+
|
60
|
+
Testing
|
61
|
+
-------
|
62
|
+
|
63
|
+
- Clone this repo
|
64
|
+
- `ln -s docker-compose-{platform}.yml docker-compose.yml`
|
65
|
+
- `docker-sync start` **Only in mac**
|
66
|
+
- `docker-compose up`
|
67
|
+
- `docker-compose run web bundle exec rake test_app` to build a dummy app directory inside specs
|
68
|
+
- `docker-compose run web bundle exec rspec spec`
|
data/Rakefile
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'bundler'
|
4
|
+
|
5
|
+
Bundler::GemHelper.install_tasks
|
6
|
+
|
7
|
+
begin
|
8
|
+
require 'spree/testing_support/extension_rake'
|
9
|
+
require 'rubocop/rake_task'
|
10
|
+
require 'rspec/core/rake_task'
|
11
|
+
|
12
|
+
RSpec::Core::RakeTask.new(:spec)
|
13
|
+
RuboCop::RakeTask.new
|
14
|
+
|
15
|
+
task default: %i(first_run rubocop spec)
|
16
|
+
rescue LoadError
|
17
|
+
# no rspec available
|
18
|
+
end
|
19
|
+
|
20
|
+
task :first_run do
|
21
|
+
if Dir['spec/dummy'].empty?
|
22
|
+
Rake::Task[:test_app].invoke
|
23
|
+
Dir.chdir('../../')
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
desc 'Generates a dummy app for testing'
|
28
|
+
task :test_app do
|
29
|
+
ENV['LIB_NAME'] = 'solidus_mercadopago'
|
30
|
+
Rake::Task['extension:test_app'].invoke
|
31
|
+
end
|
File without changes
|
@@ -0,0 +1,25 @@
|
|
1
|
+
//= require spree/frontend
|
2
|
+
|
3
|
+
Mercadopago = {
|
4
|
+
hidePaymentSaveAndContinueButton: function(paymentMethod) {
|
5
|
+
if (Mercadopago.paymentMethodID && paymentMethod.val() == Mercadopago.paymentMethodID) {
|
6
|
+
$('.continue').hide();
|
7
|
+
$('[data-hook=coupon_code]').hide();
|
8
|
+
} else {
|
9
|
+
$('.continue').show();
|
10
|
+
$('[data-hook=coupon_code]').show();
|
11
|
+
}
|
12
|
+
}
|
13
|
+
};
|
14
|
+
|
15
|
+
$(document).ready(function() {
|
16
|
+
checkedPaymentMethod = $('div[data-hook="checkout_payment_step"] input[type="radio"]:checked');
|
17
|
+
Mercadopago.hidePaymentSaveAndContinueButton(checkedPaymentMethod);
|
18
|
+
paymentMethods = $('div[data-hook="checkout_payment_step"] input[type="radio"]').click(function (e) {
|
19
|
+
Mercadopago.hidePaymentSaveAndContinueButton($(e.target));
|
20
|
+
});
|
21
|
+
|
22
|
+
$('button.mercadopago_button').click(function(event){
|
23
|
+
$(event.target).prop("disabled",true);
|
24
|
+
});
|
25
|
+
});
|
@@ -0,0 +1 @@
|
|
1
|
+
/* Custom backend assets */
|
@@ -0,0 +1 @@
|
|
1
|
+
/* Custom frontend assets */
|
@@ -0,0 +1,70 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
class MercadopagoController < StoreController
|
5
|
+
protect_from_forgery except: :ipn
|
6
|
+
# skip_before_action :set_current_order, only: :ipn
|
7
|
+
|
8
|
+
def checkout
|
9
|
+
current_order.state_name == :payment || raise(ActiveRecord::RecordNotFound)
|
10
|
+
payment_method = PaymentMethod::Mercadopago.find(params[:payment_method_id])
|
11
|
+
payment = current_order.payments
|
12
|
+
.create!(amount: current_order.total, payment_method: payment_method)
|
13
|
+
payment.started_processing!
|
14
|
+
|
15
|
+
preferences = ::Mercadopago::OrderPreferencesBuilder
|
16
|
+
.new(current_order, payment, callback_urls)
|
17
|
+
.preferences_hash
|
18
|
+
|
19
|
+
provider = payment_method.provider
|
20
|
+
provider.create_preferences(preferences)
|
21
|
+
|
22
|
+
redirect_to provider.redirect_url
|
23
|
+
end
|
24
|
+
|
25
|
+
# Success/pending callbacks are currently aliases, this may change
|
26
|
+
# if required.
|
27
|
+
def success
|
28
|
+
payment.order.next
|
29
|
+
flash.notice = Spree.t(:order_processed_successfully)
|
30
|
+
flash['order_completed'] = true
|
31
|
+
redirect_to spree.order_path(payment.order)
|
32
|
+
end
|
33
|
+
|
34
|
+
def failure
|
35
|
+
payment.failure!
|
36
|
+
flash.notice = Spree.t(:payment_processing_failed)
|
37
|
+
flash['order_completed'] = true
|
38
|
+
redirect_to spree.checkout_state_path(state: :payment)
|
39
|
+
end
|
40
|
+
|
41
|
+
def ipn
|
42
|
+
notification = Mercadopago::Notification
|
43
|
+
.new(operation_id: params[:id], topic: params[:topic])
|
44
|
+
|
45
|
+
if notification.save
|
46
|
+
Mercadopago::HandleReceivedNotification.new(notification).process!
|
47
|
+
status = :ok
|
48
|
+
else
|
49
|
+
status = :bad_request
|
50
|
+
end
|
51
|
+
|
52
|
+
render json: :empty, status: status
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
def payment
|
58
|
+
@payment ||= Spree::Payment.where(number: params[:external_reference])
|
59
|
+
.first
|
60
|
+
end
|
61
|
+
|
62
|
+
def callback_urls
|
63
|
+
@callback_urls ||= {
|
64
|
+
success: mercadopago_success_url,
|
65
|
+
pending: mercadopago_success_url,
|
66
|
+
failure: mercadopago_failure_url
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'rest_client'
|
4
|
+
require 'mercadopago/client/authentication'
|
5
|
+
require 'mercadopago/client/preferences'
|
6
|
+
require 'mercadopago/client/api'
|
7
|
+
|
8
|
+
module Mercadopago
|
9
|
+
class Client
|
10
|
+
# These three includes are because of the user of line_item_description from
|
11
|
+
# ProductsHelper
|
12
|
+
include Authentication
|
13
|
+
include Preferences
|
14
|
+
include Api
|
15
|
+
|
16
|
+
attr_reader :errors
|
17
|
+
attr_reader :auth_response
|
18
|
+
attr_reader :preferences_response
|
19
|
+
|
20
|
+
def initialize(payment_method, options = {})
|
21
|
+
@payment_method = payment_method
|
22
|
+
@api_options = options.clone
|
23
|
+
@errors = []
|
24
|
+
end
|
25
|
+
|
26
|
+
def get_operation_info(operation_id)
|
27
|
+
url = create_url(notifications_url(operation_id), access_token: access_token)
|
28
|
+
options = { content_type: 'application/x-www-form-urlencoded', accept: 'application/json' }
|
29
|
+
get(url, options, quiet: true)
|
30
|
+
end
|
31
|
+
|
32
|
+
# def get_external_reference(operation_id)
|
33
|
+
# response = send_notification_request(operation_id)
|
34
|
+
# if response
|
35
|
+
# response['collection']['external_reference']
|
36
|
+
# end
|
37
|
+
# end
|
38
|
+
|
39
|
+
def get_payment_status(external_reference)
|
40
|
+
response = send_search_request(external_reference: external_reference, access_token: access_token)
|
41
|
+
|
42
|
+
if response['results'].empty?
|
43
|
+
'pending'
|
44
|
+
else
|
45
|
+
response['results'][0]['collection']['status']
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
private
|
50
|
+
|
51
|
+
def log_error(msg, response, request, result)
|
52
|
+
Rails.logger.info msg
|
53
|
+
Rails.logger.info "response: #{response}."
|
54
|
+
Rails.logger.info "request args: #{request.args}."
|
55
|
+
Rails.logger.info "result #{result}."
|
56
|
+
end
|
57
|
+
|
58
|
+
def send_search_request(params, _options = {})
|
59
|
+
url = create_url(search_url, params)
|
60
|
+
options = { content_type: 'application/x-www-form-urlencoded', accept: 'application/json' }
|
61
|
+
get(url, options)
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class Mercadopago::Client
|
6
|
+
module Api
|
7
|
+
def redirect_url
|
8
|
+
point_key = sandbox ? 'sandbox_init_point' : 'init_point'
|
9
|
+
@preferences_response[point_key]
|
10
|
+
end
|
11
|
+
|
12
|
+
private
|
13
|
+
|
14
|
+
def notifications_url(operation_id)
|
15
|
+
sandbox_part = sandbox ? 'sandbox/' : ''
|
16
|
+
"https://api.mercadolibre.com/#{sandbox_part}collections/notifications/#{operation_id}"
|
17
|
+
end
|
18
|
+
|
19
|
+
def search_url
|
20
|
+
sandbox_part = sandbox ? 'sandbox/' : ''
|
21
|
+
"https://api.mercadolibre.com/#{sandbox_part}collections/search"
|
22
|
+
end
|
23
|
+
|
24
|
+
def create_url(url, params = {})
|
25
|
+
uri = URI(url)
|
26
|
+
uri.query = URI.encode_www_form(params)
|
27
|
+
uri.to_s
|
28
|
+
end
|
29
|
+
|
30
|
+
def preferences_url(token)
|
31
|
+
create_url('https://api.mercadolibre.com/checkout/preferences', access_token: token)
|
32
|
+
end
|
33
|
+
|
34
|
+
def sandbox
|
35
|
+
@payment_method.preferred_sandbox
|
36
|
+
end
|
37
|
+
|
38
|
+
def get(url, request_options = {}, options = {})
|
39
|
+
response = RestClient.get(url, request_options)
|
40
|
+
JSON.parse(response)
|
41
|
+
# TODO: add class to rescue
|
42
|
+
rescue => e
|
43
|
+
raise e unless options[:quiet]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
@@ -0,0 +1,38 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class Mercadopago::Client
|
6
|
+
module Authentication
|
7
|
+
def authenticate
|
8
|
+
response = send_authentication_request
|
9
|
+
@auth_response = JSON.parse(response)
|
10
|
+
rescue RestClient::Exception => e
|
11
|
+
@errors << I18n.t(:authentication_error, scope: :mercadopago)
|
12
|
+
raise e.message
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def send_authentication_request
|
18
|
+
RestClient.post(
|
19
|
+
'https://api.mercadolibre.com/oauth/token',
|
20
|
+
{ grant_type: 'client_credentials', client_id: client_id, client_secret: client_secret },
|
21
|
+
content_type: 'application/x-www-form-urlencoded', accept: 'application/json'
|
22
|
+
)
|
23
|
+
end
|
24
|
+
|
25
|
+
def client_id
|
26
|
+
@payment_method.preferred_client_id
|
27
|
+
end
|
28
|
+
|
29
|
+
def client_secret
|
30
|
+
@payment_method.preferred_client_secret
|
31
|
+
end
|
32
|
+
|
33
|
+
def access_token
|
34
|
+
authenticate unless @auth_response
|
35
|
+
@auth_response['access_token']
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
class Mercadopago::Client
|
6
|
+
module Preferences
|
7
|
+
def create_preferences(preferences)
|
8
|
+
response = send_preferences_request(preferences)
|
9
|
+
@preferences_response = JSON.parse(response)
|
10
|
+
rescue RestClient::Exception => e
|
11
|
+
@errors << I18n.t(:authentication_error, scope: :mercadopago)
|
12
|
+
raise e.message
|
13
|
+
end
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def send_preferences_request(preferences)
|
18
|
+
RestClient.post(preferences_url(access_token), preferences.to_json,
|
19
|
+
content_type: 'application/json', accept: 'application/json')
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,10 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mercadopago
|
4
|
+
class Notification < ActiveRecord::Base
|
5
|
+
self.table_name = 'mercadopago_notifications'
|
6
|
+
|
7
|
+
validates :topic, presence: true, inclusion: { in: %w[payment preapproval authorized_payment merchant_order] }
|
8
|
+
validates :operation_id, presence: true
|
9
|
+
end
|
10
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mercadopago
|
4
|
+
class OrderPreferencesBuilder
|
5
|
+
include ActionView::Helpers::TextHelper
|
6
|
+
include ActionView::Helpers::SanitizeHelper
|
7
|
+
include Spree::ProductsHelper
|
8
|
+
|
9
|
+
def initialize(order, payment, callback_urls, payer_data = nil)
|
10
|
+
@order = order
|
11
|
+
@payment = payment
|
12
|
+
@callback_urls = callback_urls
|
13
|
+
@payer_data = payer_data
|
14
|
+
end
|
15
|
+
|
16
|
+
def preferences_hash
|
17
|
+
{
|
18
|
+
external_reference: @payment.number,
|
19
|
+
back_urls: @callback_urls,
|
20
|
+
payer: @payer_data,
|
21
|
+
items: generate_items
|
22
|
+
}
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def generate_items
|
28
|
+
items = []
|
29
|
+
|
30
|
+
items += generate_items_from_line_items
|
31
|
+
items += generate_items_from_adjustments
|
32
|
+
items += generate_items_from_shipments
|
33
|
+
|
34
|
+
items
|
35
|
+
end
|
36
|
+
|
37
|
+
def generate_items_from_shipments
|
38
|
+
@order.shipments.collect do |shipment|
|
39
|
+
{
|
40
|
+
title: shipment.shipping_method.name,
|
41
|
+
unit_price: shipment.cost.to_f + shipment.adjustment_total.to_f,
|
42
|
+
quantity: 1
|
43
|
+
}
|
44
|
+
end
|
45
|
+
end
|
46
|
+
|
47
|
+
def generate_items_from_line_items
|
48
|
+
@order.line_items.collect do |line_item|
|
49
|
+
{
|
50
|
+
title: line_item_description_text(line_item.variant.product.name),
|
51
|
+
unit_price: line_item.price.to_f,
|
52
|
+
quantity: line_item.quantity
|
53
|
+
}
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def generate_items_from_adjustments
|
58
|
+
@order.all_adjustments.eligible.collect do |adjustment|
|
59
|
+
{
|
60
|
+
title: line_item_description_text(adjustment.label),
|
61
|
+
unit_price: adjustment.amount.to_f,
|
62
|
+
quantity: 1
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
@@ -0,0 +1,48 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Spree
|
4
|
+
class PaymentMethod::Mercadopago < PaymentMethod
|
5
|
+
preference :sandbox, :boolean, default: true
|
6
|
+
preference :client_id, :string, default: ENV['Mercadopago_CLIENT_ID']
|
7
|
+
preference :client_secret, :string, default: ENV['Mercadopago_CLIENT_SECRET']
|
8
|
+
|
9
|
+
def payment_profiles_supported?
|
10
|
+
false
|
11
|
+
end
|
12
|
+
|
13
|
+
def provider_class
|
14
|
+
::Mercadopago::Client
|
15
|
+
end
|
16
|
+
|
17
|
+
def provider(additional_options = {})
|
18
|
+
@provider ||= begin
|
19
|
+
options = { sandbox: preferred_sandbox }
|
20
|
+
client = provider_class.new(self, options.merge(additional_options))
|
21
|
+
client.authenticate
|
22
|
+
client
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
def source_required?
|
27
|
+
false
|
28
|
+
end
|
29
|
+
|
30
|
+
def auto_capture?
|
31
|
+
false
|
32
|
+
end
|
33
|
+
|
34
|
+
## Admin options
|
35
|
+
|
36
|
+
def can_void?(payment)
|
37
|
+
payment.state != 'void'
|
38
|
+
end
|
39
|
+
|
40
|
+
def actions
|
41
|
+
%w[void]
|
42
|
+
end
|
43
|
+
|
44
|
+
def void(*_args)
|
45
|
+
ActiveMerchant::Billing::Response.new(true, '', {}, {})
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Mercadopago
|
4
|
+
class HandleReceivedNotification
|
5
|
+
def initialize(notification)
|
6
|
+
@notification = notification
|
7
|
+
end
|
8
|
+
|
9
|
+
# The purpose of this method is to enable async/sync processing
|
10
|
+
# of Mercado Pago IPNs. For simplicity processing is synchronous but
|
11
|
+
# if you would like to enqueue the processing via Resque/Ost/etc you
|
12
|
+
# will be able to do it.
|
13
|
+
def process!
|
14
|
+
# Sync
|
15
|
+
ProcessNotification.new(@notification).process!
|
16
|
+
# Async Will be configurable via block for example:
|
17
|
+
# Resque.enqueue(ProcessNotificationWorker, {id: @notification.id})
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Process notification:
|
4
|
+
# ---------------------
|
5
|
+
# Fetch collection information
|
6
|
+
# Find payment by external reference
|
7
|
+
# If found
|
8
|
+
# Update payment status
|
9
|
+
# Notify user
|
10
|
+
# If not found
|
11
|
+
# Ignore notification (maybe payment from outside Spree)
|
12
|
+
module Mercadopago
|
13
|
+
class ProcessNotification
|
14
|
+
# Equivalent payment states
|
15
|
+
# MP state => Spree state
|
16
|
+
# =======================
|
17
|
+
#
|
18
|
+
# approved => complete
|
19
|
+
# pending => pend
|
20
|
+
# in_process => pend
|
21
|
+
# rejected => failed
|
22
|
+
# refunded => void
|
23
|
+
# cancelled => void
|
24
|
+
# in_mediation => pend
|
25
|
+
# charged_back => void
|
26
|
+
STATES = {
|
27
|
+
complete: %w[approved],
|
28
|
+
failure: %w[rejected],
|
29
|
+
void: %w[refunded cancelled charged_back]
|
30
|
+
}.freeze
|
31
|
+
|
32
|
+
attr_reader :notification
|
33
|
+
|
34
|
+
def initialize(notification)
|
35
|
+
@notification = notification
|
36
|
+
end
|
37
|
+
|
38
|
+
def process!
|
39
|
+
# Fix: Payment method is an instance of Spree::PaymentMethod::Mercadopago not THE class
|
40
|
+
client = ::Spree::PaymentMethod::Mercadopago.first.provider
|
41
|
+
raw_op_info = client.get_operation_info(notification.operation_id)
|
42
|
+
op_info = raw_op_info['collection'] if raw_op_info.present?
|
43
|
+
# TODO: rewrite this.
|
44
|
+
if op_info && (payment = Spree::Payment.where(number: op_info['external_reference']).first)
|
45
|
+
if STATES[:complete].include?(op_info['status'])
|
46
|
+
payment.complete
|
47
|
+
elsif STATES[:failure].include?(op_info['status'])
|
48
|
+
payment.failure
|
49
|
+
elsif STATES[:void].include?(op_info['status'])
|
50
|
+
payment.void
|
51
|
+
end
|
52
|
+
|
53
|
+
# When Spree issue #5246 is fixed we can remove this line
|
54
|
+
payment.order.updater.update
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -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: :mercadopago), mercadopago_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,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
Spree::Core::Engine.routes.draw do
|
4
|
+
post '/mercadopago/checkout', to: 'mercadopago#checkout', as: :mercadopago_checkout
|
5
|
+
get '/mercadopago/success', to: 'mercadopago#success', as: :mercadopago_success
|
6
|
+
get '/mercadopago/failure', to: 'mercadopago#failure', as: :mercadopago_failure
|
7
|
+
post '/mercadopago/ipn', to: 'mercadopago#ipn', as: :mercadopago_ipn
|
8
|
+
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_mercadopago\n"
|
12
|
+
append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/solidus_mercadopago\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_mercadopago\n", before: /\*\//, verbose: true
|
22
|
+
inject_into_file backend_css_file, " *= require spree/backend/solidus_mercadopago\n", before: /\*\//, verbose: true
|
23
|
+
end
|
24
|
+
|
25
|
+
def add_migrations
|
26
|
+
if !options[:auto_skip_migrations]
|
27
|
+
run 'bundle exec rake solidus_mercadopago:install:migrations'
|
28
|
+
else
|
29
|
+
puts 'Skipping rake solidus_mercadopago: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,21 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'spree/core'
|
4
|
+
|
5
|
+
module SolidusMercadopago
|
6
|
+
class Engine < Rails::Engine
|
7
|
+
include SolidusSupport::EngineExtensions
|
8
|
+
|
9
|
+
isolate_namespace ::Spree
|
10
|
+
engine_name 'solidus_mercadopago'
|
11
|
+
|
12
|
+
# use rspec for tests
|
13
|
+
config.generators do |g|
|
14
|
+
g.test_framework :rspec
|
15
|
+
end
|
16
|
+
|
17
|
+
initializer 'spree_payment_network.register.payment_methods' do |app|
|
18
|
+
app.config.spree.payment_methods << Spree::PaymentMethod::Mercadopago
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
FactoryBot.define do
|
4
|
+
# Define your Spree extensions Factories within this file to enable applications, and other extensions to use and override them.
|
5
|
+
#
|
6
|
+
# Example adding this to your spec_helper will load these Factories for use:
|
7
|
+
# require 'solidus_mercadopago/factories'
|
8
|
+
end
|
metadata
ADDED
@@ -0,0 +1,227 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: solidus_mercadopago
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 1.0.0
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Jonathan Tapia
|
8
|
+
autorequire:
|
9
|
+
bindir: bin
|
10
|
+
cert_chain: []
|
11
|
+
date: 2020-06-07 00:00:00.000000000 Z
|
12
|
+
dependencies:
|
13
|
+
- !ruby/object:Gem::Dependency
|
14
|
+
name: rest-client
|
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
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
30
|
+
requirements:
|
31
|
+
- - ">="
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '2.0'
|
34
|
+
- - "<"
|
35
|
+
- !ruby/object:Gem::Version
|
36
|
+
version: '3'
|
37
|
+
type: :runtime
|
38
|
+
prerelease: false
|
39
|
+
version_requirements: !ruby/object:Gem::Requirement
|
40
|
+
requirements:
|
41
|
+
- - ">="
|
42
|
+
- !ruby/object:Gem::Version
|
43
|
+
version: '2.0'
|
44
|
+
- - "<"
|
45
|
+
- !ruby/object:Gem::Version
|
46
|
+
version: '3'
|
47
|
+
- !ruby/object:Gem::Dependency
|
48
|
+
name: solidus_auth_devise
|
49
|
+
requirement: !ruby/object:Gem::Requirement
|
50
|
+
requirements:
|
51
|
+
- - ">="
|
52
|
+
- !ruby/object:Gem::Version
|
53
|
+
version: '2.0'
|
54
|
+
- - "<"
|
55
|
+
- !ruby/object:Gem::Version
|
56
|
+
version: '3'
|
57
|
+
type: :runtime
|
58
|
+
prerelease: false
|
59
|
+
version_requirements: !ruby/object:Gem::Requirement
|
60
|
+
requirements:
|
61
|
+
- - ">="
|
62
|
+
- !ruby/object:Gem::Version
|
63
|
+
version: '2.0'
|
64
|
+
- - "<"
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '3'
|
67
|
+
- !ruby/object:Gem::Dependency
|
68
|
+
name: solidus_support
|
69
|
+
requirement: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">="
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: '0'
|
74
|
+
type: :runtime
|
75
|
+
prerelease: false
|
76
|
+
version_requirements: !ruby/object:Gem::Requirement
|
77
|
+
requirements:
|
78
|
+
- - ">="
|
79
|
+
- !ruby/object:Gem::Version
|
80
|
+
version: '0'
|
81
|
+
- !ruby/object:Gem::Dependency
|
82
|
+
name: deface
|
83
|
+
requirement: !ruby/object:Gem::Requirement
|
84
|
+
requirements:
|
85
|
+
- - "~>"
|
86
|
+
- !ruby/object:Gem::Version
|
87
|
+
version: '1.0'
|
88
|
+
type: :runtime
|
89
|
+
prerelease: false
|
90
|
+
version_requirements: !ruby/object:Gem::Requirement
|
91
|
+
requirements:
|
92
|
+
- - "~>"
|
93
|
+
- !ruby/object:Gem::Version
|
94
|
+
version: '1.0'
|
95
|
+
- !ruby/object:Gem::Dependency
|
96
|
+
name: coffee-rails
|
97
|
+
requirement: !ruby/object:Gem::Requirement
|
98
|
+
requirements:
|
99
|
+
- - ">="
|
100
|
+
- !ruby/object:Gem::Version
|
101
|
+
version: '0'
|
102
|
+
type: :development
|
103
|
+
prerelease: false
|
104
|
+
version_requirements: !ruby/object:Gem::Requirement
|
105
|
+
requirements:
|
106
|
+
- - ">="
|
107
|
+
- !ruby/object:Gem::Version
|
108
|
+
version: '0'
|
109
|
+
- !ruby/object:Gem::Dependency
|
110
|
+
name: capybara-accessible
|
111
|
+
requirement: !ruby/object:Gem::Requirement
|
112
|
+
requirements:
|
113
|
+
- - ">="
|
114
|
+
- !ruby/object:Gem::Version
|
115
|
+
version: '0'
|
116
|
+
type: :development
|
117
|
+
prerelease: false
|
118
|
+
version_requirements: !ruby/object:Gem::Requirement
|
119
|
+
requirements:
|
120
|
+
- - ">="
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
- !ruby/object:Gem::Dependency
|
124
|
+
name: poltergeist
|
125
|
+
requirement: !ruby/object:Gem::Requirement
|
126
|
+
requirements:
|
127
|
+
- - ">="
|
128
|
+
- !ruby/object:Gem::Version
|
129
|
+
version: '0'
|
130
|
+
type: :development
|
131
|
+
prerelease: false
|
132
|
+
version_requirements: !ruby/object:Gem::Requirement
|
133
|
+
requirements:
|
134
|
+
- - ">="
|
135
|
+
- !ruby/object:Gem::Version
|
136
|
+
version: '0'
|
137
|
+
- !ruby/object:Gem::Dependency
|
138
|
+
name: solidus_dev_support
|
139
|
+
requirement: !ruby/object:Gem::Requirement
|
140
|
+
requirements:
|
141
|
+
- - ">="
|
142
|
+
- !ruby/object:Gem::Version
|
143
|
+
version: '0'
|
144
|
+
type: :development
|
145
|
+
prerelease: false
|
146
|
+
version_requirements: !ruby/object:Gem::Requirement
|
147
|
+
requirements:
|
148
|
+
- - ">="
|
149
|
+
- !ruby/object:Gem::Version
|
150
|
+
version: '0'
|
151
|
+
- !ruby/object:Gem::Dependency
|
152
|
+
name: webmock
|
153
|
+
requirement: !ruby/object:Gem::Requirement
|
154
|
+
requirements:
|
155
|
+
- - ">="
|
156
|
+
- !ruby/object:Gem::Version
|
157
|
+
version: '0'
|
158
|
+
type: :development
|
159
|
+
prerelease: false
|
160
|
+
version_requirements: !ruby/object:Gem::Requirement
|
161
|
+
requirements:
|
162
|
+
- - ">="
|
163
|
+
- !ruby/object:Gem::Version
|
164
|
+
version: '0'
|
165
|
+
description: Integrates Mercado Pago with Solidus
|
166
|
+
email: jonathan.tapia@magmalabs.io
|
167
|
+
executables: []
|
168
|
+
extensions: []
|
169
|
+
extra_rdoc_files: []
|
170
|
+
files:
|
171
|
+
- LICENSE
|
172
|
+
- README.md
|
173
|
+
- Rakefile
|
174
|
+
- app/assets/javascripts/spree/backend/solidus_mercado_pago.js
|
175
|
+
- app/assets/javascripts/spree/frontend/solidus_mercado_pago.js
|
176
|
+
- app/assets/stylesheets/spree/backend/solidus_mercado_pago.css
|
177
|
+
- app/assets/stylesheets/spree/frontend/solidus_mercado_pago.css
|
178
|
+
- app/controllers/spree/mercadopago_controller.rb
|
179
|
+
- app/models/mercadopago/client.rb
|
180
|
+
- app/models/mercadopago/client/api.rb
|
181
|
+
- app/models/mercadopago/client/authentication.rb
|
182
|
+
- app/models/mercadopago/client/preferences.rb
|
183
|
+
- app/models/mercadopago/notification.rb
|
184
|
+
- app/models/mercadopago/order_preferences_builder.rb
|
185
|
+
- app/models/spree/payment_method/mercadopago.rb
|
186
|
+
- app/services/mercadopago/handle_received_notification.rb
|
187
|
+
- app/services/mercadopago/process_notification.rb
|
188
|
+
- app/views/spree/admin/payments/source_forms/_mercadopago.html.erb
|
189
|
+
- app/views/spree/admin/payments/source_views/_mercadopago.html.erb
|
190
|
+
- app/views/spree/checkout/mercado_pago_error.html.erb
|
191
|
+
- app/views/spree/checkout/payment/_mercadopago.html.erb
|
192
|
+
- config/locales/en.yml
|
193
|
+
- config/locales/es.yml
|
194
|
+
- config/locales/pt-BR.yml
|
195
|
+
- config/routes.rb
|
196
|
+
- db/migrate/20141201204026_create_solidus_mercadopago_notifications.rb
|
197
|
+
- lib/generators/solidus_mercadopago/install/install_generator.rb
|
198
|
+
- lib/solidus_mercadopago.rb
|
199
|
+
- lib/solidus_mercadopago/engine.rb
|
200
|
+
- lib/solidus_mercadopago/factories.rb
|
201
|
+
- lib/solidus_mercadopago/version.rb
|
202
|
+
- lib/tasks/mercado_user.rake
|
203
|
+
homepage: http://github.com/magma-labs/solidus_mercadopago
|
204
|
+
licenses:
|
205
|
+
- BSD-3-Clause
|
206
|
+
metadata: {}
|
207
|
+
post_install_message:
|
208
|
+
rdoc_options: []
|
209
|
+
require_paths:
|
210
|
+
- lib
|
211
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
212
|
+
requirements:
|
213
|
+
- - ">="
|
214
|
+
- !ruby/object:Gem::Version
|
215
|
+
version: '0'
|
216
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
217
|
+
requirements:
|
218
|
+
- - ">="
|
219
|
+
- !ruby/object:Gem::Version
|
220
|
+
version: '0'
|
221
|
+
requirements: []
|
222
|
+
rubyforge_project:
|
223
|
+
rubygems_version: 2.7.6
|
224
|
+
signing_key:
|
225
|
+
specification_version: 4
|
226
|
+
summary: Solidus plugin to integrate Mercado Pago
|
227
|
+
test_files: []
|