require 'net/http' require 'time' module ActiveMerchant #:nodoc: module Billing #:nodoc: module Integrations #:nodoc: module Paypal # Parser and handler for incoming Instant payment notifications from paypal. # The Example shows a typical handler in a rails application. Note that this # is an example, please read the Paypal API documentation for all the details # on creating a safe payment controller. # # Example # # class BackendController < ApplicationController # include ActiveMerchant::Billing::Integrations # # def paypal_ipn # notify = Paypal::Notification.new(request.raw_post) # # if notify.masspay? # masspay_items = notify.items # end # # order = Order.find(notify.item_id) # # if notify.acknowledge # begin # # if notify.complete? and order.total == notify.amount # order.status = 'success' # # shop.ship(order) # else # logger.error("Failed to verify Paypal's notification, please investigate") # end # # rescue => e # order.status = 'failed' # raise # ensure # order.save # end # end # # render :nothing # end # end class Notification < ActiveMerchant::Billing::Integrations::Notification include PostsData def initialize(post, options = {}) super extend MassPayNotification if masspay? end # Was the transaction complete? def complete? status == "Completed" end # Is it a masspay notification? def masspay? type == "masspay" end # When was this payment received by the client. # sometimes it can happen that we get the notification much later. # One possible scenario is that our web application was down. In this case paypal tries several # times an hour to inform us about the notification def received_at parsed_time_fields = DateTime._strptime(params['payment_date'], "%H:%M:%S %b %d, %Y %Z") Time.gm( parsed_time_fields[:year], parsed_time_fields[:mon], parsed_time_fields[:mday], parsed_time_fields[:hour], parsed_time_fields[:min], parsed_time_fields[:sec] ) + Time.zone_offset(parsed_time_fields[:zone]) end # Status of transaction. List of possible values: # Canceled-Reversal:: # Completed:: # Denied:: # Expired:: # Failed:: # In-Progress:: # Partially-Refunded:: # Pending:: # Processed:: # Refunded:: # Reversed:: # Voided:: def status params['payment_status'] end # Id of this transaction (paypal number) def transaction_id params['txn_id'] end # What type of transaction are we dealing with? # "cart" "send_money" "web_accept" are possible here. def type params['txn_type'] end # the money amount we received in X.2 decimal. def gross params['mc_gross'] end # the markup paypal charges for the transaction def fee params['mc_fee'] end # What currency have we been dealing with def currency params['mc_currency'] end # This is the item number which we submitted to paypal # The custom field is also mapped to item_id because PayPal # doesn't return item_number in dispute notifications def item_id params['item_number'] || params['custom'] end # This is the invoice which you passed to paypal def invoice params['invoice'] end # Was this a test transaction? def test? params['test_ipn'] == '1' end def account params['business'] || params['receiver_email'] end # Acknowledge the transaction to paypal. This method has to be called after a new # ipn arrives. Paypal will verify that all the information we received are correct and will return a # ok or a fail. # # Example: # # def paypal_ipn # notify = PaypalNotification.new(request.raw_post) # # if notify.acknowledge # ... process order ... if notify.complete? # else # ... log possible hacking attempt ... # end def acknowledge(authcode = nil) payload = raw response = ssl_post(Paypal.service_url + '?cmd=_notify-validate', payload, 'Content-Length' => "#{payload.size}", 'User-Agent' => "Active Merchant -- http://activemerchant.org" ) raise StandardError.new("Faulty paypal result: #{response}") unless ["VERIFIED", "INVALID"].include?(response) response == "VERIFIED" end end module MassPayNotification # Mass pay returns a collection of MassPay Items, so inspect items to get the values def transaction_id end # Mass pay returns a collection of MassPay Items, so inspect items to get the values def gross end # Mass pay returns a collection of MassPay Items, so inspect items to get the values def fee end # Mass pay returns a collection of MassPay Items, so inspect items to get the values def currency end # Mass pay returns a collection of MassPay Items, so inspect items to get the values def item_id end # Mass pay returns a collection of MassPay Items, so inspect items to get the values def account end # Collection of notification items returned for MassPay transactions def items @items ||= (1..number_of_mass_pay_items).map do |item_number| MassPayItem.new( params["masspay_txn_id_#{item_number}"], params["mc_gross_#{item_number}"], params["mc_fee_#{item_number}"], params["mc_currency_#{item_number}"], params["unique_id_#{item_number}"], params["receiver_email_#{item_number}"], params["status_#{item_number}"] ) end end private def number_of_mass_pay_items @number_of_mass_pay_items ||= params.keys.select { |k| k.start_with? 'masspay_txn_id' }.size end end class MassPayItem < Struct.new(:transaction_id, :gross, :fee, :currency, :item_id, :account, :status) end end end end end