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
|
||||
|
||||
# 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 File.expand_path('../../config/application', __FILE__)
|
||||
|
||||
require 'rubygems'
|
||||
require 'rake'
|
||||
require 'rake/rdoctask'
|
||||
require 'rake/testtask'
|
||||
require 'rake/packagetask'
|
||||
require 'rake/gempackagetask'
|
||||
|
||||
rspec_base = File.expand_path(SPREE_ROOT + '/vendor/plugins/rspec/lib')
|
||||
$LOAD_PATH.unshift(rspec_base) if File.exist?(rspec_base)
|
||||
require 'spec/rake/spectask'
|
||||
# require 'spec/translator'
|
||||
spec = eval(File.read('spree_paypal_express.gemspec'))
|
||||
|
||||
# Cleanup the SPREE_ROOT constant so specs will load the environment
|
||||
Object.send(:remove_const, :SPREE_ROOT)
|
||||
|
||||
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"]
|
||||
Rake::GemPackageTask.new(spec) do |p|
|
||||
p.gem_spec = spec
|
||||
end
|
||||
|
||||
namespace :spec do
|
||||
desc "Run all specs in spec directory with RCov"
|
||||
Spec::Rake::SpecTask.new(:rcov) do |t|
|
||||
t.spec_opts = ['--options', "\"#{extension_root}/spec/spec.opts\""]
|
||||
t.spec_files = FileList['spec/**/*_spec.rb']
|
||||
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
|
||||
desc "Release to gemcutter"
|
||||
task :release => :package do
|
||||
require 'rake/gemcutter'
|
||||
Rake::Gemcutter::Tasks.new(spec).define
|
||||
Rake::Task['gem:push'].invoke
|
||||
end
|
||||
|
||||
desc 'Generate documentation for the paypal_express extension.'
|
||||
Rake::RDocTask.new(:rdoc) do |rdoc|
|
||||
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
|
||||
desc "Default Task"
|
||||
task :default => [ :spec ]
|
||||
|
||||
# For extensions that are in transition
|
||||
desc 'Test the paypal_express extension.'
|
||||
Rake::TestTask.new(:test) do |t|
|
||||
t.libs << 'lib'
|
||||
t.pattern = 'test/**/*_test.rb'
|
||||
t.verbose = true
|
||||
end
|
||||
require 'rspec/core/rake_task'
|
||||
RSpec::Core::RakeTask.new
|
||||
|
||||
# Load any custom rakefiles for extension
|
||||
Dir[File.dirname(__FILE__) + '/tasks/*.rake'].sort.each { |f| require f }
|
||||
# require 'cucumber/rake/task'
|
||||
# Cucumber::Rake::Task.new do |t|
|
||||
# t.cucumber_opts = %w{--format pretty}
|
||||
# end
|
|
@ -8,23 +8,10 @@ class PaypalAccount < ActiveRecord::Base
|
|||
def capture(payment)
|
||||
authorization = find_authorization(payment)
|
||||
|
||||
ppx_response = payment.payment_method.provider.capture((100 * payment.amount).to_i, authorization.transaction_id)
|
||||
if ppx_response.success?
|
||||
PaypalTxn.create(:payment => payment,
|
||||
:txn_type => PaypalTxn::TxnType::CAPTURE,
|
||||
: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!
|
||||
ppx_response = payment.payment_method.provider.capture((100 * payment.amount).to_i, authorization.params["transaction_id"])
|
||||
if ppx_response.success?
|
||||
record_log payment, ppx_response
|
||||
payment.complete
|
||||
else
|
||||
gateway_error(ppx_response.message)
|
||||
end
|
||||
|
@ -32,37 +19,30 @@ class PaypalAccount < ActiveRecord::Base
|
|||
end
|
||||
|
||||
def can_capture?(payment)
|
||||
!echeck?(payment) && find_capture(payment).nil?
|
||||
!echeck?(payment) && payment.state == "pending"
|
||||
end
|
||||
|
||||
def credit(payment, amount=nil)
|
||||
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?
|
||||
PaypalTxn.new(:payment => payment,
|
||||
: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"])
|
||||
|
||||
record_log payment, ppx_response
|
||||
payment.update_attribute(:amount, payment.amount - amount)
|
||||
payment.order.update_totals!
|
||||
payment.complete
|
||||
payment.order.update!
|
||||
else
|
||||
gateway_error(ppx_response.message)
|
||||
end
|
||||
end
|
||||
|
||||
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?
|
||||
end
|
||||
|
||||
|
@ -70,24 +50,44 @@ class PaypalAccount < ActiveRecord::Base
|
|||
def payment_gateway
|
||||
false
|
||||
end
|
||||
|
||||
|
||||
def record_log(payment, response)
|
||||
payment.log_entries.create(:details => response.to_yaml)
|
||||
end
|
||||
|
||||
private
|
||||
def find_authorization(payment)
|
||||
#find the transaction associated with the original authorization/capture
|
||||
payment.txns.find(:first,
|
||||
:conditions => {:pending_reason => "authorization", :payment_status => "Pending", :txn_type => PaypalTxn::TxnType::AUTHORIZE.to_s},
|
||||
:order => 'created_at DESC')
|
||||
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_status'] == 'Pending' && details.params['pending_reason'] == 'authorization')
|
||||
return details
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
def find_capture(payment)
|
||||
#find the transaction associated with the original authorization/capture
|
||||
payment.txns.find(:first,
|
||||
:conditions => {:payment_status => "Completed", :txn_type => PaypalTxn::TxnType::CAPTURE.to_s},
|
||||
:order => 'created_at DESC')
|
||||
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_status'] == 'Completed'
|
||||
return details
|
||||
end
|
||||
end
|
||||
return nil
|
||||
end
|
||||
|
||||
|
||||
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
|
||||
|
||||
def gateway_error(text)
|
||||
|
|
|
@ -31,40 +31,41 @@
|
|||
<fieldset>
|
||||
<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">
|
||||
<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>
|
||||
<td width="12%;"><label><%= t('type') %>:</label></td>
|
||||
<td width="20%;">
|
||||
<%= txn.txn_type_name %>
|
||||
<%= details.params["transaction_type"] %>
|
||||
</td>
|
||||
<td width="8%;"><label><%= t("result") %>:</label></td>
|
||||
<td width="20%;">
|
||||
<%= txn.response_code %>
|
||||
<%= details.message %>
|
||||
</td>
|
||||
<td width="15%;"><label><%= t("amount") %>:</label></td>
|
||||
<td width="20%;">
|
||||
<%= number_to_currency txn.amount %>
|
||||
<%= number_to_currency details.params["gross_amount"] %>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><label><%= t("comment") %>:</label></td>
|
||||
<td colspan="3">
|
||||
<%= txn.message %>
|
||||
<%= details.params["message"] %>
|
||||
</td>
|
||||
<td><label><%= t("status") %>:</label></td>
|
||||
<td>
|
||||
<%= txn.payment_status %>
|
||||
<%= details.params["payment_status"] %>
|
||||
</td>
|
||||
</tr>
|
||||
<% if txn.payment_status == "Pending" %>
|
||||
<% if details.params["payment_status"] == "Pending" %>
|
||||
<tr>
|
||||
<td><label><%= t("pending_reason") %>:</label></td>
|
||||
<td colspan="5">
|
||||
<%= txn.pending_reason %>
|
||||
<%= details.params["pending_reason"] %>
|
||||
</td>
|
||||
</tr>
|
||||
<% end %>
|
||||
|
|
|
@ -1,10 +1,10 @@
|
|||
<h1><%= t("confirm") %></h1>
|
||||
<p>
|
||||
<%= t("order_not_yet_placed") %>
|
||||
<%= raw t("order_not_yet_placed") %>
|
||||
</p>
|
||||
|
||||
<%= render :partial => 'shared/order_details', :locals => {:order => @order} -%>
|
||||
<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" %>
|
||||
</div>
|
|
@ -6,7 +6,7 @@ en-GB:
|
|||
paypal_capture_complete: Paypal Transaction has been captured.
|
||||
unable_to_capture_paypal: Unable to capture Paypal Transaction.
|
||||
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
|
||||
pending_reason: Pending Reason
|
||||
result: Result
|
||||
|
|
|
@ -6,7 +6,7 @@ en:
|
|||
paypal_capture_complete: Paypal Transaction has been captured.
|
||||
unable_to_capture_paypal: Unable to capture Paypal Transaction.
|
||||
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
|
||||
pending_reason: Pending Reason
|
||||
result: Result
|
||||
|
|
|
@ -1,14 +1,26 @@
|
|||
# Put your extension routes here.
|
||||
|
||||
map.resources :orders do |order|
|
||||
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]
|
||||
|
||||
map.namespace :admin do |admin|
|
||||
admin.resources :orders do |order|
|
||||
order.resources :paypal_payments, :member => {:capture => :get, :refund => :any}, :has_many => [:txns]
|
||||
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
|
||||
end
|
||||
|
||||
match '/paypal_notify' => 'paypal_express_callbacks#notify', :via => [:get, :post]
|
||||
|
||||
resources :paypal_express_callbacks
|
||||
namespace :admin do
|
||||
resources :orders do
|
||||
resources :paypal_payments do
|
||||
member do
|
||||
get :refund
|
||||
get :capture
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
|
@ -1,4 +1,4 @@
|
|||
module Spree::CheckoutsControllerWithPaypalExpress
|
||||
module Spree::CheckoutControllerWithPaypalExpress
|
||||
def self.included(target)
|
||||
target.before_filter :redirect_to_paypal_express_form, :only => [:update]
|
||||
end
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
# aim to unpick this later
|
||||
module Spree::PaypalExpress
|
||||
include ERB::Util
|
||||
include ActiveMerchant::RequiresParameters
|
||||
|
@ -6,11 +7,8 @@ module Spree::PaypalExpress
|
|||
target.before_filter :redirect_to_paypal_express_form_if_needed, :only => [:update]
|
||||
end
|
||||
|
||||
# Outbound redirect to PayPal from Cart Page
|
||||
# Not fully implmented or tested.
|
||||
#
|
||||
def paypal_checkout
|
||||
load_object
|
||||
load_order
|
||||
opts = all_opts(@order, params[:payment_method_id], 'checkout')
|
||||
opts.merge!(address_options(@order))
|
||||
gateway = paypal_gateway
|
||||
|
@ -20,7 +18,7 @@ module Spree::PaypalExpress
|
|||
else
|
||||
response = gateway.setup_authorization(opts[:money], opts)
|
||||
end
|
||||
|
||||
|
||||
unless response.success?
|
||||
gateway_error(response)
|
||||
redirect_to edit_order_url(@order)
|
||||
|
@ -30,10 +28,8 @@ module Spree::PaypalExpress
|
|||
redirect_to (gateway.redirect_url_for response.token, :review => payment_method.preferred_review)
|
||||
end
|
||||
|
||||
# Outbound redirect to PayPal from checkout payments step
|
||||
#
|
||||
def paypal_payment
|
||||
load_object
|
||||
load_order
|
||||
opts = all_opts(@order,params[:payment_method_id], 'payment')
|
||||
opts.merge!(address_options(@order))
|
||||
gateway = paypal_gateway
|
||||
|
@ -46,17 +42,15 @@ module Spree::PaypalExpress
|
|||
|
||||
unless response.success?
|
||||
gateway_error(response)
|
||||
redirect_to edit_order_checkout_url(@order, :step => "payment")
|
||||
redirect_to edit_order_checkout_url(@order, :state => "payment")
|
||||
return
|
||||
end
|
||||
|
||||
redirect_to (gateway.redirect_url_for response.token, :review => payment_method.preferred_review)
|
||||
end
|
||||
|
||||
# Inbound post from PayPal after (possible) successful completion
|
||||
#
|
||||
def paypal_confirm
|
||||
load_object
|
||||
load_order
|
||||
|
||||
opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order, params[:payment_method_id], 'payment')
|
||||
gateway = paypal_gateway
|
||||
|
@ -71,9 +65,8 @@ module Spree::PaypalExpress
|
|||
:payer_country => @ppx_details.params["payer_country"],
|
||||
: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
|
||||
ship_address = @ppx_details.address
|
||||
order_ship_address = Address.new :firstname => @ppx_details.params["first_name"],
|
||||
|
@ -94,28 +87,25 @@ module Spree::PaypalExpress
|
|||
|
||||
order_ship_address.save!
|
||||
|
||||
@order.checkout.ship_address = order_ship_address
|
||||
@order.ship_address = order_ship_address
|
||||
end
|
||||
@order.checkout.save
|
||||
@order.save
|
||||
|
||||
if payment_method.preferred_review
|
||||
render :partial => "shared/paypal_express_confirm", :layout => true
|
||||
render 'shared/paypal_express_confirm', :layout => 'spree_application'
|
||||
else
|
||||
paypal_finish
|
||||
end
|
||||
else
|
||||
gateway_error(@ppx_details)
|
||||
|
||||
|
||||
#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
|
||||
|
||||
# Local call from A) Order Review Screen, or B) Automatically after paypal_confirm (no review).
|
||||
# Completes checkout & order
|
||||
#
|
||||
def paypal_finish
|
||||
load_object
|
||||
load_order
|
||||
|
||||
opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order, params[:payment_method_id], 'payment' )
|
||||
gateway = paypal_gateway
|
||||
|
@ -127,71 +117,70 @@ module Spree::PaypalExpress
|
|||
end
|
||||
|
||||
if ppx_auth_response.success?
|
||||
paypal_account = PaypalAccount.find_by_payer_id(params[:PayerID])
|
||||
|
||||
payment = @order.payments.create(
|
||||
:amount => ppx_auth_response.params["gross_amount"].to_f,
|
||||
: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"])
|
||||
|
||||
# transition through the state machine. This way any callbacks can be made
|
||||
payment.started_processing!
|
||||
payment.pend!
|
||||
|
||||
#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
|
||||
when "Completed"
|
||||
payment.complete!
|
||||
when "Pending"
|
||||
else
|
||||
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])
|
||||
|
||||
payment = @order.checkout.payments.create(:amount => ppx_auth_response.params["gross_amount"].to_f,
|
||||
:source => paypal_account,
|
||||
:payment_method_id => params[:payment_method_id])
|
||||
|
||||
PaypalTxn.create(:payment => payment,
|
||||
:txn_type => txn_type,
|
||||
:amount => ppx_auth_response.params["gross_amount"].to_f,
|
||||
: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"])
|
||||
|
||||
|
||||
record_log payment, ppx_auth_response
|
||||
|
||||
@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
|
||||
if Spree::Config[:auto_capture] && txn_type == PaypalTxn::TxnType::CAPTURE
|
||||
payment.finalize!
|
||||
until @order.state == "complete"
|
||||
if @order.next!
|
||||
state_callback(:after)
|
||||
end
|
||||
end
|
||||
|
||||
flash[:notice] = I18n.t(:order_processed_successfully)
|
||||
redirect_to completion_route
|
||||
|
||||
else
|
||||
order_params = {}
|
||||
gateway_error(ppx_auth_response)
|
||||
|
||||
|
||||
#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
|
||||
|
||||
|
||||
private
|
||||
|
||||
def record_log(payment, response)
|
||||
payment.log_entries.create(:details => response.to_yaml)
|
||||
end
|
||||
|
||||
def redirect_to_paypal_express_form_if_needed
|
||||
return unless params[:step] == "payment"
|
||||
|
||||
load_object
|
||||
|
||||
payment_method = PaymentMethod.find(params[:checkout][:payments_attributes].first[:payment_method_id])
|
||||
return unless (params[:state] == "payment")
|
||||
if params[:order][:coupon_code]
|
||||
@order.update_attributes(object_params)
|
||||
@order.process_coupon_code
|
||||
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)
|
||||
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
|
||||
|
||||
|
@ -237,19 +226,20 @@ module Spree::PaypalExpress
|
|||
:height => item.variant.height,
|
||||
:width => item.variant.width,
|
||||
:depth => item.variant.weight }
|
||||
end
|
||||
|
||||
credits = order.adjustments.map do |credit|
|
||||
if credit.amount < 0.00
|
||||
{ :name => credit.label,
|
||||
:description => credit.label,
|
||||
:sku => credit.id,
|
||||
:qty => 1,
|
||||
:amount => (credit.amount*100).to_i }
|
||||
end
|
||||
end
|
||||
|
||||
#add each credit a line item to ensure totals sum up correctly
|
||||
credits = order.credits.map do |credit|
|
||||
{ :name => credit.description,
|
||||
:description => credit.description,
|
||||
:sku => credit.id,
|
||||
:qty => 1,
|
||||
:amount => (credit.amount*100).to_i }
|
||||
end
|
||||
|
||||
items.concat credits
|
||||
|
||||
items.concat credits.compact!
|
||||
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}",
|
||||
:cancel_return_url => "http://" + request.host_with_port + "/orders/#{order.number}/edit",
|
||||
:order_id => order.number,
|
||||
|
@ -259,26 +249,24 @@ module Spree::PaypalExpress
|
|||
|
||||
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)
|
||||
|
||||
# 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[:subtotal] = ((order.item_total * 100) + credits_total ).to_i
|
||||
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
|
||||
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_timeout] = 3
|
||||
elsif stage == "payment"
|
||||
#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[:tax] = (order.tax_total*100).to_i
|
||||
opts[:shipping] = (order.ship_total*100).to_i
|
||||
|
||||
opts[:subtotal] = ((order.item_total * 100) + credits_total ).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
|
||||
|
||||
#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)
|
||||
opts[:handling] = (order.total*100).to_i - opts.slice(:subtotal, :tax, :shipping).values.sum
|
||||
|
||||
|
@ -317,7 +305,7 @@ module Spree::PaypalExpress
|
|||
end
|
||||
|
||||
# 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
|
||||
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