Not receiving a response from Paypal IPN Sandbox - php

I'm putting a paypal checkout onto my website but am falling down with the listener.
For those of you who are unfamiliar with the Paypal IPN system, basically Paypal sends your script with a message about the transaction, which you send back with a couple of bits added. If Paypal receives the correct reply, it'll reply with 'VERIFIED', and if not it'll say 'INVALID'.
I've succeeded with the first bit. My code is able to receive the info from paypal, add on the extras and post it back. However, I get no response from the Sandbox saying either 'VERIFIED' or 'INVALID'. I've pretty much copied my code from the paypal website so I was hoping this was going to be fairly straightforward, so if you could take a minute to look at my code, perhaps some new eyes could pick out where I've gone wrong.
Here's the code. Nothing special, it literally just gets the info, adjusts it, passes it back and reads the response (which it either isn't getting or doesn't realise it's getting)
<?php
$debug=true;
//Put together postback info
$postback = 'cmd=_notify-validate';
foreach($_POST as $key =>$value){
$postback .= "&$key=$value";
}
// 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";
$fp = fsockopen ('www.sandbox.paypal.com', 80, $errno, $errstr, 30);//open the connection
if(!$fp){ //no conn
die();
}
//post data back
fputs($fp, $header . $postback);
while(!feof($fp)){
$res=fgets ($fp, 1024);
if((strcmp($res, "VERIFIED")) == 0){ //verified!
if($debug){
$filename = 'debug/debug5_verified.txt'; //create a file telling me we're verified
$filehandle=fopen($filename, 'w');
fwrite($filehandle,'VERIFIED!');
fclose($filehandle);
}
}
}
?>
Thanks in advance!

Switch over to using the HTTPS url, I'm not sure when but recently all of my test scripts started failing on the plain HTTP version. They look to be migrating over.
I'm using the same paypal sample code you are:
$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
or
$fp = fsockopen ('ssl://www.paypal.com', 443, $errno, $errstr, 30);

So I think I found a solution. Turns out it wasn't having trouble with connecting to ssl://sandbox...., it was actually retrieving the answer. The code was getting hung up on the
while(!feof($fp)){
$res=fgets($fp,1024);
}
bit. All I did was replace it with:
$res=stream_get_contents($fp, 1024);
and it worked first time! Now I can get on with my life. Thanks again for all the help on this one.

Perhaps the original code was missing:
$header .= "Connection: close\r\n\r\n";
Note that the Paypal sample code uses HTTP/1.0 so does not have that line. And HTTP/1.1 is fine but might need the line.
On another issue, Sandbox may no longer support port 80. I am getting a 302 redirect to https://www.sandbox.paypal.com.

I've noticed that the URL you are posting to is a little different than below, could this be it?
this is from the IPN Testing help page:
Check that your are posting your response to the correct URL, which is https://www.sandbox.paypal.com/cgi-bin/webscr or https://www.paypal.com/cgi-bin/webscr, depending on whether you are testing in the Sandbox or you are live, respectively.
Verify that your response contains exactly the same IPN variables and values in the same order, preceded with cmd=_notify-validate.
Ensure that you are encoding your response string and are using the same character encoding as the original message.
EDIT: Sorry I also wanted to mention that the port for HTTP and HTTPS are different, 80 as opposed to 443. I'm not too familiar with Paypal API but could look into it as I see you are using 80.

PayPal test server moved to:
$fp = fsockopen('ssl://ipnpb.paypal.com', 443, $errno, $errstr, 30);

check up php version tls socket. it should be tls 1.2 to get the response from sandbox account. upgrade the php version to 5.5 to get tls 1.2 socket.
paypal has disabled the service of sslv3 and changed to tls 1.2.
if you need to get the response,php version must require tls 1.2, in order to get tls 1.2 php can be upgraded to 5.5 or more.
visit the link.

Related

Paypal PHP not working. No response received from Sandbox; only Connection: close

I've been at it for 24 hours now and am about to pull my hair out. I've checked all over the Web and within Stackoverflow and can find nothing that helps. I've even checked previous posts such as Not receiving a response from Paypal IPN Sandbox but the responses aren't working for me. Perhaps I've missed the post that'll help me; if I have, please excuse me for repeating a same question, but this matter is rather urgent and I'm at the end of my rope.
Anyway, the issue is this:
I have a PHP cart going and it sends clients to Paypal. Everything is working perfectly. I've built it according to Paypal's instructions when it comes to sending all codes to them. Everything fine except when it comes to the final step, the return POST from Paypal back to my server to check everything's OK. Even there I've copied the entire Paypal code as is and just added the functions that'll check everything matches on my part. However, I can't get it to work. I try sending the results to a DB and nothing happens. So I placed a PHP Mail command right where the last action should be if everything's OK and I don't receive the E-Mail. I've placed this same Mail command almost everywhere else to try and find out what's going on and have received different clues from different places.
First, when I call to print into the E-Mail the vars Paypal's supposed to send back to my server, I get the E-Mail with all vars except for item_name and item_number, which I get blank; this is strange, because up to that point Paypal Sandbox is showing me every single product I'm supposedly purchasing. I tried giving these vars on the PHP code a fixed value in light of the fact that Paypal is not posting a value to them, at least at that point. I wanted to see if the lack of these two values was the reason for the error; the error persisted, however, even with the set values.
Second, there's a while loop in the Paypal code (the loop that a response in the link given above says one should replace with $res=stream_get_contents($fp, 1024);, this, however, didn't work for me either), so I placed the Mail command in there to see what I got back. I asked to print the $res var and the payment_status and got, as expected, several E-Mails, the collected results of which are as follows:
payment_status = Pending
$res = HTTP/1.0 302 Found
Location: https://www.sandbox.paypal.com
Server: BigIP
Connection: close
Content-Length: 0
So I'm guessing the error is occurring at this point. Something is happening (or not happening) which is not only not returning the Completed status for payment_status that the code looks for in order to continue checking and etc., but it's not even returning an INVALID response. So I'm getting no response and nothing to show me whether everything's OK or not. And, what's strange, and even worse, is the fact that Sandbox is still fake-charging me for every single one of these payment attempts, even though it's clear that there's an error somewhere and the process is not being fulfilled. (This, of course, brings out another dilemma: once all this is solved, if there is a server error at some point for X reason, will Paypal still charge my client and I won't even know somebody has bought something on my Website?)
Well, I suppose that's as much as I can explain on this matter. At this point I can't see where I could have gone wrong. The code should make this a pretty straightforward thing. So really I don't see how I could have messed up the copy & paste. I suppose that at least should have no errors, unless they come with the original code. Moreover, because everything else to that point is working 100% fine, the only place where this error could be is within this PHP file Paypal has to call once the payment process is completed by the user.
ANY idea on the matter will be EXTREMELY appreciated!
Here goes the code.
PHP CODE:
include('../inc/db_fns.inc'); //<--All my DB work is in here.
include('../inc/shipping.inc');
$paypal_email = "seller_1360198925_biz#hotmail.com";
$paypal_currency = 'USD';
//Here begin the functions I'm using to check everything and pass data to DB.
function no_paypal_trans_id($trans_id) {
$connection = db_connect();
$query = sprintf("SELECT id FROM orders WHERE paypal_trans_id = '%s'", mysql_real_escape_string($trans_id));
$result = mysql_query($query);
$num_results = mysql_num_rows($result);
if($num_results == 0) {
return true;
}
return false;
}
function payment_amount_correct($shipping, $params) {
$amount = 0.00;
for ($i=1; $i <= $params['num_cart_items']; $i++) {
$query = sprintf("SELECT precio FROM products WHERE id='%s'", mysql_real_escape_string($params["item_number{$i}"]));
$result = mysql_query($query);
if($result) {
$item_price = mysql_result($result, 0, 'precio');
$amount += $item_price * $params["quantity{$i}"];
}
}
if(($amount+$shipping) == $params['mc_gross']) {
return true;
} else {
return false;
}
}
function create_order($params) {
db_connect();
$query = sprintf("INSERT INTO orders set orders.firstname = '%s', orders.lastname = '%s', orders.email = '%s', orders.country = '%s', orders.address = '%s', orders.city = '%s', orders.zip_code = '%s', orders.state = '%s', orders.status = '%s', orders.amount = '%s', orders.paypal_trans_id = '%s', created_at = NOW()", mysql_real_escape_string($params['first_name']), mysql_real_escape_string($params['last_name']), mysql_real_escape_string($params['payer_email']), mysql_real_escape_string($params['address_country']), mysql_real_escape_string($params['address_street']), mysql_real_escape_string($params['address_city']), mysql_real_escape_string($params['address_zip']), mysql_real_escape_string($params['address_state']), mysql_real_escape_string($params['payment_status']), mysql_real_escape_string($params['mc_gross']), mysql_real_escape_string($params['txn_id']));
$result = mysql_query($query);
if(!$result) {
return false;
}
$order_id = mysql_insert_id();
for ($i=1; $i <= $params['num_cart_items'] ; $i++) {
$product = find_product($params["item_number{$i}"]);
$query = sprintf("INSERT INTO items set order_id = '%s', product_id = '%s', title = '%s', price = '%s', qty = '%s'", mysql_real_escape_string($order_id), mysql_real_escape_string($product['id']), mysql_real_escape_string($product['title']), mysql_real_escape_string($product['price']), mysql_real_escape_string($params["quantity{$i}"]));
$result = mysql_query($query);
if(!$result) {
return false;
}
}
return true;
}
//Here begins the Paypal code as is
// 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 ('www.sandbox.paypal.com', 80, $errno, $errstr, 30); //<--Here I also tried out the other response to the question linked above and it actually returned a 'Invalid host' into he $res var-
// assign posted variables to local variables
$item_name = $_POST['item_name']; //<--These are the two vars for which
$item_number = $_POST['item_number'];// Paypal Sandbox posts no value.
$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'];
if (!$fp) {
// HTTP ERROR
} else {
fputs ($fp, $header . $req);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
if (strcmp ($res, "VERIFIED") == 0) {
if ($_POST['payment_status'] == 'Completed' && no_paypal_trans_id($_POST['txn_id']) && $paypal_email == $_POST['receiver_email'] && $paypal_currency == $_POST['mc_currency'] && payment_amount_correct($shipping, $_POST)) {
// process payment
create_order($_POST);
}
} else if (strcmp ($res, "INVALID") == 0) {
// log for manual investigation
}
}
fclose ($fp);
}
EDIT 1: Gave it another go: No luck!
I've just tried the original, untouched Paypal code. I've just copied and pasted it into this PHP and then run the Sandbox buy. Same outcome. No response, no data inserted into tables, no way of me knowing somebody has actually bought something off my cart, but Paypal still fake-charges the buy. This supports my suspicion that the error comes in the original code and has nothing to do with the functions I've added. Hope somebody can make sense of this soon. Thousand thanks in advance to that person!
EDIT 2: Following 1st response.
A kind user who has now deleted his/her response was kind enough to give it a first try. He/she recommended that the Paypal documentation mentions https://www.sandbox.paypal.com/cgi-bin/webscr where I have http://www.sandbox.paypal.com. Perhaps I have an older version of the code, so I tried out his/her recommendation. Now I comment what happened:
First of all, thanks a lot for your response. Unfortunately, no luck with it. Having substituted one address with the other, now the process doesn't get to the while loop, it stops at the fsockopen. I placed a Mail command to print out the $errno and $errstr contained in that command. With https://www.sandbox.paypal.com/cgi-bin/webscr substituting http://www.sandbox.paypal.com I get Unable to find the socket transport "https" - did you forget to enable it when you configured PHP? So I stripped the "https://" and tried again. Now I get php_network_getaddresses: getaddrinfo failed: Name or service not known. Any ideas?
EDIT 3: Following my conversation with #hexacyanide in the comments section:
$req = cmd=_notify-validate&test_ipn=1&payment_type=instant&payment_date=20%3A55%3A22+Feb+08%2C+2013+PST&payment_status=Completed&address_status=confirmed&payer_status=verified&first_name=John&last_name=Smith&payer_email=buyer%40paypalsandbox.com&payer_id=TESTBUYERID01&address_name=John+Smith&address_country=United+States&address_country_code=US&address_zip=95131&address_state=CA&address_city=San+Jose&address_street=123%2C+any+street&receiver_email=seller%40paypalsandbox.com&receiver_id=TESTSELLERID1&residence_country=US&item_name1=something&item_number1=AK-1234&quantity1=1&tax=2.02&mc_currency=USD&mc_fee=0.44&mc_gross_1=9.34&mc_handling=2.06&mc_handling1=1.67&mc_shipping=3.02&mc_shipping1=1.02&txn_type=cart&txn_id=2229455&notify_version=2.4&custom=xyz123&invoice=abc1234&charset=windows-1252&verify_sign=AsdNkKD2ktCz.aUB.9WYWy-g8MHoAa-TsvSjUgGstseJVdUhQTq3aCwW
Sorry, that was $req, not $res var. The $res var is returned by the while loop. I placed a mail command there and I get several E-Mails with the $res values. These are:
X-Frame-Options: SAMEORIGIN
HTTP/1.0 200 OK
Strict-Transport-Security: max-age=14400
INVALID
Content-Type: text/html; charset=UTF-8
Date: Sat, 09 Feb 2013 05:44:33 GMT
Connection: close
Content-Length: 7
EDIT 4: Absolutely dumbfounded: Not even simple Paypal IPN code straight from the Paypal Developer website and tested with the Paypal Developer website IPN Tester works...
I've used the Paypal IPN script generator found here.
I chose to generate a script that would mail back to me a VALID or INVALID response.
I copied it in its entirety and pasted it into a PHP file I saved in my server.
I checked and re-checked that no part of the code was left behind.
I targeted this PHP file on my server from the IPN Simulator found here.
Simulator shows a Check and says "IPN successfully sent."
I go to check my mail to see what response I got, whether it was VALID or INVALID... I got no response at all. So the IPN reaches the PHP buy at some point the process is broken so that it doesn't even get to the VALID/INVALID functions. However, this is the very code Paypal gives you and is tested with the very sim it has designed for it. So what's going on?
DAY 2 OF THIS ODYSSEY
O.K. Needless to say: no luck yet. But I've now decided to exhaustively track everything that's going into the PHP code and out to Paypal. The idea is that a response of VALID occurs when this code sends back to Paypal those initial values Paypal inserted into it and in the very same order (this according to Paypal documentation).
The first issue is that I was receiving no response at all from the code. So the supposition was that the code was failing at the very start. It happened that the first issue was that the Sandbox links do no work. (I got to this point with the aid of #hexacyanide, who was very kind to follow along yesterday with my progress or lack thereof. Whenever I get enough reputation to positively mark his/her response, I will.) So I stripped off the sandbox. and, voilĂ , it was returning a response, but now the response was INVALID.
From that time to now I've understood that IPN is just for seller-side transaction checking and data logging. Paypal doesn't care about the outcome of the IPN. So my cart IS working 100%. A user can buy something and Paypal will charge the user for it. The IPN will only check for me if the buy info is in fact coming from Paypal and all is well there and will then (in my script) send all this info into a DB so I can fulfill my side of the service. So, even though the transaction happens successfully without IPN, IPN would be an invaluable mechanism to facilitate the overall selling process. However, all this last info settles my suspicion that all my cart's code is O.K., and that the issue is ONLY here, with this code, or the way Paypal is handling it. (The fact that Paypal's "virgin" code passed through Paypal's own IPN Simulator returns no response, but the same error at the initial state of not being able to connect to the the Paypal server makes me inclined to think that the issue has to do with something wrong on Paypal's side.)
So, the log of everything going into and out of this PHP. I located Mail commands at every single significant point in the code in order to create checkpoints. Inside the foreach() command I included an incremental variable in order to have and ordered list of the POSTED data coming in. And this is what I got:
This is the data being POSTED into the code by Paypal (in the order it's being POSTED)
test_ipn=1
payment_type=instant
payment_date=08%3A42%3A23+Feb+09%2C+2013+PST
payment_status=Completed
payer_status=verified
first_name=John
last_name=Smith
payer_email=buyer%40paypalsandbox.com
payer_id=TESTBUYERID01
business=seller%40paypalsandbox.com
receiver_email=seller%40paypalsandbox.com
receiver_id=TESTSELLERID1
residence_country=US
item_name1=something
item_number1=AK-1234
quantity1=1
tax=2.02
mc_currency=USD
mc_fee=0.44
mc_gross=15.34
mc_gross_1=12.34
mc_handling=2.06
mc_handling1=1.67
mc_shipping=3.02
mc_shipping1=1.02
txn_type=cart
txn_id=23291642
notify_version=2.4
custom=xyz123
invoice=abc1234
charset=windows-1252
At a glance, I seem to be getting all the data I should from Paypal. And this is the order in which PP sends it. So if I were to send this data in this order back and PP actually does check it for this same order, then I should, in theory, get a VALID response.
So what the code does is add "cmd=_notify-validate", which is the only addition (the PP documentation makes it clear that this is necessary addition is necessary), and passes all this data into a var called $req in this way: A=X&B=Y&C=Z....
Here is the message from the next checkpoint, added just after the foreach() command closes.
$req = cmd=_notify-validate&test_ipn=1&payment_type=instant&payment_date=08%3A42%3A23+Feb+09%2C+2013+PST&payment_status=Completed&payer_status=verified&first_name=John&last_name=Smith&payer_email=buyer%40paypalsandbox.com&payer_id=TESTBUYERID01&business=seller%40paypalsandbox.com&receiver_email=seller%40paypalsandbox.com&receiver_id=TESTSELLERID1&residence_country=US&item_name1=something&item_number1=AK-1234&quantity1=1&tax=2.02&mc_currency=USD&mc_fee=0.44&mc_gross=15.34&mc_gross_1=12.34&mc_handling=2.06&mc_handling1=1.67&mc_shipping=3.02&mc_shipping1=1.02&txn_type=cart&txn_id=23291642&notify_version=2.4&custom=xyz123&invoice=abc1234&charset=windows-1252&verify_sign=AFcWxV21C7fd0v3bYYYRCpSSRl31ACyRxUQ6LVDwUz.i78mjQLsN9aKb
Note the verify_sign=AFcWxV21C7fd0v3bYYYRCpSSRl31ACyRxUQ6LVDwUz.i78mjQLsN9aKb at the end. I can't account for that piece of data because it was not spit out in the form of an E-Mail during the foreach() loop. So where does it come from? Could this be the culprit piece of data that returns the INVALID response? Open question. But at this point, all this data is passed by the code at the "DATA COMING IN" checkpoint; so if this data is what's coming in and my code sends it back out, then everything should be fine.
So the next checkpoint will be "DATA COMING OUT." I located this checkpoint right before the fputs ($fp, $header . $req); code which writes $req back to PP. And this is what is coming out of the code:
$req = cmd=_notify-validate&test_ipn=1&payment_type=instant&payment_date=08%3A42%3A23+Feb+09%2C+2013+PST&payment_status=Completed&payer_status=verified&first_name=John&last_name=Smith&payer_email=buyer%40paypalsandbox.com&payer_id=TESTBUYERID01&business=seller%40paypalsandbox.com&receiver_email=seller%40paypalsandbox.com&receiver_id=TESTSELLERID1&residence_country=US&item_name1=something&item_number1=AK-1234&quantity1=1&tax=2.02&mc_currency=USD&mc_fee=0.44&mc_gross=15.34&mc_gross_1=12.34&mc_handling=2.06&mc_handling1=1.67&mc_shipping=3.02&mc_shipping1=1.02&txn_type=cart&txn_id=23291642&notify_version=2.4&custom=xyz123&invoice=abc1234&charset=windows-1252&verify_sign=AFcWxV21C7fd0v3bYYYRCpSSRl31ACyRxUQ6LVDwUz.i78mjQLsN9aKb
HUM! Am I missing something? Are my eyes skipping over the tiniest hiccough that makes this code trip? The output seems to be exactly the same as the input. Let's assume that PP worked up their code fine, that the POSTED data is turned into a string in a way that PP expects and, therefore, checks for... assuming all this, what could be wrong?
Let's see... again: open questions:
Could it be an issue with the header? According to documentation, this is the header one should use... so unless they're off on this, then it should work fine.
Could it be an issue with the URL given? O.K., this IS in fact an issue, or rather WAS at some point. There are at least 6 or 7 different URLs running around the Internet which are supposed to work and not work. Some have worked for some people and not for others, some seem never to work, etc. Not even PP themselves are sure what URL is supposed to be used, as in their own documentation it varies. What is clear is that using the sandbox. URLs will return nothing--one is to suppose that these are broken.
Could it be an issue with the charset? Could there be an incompatibility? This just occurred to me as I was seeing the data come in and out with Hex codes. The thing is that this should make it compatible, right? However, yesterday, as I was checking what was going on in the while() loop, and catching the info sent back from PP into the $res var I wasn't getting Hex codes, as you can see further up in this testament.
The next checkpoint is the classic one at if() VALID and if() INVALID. And, of course, I get the one that tells me it all came out INVALID, once again.
Hopefully this detailed info I'm giving will make someone's light bulb shine brightly! As for me, I'm still dumbfounded.
Thanks in advance!
THANKS A LOT TO BOTH OF YOU FOR YOUR RESPONSES!
I haven't tried the second one out, though it sounds pretty sensible. Sounds like that could have been the issue with the original code I got from PayPal. If I have any time to try it out and see if the code works with it, I'll post my feedback here. Hopefully someone will find all this useful.
After a week banging my head against the walls and a little time off to cool the issue in my brain, I have finally, a couple of days ago, found another way to solve the issue somewhere in the Web which is pretty similar to the second part of Hexacyanide's response. I'm posting it below for anyone who's following this string. However, I must say, the PayPal code is pretty buggy. At least the one I received. I have since had several other issues I've had to solve via workarounds. An the worst part is that PayPal has no support AT ALL. I even called three times: the first, the person hung up on me; then, they told me it could be a virus issue (typical response when they don't really know what's going on).
Anyway, I wish I could rate both your responses but I need more than +15 points for that, so I'll praise them verbally here. I tried out Hexacynide's response code as is but it didn't work or me, so I can't mark it as checked. If I have time to try out the other in the future, and it works, I'll mark it as correct.
Until then, here is my code update using CURL.
$ch = curl_init('https://www.paypal.com/pe/cgi-bin/webscr');
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_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
if( !($res = curl_exec($ch)) ) {
// error_log("Got " . curl_error($ch) . " when processing IPN data");
curl_close($ch);
exit;
}
curl_close($ch);
NOTE: Here it's targeted to the real thing because it's now up and running, however, using it with sandbox. works as well.
I'm new to Stackoverflow, so I didn't realize one can answer one's own question. Anyway, I had answered it at the end of the original post by editing it about last week. Now I include it here so that anybody who's having such trouble can refer back to this and realize this is the way I was able to solve the issue.
I tried the first solution given here by a fellow contributor but unfortunately it didn't work for me. The second solution arrived once I had already been able to solve it another way, so I haven't tried it. Perhaps I'll get to it sometime in the future. But if anyone tries it, please leave your comments here so other benefit from the wisdom.
Here is my solution:
After a week banging my head against the walls and a little time off to cool the issue in my brain, I have finally, a couple of days ago, found another way to solve the issue somewhere in the Web which is pretty similar to the second part of Hexacyanide's response. I'm posting it below for anyone who's following this string. However, I must say, the PayPal code is pretty buggy. At least the one I received. I have since had several other issues I've had to solve via workarounds. An the worst part is that PayPal has no support AT ALL. I even called three times: the first, the person hung up on me; then, they told me it could be a virus issue (typical response when they don't really know what's going on).
Anyway, I wish I could rate both your responses but I need more than +15 points for that, so I'll praise them verbally here. I tried out Hexacynide's response code as is but it didn't work or me, so I can't mark it as checked. If I have time to try out the other in the future, and it works, I'll mark it as correct.
Until then, here is my code update using CURL.
$ch = curl_init('https://www.paypal.com/pe/cgi-bin/webscr');
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_SSL_VERIFYPEER, 1);
curl_setopt($ch, CURLOPT_SSL_VERIFYHOST, 2);
curl_setopt($ch, CURLOPT_FORBID_REUSE, 1);
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Connection: Close'));
if( !($res = curl_exec($ch)) ) {
// error_log("Got " . curl_error($ch) . " when processing IPN data");
curl_close($ch);
exit;
}
curl_close($ch);
NOTE: Here it's targeted to the real thing because it's now up and running, however, using it with sandbox. works as well.
Try changing this:
// 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 ('www.sandbox.paypal.com', 80, $errno, $errstr, 30);
To this:
// 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 .= "Connection: close\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.sandbox.paypal.com', 443, $errno, $errstr, 30);
Essentially, PayPal is discontinuing support for HTTP 1.0. I believe that it's already been discontinued on the Sandbox, but it's still supported on the live site (but not for much longer). This article has some more information on it: https://www.paypal-notify.com/eventnotification/event_details?eventId=2891
Additionally, you should be posting back to the SSL-enabled host. I want to say that PayPal will refuse to verify an IPN on the HTTP URL (and just redirect to the PayPal home page on the SSL-enabled server), but I'm not 100% sure on this.
Lastly, to answer your question -- IPN is an asynchronous process, and it doesn't start until after the payment has completed. Therefore, if you don't receive an IPN for whatever reason, the payment has already completed and won't get reversed just because you didn't receive the IPN. If you want something that will definitely ping back your site before the payment is made, I'd suggest looking into Express Checkout.
I've just been through this. You have to APPEND the command to the response rather than prepending it. Once you do that, you should receive the coveted 'VERIFIED' response from paypal. For your convenience, I have posted the code that worked for me. I've been where you are, brother, and figured I would actually respond rather than closing the browser and going to bed (I've had a long day with this too!). Anyway, without further adieu, here 'tis:
function verifyPayment($request)
{
if($request->is('post') && !empty($request->data))
{
//build response string
$response = '';
foreach($request->data as $field => $value)
{
//build response string
$response .= $field . '=' . urlencode(stripslashes($value)) . '&';
}
if(empty($id))
{
return false; //invalid transaction
}
//append notify validate command
$response .= "cmd=_notify-validate";
//respond to paypal - open connection
$parsedUrl = parse_url('https://www.sandbox.paypal.com/cgi-bin/webscr');
$sock = fsockopen('ssl://' . $parsedUrl['host'], "443", $errorNumber, $error, 30);
if(!$sock)
{
return false; //could not open connection - must be able to verify
}
//post data back to paypal
fputs($sock, "POST $parsedUrl[path] HTTP/1.1\r\n");
fputs($sock, "Host: $parsedUrl[host]\r\n");
fputs($sock, "Content-type: application/x-www-form-urlencoded\r\n");
fputs($sock, "Content-length: " . strlen($response) . "\r\n");
fputs($sock, "Connection: close\r\n\r\n");
fputs($sock, $response . "\r\n\r\n");
//loop through response and append
$ipnResponse = '';
while(!feof($sock))
{
$ipnResponse .= fgets($sock, 1024);
}
//close connection
fclose($sock);
if(!eregi("VERIFIED", $ipnResponse))
{
return false; //paypal reports invalid transaction
}
//if we made it here, transaction is valid
return true;
}
}
I sincerely hope that this helps you and anyone else dealing with the frustration of paypal!
Cheers!
Jay Good

Connect through HTTPS instead of HTTP

I want to use a simple API and i want to do it in the secure way.
It is currently using sockets and port 80. As far as I know port 80 is open and it doesn't seem such a secure connection.
As the data to send contains user and password i want to use HTTPS instead of HTTP to make it secure.
I was wondering if it is so simple as just changing this line;
$headers = "POST /api/api.php HTTP/1.0\r\n";
For this other line
$headers = "POST /api/api.php HTTPS/1.0\r\n";
And changing the port to 443
Here is the connect function:
// api connect function
function api_connect($Username, $Password, $ParameterArray)
{
// Create the URL to send the message.
// The variables are set using the input from an HTML form
$err = array();
$url = "api.text-connect.co.uk";
$headers = "POST /api/api.php HTTP/1.0\r\n";
$headers .= "Host: ".$url."\r\n";
// Create post string
// Username and Password
$poststring = "Username=".$Username."&";
$poststring .= "Password=".$Password;
// Turn the parameter array into the variables
while (list($Key, $Value)=#each($ParameterArray))
{
$poststring .= "&".$Key."=".urlencode($Value);
}
// Finish off the headers
$headers .= "Content-Length: ".strlen($poststring)."\r\n";
$headers .= "Content-Type: application/x-www-form-urlencoded\r\n";
// Open a socket
$http = fsockopen ($url, 80, $err[0], $err[1]);
if (!$http)
{
echo "Connection to ".$url.":80 failed: ".$err[0]." (".$err[1].")";
exit();
}
// Socket was open successfully, post the data.
fwrite ($http, $headers."\r\n".$poststring."\r\n");
// Read the results from the post
$result = "";
while (!feof($http))
{
$result .= fread($http, 8192);
}
// Close the connection
fclose ($http);
// Strip the headers from the result
list($resultheaders, $resultcode)=split("\r\n\r\n", $result, 2);
return $resultcode;
}
?>
Your code has a huge number of issues regardless if it's using HTTP or HTTPS - implementing an HTTP client (or server) is MUCH more complicated than simply throwing some headers across a socket then sinking the response.
What's particularly bad about this approach is that it will work some of the time - then it will fail and you won't understand why.
Start again using curl.
Doing it this way you only need to change the URL (it also implements a cookie jar, support for header injection, automatic following of redirects, routing via proxies, verification or non-verification of SSL certificates amongst other things).
I was wondering if it is so simple as
No, it isn't. It really, really isn't.
HTTPS is HTTP tunnelled over SSL. So you don't change the content of the HTTP request at all.
You do need to perform all the SSL handshaking before you do the HTTP stuff though.
SSL is crypto, it is therefore hard. Don't try reinventing this wheel. Use a library such as cURL.
curl
and set CURLOPT_SSL_VERIFYPEER = false

CodeIgniter RESTful, async / background process

I'm using codeIgniter RESTful API (https://github.com/philsturgeon/codeigniter-restserver) that return information (json format) to my android/iphone app.
There are an operation where i send some values, if it is everything OK i return 200 code as response.
Now, i want to add a new operation at the same method: send notifications of this modifications with APNS (Apple Push Notificacion Service) and GCM (Google Cloud Messaging).
It works well when i have to send no more than 3-5 notifications, the problem is APNS, because i have to send this messages one by one and it takes a long time, so my apps recieves a timeout exception (all the notifications are sent but the user get the Error Connection...)
Can i send the 200 code response and then continue sending this notifications? (Something like this...)
function my_update_method_post(){
//....GET my POST values
update($data);
$this->response(array('result'=>1),200));
//Send Notifications
....
}
Thanks in advance...
I found a solution that works perfect for me because i don't expect any result value. If notification can't be send...i log it in my database.
This is the function that i use to send "async" request (yes, This is not an asynchronous request, but it works how i'm looking for)
function curl_post_async($url, $params)
{
$post_string = http_build_query($params);
$parts=parse_url($url);
$fp = fsockopen($parts['host'],
isset($parts['port'])?$parts['port']:80,
$errno, $errstr, 30);
if(!$fp)
{
//Perform whatever logging you want to have happen b/c this call failed!
}
$out = "POST ".$parts['path']." HTTP/1.1\r\n";
$out.= "Host: ".$parts['host']."\r\n";
$out.= "Content-Type: application/x-www-form-urlencoded\r\n";
$out.= "Content-Length: ".strlen($post_string)."\r\n";
$out.= "Connection: Close\r\n\r\n";
if (isset($post_string)) $out.= $post_string;
fwrite($fp, $out);
fclose($fp);
}
Yes this is possible.
You should look at PHP exec() and this link. You should set up a function in your controller to be called from the command line. you will then pass in an array of the GCM/APNS data to be used.
This solution is not ideal because you won't be able to tell the client that all message were sent successfully. You will send back 200 to say the request was received ok and that is all.
Since PHP doesn't natively support threads or asynchronus function calls you will have to use a kindof hacky solution.
Have a look at my question here: PHP file_get_contents() follow Content-length header
The Solution is to send a Connection: Close and Content-Length header, then make the client to be aware of these headers (see link above). In case of curl for example the connection will be closed as soon as the Content-Length is reached, but your PHP Script still runs "in the background" so you can start time consuming operations then.
Kind regards,
Stefan
P.S. If the Script takes really long to execute, make sure that the PHP max exection time doesn't get in your way
Take a look at this article. I like this solution much more than one where you have the client tell the server to hang up immediately; there are multiple benefits if you build this solution on the server side.
You know the server will continue processing once the client has disconnected
The client can still receive a response from the server
EDIT
I'd not realized OP doesn't have access to the service here. In this case, the article I've mentioned is of little value. The problem here is the server is taking a long time to respond and hanging the client up. For this I suggest curl_multi_init. This allows you to make a number of requests simultaneously.

PHP Async GET request works on one server, but doesn't on the other

Please see the edits at the bottom for additional information!
I have two servers. Both should be able to call each other with a GET request.
To make the request (it's more firing an event than makeing a request actually) I am using this code:
function URLCallAsync($url, $params, $type='POST')
{
foreach ($params as $key => &$val) {
if (is_array($val)) $val = implode(',', $val);
$post_params[] = $key.'='.urlencode($val);
}
$post_string = implode('&', $post_params);
$parts=parse_url($url);
$fp = fsockopen($parts['host'],
isset($parts['port'])?$parts['port']:80,
$errno, $errstr, 30);
// Data goes in the path for a GET request
if('GET' == $type) $parts['path'] .= '?'.$post_string;
$out = "$type ".$parts['path']." HTTP/1.1\r\n";
$out.= "Host: ".$parts['host']."\r\n";
$out.= "Content-Type: application/x-www-form-urlencoded\r\n";
$out.= "Content-Length: ".strlen($post_string)."\r\n";
$out.= "Connection: Close\r\n\r\n";
// Data goes in the request body for a POST request
if ('POST' == $type && isset($post_string)) $out.= $post_string;
fwrite($fp, $out);
fclose($fp);
}
I feed the function with the exact same data (but the url) on both servers (I copied the calling file to test it!!) but it only works in one direction!
I write the calls to that function in a log file so I can investigate if something is going wrong.
Server A -> Server B, works exactly as it should, the logfile at server A contains the correct url
Server B -> Server A, only prints the correct information in the logfile of server B, but Server A never receives the request.
What could be the reason for something like this?
edit:
Could it be the differnt kinds of server?
Server A is nginx, Server B is apache.
Server A also has a '~' symbol in it's url, maybe thats the problem?
The parameters of the get request are encoded with php's "urlencode" maybe that creates problems?
I tried around a bit, but the problem is still that the request isn't coming trough to Server A. But from a browser it works perfectly somehow (assuming I enter the correct URL with the parameters).
edit2:
If I exchange "URLCallAsync" with "file_get_contents" it works like it should. But the problem is that file_get_contents is blocking!
So it can only be the function itself. But strangely it works in the opposite direction :(
edit3:
The function "URLCallAsync" runs trough without error, notice or anything else.
It just isn't received by the other server.
What exactly is file_get_contents doing so different???
I got it working.
After a lot of fiddling with wireshark I found that file_get_contents is even simpler than my async function!
It simply omits the Content-Length field completly! It just provides "GET ..." and "Host".
It also uses HTTP/1.0 instead of 1.1, but that didn't change anything.
So the solution is: Also posting the Content-Length header (which had the value 0, since i used GET) will somehow make the server reject the request. I don't know for sure if it was the server that rejected the request, or something else, like a firewall that maybe detected a "malformed" request, but at least the problem is solved.
So next time you send requests, don't provide the Content-Length header if you don't need it :)

PHP/CodeIgniter PayPal IPN Invalid Validation

I'm having a website build on CodeIgniter 2 and I'm using the CodeIgniter PayPal Lib. I have done everything neccessary and I'm now able to proceed payments. I receive an IPN data an I have it send to my e-mail. I have read the PayPal IPN Guide, but I couldn't find a solution there.
Evetything fine untill here and I'm happy with the result, but I'm concerned, because the PayPal IPN verification fails and I cannot understand where is the problem.
When I send an IPN test from the sandbox test site I receive a valid IPN, but when I make a payment from my website the IPN validation fails.
I'm logging all the data and in the both cases (valid or invalid) the payment is successful and i have a "SUCCESS!" message from PayPal.
Things I have tryed
Change the CSRF protection on/off
Change encoding (utf-8 | windows-1252)
Adding|Removing fields from my PayPal request
Code I'm using
The fields I'm using
$this->paypal_lib->add_field('business', 'name_1321715512_biz#gmail.com');
$this->paypal_lib->add_field('return', site_url('paypal/success'));
$this->paypal_lib->add_field('cancel_return', site_url('paypal/cancel'));
$this->paypal_lib->add_field('notify_url', site_url('contest/receive_ipn'));
$this->paypal_lib->add_field('item_name', 'Contest Subscribtion Payment (Test)');
$this->paypal_lib->add_field('amount', '30');
$this->paypal_lib->add_field('item_number', Y11-1329469079-12); // Reference number
$this->paypal_lib->add_field('quantity', '1');
$this->paypal_lib->add_field('charset', 'utf8');
$this->paypal_lib->add_field('custom', 1723); //This is an id that I need.
The post to PayPal for validation
$post_string.="cmd=_notify-validate";
if (isset($_POST)){
foreach ($_POST as $field=>$value){
$value = str_replace("\n", "\r\n", $value);
$value = urlencode(stripslashes($value));
$post_string .= "&$field=$value";
$this->ipn_data[$field] = $value; //this is part of the library
}
}
$header .= "POST /cgi-bin/webscr HTTP/1.0\r\n";
$header .= "Content-Type: application/x-www-form-urlencoded\r\n";
$header .= "Content-Length: " . strlen($post_string) . "\r\n\r\n";
$fp = fsockopen ('ssl://www.sandbox.paypal.com', 443, $errno, $errstr, 30);
The code to verify the IPN
fputs ($fp, $header . $post_string);
while (!feof($fp)) {
$res = fgets ($fp, 1024);
if (strcmp ($res, "VERIFIED") == 0) {
// Send me e-mail - Verified
} else if (strcmp ($res, "INVALID") == 0) {
// Send me e-mail - Invalid
}
}
fclose($fp);
Also I'm posting the response I receive
This one is the INVALID (then send from my website)
cmd=_notify-validate&mc_gross=30.00&protection_eligibility=Ineligible&address_status=unconfirmed&payer_id=LD9UMUWHD44GY&tax=0.00&address_street=Via+Unit+d%27Italia%2C+5783296&payment_date=02%3A19%3A29+Feb+17%2C+2012+PST&payment_status=Completed&charset=windows-1252&address_zip=80127&first_name=Alexander&mc_fee=1.37&address_country_code=IT&address_name=Alexander+Videnov&notify_version=3.4&custom=1721&payer_status=verified&business=aviden_1321715512_biz%40gmail.com&address_country=Italy&address_city=Napoli&quantity=1&verify_sign=AF1YwvTycK97c-VCwQnfsdzArWAcAqZjskElh-FsmZ0s9HqL9BjFUKVH&payer_email=aviden_1329133801_per%40gmail.com&txn_id=32Y96385XJ0686735&payment_type=instant&last_name=Videnov&address_state=Napoli&receiver_email=aviden_1321715512_biz%40gmail.com&payment_fee=&receiver_id=TQVQ3ASRDBW28&txn_type=web_accept&item_name=Yicca+Contest+Subscribtion+Payment+%28Test%29&mc_currency=EUR&item_number=Y11-1329473936-12&residence_country=IT&test_ipn=1&handling_amount=0.00&transaction_subject=1721&payment_gross=&shipping=0.00&ipn_track_id=35382e1887f00
And this is the VERIFIED (when send from PayPal test site)
cmd=_notify-validate&test_ipn=1&payment_type=echeck&payment_date=02%3A28%3A24+Feb+17%2C+2012+PST&payment_status=Completed&address_status=confirmed&payer_status=verified&first_name=John&last_name=Smith&payer_email=buyer%40paypalsandbox.com&payer_id=TESTBUYERID01&address_name=John+Smith&address_country=United+States&address_country_code=US&address_zip=95131&address_state=CA&address_city=San+Jose&address_street=123%2C+any+street&business=seller%40paypalsandbox.com&receiver_email=seller%40paypalsandbox.com&receiver_id=TESTSELLERID1&residence_country=US&item_name=something&item_number=AK-1234&quantity=1&shipping=3.04&tax=2.02&mc_currency=USD&mc_fee=0.44&mc_gross=12.34&txn_type=web_accept&txn_id=242171028&notify_version=2.1&custom=xyz123&invoice=abc1234&charset=windows-1252&verify_sign=An5ns1Kso7MWUdW4ErQKJJJ4qi4-Arge8MWjXZSo7fPSQf3xaqAOjrSH
Two things I have noticed
The order of the fields (the request) is different between IPN request send by my website and the one send from sandbox test site.
There is difference in the notify_version field. From my website (3.4) | From PayPal (2.1)
Did anybody expirienced the same problem with the validation. Is there something that I'm missing, or some way that I can debug more?
I got this response from Paypal which might be relevant to you also:
Now we know from experience that special characters can be difficult to deal with during the IPN validation.
Basically, the character encoding that we use to send the IPN Message to your server needs to be matched by your IPN Handler when it posts the message back to the PayPal Systems for verification.
We've had a lot of success at resolving these issues by changing the language encoding options of the account to UTF-8.
Here's how you do it:
1. Please log in to your PayPal Account.
Then follow this link that will take you directly to the language encoding options: https://www.paypal.com/cgi-bin/webscr?cmd=_profile-language-encoding
Click on the "More Options" Button.
Change both pull down menus to UTF-8 and click on save.
If you've specified a different character encoding in your code, please switch it to UTF-8 as well.
Not sure if you're using adaptive payments or not, but I used this library: http://www.binpress.com/app/paypal-adaptive-payments-pro-codeigniter-library/140 and it works awesome and the author is very helpful.
See my question here: Paypal IPN Issue with parallel payment. I've posted my paypal IPN code in the answer.
The CodeIgniter PayPal Lib is outdated and bugged all the time. At least in my experience.
What I do, and I would recommend, is using the official PayPal SDK's and 'convert' them to CI.
This is fairly easy.
You can find these here:
https://cms.paypal.com/us/cgi-bin/?cmd=_render-content&content_ID=developer/library_download_sdks
Also do check out there docs:
https://www.x.com/developers/paypal/development-integration-guides#ec
You can't live without them!

Categories