Processing Your Own Online Payments - A Brief Tutorial [Part 2]
August 9th, 2007This is the second part of an ongoing series on setting up your own online store. My initial plan was that this was going to be a two part series. Part 1 would be the client-side stuff, and part 2 would be the server-side stuff. Well as I sat down to write part 2, it kept getting longer and longer, so I decided to split it up. The new format as planned is:
- Part 1: Client-Side Storefront
- Part 2: Server-Side Validation and Routing
- Part 3: Server-Side Payment Processing
- Part 4: Adding PayPal Support
Once again, I’m using a PHP/JavaScript system because that’s what I’m familiar with. If your webhost supports Ruby on Rails, then you’ll save a lot of time by downloading the Potion Store from the folks over at Potion Factory.
And once again a discalaimer:
Disclaimer: I am not an accountant or lawyer, before doing anything involving other people’s money you should check with a lawyer and an accountant.
I’d like to thank everyone that gave me feedback from the previous article, please continue to do so.
Site Map
At the end of this series, I will have described a store that looks more or less like this:

Now I’m going to go through each of the server side pieces of this setup step by step, however first an update from the structure defined in Part 1.
Planning for PayPal
When I first set out to create my own online store, I never planned on having PayPal support. However it turns out that about 10% of all my prior sales went through PayPal (via Kagi), so I thought it would be worth my time to add it in.
With that in mind, I thought I’d go ahead and modify the storefront to allow for future payment processors as well.
This changes the storefront page that we talked about last week. If you look at our current storefront you’ll see that it now only has three items of data to collect:
- Product Quantity
- Upgrade Code
- Payment Processor
Everything else was moved to the store.php page further downstream. This change allows us to expand the store to add whatever payment processors we want. In part 4 of this tutorial I’ll talk about the specifics of adding PayPal support, for now though let’s go through the other store pages one by one to see how the process flow works.
-
index.php
As I mentioned above this page has three items. First it gets the number of items you want and using the fancy javascript from part 1, calculates the total price.
This page also allows you to enter the upgrade code to receive a price break. Since we want the customer to know exactly how much they are going to pay, it sure would be nice to give them the actual price after the upgrade discount. However in order to do that we need to make sure the upgrade code is valid. Web 2.0 to the rescue!
There are three steps to our live upgrade code validations:
- Create a table in a mysql database that has all of the 1.0 product codes in it.
- Create a php page that will lookup a code in the table and return an indicator saying if it is valid
- Create a little javascript function to call this page asynchronously so that we can update the price as soon as the user enters the code.
Create the table however you like, I use phpMyAdmin, but you may prefer to do it the hard way and use the MySQL CLI. The table that I created has exactly two fields: Code (a varchar field), and Used (a boolean or tinyint field).
The premise is simple. When the user enters the upgrade code, a javascript event will fire that will pass the upgrade code to the php page. The php page looks up the code in the table to verify that it exists and that it hasn’t already been used.
Here is the code for the php page (codeLookup.php on our chart):
- <?php
- require_once ‘./include/storeSettings.php’;
- $conn = mysql_connect(DB_HOST, DB_USER, DB_PASS) or die (”Not Found”);
- mysql_select_db(DB_NAME);
- $upgradeCode = mysql_real_escape_string($_GET["code"]);
- $query = “Select * from UpgradeCodes where Code = ‘$upgradeCode’”;
- $result = mysql_query($query);
- if(mysql_num_rows($result) == 0)
- {
- echo “Not Found”;
- }
- else
- {
- $row = mysql_fetch_array($result, MYSQL_ASSOC);
- $alreadyUsed = $row["Used"];
- if($alreadyUsed)
- {
- echo “Already Used”;
- }
- else
- {
- echo “Good Code”;
- }
- }
- mysql_close();
- ?>
This code is pretty straight forward if you have any familiarity with php/mysql. Line 2 is worth pointing out before we go any further. On line 2 we import a file that is in the include directory called storeSettings.php. This directory is password protected so that web browsers can’t request it directly, it has to come from the server. This file uses php define statements in the form of:
define(’DB_HOST’, ’someDB.Host.com’);
This defines the constant DB_HOST to be equal to the value “someDB.Host.com”. This allows us to reuse these values without having to retype them. Keeping them in the password protected directory keeps bad people from doing bad things with them. This can easily be done on Apache by putting an .htaccess file in the include directory that contains something along the lines of:
- AuthType Basic
- AuthName “Access for mysite.com/store/include”
- AuthUserFile /some/file/outside/the/webroot/folder
- require user someuser
The AuthName on line 2 is the blurb of text that will appear on the browser alert box that is displayed if the user tries to access the file. The AuthUserFile on line 3 is the name of the file that contains the username/password pairs. It is important that this file is not anywhere in your web hierarchy. Line 4 is the username that has access to this folder. For details on creating the password file see the man entry for htpasswd or this site.
Returning to our php code, on lines 3 and 4 we connect to our database using the constants defined in our storeSettings.php file. In line 5 we use a handy php function called mysql_real_escape_string. This function prevents clever hackers from trying to use sql injection attacks on our database by escaping out any characters that would cause the mysql engine to interpret the code as anything other than a plain old string.
In some cases, your php setup may do this automatically whenever a form is submitted, if that is true then using mysql_real_escape_string could cause somethings to be escaped twice which would be bad. You can check if this is the case by calling the get_magic_quotes function. It is considered a “best practice” to check this value before calling mysql_real_escape_string. In my case I know that this magic quotes isn’t enabled on my server so I don’t bother.
Moving on to lines 9 and 10 we run the actual query and get the result. In lines 6 - 11 we check to see if there were any rows returned by the query. If there were no rows returned, that means whatever the user entered isn’t a code in our database, so we return a value indicating that.
If there is a row returned, lines 14 and 15 retrieve the value from the “Used” column to see if that upgrade code has already been used. (It’s important to note that we don’t mark it as used until the sale is finalized, much later in the process.)
Finally in lines 16 - 23 we return the value indicating whether the code was used or not, and down in line 25 we close the database connection.
Now the javascript code is going to be a little more complex. The first thing that happens is that the user types in an upgrade code and then leaves the field (by tabbing or clicking something else). This causes the “onChange” event to fire.<input type=”text” onChange=”checkUpgrade(’itemWaitImg’)” size=”42″ name=”upgradeCode” class=”storeFormField” value=”"/>
Our onChange event is going to call a javascript function called checkUpgrade:
- function checkUpgrade(waitIMG)
- {
- document.getElementById(waitIMG).innerHTML = ‘<img src=”images/spinner.gif” alt=”Checking Code…”>’;
- var code = document.form1.upgradeCode.value;
- lookupCode(code, waitIMG);
- }
This function is pretty simple. It takes as its argument an html field which on line 3 it stuffs an image into. This image is meant to indicate to the user that something is happening behind the scenes. Since we live in a web 2.0 world, I’m using a spinner for this. A “span” element works well for this image container. Notice that I’m passing in the string “itemWaitImage”, this is the id of the span element where I want the image to appear.
Next on lines 4 and 5, we get the code that the user entered and pass that along with our image holder to the lookupCode function. This is the most complex function of the three:
- function lookupCode(code, waitIMG)
- {
- var xmlhttp=false;
- var checkResults = “Not Found”;
- try
- {
- xmlhttp = new ActiveXObject(’Msxml2.XMLHTTP’); //Try the first kind of activeX object
- }
- catch (e)
- {
- try
- {
- xmlhttp = new ActiveXObject(’Microsoft.XMLHTTP’); //Try the second kind of activeX object
- }
- catch (E)
- {
- xmlhttp = false;
- }
- }
- if (!xmlhttp && typeof XMLHttpRequest!=’undefined’)
- {
- xmlhttp = new XMLHttpRequest(); //If we were able to get a working active x object, start an XMLHttpRequest
- }
- var file = ‘codeLookup.php?code=’;
- xmlhttp.open(’GET’, file + code, true);
- var spinnerImg = waitIMG;
- xmlhttp.onreadystatechange=function()
- {
- if (xmlhttp.readyState == 4)
- {
- var content = xmlhttp.responseText;
- if( content )
- {
- checkResults = content;
- codeFound(checkResults, spinnerImg);
- }
- }
- }
- xmlhttp.send(null) //Nullify the XMLHttpRequest
- return checkResults;
- }
Wow! That code should help drive home the reason why you’re not a web developer. This is a cookie cutter AJAX processing function. If you google for any tutorials on AJAX, chances are you’ll find some version of this function.
The reason this function is so large and unwieldily is because of the dark and sordid history of web browsers. None of them can agree on how exactly to create the object responsible for asynchronous communication.
With the exception of line 4, lines 1 - 23 contain various attempts at creating an XMLHTTP object until the method is found. Line 4 simply specifies the default value for our code lookup.
Line 24 specifies the name of the php file that has our upgrade code lookup function. Notice that we’re appending a “?code=” to the end. This is so that we can pass in the code the user typed in and have it available to the codeLookup.php page that we discussed earlier. On line 25 is where we actually pass the code in using a “GET” method. The third parameter in that function (true), specifies whether or not the function should run asynchronously.
Next on line 26 we save a reference to our spinner image holder so that we can easily remove the spinner later.
Lines 27 - 38 define an inline function that will be called every time the asynchronous request changes state. There are 5 of these “ready states” (0 - 4), but we only care about state 4, “Loading Complete”. So in our function on line 29 we check if this new state that triggered the function is the final state. If it is, we pull out the response text and send it (along with our ever-present spinner image holder) to the codeFound function:
- function codeFound(checkResults, waitIMG)
- {
- document.getElementById(waitIMG).innerHTML = ”;
- var colorCode;
- if(checkResults == “Good Code”)
- {
- // Good Code
- itemPrice = 3995.0;
- colorCode = ‘#006600′;
- }
- else if(checkResults == “Already Used”)
- {
- alert(”It appears that the code you have entered has already been used.”);
- itemPrice = 6495.0;
- colorCode = ‘#000066′
- }
- else
- {
- // Bad Code
- itemPrice = 6495.0;
- colorCode = ‘#000066′
- }
- document.getElementById(”itemPrice”).innerHTML = “@ $” + (itemPrice/100).toFixed(2);
- document.getElementById(’itemPrice’).style.color = colorCode;
- updateField();
- }
This function is very simple. It could have been written more simply, but each case is broken out here for clarity. On line 3 we turn off our spinner image by setting the innerHTML property of our span container to an empty string.
Then on lines 5 - 22 we check which result we have. If we have a response of “Good Code”, then the price is changed, and the color of the product price is updated to highlight this fact to the user. These changes happen in lines 23 and 24.
Line 25 calls our updateField function from part 1 that updates the total field.
But wait! Can’t the user just change this price value themselves? Of course they can. This is just client-side, presentation stuff. Everything gets verified on the server. Repeat this in your mind: Never trust client-side forms, never trust client-side forms.
-
verify.php
From here on out things get considerably easier. No more nasty javascript code. The verify.php page is where the index.php form is submitted to:
- <?php
- session_start();
- require_once ‘./include/storeSettings.php’;
- // Set session variables
- $_SESSION['productPrice'] = 64.95;
- $_SESSION['productQuantity'] = $_REQUEST['productQuantity'];
- $_SESSION['upgradeCode'] = $_REQUEST['upgradeCode'];
- $_SESSION['paymentMethod'] = $_REQUEST['paymentMethod'];
- $_SESSION['isUpgrading'] = 0;
- // Connect to DB
- $conn = mysql_connect(DB_HOST, DB_USER, DB_PASS) or header(”Location: ” . ERROR_PAGE . “101″); // Error connectiong to DB
- mysql_select_db(DB_NAME);
- // Check quantity incase someone fooled with the page
- if($_SESSION['productQuantity'] < 1)
- {
- mysql_close();
- header(”Location: ” . ERROR_PAGE . “102″); // Invalid quantity
- }
- // Check upgrade code
- $UpgradeCode = mysql_real_escape_string($_SESSION['upgradeCode']);
- if(!empty($UpgradeCode))
- {
- // Check for code in database
- $query = “Select * from UpgradeCodes where Code = ‘$UpgradeCode’”;
- $result = mysql_query($query);
- if(mysql_num_rows($result) == 0)
- {
- mysql_close();
- header(”Location: ” . ERROR_PAGE . “103″); // Invalid upgrade code
- }
- $row = mysql_fetch_array($result, MYSQL_ASSOC);
- $alreadyUsed = $row["Used"];
- if($alreadyUsed)
- {
- mysql_close();
- header(”Location: ” . ERROR_PAGE . “104″); // Code already used
- }
- // Set for upgrade mode
- $_SESSION['productPrice'] = 39.95;
- $_SESSION['isUpgrading'] = 1;
- mysql_close();
- }
- $_SESSION['amountTotal'] = $_SESSION['productQuantity'] * $_SESSION['productPrice'];
- // Route to handler
- if($_SESSION['paymentMethod'] == “cc”) // Our own cc processor
- {
- header(”Location: https://www.polarian.com/store/polarianStore.php”);
- }
- else // PayPal
- {
- header(”Location: https://www.polarian.com/store/PayPalRouting.php”);
- }
- ?>
Line 2 of this script calls the session_start function. This will start a new session or resume the previous one started by the current client. This is handy because on lines 5-9 we store some things in session variables so that we can pick them up later in the process.
The first thing we do is verify that the quantity ordered is not less than one. This is prevented by our client-side javascript checking, but since we don’t trust client side data, we’re checking it again on the server. Why? Well if someone decided to make the quantity 0, when the total is calculated they would be getting some really discounted software.
Also notice that up on line 5, we specify the original product price, regardless of what happened with upgrade codes on the storefront page. This is for the same reason. We’re going to verify the upgrade code again in lines 19 - 42. Isn’t that redundant? Yes, but since we don’t trust client-side data, we have no choice.
On line 43, if we made it through all of those checks, we recalculate the total, and then in lines 45 through 53 we send the client off to the correct processing page based on the payment method that they selected on the storefront page. There are several ways to handle redirecting the client to another page, however I like the header function. This function sends a command to the browser along with a 302 redirect code, telling it to go to the page we specify. The only caveat to using the header command is that you have to send it before you send any other output to the client, otherwise you’ll get an error.
Notice that throughout the above verifications, if something goes wrong we use this line:
header(”Location: ” . ERROR_PAGE . “104″);
This sends a header command to the browser that tells it to go to a new location, the one specified by the ERROR_PAGE constant. This is another constant defined in our storeSettings.php file, and it is defined as follows:
define(’ERROR_PAGE’, ‘https://www.polarian.com/store/error.php?err=’);
The error number is then appended on, (in our above example it is error 104). The error page is amazingly simple:
- <?php
- $errorText = “We’re sorry, there was a problem with your transaction.”;
- switch($_REQUEST['err'])
- {
- case “103″:
- $errorText = “We’re sorry, there was a problem validating your upgrade code.”;
- break;
- case “104″:
- $errorText = “We’re sorry, It appears that the upgrade code you entered has already been used.”;
- break;
- }
- $mailText = “Error Number: ” . $_REQUEST['err'] . “\nTime: ” . gmstrftime(”%b %d %Y %H:%M:%S”, time()) . “\nReferrer: ” . $_SERVER["HTTP_REFERER"];
- mail(”me@mysite.com”, “Transaction Error Report”, $mailText, “From:errorpage@mysite.com\r\nReply-to:productsupport@polarian.com”);
- ?>
- <html>
- <head>
- <title>Error Page</title>
- </head>
- <body>
- <h3>Problem With Transaction</h3>
- <p><?=$errorText ?>Please verify that the information you entered was correct. <b>Your credit card has NOT been charged.</b>
- <p>If you feel that this is an error, please contact us at… </p>
- <p>You may try your transaction again by returning to the <a href=”https://www.polarian.com/store/”>store page</a>.</p>
- </body>
- </html>
.
.
.This page has three parts. Lines 3 - 11 consists of a switch statement that will set the message that is displayed to the user based on the error code that we passed in.
Lines 12 and 13 use the mail function to send me an email whenever someone arrives at the error page. This email will tell me what the error number was ($_REQUEST['err'] on line 12), what time it happened (gmstrftime(”%b %d %Y %H:%M:%S”, time())), and what page they came from ($_SERVER["HTTP_REFERER"]).
Lines 15 - 25 Are just standard HTML, with the exception of line 21 which has this handy php snippet:
<?=$errorText ?>
This prints out the $errorText variable that we set earlier in the switch statement. Notice that I’m using <?= rather than just <? to start the php code here. <?=$somevar ?> is a handy shortcut for <?php echo($somevar); ?>
End of Part 2
That wraps it up for part 2. Next time we’ll look at store.php, confirm.php, and process.php. Then in part 4 we’ll look at paypal.php and see what changes need to be made to incorporate PayPal on the server side.
Loving this series, Lee. I look forward to reading the rest so I can overhaul my own online store.
Lee,
This has been a really helpful set of articles. Are you still planning on finishing the series?
Extremely helpful! Thanks for the time you have put in to these articles.