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
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