Basic Order Processing
Introduction
GPG Setup/Installation
Form-handling - Basic Form
Form-handling - Error Checking
Order Processing
PGP-enabled pine(optional)
Conclusion
Introduction
I recently had to set up a basic, but secure ordering system for a single product. In this system, the user would go online to a website, secured with HTTPS, enter all required information(Name, Phone Number, Address, Card Information, etc.) and have it all parsed by PHP. If all information was found to be valid, it would then need to transfer the credentials to a third-party that could then charge the account and ship out the order. This step is one of the most worrisome, as the credit information would have to be passed through a protocol other than HTTPS. The answer I came up with, however, was to use PGP(or GPG) to encrypt the information, making it so that only the end-destination could decrypt it. Once encrypted, the information was then transferred through email in an ASCII-friendly GPG encryption. This can then easily be printed out and/or deleted through MIME-TYPE triggers or other such options.
Please note that I have compressed a working example of all the required PHP code in a .tgz(gzipped tarball) located in Conclusion.
Please note that I have compressed a working example of all the required PHP code in a .tgz(gzipped tarball) located in Conclusion.
GPG Setup/Installation
Our very first step will be to install GPG on both the receiving system, as well as the sending system. Although there are PHP extensions that allow GPG/PGP usage through functions, we will be using the shell_exec command to interface with the gpg program, as that was how I was forced to implement it.
This step can, of course, be skipped if you have already set up PGP/GPG, but for the sake of a complete example, I will provide the details here. All these steps will need to be done for both/all machines involved.
Once these steps are repeated for both machines, we can transfer a test GPG-encrypted message from one machine to the other. First off, create the text file for transfer:
This step can, of course, be skipped if you have already set up PGP/GPG, but for the sake of a complete example, I will provide the details here. All these steps will need to be done for both/all machines involved.
thanatos@Glory:~/> gpg --gen-key
gpg (GnuPG) 1.4.10; Copyright (C) 2008 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
Please select what kind of key you want:
(1) RSA and RSA (default)
(2) DSA and Elgamal
(3) DSA (sign only)
(4) RSA (sign only)
Your selection? 1
RSA keys may be between 1024 and 4096 bits long.
What keysize do you want? (2048)
Requested keysize is 2048 bits
Please specify how long the key should be valid.
0 = key does not expire
= key expires in n days
w = key expires in n weeks
m = key expires in n months
y = key expires in n years
Key is valid for? (0)
Key does not expire at all
Is this correct? (y/N) y
You need a user ID to identify your key; the software constructs the user ID
from the Real Name, Comment and Email Address in this form:
"Heinrich Heine (Der Dichter) "
Real name: Thanatos
Email address: thanatos@glory.org
Comment:
You selected this USER-ID:
"Thanatos <thanatos@glory.org>"
Change (N)ame, (C)omment, (E)mail or (O)kay/(Q)uit? o
You need a Passphrase to protect your secret key.
We need to generate a lot of random bytes. It is a good idea to perform
some other action (type on the keyboard, move the mouse, utilize the
disks) during the prime generation; this gives the random number
generator a better chance to gain enough entropy.
......+++++
+++++
gpg: key 12345678 marked as ultimately trusted
public and secret key created and signed.
gpg: checking the trustdb
gpg: 3 marginal(s) needed, 1 complete(s) needed, PGP trust model
gpg: depth: 0 valid: 2 signed: 0 trust: 0-, 0q, 0n, 0m, 0f, 2u
pub 2048R/12345678 2010-07-31
Key fingerprint = AAAA BBBB CCCC DDDD EEEE FFFF GGGG HHHH IIII JJJJ
uid Thanatos
sub 2048R/ABCDEFGH 2010-07-31
Once you have created your PGP key, you will want to export your public key and transfer it to the other machine:
thanatos@Glory:~/> gpg --armor --export thanatos@glory.org > thanatos.keyThis will then create a file with the public PGP key contained within. Once this file is transferred to the other machine, and providing the other machine has GPG set up, it can be imported with the following command:
-bash-3.2$ ./gpg --import thanatos.key gpg: key 12345678: public key "Thanatos " imported gpg: Total number processed: 1 gpg: imported: 1 (RSA: 1)The next (optional) step you will want to do is to sign the key:
-bash-3.2$ ./gpg --edit-key thanatos@glory.org
gpg (GnuPG) 1.4.10; Copyright (C) 2008 Free Software Foundation, Inc.
This is free software: you are free to change and redistribute it.
There is NO WARRANTY, to the extent permitted by law.
pub 2048R/12345678 created: 2010-07-31 expires: never usage: SC
trust: unknown validity: unknown
sub 2048R/ABCDEFGH created: 2010-07-31 expires: never usage: E
[ unknown] (1). Thanatos
Command> sign
pub 2048R/12345678 created: 2010-07-31 expires: never usage: SC
trust: unknown validity: unknown
Primary key fingerprint: AAAA BBBB CCCC DDDD EEEE FFFF GGGG HHHH IIII JJJJ
Thanatos
Are you sure that you want to sign this key with your
key "Krang (The Main Brain) " (87654321)
Really sign? (y/N) y
You need a passphrase to unlock the secret key for
user: "Krang (The Main Brain) "
2048-bit RSA key, ID 87654321, created 2010-07-31
Command> quit
Save changes? (y/N) y
Once these steps are repeated for both machines, we can transfer a test GPG-encrypted message from one machine to the other. First off, create the text file for transfer:
receipt194023
Receipt Information: 1257097832109-0 Credit card #: 21478210947921842108This file can then be encrypted for delivery:
-bash-3.2$ ./gpg -a -r thanatos@glory.org -e receipt194023This should create a new file named "receipt194023.asc", which can be transferred to the other machine(in this case, Thanatos). Once transferred, it can then be decrypted using the following command:
thanatos@Glory:~/> gpg --decrypt receipt194023.asc
You need a passphrase to unlock the secret key for
user: "Thanatos "
2048-bit RSA key, ID 12345678, created 2010-07-31 (main key ID ABCDEFGH)
gpg: encrypted with 2048-bit RSA key, ID 12345678, created 2010-07-31
"Thanatos "
Receipt Information:
1257097832109-0
Credit card #: 21478210947921842108
With all these steps completed, we can now move on to the actual ordering and programming segment of this document.
Form-handling - Basic Form
The first step was, of course, to secure the server with an SSL certificate, allowing the webserver(in this case, Apache2) to initiate a connection to the client with HTTPS. I will not cover this here, as it can be a very lengthy process, especially if you need an authentic certificate(i.e., not self-signed or reported as insecure). However, if your host does not provide a certificate, or you are your own host, you can opt to either pay for a certificate, or use CAcert's free certificates. Keep in mind that CAcert functions based upon the length of your certificate ownership to verify trust, so it is wise to acquire a CAcert even if you are not immediately in need of one.
So, first step for the ordering page, which will be implemented with PHP, would be to create the PHP ordering page and add the following to the beginning of the page. This ensures that if a client connects using HTTP, it will always redirect to HTTPS:
Fairly straightforward to set up, however we will want to set up a "feature" to ensure that the end-user will not have to re-input all their information if they accidentally forget to input something. This can be accomplished using ternary short-hand in-text to add 'value="'.$_POST['value'].'"' to any of the form inputs. This will make it so that if the user has entered their information, but forgets to fill one of the fields, it will restore fields with said information. Although we are using SSL security, I find it unnecessary to transfer credit information back and forth so many times, and so credit information will not be restored.
The PHP code with the value restoration implemented would then be as follows:
So, first step for the ordering page, which will be implemented with PHP, would be to create the PHP ordering page and add the following to the beginning of the page. This ensures that if a client connects using HTTP, it will always redirect to HTTPS:
if ($_SERVER['HTTPS']!='on') {
header('Location: https://YOURDOMAIN.NET/');
exit;
}
The next step will be the basic implementation of the form itself, which should input the following information:
* - Denotes required * First Name Middle Initials * Last Name * Address Address 2 * City * State * Zip Code * Phone Number * E-mail Address (for updates and verification) ---------- * Cardholder Name (inputted exactly as appears on card) * Card Number * Card Expiration Date * CW2 Code Card Issuer Phone Number (for quick credit card verification)
Fairly straightforward to set up, however we will want to set up a "feature" to ensure that the end-user will not have to re-input all their information if they accidentally forget to input something. This can be accomplished using ternary short-hand in-text to add 'value="'.$_POST['value'].'"' to any of the form inputs. This will make it so that if the user has entered their information, but forgets to fill one of the fields, it will restore fields with said information. Although we are using SSL security, I find it unnecessary to transfer credit information back and forth so many times, and so credit information will not be restored.
The PHP code with the value restoration implemented would then be as follows:
echo ' <form action="?order" method="POST"> <label for="fname">* First Name: </label> <input type="text" name="fname"', ($_POST['fname'] != '' ? 'value="'.$_POST['fname'].'"' : ''),'> <br> <label for="mi">MI:</label> <input type="text" name="mi" size="2" ', ($_POST['mi'] != '' ? 'value="'.$_POST['mi'].'"' : ''),'> <br> <label for="lname">* Last Name:</label> <input type="text" name="lname" ', ($_POST['lname'] != '' ? 'value="'.$_POST['lname'].'"' : ''),'> <br> <label for="address">* Address:</label> <input type="text" name="address" size="48" ', ($_POST['address'] != '' ? 'value="'.$_POST['address'].'"' : ''),'> <br> <label for="address2">Address 2:</label> <input type="text" name="address2" size="48" ', ($_POST['address2'] != '' ? 'value="'.$_POST['address2'].'"' : ''), '> <br> <label for="city">* City:</label> <input type="text" name="city" size="12" ', ($_POST['city'] != '' ? 'value="'.$_POST['city'].'"' : '') ,'> <br> <label for="state">* State:</label> <input type="text" name="state" size="12" ', ($_POST['state'] != '' ? 'value="'.$_POST['state'].'"' : '') ,'> <br> <label for="zip">* Zip Code:</label> <input type="text" name="zip" size="14" ', ($_POST['zip'] != '' ? 'value="'.$_POST['zip'].'"' : '') ,'> <br> <label for="p1">* Phone:</label> (<input type="text" name="p1" size="3" ', ($_POST['p1'] != '' ? 'value="'.$_POST['p1'].'"' : '') ,'>) - <input type="text" name="p2" size="3" ', ($_POST['p2'] != '' ? 'value="'.$_POST['p2'].'"' : '') ,'> - <input type="text" name="p3" size="4" ', ($_POST['p3'] != '' ? 'value="'.$_POST['p3'].'"' : '') ,'> ext: <input type="text" name="pext" size="4" ', ($_POST['pext'] != '' ? 'value="'.$_POST['pext'].'"' : '') ,'> <br> <label for="email">* E-mail:</label> <input type="text" name="email" size="24" ', ($_POST['email'] != '' ? 'value="'.$_POST['email'].'"' : '') ,'> - Used for order updates and receipts. <br><br /> <label for="cname">* Cardholder Name:</label> <input type="text" name="cname" size="48"> - Enter exactly as appears on card. <br> <label for="cnum">* Card #:</label> <input type="text" name="cnum" size="17"> <br> <label for="cexp">* Exp. Date:</label> <input type="text" name="cexp" size="6"> <br> <label for="cw2">* CW2 Code:</label> <input type="text" name="cw2" size="4"> - 3 digit number found on the back of the card. <br> <label for="cphone">Card Issuer Phone #:</label> <input type="text" name="cphone" size="13"> - Optional for quick verification. <br><br> <input type="submit" name="submit" value="Process"> </form> ';
Form-handling - Error Checking
Now that we have set up our basic ordering form, with our $_POST['value'] checks in place, we can now set up error checking. There is a multitude of ways to handle basic error checking, however for the sake of tidy code, I will be using an array that references an error message based upon the <input> tag's name.
We will then run "foreach" with $_POST as the value - if an error is found, set the $return variable to "1", as well as output the error message.
The $return variable's "codes" will be:
We will then run "foreach" with $_POST as the value - if an error is found, set the $return variable to "1", as well as output the error message.
The $return variable's "codes" will be:
0 = begin processing 1 = no processingThe necessary code should then be inserted before the basic form, and should be the following:
$return = 1;
if ($_POST['submit']) {
$return = 0;
$inputchecks = array('fname' => 'You must enter a first name<br>',
'lname' => 'You must enter a last name<br>',
'address' => 'You must enter an address<br>',
'city' => 'You must enter a city<br>',
'state' => 'You must enter a state<br>',
'zip' => 'You must enter a zip code<br>',
'p1' => 'You must enter an area code<br>',
'p2' => 'You must enter the first 3 digits of the phone number<br>',
'p3' => 'You must enter the last 4 digits of the phone number<br>',
'cname' => 'You must enter the cardholder name<br>',
'cnum' => 'You must enter a credit card number<br>',
'cexp' => 'You must enter the credit card\'s expiration date<br>',
'cw2' => 'You must enter the credit card\'s CW3 Code<br>');
foreach ($_POST as $input=>$value) {
if($value == '' && $inputchecks[$input]) {
echo $inputchecks[$input];
$return = 1;
}
}
}
Now based upon the result of $return we can either process the order, or display the form again, reporting that an error has occured. This will be accomplished by putting a $return if check around the earlier basic form:
[Previous Error Checking Code]
if ($return != 0) {
[Even Earlier Basic Form]
} else {
[Future Location for Order Processing]
}
Order Processing
For this final segment we will want to accomplish the following:
Please note that I have provided a .tgz(tarred and gzipped) version of all the code, which is available in Conclusion.
[Read in form results and format in an easily understandable fashion] [Encrypt the formatted form results through GPG] [E-mail the PGP-encrypted block to the third-party for credit charging]First of all, let us create a $_POST['value'] reference array, similar to the one used in the Error-Checking segment:
$inputs = array("fname" => "First Name", "lname" => "Last Name",
"mi" => "Middle Initial", "address" => "Address",
"address2" => "Address 2", "city" => "City", state => "State",
"zip" => "Zipcode", "p1" => "Area Code", "p2" => "Phone 1",
"p3" => "Phone 2", "pext" => "Phone Extension", "email" => "E-mail",
"cname" => "Cardholder Name", "cnum" => "Card Number",
"cexp" => "Card Expiration Date", "cw2" => "CW2 Code",
"cphone" => "Card Issuer Phone Number");
With this in place we can then run a foreach $_POST loop similar to the one in Error-Checking:
foreach($_POST as $input=>$value) {
if ($input != 'submit') {
$msg = $msg.$inputs[$input].': '.$value.chr(13);
}
}
Our variable $msg now contains all the needed information in a tidy fashion. We can now generate a unique Order ID, encrypt $msg with GPG, and send off the encrypted order information:
$orderID = rand(0, 999999);
$encodedmsg = shell_exec('echo "'.$msg.'" | gpg -a -e -r thanatos@glory.org');
if (mail('thanatos@glory.org', 'Order #: '.$orderID, $encodedmsg)) {
echo '<h3>Order #:',$orderID,'</h3>
Your order has been placed and is currently being processed. You will receive an e-mail once the order has been completed.<br>
';
} else {
echo 'An error has occurred during pre-processing. Please send an email to tech@support.org<br>';
}
Now all that needs to be done is for the recipent of the PGP-encrypted order to decrypt it and process the order.
Please note that I have provided a .tgz(tarred and gzipped) version of all the code, which is available in Conclusion.
PGP-enabled pine(optional)
For this ordering system I had to implement PGP decryption support with the email program, pine. This part is, obviously, completely optional. I also realize I probably, in part, re-invented the pine-PGP wheel, as there are many guides and scripts on pine-PGP support.
Run pine, go into SETUP(shortcut key, 'S'), then into Config(shortcut key, 'C'). Once in those settings, hit 'W' and search for 'display-filters'. You may have to search twice, but once you find "dispay-filters =", you will want to hit enter to edit and add the following(replace /usr/local/bin/gpg with the ultimate path to your installation of gpg:
Run pine, go into SETUP(shortcut key, 'S'), then into Config(shortcut key, 'C'). Once in those settings, hit 'W' and search for 'display-filters'. You may have to search twice, but once you find "dispay-filters =
_BEGINNING("-----BEGIN PGP")_ /usr/local/bin/gpg -d
Hit return to accept, then 'E' to exit SETUP. Be sure to save your changes. You should now be able to open the PGP encrypted email. It will ask for a passphrase:
You need a passphrase to unlock the secret key for user: "Thanatos <thanatos@glory.org>" 2048-bit ELG-E key, ID ABCDEFGH, created 2010-07-31 (main key ID 1234578) Enter passphrase:It should then put you back in pine, but with the decrypted message displayed:
PINE 4.64 MESSAGE TEXT Folder: INBOX Message 12 of 12 79% Date: Sat, 31 Jul 2010 15:50:18 -0600 From: Ordering To: ThanatosSubject: Order #: 893933 Credit information, etc. ? Help < MsgIndex P PrevMsg - PrevPage D Delete R Reply O OTHER CMDS > ViewAttch N NextMsg Spc NextPage U Undelete F Forward
Conclusion
Although there are many open-source/free ordering PHP packages available, I found myself to be most comfortable with creating my own system, especially due to the fact that the setup itself was somewhat unique. There are, obviously, much room for improvement, such as keeping a check of unique order IDs(so that by some freak random occurance two users do not get the same ID), as well as many others minor things. However, I find it to be very functional and extremely reliable even in its current state.
If you choose to implement or use what I have done, be sure that you have a strong grasp on the concept of security, as well as the risks and responsibilities that accompany dealing with credit card information.
Since this guide is rather lengthy and can, no doubt, be confusing, here is a .tgz of all the required PHP code.
If you choose to implement or use what I have done, be sure that you have a strong grasp on the concept of security, as well as the risks and responsibilities that accompany dealing with credit card information.
Since this guide is rather lengthy and can, no doubt, be confusing, here is a .tgz of all the required PHP code.
0.0094258785247803 µs