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.
Related
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
When somebody wants to buy something from me website he has to fill a form that is creating a URL like this and the redirect to PayPal:
https://www.paypal.com/cgi-bin/webscr?cmd=_xclick&business=buy#example.com&Product_Number_11&amount=4.20&return=http://www.example.com/product/Product_Number_11/%26payment=success%26order_token=a3f7e6fc6273c368ab374c5152ff2b63&cancel_return=http://www.example.com/error.php
This will transmit the billing details to PayPal. If payment succeed PayPal will redirect to http://www.example.com/product/Product_Number_11/&payment=success&order_token=a3f7e6fc6273c368ab374c5152ff2b63. The script on my website will get the order_token and finish the order by sending the product to the buyer.
Problem is: A user could copy the string http://www.example.com/product/Product_Number_11/&payment=success&order_token=a3f7e6fc6273c368ab374c5152ff2b63 and call it in browser manually without having payed.
What can I do to prevent this? Is PayPal website certificate the right?
In fact, you cannot prevent a user to call the payment URL manually.
What you have to do is to request a validation from PayPal itself.
Each time you receive a payment, the first thing to do is to open a connection to www.paypal.com, and send all the POST data you received, with the additional variable cmd: cmd=_notify-validate.
Here is an example payment validation:
// read the post from PayPal system and add 'cmd'
$req = 'cmd=_notify-validate';
foreach ($_POST as $key => $value) {
$value = urlencode(stripslashes($value));
$req .= "&$key=$value";
}
// 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";
$fp = fsockopen('ssl://www.paypal.com', 443, $errno, $errstr, 30);
if (!$fp) {
// HTTP ERROR => RETRY LATER and check $errstr
}
else {
fputs($fp, $header.$req);
while (!feof($fp)) {
$res = fgets($fp, 1024);
if (strcmp($res, "VERIFIED") == 0) {
// PAYMENT VALIDATED & VERIFIED!
break;
}
else if (strcmp($res, "INVALID") == 0) {
// PAYMENT INVALID => INVESTIGATE MANUALLY!
break;
}
}
fclose($fp);
}
Conclusion: do not send automatically a product to a customer if you do not get the VERIFIED status from PayPal.
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
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?