USBSwiper Home » PayPal Instant Payment Notification (IPN) PHP Template
Questions? Call (224) 677-0283

PayPal Instant Payment Notification (IPN) PHP Template

NOTE: This template has been turned into an installable solution available at PayPalDeveloper.com.  The template here will still work fine, but you might like the full solution better.

PayPal’s IPN feature is a great way to easily integrate standard payment buttons or automate back-end procedures for Payments Pro solutions. While it’s actually very simple, the process can be confusing when you first begin developing and I’ve seen people give up before they even truly get started.

The following script is a basic PHP template that handles everything you need to get started with PayPal Instant Payment Notification. There are a couple of simple options at the top of the script for handling test servers and SSL. The template handles verifying the POST data with PayPal’s servers to ensure it’s valid and then stores every possible IPN value in PHP variables that are ready for use. If a particular field was not included in the IPN received then it’s simply populated with an empty value, but it’s still available within the script.

You can begin at the very bottom and simply do whatever you want with the store data, whether it’s update a database, format an email and send it to customers, send a text message notification to customers or yourself, etc.

I’ve also included a SQL file you can  use to easily create database tables for use with PayPal’s IPN system.  Later I’ll be completing this entire solution as an easily installed all-in-one package.

Happy scripting!

[sourcecode language="php"]

////////////////////////////////////////////////////////////
// Angell EYE : PayPal IPN Template : 02.01.2009 //////////
//////////////// angelleye.com  //////////////////////
/////////////////////////////////////////////////////////
// PayPal now provides a variable called test_ipn on sandbox IPN's for simple flagging of sandbox IPN vs. production IPN
$sandbox = isset($_POST['test_ipn']) ? true : false;
$ssl = $sandbox ? false : false;
$ppHost = $sandbox ? 'www.sandbox.paypal.com' : 'www.paypal.com';

// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';

// Store each $_POST value in a NVP string: 1 string encoded and 1 string decoded
$IPNDecoded = '';
foreach ($_POST as $key => $value)
{
$value = urlencode(stripslashes($value));
$req .= “&$key=$value”;
$IPNDecoded .= $key . ” = ” . urldecode($value) .”

“;
}

// post back to PayPal system to validate using SSL or not based on flag set above.
if($ssl)
{
$header = ”;
$header .= “POST /cgi-bin/webscr HTTP/1.0rn”;
$header .= “Host: ” . $ppHost . “:443rn”;
$header .= “Content-Type: application/x-www-form-urlencodedrn”;
$header .= “Content-Length: ” . strlen($req) . “rnrn”;
$fp = fsockopen (‘ssl://’ . $ppHost, 443, $errno, $errstr, 30);
}
else
{
$header = ”;
$header .= “POST /cgi-bin/webscr HTTP/1.0rn”;
$header .= “Host: ” . $ppHost . “:80rn”;
$header .= “Content-Type: application/x-www-form-urlencodedrn”;
$header .= “Content-Length: ” . strlen($req) . “rnrn”;
$fp = fsockopen ($ppHost, 80, $errno, $errstr, 30);
}

if (!$fp)
{
$IsValid = false;
}
else
{
// Response from PayPal was good.  Now check to see if it returned verified or invalid.  Simply set $IsValud to true/false accordingly.
fputs ($fp, $header . $req);
while(!feof($fp))
{
$res = fgets ($fp, 1024);
if(strcmp ($res, “VERIFIED”) == 0)
$IsValid = true;
elseif (strcmp ($res, “INVALID”) == 0)
$IsValid = false;
}
fclose ($fp);
}

// Buyer Information
$address_city = isset($_POST['address_city']) ? $_POST['address_city'] : ”;
$address_country = isset($_POST['address_country']) ? $_POST['address_country'] : ”;
$address_country_code = isset($_POST['address_country_code']) ? $_POST['address_country_code'] : ”;
$address_name = isset($_POST['address_name']) ? $_POST['address_name'] : ”;
$address_state = isset($_POST['address_state']) ? $_POST['address_state'] : ”;
$address_status = isset($_POST['address_status']) ? $_POST['address_status'] : ”;
$address_street = isset($_POST['address_street']) ? $_POST['address_street'] : ”;
$address_zip = isset($_POST['address_zip']) ? $_POST['address_zip'] : ”;
$first_name = isset($_POST['first_name']) ? $_POST['first_name'] : ”;
$last_name = isset($_POST['last_name']) ? $_POST['last_name'] : ”;
$payer_business_name = isset($_POST['payer_business_name']) ? $_POST['payer_business_name'] : ”;
$payer_email = isset($_POST['payer_email']) ? $_POST['payer_email'] : ”;
$payer_id = isset($_POST['payer_id']) ? $_POST['payer_id'] : ”;
$payer_status = isset($_POST['payer_status']) ? $_POST['payer_status'] : ”;
$contact_phone = isset($_POST['contact_phone']) ? $_POST['contact_phone'] : ”;
$residence_country = isset($_POST['residence_country']) ? $_POST['residence_country'] : ”;

// Basic Information
$notify_version = isset($_POST['notify_version']) ? $_POST['notify_version'] : ”;
$charset = isset($_POST['charset']) ? $_POST['charset'] : ”;
$business = isset($_POST['business']) ? $_POST['business'] : ”;
$item_name = isset($_POST['item_name']) ? $_POST['item_name'] : ”;
$item_number = isset($_POST['item_number']) ? $_POST['item_number'] : ”;
$quantity = isset($_POST['quantity']) ? $_POST['quantity'] : ”;
$receiver_email = isset($_POST['receiver_email']) ? $_POST['receiver_email'] : ”;
$receiver_id = isset($_POST['receiver_id']) ? $_POST['receiver_id'] : ”;

// Cart Items
$num_cart_items = isset($_POST['num_cart_items']) ? $_POST['num_cart_items'] : ”;

$i = 1;
$cart_items = array();
while(isset($_POST['item_number' . $i]))
{
$item_number = isset($_POST['item_number' . $i]) ? $_POST['item_number' . $i] : ”;
$item_name = isset($_POST['item_name' . $i]) ? $_POST['item_name' . $i] : ”;
$quantity = isset($_POST['quantity' . $i]) ? $_POST['quantity' . $i] : ”;
$mc_gross = isset($_POST['mc_gross_' . $i]) ? $_POST['mc_gross_' . $i] : ”;
$mc_handling = isset($_POST['mc_handling' . $i]) ? $_POST['mc_handling' . $i] : ”;
$mc_shipping = isset($_POST['mc_shipping' . $i]) ? $_POST['mc_shipping' . $i] : ”;
$custom = isset($_POST['custom' . $i]) ? $_POST['custom' . $i] : ”;
$option_name1 = isset($_POST['option_name1_' . $i]) ? $_POST['option_name1_' . $i] : ”;
$option_selection1 = isset($_POST['option_selection1_' . $i]) ? $_POST['option_selection1_' . $i] : ”;
$option_name2 = isset($_POST['option_name2_' . $i]) ? $_POST['option_name2_' . $i] : ”;
$option_selection2 = isset($_POST['option_selection2_' . $i]) ? $_POST['option_selection2_' . $i] : ”;

$current_item = array(
‘item_number’ => $item_number,
‘item_name’ => $item_name,
‘quantity’ => $quantity,
‘mc_gross’ => $mc_gross,
‘mc_handling’ => $mc_handling,
‘mc_shipping’ => $mc_shipping,
‘custom’ => $custom,
‘option_name1′ => $option_name1,
‘option_selection1′ => $option_selection1,
‘option_name2′ => $option_name2,
‘option_selection2′ => $option_selection2
);

array_push($cart_items, $current_item);
$i++;
}

// Advanced and Custom Information
$custom = isset($_POST['custom']) ? $_POST['custom'] : ”;
$invoice = isset($_POST['invoice']) ? $_POST['invoice'] : ”;
$memo = isset($_POST['memo']) ? $_POST['memo'] : ”;
$option_name1 = isset($_POST['option_name1']) ? $_POST['option_name1'] : ”;
$option_selection1 = isset($_POST['option_selection1']) ? $_POST['option_selection1'] : ”;
$option_name2 = isset($_POST['option_name2']) ? $_POST['option_name2'] : ”;
$option_selection2 = isset($_POST['option_selection2']) ? $_POST['option_selection2'] : ”;
$tax = isset($_POST['tax']) ? $_POST['tax'] : ”;

// Website Payments Standard, Website Payments Pro, and Refund Information
$auth_id = isset($_POST['auth_id']) ? $_POST['auth_id'] : ”;
$auth_exp = isset($_POST['auth_exp']) ? $_POST['auth_exp'] : ”;
$auth_amount = isset($_POST['auth_amount']) ? $_POST['auth_amount'] : ”;
$auth_status = isset($_POST['auth_status']) ? $_POST['auth_status'] : ”;

// Fraud Management Filters
$i = 1;
$fraud_management_filters = array();
while(isset($_POST['fraud_management_filters_' . $i]))
{
$filter_name = isset($_POST['fraud_management_filter_' . $i]) ? $_POST['fraud_management_filter_' . $i] : ”;

array_push($fraud_management_filters, $filter_name);
$i++;
}

$mc_gross = isset($_POST['mc_gross']) ? $_POST['mc_gross'] : ”;
$mc_handling = isset($_POST['mc_handling']) ? $_POST['mc_handling'] : ”;
$mc_shipping = isset($_POST['mc_shipping']) ? $_POST['mc_shipping'] : ”;
$mc_fee = isset($_POST['mc_fee']) ? $_POST['mc_fee'] : ”;
$num_cart_items = isset($_POST['num_cart_items']) ? $_POST['num_cart_items'] : ”;
$parent_txn_id = isset($_POST['parent_txn_id']) ? $_POST['parent_txn_id'] : ”;
$payment_date = isset($_POST['payment_date']) ? $_POST['payment_date'] : ”;
$payment_status = isset($_POST['payment_status']) ? $_POST['payment_status'] : ”;
$payment_type = isset($_POST['payment_type']) ? $_POST['payment_type'] : ”;
$pending_reason = isset($_POST['pending_reason']) ? $_POST['pending_reason'] : ”;
$protection_eligibility  = isset($_POST['protection_eligibility']) ? $_POST['protection_eligibility'] : ”;
$reason_code = isset($_POST['reason_code']) ? $_POST['reason_code'] : ”;
$remaining_settle = isset($_POST['remaining_settle']) ? $_POST['remaining_settle'] : ”;
$shipping_method = isset($_POST['shipping_method']) ? $_POST['shipping_method'] : ”;
$shipping = isset($_POST['shipping']) ? $_POST['shipping'] : ”;
$tax = isset($_POST['tax']) ? $_POST['tax'] : ”;
$transaction_entity = isset($_POST['transaction_entity']) ? $_POST['transaction_entity'] : ”;
$txn_id = isset($_POST['txn_id']) ? $_POST['txn_id'] : ”;
$txn_type = isset($_POST['txn_type']) ? $_POST['txn_type'] : ”;
// Currency and Currency Exchange Information
$exchange_rate = isset($_POST['exchange_rate']) ? $_POST['exchange_rate'] : ”;
$mc_currency = isset($_POST['mc_currency']) ? $_POST['mc_currency'] : ”;
$settle_amount = isset($_POST['settle_amount']) ? $_POST['settle_amount'] : ”;
$settle_currency = isset($_POST['settle_currency']) ? $_POST['settle_currency'] : ”;

// Auction Variables
$auction_buyer_id = isset($_POST['auction_buyer_id']) ? $_POST['auction_buyer_id'] : ”;
$auction_closing_date = isset($_POST['auction_closing_date']) ? $_POST['auction_closing_date'] : ”;
$auction_multi_item = isset($_POST['auction_multi_item']) ? $_POST['auction_multi_item'] : ”;
$for_auction = isset($_POST['for_auction']) ? $_POST['for_auction'] : ”;

// Mass Payments
$i = 1;
$mass_payments = array();
while(isset($_POST['masspay_txn_id_' . $i]))
{
$masspay_txn_id = isset($_POST['masspay_txn_id_' . $i]) ? $_POST['masspay_txn_id_' . $i] : ”;
$mc_currency = isset($_POST['mc_currency_' . $i]) ? $_POST['mc_currency_' . $i] : ”;
$mc_fee = isset($_POST['mc_fee_' . $i]) ? $_POST['mc_fee_' . $i] : ”;
$mc_gross = isset($_POST['mc_gross_' . $i]) ? $_POST['mc_gross_' . $i] : ”;
$receiver_email = isset($_POST['receiver_email_' . $i]) ? $_POST['receiver_email_' . $i] : ”;
$status = isset($_POST['status_' . $i]) ? $_POST['status_' . $i] : ”;
$unique_id = isset($_POST['unique_id_' . $i]) ? $_POST['unique_id_' . $i] : ”;

$current_payment_data_set = array(
‘masspay_txn_id’ => $masspay_txn_id,
‘mc_currency’ => $mc_currency,
‘mc_fee’ => $mc_fee,
‘mc_gross’ => $mc_gross,
‘receiver_email’ => $receiver_email,
‘status’ => $status,
‘unique_id’ => $unique_id
);

array_push($mass_payments, $current_payment_data_set);
$i++;
}
// Recurring Payments Information
$initial_payment_status = isset($_POST['initial_payment_status']) ? $_POST['initial_payment_status'] : ”;
$initial_payment_txn_id = isset($_POST['initial_payment_txn_id']) ? $_POST['initial_payment_txn_id'] : ”;
$recurring_payment_id = isset($_POST['recurring_payment_id']) ? $_POST['recurring_payment_id'] : ”;
$product_name = isset($_POST['product_name']) ? $_POST['product_name'] : ”;
$product_type = isset($_POST['product_type']) ? $_POST['product_type'] : ”;
$period_type = isset($_POST['period_type']) ? $_POST['period_type'] : ”;
$payment_cycle = isset($_POST['payment_cycle']) ? $_POST['payment_cycle'] : ”;
$outstanding_balance = isset($_POST['outstanding_balance']) ? $_POST['outstanding_balance'] : ”;
$amount_per_cycle = isset($_POST['amount_per_cycle']) ? $_POST['amount_per_cycle'] : ”;
$initial_payment_amount = isset($_POST['initial_payment_amount']) ? $_POST['initial_payment_amount'] : ”;
$profile_status = isset($_POST['profile_status']) ? $_POST['profile_status'] : ”;
$amount = isset($_POST['amount']) ? $_POST['amount'] : ”;
$time_created = isset($_POST['time_created']) ? $_POST['time_created'] : ”;
$next_payment_date = isset($_POST['next_payment_date']) ? $_POST['next_payment_date'] : ”;
$rp_invoice_id = isset($_POST['rp_invoice_id']) ? $_POST['rp_invoice_id'] : ”;

// Subscription Variables
$subscr_date = isset($_POST['subscr_date']) ? $_POST['subscr_date'] : ”;
$subscr_effective = isset($_POST['subscr_effective']) ? $_POST['subscr_effective'] : ”;
$period1 = isset($_POST['period1']) ? $_POST['period1'] : ”;
$period2 = isset($_POST['period2']) ? $_POST['period2'] : ”;
$period3 = isset($_POST['period3']) ? $_POST['period3'] : ”;
$amount1 = isset($_POST['amount1']) ? $_POST['amount1'] : ”;
$amount2 = isset($_POST['amount2']) ? $_POST['amount2'] : ”;
$amount3 = isset($_POST['amount3']) ? $_POST['amount3'] : ”;
$mc_amount1 = isset($_POST['mc_amount1']) ? $_POST['mc_amount1'] : ”;
$mc_amount2 = isset($_POST['mc_amount2']) ? $_POST['mc_amount2'] : ”;
$mc_amount3 = isset($_POST['mc_amount3']) ? $_POST['mc_amount3'] : ”;
$mc_currency = isset($_POST['mc_currency']) ? $_POST['mc_currency'] : ”;
$recurring = isset($_POST['recurring']) ? $_POST['recurring'] : ”;
$reattempt = isset($_POST['reattempt']) ? $_POST['reattempt'] : ”;
$retry_at = isset($_POST['retry_at']) ? $_POST['retry_at'] : ”;
$recur_times = isset($_POST['recur_times']) ? $_POST['recur_times'] : ”;
$username = isset($_POST['username']) ? $_POST['username'] : ”;
$password = isset($_POST['password']) ? $_POST['password'] : ”;
$subscr_id = isset($_POST['subscr_id']) ? $_POST['subscr_id'] : ”;

// Dispute Notification Variables
$case_id = isset($_POST['case_id']) ? $_POST['case_id'] : ”;
$case_type = isset($_POST['case_type']) ? $_POST['case_type'] : ”;
$case_creation_date = isset($_POST['case_creation_date']) ? $_POST['case_creation_date'] : ”;
?>
[/sourcecode]

And here is a SQL file you can use to easily create the database tables.

[sourcecode language="sql"]

– phpMyAdmin SQL Dump
– version 3.1.1
– http://www.phpmyadmin.net

– Host: localhost
– Generation Time: Feb 20, 2009 at 11:17 PM
– Server version: 5.0.67
– PHP Version: 5.2.6

SET SQL_MODE=”NO_AUTO_VALUE_ON_ZERO”;


– Database: `paypal_ipn`

– ——————————————————–


– Table structure for table `paypal_disputes`

CREATE TABLE IF NOT EXISTS `paypal_disputes` (
`id` tinyint(10) NOT NULL auto_increment,
`txn_id` varchar(25) collate utf8_bin NOT NULL,
`case_id` varchar(25) collate utf8_bin NOT NULL,
`case_type` varchar(25) collate utf8_bin NOT NULL,
`case_creation_date` varchar(100) collate utf8_bin NOT NULL,
`payment_date` varchar(100) collate utf8_bin NOT NULL,
`receipt_id` varchar(25) collate utf8_bin NOT NULL,
`verify_sign` varchar(255) collate utf8_bin NOT NULL,
`payer_email` varchar(127) collate utf8_bin NOT NULL,
`payer_id` varchar(20) collate utf8_bin NOT NULL,
`invoice` varchar(127) collate utf8_bin NOT NULL,
`reason_code` varchar(25) collate utf8_bin NOT NULL,
`custom` varchar(255) collate utf8_bin NOT NULL,
`notify_version` varchar(25) collate utf8_bin NOT NULL,
`creation_timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


– Dumping data for table `paypal_disputes`

– ——————————————————–


– Table structure for table `paypal_ipn_log`

CREATE TABLE IF NOT EXISTS `paypal_ipn_log` (
`id` tinyint(4) NOT NULL auto_increment,
`created_timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
`ipn_data_serialized` text collate utf8_bin NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


– Dumping data for table `paypal_ipn_log`

– ——————————————————–


– Table structure for table `paypal_mass_payments`

CREATE TABLE IF NOT EXISTS `paypal_mass_payments` (
`id` tinyint(4) NOT NULL auto_increment,
`masspay_txn_id` varchar(25) collate utf8_bin NOT NULL,
`mc_currency` varchar(50) collate utf8_bin NOT NULL,
`mc_fee` double NOT NULL,
`mc_gross` double NOT NULL,
`receiver_email` varchar(127) collate utf8_bin NOT NULL,
`status` varchar(25) collate utf8_bin NOT NULL,
`unique_id` varchar(20) collate utf8_bin NOT NULL,
`creation_timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


– Dumping data for table `paypal_mass_payments`

– ——————————————————–


– Table structure for table `paypal_orders`

CREATE TABLE IF NOT EXISTS `paypal_orders` (
`id` tinyint(10) unsigned NOT NULL auto_increment,
`receiver_email` varchar(127) collate utf8_bin NOT NULL,
`payment_status` varchar(25) collate utf8_bin NOT NULL,
`pending_reason` varchar(25) collate utf8_bin NOT NULL,
`payment_date` varchar(100) collate utf8_bin NOT NULL,
`mc_gross` double NOT NULL,
`mc_fee` double NOT NULL,
`tax` double NOT NULL,
`mc_currency` varchar(10) collate utf8_bin NOT NULL,
`txn_id` varchar(25) collate utf8_bin NOT NULL,
`txn_type` varchar(25) collate utf8_bin NOT NULL,
`first_name` varchar(75) collate utf8_bin NOT NULL,
`last_name` varchar(75) collate utf8_bin NOT NULL,
`address_street` varchar(200) collate utf8_bin NOT NULL,
`address_city` varchar(50) collate utf8_bin NOT NULL,
`address_state` varchar(40) collate utf8_bin NOT NULL,
`address_zip` varchar(20) collate utf8_bin NOT NULL,
`address_country` varchar(64) collate utf8_bin NOT NULL,
`address_status` varchar(25) collate utf8_bin NOT NULL,
`payer_email` varchar(127) collate utf8_bin NOT NULL,
`payer_status` varchar(25) collate utf8_bin NOT NULL,
`payment_type` varchar(25) collate utf8_bin NOT NULL,
`notify_version` varchar(50) collate utf8_bin NOT NULL,
`verify_sign` varchar(255) collate utf8_bin NOT NULL,
`address_name` varchar(130) collate utf8_bin NOT NULL,
`protection_eligibility` varchar(50) collate utf8_bin NOT NULL,
`ipn_status` varchar(25) collate utf8_bin NOT NULL,
`subscr_id` varchar(25) collate utf8_bin NOT NULL,
`custom` varchar(255) collate utf8_bin NOT NULL,
`reason_code` varchar(25) collate utf8_bin NOT NULL,
`contact_phone` varchar(25) collate utf8_bin NOT NULL,
`item_name` varchar(127) collate utf8_bin NOT NULL,
`item_number` varchar(127) collate utf8_bin NOT NULL,
`invoice` varchar(127) collate utf8_bin NOT NULL,
`for_auction` tinyint(10) NOT NULL,
`auction_buyer_id` varchar(75) collate utf8_bin NOT NULL,
`auction_closing_date` varchar(100) collate utf8_bin NOT NULL,
`auction_multi_item` double NOT NULL default ’1′,
`creation_timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
`address_country_code` varchar(2) collate utf8_bin NOT NULL,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


– Dumping data for table `paypal_orders`

– ——————————————————–


– Table structure for table `paypal_order_items`

CREATE TABLE IF NOT EXISTS `paypal_order_items` (
`id` tinyint(10) NOT NULL auto_increment,
`order_id` tinyint(10) NOT NULL,
`subscr_id` varchar(25) collate utf8_bin NOT NULL,
`item_name` varchar(130) collate utf8_bin NOT NULL,
`item_number` varchar(130) collate utf8_bin NOT NULL,
`os0` varchar(200) collate utf8_bin NOT NULL,
`on0` varchar(75) collate utf8_bin NOT NULL,
`os1` varchar(200) collate utf8_bin NOT NULL,
`on1` varchar(75) collate utf8_bin NOT NULL,
`quantity` double NOT NULL default ’0′,
`custom` varchar(255) collate utf8_bin NOT NULL,
`mc_gross` double NOT NULL default ’0′,
`mc_handling` double NOT NULL default ’0′,
`mc_shipping` double NOT NULL default ’0′,
`creation_timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


– Dumping data for table `paypal_order_items`

– ——————————————————–


– Table structure for table `paypal_recurring_payments`

CREATE TABLE IF NOT EXISTS `paypal_recurring_payments` (
`id` tinyint(10) NOT NULL auto_increment,
`mc_gross` double NOT NULL default ’0′,
`protection_eligibility` varchar(50) collate utf8_bin NOT NULL,
`payment_date` varchar(100) collate utf8_bin NOT NULL,
`payment_status` varchar(25) collate utf8_bin NOT NULL,
`mc_fee` double NOT NULL default ’0′,
`notify_version` varchar(25) collate utf8_bin NOT NULL,
`payer_status` varchar(25) collate utf8_bin NOT NULL,
`currency_code` varchar(10) collate utf8_bin NOT NULL,
`verify_sign` varchar(255) collate utf8_bin NOT NULL,
`amount` double NOT NULL default ’0′,
`txn_id` varchar(25) collate utf8_bin NOT NULL,
`payment_type` varchar(25) collate utf8_bin NOT NULL,
`receiver_email` varchar(130) collate utf8_bin NOT NULL,
`receiver_id` varchar(15) collate utf8_bin NOT NULL,
`txn_type` varchar(25) collate utf8_bin NOT NULL,
`mc_currency` varchar(25) collate utf8_bin NOT NULL,
`residence_country` varchar(2) collate utf8_bin NOT NULL,
`receipt_id` varchar(50) collate utf8_bin NOT NULL,
`transaction_subject` varchar(150) collate utf8_bin NOT NULL,
`shipping` double NOT NULL default ’0′,
`product_type` varchar(50) collate utf8_bin NOT NULL,
`time_created` varchar(100) collate utf8_bin NOT NULL,
`rp_invoice_id` varchar(127) collate utf8_bin NOT NULL,
`ipn_status` varchar(25) collate utf8_bin NOT NULL,
`creation_timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


– Dumping data for table `paypal_recurring_payments`

– ——————————————————–


– Table structure for table `paypal_recurring_payment_profiles`

CREATE TABLE IF NOT EXISTS `paypal_recurring_payment_profiles` (
`id` tinyint(10) NOT NULL auto_increment,
`payment_cycle` varchar(50) collate utf8_bin NOT NULL,
`txn_type` varchar(30) collate utf8_bin NOT NULL,
`last_name` varchar(75) collate utf8_bin NOT NULL,
`first_name` varchar(75) collate utf8_bin NOT NULL,
`next_payment_date` varchar(100) collate utf8_bin NOT NULL,
`residence_country` varchar(2) collate utf8_bin NOT NULL,
`initial_payment_amount` double NOT NULL default ’0′,
`rp_invoice_id` varchar(127) collate utf8_bin NOT NULL,
`currency_code` varchar(10) collate utf8_bin NOT NULL,
`time_created` varchar(100) collate utf8_bin NOT NULL,
`verify_sign` varchar(255) collate utf8_bin NOT NULL,
`period_type` varchar(25) collate utf8_bin NOT NULL,
`payer_status` varchar(25) collate utf8_bin NOT NULL,
`payer_email` varchar(130) collate utf8_bin NOT NULL,
`receiver_email` varchar(130) collate utf8_bin NOT NULL,
`payer_id` varchar(20) collate utf8_bin NOT NULL,
`product_type` varchar(50) collate utf8_bin NOT NULL,
`payer_business_name` varchar(130) collate utf8_bin NOT NULL,
`shipping` double NOT NULL default ’0′,
`amount_per_cycle` double NOT NULL default ’0′,
`profile_status` varchar(25) collate utf8_bin NOT NULL,
`notify_version` varchar(25) collate utf8_bin NOT NULL,
`amount` double NOT NULL default ’0′,
`outstanding_balance` double NOT NULL default ’0′,
`recurring_payment_id` varchar(50) collate utf8_bin NOT NULL,
`product_name` varchar(130) collate utf8_bin NOT NULL,
`ipn_status` varchar(25) collate utf8_bin NOT NULL,
`creation_timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


– Dumping data for table `paypal_recurring_payment_profiles`

– ——————————————————–


– Table structure for table `paypal_subscriptions`

CREATE TABLE IF NOT EXISTS `paypal_subscriptions` (
`id` tinyint(10) NOT NULL auto_increment,
`custom` varchar(255) collate utf8_bin NOT NULL,
`subscr_id` varchar(25) collate utf8_bin NOT NULL,
`sub_event` varchar(50) collate utf8_bin NOT NULL,
`subscr_date` varchar(100) collate utf8_bin NOT NULL,
`subscr_effective` varchar(100) collate utf8_bin NOT NULL,
`period1` varchar(50) collate utf8_bin NOT NULL,
`period2` varchar(50) collate utf8_bin NOT NULL,
`period3` varchar(50) collate utf8_bin NOT NULL,
`amount1` double NOT NULL default ’0′,
`amount2` double NOT NULL default ’0′,
`amount3` double NOT NULL default ’0′,
`mc_amount1` double NOT NULL default ’0′,
`mc_amount2` double NOT NULL default ’0′,
`mc_amount3` double NOT NULL default ’0′,
`recurring` varchar(10) collate utf8_bin NOT NULL,
`reattempt` varchar(10) collate utf8_bin NOT NULL,
`retry_at` varchar(100) collate utf8_bin NOT NULL,
`recur_times` varchar(25) collate utf8_bin NOT NULL,
`username` varchar(70) collate utf8_bin NOT NULL,
`password` varchar(30) collate utf8_bin NOT NULL,
`txn_id` varchar(25) collate utf8_bin NOT NULL,
`payer_email` varchar(130) collate utf8_bin NOT NULL,
`residence_country` varchar(2) collate utf8_bin NOT NULL,
`mc_currency` varchar(10) collate utf8_bin NOT NULL,
`verify_sign` varchar(255) collate utf8_bin NOT NULL,
`payer_status` varchar(25) collate utf8_bin NOT NULL,
`first_name` varchar(75) collate utf8_bin NOT NULL,
`last_name` varchar(75) collate utf8_bin NOT NULL,
`receiver_email` varchar(130) collate utf8_bin NOT NULL,
`payer_id` varchar(15) collate utf8_bin NOT NULL,
`notify_version` varchar(25) collate utf8_bin NOT NULL,
`item_name` varchar(130) collate utf8_bin NOT NULL,
`item_number` varchar(130) collate utf8_bin NOT NULL,
`ipn_status` varchar(25) collate utf8_bin NOT NULL,
`creation_timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


– Dumping data for table `paypal_subscriptions`

– ——————————————————–


– Table structure for table `paypal_subscription_payments`

CREATE TABLE IF NOT EXISTS `paypal_subscription_payments` (
`id` tinyint(10) NOT NULL auto_increment,
`first_name` varchar(75) collate utf8_bin NOT NULL,
`last_name` varchar(75) collate utf8_bin NOT NULL,
`payer_email` varchar(130) collate utf8_bin NOT NULL,
`memo` text collate utf8_bin NOT NULL,
`item_name` varchar(130) collate utf8_bin NOT NULL,
`item_number` varchar(130) collate utf8_bin NOT NULL,
`os0` varchar(200) collate utf8_bin NOT NULL,
`on0` varchar(65) collate utf8_bin NOT NULL,
`os1` varchar(200) collate utf8_bin NOT NULL,
`on1` varchar(65) collate utf8_bin NOT NULL,
`quantity` double NOT NULL default ’0′,
`payment_date` varchar(100) collate utf8_bin NOT NULL,
`payment_type` varchar(25) collate utf8_bin NOT NULL,
`txn_id` varchar(25) collate utf8_bin NOT NULL,
`mc_gross` double NOT NULL default ’0′,
`mc_fee` double NOT NULL default ’0′,
`payment_status` varchar(25) collate utf8_bin NOT NULL,
`pending_reason` varchar(25) collate utf8_bin NOT NULL,
`txn_type` varchar(30) collate utf8_bin NOT NULL,
`tax` double NOT NULL default ’0′,
`mc_currency` varchar(25) collate utf8_bin NOT NULL,
`reason_code` varchar(25) collate utf8_bin NOT NULL,
`custom` varchar(255) collate utf8_bin NOT NULL,
`address_country` varchar(50) collate utf8_bin NOT NULL,
`subscr_id` varchar(25) collate utf8_bin NOT NULL,
`payer_status` varchar(25) collate utf8_bin NOT NULL,
`ipn_status` varchar(25) collate utf8_bin NOT NULL,
`creation_timestamp` timestamp NOT NULL default CURRENT_TIMESTAMP,
PRIMARY KEY  (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COLLATE=utf8_bin AUTO_INCREMENT=1 ;


– Dumping data for table `paypal_subscription_payments`

[/sourcecode]

Share and Enjoy:
  • Print
  • Digg
  • StumbleUpon
  • del.icio.us
  • Facebook
  • Yahoo! Buzz
  • Twitter
  • Google Bookmarks
Tags: , , , ,

36 Responses

  1. Mark Smith says:

    Hi,

    Just to avoid anyone else banging their head against their desk, you might want to change line 19 of the PHP script.

    Change
    $req .= “&” . $key . “=” . $value;
    to
    $req .= “&” . $key . “=” . $value;

    I finally got a Verified status from PayPal :)

  2. masud says:

    The above code i wanna use to test my transaction, would you please provide me the FORM to use the above code?

    • Not really sure what you mean..?? There is no form that would post directly to this script. IPN takes place in the background separate from your checkout flow. This script accepts the POSTed data from PayPal’s servers directly. You could create a local form to test with and just set the action to your IPN script. I have another blog article that covers exactly that. Look in the PayPal category.

  3. gus mctavish says:

    looks good and i will try it out

    but where are the credentials required by paypal
    i looked specifically for thier use on many scripts
    and seldom see them included in the examples.

    or am i just mistaken to believe they are necessary

    thanks

    • You might also want to take a look at this thread at PayPalDeveloper.com: http://www.paypaldeveloper.com/pdn/board/message?board.id=ipn&thread.id=14078. I posted an updated version of this solution that’s more of a complete deal. It includes an installer to create all of the DB tables and also includes an admin panel to manage all of the IPN’s that hit it. It covers everything IPN does. This template can still be used as always if you don’t need the rest of the solution but you might check it out.

      As for credentials, IPN doesn’t need credentials. That’s only for API calls. IPN simply makes a call-back to PayPal to ensure the IPN actually came from them. The template takes care of that stuff for you.

  4. warren says:

    Thanks alot Andrew Angell…

    Your template codes really help me alot.

    Thank you.

    And god bless you.

  5. IG Webs says:

    This is what I was looking for. Great work and thanks for sharing it. One question: Your script could be used as the listener PayPal talks about, in the head section of my page PayPal returns to after completion of a transaction, right?

  6. IG Webs says:

    If youi mean the zip file you put together, yes I already have downloaded it but I am not sure if I need all that for the project I am working on. Essentially I need to get one or two pieces of data from Paypal to set up the delivery of the goods. I believe I only need to work with your template, wouldn’t you agree?

  7. IG Webs says:

    Hi Angelleye
    I have downloaded your zip file. Is there a text file or something about how to install all that? Or do you have a procedure to use somewhere?
    Thanks

    • Did you download the zip file from the PayPal developer forum? There are basic instructions in that thread. All you need to do is edit the /admin/config.php file accordingly and then load /admin/install in a browser. There’s 1 link that installs all of the DB tables for you. After that you just need to set your IPN in PayPal to point to the ipn-listener.php page.

  8. IG Webs says:

    Yes I did. I appologize for being a newbe on this subject. So far I have installed your IPN scripts and satabase tables, setup two accounts, seller and buyer, on PayPal’s sandbox and set the preferences. I guess so far so good! Do I need a whole new website for the sandbox seller for testing or will a page do or can I use the actual website?

    • Sounds like you’re on the right track. There is a sandbox flag sent with the IPN’s when they come from the sandbox so the scripts will automatically know to verify against the sandbox servers when a test transactions occurs. Also, in the admin panel of the solution, sandbox transactions are highlighted yellow.

  9. IG Webs says:

    Also is it possible to do the teting on a local Apache server?
    Thanks

    • Sure, you just need to make sure the local server is publicly accessible. You can just use the IP address or you could setup a subdomain at your host (sandbox.yourdomain.com) to point to your local IP address. That’s the way I do it.

  10. IG Webs says:

    OK, everything went like a breeze in installation. I used the Instant IPN simulator and sure enough, I got the IPN nicely along with the raw data. So I felt brave enough to do a sandbox test. Using the fake buyer account I ordered an item from the fake business site and completed the order process on the fake site. I got the email from PayPal, all correct but I didn’t get the IPN from the fake order. No raw data either. I think everything is setup correctly since I am getting the simulator IPN. No?Any ideas?
    Thanks

    • It sounds like you didn’t set the IPN URL in your sandbox seller account. Log into the seller account, go into the profile and make sure to set the IPN URL to point to the ipn-listener.php page on your server. You could also use notify_url in your API calls or button code (but only with non-hosted buttons). Notify_url would overwrite anything in your profile config when used.

  11. IG Webs says:

    One thing I forgot to mention. One of your tables, orders, has the same name as one of mine in the same site so I had to install your zip file in a folder of its own and a database of its own. I really would rather everything be in the same database. Is there a simple way to change your table name to something like myOrders or something so all the tables can be in the same database?
    Thanks

    • In the /admin/config.php prior to installation you can set a value for the $db_table_prefix field. I thought the default was set to paypal_ which would mean all of the tables created would be like paypal_orders, paypal_refunds, etc. You could set the prefix to whatever you want, though.

  12. IG Webs says:

    I have checked the IPN URL and it is correctly set and it is an absolute path to the listener file location. Now that is different than where PayPal will return an actual user.

  13. IG Webs says:

    Well, it has suddenly started to work! Thanks

  14. IG Webs says:

    I have a question about the email. I filled out the information for the mail in the config.php but I don’t see any email php files. Is there a way I can edit the email being sent in my name?!

    • The email that gets sent is a very basic email that gets generated within the ipn-listener.php page. At around line 70 there is an email that gets sent if an error occurs and then at the very bottom you’ll see 2 separate emails for whether or not the IPN was verified or not. In each place there is a line that says $mail -> Body = and then sets that to a value. Just scroll up a little bit to see how that value gets generated.

      The $mail -> Body value can be any HTML or regular text value that you want so you can customize it however you want.

  15. IG Webs says:

    Thanks for all your help. I am not getting any emails! I guess I could add what you mention but since I am not getting even an admin email, I must have a problem with my php mailer. Is there a solution for that?

  16. IG Webs says:

    I checked with my host, fsock is on and its on port 80 so I guess that part is OK. Not sure what else it could be!

  17. toy says:

    hey andrew
    great code –

    so heres my question
    this is my first time with paypal and it looks like IPN is what i want
    (ie, when the user checks out and pays
    a notification is sent to my website
    and deducts quantities, sums totals etc in the db without a human)

    i think my disconnect is
    i dont really understand IPN
    i mean isnt the web stateless?
    could you start from the beginning for this noob
    and tell me or show me a link where i can understand the how IPN works?

    thanks a bunch

  18. Rick Weston says:

    Andrew,

    Is there away to make this script post the same IPN info to another script that needs IPN results? I have two sites that need to show payment information from paypal sales and apparently the notify_url setting in the buttons will only allow for one url not two… So I was thinking that maybe I could set the notify url to this script then have it transmit the data to the other two scripts so that my sales info and my affiliate tracking db’s get updated correctly.

    Thanks for any help you can provide!

  19. Mark says:

    Hi Andrew,

    I’m not sure I understand your database schema. For example, why are you putting payer_email in the paypal_disputes table, when you can link paypal_disputes with a txn_id in paypal_orders, which already has payer_email. Seems like you’re duplicating data needlessly. Where is parent_txn_id in your database schema? Wouldn’t you want that to tie the dispute back to the transaction that’s being disputed? And wouldn’t you want a separate transaction table since one buyer can have multiple transactions?

    Confused.

    • Hi Mark,

      You’re correct. The database I made for this isn’t normalized and doesn’t follow very good relational database rules. When I first started developing I was torn between doing it the correct way or doing it how PayPal did it with their minimal IPN solution. I decided to just go the way PayPal did and make tables for each type of IPN.

      The idea here is that each table is a RAW log of what that particular IPN txn_type returned. Any fields that an order might return are included in the order table. Any fields a refund might include are in the refund table, etc. So yes, there are indeed fields that are showing in multiple places but I think it would have ultimately confused more people than helped if I only added each field once and then relied on relationships to connect everything like I do with most my db solutions.

      The data is still related, though. For example, you asked about parent_tx_id. The refunds table is where that field might come back so that’s where it’s included. Then if you need to find the original transactions you can easily look it up in the orders table because orders.txn_id = refunds.parent_txn_id. The same goes for subscriptions and payment history. When a new subscription is created a record in the subscriptions table is added with the new subscr_id. Then as each payment comes through they’ll go into subscription_payments with the same subscr_id. So, while I haven’t included it in the solution yet, it’d be very simple to create links to view only related payments.

      So again, while it’s not exactly normalized the relationships do exist so you can find any related data you need. Hope that helps you understand what I did here a little bit. I’m with you, I would typically normalize my database first thing but I decided to follow what PayPal did instead and just expand on it.

  20. Daniel O says:

    What do I need to do to make it work live?
    It works for me on sandbox test, but not with true botons thru paypal site. It does not bring the variables.

    1) Do I have to change something at the top of the script?
    2) Do I need to change something in my Paypal administration?

    thank you,

    • The clean install should work just fine on both. The sandbox orders send a variable called test_ipn and when that variable is present the IPN system works the same except that it highlights the transaction in the admin panel Yellow to show it’s a test order.

      The only thing you need to change anywhere is the /admin/config.php file. That shouldn’t effect the solution as a whole, though, if it is indeed working in the sandbox.

      Did you ensure that your IPN configuration is setup in your live account profile? You need to make sure it’s turned on and pointing to the ipn-listener.php file.

      Also, did you just use the basic template or did you install my full solution? I think I need to update this blog. I have an installable solution now so if you’re not using that then /admin/config.php won’t be there for you. Let me know if that’s the case and I’ll get you the full solution. Actually, you can download it at http://www.angelleye.com/solutions/Angell_EYE_PayPal_IPN_Solution.zip

  21. Daniel O says:

    Hi Andrew,

    I downloaded and installed the script in 2 servers because it works (email, database) in the sandbox, but not in real transaction. I get a blank email, and no data entered in database.

    When Paypal sends me back to my site, it shows this:
    http:www.something.com/ipn-listener.php?tx=8TX21385HY744893F&st=Completed&amt=0.01&cc=USD&cm=&item_number=

    IPN is turned on in the paypal configuration and in the botton.
    Any help is apreciated, I have been stuck here for days.

Leave a Reply