I know this question has been asked multiple times, however looking through all the solutions, none of them have seemed to work for me. I've been using the IPN Simulator on the PayPal website to integrate Subscriptions into my website, yet the IPN is always returning invalid. I've tried multiple ways to get this working, but this is my current method:
<?php
class IpnListener {
public $use_curl = true;
public $force_ssl_v3 = false;
public $follow_location = false;
public $use_ssl = true;
public $use_sandbox = false;
public $timeout = 30;
private $post_data = array();
private $post_uri = '';
private $response_status = '';
private $response = '';
const PAYPAL_HOST = 'www.paypal.com';
const SANDBOX_HOST = 'www.sandbox.paypal.com';
protected function curlPost($encoded_data) {
if ($this->use_ssl) {
$uri = 'https://'.$this->getPaypalHost().'/cgi-bin/webscr';
$this->post_uri = $uri;
} else {
$uri = 'http://'.$this->getPaypalHost().'/cgi-bin/webscr';
$this->post_uri = $uri;
}
$ch = curl_init();
curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, true);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_CAINFO, dirname(__FILE__)."/cacert.pem");
curl_setopt($ch, CURLOPT_URL, $uri);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_POSTFIELDS, $encoded_data);
curl_setopt($ch, CURLOPT_FOLLOWLOCATION, $this->follow_location);
curl_setopt($ch, CURLOPT_TIMEOUT, $this->timeout);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HEADER, true);
if ($this->force_ssl_v3) {
curl_setopt($ch, CURLOPT_SSLVERSION, 3);
} else {
curl_setopt($ch, CURLOPT_SSLVERSION, 4);
}
$this->response = curl_exec($ch);
$this->response_status = strval(curl_getinfo($ch, CURLINFO_HTTP_CODE));
if ($this->response === false || $this->response_status == '0') {
$errno = curl_errno($ch);
$errstr = curl_error($ch);
throw new Exception("cURL error: [$errno] $errstr");
}
}
protected function fsockPost($encoded_data) {
if ($this->use_ssl) {
$uri = 'ssl://'.$this->getPaypalHost();
$port = '443';
$this->post_uri = $uri.'/cgi-bin/webscr';
} else {
$uri = $this->getPaypalHost(); // no "http://" in call to fsockopen()
$port = '80';
$this->post_uri = 'http://'.$uri.'/cgi-bin/webscr';
}
$fp = fsockopen($uri, $port, $errno, $errstr, $this->timeout);
if (!$fp) {
// fsockopen error
throw new Exception("fsockopen error: [$errno] $errstr");
}
$header = "POST /cgi-bin/webscr HTTP/1.1
";
$header .= "Host: ".$this->getPaypalHost()."
";
$header .= "Content-Type: application/x-www-form-urlencoded
";
$header .= "Content-Length: ".strlen($encoded_data)."
";
$header .= "Connection: Close
";
fputs($fp, $header.$encoded_data."
");
while(!feof($fp)) {
if (empty($this->response)) {
// extract HTTP status from first line
$this->response .= $status = fgets($fp, 1024);
$this->response_status = trim(substr($status, 9, 4));
} else {
$this->response .= fgets($fp, 1024);
}
}
fclose($fp);
}
private function getPaypalHost() {
if ($this->use_sandbox) return self::SANDBOX_HOST;
else return self::PAYPAL_HOST;
}
public function getPostUri() {
return $this->post_uri;
}
public function getResponse() {
return $this->response;
}
public function getResponseStatus() {
return $this->response_status;
}
public function getTextReport() {
$r = '';
// date and POST url
for ($i=0; $i<80; $i++) { $r .= '-'; }
$r .= "
[".date('m/d/Y g:i A').'] - '.$this->getPostUri();
if ($this->use_curl) $r .= " (curl)
";
else $r .= " (fsockopen)
";
// HTTP Response
for ($i=0; $i<80; $i++) { $r .= '-'; }
$r .= "
{$this->getResponse()}
";
// POST vars
for ($i=0; $i<80; $i++) { $r .= '-'; }
$r .= "
";
foreach ($this->post_data as $key => $value) {
$r .= str_pad($key, 25)."$value
";
}
$r .= "
";
return $r;
}
public function processIpn($post_data=null) {
$encoded_data = 'cmd=_notify-validate';
if ($post_data === null) {
// use raw POST data
if (!empty($_POST)) {
$this->post_data = $_POST;
$encoded_data .= '&'.file_get_contents('php://input');
} else {
throw new Exception("No POST data found.");
}
} else {
// use provided data array
$this->post_data = $post_data;
foreach ($this->post_data as $key => $value) {
$encoded_data .= "&$key=".urlencode($value);
}
}
if ($this->use_curl) $this->curlPost($encoded_data);
else $this->fsockPost($encoded_data);
if (strpos($this->response_status, '200') === false) {
throw new Exception("Invalid response status: ".$this->response_status);
}
if (strpos($this->response, "VERIFIED") !== false) {
return true;
} elseif (strpos($this->response, "INVALID") !== false) {
return false;
} else {
throw new Exception("Unexpected response from PayPal.");
}
}
public function requirePostMethod() {
// require POST requests
if ($_SERVER['REQUEST_METHOD'] && $_SERVER['REQUEST_METHOD'] != 'POST') {
header('Allow: POST', true, 405);
throw new Exception("Invalid HTTP request method.");
}
}
Then the Payment.php class, which is where PayPal sends the request too.
/*The Database Connection Code Is Here*/
include('ipnlistener.php');
$listener = new IpnListener();
$listener->use_sandbox = true;
//$listener->use_curl = false;
try {
$verified = $listener->processIpn();
} catch (Exception $e) {
file_put_contents("paymentdev.php", $e->getMessage()." ".date("H:i"));
//file_put_contents("paymentdev.php", "fudge");
exit(0);
}
if ($verified) {
file_put_contents("paymentdev.php", "test");
$data = $_POST;
$user_id = json_decode($data['custom'])->user_id;
$subscription = ($data['mc_gross_1'] == '49.95') ? 2 : 1;
$txn_id = $data['txn_id'];
$user_id = $user_id;
$paypal_id = $data['subsc_id'];
$subscription = $subscription;
$expires = date('Y-m-d H:i:s', strtotime('+1 Month'));
$sql = "SELECT * FROM payments WHERE user_id=$user_id";
$payment = $mysqli->query($sql);
if(isset($payment) && $payment->num_rows > 0)
{
$sql = "DELETE FROM payments WHERE user_id=$user_id";
$delete = $mysqli->query($sql);
}
$sql = "INSERT INTO payments (txn_id, user_id, paypal_id, expires, subscription) VALUES ('$txn_id', $user_id, '$paypal_id', '$expires', $subscription)";
$insertpayment = $mysqli->query($sql);
file_put_contents("paymentdev.php", $mysqli->error);
} else {
file_put_contents("paymentdev.php", "Transaction not verified ".date("H:i")."<br>".$listener->getTextReport());
echo "Error: Transaction not verified";
}
Thanks in advance for all your help guys!
Turns out the code I was using was just fine, I updated the server a bit more just to ensure it was using the latest cURL and open ssl, but even so the sandbox would always return INVALID. I tried the live version with subscriptions priced at $0.01 and the payment went through, was valid and my database was updated
Must just be something weird with the IPN simulator/sandbox