Query String Vanishing?

The Scenario
I’ve got a PHP script (PHP version 5.2.17) that creates an order record in a MySQL database and then looks at how the user elected to pay for the order (credit card, PayPal, etc) and redirects to the appropriate payment page. As part of the redirection the Order ID is passed as a URL query string so that the relevant order may be retrieved by the payment page.

The payment page grabs the Order ID from the query string and retrieves the relevant order and then goes on to continue processing the order.

The Problem
99% of the time this works fine. But a very small number of times the payment page fails and emails me an error message saying that the order record couldn’t be read and that the Order ID was empty. If I compare the timestamp on the email with the timestamp of records in the Orders table I can see that an order was indeed created at that time and it has an Order ID. The only conclusion that I can come to is that the query string is somehow being altered to remove the Order ID.

The Code
The portion of code that generates the Order ID, saves the record and redirects to the appropriate payment page looks like this:


$orderid = md5(uniqid(rand()));
$schoolid = mysql_real_escape_string($_SESSION['SchoolID']);
$parentname = mysql_real_escape_string($_SESSION['ParentName']);
$email = mysql_real_escape_string($_SESSION['Email']);
$phone = mysql_real_escape_string($_SESSION['Phone']);
$sql = "INSERT INTO orders (OrderID,SchoolID,OrderDate,ParentName,Email,Phone) VALUES " .
"(\\"$orderid\\",\\"$schoolid\\",CURDATE(),\\"$parentname\\",\\"$email\\",\\"$phone\\")";
mysql_query($sql);
if (mysql_error()) {
  DisplayMySQLError("Could not create Orders record", $sql, TRUE);
}

switch($paymentmethod) {
  case "DPS":
    header("Location: payment-dps-send.php?orderid=$orderid");
    break;
  case "Direct Credit":
    header("Location: payment-direct-credit-response.php?orderid=$orderid");
    break;
  case "School":
    header("Location: payment-school-response.php?orderid=$orderid");
    break;
}
exit;

And then in each payment page I do the following:


$orderid = isset($_GET['orderid']) ? $_GET['orderid'] : "";
if (!$ordersRow = GetOrdersRecord($orderid)) {
  echo "Invalid Order ID";
  $msg = "Could not find Orders record for Order ID \\"$orderid\\" in " . $_SERVER['PHP_SELF'];
  NotifyWebmaster($msg);
  exit;
}

I know there are a few functions here that I haven’t included (ie GetOrdersRecords()) but I don’t think they are at fault.

I get an error saying: Could not find Orders record for Order ID “” in /payment-direct-credit-response.php

The error only seems to occur about 1% of the time, the rest of the time everything works fine. I’ve never been able to cause the error to occur myself, it’s only with live users (who unfortunately I’ve been unable to communicate with). The user usually tries again and everything goes through ok.

Things I’ve Considered
If the payment pages are accessed directly (i.e. not through the order page) then the Order ID won’t be sent in the query string and I will see the behaviour that I’m getting (an error message sent to me saying that there was no Order ID). I can think of a few different ways that the payment pages could be access directly:

[LIST=1][]A user types the URL in directly
[
]A search engine or other automated spider is accessing the page
[]A browser add-on is trying to pre-fetch the page to speed up access
[
]An anti-virus program is requesting the page so it can check it for malware[/LIST]

I don’t think it’s #1 or #2 because I know that these are real orders because real data is being stored in the database, which tells me that the order page has been accessed first.

I don’t think it’s #3 or #4 because why would they remove the query string before retrieving the page?

I’m now wondering if there is a bug in PHP that sometimes causes it to mis-handle query strings. Or maybe a bad browser that sometimes mangles the query string.

The fact that it works 99% of the time makes me believe that it isn’t my code that is at fault and it’s something outside my control.

I’m using this order setup on 5 different websites, all hosted by the same host, and they all get this error from time to time.

Does anyone have any ideas on what the problem might be?

Some more things you could try:

check your server log files and see if indeed an empty string is passed in these cases.

check the offending database entries, do they have similar characteristics?

The code in your payments page is somewhat ambiguous in that it will give out the same message given either case that:

$_GET[‘orderid’] is set at all

OR

if it is set, it does not match that from your db.

So, you might need to differentiate the cases and give yourself a different message:


$orderid = isset($_GET['orderid']) ? $_GET['orderid'] : "";  

if($orderid === ""){
  echo "Invalid Order ID";  
  // get some more useful info about the process .. you could add more
  $msg = "Failed to pass and order id in " . $_SERVER['PHP_SELF'] . " entire SERVER vars were " . var_export($_SERVER, true);  
  NotifyWebmaster($msg);  
  exit;  
} elseif ($ordersRow != GetOrdersRecord($orderid)) {  // changed condition slightly
  echo "Invalid Order ID";  
  $msg = "Could not find Orders record for Order ID \\"$orderid\\" in " . $_SERVER['PHP_SELF'];  
  NotifyWebmaster($msg);  
  exit;  
}

Overall though, unless I am missing a vital piece of information, would $_SESSION not be the correct place to store and retrieve such a crucial piece of information rather than leave your app open to query string abuse?

Thanks for your suggestions, Cups, I’ll definitely follow them up and see what I can find out.

Let us know how you get on. :slight_smile:

Someone else on another forum pointed out to me that my header() calls didn’t follow the specification because they weren’t absolute URI’s. I was doing:

header("Location: payment-dps-send.php?orderid=$orderid");

when I should have been doing:

header("Location: http://www.example.com/payment-dps-send.php?orderid=$orderid");

I don’t think this error was causing the problem but I’ve updated my header() calls and also added in more diagnostics in the email I receive when the error occurs to see if I can pick up any patterns. Unfortunately the problem hasn’t reoccurred since I’ve made those changes.

When you insert the unique order id in mysql database then pass the same order id on payment page by using $order_id = mysql_insert_id; and pass this id to the payment page. I think that can help you out.

I don’t use mysql_insert_id() because I generate the Order ID myself rather than using MySQL’s auto increment. This is because the Order ID may become public knowledge and I don’t want people to be able to guess the sequence and possibly interfere with things by injecting their own Order ID.

mysql_insert_id is the last inserted id in your table. I mean first you generate the unique id with unique function then it is inserted into mysql table and then use mysql_insert_id to payment page so that there will be no misconfustion that what id is going on payment page. Just i wanted to say.