I know that there have been a few changes to the PayPal IPN system as of May 15th, 2018. I happen to be in the middle of implementing my first IPN listener; and I'm not sure if I'm lost because of my resources (including SO posts, some dating back to 2009 on this subject) have been obsolesced by PayPal's changes, or merely because I am inexperienced in this field.
My suspicion is that I am pointing to the incorrect PayPal address:
$fh = fsockopen('ssl://www.paypal.com',443,$errno,$errstr,30);
//$fh = fsockopen('https://www.sandbox.paypal.com',80,$errno,$errstr,30);
//$fh = fsockopen('https://ipnpb.sandbox.paypal.com/cgi-bin/webscr',80,$errno,$errstr,30);
The first address completes in a successful handshake with PayPal, but returns INVALID as the IPN response. The second two handshake, but don't pass the if (!$fh) test.
I have tried both the code below, and the CURL code found in Chris Muench's answer here: PayPal IPN returns invalid in sandbox
<?php
// Paypal IPN code
header('HTTP/1.1 200 OK'); // send header
$resp = 'cmd=_notify-validate';
foreach ($_POST as $parm => $var){
$var = urlencode(stripslashes($var));
$resp .= "&$parm=$var";
}
$httphead = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$httphead .= "Content-Type: application/x-www-form-urlencoded\r\n";
$httphead .= "Content-Length: " . strlen($resp) . "\r\n\r\n";
// create a ="file handle" for writing to a URL to paypal.com on Port 443 (the IPN port)
$errno ='';
$errstr='';
$fh = fsockopen('ssl://www.paypal.com',443,$errno,$errstr,30);
//$fh = fsockopen('https://www.sandbox.paypal.com',443,$errno,$errstr,30);
//$fh = fsockopen('https://ipnpb.sandbox.paypal.com/cgi-bin/webscr',443,$errno,$errstr,30);
if (!$fh) {
// if-fh does not work - cnxn FAILED
}
else{
// Connection opened, so spit back the response and get PayPal's view whether it was an authentic notification
fputs ($fh,$httphead.$resp);
while (!feof($fh)){
$readresp = fgets ($fh, 1024);
if (strcmp(trim($readresp),"VERIFIED") == 0){
// if-fh works - cnxn OPEN';
// WE ALL WIN!!
}
else if(strcmp(trim($readresp),"INVALID") == 0){
// A possible hacking attempt, or
// In my case, a possible hacking false-positive
}
}
fclose ($fh);
}
?>
I'm testing using the IPN simulator in my sandbox account.
None of the SO recommended solutions have worked.
Please help!
For the record, this url seems to be working fine: https://www.paypal.com/cgi-bin/webscr
Related
I am testing with PayPals example IPN code which should return valid, or invalid for a transaction. I am testing with PayPals IPN simulator which should send some dummy data, and then validate it (returning "Valid").
I am testing with two separate web servers, both have OpenSSL installed and enabled.
On our local web server, we get this error message.
fgets(): SSL: An existing connection was forcibly closed by the remote host.
On our clients web server, with the same code, we get this:
fgets() [<a href='function.fgets'>function.fgets</a>]: SSL: Connection reset by peer in ...../paypal_ipn.php on line 43
PayPal doesn't seem to have a non-SSL version of this anymore.
paypal_ipn.php:
<?php
ini_set("log_errors", 1);
ini_set("error_log", "error.log");
// Send an empty HTTP 200 OK response to acknowledge receipt of the notification
header('HTTP/1.1 200 OK');
// Assign payment notification values 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'];
// Build the required acknowledgement message out of the notification just received
$req = 'cmd=_notify-validate'; // Add 'cmd=_notify-validate' to beginning of the acknowledgement
$req .= '&'.http_build_query($_POST);
// Set up the acknowledgement request headers
$header = "POST /cgi-bin/webscr HTTP/1.1\r\n"; // HTTP POST request
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
// Open a socket for the acknowledgement request
//$fp = fsockopen('www.sandbox.paypal.com', 80, $errno, $errstr, 30);
//$fp = fsockopen ('www.paypal.com', 80, $errno, $errstr, 30);
$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
if ($fp === FALSE) {
error_log("Could not open socket");
exit("Could not open socket");
}
// Send the HTTP POST request back to PayPal for validation
fputs($fp, $header . $req);
while (!feof($fp)) { // While not EOF
$res = fgets($fp, 1024); // Get the acknowledgement response
if (strcmp ($res, "VERIFIED") == 0) { // Response contains VERIFIED - process notification
// Send an email announcing the IPN message is VERIFIED
$mail_From = "IPN#example.com";
$mail_To = "Your-eMail-Address";
$mail_Subject = "VERIFIED IPN";
$mail_Body = $req;
file_put_contents("log.txt", "valid: " . $req, FILE_APPEND | LOCK_EX);
// Authentication protocol is complete - OK to process notification contents
// Possible processing steps for a payment include the following:
// Check that the payment_status is Completed
// Check that txn_id has not been previously processed
// Check that receiver_email is your Primary PayPal email
// Check that payment_amount/payment_currency are correct
// Process payment
}
else if (strcmp ($res, "INVALID") == 0) { //Response contains INVALID - reject notification
// Authentication protocol is complete - begin error handling
// Send an email announcing the IPN message is INVALID
$mail_From = "IPN#example.com";
$mail_To = "Your-eMail-Address";
$mail_Subject = "INVALID IPN";
$mail_Body = $req;
file_put_contents("log.txt", "invalid: " . $req, FILE_APPEND | LOCK_EX);
}
}
fclose($fp); // Close the file
?>
I am not going to be using CURL, as that is whole other lot of problems! Can anyone see what could be causing these two (separate) errors?
EDIT:
I've just tested on another server running XAMPP (nearly everything enabled), and I now get this 'error':
PHP Warning: fgets(): SSL: The operation completed successfully.
Yet, the transaction doesn't get validated at all.
Right well after a day of struggling with this, I went home, and decided to tackle it this morning.
It looked like there was an issue with using fget / fputs. I could browse to the verification URL using the post data in my browser and could see that the URL I was using was working fine.
I couldn't use CURL due to some other issues and not enough time to solve them.
*Solution*:
Use file_get_contents() instead. This made things easier, and no need to send headers or anything else. This works flawlessly!
$url = 'https://www.sandbox.paypal.com/cgi-bin/webscr?' . $req;
$res = file_get_contents($url);
I've had the same exact problem today but after a couple hours I finally located the root cause. It's perfectly fine to use Paypal's original PHP code but unfortunately it's fairly outdated ever since they switched over to HTTPS. In order to use fgets, you'll need to include the HOST in the header. For a quick fix, here is the code sample I used:
$parsed_url = parse_url('https://www.sandbox.paypal.com/cgi-bin/webscr'); // Development (sandbox) or production URL
$header = "POST $parsed_url[path] HTTP/1.1\r\n";
$header .= "Host: $parsed_url[host]\r\n";
Hope it works for you.
this is the first time I am using Paypal to process a payment. I have set up a developer account, created some test merchant/buyer accounts and successfully created a cart that I sent to paypal sandbox. Here, using one of my buyers accounts, I can, again, successfully complete the purchase. In the account history of paypal, it shows that the transaction was completed. All good till now.
The problem lays in the return url, where I get the IPN. Here is the code I copied from paypal website.
<?php
include($_SERVER['WROOT'].'core/init.php');
//Put together postback info
$postback = 'cmd=_notify-validate';
foreach($_POST as $key =>$value){
$postback .= "&$key=$value";
}
// EMAIL $postback
// build the header string to post back to PayPal system to validate
$header = "POST /cgi-bin/webscr HTTP/1.1\r\n";
$header .= "Host: www.sandbox.paypal.com\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($postback) . "\r\n\r\n";
// also tried with $fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
$fp = fsockopen ('www.sandbox.paypal.com', 80, $errno, $errstr, 30);
if(!$fp){ //no conn
die();
}
//post data back
fputs($fp, $header . $postback);
while(!feof($fp)){
$res=stream_get_contents($fp, 1024);
if((strcmp($res, "VERIFIED")) == 0){
// EMAIL with a success notification
// update the database - THIS IS ONLY A SMALL TEST TO SEE IF THE TRANSACTION IS SUCCESSFUL -
$new = $dbh->prepare(" ISNERT INTO orders(txn_id) VALUES(:txn_id) ");
$new->execute(array( 'txn_id' => $_POST['txn_id'] ));
} else if ( strcmp ($res, "INVALID") == 0 ) {
// EMAIL with a error notification
// LOG THE ERROR TO A FILE
}
}
?>
Ok, first of all I don't do any check to see if the email, gross amount and other parameters are valid, that is something I will do after I can solve this problem.
Anyway, after a buyer pays, my database should update, but it does not.
What I did ?
First of all, I email me the $postback variable as soon as it's created and it worked. I got the email with a huge string of response and all the data was correct.
But the $res variable is not either VERIFIED or INVALID so anything past the fsockopen() does not work.
As I said, the payment on the paypal website is successfull. The documentation is fairly poor and I can't get an answer.
The one thing I want to add is that my website does NOT have a SSL certificate, but I do not store any of the customer data, everything is processed on the secure paypal website. Do I need a SSL certificate ?
One last thing. I tried using this class https://github.com/Quixotix/PHP-PayPal-IPN and the error log message is Invalid response status: 302
I am trying to capture when the user made a payment in paypal(sandbox). IPN was the answer for this but it seems I am not getting any response from it. I tried to put it in a log and even the DB but there are no data entering. I know IPN is a listener but I dunno why isnt working.. Any clarification and thoughts about this?
I am using codeigniter Paypal_Lib
Here are my script:
First I declared the fields
$this->paypal_lib->add_field('cmd','_cart');
$this->paypal_lib->add_field('upload','1');
$this->paypal_lib->add_field('business', $business_email);
$this->paypal_lib->add_field('return', site_url('invoice/payment_success/'.$invoice_id.'/'.$album_id));
$this->paypal_lib->add_field('cancel_return', site_url('invoice/payment_cancel/'.$invoice_id));
$this->paypal_lib->add_field('notify_url', site_url('invoice/payment_validate/'.$invoice_id)); // <-- IPN url
$this->paypal_lib->add_field('custom', $invoice_id); // <-- Verify return
Here is for my IPN , just checking if it works by having a log but no luck.
public function payment_validate(){
//$this->paypal_lib->dump();
$invoice_id = $this->uri->segment(3);
if ($this->paypal_lib->validate_ipn()){
$payer_email = $this->paypal_lib->ipn_data['payer_email'];
$fp=fopen('temp/logs/paypal_ipn.log','a');
fwrite($fp, $this->paypal_lib->ipn_data . "\n");
fclose($fp); // close file
}
}
And here is the validate_ipn in the library.
function validate_ipn()
{
// get instance
$CI =& get_instance();
// parse the paypal URL
$url_parsed = parse_url($this->paypal_url);
// generate the post string from the _POST vars aswell as load the
// _POST vars into an arry so we can play with them from the calling
// script.
$post_string = '';
#if(count($_POST))
if($_POST)
{
#foreach (array_keys($_POST) as $field)
foreach ($_POST as $field => $value)
{
#$value = $CI->input->post($field, true);
$this->ipn_data[$field] = $value;
$post_string .= $field.'='.urlencode(stripslashes($value)).'&';
}
}
$post_string.='cmd=_notify-validate'; // append ipn command
// open the connection to paypal
$fp = fsockopen($url_parsed['host'],'80',$err_num,$err_str,30);
if(!$fp)
{
// could not open the connection. If loggin is on, the error message
// will be in the log.
$this->last_error = 'fsockopen error no. '.$err_num.': '.$err_str;
$this->log_ipn_results(false);
return false;
}
else
{
// Post the data back to paypal
fputs($fp, "POST $url_parsed[path] HTTP/1.1\r\n");
fputs($fp, "Host: $url_parsed[host]\r\n");
fputs($fp, "Content-type: application/x-www-form-urlencoded\r\n");
fputs($fp, "Content-length: ".strlen($post_string)."\r\n");
fputs($fp, "Connection: close\r\n\r\n");
fputs($fp, $post_string . "\r\n\r\n");
// loop through the response from the server and append to variable
while(!feof($fp))
$this->ipn_response .= fgets($fp, 1024);
fclose($fp); // close connection
}
if (eregi('VERIFIED',$this->ipn_response))
{
// Valid IPN transaction.
if ($this->ipn_log_method == 'db')
{
return $this->log_ipn_results(true);
}
/*// Valid IPN transaction.
$this->log_ipn_results(true);*/
return true;
}
else
{
// Invalid IPN transaction. Check the log for details.
$this->last_error = 'IPN Validation Failed.';
$this->log_ipn_results(false);
return false;
}
}
Answering my own question:
Unable to receive paypal IPN because there's a problem when connecting to paypal , fixing this issue all you have to do is to change the port from:
$fp = fsockopen($url_parsed['host'],'80',$err_num,$err_str,30);
to
$fp = fsockopen($url_parsed['host'],'443',$err_num,$err_str,30);
Now receiving the connection is established and receiving paypal IPN notification.
Later part I experience that testing IPN in sandbox is failing and I found out that IPN in sandbox is not working. You can refer here
I know this is a silly question, but are you developing this on a localhost? The PayPal IPN may not be able to reach your server when you test it on localhost. Consider tools like PageKite, Localtunnel and etc to put your local computer to the internet so PayPal can reach it.
Another way is to login to the merchant sandbox account and view the IPN history to see exactly what's the error.
Having made a newbe error let me try again.
The website is for a non-profit and I added a PayPal "donation" page couple of years ago; done as a shopping cart (3 types of donations and/or membership). PP returns to a php script that uses the PDT data to build a thank-you page and set cookies for a double-opt-in mailing list. IPN sends the thank-you email, opt-in email, database, etc. works fine.
Now adding a PayPal option to ticket reservation pages. Again, a shopping cart that calls PayPal and seems to work fine in the sandbox; PayPal screen is as expected, and the two foo emails look correct. I pass a different return URL which gets called but bombs on the hand shake. The code is cut&past from the previous effort and based on the PayPal PDT example.
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-synch';
$header = "";
$tx_token = $_GET['tx'];
$req .= "&tx=$tx_token&at=$auth_token"; // see header for def of auth_token
// post back to PayPal system to validate
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($req) . "\r\n\r\n";
// If possible, securely post back to paypal using HTTPS
// Your PHP server will need to be SSL enabled
$fp = fsockopen ($socket, $port, $errno, $errstr, 30); // see header
if (!$fp)
{
// HTTP ERROR
mail($email, "PDT Error", "fsockopen error " . $errno . ": " . $errstr);
}
else
{
fputs ($fp, $header . $req);
// read the body data
$res = '';
$headerdone = false;
while (!feof($fp))
{
$line = fgets ($fp, 1024);
echo $line . "<br>";
if (strcmp($line, "\r\n") == 0)
{
// read the header
$headerdone = true;
}
else if ($headerdone)
{
// header has been read. now read the contents
$res .= $line;
}
}
The results from the added echo are:
HTTP/1.0 302 Found
Location: https://www.sandbox.paypal.com
Server: BigIP
Connection: close
Content-Length: 0
The var $auth_token, $socket and $port are set up with a "if ($test)" to switch between the sandbox and live. Obviously with 0 length payload, nothing else works.
I read here that about the new Auth_token. There are some other nits about testing for SUCCESS that I haven't gotten to, with no data to play with..
I can't remember, does the sandbox trigger the IPN? I'm getting nothing there either - set for the sandbox. Thanks for any suggestions about where to look.
I implemented a dynamic button "buy now" (not saved in my PayPal account) with IPN and it works fine (yeah!).
Now I have a doubt about his security, because if someone change with firebug (for example) the amount value, the transaction is valid for paypal also if my IPN listener says there is a problem with amount.
My question is "Can I encrypt the form with a php / codeigniter library?"
Because I tried to check amount in the IPN listener, but the transaction on paypal continue correctly and It isn't blocked from IPN.
Here, you find a part of my listener code:
private function isVerifiedIPN(){
$req = 'cmd=_notify-validate';
$posts = $this->input->post();
foreach ($posts as $key => $value){
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
if($this->config->item('SIMULATION'))
$url = $this->config->item('SIMULATION_URL');
else
$url = $this->config->item('PRODUCTION_URL');
if(!$this->isVerifiedAmmount() ||
!$this->isPrimaryPayPalEmail() ||
!$this->isNotProcessed()){
$req = '';
}
$header = "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Host: $url\r\n"; //443
$header .= "Content-type: application/x-www-form-urlencoded\r\n";
$header .= "Content-length: " . strlen($req) . "\r\n\r\n";
$fp = fsockopen ("ssl://$url", 443, $errno, $errstr, 30);
if (!$fp)
{
$this->sendReport("Errore connessione socket");
return FALSE;
}
else
{
fputs ($fp, $header . $req);
while (!feof($fp))
{
$res = fgets ($fp, 1024);
if (strcmp($res, "VERIFIED") == 0)
{
// transizione valida
fclose ($fp);
return TRUE;
}
else if (strcmp ($res, "INVALID") == 0)
{
$this->sendReport('Transizione non valida');
fclose ($fp);
return FALSE;
}
}
}
}
You can dynamically encrypt buttons so that people with Firebug (or similar software) can't edit them. The PayPal API library has an example of this you can use, but I can't find it again right now.
This PayPal help file explains how to get the various keys you need using your server command line.
I also found a tutorial and a certificate builder (I didn't use, so can't confirm how secure it is...)
Once you've generated your key and certificate, you need to put them on your server and set DEFAULT_EWP_PRIVATE_KEY_PATH and DEFAULT_EWP_CERT_PATH to the relevant files.
Upload the public certificate to PayPal (instructions in linked tutorials), and set DEFAULT_CERT_ID to the Cert ID it gives you for that file. It'll also give you a file you can download - add that to your server and set PAYPAL_CERT_PATH to the path for that file.
For those who find it too hard to use a library to get the encryption going, or have hosting requirement issues with getting that working, the other trick is to not encrypt, but create a hash that you pass so that you can detect tampering, and then validate this hash when the IPN comes in for processing. I explain this here:
How do I make a PayPal encrypted buy now button with custom fields?