Hostingheaderbarlogoj
Join InMotion Hosting for $3.49/mo & get a year on Tuts+ FREE (worth $180). Start today.
Advertisement

So You Want to Accept Credit Cards Online?

by

Until recently, accepting credit cards on a website was expensive and complicated. But that was before Stripe: a radically different and insanely awesome credit card processing company. Today, I'll show you how to start accepting cards in 30 minutes or less - without spending a dime.

Republished Tutorial

Every few weeks, we revisit some of our reader's favorite posts from throughout the history of the site. This tutorial was first published in June, 2012.


The Way Things Used To Be

Without Stripe, accepting credit cards on a website is a massive undertaking. First, you need to open a "merchant account", which is like a regular bank account, but with more fees. Then, you need a "payment gateway" - because credit card processing apparently takes place in a separate dimension where bankers rule with an iron fist and Lindsey Lohan has a successful acting career. And then come the leeches: $25 monthly fee. $99 setup fee. $50 annual fee. $0.35 failed transaction fee (YOU pay when your customer's card fails to go through!). $0.25 + 2.0% successful transaction fee. $2.00 batch fee. $5.00 daddy-needs-a-new-porsche fee. It's ridiculous. The most popular card processor is Authorize.net, and the folks at that company (and its many resellers) spend every day thinking of new, ridiculous ways to take your money.


Enter Stripe

Setup takes about five minutes.

Unfortunately, it is illegal to kidnap the CEO of Authorize.net, slather him in barbecue sauce and drop him into a pit of honey badgers. But, you can do the next best thing: don't use his service. Switch to Stripe. You won't need a merchant account or payment gateway. Stripe will deposit money into any bank account you like. There are zero fees. Setup takes about five minutes. (Yes, seriously.) And you pay exactly one thing: 2.9% + $0.30 on each successful card transaction. (So, if you're selling something for $30, you keep $28.83, and Stripe gets $1.17.) The website is simple and intuitive and the staff are super helpful. The only drawback is that Stripe is currently unavailable outside of the United States. (Note: Stripe DOES accept credit cards from overseas; it's just that you can't sign up for a Stripe account outside of the U.S.) They're working on expanding to other countries.

The rest of this tutorial will detail how to implement Stripe on your website with PHP and Javascript (jQuery). The service also has APIs for Ruby, Python, Java and other platforms. Although it might look like there's a lot of work ahead, there really isn't; you'll be up and running in no time. Let's get started:


Step 0: Install an SSL Certificate

We're dealing with credit card information, so of course we have to secure the user's connection to our server. We do this using an SSL certificate and it's not optional. Not only do users expect to see the "https://" protocol on an order page, Stripe requires it. But don't worry: implementing SSL is very simple. Almost all hosting providers offer automatic SSL certificate installation. You simply buy the certificate through your provider and they automatically install and configure it for you. You don't need to do anything else to your site. If your order form is at http://mydomain.com/order.php, you simply send the customer to https://mydomain.com/order.php instead and the connection will be secured with your new SSL certificate. That's it!

Note: there is one exception. If your order page loads resources such as stylesheets, scripts or images using an absolute (as opposed to relative) URL, you'll need to make sure those URLs use the "https://" protocol. For example, if you include an image on your secure order page like this, you'll get a warning in the browser that the page contains both secure and insecure elements:

	
<img src="http://someremotedomain.com/someImage.jpg">

To fix this, load the image from a secure URL, like this:

	
<img src="https://someremotedomain.com/someImage.jpg">

You don't need to worry about this issue for relative urls (such as "../images/someImage.jpg") because your server will automatically load these items securely.


Step 1: Create an Account

Visit Stripe.com and create a new account. Once you're past the initial username/password prompt, click the "Your Account" menu in the top right and open the "Account Settings" pane, which is pictured below. First, make sure you set a good "Statement Descriptor". This is what customers will see on their credit card statements. A good descriptor helps the customer remember what they bought so that they don't mistake your transaction for fraud and cancel the charge. (When this happens, it's called a "chargeback" and you'll pay a $15 fee on top of losing the sale, so make sure your descriptor is set!) Next, specify the bank account to which you'd like your money deposited. You are welcome to use mine. And finally, take a look at the "API Keys" tab. We'll be using these shortly, so keep them handy.


Step 2: Create Your Payment Form

The next thing we need is a form that our customers fill out to place a credit card order with us. Today, we'll use this vastly over-simplified PHP page, called "buy.php":

<!DOCTYPE html>
<html>
	<head>
		<script src="scripts/jquery.js"></script>
	</head>
	
	<body>
		<h2>Payment Form</h2>
	
		<form id="buy-form" method="post" action="javascript:">
			
			<p class="form-label">First Name:</p>
			<input class="text" id="first-name" spellcheck="false"></input>
			
			<p class="form-label">Last Name:</p>
			<input class="text" id="last-name" spellcheck="false"></input>
			
			<p class="form-label">Email Address:</p>
			<input class="text" id="email" spellcheck="false"></input>
			
			<p class="form-label">Credit Card Number:</p>
			<input class="text" id="card-number" autocomplete="off"></input>
			
			<p class="form-label">Expiration Date:</p>
			<select id="expiration-month">
			<option value="1">January</option>
		    <option value="2">February</option>
		    <option value="3">March</option>
		    <option value="4">April</option>
		    <option value="5">May</option>
		    <option value="6">June</option>
		    <option value="7">July</option>
		    <option value="8">August</option>
		    <option value="9">September</option>
		    <option value="10">October</option>
		    <option value="11">November</option>
		    <option value="12">December</option>
			</select>
			
			<select id="expiration-year">
				<?php 
					$yearRange = 20;
					$thisYear = date('Y');
					$startYear = ($thisYear + $yearRange);
				
					foreach (range($thisYear, $startYear) as $year) 
					{
						if ( $year == $thisYear) {
							print '<option value="'.$year.'" selected="selected">' . $year . '</option>';
						} else {
							print '<option value="'.$year.'">' . $year . '</option>';
						}
					}
				?>
			</select>
			
			<p class="form-label">CVC:</p>
			<input class="text" id="card-security-code" autocomplete="off"></input>
			
			<input id="buy-submit-button" type="submit" value="Place This Order »"></input>
		</form>
	</body>
</html>

There are three things to note about the code snippet above.

  1. First, we've set the form's action to "javascript:" rather than providing a path to a server-side script. (You'll see why in just a minute.)
  2. Secondly, there's a short snippet of PHP that automatically populates our expiration-year field with the next 20 years so that we don't have to update that manually in the future.
  3. Thirdly, none of the form fields have a "name" parameter set. This is crucial because it will prevent the value of the field (such as the credit card number) from being sent to our server when the form is submitted. We'll talk about why this is important in just a minute.

How Much Info Should I Collect?

The only things you absolutely must have to charge a credit card are the card number and the expiration date. But you should always collect at least some additional information. Here's why: if a customer disputes the charge on their card, you'll be required to prove that they did, in fact, place an order with you.

The more information you collect, the easier it will be to prove that the customer (as opposed to an identity thief) placed the order on your site.


What's Next: The Big Picture

Okay, we've got SSL installed and a payment form ready to go. Let's assume we're going to charge the customer $20.00 for this order. (In reality, you'd calculate the total based on what the customer ordered, etc. That's up to you.) When he fills out the form and presses the submit button, three things happen in this order:

  1. Using Javascript (jQuery), we collect each form field's value. We pass this information directly to Stripe's server, using Stripe.js.
  2. Stripe's server will ensure that the credit card data is well-formed, prepare a transaction and send us back a "single-use token".
  3. We pass the token to a server-side script on our own server, which contacts Stripe again and triggers the actual charge to the credit card. That's it!

Why Do It This Way?

Security. The user's credit card information never touches our own server. We pass it directly to Stripe on the client-side using Javascript. Stripe's server takes that information and prepares a transaction. The "token" that it sends back to us does NOT contain the credit card details, but DOES contain an ID that lets us trigger the transaction that Stripe has prepared on their end. Thus, we can safely pass the token to our own server without risking the security of the user's credit card details.

Note: while you can use Stripe without the token process, I strongly discourage it. If you pass the raw credit card details to your own server, you have to be insanely careful to protect them and there are many ways to screw up. For example, server error logs could easily record sensitive information, so you have to scrub them securely and regularly. If you're on a shared hosting plan, you probably don't have the control required to do that. Plus, if your server is ever hacked, you might be sued into oblivion by ticked-off customers. And if you do something really stupid like store unencrypted card information in a database, I will personally drive to your house and beat you with a cactus. Play it safe; use the token process.


Step 3: Collect The Form Values

Create a new Javascript file, called "buy-controller.js". Let's start coding that file with some basic validation checks:

function showErrorDialogWithMessage(message)
{
	// For the tutorial, we'll just do an alert. You should customize this function to 
	// present "pretty" error messages on your page.
	alert(message);

	// Re-enable the order button so the user can try again
	$('#buy-submit-button').removeAttr("disabled");
}

$(document).ready(function() 
{
	$('#buy-form').submit(function(event)
	{
		// immediately disable the submit button to prevent double submits
		$('#buy-submit-button').attr("disabled", "disabled");
		
		var fName = $('#first-name').val();
		var lName = $('#last-name').val();
		var email = $('#email').val();
		var cardNumber = $('#card-number').val();
		var cardCVC = $('#card-security-code').val();
		
		// First and last name fields: make sure they're not blank
		if (fName === "") {
			showErrorDialogWithMessage("Please enter your first name.");
			return;
		}
		if (lName === "") {
			showErrorDialogWithMessage("Please enter your last name.");
			return;
		}
		
		// Validate the email address:
		var emailFilter = /^([a-zA-Z0-9_\.\-])+\@(([a-zA-Z0-9\-])+\.)+([a-zA-Z0-9]{2,4})+$/;
		if (email === "") {
			showErrorDialogWithMessage("Please enter your email address.");
			return;
		} else if (!emailFilter.test(email)) {
			showErrorDialogWithMessage("Your email address is not valid.");
			return;
		}
		 
		// Stripe will validate the card number and CVC for us, so just make sure they're not blank
		if (cardNumber === "") {
			showErrorDialogWithMessage("Please enter your card number.");
			return;
		}
		if (cardCVC === "") {
			showErrorDialogWithMessage("Please enter your card security code.");
			return;
		}
		
		// Boom! We passed the basic validation, so we're ready to send the info to 
		// Stripe to create a token! (We'll add this code soon.)
		
	});
});

Next, we need to add this new JavaScript file to the <head> element of our "buy.php" page. We're also going to add "Stripe.js", which is a file hosted on Stripe's server that allows us to contact Stripe from the client-side to pass credit card details and receive our token. (Note that we load Stripe.js using the "https://" protocol!) Modify the <head> element of "buy.php" to look like this:

<head>
	<script src="scripts/jquery.js"></script>
	<script src="https://js.stripe.com/v1/"></script>
	<script src="scripts/buy-controller.js"></script>
</head>

API Keys

Before we can submit information to Stripe, we have to somehow tell Stripe who we are. To do that, we use a pair of "keys", which are unique strings that identify our account. To locate these keys, go to your Stripe account settings pane and pull up the API Keys tab, pictured here:

As you can see, there are a total of four keys in two sets: "Test" and "Live". You use the test set during development so that you can verify your code without actually charging any cards. When you're ready to deploy a website, simply replace the test keys with the live ones. There are two keys in each set: "publishable" and "secret". (We'll use the "secret" key in our server-side script once we've received a token from Stripe.) For now, take the publishable test key and add it to the HEAD element of "buy.php" like this:

<head>
	<script src="scripts/jquery.js"></script>
	<script src="https://js.stripe.com/v1/"></script>
	
	<script>
		Stripe.setPublishableKey('pk_0xT4IHiAt1NxoBDJlE2jfLnG5xWQv');	// Test key!
	</script>
	
	<script src="scripts/buy-controller.js"></script>
</head>

Warning: You MUST include Stripe.js BEFORE you set the publishable key. Additionally, be very careful that you don't take a website live without switching to the "live" keys! And finally, be absolutely sure to keep your secret keys safe and secret!


Step 4: Request a Token

Back at the bottom of "buy-controller.js", we're ready to add the code that requests a token from Stripe. It's just a few lines:

	// Boom! We passed the basic validation, so request a token from Stripe:
	Stripe.createToken({
		number: cardNumber,
		cvc: cardCVC,
		exp_month: $('#expiration-month').val(),
		exp_year: $('#expiration-year').val()
	}, stripeResponseHandler);
	
	// Prevent the default submit action on the form
	return false;

The "createToken" function (which is defined in Stripe.js) accepts two parameters. The first is an object with the credit card details. The second is the name of the callback function that will be invoked when Stripe's server finishes preparing the transaction and returns the token. In this case, our callback function is called "stripeResponseHandler". Let's add that function to the top of "buy-controller.js":

function stripeResponseHandler(status, response)
{
	if (response.error) 
	{
		// Stripe.js failed to generate a token. The error message will explain why.
		// Usually, it's because the customer mistyped their card info.
		// You should customize this to present the message in a pretty manner:
		alert(response.error.message);
	} 
	else 
	{	
		// Stripe.js generated a token successfully. We're ready to charge the card!
		var token = response.id;
		var firstName = $("#first-name").val();
		var lastName = $("#last-name").val();
		var email = $("#email").val();

		// We need to know what amount to charge. Assume $20.00 for the tutorial. 
		// You would obviously calculate this on your own:
		var price = 20;

		// Make the call to the server-script to process the order.
		// Pass the token and non-sensitive form information.
		var request = $.ajax ({
			type: "POST",
			url: "pay.php",
			dataType: "json",
			data: {
				"stripeToken" : token,
				"firstName" : firstName,
				"lastName" : lastName,
				"email" : email,
				"price" : price
				}
		});

		request.done(function(msg)
		{
			if (msg.result === 0)
			{
				// Customize this section to present a success message and display whatever
				// should be displayed to the user.
				alert("The credit card was charged successfully!");
			}
			else
			{
				// The card was NOT charged successfully, but we interfaced with Stripe
				// just fine. There's likely an issue with the user's credit card.
				// Customize this section to present an error explanation
				alert("The user's credit card failed.");
			}
		});

		request.fail(function(jqXHR, textStatus)
		{
			// We failed to make the AJAX call to pay.php. Something's wrong on our end.
			// This should not normally happen, but we need to handle it if it does.
			alert("Error: failed to call pay.php to process the transaction.");
		});
	}
}

This function first checks to see if there was an error creating the token. If Stripe.js fails to return a valid token, it's usually because the customer entered some of their credit card information incorrectly. They may have mistyped a number or selected the wrong expiration date. Fortunately, the error message that comes along with the response will tell you exactly why the token-creation failed. Stripe guarantees that this error message is suitable for display, but it's not verbose. Expect to see strings like "invalid expiration date" or "incorrect CVC" rather than full sentences.

If, on the other hand, everything validated and Stripe created a token, we're ready to hand that token to our server-side script and actually place the charge. In the code above, we're using jQuery's Ajax function to do that. We pass the token as well as some information we might want to record in a database: the customer's name and email. Finally, we need to know how much money to charge the card. We're assuming $20.00 today, but you'd pass a calculated value from your shopping cart, etc. We throw all of that information into a JSON object and make the Ajax call to our server-side script, "pay.php" (which we'll create below). Then, we simply look at the response and present the user with a success or error message. You would obviously customize this code to fit your site's design.


Step 5: Create a Server-Side Script

The only thing left to do is create the server-side PHP script that actually triggers the charge on our customer's card. First, we'll need Stripe's PHP library. To download it, go to Stripe's website, click the "Documentation" link in the upper right, and then choose the "API Libraries" section. (Or you can go straight there by clicking here.) Scroll down the page until you see the PHP section, which looks like this:

Download the latest version and unzip it. You'll see two items: "Stripe.php" and a folder named "Stripe" that contains a bunch of other PHP files. Drop both these items into your website's folder.

Now, create a new file called "pay.php". We'll start coding this file with some basic stuff:

<?php
// Helper Function: used to post an error message back to our caller
function returnErrorWithMessage($message) 
{
	$a = array('result' => 1, 'errorMessage' => $message);
	echo json_encode($a);
}

// Credit Card Billing 
require_once('Stripe.php');	 // change this path to wherever you put the Stripe PHP library!

$trialAPIKey = "oRU5rYklVzp94Ab0RbBTP0soVdlaEtvm";	// These are the SECRET keys!
$liveAPIKey = "4BYrmtvwLb8iiiq9KIdbnRh5KCeSfPsX";

Stripe::setApiKey($trialAPIKey);  // Switch to change between live and test environments

// Get all the values from the form
$token = $_POST['stripeToken'];
$email = $_POST['email'];
$firstName = $_POST['firstName'];
$lastName = $_POST['lastName'];
$price = $_POST['price'];

$priceInCents = $price * 100;	// Stripe requires the amount to be expressed in cents

At the top, we have a simple function that we'll call whenever our script hits an error. It returns a JSON object with two items: "result" and "errorMessage". This JSON object is sent back to "buy-controller.js" (where we used jQuery's AJAX function to call this server-side script). There, we can inspect the value of "result" to see what happened. If it's 0, the payment script completed successfully. If it's 1, the script hit an error and we can use the "errorMessage" item to report what happened to the user.

Next, we bring in Stripe's PHP library that we downloaded earlier. There's nothing too complicated here; just make sure you update the path in the require statement to the relative location of the Stripe PHP library. After that, we have both of our SECRET API keys. We call the "setApiKey" function (which is part of Stripe's PHP library) and pass it our trial key. Combined with the "publishable" key that we set earlier, Stripe now has all the information it needs to verify our identity and associate this transaction with our account. Of course, when we take the website live, we would switch this statement to use $liveAPIKey!

Warning: Don't forget to switch to the LIVE API keys when you publish your site! You must switch both the "publishable" key in the HEAD element of "buy.php" and the "secret" key, which appears in "pay.php", above.

And finally, we grab all the data that we passed from the AJAX call in "buy-controller.js". Note that Stripe requires us to specify the charge amount in cents. Here, we passed the value in dollars, so we multiply by 100 to convert it to cents.

Actually Charge The Card

Here's the rest of the code for pay.php:

try 
{
	// We must have all of this information to proceed. If it's missing, balk.
	if (!isset($token)) throw new Exception("Website Error: The Stripe token was not generated correctly or passed to the payment handler script. Your credit card was NOT charged. Please report this problem to the webmaster.");
	if (!isset($email)) throw new Exception("Website Error: The email address was NULL in the payment handler script. Your credit card was NOT charged. Please report this problem to the webmaster.");
	if (!isset($firstName)) throw new Exception("Website Error: FirstName was NULL in the payment handler script. Your credit card was NOT charged. Please report this problem to the webmaster.");
	if (!isset($lastName)) throw new Exception("Website Error: LastName was NULL in the payment handler script. Your credit card was NOT charged. Please report this problem to the webmaster.");
	if (!isset($priceInCents)) throw new Exception("Website Error: Price was NULL in the payment handler script. Your credit card was NOT charged. Please report this problem to the webmaster.");

	try 
	{
		// create the charge on Stripe's servers. THIS WILL CHARGE THE CARD!
		$charge = Stripe_Charge::create(array(
			"amount" => $priceInCents,
			"currency" => "usd",
			"card" => $token,
			"description" => $email)
		);

		// If no exception was thrown, the charge was successful! 
		// Here, you might record the user's info in a database, email a receipt, etc.

		// Return a result code of '0' and whatever other information you'd like.
		// This is accessible to the jQuery Ajax call return-handler in "buy-controller.js"
		$array = array('result' => 0, 'email' => $email, 'price' => $price, 'message' => 'Thank you; your transaction was successful!');
		echo json_encode($array);
	}
	catch (Stripe_Error $e)
	{
		// The charge failed for some reason. Stripe's message will explain why.
		$message = $e->getMessage();
		returnErrorWithMessage($message);
	}
}
catch (Exception $e) 
{
	// One or more variables was NULL
	$message = $e->getMessage();
	returnErrorWithMessage($message);
}
?>

Surprisingly simple, no? First, we verify that none of our variables are null. Although we don't need all of them to charge the card, we might want to record this information in a database or use it to email the customer a receipt, so we don't want to proceed if it's not available.

Then, we use the "Stripe_Charge::create()" method, which is part of the Stripe PHP library. This is the line that actually charges the user's card (or attempts to, anyway). The first two items in the array are self-explanatory. The third, "card", is where we pass the token that we requested from Stripe earlier. The fourth item, "description" is vitally important. Whatever we pass here is what WE will see when we log into Stripe and view our transactions. You should choose something short that identifies the customer who placed this order. An email address is your best bet, as many customers might have the same name.

Why Might The Charge Fail At This Point?

If we were able to successfully get a token from Stripe, why would the charge fail at this point? The answer is that the validation Stripe performed earlier checked only that the credit card data was well-formed; it did not run a transaction through the credit card networks. It may be the case that the customer's card is over its limit. Or, if it's a debit card, there may not be enough money in the customer's account to cover this purchase. It could also be that the credit card company simply flags the transaction as unusual and requires the customer's approval to let it through (this has happened to me with American Express cardholders). In situations like these, the card will validate correctly when we request a token, but fail when we attempt to actually charge it. Fortunately, Stripe makes it really easy to handle these failures. We simply use try/catch blocks, as you see above.

Charge The Card Last!

If that customer is me, you're in for a cactus beating.

If your website needs to do things, such as generating a serial number for a software license, you should do that BEFORE you charge the customer's card. If you charge the card first and then your site fails to generate a serial for any reason, your customer is going to be ticked off. (If that customer is me, you're in for a cactus beating.) They might even call their credit card company to cancel the charge, which results in a $15 fee to you and the loss of a sale. So play it safe: be sure you have everything ready to go BEFORE you charge the customer!

That's it! That's all the code you need to charge a credit card on your website. The rest of the article covers some additional details about using Stripe that you might find handy:


Testing & Debugging

When we're using the "test" API keys, we can use special credit card numbers that force Stripe to return a certain type of response so that we can thoroughly test our code. Here's the special numbers:

  • 4242-4242-4242-4242: Simulate a successful card transaction
  • 4000-0000-0000-0002: Force a "card declined" response
  • 4242-4242-4242-4241: Force an "invalid card number" response

In test mode, any 3 or 4-digit CVC number is considered valid. Any expiration date that is in the future is valid. You can pass a two-digit CVC number to test that error case. Likewise, you can pass any date in the past to test the invalid expiration date response. And finally, if you'd like to test the "invalid amount" response, simply pass any non-integer (such as 1.35) as the amount to charge.

For exhaustive information on testing Stripe, you can visit their documentation page.


Subscriptions, Storing Card Info & More

Stripe allows you to do more than one-time charges to a customer's card. You can set up a subscription that will charge the card a specified amount at an interval of your choosing. The APIs you need to do this are part of Stripe's PHP library and the website contains excellent documentation that will walk you through the process.

What if you want to store credit card information so that customers don't have to enter it every time they visit your site? Stripe lets you do that too! You simply create a "customer" object in much the same way that we created a token. This object contains all the sensitive data that pertains to a particular customer. Stripe will securely store this information on their end (which means you don't have to risk a cactus beating) and you can bill the user whenever you like simply by requesting the appropriate "customer" object, just like we did with the token. Again, all the APIs are part of Stripe's PHP library and the website will walk you through it.


See it in Action

So that's it: Stripe in a nutshell! If you'd like to see a working example of what we've just covered with a bit more complexity and design, swing by this page and inspect the source. (Hint: it will look familiar.) Otherwise, if you've got questions leave a comment below, check out the Stripe Support Page or find me on Twitter: @bdkjones. Thanks and good luck!

Advertisement