Browse Source

Modified to work with Spree 0.30.1.

1-2-stable-sysmocom
reaper 11 years ago
parent
commit
b79fb41c4d
  1. 23
      LICENSE
  2. 129
      Rakefile
  3. 90
      app/models/paypal_account.rb
  4. 19
      app/views/admin/payments/source_views/_paypalexpress.html.erb
  5. 0
      app/views/checkout/payment/_paypalexpress.html.erb
  6. 4
      app/views/shared/paypal_express_confirm.html.erb
  7. 2
      config/locales/en-GB.yml
  8. 2
      config/locales/en.yml
  9. 36
      config/routes.rb
  10. 2
      lib/spree/checkouts_controller_with_paypal_express.rb
  11. 176
      lib/spree/paypal_express.rb
  12. 29
      lib/spree_paypal_express.rb
  13. 3
      lib/spree_paypal_express_hooks.rb
  14. 26
      lib/tasks/spree_paypal_express.rake
  15. 31
      spec/spec_helper.rb
  16. 21
      spree_paypal_express.gemspec

23
LICENSE

@ -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

@ -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'
# 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"
spec = eval(File.read('spree_paypal_express.gemspec'))
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

90
app/models/paypal_account.rb

@ -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)
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"])
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?
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)

19
app/views/admin/payments/source_views/_paypalexpress.html.erb

@ -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 %>

0
app/views/checkouts/payment/_paypalexpress.html.erb → app/views/checkout/payment/_paypalexpress.html.erb

4
app/views/shared/_paypal_express_confirm.html.erb → app/views/shared/paypal_express_confirm.html.erb

@ -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>

2
config/locales/en-GB.yml

@ -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

2
config/locales/en.yml

@ -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

36
config/routes.rb

@ -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

2
lib/spree/checkouts_controller_with_paypal_express.rb

@ -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

176
lib/spree/paypal_express.rb

@ -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 redirect_to_paypal_express_form_if_needed
return unless params[:step] == "payment"
load_object
def record_log(payment, response)
payment.log_entries.create(:details => response.to_yaml)
end
payment_method = PaymentMethod.find(params[:checkout][:payments_attributes].first[:payment_method_id])
def redirect_to_paypal_express_form_if_needed
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
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 }
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
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

29
lib/spree_paypal_express.rb

@ -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

3
lib/spree_paypal_express_hooks.rb

@ -0,0 +1,3 @@
class SpreePaypalExpressHooks < Spree::ThemeSupport::HookListener
# custom hooks go here
end

26
lib/tasks/spree_paypal_express.rake

@ -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

31
spec/spec_helper.rb

@ -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")

21
spree_paypal_express.gemspec

@ -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…
Cancel
Save