Modified to work with Spree 0.30.1.
This commit is contained in:
parent
898b6d4bb3
commit
b79fb41c4d
|
@ -0,0 +1,23 @@
|
||||||
|
Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
are permitted provided that the following conditions are met:
|
||||||
|
|
||||||
|
* Redistributions of source code must retain the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer.
|
||||||
|
* Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
this list of conditions and the following disclaimer in the documentation
|
||||||
|
and/or other materials provided with the distribution.
|
||||||
|
* Neither the name of the Rails Dog LLC nor the names of its
|
||||||
|
contributors may be used to endorse or promote products derived from this
|
||||||
|
software without specific prior written permission.
|
||||||
|
|
||||||
|
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
|
||||||
|
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
|
||||||
|
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
|
||||||
|
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
|
||||||
|
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
|
||||||
|
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||||
|
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
129
Rakefile
129
Rakefile
|
@ -1,120 +1,31 @@
|
||||||
# I think this is the one that should be moved to the extension Rakefile template
|
require File.expand_path('../../config/application', __FILE__)
|
||||||
|
|
||||||
# In rails 1.2, plugins aren't available in the path until they're loaded.
|
|
||||||
# Check to see if the rspec plugin is installed first and require
|
|
||||||
# it if it is. If not, use the gem version.
|
|
||||||
|
|
||||||
# Determine where the RSpec plugin is by loading the boot
|
|
||||||
unless defined? SPREE_ROOT
|
|
||||||
ENV["RAILS_ENV"] = "test"
|
|
||||||
case
|
|
||||||
when ENV["SPREE_ENV_FILE"]
|
|
||||||
require File.dirname(ENV["SPREE_ENV_FILE"]) + "/boot"
|
|
||||||
when File.dirname(__FILE__) =~ %r{vendor/SPREE/vendor/extensions}
|
|
||||||
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../../../")}/config/boot"
|
|
||||||
else
|
|
||||||
require "#{File.expand_path(File.dirname(__FILE__) + "/../../../")}/config/boot"
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
|
require 'rubygems'
|
||||||
require 'rake'
|
require 'rake'
|
||||||
require 'rake/rdoctask'
|
|
||||||
require 'rake/testtask'
|
require 'rake/testtask'
|
||||||
|
require 'rake/packagetask'
|
||||||
|
require 'rake/gempackagetask'
|
||||||
|
|
||||||
rspec_base = File.expand_path(SPREE_ROOT + '/vendor/plugins/rspec/lib')
|
spec = eval(File.read('spree_paypal_express.gemspec'))
|
||||||
$LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
|
|
||||||
require 'spec/rake/spectask'
|
|
||||||
# require 'spec/translator'
|
|
||||||
|
|
||||||
# Cleanup the SPREE_ROOT constant so specs will load the environment
|
Rake::GemPackageTask.new(spec) do |p|
|
||||||
Object.send(:remove_const, :SPREE_ROOT)
|
p.gem_spec = spec
|
||||||
|
|
||||||
extension_root = File.expand_path(File.dirname(__FILE__))
|
|
||||||
|
|
||||||
task :default => :spec
|
|
||||||
task :stats => "spec:statsetup"
|
|
||||||
|
|
||||||
desc "Run all specs in spec directory"
|
|
||||||
Spec::Rake::SpecTask.new(:spec) do |t|
|
|
||||||
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
|
||||||
t.spec_files = FileList["#{extension_root}/spec/**/*_spec.rb"]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
namespace :spec do
|
desc "Release to gemcutter"
|
||||||
desc "Run all specs in spec directory with RCov"
|
task :release => :package do
|
||||||
Spec::Rake::SpecTask.new(:rcov) do |t|
|
require 'rake/gemcutter'
|
||||||
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
Rake::Gemcutter::Tasks.new(spec).define
|
||||||
t.spec_files = FileList['spec/**/*_spec.rb']
|
Rake::Task['gem:push'].invoke
|
||||||
t.rcov = true
|
|
||||||
t.rcov_opts = ['--exclude', 'spec', '--rails']
|
|
||||||
end
|
|
||||||
|
|
||||||
desc "Print Specdoc for all specs"
|
|
||||||
Spec::Rake::SpecTask.new(:doc) do |t|
|
|
||||||
t.spec_opts = ["--format", "specdoc", "--dry-run"]
|
|
||||||
t.spec_files = FileList['spec/**/*_spec.rb']
|
|
||||||
end
|
|
||||||
|
|
||||||
[:models, :controllers, :views, :helpers].each do |sub|
|
|
||||||
desc "Run the specs under spec/#{sub}"
|
|
||||||
Spec::Rake::SpecTask.new(sub) do |t|
|
|
||||||
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
|
||||||
t.spec_files = FileList["spec/#{sub}/**/*_spec.rb"]
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
# Hopefully no one has written their extensions in pre-0.9 style
|
|
||||||
# desc "Translate specs from pre-0.9 to 0.9 style"
|
|
||||||
# task :translate do
|
|
||||||
# translator = ::Spec::Translator.new
|
|
||||||
# dir = RAILS_ROOT + '/spec'
|
|
||||||
# translator.translate(dir, dir)
|
|
||||||
# end
|
|
||||||
|
|
||||||
# Setup specs for stats
|
|
||||||
task :statsetup do
|
|
||||||
require 'code_statistics'
|
|
||||||
::STATS_DIRECTORIES << %w(Model\ specs spec/models)
|
|
||||||
::STATS_DIRECTORIES << %w(View\ specs spec/views)
|
|
||||||
::STATS_DIRECTORIES << %w(Controller\ specs spec/controllers)
|
|
||||||
::STATS_DIRECTORIES << %w(Helper\ specs spec/views)
|
|
||||||
::CodeStatistics::TEST_TYPES << "Model specs"
|
|
||||||
::CodeStatistics::TEST_TYPES << "View specs"
|
|
||||||
::CodeStatistics::TEST_TYPES << "Controller specs"
|
|
||||||
::CodeStatistics::TEST_TYPES << "Helper specs"
|
|
||||||
::STATS_DIRECTORIES.delete_if {|a| a[0] =~ /test/}
|
|
||||||
end
|
|
||||||
|
|
||||||
namespace :db do
|
|
||||||
namespace :fixtures do
|
|
||||||
desc "Load fixtures (from spec/fixtures) into the current environment's database. Load specific fixtures using FIXTURES=x,y"
|
|
||||||
task :load => :environment do
|
|
||||||
require 'active_record/fixtures'
|
|
||||||
ActiveRecord::Base.establish_connection(RAILS_ENV.to_sym)
|
|
||||||
(ENV['FIXTURES'] ? ENV['FIXTURES'].split(/,/) : Dir.glob(File.join(RAILS_ROOT, 'spec', 'fixtures', '*.{yml,csv}'))).each do |fixture_file|
|
|
||||||
Fixtures.create_fixtures('spec/fixtures', File.basename(fixture_file, '.*'))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
end
|
||||||
|
|
||||||
desc 'Generate documentation for the paypal_express extension.'
|
desc "Default Task"
|
||||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
task :default => [ :spec ]
|
||||||
rdoc.rdoc_dir = 'rdoc'
|
|
||||||
rdoc.title = 'PaypalExpressExtension'
|
|
||||||
rdoc.options << '--line-numbers' << '--inline-source'
|
|
||||||
rdoc.rdoc_files.include('README')
|
|
||||||
rdoc.rdoc_files.include('lib/**/*.rb')
|
|
||||||
end
|
|
||||||
|
|
||||||
# For extensions that are in transition
|
require 'rspec/core/rake_task'
|
||||||
desc 'Test the paypal_express extension.'
|
RSpec::Core::RakeTask.new
|
||||||
Rake::TestTask.new(:test) do |t|
|
|
||||||
t.libs << 'lib'
|
|
||||||
t.pattern = 'test/**/*_test.rb'
|
|
||||||
t.verbose = true
|
|
||||||
end
|
|
||||||
|
|
||||||
# Load any custom rakefiles for extension
|
# require 'cucumber/rake/task'
|
||||||
Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
|
# Cucumber::Rake::Task.new do |t|
|
||||||
|
# t.cucumber_opts = %w{--format pretty}
|
||||||
|
# end
|
|
@ -8,23 +8,10 @@ class PaypalAccount < ActiveRecord::Base
|
||||||
def capture(payment)
|
def capture(payment)
|
||||||
authorization = find_authorization(payment)
|
authorization = find_authorization(payment)
|
||||||
|
|
||||||
ppx_response = payment.payment_method.provider.capture((100 * payment.amount).to_i, authorization.transaction_id)
|
ppx_response = payment.payment_method.provider.capture((100 * payment.amount).to_i, authorization.params["transaction_id"])
|
||||||
if ppx_response.success?
|
if ppx_response.success?
|
||||||
PaypalTxn.create(:payment => payment,
|
record_log payment, ppx_response
|
||||||
:txn_type => PaypalTxn::TxnType::CAPTURE,
|
payment.complete
|
||||||
:amount => ppx_response.params["gross_amount"].to_f,
|
|
||||||
:message => ppx_response.params["message"],
|
|
||||||
:payment_status => ppx_response.params["payment_status"],
|
|
||||||
:pending_reason => ppx_response.params["pending_reason"],
|
|
||||||
:transaction_id => ppx_response.params["transaction_id"],
|
|
||||||
:transaction_type => ppx_response.params["transaction_type"],
|
|
||||||
:payment_type => ppx_response.params["payment_type"],
|
|
||||||
:response_code => ppx_response.params["ack"],
|
|
||||||
:token => ppx_response.params["token"],
|
|
||||||
:avs_response => ppx_response.avs_result["code"],
|
|
||||||
:cvv_response => ppx_response.cvv_result["code"])
|
|
||||||
|
|
||||||
payment.finalize!
|
|
||||||
else
|
else
|
||||||
gateway_error(ppx_response.message)
|
gateway_error(ppx_response.message)
|
||||||
end
|
end
|
||||||
|
@ -32,37 +19,30 @@ class PaypalAccount < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_capture?(payment)
|
def can_capture?(payment)
|
||||||
!echeck?(payment) && find_capture(payment).nil?
|
!echeck?(payment) && payment.state == "pending"
|
||||||
end
|
end
|
||||||
|
|
||||||
def credit(payment, amount=nil)
|
def credit(payment, amount=nil)
|
||||||
authorization = find_capture(payment)
|
authorization = find_capture(payment)
|
||||||
amount ||= payment.order.outstanding_credit
|
|
||||||
ppx_response = payment.payment_method.provider.credit(amount.nil? ? (100 * amount).to_i : (100 * amount).to_i, authorization.transaction_id)
|
amount = payment.credit_allowed >= payment.order.outstanding_balance.abs ? payment.order.outstanding_balance : payment.credit_allowed
|
||||||
|
|
||||||
|
ppx_response = payment.payment_method.provider.credit(amount.nil? ? (100 * amount).to_i : (100 * amount).to_i, authorization.params['transaction_id'])
|
||||||
|
|
||||||
if ppx_response.success?
|
if ppx_response.success?
|
||||||
PaypalTxn.new(:payment => payment,
|
record_log payment, ppx_response
|
||||||
:txn_type => PaypalTxn::TxnType::CREDIT,
|
|
||||||
:amount => ppx_response.params["gross_refund_amount"].to_f,
|
|
||||||
:message => ppx_response.params["message"],
|
|
||||||
:payment_status => "Refunded",
|
|
||||||
:pending_reason => ppx_response.params["pending_reason"],
|
|
||||||
:transaction_id => ppx_response.params["refund_transaction_id"],
|
|
||||||
:transaction_type => ppx_response.params["transaction_type"],
|
|
||||||
:payment_type => ppx_response.params["payment_type"],
|
|
||||||
:response_code => ppx_response.params["ack"],
|
|
||||||
:token => ppx_response.params["token"],
|
|
||||||
:avs_response => ppx_response.avs_result["code"],
|
|
||||||
:cvv_response => ppx_response.cvv_result["code"])
|
|
||||||
|
|
||||||
payment.update_attribute(:amount, payment.amount - amount)
|
payment.update_attribute(:amount, payment.amount - amount)
|
||||||
payment.order.update_totals!
|
payment.complete
|
||||||
|
payment.order.update!
|
||||||
else
|
else
|
||||||
gateway_error(ppx_response.message)
|
gateway_error(ppx_response.message)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def can_credit?(payment)
|
def can_credit?(payment)
|
||||||
|
return false unless payment.state == "completed"
|
||||||
|
return false unless payment.order.payment_state == "credit_owed"
|
||||||
|
payment.credit_allowed > 0
|
||||||
!find_capture(payment).nil?
|
!find_capture(payment).nil?
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -71,23 +51,43 @@ class PaypalAccount < ActiveRecord::Base
|
||||||
false
|
false
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def record_log(payment, response)
|
||||||
|
payment.log_entries.create(:details => response.to_yaml)
|
||||||
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
def find_authorization(payment)
|
def find_authorization(payment)
|
||||||
#find the transaction associated with the original authorization/capture
|
logs = payment.log_entries.all(:order => 'created_at DESC')
|
||||||
payment.txns.find(:first,
|
logs.each do |log|
|
||||||
:conditions => {:pending_reason => "authorization", :payment_status => "Pending", :txn_type => PaypalTxn::TxnType::AUTHORIZE.to_s},
|
details = YAML.load(log.details) # return the transaction details
|
||||||
:order => 'created_at DESC')
|
if (details.params['payment_status'] == 'Pending' && details.params['pending_reason'] == 'authorization')
|
||||||
|
return details
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def find_capture(payment)
|
def find_capture(payment)
|
||||||
#find the transaction associated with the original authorization/capture
|
#find the transaction associated with the original authorization/capture
|
||||||
payment.txns.find(:first,
|
logs = payment.log_entries.all(:order => 'created_at DESC')
|
||||||
:conditions => {:payment_status => "Completed", :txn_type => PaypalTxn::TxnType::CAPTURE.to_s},
|
logs.each do |log|
|
||||||
:order => 'created_at DESC')
|
details = YAML.load(log.details) # return the transaction details
|
||||||
|
if details.params['payment_status'] == 'Completed'
|
||||||
|
return details
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return nil
|
||||||
end
|
end
|
||||||
|
|
||||||
def echeck?(payment)
|
def echeck?(payment)
|
||||||
payment.txns.exists?(:payment_type => "echeck")
|
logs = payment.log_entries.all(:order => 'created_at DESC')
|
||||||
|
logs.each do |log|
|
||||||
|
details = YAML.load(log.details) # return the transaction details
|
||||||
|
if details.params['payment_type'] == 'echeck'
|
||||||
|
return true
|
||||||
|
end
|
||||||
|
end
|
||||||
|
return false
|
||||||
end
|
end
|
||||||
|
|
||||||
def gateway_error(text)
|
def gateway_error(text)
|
||||||
|
|
|
@ -31,40 +31,41 @@
|
||||||
<fieldset>
|
<fieldset>
|
||||||
<legend><%= t('transactions') %></legend>
|
<legend><%= t('transactions') %></legend>
|
||||||
|
|
||||||
<% payment.txns.reverse.each do |txn| %>
|
<% payment.log_entries.reverse.each do |log| %>
|
||||||
|
<% details = YAML.load(log.details) %>
|
||||||
<table class="index">
|
<table class="index">
|
||||||
<tr>
|
<tr>
|
||||||
<th colspan="6"><%= t('transaction') %> <%= txn.transaction_id %> - <%= txn.created_at.to_s(:date_time24) %></th>
|
<th colspan="6"><%= t('transaction') %> <%= details.params["transaction_id"] %> - <%= log.created_at.to_s(:date_time24) %></th>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td width="12%;"><label><%= t('type') %>:</label></td>
|
<td width="12%;"><label><%= t('type') %>:</label></td>
|
||||||
<td width="20%;">
|
<td width="20%;">
|
||||||
<%= txn.txn_type_name %>
|
<%= details.params["transaction_type"] %>
|
||||||
</td>
|
</td>
|
||||||
<td width="8%;"><label><%= t("result") %>:</label></td>
|
<td width="8%;"><label><%= t("result") %>:</label></td>
|
||||||
<td width="20%;">
|
<td width="20%;">
|
||||||
<%= txn.response_code %>
|
<%= details.message %>
|
||||||
</td>
|
</td>
|
||||||
<td width="15%;"><label><%= t("amount") %>:</label></td>
|
<td width="15%;"><label><%= t("amount") %>:</label></td>
|
||||||
<td width="20%;">
|
<td width="20%;">
|
||||||
<%= number_to_currency txn.amount %>
|
<%= number_to_currency details.params["gross_amount"] %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td><label><%= t("comment") %>:</label></td>
|
<td><label><%= t("comment") %>:</label></td>
|
||||||
<td colspan="3">
|
<td colspan="3">
|
||||||
<%= txn.message %>
|
<%= details.params["message"] %>
|
||||||
</td>
|
</td>
|
||||||
<td><label><%= t("status") %>:</label></td>
|
<td><label><%= t("status") %>:</label></td>
|
||||||
<td>
|
<td>
|
||||||
<%= txn.payment_status %>
|
<%= details.params["payment_status"] %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% if txn.payment_status == "Pending" %>
|
<% if details.params["payment_status"] == "Pending" %>
|
||||||
<tr>
|
<tr>
|
||||||
<td><label><%= t("pending_reason") %>:</label></td>
|
<td><label><%= t("pending_reason") %>:</label></td>
|
||||||
<td colspan="5">
|
<td colspan="5">
|
||||||
<%= txn.pending_reason %>
|
<%= details.params["pending_reason"] %>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
<h1><%= t("confirm") %></h1>
|
<h1><%= t("confirm") %></h1>
|
||||||
<p>
|
<p>
|
||||||
<%= t("order_not_yet_placed") %>
|
<%= raw t("order_not_yet_placed") %>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<%= render :partial => 'shared/order_details', :locals => {:order => @order} -%>
|
<%= render :partial => 'shared/order_details', :locals => {:order => @order} -%>
|
||||||
<div class="form-buttons">
|
<div class="form-buttons">
|
||||||
<%= button_to t('place_order'), paypal_finish_order_checkout_url(@checkout.order, {:token => params[:token] , :PayerID => params[:PayerID], :payment_method_id =>
|
<%= button_to t('place_order'), paypal_finish_order_checkout_url(@order, {:token => params[:token] , :PayerID => params[:PayerID], :payment_method_id =>
|
||||||
params[:payment_method_id] } ), :class => "button primary" %>
|
params[:payment_method_id] } ), :class => "button primary" %>
|
||||||
</div>
|
</div>
|
|
@ -6,7 +6,7 @@ en-GB:
|
||||||
paypal_capture_complete: Paypal Transaction has been captured.
|
paypal_capture_complete: Paypal Transaction has been captured.
|
||||||
unable_to_capture_paypal: Unable to capture Paypal Transaction.
|
unable_to_capture_paypal: Unable to capture Paypal Transaction.
|
||||||
signature: Signature
|
signature: Signature
|
||||||
order_not_yet_placed: "Your order has <strong>not</strong> been be placed, please review the details and click Confirm below to finalise your order."
|
order_not_yet_placed: "Your order has <strong>NOT</strong> been be placed, please review the details and click <strong>Place Order</strong> below to finalize your order."
|
||||||
paypal_payment_id: PayPal Payment ID
|
paypal_payment_id: PayPal Payment ID
|
||||||
pending_reason: Pending Reason
|
pending_reason: Pending Reason
|
||||||
result: Result
|
result: Result
|
||||||
|
|
|
@ -6,7 +6,7 @@ en:
|
||||||
paypal_capture_complete: Paypal Transaction has been captured.
|
paypal_capture_complete: Paypal Transaction has been captured.
|
||||||
unable_to_capture_paypal: Unable to capture Paypal Transaction.
|
unable_to_capture_paypal: Unable to capture Paypal Transaction.
|
||||||
signature: Signature
|
signature: Signature
|
||||||
order_not_yet_placed: "Your order has <strong>not</strong> been be placed, please review the details and click Confirm below to finalise your order."
|
order_not_yet_placed: "Your order has <strong>NOT</strong> been be placed, please review the details and click <strong>Place Order</strong> below to finalize your order."
|
||||||
paypal_payment_id: PayPal Payment ID
|
paypal_payment_id: PayPal Payment ID
|
||||||
pending_reason: Pending Reason
|
pending_reason: Pending Reason
|
||||||
result: Result
|
result: Result
|
||||||
|
|
|
@ -1,14 +1,26 @@
|
||||||
# Put your extension routes here.
|
Rails.application.routes.draw do
|
||||||
|
resources :orders do
|
||||||
|
resource :checkout, :controller => 'checkout' do
|
||||||
|
member do
|
||||||
|
get :paypal_checkout
|
||||||
|
get :paypal_payment
|
||||||
|
get :paypal_confirm
|
||||||
|
post :paypal_finish
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
map.resources :orders do |order|
|
match '/paypal_notify' => 'paypal_express_callbacks#notify', :via => [:get, :post]
|
||||||
order.resource :checkout, :member => {:paypal_checkout => :any, :paypal_payment => :any, :paypal_confirm => :any, :paypal_finish => :any}
|
|
||||||
end
|
|
||||||
|
|
||||||
map.paypal_notify "/paypal_notify", :controller => :paypal_express_callbacks, :action => :notify, :method => [:post, :get]
|
resources :paypal_express_callbacks
|
||||||
|
namespace :admin do
|
||||||
map.namespace :admin do |admin|
|
resources :orders do
|
||||||
admin.resources :orders do |order|
|
resources :paypal_payments do
|
||||||
order.resources :paypal_payments, :member => {:capture => :get, :refund => :any}, :has_many => [:txns]
|
member do
|
||||||
|
get :refund
|
||||||
|
get :capture
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
module Spree::CheckoutsControllerWithPaypalExpress
|
module Spree::CheckoutControllerWithPaypalExpress
|
||||||
def self.included(target)
|
def self.included(target)
|
||||||
target.before_filter :redirect_to_paypal_express_form, :only => [:update]
|
target.before_filter :redirect_to_paypal_express_form, :only => [:update]
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
# aim to unpick this later
|
||||||
module Spree::PaypalExpress
|
module Spree::PaypalExpress
|
||||||
include ERB::Util
|
include ERB::Util
|
||||||
include ActiveMerchant::RequiresParameters
|
include ActiveMerchant::RequiresParameters
|
||||||
|
@ -6,11 +7,8 @@ module Spree::PaypalExpress
|
||||||
target.before_filter :redirect_to_paypal_express_form_if_needed, :only => [:update]
|
target.before_filter :redirect_to_paypal_express_form_if_needed, :only => [:update]
|
||||||
end
|
end
|
||||||
|
|
||||||
# Outbound redirect to PayPal from Cart Page
|
|
||||||
# Not fully implmented or tested.
|
|
||||||
#
|
|
||||||
def paypal_checkout
|
def paypal_checkout
|
||||||
load_object
|
load_order
|
||||||
opts = all_opts(@order, params[:payment_method_id], 'checkout')
|
opts = all_opts(@order, params[:payment_method_id], 'checkout')
|
||||||
opts.merge!(address_options(@order))
|
opts.merge!(address_options(@order))
|
||||||
gateway = paypal_gateway
|
gateway = paypal_gateway
|
||||||
|
@ -30,10 +28,8 @@ module Spree::PaypalExpress
|
||||||
redirect_to (gateway.redirect_url_for response.token, :review => payment_method.preferred_review)
|
redirect_to (gateway.redirect_url_for response.token, :review => payment_method.preferred_review)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Outbound redirect to PayPal from checkout payments step
|
|
||||||
#
|
|
||||||
def paypal_payment
|
def paypal_payment
|
||||||
load_object
|
load_order
|
||||||
opts = all_opts(@order,params[:payment_method_id], 'payment')
|
opts = all_opts(@order,params[:payment_method_id], 'payment')
|
||||||
opts.merge!(address_options(@order))
|
opts.merge!(address_options(@order))
|
||||||
gateway = paypal_gateway
|
gateway = paypal_gateway
|
||||||
|
@ -46,17 +42,15 @@ module Spree::PaypalExpress
|
||||||
|
|
||||||
unless response.success?
|
unless response.success?
|
||||||
gateway_error(response)
|
gateway_error(response)
|
||||||
redirect_to edit_order_checkout_url(@order, :step => "payment")
|
redirect_to edit_order_checkout_url(@order, :state => "payment")
|
||||||
return
|
return
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to (gateway.redirect_url_for response.token, :review => payment_method.preferred_review)
|
redirect_to (gateway.redirect_url_for response.token, :review => payment_method.preferred_review)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Inbound post from PayPal after (possible) successful completion
|
|
||||||
#
|
|
||||||
def paypal_confirm
|
def paypal_confirm
|
||||||
load_object
|
load_order
|
||||||
|
|
||||||
opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order, params[:payment_method_id], 'payment')
|
opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order, params[:payment_method_id], 'payment')
|
||||||
gateway = paypal_gateway
|
gateway = paypal_gateway
|
||||||
|
@ -71,9 +65,8 @@ module Spree::PaypalExpress
|
||||||
:payer_country => @ppx_details.params["payer_country"],
|
:payer_country => @ppx_details.params["payer_country"],
|
||||||
:payer_status => @ppx_details.params["payer_status"])
|
:payer_status => @ppx_details.params["payer_status"])
|
||||||
|
|
||||||
@order.checkout.special_instructions = @ppx_details.params["note"]
|
@order.special_instructions = @ppx_details.params["note"]
|
||||||
|
|
||||||
#@order.update_attribute(:user, current_user)
|
|
||||||
unless payment_method.preferred_no_shipping
|
unless payment_method.preferred_no_shipping
|
||||||
ship_address = @ppx_details.address
|
ship_address = @ppx_details.address
|
||||||
order_ship_address = Address.new :firstname => @ppx_details.params["first_name"],
|
order_ship_address = Address.new :firstname => @ppx_details.params["first_name"],
|
||||||
|
@ -94,12 +87,12 @@ module Spree::PaypalExpress
|
||||||
|
|
||||||
order_ship_address.save!
|
order_ship_address.save!
|
||||||
|
|
||||||
@order.checkout.ship_address = order_ship_address
|
@order.ship_address = order_ship_address
|
||||||
end
|
end
|
||||||
@order.checkout.save
|
@order.save
|
||||||
|
|
||||||
if payment_method.preferred_review
|
if payment_method.preferred_review
|
||||||
render :partial => "shared/paypal_express_confirm", :layout => true
|
render 'shared/paypal_express_confirm', :layout => 'spree_application'
|
||||||
else
|
else
|
||||||
paypal_finish
|
paypal_finish
|
||||||
end
|
end
|
||||||
|
@ -107,15 +100,12 @@ module Spree::PaypalExpress
|
||||||
gateway_error(@ppx_details)
|
gateway_error(@ppx_details)
|
||||||
|
|
||||||
#Failed trying to get payment details from PPX
|
#Failed trying to get payment details from PPX
|
||||||
redirect_to edit_order_checkout_url(@order, :step => "payment")
|
redirect_to edit_order_checkout_url(@order, :state => "payment")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
# Local call from A) Order Review Screen, or B) Automatically after paypal_confirm (no review).
|
|
||||||
# Completes checkout & order
|
|
||||||
#
|
|
||||||
def paypal_finish
|
def paypal_finish
|
||||||
load_object
|
load_order
|
||||||
|
|
||||||
opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order, params[:payment_method_id], 'payment' )
|
opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order, params[:payment_method_id], 'payment' )
|
||||||
gateway = paypal_gateway
|
gateway = paypal_gateway
|
||||||
|
@ -127,71 +117,70 @@ module Spree::PaypalExpress
|
||||||
end
|
end
|
||||||
|
|
||||||
if ppx_auth_response.success?
|
if ppx_auth_response.success?
|
||||||
#confirm status
|
|
||||||
case ppx_auth_response.params["payment_status"]
|
|
||||||
when "Completed"
|
|
||||||
txn_type = PaypalTxn::TxnType::CAPTURE
|
|
||||||
when "Pending"
|
|
||||||
txn_type = PaypalTxn::TxnType::AUTHORIZE
|
|
||||||
else
|
|
||||||
txn_type = PaypalTxn::TxnType::UNKNOWN
|
|
||||||
Rails.logger.error "Unexpected response from PayPal Express"
|
|
||||||
Rails.logger.error ppx_auth_response.to_yaml
|
|
||||||
end
|
|
||||||
|
|
||||||
paypal_account = PaypalAccount.find_by_payer_id(params[:PayerID])
|
paypal_account = PaypalAccount.find_by_payer_id(params[:PayerID])
|
||||||
|
|
||||||
payment = @order.checkout.payments.create(:amount => ppx_auth_response.params["gross_amount"].to_f,
|
payment = @order.payments.create(
|
||||||
:source => paypal_account,
|
:amount => ppx_auth_response.params["gross_amount"].to_f,
|
||||||
:payment_method_id => params[:payment_method_id])
|
:source => paypal_account,
|
||||||
|
:source_type => 'PaypalAccount',
|
||||||
|
:payment_method_id => params[:payment_method_id],
|
||||||
|
:response_code => ppx_auth_response.params["ack"],
|
||||||
|
:avs_response => ppx_auth_response.avs_result["code"])
|
||||||
|
|
||||||
PaypalTxn.create(:payment => payment,
|
# transition through the state machine. This way any callbacks can be made
|
||||||
:txn_type => txn_type,
|
payment.started_processing!
|
||||||
:amount => ppx_auth_response.params["gross_amount"].to_f,
|
payment.pend!
|
||||||
:message => ppx_auth_response.params["message"],
|
|
||||||
:payment_status => ppx_auth_response.params["payment_status"],
|
|
||||||
:pending_reason => ppx_auth_response.params["pending_reason"],
|
|
||||||
:transaction_id => ppx_auth_response.params["transaction_id"],
|
|
||||||
:transaction_type => ppx_auth_response.params["transaction_type"],
|
|
||||||
:payment_type => ppx_auth_response.params["payment_type"],
|
|
||||||
:response_code => ppx_auth_response.params["ack"],
|
|
||||||
:token => ppx_auth_response.params["token"],
|
|
||||||
:avs_response => ppx_auth_response.avs_result["code"],
|
|
||||||
:cvv_response => ppx_auth_response.cvv_result["code"])
|
|
||||||
|
|
||||||
|
#confirm status
|
||||||
|
case ppx_auth_response.params["payment_status"]
|
||||||
|
when "Completed"
|
||||||
|
payment.complete!
|
||||||
|
when "Pending"
|
||||||
|
else
|
||||||
|
Rails.logger.error "Unexpected response from PayPal Express"
|
||||||
|
Rails.logger.error ppx_auth_response.to_yaml
|
||||||
|
end
|
||||||
|
|
||||||
|
record_log payment, ppx_auth_response
|
||||||
|
|
||||||
@order.save!
|
@order.save!
|
||||||
@checkout.reload
|
|
||||||
#need to force checkout to complete state
|
|
||||||
until @checkout.state == "complete"
|
|
||||||
@checkout.next!
|
|
||||||
end
|
|
||||||
complete_checkout
|
|
||||||
|
|
||||||
# even with auto_capture , an Auth might be returned / forced by PPX
|
#need to force checkout to complete state
|
||||||
if Spree::Config[:auto_capture] && txn_type == PaypalTxn::TxnType::CAPTURE
|
until @order.state == "complete"
|
||||||
payment.finalize!
|
if @order.next!
|
||||||
|
state_callback(:after)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
flash[:notice] = I18n.t(:order_processed_successfully)
|
||||||
|
redirect_to completion_route
|
||||||
|
|
||||||
else
|
else
|
||||||
order_params = {}
|
order_params = {}
|
||||||
gateway_error(ppx_auth_response)
|
gateway_error(ppx_auth_response)
|
||||||
|
|
||||||
#Failed trying to complete pending payment!
|
#Failed trying to complete pending payment!
|
||||||
redirect_to edit_order_checkout_url(@order, :step => "payment")
|
redirect_to edit_order_checkout_url(@order, :state => "payment")
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def record_log(payment, response)
|
||||||
|
payment.log_entries.create(:details => response.to_yaml)
|
||||||
|
end
|
||||||
|
|
||||||
def redirect_to_paypal_express_form_if_needed
|
def redirect_to_paypal_express_form_if_needed
|
||||||
return unless params[:step] == "payment"
|
return unless (params[:state] == "payment")
|
||||||
|
if params[:order][:coupon_code]
|
||||||
load_object
|
@order.update_attributes(object_params)
|
||||||
|
@order.process_coupon_code
|
||||||
payment_method = PaymentMethod.find(params[:checkout][:payments_attributes].first[:payment_method_id])
|
end
|
||||||
|
load_order
|
||||||
|
payment_method = PaymentMethod.find(params[:order][:payments_attributes].first[:payment_method_id])
|
||||||
|
|
||||||
if payment_method.kind_of?(BillingIntegration::PaypalExpress) || payment_method.kind_of?(BillingIntegration::PaypalExpressUk)
|
if payment_method.kind_of?(BillingIntegration::PaypalExpress) || payment_method.kind_of?(BillingIntegration::PaypalExpressUk)
|
||||||
redirect_to paypal_payment_order_checkout_url(@checkout.order, :payment_method_id => payment_method)
|
redirect_to paypal_payment_order_checkout_url(@order, :payment_method_id => payment_method)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -237,18 +226,19 @@ module Spree::PaypalExpress
|
||||||
:height => item.variant.height,
|
:height => item.variant.height,
|
||||||
:width => item.variant.width,
|
:width => item.variant.width,
|
||||||
:depth => item.variant.weight }
|
:depth => item.variant.weight }
|
||||||
end
|
end
|
||||||
|
|
||||||
#add each credit a line item to ensure totals sum up correctly
|
credits = order.adjustments.map do |credit|
|
||||||
credits = order.credits.map do |credit|
|
if credit.amount < 0.00
|
||||||
{ :name => credit.description,
|
{ :name => credit.label,
|
||||||
:description => credit.description,
|
:description => credit.label,
|
||||||
:sku => credit.id,
|
:sku => credit.id,
|
||||||
:qty => 1,
|
:qty => 1,
|
||||||
:amount => (credit.amount*100).to_i }
|
:amount => (credit.amount*100).to_i }
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
items.concat credits.compact!
|
||||||
items.concat credits
|
credits_total = credits.map {|i| i[:amount] * i[:qty] }.sum
|
||||||
|
|
||||||
opts = { :return_url => request.protocol + request.host_with_port + "/orders/#{order.number}/checkout/paypal_confirm?payment_method_id=#{payment_method}",
|
opts = { :return_url => request.protocol + request.host_with_port + "/orders/#{order.number}/checkout/paypal_confirm?payment_method_id=#{payment_method}",
|
||||||
:cancel_return_url => "http://" + request.host_with_port + "/orders/#{order.number}/edit",
|
:cancel_return_url => "http://" + request.host_with_port + "/orders/#{order.number}/edit",
|
||||||
|
@ -259,26 +249,24 @@ module Spree::PaypalExpress
|
||||||
|
|
||||||
if stage == "checkout"
|
if stage == "checkout"
|
||||||
# recalculate all totals here as we need to ignore shipping & tax because we are checking-out via paypal (spree checkout not started)
|
# recalculate all totals here as we need to ignore shipping & tax because we are checking-out via paypal (spree checkout not started)
|
||||||
|
opts[:subtotal] = ((order.item_total * 100) + credits_total ).to_i
|
||||||
# get the main totals from the items (already *100)
|
|
||||||
opts[:subtotal] = opts[:items].map {|i| i[:amount] * i[:qty] }.sum
|
|
||||||
opts[:tax] = opts[:items].map {|i| i[:tax] * i[:qty] }.sum
|
|
||||||
opts[:handling] = 0
|
opts[:handling] = 0
|
||||||
opts[:shipping] = (order.ship_total*100).to_i
|
opts[:tax] = ((order.adjustments.map { |a| a.amount if ( a.source_type == 'Order' && a.label == 'Tax') }.compact!.sum) * 100 ).to_i
|
||||||
|
opts[:shipping] = ((order.adjustments.map { |a| a.amount if a.source_type == 'Shipment' }.compact!.sum) * 100 ).to_i
|
||||||
|
|
||||||
# overall total
|
# overall total
|
||||||
opts[:money] = opts.slice(:subtotal, :tax, :shipping, :handling).values.sum
|
opts[:money] = (order.total * 100 ).to_i
|
||||||
|
|
||||||
opts[:callback_url] = "http://" + request.host_with_port + "/paypal_express_callbacks/#{order.number}"
|
opts[:callback_url] = "http://" + request.host_with_port + "/paypal_express_callbacks/#{order.number}"
|
||||||
opts[:callback_timeout] = 3
|
opts[:callback_timeout] = 3
|
||||||
elsif stage == "payment"
|
elsif stage == "payment"
|
||||||
#use real totals are we are paying via paypal (spree checkout almost complete)
|
#use real totals are we are paying via paypal (spree checkout almost complete)
|
||||||
opts[:subtotal] = ((order.item_total + order.credits.total)*100).to_i
|
opts[:subtotal] = ((order.item_total * 100) + credits_total ).to_i
|
||||||
opts[:tax] = (order.tax_total*100).to_i
|
opts[:tax] = ((order.adjustments.map { |a| a.amount if ( a.source_type == 'Order' && a.label == 'Tax') }.compact!.sum) * 100 ).to_i
|
||||||
opts[:shipping] = (order.ship_total*100).to_i
|
opts[:shipping] = ((order.adjustments.map { |a| a.amount if a.source_type == 'Shipment' }.compact!.sum) * 100 ).to_i
|
||||||
|
|
||||||
#hack to add float rounding difference in as handling fee - prevents PayPal from rejecting orders
|
#hack to add float rounding difference in as handling fee - prevents PayPal from rejecting orders
|
||||||
#becuase the integer totals are different from the float based total. This is temporary and will be
|
#because the integer totals are different from the float based total. This is temporary and will be
|
||||||
#removed once Spree's currency values are persisted as integers (normally only 1c)
|
#removed once Spree's currency values are persisted as integers (normally only 1c)
|
||||||
opts[:handling] = (order.total*100).to_i - opts.slice(:subtotal, :tax, :shipping).values.sum
|
opts[:handling] = (order.total*100).to_i - opts.slice(:subtotal, :tax, :shipping).values.sum
|
||||||
|
|
||||||
|
@ -317,7 +305,7 @@ module Spree::PaypalExpress
|
||||||
end
|
end
|
||||||
|
|
||||||
# suggest current user's email or any email stored in the order
|
# suggest current user's email or any email stored in the order
|
||||||
opts[:email] = current_user ? current_user.email : order.checkout.email
|
opts[:email] = current_user ? current_user.email : order.email
|
||||||
|
|
||||||
opts
|
opts
|
||||||
end
|
end
|
||||||
|
|
|
@ -0,0 +1,29 @@
|
||||||
|
require 'spree_core'
|
||||||
|
require 'spree_paypal_express_hooks'
|
||||||
|
|
||||||
|
module SpreePaypalExpress
|
||||||
|
class Engine < Rails::Engine
|
||||||
|
|
||||||
|
config.autoload_paths += %W(#{config.root}/lib)
|
||||||
|
|
||||||
|
def self.activate
|
||||||
|
Dir.glob(File.join(File.dirname(__FILE__), "../app/**/*_decorator*.rb")) do |c|
|
||||||
|
Rails.env.production? ? require(c) : load(c)
|
||||||
|
end
|
||||||
|
BillingIntegration::PaypalExpress.register
|
||||||
|
BillingIntegration::PaypalExpressUk.register
|
||||||
|
|
||||||
|
# Load up over-rides for ActiveMerchant files
|
||||||
|
# these will be submitted to ActiveMerchant some time...
|
||||||
|
require File.join(File.dirname(__FILE__), "active_merchant", "billing", "gateways", "paypal", "paypal_common_api.rb")
|
||||||
|
require File.join(File.dirname(__FILE__), "active_merchant", "billing", "gateways", "paypal_express_uk.rb")
|
||||||
|
|
||||||
|
# inject paypal code into orders controller
|
||||||
|
CheckoutController.class_eval do
|
||||||
|
include Spree::PaypalExpress
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
config.to_prepare &method(:activate).to_proc
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,3 @@
|
||||||
|
class SpreePaypalExpressHooks < Spree::ThemeSupport::HookListener
|
||||||
|
# custom hooks go here
|
||||||
|
end
|
|
@ -0,0 +1,26 @@
|
||||||
|
namespace :spree_paypal_express do
|
||||||
|
desc "Copies all migrations and assets (NOTE: This will be obsolete with Rails 3.1)"
|
||||||
|
task :install do
|
||||||
|
Rake::Task['spree_paypal_express:install:migrations'].invoke
|
||||||
|
Rake::Task['spree_paypal_express:install:assets'].invoke
|
||||||
|
end
|
||||||
|
|
||||||
|
namespace :install do
|
||||||
|
desc "Copies all migrations (NOTE: This will be obsolete with Rails 3.1)"
|
||||||
|
task :migrations do
|
||||||
|
source = File.join(File.dirname(__FILE__), '..', '..', 'db')
|
||||||
|
destination = File.join(Rails.root, 'db')
|
||||||
|
puts "INFO: Mirroring assets from #{source} to #{destination}"
|
||||||
|
Spree::FileUtilz.mirror_files(source, destination)
|
||||||
|
end
|
||||||
|
|
||||||
|
desc "Copies all assets (NOTE: This will be obsolete with Rails 3.1)"
|
||||||
|
task :assets do
|
||||||
|
source = File.join(File.dirname(__FILE__), '..', '..', 'public')
|
||||||
|
destination = File.join(Rails.root, 'public')
|
||||||
|
puts "INFO: Mirroring assets from #{source} to #{destination}"
|
||||||
|
Spree::FileUtilz.mirror_files(source, destination)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
end
|
|
@ -0,0 +1,31 @@
|
||||||
|
# This file is copied to ~/spec when you run 'ruby script/generate rspec'
|
||||||
|
# from the project root directory.
|
||||||
|
ENV["RAILS_ENV"] ||= 'test'
|
||||||
|
require File.expand_path("../../../config/environment", __FILE__)
|
||||||
|
require 'rspec/rails'
|
||||||
|
require 'fabrication'
|
||||||
|
|
||||||
|
# Requires supporting files with custom matchers and macros, etc,
|
||||||
|
# in ./support/ and its subdirectories.
|
||||||
|
Dir["#{File.dirname(__FILE__)}/support/**/*.rb"].each {|f| require f}
|
||||||
|
|
||||||
|
RSpec.configure do |config|
|
||||||
|
# == Mock Framework
|
||||||
|
#
|
||||||
|
# If you prefer to use mocha, flexmock or RR, uncomment the appropriate line:
|
||||||
|
#
|
||||||
|
# config.mock_with :mocha
|
||||||
|
# config.mock_with :flexmock
|
||||||
|
# config.mock_with :rr
|
||||||
|
config.mock_with :rspec
|
||||||
|
|
||||||
|
config.fixture_path = "#{::Rails.root}/spec/fixtures"
|
||||||
|
|
||||||
|
#config.include Devise::TestHelpers, :type => :controller
|
||||||
|
# If you're not using ActiveRecord, or you'd prefer not to run each of your
|
||||||
|
# examples within a transaction, comment the following line or assign false
|
||||||
|
# instead of true.
|
||||||
|
config.use_transactional_fixtures = true
|
||||||
|
end
|
||||||
|
|
||||||
|
@configuration ||= AppConfiguration.find_or_create_by_name("Default configuration")
|
|
@ -0,0 +1,21 @@
|
||||||
|
Gem::Specification.new do |s|
|
||||||
|
s.platform = Gem::Platform::RUBY
|
||||||
|
s.name = 'spree_paypal_express'
|
||||||
|
s.version = '2.0.0'
|
||||||
|
s.summary = 'Add gem summary here'
|
||||||
|
#s.description = 'Add (optional) gem description here'
|
||||||
|
s.required_ruby_version = '>= 1.8.7'
|
||||||
|
|
||||||
|
# s.author = 'David Heinemeier Hansson'
|
||||||
|
# s.email = 'david@loudthinking.com'
|
||||||
|
# s.homepage = 'http://www.rubyonrails.org'
|
||||||
|
# s.rubyforge_project = 'actionmailer'
|
||||||
|
|
||||||
|
s.files = Dir['CHANGELOG', 'README.md', 'LICENSE', 'lib/**/*', 'app/**/*']
|
||||||
|
s.require_path = 'lib'
|
||||||
|
s.requirements << 'none'
|
||||||
|
|
||||||
|
s.has_rdoc = true
|
||||||
|
|
||||||
|
s.add_dependency('spree_core', '>= 0.30.1')
|
||||||
|
end
|
Loading…
Reference in New Issue