From 038ae315b30fc460b4457d451936e2a3aa66c363 Mon Sep 17 00:00:00 2001 From: Brian Quinn Date: Sun, 21 Nov 2010 13:41:06 +0000 Subject: [PATCH] Added IPN support, initially only covers e-checks --- .gitignore | 1 + .../paypal_express_callbacks_controller.rb | 54 ++++++++++++++++--- app/models/paypal_txn.rb | 2 +- config/routes.rb | 2 +- lib/spree/paypal_express.rb | 48 +++++++++++++---- 5 files changed, 90 insertions(+), 17 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1377554 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +*.swp diff --git a/app/controllers/paypal_express_callbacks_controller.rb b/app/controllers/paypal_express_callbacks_controller.rb index 863ce1f..d11937c 100644 --- a/app/controllers/paypal_express_callbacks_controller.rb +++ b/app/controllers/paypal_express_callbacks_controller.rb @@ -1,8 +1,50 @@ -class PaypalExpressCallbacksController < Admin::BaseController - def index - render :text => "index" - end - def show - render :text => "text to render..." +class PaypalExpressCallbacksController < Spree::BaseController + include ActiveMerchant::Billing::Integrations + skip_before_filter :verify_authenticity_token + + def notify + @notification = Paypal::Notification.new(request.raw_post) + debugger + + # we only care about eChecks (for now?) + if @notification.params["payment_type"] == "echeck" && @notification.acknowledge + case @notification.params["payment_status"] + when "Denied" + retrieve_details + create_txn PaypalTxn::TxnType::DENIED + + when "Completed" + retrieve_details + create_txn PaypalTxn::TxnType::CAPTURE + end + + end + + render :nothing => true end + + private + def retrieve_details + @order = Order.find_by_number(@notification.params["invoice"]) + @payment = @order.checkout.payments.find(:first, + :conditions => {"transactions.txn_type" => PaypalTxn::TxnType::AUTHORIZE, + "transactions.payment_type" => @notification.params["payment_type"]}, + :joins => :transactions) + end + + def create_txn(txn_type) + if @payment.can_finalize? + @payment.finalize! + PaypalTxn.create(:payment => @payment, + :txn_type => txn_type, + :amount => @notification.params["payment_gross"].to_f, + :payment_status => @notification.params["payment_status"], + :transaction_id => @notification.params["txn_id"], + :transaction_type => @notification.params["txn_type"], + :payment_type => @notification.params["payment_type"]) + + end + + end + end diff --git a/app/models/paypal_txn.rb b/app/models/paypal_txn.rb index d5f83d3..9e33bd6 100644 --- a/app/models/paypal_txn.rb +++ b/app/models/paypal_txn.rb @@ -1,5 +1,5 @@ class PaypalTxn < Transaction - enumerable_constant :txn_type, :constants => [:authorize, :capture, :purchase, :void, :credit] + enumerable_constant :txn_type, :constants => [:authorize, :capture, :purchase, :void, :credit, :denied, :unknown] def txn_type_name TxnType.from_value(txn_type) diff --git a/config/routes.rb b/config/routes.rb index e6b349d..ee34104 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -4,7 +4,7 @@ map.resources :orders do |order| order.resource :checkout, :member => {:paypal_checkout => :any, :paypal_payment => :any, :paypal_confirm => :any, :paypal_finish => :any} end -map.resources :paypal_express_callbacks, :only => [:index] +map.paypal_notify "/paypal_notify", :controller => :paypal_express_callbacks, :action => :notify, :method => [:post, :get] map.namespace :admin do |admin| admin.resources :orders do |order| diff --git a/lib/spree/paypal_express.rb b/lib/spree/paypal_express.rb index a50cb84..bfaacac 100644 --- a/lib/spree/paypal_express.rb +++ b/lib/spree/paypal_express.rb @@ -1,4 +1,3 @@ -# aim to unpick this later module Spree::PaypalExpress include ERB::Util include ActiveMerchant::RequiresParameters @@ -7,13 +6,21 @@ 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 opts = all_opts(@order, params[:payment_method_id], 'checkout') opts.merge!(address_options(@order)) gateway = paypal_gateway - response = gateway.setup_authorization(opts[:money], opts) + if Spree::Config[:auto_capture] + response = gateway.setup_purchase(opts[:money], opts) + else + response = gateway.setup_authorization(opts[:money], opts) + end + unless response.success? gateway_error(response) redirect_to edit_order_url(@order) @@ -23,13 +30,20 @@ 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 opts = all_opts(@order,params[:payment_method_id], 'payment') opts.merge!(address_options(@order)) gateway = paypal_gateway - response = gateway.setup_authorization(opts[:money], opts) + if Spree::Config[:auto_capture] + response = gateway.setup_purchase(opts[:money], opts) + else + response = gateway.setup_authorization(opts[:money], opts) + end + unless response.success? gateway_error(response) redirect_to edit_order_checkout_url(@order, :step => "payment") @@ -39,6 +53,8 @@ module Spree::PaypalExpress 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 @@ -92,6 +108,9 @@ module Spree::PaypalExpress 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 @@ -100,13 +119,23 @@ module Spree::PaypalExpress if Spree::Config[:auto_capture] ppx_auth_response = gateway.purchase((@order.total*100).to_i, opts) - txn_type = PaypalTxn::TxnType::CAPTURE else ppx_auth_response = gateway.authorize((@order.total*100).to_i, opts) - txn_type = PaypalTxn::TxnType::AUTHORIZE end if ppx_auth_response.success? + #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 + end + paypal_account = PaypalAccount.find_by_payer_id(params[:PayerID]) payment = @order.checkout.payments.create(:amount => ppx_auth_response.params["gross_amount"].to_f, @@ -136,13 +165,17 @@ module Spree::PaypalExpress end complete_checkout - if Spree::Config[:auto_capture] + # even with auto_capture , an Auth might be returned / forced by PPX + if Spree::Config[:auto_capture] && txn_type == PaypalTxn::TxnType::CAPTURE payment.finalize! end else order_params = {} gateway_error(ppx_auth_response) + + #Failed trying to complete pending payment! + redirect_to edit_order_checkout_url(@order, :step => "payment") end end @@ -173,11 +206,8 @@ module Spree::PaypalExpress :background_color => "ffffff", # must be hex only, six chars :header_background_color => "ffffff", :header_border_color => "ffffff", - :allow_note => true, :locale => Spree::Config[:default_locale], - :notify_url => 'to be done', # this is a callback, not tried it yet - :req_confirm_shipping => false, # for security, might make an option later :user_action => user_action