Browse Source

Added PayPal Instant Update feature

1-2-stable-sysmocom
Vladimir Fedorov 9 years ago
committed by Jeff Dutil
parent
commit
ca60407d5d
  1. 3
      README.markdown
  2. 111
      app/controllers/spree/checkout_controller_decorator.rb
  3. 48
      app/controllers/spree/paypal_express_callbacks_controller.rb
  4. 3
      app/models/spree/billing_integration/paypal_express_base.rb
  5. 1
      config/locales/en-GB.yml
  6. 1
      config/locales/en.yml
  7. 2
      config/routes.rb

3
README.markdown

@ -13,9 +13,10 @@ This extension allows the store to use PayPal Express from two locations:
sent to the PayPal review page (along with detailed order information).
2. Cart Checkout (THIS FEATURE IS NOT YET COMPLETE) - Presents the PayPal checkout button on the users Cart page and redirects the user to complete
2. Cart Checkout* - Presents the PayPal checkout button on the users Cart page and redirects the user to complete
all shipping / addressing information on PaypPal's site. This also supports PayPal's Instant Update feature to retrieve shipping options live from
Spree when the user selects / changes their shipping address on PayPal's site.
* Check "Checkout from cart" in admin for feature to work
This extension follows the documented flow for a PayPal Express Checkout, where a user is forwarded to PayPal to allow them to login and review
the order (possibly select / change shipping address and method), then the user is redirected back to Spree to confirm the order. The user

111
app/controllers/spree/checkout_controller_decorator.rb

@ -29,7 +29,12 @@ module Spree
def paypal_payment
load_order
opts = all_opts(@order,params[:payment_method_id], 'payment')
opts.merge!(address_options(@order))
unless payment_method.preferred_cart_checkout
opts.merge!(address_options(@order))
else
opts.merge!(shipping_options)
end
@gateway = paypal_gateway
if Spree::Config[:auto_capture]
@ -61,6 +66,8 @@ module Spree
if @ppx_details.success?
# now save the updated order info
#TODO Search for existing records
Spree::PaypalAccount.create(:email => @ppx_details.params["payer"],
:payer_id => @ppx_details.params["payer_id"],
:payer_country => @ppx_details.params["payer_country"],
@ -90,11 +97,18 @@ module Spree
@order.ship_address = order_ship_address
@order.bill_address ||= order_ship_address
#Add Instant Update Shipping
if payment_method.preferred_cart_checkout
add_shipping_charge
end
end
@order.state = "payment"
@order.save
if payment_method.preferred_review
@order.next
render 'spree/shared/paypal_express_confirm'
else
@ -231,7 +245,7 @@ module Spree
:background_color => "ffffff", # must be hex only, six chars
:header_background_color => "ffffff",
:header_border_color => "ffffff",
:header_image => chosen_image,
:header_image => chosen_image,
:allow_note => true,
:locale => user_locale,
:req_confirm_shipping => false, # for security, might make an option later
@ -283,6 +297,15 @@ module Spree
credits_total = credits.map {|i| i[:amount] * i[:quantity] }.sum
end
unless @order.payment_method.preferred_cart_checkout
order_total = (order.total * 100).to_i
shipping_total = (order.ship_total*100).to_i
else
shipping_cost = shipping_options[:shipping_options].first[:amount]
order_total = (order.total * 100 + (shipping_cost)).to_i
shipping_total = (shipping_cost).to_i
end
opts = { :return_url => paypal_confirm_order_checkout_url(order, :payment_method_id => payment_method),
:cancel_return_url => edit_order_checkout_url(order, :state => :payment),
:order_id => order.number,
@ -290,8 +313,9 @@ module Spree
:items => items,
:subtotal => ((order.item_total * 100) + credits_total).to_i,
:tax => (order.tax_total*100).to_i,
:shipping => (order.ship_total*100).to_i,
:money => (order.total * 100 ).to_i }
:shipping => shipping_total,
:money => order_total,
:max_amount => (order.total * 300).to_i}
if stage == "checkout"
opts[:handling] = 0
@ -302,12 +326,40 @@ module Spree
#hack to add float rounding difference in as handling fee - prevents PayPal from rejecting orders
#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
if @order.payment_method.preferred_cart_checkout
opts[:handling] = 0
else
opts[:handling] = (order.total*100).to_i - opts.slice(:subtotal, :tax, :shipping).values.sum
end
end
opts
end
def shipping_options
#Uses users address if exists, if not uses first shipping method
if (current_user.present? && current_user.addresses.present?)
estimate_shipping_for_user
shipping_default = @rate_hash_user.map.with_index do |shipping_method, idx|
{ :default => (idx == 0 ? true : false),
:name => shipping_method.name,
:amount => (shipping_method.cost*100).to_i }
end
else
shipping_method = ShippingMethod.all.first
shipping_default = [{ :default => true,
:name => shipping_method.name,
:amount => ((shipping_method.calculator.compute(self).to_f) * 100).to_i }]
end
{
:callback_url => spree_root_url + "paypal_shipping_update",
:callback_timeout => 6,
:callback_version => '61.0',
:shipping_options => shipping_default
}
end
def address_options(order)
if payment_method.preferred_no_shipping
{ :no_shipping => true }
@ -346,22 +398,19 @@ module Spree
def flat_rate_shipping_and_handling_options(order, stage)
# max_fallback = 0.0
# shipping_options = ShippingMethod.all.map do |shipping_method|
# max_fallback = shipping_method.fallback_amount if shipping_method.fallback_amount > max_fallback
# { :name => "#{shipping_method.id}",
# :label => "#{shipping_method.name} - #{shipping_method.zone.name}",
# :amount => (shipping_method.fallback_amount*100) + 1,
# { :name => "#{shipping_method.name}",
# :amount => (shipping_method.rate),
# :default => shipping_method.is_default }
# end
#
#
# default_shipping_method = ShippingMethod.find(:first, :conditions => {:is_default => true})
#
# opts = { :shipping_options => shipping_options,
# :max_amount => (order.total + max_fallback)*100
# }
#
# opts[:shipping] = (default_shipping_method.nil? ? 0 : default_shipping_method.fallback_amount) if stage == "checkout"
#
# #opts[:shipping] = (default_shipping_method.nil? ? 0 : default_shipping_method.fallback_amount) if stage == "checkout"
# opts
{}
end
@ -389,5 +438,35 @@ module Spree
payment_method.provider
end
def add_shipping_charge
# Replace with these changes once Active_Merchant pushes pending pull request
# shipment_name = @ppx_details.shipping['amount'].chomp(" Shipping")
# shipment_cost = @ppx_details.shipping['name'].to_f
shipment_name = @ppx_details.params['UserSelectedOptions']['ShippingOptionName'].chomp(" Shipping")
shipment_cost = @ppx_details.params['UserSelectedOptions']['ShippingOptionAmount'].to_f
if @order.shipping_method_id.blank? && @order.rate_hash.present?
selected_shipping = @order.rate_hash.detect { |v| v['name'] == shipment_name && v['cost'] == shipment_cost }
@order.shipping_method_id = selected_shipping.id
end
@order.shipments.each { |s| s.destroy unless s.shipping_method.available_to_order?(@order) }
@order.create_shipment!
@order.update!
end
def estimate_shipping_for_user
zipcode = current_user.addresses.first.zipcode
country = current_user.addresses.first.country.iso
shipping_methods = Spree::ShippingMethod.all
#TODO remove hard coded shipping
#Make a deep copy of the order object then stub out the parts required to get a shipping quote
@shipping_order = Marshal::load(Marshal.dump(@order)) #Make a deep copy of the order object
@shipping_order.ship_address = Spree::Address.new(:country => Spree::Country.find_by_iso(country), :zipcode => zipcode)
shipment = Spree::Shipment.new(:address => @shipping_order.ship_address)
@shipping_order.ship_address.shipments<<shipment
@shipping_order.shipments<<shipment
@rate_hash_user = @shipping_order.rate_hash
#TODO
end
end
end

48
app/controllers/spree/paypal_express_callbacks_controller.rb

@ -8,7 +8,6 @@ module Spree
def notify
retrieve_details #need to retreive details first to ensure ActiveMerchant gets configured correctly.
@notification = Paypal::Notification.new(request.raw_post)
# we only care about eChecks (for now?)
@ -29,6 +28,39 @@ module Spree
render :nothing => true
end
def shipping_estimate
#details from Paypal
if request.post?
@method = params[:METHOD]
@version = params[:CALLBACKVERSION]
@token = params[:TOKEN]
@currency = params[:CURRENCYCODE]
@locale = params[:LOCALECODE]
@street = params[:SHIPTOSTREET]
@street2 = params[:SHIPTOSTREET2]
@city = params[:SHIPTOCITY]
@state = params[:SHIPTOSTATE]
@country = params[:SHIPTOCOUNTRY]
@zip = params[:SHIPTOZIP]
end
#available shipping based on paypal details
estimate_shipping_and_taxes
payment_methods_atts2 = {}
@rate_hash.each_with_index do |shipping_method, idx|
payment_methods_atts2["L_TAXAMT#{idx}"] = @order.tax_total #TODO need to calculate based on shipping method
payment_methods_atts2["L_SHIPPINGOPTIONAMOUNT#{idx}"] = shipping_method.cost
payment_methods_atts2["L_SHIPPINGOPTIONNAME#{idx}"] = shipping_method.name
payment_methods_atts2["L_SHIPPINGOPTIONLABEL#{idx}"] = "Shipping" #Do not change, required field
payment_methods_atts2["L_SHIPPINGOPTIONISDEFAULT#{idx}"] = (idx == 0 ? true : false)
end
#compiles NVP query used by paypal callback
query = payment_methods_atts2.inject('METHOD=CallbackResponse&CALLBACKVERSION=61&OFFERINSURANCEOPTION=false') { |string, pair| string + '&' + pair[0].to_s + '=' + pair[1].to_s }
render :text => query #query read by PayPal
end
private
def retrieve_details
@order = Spree::Order.find_by_number(params["invoice"])
@ -40,5 +72,19 @@ module Spree
end
end
def estimate_shipping_and_taxes
@order = Spree::Order.find_by_number(current_order(true).number)
zipcode = @zip
shipping_methods = Spree::ShippingMethod.all
#TODO remove hard coded shipping
#Make a deep copy of the order object then stub out the parts required to get a shipping quote
@shipping_order = Marshal::load(Marshal.dump(@order)) #Make a deep copy of the order object
@shipping_order.ship_address = Spree::Address.new(:country => Spree::Country.find_by_iso("#{@country}"), :zipcode => zipcode)
shipment = Spree::Shipment.new(:address => @shipping_order.ship_address)
@shipping_order.ship_address.shipments<<shipment
@shipping_order.shipments<<shipment
@rate_hash = @shipping_order.rate_hash
end
end
end

3
app/models/spree/billing_integration/paypal_express_base.rb

@ -4,10 +4,11 @@ class Spree::BillingIntegration::PaypalExpressBase < Spree::BillingIntegration
preference :signature, :string
preference :review, :boolean, :default => false
preference :no_shipping, :boolean, :default => false
preference :cart_checkout, :boolean, :default => false
preference :currency, :string, :default => 'USD'
preference :allow_guest_checkout, :boolean, :default => false
attr_accessible :preferred_login, :preferred_password, :preferred_signature, :preferred_review, :preferred_no_shipping, :preferred_currency, :preferred_allow_guest_checkout, :preferred_server, :preferred_test_mode
attr_accessible :preferred_login, :preferred_password, :preferred_signature, :preferred_review, :preferred_no_shipping, :preferred_currency, :preferred_allow_guest_checkout, :preferred_server, :preferred_test_mode, :preferred_cart_checkout
def provider_class
ActiveMerchant::Billing::PaypalExpressGateway

1
config/locales/en-GB.yml

@ -12,6 +12,7 @@ en-GB:
result: Result
review: Review
no_shipping: No Shipping
cart_checkout: Checkout From Cart
paypal_account: PayPal Account
payer_id: Payer ID
payer_country: Country

1
config/locales/en.yml

@ -14,6 +14,7 @@ en:
result: Result
review: Review
no_shipping: No Shipping
cart_checkout: Checkout From Cart
paypal_account: PayPal Account
payer_id: Payer ID
payer_country: Country

2
config/routes.rb

@ -12,6 +12,8 @@ Spree::Core::Engine.routes.draw do
match '/paypal_notify' => 'paypal_express_callbacks#notify', :via => [:get, :post]
match '/paypal_shipping_update' => 'paypal_express_callbacks#shipping_estimate', :via => :post
namespace :admin do
resources :orders do
resources :paypal_payments do

Loading…
Cancel
Save