diff --git a/lib/spree/paypal_express.rb b/lib/spree/paypal_express.rb index 2ca99ce..52de761 100644 --- a/lib/spree/paypal_express.rb +++ b/lib/spree/paypal_express.rb @@ -1,11 +1,12 @@ -# WARNING: the details of UK tax and my site's shipping are a bit hard-coded here for now # aim to unpick this later module Spree::PaypalExpress include ERB::Util include Spree::PaymentGateway + include ActiveMerchant::RequiresParameters + def fixed_opts - { :description => "Goods from a Spree-based site", # site details... + { :description => "Goods from #{Spree::Config[:site_name]}", # site details... #:page_style => "foobar", # merchant account can set named config :header_image => "https://" + Spree::Config[:site_url] + "/images/logo.png", @@ -15,9 +16,16 @@ module Spree::PaypalExpress :allow_note => true, :locale => Spree::Config[:default_locale], - :notify_url => 'to be done', # this is a callback + :notify_url => 'to be done', # this is a callback, not tried it yet :req_confirm_shipping => false, # for security, might make an option later + + # :no_shipping => false, + # :address_override => false, + + # WARNING -- don't use :ship_discount, :insurance_offered, :insurance since + # they've not been tested and may trigger some paypal bugs, eg not showing order + # see http://www.pdncommunity.com/t5/PayPal-Developer-Blog/Displaying-Order-Details-in-Express-Checkout/bc-p/92902#C851 } end @@ -25,12 +33,13 @@ module Spree::PaypalExpress # might be able to get paypal to do some of the shipping choice and costing def order_opts(order) items = order.line_items.map do |item| + tax = paypal_variant_tax(item.price, item.variant) { :name => item.variant.product.name, :description => item.variant.product.description[0..120], :sku => item.variant.sku, :qty => item.quantity, - :amount => item.price - 0.15 * item.price, # avoid some rounding err, more needed - :tax => 0.15 * item.price, + :amount => item.price - tax, # TODO: test if ok on multi-qty orders + :tax => tax, :weight => item.variant.weight, :height => item.variant.height, :width => item.variant.width, @@ -42,47 +51,65 @@ module Spree::PaypalExpress :order_id => order.number, :custom => order.number, - # :no_shipping => false, - # :address_override => false, - :items => items, :subtotal => items.map {|i| i[:amount] * i[:qty] }.sum, - :handling => 0, :tax => items.map {|i| i[:tax] * i[:qty]}.sum - # WARNING -- don't use :ship_discount, => :insurance_offered, :insurance since - # they've not been tested and may trigger some paypal bugs, eg not showing order - # see http://www.pdncommunity.com/t5/PayPal-Developer-Blog/Displaying-Order-Details-in-Express-Checkout/bc-p/92902#C851 } - - opts[:email] = current_user.email if current_user - opts end - def all_opts(order) - shipping_cost = NetstoresShipping::Calculator.calculate_order_shipping(order) - opts = fixed_opts.merge(:shipping => shipping_cost).merge(order_opts order) + # hook for supplying tax amount for a single unit of a variant + # expects the sale price from the line_item and the variant itself, since + # line_item price and variant price can diverge in time + def paypal_variant_tax(sale_price, variant) + {} + end - # WARNING: paypal expects this sum to work (TODO: shift to AM code? and throw wobbly?) - # however: might be rounding issues when it comes to tax, though you can capture slightly extra - opts[:money] = opts.slice(:subtotal, :shipping, :handling, :tax).values.sum - if opts[:money] != order.total - raise "Ouch - precision problems: #{opts[:money]} vs #{order.total}" + # hook for easy site configuration, needs to return a hash + # you probably wanto over-ride the description option here, maybe the colours and logo + def paypal_site_options(order) + {} + end + + # hook to allow applications to load in their own shipping and handling costs + # eg might want to estimate from cheapest shipping option and rely on ability to + # claim an extra 15% in the final auth + def paypal_shipping_and_handling_costs(order) + {} + end + + def all_opts(order) + + opts = fixed_opts.merge(order_opts order). + merge({ :shipping => 0, :handling => 0 } ). + merge(paypal_shipping_and_handling_costs order). + merge(paypal_site_options order) + + # add the shipping and handling estimates to spree's order total + # (spree won't add them yet, since we've not officially chosen the shipping method) + spree_total = order.total + opts[:shipping] + opts[:handling] + + # paypal expects this sum to work out (TODO: shift to AM code? and throw wobbly?) + # there might be rounding issues when it comes to tax, though you can capture slightly extra + opts[:money] = opts.slice(:subtotal, :tax, :shipping, :handling).values.sum + if opts[:money] != spree_total + raise "Ouch - precision problems: #{opts[:money]} vs #{spree_total}" end + # prepare the numbers for the gateway [:money, :subtotal, :shipping, :handling, :tax].each {|amt| opts[amt] *= 100} opts[:items].each {|item| [:amount,:tax].each {|amt| item[amt] *= 100} } + # suggest current user's email or any email stored in the order + opts[:email] = current_user ? current_user.email : order.checkout.email + opts end def paypal_checkout - # need build etc? at least to finalise the total? - gateway = paypal_gateway - @order.update_totals - opts = all_opts(@order) + gateway = paypal_gateway response = gateway.setup_authorization(opts[:money], opts) gateway_error(response) unless response.success? @@ -91,9 +118,8 @@ module Spree::PaypalExpress end def paypal_finish + opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(@order) gateway = paypal_gateway - opts = { :token => params[:token], - :payer_id => params[:PayerID] }.merge all_opts(@order) info = gateway.details_for params[:token] response = gateway.authorize(opts[:money], opts) @@ -141,14 +167,15 @@ module Spree::PaypalExpress payment.creditcard_txns << transaction order.user = current_user - order.save! - order.complete # get return of status? throw of problems??? else weak go-ahead - session[:order_id] = nil if order.checkout_complete - redirect_to order_url(order, :checkout_complete => true, :order_token => session[:order_token]) - end + flash[:notice] = t('order_processed_successfully') + order_params = {:checkout_complete => true} + order_params[:order_token] = @order.token unless @order.user + session[:order_id] = nil if @order.checkout.completed_at + redirect_to order_url(@order, order_params) + end def do_capture(authorization) response = paypal_gateway.capture((100 * authorization.amount).to_i, authorization.response_code) @@ -167,17 +194,20 @@ module Spree::PaypalExpress private - # copied from main spree code, and slightly tweaked + # create the gateway from the supplied options def paypal_gateway - #? return Spree::BogusGateway.new if ENV['RAILS_ENV'] == "development" and Spree::Gateway::Config[:use_bogus] - paypal_gw = ::Gateway.find_by_name("Paypal Express UK") - gateway_config = GatewayConfiguration.find_by_gateway_id(paypal_gw.id) - config_options = {} - gateway_config.gateway_option_values.each do |option_value| - key = option_value.gateway_option.name.to_sym - config_options[key] = option_value.value - end - gateway = gateway_config.gateway.clazz.constantize.new(config_options) - end + gw_defaults = { :ppx_class => "ActiveMerchant::Billing::PaypalExpressUkGateway" } + gw_opts = gw_defaults.merge(paypal_site_options @order) + + begin + requires!(gw_opts, :ppx_class, :login, :password, :signature) + rescue ArgumentError => err + raise ArgumentError.new(<<"EOM" + err.message) +Problem with configuring Paypal Express Gateway: + You need to ensure that hook "paypal_site_options" sets values for login, password, and signature. +EOM + end + gateway = gw_opts[:ppx_class].constantize.new(gw_opts) + end end