Working With authorize.net Server Integration Method (SIM) Payment Gateway, Part 2: Proper Form Design

The most important step in using the authorize.net Server Integration Method (SIM) payment gateway is properly designing your ordering system / shopping cart, well before you ever request payment.

Let me repeat that: If you want a secure, sensible and error-free checkout experience, you need to design a storefront that makes those things possible. Just as it is with building a house, if the foundation is crap, it won’t stand up to a storm.

I promised in a recent post to show how to properly send transaction requests to SIM. So, here’s the first step: An overview of best practices, and a sample order form that follows them.

Let me offer this, right up front: If your Web sales are casual — say, you want to let people buy annual banquet tickets online, or you sell a couple coffee mugs / T-shirts each week — you should seriously consider using a third-party turnkey solution.

The legal, practical and technical requirements of running your own ecommerce solution generally aren’t worth the hassle if you’re not doing a significant volume of sales.

I like EventBrite for handling ticket sales and CafePress for selling merchandise. There are other storefront options out there, but those are ones I have used and found reliable.

That said, there are circumstances where low-volume sellers still need custom ecommerce solutions. So, with that in mind, let’s cover the basics of making a secure, simple ordering system.

Best Practices

Security. It’s important in all Web applications, but especially in ecommerce. And the best way to keep your applications secure is to keep them simple. As a rule, the less they do, and the more directly and simply they do it, the more secure they are. There’s less to attack.

  • Always process user input server-side. It’s fine to use JavaScript to aid your customers in providing the kinds of information you want, in the format you want them to use; but you should never rely on JavaScript alone to ensure proper data has been passed to your application. (See Part 1 of this series for why.)
  • Don’t give end users access to sensitive data or the logic that runs your application. For example, your customers need to know how much things cost, what you charge for shipping, etc. But don’t use prices as form field values that you’ll use when you compute the cart total; use a quantity value where possible, instead. An attacker can change a price to rip you off. Changing quantities does him no good.
  • Assume every transaction will be made by someone who wants to rip you off, crack your site or otherwise screw you over. That’s a good rule of thumb any time you make a Web application, but especially in ecommerce.
  • Double-check transactions before submitting them to payment. If you submit more than the rate disputed transaction or wind up issuing a lot of credits, voids and refunds, your merchant bank is going to pull the plug on you. Trust me, Visa and MasterCard hate disputes, and they will come down hard on you.
  • Limit options, styles, etc. to the bare minimum necessary.
    • When you must have an option, use select lists, checkboxes and / or radio buttons to limit input options.
    • Assume the value for an option will be out of range, the wrong data type, etc.

End of sermon. Let’s get on to the work.

A One-Page Order Form

For the first example, let’s make a simple membership / dues payment order form, that includes a few options.

<form id="orderform" name="orderform" method="post" action="<?php echo htmlspecialchars($_SERVER['PHP_SELF']); ?>">
	<p>Select a membership level:&nbsp;
		<select id="mlevel" name="mlevel">
			<option value="basic">Basic, $25 / year</option>
			<option value="family">Family, $35 / year</option>
			<option value="senior">Senior (65 or older), $15 / year</option>
			<option value="student">Student, $10 / year</option>
		</select>
	</p>

	<p>Your membership comes with a free T-shirt. Please select a size:&nbsp;
		<select id="tsize" name="tsize">
			<option value="n">No T-shirt</option>
			<option value="s">Small</option>
			<option value="m" selected="selected">Medium</option>
			<option value="l">Large</option>
			<option value="x">Extra Large</option>
		</select>
	</p>

	<p>Please check the email lists you'd like to join (you can unsubscribe at any time).
		<br />
		<label><input type="checkbox" name="subs[]" id="subGeneral" value="general" checked="checked" />&nbsp;General mailing list&nbsp;|</label>
		<label><input type="checkbox" name="subs[]" id="subTips" value="tips" />&nbsp;Tips and tricks&nbsp;|</label>
		<label><input type="checkbox" name="subs[]" id="subValues" value="values" />&nbsp;Special sales and values</label>
	</p>

	<p>Would you like to make an additional donation?
		<br />
		<label><input type="radio" name="donation" id="donate0" value="0" checked="checked" />&nbsp;No&nbsp;|</label>
		<label><input type="radio" name="donation" id="donate10" value="10" />&nbsp;$10&nbsp;|</label>
		<label><input type="radio" name="donation" id="donate25" value="25" />&nbsp;$25&nbsp;|</label>
		<label><input type="radio" name="donation" id="donate50" value="50" />&nbsp;$50&nbsp;|</label>
		<label><input type="radio" name="donation" id="donateX" value="1" />&nbsp;Other amount:&nbsp;<input name="donate-other" id="donate-other" size="4" maxlength="4" value="" /></label>
	</p>
	<p>Click the "Order Summary" button below to confirm your order and proceed to the payment screen.</p>
	<input type="submit" id="order_submit" name="order_submit" value="Order Summary" />
</form>

Some notes about this form:

  • I intentionally limit potential inputs via select lists, radio buttons, checkboxes, etc. I want to limit the possible values I accept, in order to prevent the ability of end users to poison — intentionally or otherwise — my variables.
  • I’m not using prices as form variable values. Yes, it looks like that under the additional donation select list, but when we process this code in the next section, you’ll see that I am using those values as text, not values I’ll actually compute.

The Processing Code

OK, let’s put together some PHP processing code that ensures we’ll pass a reasonable payment request to SIM.

I’m just going to output an order total and a message indicating what was purchased. In a later blog post, I will show you how to prepare your validated data for submission to SIM.

$total = 0.00; // running total variable, set to float
$msg = "<p>Here is your order summary:\n<br />\n"; 

First, we want to ensure that our form variables are present. We’ll check them for valid values later, but we want to ensure, at the macroscopic level, that everything we need to process the order is at least on hand. That’s because someone may have forged our form, or may be trying to bypass what we want to do with a form of their own creation.

An advanced solution to this problem would be to check the referring page to ensure the form we get is the same one on our site; or to generate some sort of origin token as a form field, which we later check.

Since we’re going to go over the inputs with a fine-toothed comb, it’s fine just to make sure the form elements are all present. So what if someone forges our form and puts it on their site? So long as it contains what we need in the formats we expect, go ahead and sell things for us, remote cracker site.

if(!isset($_POST['mlevel']) || !isset($_POST['tsize']) || !isset($_POST['donation'])) {
	$msg = "<p>It appears one or more of the order form options is missing. Please check your order and try again.</p>\n";
}
else { 
	//code that begins examining individual form elements
}

Our form is going to require the end user to buy some sort of membership, at minimum; that is, no matter what, a membership must be purchased. So, we’ll process the membership select list with a default condition that ensures at least a basic membership is chosen.

switch($_POST['mlevel']) {
	case 'family':
		$total += 35;
		$msg .= "Family membership, $35<br />\n";
		break;
	case 'senior':
		$total += 15;
		$msg .= "Senior membership, $15<br />\n";
		break;
	case 'student':
		$total += 10;
		$msg .= "Student membership, $10<br />\n";
		break;
	default:
		$total += 25;
		$msg .= "Basic membership, $25<br />\n";
}

Note that my default condition is a basic membership. This protects me against attempts to poison the form variables; if I get something unexpected for membership level, I’m going to sell you a basic membership.

I take the same basic approach with the T-shirt: If you don’t send me a valid option, I’m going to send you a medium T-shirt.

switch($_POST['tsize']) {
	case 'n':
		$msg .= "No T-shirt<br />\n";
		break;
	case 's':
		$msg .= "Small T-shirt<br />\n";
		break;
	case 'l':
		$msg .= "Large T-shirt<br />\n";
		break;
	case 'x':
		$msg .= "Extra Large T-shirt<br />\n";
		break;
	default:
		$msg .= "Medium T-shirt<br />\n";
}

In the case of checkboxes and PHP, if nothing is checked, the $_POST variable that would have contained checked values is not set. In other words, if no subscriptions are checked in my order form, $_POST[‘subs’] will not be set on postback.

It would normally suffice, therefore, to check if $_POST[‘subs’] is set. But I am concerned about forgeries, so I anticipate that someone may be trying to send me bad values for subscription options. That attempt may include converting the data type of $_POST[‘subs’] to something other than the array it ought to be.

So, I check if $_POST[‘subs’] is an array with a count greater than 0. Then, I go through the values in that array.

$msg .= "Subscriptions: ";

if(is_array($_POST['subs']) && count($_POST['subs']) > 0) {
	foreach($_POST['subs'] as $sub) {
		if($sub == 'general') {
			$msg .= "General mailing list; ";
		}
		if($sub == 'tips') {
			$msg .= "Tips and tricks; ";
		}
		if($sub == 'values') {
			$msg .= "Special sales and values; ";
		}
	}
	$msg = substr($msg, 0, strlen($msg) - 2);
}
else {
	$msg .= "None";
}

$msg .= "<br />\n";

Check out Line 15. It’s an old trick for handling string concatenations of listed items when you’re unsure how long the list will be / what items will be appended.

  • Ensure the item separator for each thing in the list is the same number of characters;
  • append your items to your string;
  • trim the right end of your completed string by however many characters are in your item separator; and, optionally
  • append whatever you want the end of the string to be.

Note that in my sample, T-shirts and subscriptions don’t have a direct impact on cart price. In this case, we’d need to store this order information locally; we’ll go over that in a later post.

Finally, I have a mixed-input group for determining an additional donation, including a free-text field to type in an arbitrary value.

To accommodate that free text field, I create a function that checks the value entered against a regular expression. If the pattern matches, the function returns true; otherwise, it returns false.

function checkDonationAmt($input) {
	if(preg_match('/^[1-9]{1}[0-9]?(\.[0-9]{2})?$/', $input) > 0) {
		return true;
	}
	else {
		return false;
	}
}
Let’s look more closely at the regular expression:

^[1-9]{1}[0-9]?(\.[0-9]{2})?$

What this says is, from the start of the input string, I need at least one integer between 1 and 9. It can be followed, optionally, by another integer, from 0 to 9. That constitutes the dollar amount. Optionally, there can be a cents amount, which begins with a decimal point and is followed by two integers, each from 0 to 9. Nothing else may appear after that.

Therefore, valid inputs would be 1, 10, 87, 3.34 or 19.01. Invalid inputs would be foo, 100, 0.23, $1.25 or 34.5.

That function is needed for another switch I use, to check the validity of the additional donation radio button selections:

$msg .= "Additional donation: ";

switch($_POST['donation']) {
	case '10':
		$total += 10;
		$msg .= "$10";
		break;
	case '25':
		$total += 25;
		$msg .= "$25";
		break;
	case '50':
		$total += 50;
		$msg .= "$50";
		break;
	case '1':
		if(checkDonationAmt($_POST['donate-other'])) {
			$total += number_format($_POST['donate-other'], 2);
			$msg .= "$" . number_format($_POST['donate-other'], 2);
		}
		else {
			$msg .= "None (invalid other donation amount provided)";
		}
		break;
	default:
		$msg .= "None";
}

Note that in this switch, as well as the membership switch, I don’t use the form field values to calculate the total; I explicitly use integers. That’s to avoid the kind of JavaScript-based form field variable poisoning I previously wrote about.

All that’s left is to close up my strings, cast my total to currency precision, and output my status messages.

	$msg .= "</p>\n";
	
	$total = number_format($total, 2);
}
echo "<p>Your order total is $" . $total . "</p>\n";
echo $msg;

You can see a working demo of this here: http://demo.dougv.com/php_sim_part2/.

You can download the code here: Working With authorize.net Server Integration Method (SIM) Payment Gateway, Part 2: Proper Form Design demo code

All links in this post on delicious: http://delicious.com/dougvdotcom/working-with-authorize-net-server-integration-method-sim-payment-gateway-part-2-proper-form-design

Leave a Reply

  • Check out the Commenting Guidelines before commenting, please!
  • Want to share code? Please put it into a GitHub Gist, CodePen or pastebin and link to that in your comment.
  • Just have a line or two of markup? Wrap them in an appropriate SyntaxHighlighter Evolved shortcode for your programming language, please!