My IPN script exactly as the PayPal sample code: http://www.example.com/cart/ipn2.php (only thing different is I added cacert.pem to cURL since my server environment lacks it)
<?php
// CONFIG: Enable debug mode. This means we'll log requests into 'ipn.log' in the same directory.
// Especially useful if you encounter network errors or other intermittent problems with IPN (validation).
// Set this to 0 once you go live or don't require logging.
define("DEBUG", 1);
// Set to 0 once you're ready to go live
define("USE_SANDBOX", 1);
define("LOG_FILE", "./ipn.log");
// Read POST data
// reading posted data directly from $_POST causes serialization
// issues with array data in POST. Reading raw POST data from input stream instead.
$raw_post_data = file_get_contents('php://input');
$raw_post_array = explode('&', $raw_post_data);
$myPost = array();
foreach ($raw_post_array as $keyval) {
$keyval = explode ('=', $keyval);
if (count($keyval) == 2)
$myPost[$keyval[0]] = urldecode($keyval[1]);
}
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
if(function_exists('get_magic_quotes_gpc')) {
$get_magic_quotes_exists = true;
}
foreach ($myPost as $key => $value) {
if($get_magic_quotes_exists == true && get_magic_quotes_gpc() == 1) {
$value = urlencode(stripslashes($value));
} else {
$value = urlencode($value);
}
$req .= "&$key=$value";
}
// Post IPN data back to PayPal to validate the IPN data is genuine
// Without this step anyone can fake IPN data
if(USE_SANDBOX == true) {
$paypal_url = "https://www.sandbox.paypal.com/cgi-bin/webscr";
} else {
$paypal_url = "https://www.paypal.com/cgi-bin/webscr";
}
$ch = curl_init($paypal_url);
if ($ch == FALSE) {
return FALSE;
}
curl_setopt($ch, CURLOPT_HTTP_VERSION, CURL_HTTP_VERSION_1_1);
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_RETURNTRANSFER,1);
curl_setopt($ch, CURLOPT_POSTFIELDS, $req);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
if(DEBUG == true) {
curl_setopt($ch, CURLOPT_HEADER, 1);
curl_setopt($ch, CURLINFO_HEADER_OUT, 1);
}
// CONFIG: Optional proxy configuration
//curl_setopt($ch, CURLOPT_PROXY, $proxy);
//curl_setopt($ch, CURLOPT_HTTPPROXYTUNNEL, 1);
// Set TCP timeout to 30 seconds
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
// CONFIG: Please download 'cacert.pem' from "http://curl.haxx.se/docs/caextract.html" and set the directory path
// of the certificate as shown below. Ensure the file is readable by the webserver.
// This is mandatory for some environments.
$cert = __DIR__ . "/cacert.pem";
curl_setopt($ch, CURLOPT_CAINFO, $cert);
$res = curl_exec($ch);
if (curl_errno($ch) != 0) // cURL error
{
if(DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "Can't connect to PayPal to validate IPN message: " . curl_error($ch) . PHP_EOL, 3, LOG_FILE);
}
curl_close($ch);
exit;
} else {
// Log the entire HTTP response if debug is switched on.
if(DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "HTTP request of validation request:". curl_getinfo($ch, CURLINFO_HEADER_OUT) ." for IPN payload: $req" . PHP_EOL, 3, LOG_FILE);
error_log(date('[Y-m-d H:i e] '). "HTTP response of validation request: $res" . PHP_EOL, 3, LOG_FILE);
}
curl_close($ch);
}
// Inspect IPN validation result and act accordingly
// Split response headers and payload, a better way for strcmp
$tokens = explode("\r\n\r\n", trim($res));
$res = trim(end($tokens));
if (strcmp ($res, "VERIFIED") == 0) {
// check whether the payment_status is Completed
// check that txn_id has not been previously processed
// check that receiver_email is your PayPal email
// check that payment_amount/payment_currency are correct
// process payment and mark item as paid.
// assign posted variables to local variables
//$item_name = $_POST['item_name'];
//$item_number = $_POST['item_number'];
//$payment_status = $_POST['payment_status'];
//$payment_amount = $_POST['mc_gross'];
//$payment_currency = $_POST['mc_currency'];
//$txn_id = $_POST['txn_id'];
//$receiver_email = $_POST['receiver_email'];
//$payer_email = $_POST['payer_email'];
if(DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "Verified IPN: $req ". PHP_EOL, 3, LOG_FILE);
}
} else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
// Add business logic here which deals with invalid IPN messages
if(DEBUG == true) {
error_log(date('[Y-m-d H:i e] '). "Invalid IPN: $req" . PHP_EOL, 3, LOG_FILE);
}
}
I then test it at here: https://developer.paypal.com/developer/ipnSimulator/
- IPN handler URL: http://www.example.com/cart/ipn2.php
- Transaction type: Cart checkout
Clicked "Send IPN".
IPN was sent and the handshake was verified.
Now I check ipn.log in the same directory as ipn2.php:
[2016-05-29 03:33 UTC] HTTP request of validation request:POST /cgi-bin/webscr HTTP/1.1
Host: www.sandbox.paypal.com
Accept: */*
Connection: Close
Content-Length: 956
Content-Type: application/x-www-form-urlencoded
for IPN payload: cmd=_notify-validate&payment_type=instant&payment_date=Sun+May+29+2016+09%3A47%3A44+GMT+0800+%28%25u4E2D%25u56FD%25u6807%25u51C6%25u65F6%25u95F4%29&payment_status=Completed&payer_status=verified&first_name=John&last_name=Smith&payer_email=buyer%40paypalsandbox.com&payer_id=TESTBUYERID01&address_name=John+Smith&address_country=United+States&address_country_code=US&address_zip=95131&address_state=CA&address_city=San+Jose&address_street=123+any+street&business=seller%40paypalsandbox.com&receiver_email=seller%40paypalsandbox.com&receiver_id=seller%40paypalsandbox.com&residence_country=US&item_name1=something&item_number1=AK-1234&quantity=1&shipping=3.04&tax=2.02&mc_currency=USD&mc_fee=0.44&mc_gross=12.34&mc_gross_1=12.34&mc_handling=2.06&mc_handling1=1.67&mc_shipping=3.02&mc_shipping1=1.02&txn_type=cart&txn_id=899327589¬ify_version=2.4&custom=xyz123&invoice=abc1234&test_ipn=1&verify_sign=Ax7s3TqIWw66TCwHqtv5jrDudIkqArp9Q5MbSvZN7Hmp0hVcLJIPMGtn
[2016-05-29 03:33 UTC] HTTP response of validation request: HTTP/1.1 200 OK
Date: Sun, 29 May 2016 03:33:52 GMT
Server: Apache
X-Frame-Options: SAMEORIGIN
Set-Cookie: c9MWDuvPtT9GIMyPc3jwol1VSlO=275N2N7S363a15EPCeqtgTf8h6q9dGxoqH7G2qLXSZ_CsWkTd-fn7MPTwWkNErzsHwZmTFSzM6aG-Uidv3uJDu-qATD4hP8S724yYqEFXgNSfr1n7rBUKf81IYFx1nwn3saS7puTcmRSTc1HUvXwTOY0xLie6LpPJXhg8mX92feeUkUo1_4Ndye5D67XLpfjTbHsby32vssICqRf-4XUqw9Vqz6OgWMWjYq8vyEAy4S7ojpFQxs2Bb61hY4Plum1LhSscdb_xXeKFqRjc89QU3w2S51usLzma39SM1WsFCayybOyXuYJcUyXMdCg0--tyDxO9Ru3eAjQixTZPCN9Y9TbKd0HIxBqtqxmdRGFtd0GTzFj__zc8sjO4cliOJCRI30YLCfUbMqf4G8bgDymdR7gcqIf1CNWcvMGwW; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: cookie_check=yes; expires=Wed, 27-May-2026 03:33:53 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: navcmd=_notify-validate; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: navlns=0.0; expires=Tue, 29-May-2018 03:33:53 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: Apache=10.72.108.11.1464492832868615; path=/; expires=Tue, 22-May-46 03:33:52 GMT
Vary: Accept-Encoding,User-Agent
Connection: close
HTTP_X_PP_AZ_LOCATOR: sandbox.slc
Paypal-Debug-Id: ef5f7fe7cf2cc
Set-Cookie: X-PP-SILOVER=name%3DSANDBOX3.WEB.1%26silo_version%3D1880%26app%3Dappdispatcher%26TIME%3D543378007%26HTTP_X_PP_AZ_LOCATOR%3Dsandbox.slc; Expires=Sun, 29 May 2016 04:03:53 GMT; domain=.paypal.com; path=/; Secure; HttpOnly
Set-Cookie: X-PP-SILOVER=; Expires=Thu, 01 Jan 1970 00:00:01 GMT
Strict-Transport-Security: max-age=14400
Transfer-Encoding: chunked
Content-Type: text/html; charset=UTF-8
INVALID
[2016-05-29 03:33 UTC] Invalid IPN: cmd=_notify-validate&payment_type=instant&payment_date=Sun+May+29+2016+09%3A47%3A44+GMT+0800+%28%25u4E2D%25u56FD%25u6807%25u51C6%25u65F6%25u95F4%29&payment_status=Completed&payer_status=verified&first_name=John&last_name=Smith&payer_email=buyer%40paypalsandbox.com&payer_id=TESTBUYERID01&address_name=John+Smith&address_country=United+States&address_country_code=US&address_zip=95131&address_state=CA&address_city=San+Jose&address_street=123+any+street&business=seller%40paypalsandbox.com&receiver_email=seller%40paypalsandbox.com&receiver_id=seller%40paypalsandbox.com&residence_country=US&item_name1=something&item_number1=AK-1234&quantity=1&shipping=3.04&tax=2.02&mc_currency=USD&mc_fee=0.44&mc_gross=12.34&mc_gross_1=12.34&mc_handling=2.06&mc_handling1=1.67&mc_shipping=3.02&mc_shipping1=1.02&txn_type=cart&txn_id=899327589¬ify_version=2.4&custom=xyz123&invoice=abc1234&test_ipn=1&verify_sign=Ax7s3TqIWw66TCwHqtv5jrDudIkqArp9Q5MbSvZN7Hmp0hVcLJIPMGtn
Why? I literally followed the code sample to the word. Why it's always INVALID? I've been scratching my head about this for hours and nothing relevant came up. Please help me anyone!
You may try create sandbox accounts and make test payments to check your IPN script. My script is also receiving "INVALID" response for IPN data from IPN simulator but it receives "VERIFIED" for IPN data from sandbox test payments.
You might want to check the IPN trouble shooting tips on INVALID response HERE
Charset config in the account profile is the most typical cause of the issue, and if you're testing against
www.sandbox.paypal.com
, you would need to update the language encoding settings in your sandbox seller account (login to www.sandbox.paypal.com with your sandbox seller credentials) rather than your LIVE account.