solidus_mercadopago 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/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: []
|