I’ve been struggling with this for a few days now.
I would like to read the custom variable that is passed through the IPN system.
Most people say $custom = $_POST['custom'];
– is how you do it?
However, the postback isn’t the expected data.
Here is the Code:
ipn.php
<?php
ini_set('display_errors', 1);
ini_set('display_startup_errors', 1);
error_reporting(E_ALL);
$servername = "localhost";
$username = "xxxxxxxx";
$password = "xxxxxxxxx";
$db = "xxxxxxxx";
// Create connection
$conn = new mysqli($servername, $username, $password,$db);
// Check connection
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
$replyTO = "[email protected]";
$senderName = "POWERMAILER IPN";
$timestamp = time();
$headers = "MIME-Version: 1.0" . "rn";
$headers .= "Content-type:text/html;charset=UTF-8" . "rn";
$headers .= 'From: <'.$replyTO.'>'.$senderName.'' . "rn" .
'Reply-To: '.$replyTO.'' . "rn" .
'X-Mailer: PHP/' . phpversion();
require('PaypalIPN.php');
use PaypalIPN;
$ipn = new PaypalIPN();
// Use the sandbox endpoint during testing.
//$ipn->useSandbox();
$verified = $ipn->verifyIPN();
if ($verified) {
/*
* Process IPN
* A list of variables is available here:
* https://developer.paypal.com/webapps/developer/docs/classic/ipn/integration-guide/IPNandPDTVariables/
*/
$custom = $_POST['custom'];
$vars = explode(",",$custom);
$uid = $vars[0];
$sid = $vars[1];
$newExpiry = $timestamp+2592000;
$stmt = $conn->prepare('UPDATE users SET subscription_id = $sid, subscription_expiry = $newExpiry WHERE id = $uid');
$stmt->bind_param('i', $uid);
$stmt->bind_param('i', $sid);
$stmt->bind_param('i', $newExpiry);
$stmt->execute();
$result = $stmt->get_result();
if ($result === TRUE) {
mail($replyTO,"POWER MAILER SUBSCRIPTION","New Subscriber: IPN SUCCESS :: UID=".$uid." SID=".$sid." NEWEXP=".$newExpiry."",$headers);
} else {
mail($replyTO,"POWER MAILER SUBSCRIPTION","New Subscriber: IPN FAILED :: UID=".$uid." SID=".$sid." NEWEXP=".$newExpiry."",$headers);
}
$conn->close();
}
// Reply with an empty 200 response to indicate to paypal the IPN was received correctly.
header("HTTP/1.1 200 OK");
?>
PaypalIPN.php (verifyer)
<?php
class PaypalIPN
{
/** @var bool Indicates if the sandbox endpoint is used. */
private $use_sandbox = false;
/** @var bool Indicates if the local certificates are used. */
private $use_local_certs = true;
/** Production Postback URL */
const VERIFY_URI = 'https://ipnpb.paypal.com/cgi-bin/webscr';
/** Sandbox Postback URL */
const SANDBOX_VERIFY_URI = 'https://ipnpb.sandbox.paypal.com/cgi-bin/webscr';
/** Response from PayPal indicating validation was successful */
const VALID = 'VERIFIED';
/** Response from PayPal indicating validation failed */
const INVALID = 'INVALID';
/**
* Sets the IPN verification to sandbox mode (for use when testing,
* should not be enabled in production).
* @return void
*/
public function useSandbox()
{
$this->use_sandbox = true;
}
/**
* Sets curl to use php curl's built in certs (may be required in some
* environments).
* @return void
*/
public function usePHPCerts()
{
$this->use_local_certs = false;
}
/**
* Determine endpoint to post the verification data to.
*
* @return string
*/
public function getPaypalUri()
{
if ($this->use_sandbox) {
return self::SANDBOX_VERIFY_URI;
} else {
return self::VERIFY_URI;
}
}
/**
* Verification Function
* Sends the incoming post data back to PayPal using the cURL library.
*
* @return bool
* @throws Exception
*/
public function verifyIPN()
{
if ( ! count($_POST)) {
throw new Exception("Missing POST Data");
}
$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) {
// Since we do not want the plus in the datetime string to be encoded to a space, we manually encode it.
if ($keyval[0] === 'payment_date') {
if (substr_count($keyval[1], '+') === 1) {
$keyval[1] = str_replace('+', '%2B', $keyval[1]);
}
}
$myPost[$keyval[0]] = urldecode($keyval[1]);
}
}
// Build the body of the verification post request, adding the _notify-validate command.
$req = 'cmd=_notify-validate';
$get_magic_quotes_exists = false;
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 the data back to PayPal, using curl. Throw exceptions if errors occur.
$ch = curl_init($this->getPaypalUri());
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_SSLVERSION, 6);
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
// This is often required if the server is missing a global cert bundle, or is using an outdated one.
if ($this->use_local_certs) {
curl_setopt($ch, CURLOPT_CAINFO, __DIR__ . "/cert/cacert.pem");
}
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 30);
curl_setopt($ch, CURLOPT_HTTPHEADER, array(
'User-Agent: PHP-IPN-Verification-Script',
'Connection: Close',
));
$res = curl_exec($ch);
if ( ! ($res)) {
$errno = curl_errno($ch);
$errstr = curl_error($ch);
curl_close($ch);
throw new Exception("cURL error: [$errno] $errstr");
}
$info = curl_getinfo($ch);
$http_code = $info['http_code'];
if ($http_code != 200) {
throw new Exception("PayPal responded with http code $http_code");
}
curl_close($ch);
// Check if PayPal verifies the IPN data, and if so, return true.
if ($res == self::VALID) {
return true;
} else {
return false;
}
}
}
?>
Paypal Pay Button Form:
<form action="https://www.paypal.com/cgi-bin/webscr" method="post" target="_top">
<input type="hidden" name="custom" value="<?php echo $uid;?>,4"/>
<input type="hidden" name="cmd" value="_s-xclick" />
<input type="hidden" name="hosted_button_id" value="HU6L2WHVBPXWU" />
<input type="hidden" name="currency_code" value="CAD" />
<input type="image" src="https://www.paypalobjects.com/en_US/i/btn/btn_buynowCC_LG.gif" border="0" name="submit" title="PayPal - The safer, easier way to pay online!" alt="Buy Now" />
</form>
You can see in the form I have a custom var which takes a posted var and a comma with a set value.
I explode that on the ipn.php
I also see that the code on the verify php (PaypalIPN.php) decodes the postback vars. But I’m not quite sure if I can just grab that loop and use it again on the ipn.php
Can someone shed some light on how to properly read the variables?
Thanks in advance!
- I’ve tried Webhooks (unreliable)
- I’ve tried using the sandbox account but the sandbox credentials never work, so I’m testing this in a live environment so getting it right sooner than later would help.
- The next test I do will send me an email with the output. But I would like to have the code
more ready before tomorrow mornings test.
Update:
I also updated the $vars[] to $vars
.
2
Answers
$vars[] = explode(",",$custom); appends values to an array, creating an empty one if needed. You probably want $vars = explode(",",$custom); which will just set $vars to the resulting exploded array. – Alex Howansky 23 hours ago
the test was successful thanks
I just read
$_POST['custom']
. It’s that simple. Be sure to do the postback validation to ensure the incoming POST is really from PayPal, however.Note, some events may come in without the custom variable. But these are follow-on for things like a subscription or a decline. However, for the customer’s form post, they will definitely have the custom variable. Now, I noticed you used the Buy Now button strategy, and that won’t work with subscriptions. They have another form and button strategy for that use-case. Now, if you’re doing your own "subscriptions" in your own code, that’s a different thing.
You might also want to check different string characters you can and cannot post into that variable, and how long the variable can be. It might get munged in transit.