2009-07-06 10:57:58 +00:00
|
|
|
# aim to unpick this later
|
2009-05-14 10:11:59 +00:00
|
|
|
module Spree::PaypalExpress
|
|
|
|
include ERB::Util
|
|
|
|
include Spree::PaymentGateway
|
2009-07-15 09:26:43 +00:00
|
|
|
include ActiveMerchant::RequiresParameters
|
|
|
|
|
2009-05-14 10:11:59 +00:00
|
|
|
|
|
|
|
def fixed_opts
|
2009-07-15 09:26:43 +00:00
|
|
|
{ :description => "Goods from #{Spree::Config[:site_name]}", # site details...
|
2009-05-14 10:11:59 +00:00
|
|
|
|
2009-07-06 10:57:58 +00:00
|
|
|
#:page_style => "foobar", # merchant account can set named config
|
2009-05-14 10:11:59 +00:00
|
|
|
:header_image => "https://" + Spree::Config[:site_url] + "/images/logo.png",
|
2009-07-13 13:11:53 +00:00
|
|
|
:background_color => "ffffff", # must be hex only, six chars
|
2009-05-14 10:11:59 +00:00
|
|
|
:header_background_color => "ffffff",
|
2009-07-13 13:11:53 +00:00
|
|
|
:header_border_color => "ffffff",
|
2009-05-14 10:11:59 +00:00
|
|
|
|
|
|
|
:allow_note => true,
|
|
|
|
:locale => Spree::Config[:default_locale],
|
2009-07-15 09:26:43 +00:00
|
|
|
:notify_url => 'to be done', # this is a callback, not tried it yet
|
2009-05-14 10:11:59 +00:00
|
|
|
|
|
|
|
:req_confirm_shipping => false, # for security, might make an option later
|
2009-07-15 09:26:43 +00:00
|
|
|
|
|
|
|
# :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
|
2009-05-14 10:11:59 +00:00
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2009-09-07 08:27:23 +00:00
|
|
|
# TODO: might be able to get paypal to do some of the shipping choice and costing
|
2009-05-14 10:11:59 +00:00
|
|
|
def order_opts(order)
|
|
|
|
items = order.line_items.map do |item|
|
2009-07-15 09:26:43 +00:00
|
|
|
tax = paypal_variant_tax(item.price, item.variant)
|
2009-09-07 08:27:23 +00:00
|
|
|
price = (item.price * 100).to_i # convert for gateway
|
|
|
|
tax = (tax * 100).to_i # truncate the tax slice
|
2009-05-14 10:11:59 +00:00
|
|
|
{ :name => item.variant.product.name,
|
|
|
|
:description => item.variant.product.description[0..120],
|
|
|
|
:sku => item.variant.sku,
|
|
|
|
:qty => item.quantity,
|
2009-09-07 08:27:23 +00:00
|
|
|
:amount => price - tax,
|
2009-07-15 09:26:43 +00:00
|
|
|
:tax => tax,
|
2009-05-14 10:11:59 +00:00
|
|
|
:weight => item.variant.weight,
|
|
|
|
:height => item.variant.height,
|
|
|
|
:width => item.variant.width,
|
|
|
|
:depth => item.variant.weight }
|
|
|
|
end
|
|
|
|
|
2009-07-06 10:57:58 +00:00
|
|
|
opts = { :return_url => request.protocol + request.host_with_port + "/orders/#{order.number}/paypal_finish",
|
|
|
|
:cancel_return_url => "http://" + request.host_with_port + "/orders/#{order.number}/edit",
|
2009-05-14 10:11:59 +00:00
|
|
|
:order_id => order.number,
|
2009-07-06 10:57:58 +00:00
|
|
|
:custom => order.number,
|
2009-05-14 10:11:59 +00:00
|
|
|
|
|
|
|
:items => items,
|
|
|
|
}
|
2009-07-15 09:26:43 +00:00
|
|
|
opts
|
|
|
|
end
|
2009-07-06 10:57:58 +00:00
|
|
|
|
2009-07-15 09:26:43 +00:00
|
|
|
# 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)
|
2009-07-20 17:07:49 +00:00
|
|
|
0.0
|
2009-07-15 09:26:43 +00:00
|
|
|
end
|
2009-07-06 10:57:58 +00:00
|
|
|
|
2009-07-15 09:26:43 +00:00
|
|
|
# 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)
|
|
|
|
{}
|
2009-07-06 10:57:58 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def all_opts(order)
|
2009-07-15 09:26:43 +00:00
|
|
|
|
|
|
|
opts = fixed_opts.merge(order_opts order).
|
|
|
|
merge({ :shipping => 0, :handling => 0 } ).
|
|
|
|
merge(paypal_shipping_and_handling_costs order).
|
|
|
|
merge(paypal_site_options order)
|
|
|
|
|
2009-09-07 08:27:23 +00:00
|
|
|
# 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
|
|
|
|
|
|
|
|
# prepare the shipping and handling costs
|
|
|
|
[:shipping, :handling].each {|amt| opts[amt] *= 100 }
|
|
|
|
|
|
|
|
# overall total
|
|
|
|
opts[:money] = opts.slice(:subtotal, :tax, :shipping, :handling).values.sum
|
|
|
|
|
|
|
|
# # 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
|
|
|
|
# if opts[:money] != spree_total
|
|
|
|
# raise "Ouch - precision problems: #{opts[:money]} vs #{spree_total}"
|
|
|
|
# if (opts[:money].to_f - spree_total.to_f).abs > 0.01
|
|
|
|
# raise "Ouch - precision problems: #{opts[:money].to_f} vs #{spree_total.to_f}, diff #{opts[:money].to_f - spree_total.to_f}"
|
|
|
|
# end
|
2009-05-14 10:11:59 +00:00
|
|
|
|
2009-07-15 09:26:43 +00:00
|
|
|
# suggest current user's email or any email stored in the order
|
|
|
|
opts[:email] = current_user ? current_user.email : order.checkout.email
|
|
|
|
|
2009-05-14 10:11:59 +00:00
|
|
|
opts
|
|
|
|
end
|
|
|
|
|
|
|
|
def paypal_checkout
|
2009-09-07 11:10:32 +00:00
|
|
|
# fix a shipping method if not already done - DISABLE - avoid spree totals interference
|
|
|
|
# @order.checkout.shipment.shipping_method ||= ShippingMethod.first
|
|
|
|
# @order.checkout.shipment.save
|
|
|
|
|
2009-05-14 10:11:59 +00:00
|
|
|
opts = all_opts(@order)
|
2009-07-15 09:26:43 +00:00
|
|
|
gateway = paypal_gateway
|
2009-07-06 10:57:58 +00:00
|
|
|
response = gateway.setup_authorization(opts[:money], opts)
|
|
|
|
|
|
|
|
gateway_error(response) unless response.success?
|
2009-05-14 10:11:59 +00:00
|
|
|
|
2009-07-06 10:57:58 +00:00
|
|
|
redirect_to (gateway.redirect_url_for response.token)
|
2009-05-14 10:11:59 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
def paypal_finish
|
2009-08-11 13:07:16 +00:00
|
|
|
order = Order.find_by_number(params[:id])
|
|
|
|
|
|
|
|
opts = { :token => params[:token], :payer_id => params[:PayerID] }.merge all_opts(order)
|
2009-05-14 10:11:59 +00:00
|
|
|
gateway = paypal_gateway
|
2009-08-11 13:19:09 +00:00
|
|
|
|
2009-05-14 10:11:59 +00:00
|
|
|
info = gateway.details_for params[:token]
|
2009-08-11 13:19:09 +00:00
|
|
|
gateway_error(info) unless info.success?
|
2009-05-14 10:46:11 +00:00
|
|
|
|
2009-05-14 10:11:59 +00:00
|
|
|
|
2009-09-07 09:24:55 +00:00
|
|
|
# now save the order info
|
2009-07-06 10:57:58 +00:00
|
|
|
order.checkout.email = info.email
|
|
|
|
order.checkout.special_instructions = info.params["note"]
|
2009-09-07 09:24:55 +00:00
|
|
|
order.checkout.save
|
|
|
|
order.update_attribute(:user, current_user)
|
2009-05-14 10:11:59 +00:00
|
|
|
|
2009-09-07 09:24:55 +00:00
|
|
|
# save the address info
|
2009-05-14 10:11:59 +00:00
|
|
|
ship_address = info.address
|
2009-07-13 11:55:24 +00:00
|
|
|
order_ship_address = Address.new :firstname => info.params["first_name"],
|
|
|
|
:lastname => info.params["last_name"],
|
|
|
|
:address1 => ship_address["address1"],
|
|
|
|
:address2 => ship_address["address2"],
|
|
|
|
:city => ship_address["city"],
|
|
|
|
:country => Country.find_by_iso(ship_address["country"]),
|
|
|
|
:zipcode => ship_address["zip"],
|
2009-08-11 13:19:09 +00:00
|
|
|
# phone is currently blanked in AM's PPX response lib
|
|
|
|
:phone => info.params["phone"] || "(not given)"
|
2009-07-13 11:55:24 +00:00
|
|
|
|
2009-07-13 11:59:16 +00:00
|
|
|
if (state = State.find_by_name(ship_address["state"]))
|
2009-07-13 11:55:24 +00:00
|
|
|
order_ship_address.state = state
|
|
|
|
else
|
|
|
|
order_ship_address.state_name = ship_address["state"]
|
|
|
|
end
|
|
|
|
order_ship_address.save!
|
2009-05-14 10:11:59 +00:00
|
|
|
|
2009-09-07 09:24:55 +00:00
|
|
|
# TODO: refine/choose the shipping method via paypal, or in advance
|
2009-09-07 11:10:32 +00:00
|
|
|
order.checkout.shipment.update_attributes :address => order_ship_address
|
2009-09-07 09:24:55 +00:00
|
|
|
|
|
|
|
|
|
|
|
# now do the authorization and build the record of payment
|
|
|
|
# use the info total from paypal, in case the user has changed their order
|
2009-09-07 11:10:32 +00:00
|
|
|
response = gateway.authorize(opts[:money], opts)
|
2009-09-07 09:24:55 +00:00
|
|
|
gateway_error(response) unless response.success?
|
|
|
|
|
2009-07-06 10:57:58 +00:00
|
|
|
fake_card = Creditcard.new :checkout => order.checkout,
|
2009-09-07 09:24:55 +00:00
|
|
|
:cc_type => "visa", # fixed set of labels here
|
2009-05-14 10:11:59 +00:00
|
|
|
:month => Time.now.month,
|
|
|
|
:year => Time.now.year,
|
|
|
|
:first_name => info.params["first_name"],
|
|
|
|
:last_name => info.params["last_name"],
|
|
|
|
:display_number => "paypal:" + info.payer_id
|
2009-09-07 11:10:32 +00:00
|
|
|
payment = order.paypal_payments.create(:amount => response.params["gross_amount"].to_f,
|
2009-05-14 10:11:59 +00:00
|
|
|
:creditcard => fake_card)
|
|
|
|
|
|
|
|
# query - need 0 in amount for an auth? see main code
|
2009-09-07 11:10:32 +00:00
|
|
|
transaction = CreditcardTxn.new( :amount => response.params["gross_amount"].to_f,
|
2009-05-14 10:11:59 +00:00
|
|
|
:response_code => response.authorization,
|
|
|
|
:txn_type => CreditcardTxn::TxnType::AUTHORIZE)
|
|
|
|
payment.creditcard_txns << transaction
|
|
|
|
|
2009-09-07 11:10:32 +00:00
|
|
|
# save this for future reference
|
|
|
|
order.checkout.shipment.shipping_method ||= ShippingMethod.first
|
|
|
|
order.checkout.shipment.save
|
|
|
|
|
2009-05-14 10:11:59 +00:00
|
|
|
order.save!
|
|
|
|
order.complete # get return of status? throw of problems??? else weak go-ahead
|
|
|
|
|
2009-09-07 09:24:55 +00:00
|
|
|
# todo - share code
|
2009-07-15 09:26:43 +00:00
|
|
|
flash[:notice] = t('order_processed_successfully')
|
|
|
|
order_params = {:checkout_complete => true}
|
2009-08-11 13:07:16 +00:00
|
|
|
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)
|
2009-07-15 09:26:43 +00:00
|
|
|
end
|
2009-07-06 10:57:58 +00:00
|
|
|
|
|
|
|
def do_capture(authorization)
|
|
|
|
response = paypal_gateway.capture((100 * authorization.amount).to_i, authorization.response_code)
|
|
|
|
|
|
|
|
gateway_error(response) unless response.success?
|
|
|
|
|
|
|
|
# TODO needs to be cleaned up or recast...
|
|
|
|
payment = PaypalPayment.find(authorization.creditcard_payment_id)
|
|
|
|
|
|
|
|
# create a transaction to reflect the capture
|
|
|
|
payment.txns << CreditcardTxn.new( :amount => authorization.amount,
|
|
|
|
:response_code => response.authorization,
|
|
|
|
:txn_type => CreditcardTxn::TxnType::CAPTURE )
|
2009-09-22 19:40:24 +00:00
|
|
|
payment.save
|
2009-07-06 10:57:58 +00:00
|
|
|
end
|
|
|
|
|
|
|
|
|
2009-05-14 10:11:59 +00:00
|
|
|
private
|
|
|
|
|
2009-07-15 09:26:43 +00:00
|
|
|
# create the gateway from the supplied options
|
2009-05-14 10:11:59 +00:00
|
|
|
def paypal_gateway
|
2009-07-15 09:26:43 +00:00
|
|
|
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:
|
2009-07-20 17:07:49 +00:00
|
|
|
You need to ensure that hook "paypal_site_options" sets values for login, password, and signature.
|
|
|
|
It currently produces: #{paypal_site_options.inspect}
|
2009-07-15 09:26:43 +00:00
|
|
|
EOM
|
|
|
|
end
|
|
|
|
|
|
|
|
gateway = gw_opts[:ppx_class].constantize.new(gw_opts)
|
2009-05-14 10:11:59 +00:00
|
|
|
end
|
|
|
|
end
|