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
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.
I'm using the Paypal IPN to update the status of an order to 2. Once we deliver the order, we manually change it to 3, but then sometimes it goes back to 2 on it's own (note that until it's 2, we can't even see it in our order panel).
Does the Paypal IPN update it again? What's causing this?
<?php
include('includes/config.php');
// 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
} else {
fputs ($fp, $header . $req);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
if (strpos($res, "VERIFIED") == 0) {
...
It sounds like IPN is updating it again, but there are a number of a reasons that could be happening.
If your IPN script has a problem with it that keeps from fully completing then PayPal will send the IPN again until it receives a 200 response back from your server. It's possible you have some sort of a syntax error or something that happens after the majority of the code runs. So the code runs, updates your system to a 2, but then fails after that, so PayPal sends the IPN again. Log into your PayPal account and check your IPN History to see if that could be what's going on.
Another reason could be if the original payment was pending for some reason. When IPN hit with the pending status it updates to 2, then it hits again once the payment status updates and updates it to a 2 again.
These are just a couple of ideas of what could possibly be happening. I would recommend adding some logging to your IPN script of some sort. A log file, database log, or even an email to yourself so you can see if it's running more than once for the same transaction. From there you could figure out exactly why it's running more than once.
You know those times where you feel really dumb after asking a question? This is one of those times.
I added AND 'status' = '0' to the query so even if it does check it later, it won't do anything.
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?