Need help with PayPal IPN

I am following a book on e-commerce and having issues implementing PayPal’s “Instant Payment Notification (IPN)”.

I have a “renew” button, that when clicked, sends data to PayPal, and then it is stored on PayPal’s servers.

Here is code from “renew.php”

<p>Thank you for your interest in renewing your account!
		To complete the process, please now click the button below so that
		you may pay for your renewal via PayPal. The cost is $10 (US) per year.
		<strong>Note: After renewing your membership at PayPal, you must logout
		and log back in at this site in order process the renewal.</strong>
<form action="" method="post">
	<input type="hidden" name="cmd" value="_s-xclick">
	<input type="hidden" name="custom" value="<?php echo $_SESSION['user_id']; ?>">
	<input type="hidden" name="hosted_button_id" value="SomeSecretCode">
	<input type="submit" name="submit_button" value="Renew &rarr;"
			 id="submit_button" class="formbutton" />

In my test PayPal merchant account, I have turned on the IPN feature, which points to a file (“”) that is supposed to act as a “listener” file. This listener file receives messages from PayPal any time a user makes a payment - regardless of whether they then come back to my website or go somewhere else. (The whole idea of IPN is that it gives you information back that you need - as a merchant - to complete a sales transaction if the user doesn’t return to your website.)

So, when I click the “renew” button, the field “date_expires” is not getting updated in my back-end database table.

My suspicion is that something is failing in my “ipn.php” file, but since everything is up online, I have no way to step through my code like if it was in NetBeans locally. (In fact, I can only work with this code online, because it requires use of my SSL certificate and a live connection to PayPal, so this is a pain to debug!) :frowning:

Here is code from “IPN.php”

	// The next part of the process of implementing IPN is creating the 
	// listener script (i.e. "ipn.php")  This is the file which PayPal
	// will communicate with automatically, behind-the-scenes.
	// The process will go like this...
	//   a.) When this page is requested, it must immediately confirm the request
	//       with PayPal.  (This keeps the script from being fraudulently used.)
	//   b.) It will read PayPal's response.
	//   c.) Next it will validates the response.
	//   d.) If valid, the database will be updated.
	//				(Because this script does not generate any HTML,
	//					it is never run through the browser.)

	// Get Configuration settings.

	// Reason for communicating with PayPal.
	$req = 'cmd=_notify-validate';
	// For each element in $_POST, a Key/Value pair is added to the $req variable.
	foreach ($_POST as $key => $value){
		// Strip any slashes.
		// Make string safe for URL.
		$value = urlencode(stripslashes($value));
		$req .= "&$key=$value";

	// Open a socket connection to PayPal.
	// Store any errors that occur during the connection process.
	// Take up to 30 seconds to make the connection.
	$fp = fsockopen ('ssl://', 443, $errno, $errstr, 30);

	if (!$fp){
		// Can't open connection.
		trigger_error('Could not connect for the IPN!');
		// Send request to PayPal.

		// Post request should be made to /cgi-bin/webscr using HTTP 1.0 protocol.
		$header = "POST /cgi-bin/webscr HTTP/1.0\\r\

		// The content type will be URL-encoded form data.
		$header .= "Content-Type: application/x-www-form-urlencoded\\r\

		//The length of the request.
		$header .= "Content-Length: " . strlen($req) . "\\r\

		// Send the headers and request data to PayPal.
		fputs ($fp, $header . $req);

		// Read in the response until end-of-file of the open connection.
		while (!feof($fp)) {
			// Get up to one kilobyte of data or until end of the line.
			$res = fgets ($fp, 1024);

			if (strcmp ($res, "VERIFIED") == 0) {

				// Check for the right values.
				// Validating the values posted to the script is key to preventing
				// users from defrauding your site.
				// Just looking for a verified response is not enough.  For the
				// transaction to be official enough to warrant updating the site's
				// database, several other qualities should exist...
				// The payment_status needs to be 'complete'.
				// Confirm that the payment was received by the same e-mail address
				// as listed in the merchant's PayPal account.  This check prevents
				// the site from taking action based upon a payment that didn't go
				// to it because someone attempted a hack.
				// The mc_gross and mc_currency should match the gross cost and
				// currency of the transaction.  This keeps someone from trying
				// to pay just one cent or 10.00 Thai baht (equivalent to 30 cents)!
				// Finally, make sure that the transaction ID is not empty.
				if ( isset($_POST['payment_status'])
				 && ($_POST['payment_status'] == 'Completed')
				 && ($_POST['receiver_email'] == '')
				 && ($_POST['mc_gross'] == 10.00)
				 && ($_POST['mc_currency']  == 'USD')
				 && (!empty($_POST['txn_id']))) {

					// Get Database Connection.

					// It is possible through nefarious actions or normal operations,
					// that the IPN script might get repeated for the same transaction.
					// To prevent such an occurrence from crediting a user's account
					// again, this query checks if the transaction ID is already listed
					// in the orders table.
					// Check for this transaction in the database.
					$txn_id = mysqli_real_escape_string($dbc, $_POST['txn_id']);
					$q = "SELECT id FROM orders WHERE transaction_id='$txn_id'";
					$r = mysqli_query ($dbc, $q);

					if (mysqli_num_rows($r) == 0) {
						// Add transaction to database.

						// $_POST['custom'] is where the user's ID was originally stored
						// in the "register.php" script, before it was passed to PayPal.
						// This value gets passed back to the site via IPN whether the
						// customer immediately returns to the site or not.
						$uid = (isset($_POST['custom'])) ? (int) $_POST['custom'] : 0;
						$status = mysqli_real_escape_string($dbc, $_POST['payment_status']);
						$amount = (float) $_POST['mc_gross'];
						$q = "INSERT INTO orders 
										(user_id, transaction_id, payment_status, payment_amount)
									VALUES ($uid, '$txn_id', '$status', $amount)";
						$r = mysqli_query ($dbc, $q);

						if (mysqli_affected_rows($dbc) == 1) {
							// Insert was successful.
							if ($uid > 0) {
								// Update the users table:
								$q = "UPDATE users
											SET date_expires = IF(date_expires > NOW(),
																						ADDDATE(date_expires, INTERVAL 1 YEAR),
																						ADDDATE(NOW(), INTERVAL 1 YEAR)),
											WHERE id=$uid";
								$r = mysqli_query ($dbc, $q);

								if (mysqli_affected_rows($dbc) != 1) {
									trigger_error('The user\\'s expiration date could not be updated!');
						} else {
							trigger_error('The transaction could not be stored in the orders table!');
			} elseif (strcmp ($res, "INVALID") == 0) {
				// log for manual investigation
		} // End of the WHILE loop.

		// Close the connection.
		fclose ($fp);

	} // End of $fp IF-ELSE.


[b]Any ideas how to figure out what is going wrong??

Any ideas how to figure out what PayPal might be returning back to my “listener” script?[/b]



i have PP IPN script but implemented in CURL, are u interested to look at my code?

perhaps create a ‘write to file’ script and include it in your ipn page so you can identify where the error is coming from

I agree with @kenetix, to many variables come into play to tell what is happening. First thing i would do is log $_POST data to a file.

You can do this with

file_put_contents("log.txt", print_r($_POST, true));

Put that line at the beginning of the IPN.php, after making payment it should be populated with data that PayPal send to your script. Next you can examine the contents of log.txt file to check what went wrong. If the log.txt is empty or does not exist it’s even easier, it will mean that request was not even sent, which might indicate that your IPN is not correctly set (or PHP does not have write permissions to the directory where it’s saves log.txt file).

Thanks for the idea, Greg and kenetix.

Say, is there a way to do what you suggested, but have it e-mail the errors/dump instead?

I have an “” that I could use.