This is part five of a multi-part series on handling online payments.
In Part 1 of this series we identified our goals (creating a payment form that was usable, accessible, and secure) and began by creating the form we will use to capture payment information. In Part 2 of this series we continued this process by exploring how we will handle the data submitted by that form. In Part 3 of this series we took the data received and sanitized in Part 2 and validated that it was in a format we required. In Part 4 of this series we took the errors we found in Part 3 and displayed them in a user-friendly format to minimize cart abandonment. In this installment we will process the payment using the PHP SDK and handle the response in an appropriate manner.
Picking Up Where We Left Off
In part 4 of this series we helped our users who submitted erroneous information to find where they made an error and correct it. Once a user has submitted their payment without any errors it is time to process their payment. When a payment is submitted there are three potential scenarios that can occur:
Each of these three scenarios require us to deliver custom responses to our users. How we handle the information submitted to us also will vary depending on your business needs. Let's look at how you might want to handle each of these scenarios one at a time.
Once the payment is approved we do not need anything else from our user. At this point we just want to complete the transaction and hand it off to whoever will be doing order fulfillment.
Our responsibilities will be to:
If a payment is declined we cannot complete the transaction as we do not have payment yet. However, the user is still present when this happens so all is not lost.
Our responsibilities will be to:
If an error occurs during the payment process, whether due to connectivity or server issues, things can become a bit complicated. Do we try again? Do we ask the user to try again? How do we handle this from a customer service point of view? Exactly how you want to handle this is a business decision you need to make.
In this example, our responsibilities will be to:
Let's Write The Code To Do This
Once we have finished validating our user's data we need to check to see if there where any errors. We can do this by checking to see if there are any values in the $errors array. If there are we know not to process the transaction and display our error message to the user. If there are none we can attempt to process the transaction.
If you haven't done so already you will need to download the PHP SDK as we will use it in our example.
// If there are no errors let's process the payment
if (count($errors) === 0)
{
// Format the expiration date
$expiration_date = sprintf("%04d-%02d", $expiration_year, $expiration_month);
// Include the SDK
require_once('./config.php');
// Process the transaction using the AIM API
$transaction = new AuthorizeNetAIM;
$transaction->setSandbox(AUTHORIZENET_SANDBOX);
$transaction->setFields(
array(
'amount' => '1.00',
'card_num' => $credit_card,
'exp_date' => $expiration_date,
'first_name' => $cardholder_first_name,
'last_name' => $cardholder_last_name,
'address' => $billing_address,
'city' => $billing_city,
'state' => $billing_state,
'zip' => $billing_zip,
'email' => $email,
'card_code' => $cvv,
'ship_to_first_name' => $recipient_first_name,
'ship_to_last_name' => $recipient_last_name,
'ship_to_address' => $shipping_address,
'ship_to_city' => $shipping_city,
'ship_to_state' => $shipping_state,
'ship_to_zip' => $shipping_zip,
)
);
$response = $transaction->authorizeAndCapture();
if ($response->approved)
{
// Transaction approved. Collect pertinent transaction information for saving in the database.
$transaction_id = $response->transaction_id;
$authorization_code = $response->authorization_code;
$avs_response = $response->avs_response;
$cavv_response = $response->cavv_response;
// Put everything in a database for later review and order processing
// How you do this depends on how your application is designed
// and your business needs.
// Once we're finished let's redirect the user to a receipt page
header('Location: thank-you-page.php');
exit;
}
else if ($response->declined)
{
// Transaction declined. Set our error message.
$errors['declined'] = 'Your credit card was declined by your bank. Please try another form of payment.';
}
else
{
// And error has occurred. Set our error message.
$errors['error'] = 'We encountered an error while processing your payment. Your credit card was not charged. Please try again or contact customer service to place your order.';
// Collect transaction response information for possible troubleshooting
// Since our application won't be doing this we'll comment this out for now.
//
// $response_subcode = $response->response_subcode;
// $response_reason_code = $response->response_reason_code;
}
}
You can see we start of by checking the size of the $errors array and if it is zero we prepare to process the payment. We start by formatting the expiration date in a format the Authorize.Net will accept. In this case we choose to use the four digit year, a dash, and the two digit month. We follow that up by including the config file from the PHP SDK and initializing our the class that uses the AIM API. We set the code to use the development server so we don't incur any fees for our testing and then we provide the parameters needed to process the transaction.
We then process the payment and get the response object which we will use to determine the status of our payment. This is where we have to include some logic to handle the possible outcomes of the transaction. If the transaction is approved we collect the pertinent data returned to us by the merchant account provide: AVS results, CVV results, transaction ID, and authorization number. At this point you probably will want to store the order in the database and send the customer an email receipt for their order. In our example we send the user to a thank you page which probably should also include an order confirmation and printable receipt.
If the transaction is declined we really don't have to do much. We can add the notice to our $errors array and display it to the user when the page reloads.
If the transaction results in an error things can get dicey from a business perspective. In our example handling it with code is no different then a declined transaction except we are displaying a different message. Naturally an error at this point in the payment process to encounter an error is unexpected and definitely unwanted. You almost certainly should include some code to help you troubleshoot the error and attempt to prevent it from happening again. How you choose to do that is up to you.
Our Form Page So Far
Now that we have some more code written, let's see our how our page looks now:
<?php
$errors = array();
if ('POST' === $_SERVER['REQUEST_METHOD'])
{
$credit_card = sanitize($_POST['credit_card']);
$expiration_month = (int) sanitize($_POST['expiration_month']);
$expiration_year = (int) sanitize($_POST['expiration_year']);
$cvv = sanitize($_POST['cvv']);
$cardholder_first_name = sanitize($_POST['cardholder_first_name']);
$cardholder_last_name = sanitize($_POST['cardholder_last_name']);
$billing_address = sanitize($_POST['billing_address']);
$billing_address2 = sanitize($_POST['billing_address2']);
$billing_city = sanitize($_POST['billing_city']);
$billing_state = sanitize($_POST['billing_state']);
$billing_zip = sanitize($_POST['billing_zip']);
$telephone = sanitize($_POST['telephone']);
$email = sanitize($_POST['email']);
$recipient_first_name = sanitize($_POST['recipient_first_name']);
$recipient_last_name = sanitize($_POST['recipient_last_name']);
$shipping_address = sanitize($_POST['shipping_address']);
$shipping_address2 = sanitize($_POST['shipping_address2']);
$shipping_city = sanitize($_POST['shipping_city']);
$shipping_state = sanitize($_POST['shipping_state']);
$shipping_zip = sanitize($_POST['shipping_zip']);
if (!validateCreditcard_number($credit_card))
{
$errors['credit_card'] = "Please enter a valid credit card number";
}
if (!validateCreditCardExpirationDate($expiration_mon th, $expiration_year))
{
$errors['expiration_month'] = "Please enter a valid exopiration date for your credit card";
}
if (!validateCVV($credit_card, $cvv))
{
$errors['cvv'] = "Please enter the security code (CVV number) for your credit card";
}
if (empty($cardholder_first_name))
{
$errors['cardholder_first_name'] = "Please provide the card holder's first name";
}
if (empty($cardholder_last_name))
{
$errors['cardholder_last_name'] = "Please provide the card holder's last name";
}
if (empty($billing_address))
{
$errors['billing_address'] = 'Please provide your billing address.';
}
if (empty($billing_city))
{
$errors['billing_city'] = 'Please provide the city of your billing address.';
}
if (empty($billing_state))
{
$errors['billing_state'] = 'Please provide the state for your billing address.';
}
if (!preg_match("/^\d{5}$/", $billing_zip))
{
$errors['billing_zip'] = 'Make sure your billing zip code is 5 digits.';
}
if (empty($telephone) || strlen($telephone) > 20)
{
$errors['billing_city'] = 'Please provide a telephone number where we can reach you if necessary.';
}
if (!filter_var($email, FILTER_VALIDATE_EMAIL))
{
$errors['email'] = "Please provide a valid email address";
}
if (empty($recipient_first_name))
{
$errors['recipient_first_name'] = "Please provide the recipient's first name";
}
if (empty($recipient_last_name))
{
$errors['recipient_last_name'] = "Please provide the recipient's last name";
}
if (empty($shipping_address))
{
$errors['shipping_address'] = 'Please provide your shipping address.';
}
if (empty($shipping_city))
{
$errors['shipping_city'] = 'Please provide the city of your shipping address.';
}
if (empty($shipping_state))
{
$errors['shipping_state'] = 'Please provide the state for your shipping address.';
}
if (!preg_match("/^\d{5}$/", $shipping_zip))
{
$errors['shipping_zip'] = 'Make sure your shipping zip code is 5 digits.';
}
// If there are no errors let's process the payment
if (count($errors) === 0)
{
// Format the expiration date
$expiration_date = sprintf("%04d-%02d", $expiration_year, $expiration_month);
// Include the SDK
require_once('./config.php');
// Process the transaction using the AIM API
$transaction = new AuthorizeNetAIM;
$transaction->setSandbox(AUTHORIZENET_SANDBOX);
$transaction->setFields(
array(
'amount' => '1.00',
'card_num' => $credit_card,
'exp_date' => $expiration_date,
'first_name' => $cardholder_first_name,
'last_name' => $cardholder_last_name,
'address' => $billing_address,
'city' => $billing_city,
'state' => $billing_state,
'zip' => $billing_zip,
'email' => $email,
'card_code' => $cvv,
'ship_to_first_name' => $recipient_first_name,
'ship_to_last_name' => $recipient_last_name,
'ship_to_address' => $shipping_address,
'ship_to_city' => $shipping_city,
'ship_to_state' => $shipping_state,
'ship_to_zip' => $shipping_zip,
)
);
$response = $transaction->authorizeAndCapture();
if ($response->approved)
{
// Transaction approved. Collect pertinent transaction information for saving in the database.
$transaction_id = $response->transaction_id;
$authorization_code = $response->authorization_code;
$avs_response = $response->avs_response;
$cavv_response = $response->cavv_response;
// Put everything in a database for later review and order processing
// How you do this depends on how your application is designed
// and your business needs.
// Once we're finished let's redirect the user to a receipt page
header('Location: thank-you-page.php');
exit;
}
else if ($response->declined)
{
// Transaction declined. Set our error message.
$errors['declined'] = 'Your credit card was declined by your bank. Please try another form of payment.';
}
else
{
// And error has occurred. Set our error message.
$errors['error'] = 'We encountered an error while processing your payment. Your credit card was not charged. Please try again or contact customer service to place your order.';
// Collect transaction response information for possible troubleshooting
// Since our application won't be doing this we'll comment this out for now.
//
// $response_subcode = $response->response_subcode;
// $response_reason_code = $response->response_reason_code;
}
}
}
function sanitize($value)
{
return trim(strip_tags($value));
}
function validateCreditcard_number($credit_card_number)
{
$firstnumber = substr($credit_card_number, 0, 1);
switch ($firstnumber)
{
case 3:
if (!preg_match('/^3\d{3}[ \-]?\d{6}[ \-]?\d{5}$/', $credit_card_number))
{
return false;
}
break;
case 4:
if (!preg_match('/^4\d{3}[ \-]?\d{4}[ \-]?\d{4}[ \-]?\d{4}$/', $credit_card_number))
{
return false;
}
break;
case 5:
if (!preg_match('/^5\d{3}[ \-]?\d{4}[ \-]?\d{4}[ \-]?\d{4}$/', $credit_card_number))
{
return false;
}
break;
case 6:
if (!preg_match('/^6011[ \-]?\d{4}[ \-]?\d{4}[ \-]?\d{4}$/', $credit_card_number))
{
return false;
}
break;
default:
return false;
}
$credit_card_number = str_replace('-', '', $credit_card_number);
$map = array(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 2, 4, 6, 8, 1, 3, 5, 7, 9);
$sum = 0;
$last = strlen($credit_card_number) - 1;
for ($i = 0; $i <= $last; $i++)
{
$sum += $map[$credit_card_number[$last - $i] + ($i & 1) * 10];
}
if ($sum % 10 != 0)
{
return false;
}
return true;
}
function validateCreditCardExpirationDate($month, $year)
{
if (!preg_match('/^\d{1,2}$/', $month))
{
return false;
}
else if (!preg_match('/^\d{4}$/', $year))
{
return false;
}
else if ($year < date("Y"))
{
return false;
}
else if ($month < date("m") && $year == date("Y"))
{
return false;
}
return true;
}
function validateCVV($cardNumber, $cvv)
{
$firstnumber = (int) substr($cardNumber, 0, 1);
if ($firstnumber === 3)
{
if (!preg_match("/^\d{4}$/", $cvv))
{
return false;
}
}
else if (!preg_match("/^\d{3}$/", $cvv))
{
return false;
}
return true;
}
?>
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
<html>
<head>
<title>Payment Form</title>
<meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1">
<meta http-equiv="Content-Language" content="en-us">
<style type="text/css">
#errormessage
{
background-color: #FFE7E7;
border: 3px solid #CC0033;
color: #000000;
margin: 20px auto;
padding: 10px;
width: 890px;
-moz-border-radius: 6px;
-webkit-border-radius: 6px;
border-radius: 6px;
-moz-box-shadow: 5px 5px 5px #ccc;
-webkit-box-shadow: 5px 5px 5px #ccc;
box-shadow: 5px 5px 5px #ccc;
background: -webkit-gradient(linear, 0 0, 0 bottom, from(#FFEAEA), to(#FFB3B3));
background: -moz-linear-gradient(#FFEAEA, #FFB3B3);
background: linear-gradient(#FFEAEA, #FFB3B3);
}
.labelerror
{
color: #ff0000;
font-weight: bold;
}
</style>
</head>
<body>
<?php
if (count($errors))
{
?>
<div id="errormessage">
<h2>
There was an error with your submission. Please make the necessary corrections and try again.
</h2>
<ul>
<?php
foreach ($errors as $error)
{
?>
<li><?php echo $error; ?></li>
<?php
}
?>
</ul>
</div>
<?php
}
?>
<form action="/payment-form.php" method="post">
<p>
<label for="credit_card"<?php if (in_array('credit_card', $errors)) echo ' class="labelerror"'; ?>>Credit Card Number</label>
<input type="text" name="credit_card" id="credit_card" autocomplete="off" maxlength="19" value="">
</p>
<p>
<label for="expiration_month"<?php if (in_array('expiration_month', $errors)) echo ' class="labelerror"'; ?>>Expiration Date</label>
<select name="expiration_month" id="expiration_month">
<option value="0"> </option>
<option value="1">1</option>
<option value="2">2</option>
<option value="3">3</option>
<option value="4">4</option>
<option value="5">5</option>
<option value="6">6</option>
<option value="7">7</option>
<option value="8">8</option>
<option value="9">9</option>
<option value="10">10</option>
<option value="11">11</option>
<option value="12">12</option>
</select>
<select name="expiration_year" id="expiration_year">
<option value="0"> </option>
<option value="2010">2010</option>
<option value="2011">2011</option>
<option value="2012">2012</option>
<option value="2013">2013</option>
<option value="2014">2014</option>
<option value="2015">2015</option>
<option value="2016">2016</option>
<option value="2017">2017</option>
<option value="2018">2018</option>
<option value="2019">2019</option>
<option value="2020">2020</option>
<option value="2021">2021</option>
</select>
</p>
<p>
<label for="cvv"<?php if (in_array('cvv', $errors)) echo ' class="labelerror"'; ?>>Security Code</label>
<input type="text" name="cvv" id="cvv" autocomplete="off" value="" maxlength="4">
</p>
<p>
<label for="cardholder_first_name"<?php if (in_array('cardholder_first_name', $errors)) echo ' class="labelerror"'; ?>>Cardholder's First Name</label>
<input type="text" name="cardholder_first_name" id="cardholder_first_name" maxlength="30" value="">
</p>
<p>
<label for="cardholder_last_name"<?php if (in_array('cardholder_last_name', $errors)) echo ' class="labelerror"'; ?>>Cardholder's Last Name</label>
<input type="text" name="cardholder_last_name" id="cardholder_last_name" maxlength="30" value="">
</p>
<p>
<label for="billing_address"<?php if (in_array('billing_address', $errors)) echo ' class="labelerror"'; ?>>Billing Address</label>
<input type="text" name="billing_address" id="billing_address" maxlength="45" value="">
</p>
<p>
<label for="billing_address2"<?php if (in_array('billing_address2', $errors)) echo ' class="labelerror"'; ?>>Suite/Apt #</label>
<input type="text" name="billing_address2" id="billing_address2" maxlength="45" value="">
</p>
<p>
<label for="billing_city"<?php if (in_array('billing_city', $errors)) echo ' class="labelerror"'; ?>>City</label>
<input type="text" name="billing_city" id="billing_city" maxlength="25" value="">
</p>
<p>
<label for="billing_state"<?php if (in_array('billing_state', $errors)) echo ' class="labelerror"'; ?>>State</label>
<select id="billing_state" name="billing_state">
<option value="0"> </option>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<option value="CT">Connecticut</option>
<option value="DE">Delaware</option>
<option value="DC">District Of Columbia</option>
<option value="FL">Florida</option>
<option value="GA">Georgia</option>
<option value="HI">Hawaii</option>
<option value="ID">Idaho</option>
<option value="IL">Illinois</option>
<option value="IN">Indiana</option>
<option value="IA">Iowa</option>
<option value="KS">Kansas</option>
<option value="KY">Kentucky</option>
<option value="LA">Louisiana</option>
<option value="ME">Maine</option>
<option value="MD">Maryland</option>
<option value="MA">Massachusetts</option>
<option value="MI">Michigan</option>
<option value="MN">Minnesota</option>
<option value="MS">Mississippi</option>
<option value="MO">Missouri</option>
<option value="MT">Montana</option>
<option value="NE">Nebraska</option>
<option value="NV">Nevada</option>
<option value="NH">New Hampshire</option>
<option value="NJ">New Jersey</option>
<option value="NM">New Mexico</option>
<option value="NY">New York</option>
<option value="NC">North Carolina</option>
<option value="ND">North Dakota</option>
<option value="OH">Ohio</option>
<option value="OK">Oklahoma</option>
<option value="OR">Oregon</option>
<option value="PA">Pennsylvania</option>
<option value="RI">Rhode Island</option>
<option value="SC">South Carolina</option>
<option value="SD">South Dakota</option>
<option value="TN">Tennessee</option>
<option value="TX">Texas</option>
<option value="UT">Utah</option>
<option value="VT">Vermont</option>
<option value="VA">Virginia</option>
<option value="WA">Washington</option>
<option value="WV">West Virginia</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
</select>
</p>
<p>
<label for="billing_zip"<?php if (in_array('billing_zip', $errors)) echo ' class="labelerror"'; ?>>Zip Code</label>
<input type="text" name="billing_zip" id="billing_zip" maxlength="5" value="">
</p>
<p>
<label for="telephone"<?php if (in_array('telephone', $errors)) echo ' class="labelerror"'; ?>>Telephone Number</label>
<input type="text" name="telephone" id="telephone" maxlength="20" value="">
</p>
<p>
<label for="email"<?php if (in_array('email', $errors)) echo ' class="labelerror"'; ?>>Email Address</label>
<input type="text" name="email" id="email" maxlength="20" value="">
</p>
<p>
<label for="recipient_first_name"<?php if (in_array('recipient_first_name', $errors)) echo ' class="labelerror"'; ?>>Recipient's First Name</label>
<input type="text" name="recipient_first_name" id="recipient_first_name" maxlength="30" value="">
</p>
<p>
<label for="recipient_last_name"<?php if (in_array('recipient_last_name', $errors)) echo ' class="labelerror"'; ?>>Recipient's Last Name</label>
<input type="text" name="recipient_last_name" id="recipient_last_name" maxlength="30" value="">
</p>
<p>
<label for="shipping_address"<?php if (in_array('shipping_address', $errors)) echo ' class="labelerror"'; ?>>Shipping Address</label>
<input type="text" name="shipping_address" id="shipping_address" maxlength="45" value="">
</p>
<p>
<label for="shipping_address2"<?php if (in_array('shipping_address2', $errors)) echo ' class="labelerror"'; ?>>Suite/Apt #</label>
<input type="text" name="shipping_address2" id="shipping_address2" maxlength="45" value="">
</p>
<p>
<label for="shipping_city"<?php if (in_array('shipping_city', $errors)) echo ' class="labelerror"'; ?>>City</label>
<input type="text" name="shipping_city" id="shipping_city" maxlength="30" value="">
</p>
<p>
<label for="shipping_state"<?php if (in_array('shipping_state', $errors)) echo ' class="labelerror"'; ?>>State</label>
<select id="shipping_state" name="shipping_state">
<option value="0"> </option>
<option value="AL">Alabama</option>
<option value="AK">Alaska</option>
<option value="AZ">Arizona</option>
<option value="AR">Arkansas</option>
<option value="CA">California</option>
<option value="CO">Colorado</option>
<option value="CT">Connecticut</option>
<option value="DE">Delaware</option>
<option value="DC">District Of Columbia</option>
<option value="FL">Florida</option>
<option value="GA">Georgia</option>
<option value="HI">Hawaii</option>
<option value="ID">Idaho</option>
<option value="IL">Illinois</option>
<option value="IN">Indiana</option>
<option value="IA">Iowa</option>
<option value="KS">Kansas</option>
<option value="KY">Kentucky</option>
<option value="LA">Louisiana</option>
<option value="ME">Maine</option>
<option value="MD">Maryland</option>
<option value="MA">Massachusetts</option>
<option value="MI">Michigan</option>
<option value="MN">Minnesota</option>
<option value="MS">Mississippi</option>
<option value="MO">Missouri</option>
<option value="MT">Montana</option>
<option value="NE">Nebraska</option>
<option value="NV">Nevada</option>
<option value="NH">New Hampshire</option>
<option value="NJ">New Jersey</option>
<option value="NM">New Mexico</option>
<option value="NY">New York</option>
<option value="NC">North Carolina</option>
<option value="ND">North Dakota</option>
<option value="OH">Ohio</option>
<option value="OK">Oklahoma</option>
<option value="OR">Oregon</option>
<option value="PA">Pennsylvania</option>
<option value="RI">Rhode Island</option>
<option value="SC">South Carolina</option>
<option value="SD">South Dakota</option>
<option value="TN">Tennessee</option>
<option value="TX">Texas</option>
<option value="UT">Utah</option>
<option value="VT">Vermont</option>
<option value="VA">Virginia</option>
<option value="WA">Washington</option>
<option value="WV">West Virginia</option>
<option value="WI">Wisconsin</option>
<option value="WY">Wyoming</option>
</select>
</p>
<p>
<label for="shipping_zip"<?php if (in_array('shipping_zip', $errors)) echo ' class="labelerror"'; ?>>Zip Code</label>
<input type="text" name="shipping_zip" id="shipping_zip" maxlength="5" value="">
</p>
<p>
<input type="submit" value="Checkout">
</p>
</form>
</body>
</html>
What's Next?
At this point we have a fully functional checkout form that's usable and secure. But that doesn't mean we can't improve upon it. In the remaining posts in this series we are going to make incremental improvements to our form and payment process. We'll begin by preventing a page refresh from resubmitting the form again.
The Handling Online Payments Series
John Conde is a certified Authorize.Net developer
You must be a registered user to add a comment here. If you've already registered, please log in. If you haven't registered yet, please register and log in.
Watch the new developer training videos, ramp up your knowledge about payments as you build your Authorize.Net integrations.
Looking for work? Check out Authorize.Net-related jobs on Elance.