So You Want to Accept Credit Cards Online?

So You Want to Accept Credit Cards Online?

Tutorial Details
  • Topic: Stripe
  • Difficulty:Beginner
  • Estimated Completion Time: 30 Minutes

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!

Note: Want to add some source code? Type <pre><code> before it and </code></pre> after it. Find out more
  • Mjay

    Damn! I want this, it’s just what I need.. but I live outside the states! :-(

  • http://www.gulfcoastwebdesign.com Mike

    This looks pretty promising. It’s about time someone took the hassle out of the merchant account / gateway scenario. I like the subscription payments option that allows you to do recurring billing without having to store client’s credit card on your own server. Good stuff. I’ll plan to give it a try on my next custom eCommerce client.

  • bill

    Sounds interesting. I’ve used PayPal for a very long time and been generally happy with them, so it would be nice to see how Stripe stacks up against them.

  • http://roadha.us haliphax

    Would be awesome if they’d build a microtransaction model that doesn’t sap $0.30 from each transaction. If you’re only charging $1.00 for little things, 2.9% + $0.30 is quite a lot!

  • Tim S.

    This is awesome! I’ve wanted to learn how to use this for a while, but only just starting out in web development.

    • Tim S.

      And now I just read that it isn’t available in UK!

  • Ian

    I would be concerned that it would scare customers to not see a familiar paypal payment portal because I won’t pay for anything online unless it goes through paypal.

    But it’s interesting to know about Stripe and will definitely look into them when it comes time to set up monthly payment plans with customers.

    I wonder what there policy is on charge backs for stolen credit cards. Or what other vendor protection they offer. I think that is one reason for going with paypal, google checkout, or another one of the big players who charge you all those extra fees but if Stripe provides the same vendor support it’s definitely worth looking into.

    • http://incident57.com/codekit Bryan
      Author

      From my experience, the proportion of users who are uncomfortable with smaller processors like Stripe is very small. I’ve handled thousands of transactions with the service and, over the last three months, maybe five people have emailed me and asked if they could pay with some bigger service such as PayPal.

      That said, I think how you build your order form matters. I’m careful to point out how I treat the user’s credit card data and I provide a link to Stripe right on the form so that users can go check them out if they want. If you’re honest and upfront about the situation, I think most folks will trust you.

      On a related note, Stripe is well aware of this issue. I just had a phone call with one of their developers this morning and he told me they’re working on some things that should make it easier to convince users that their data is secure.

  • Tommy

    In your example you list above (Your BUY page), you send a Serial Number to the user. Can you go into that a little more? Is that tied to a database?

    • http://incident57.com/codekit Bryan
      Author

      Whew, the process for generating a serial number properly is an entirely different article. You’d generate a public/private key pair with at least 4096 bit strength. Then, you take a string that identifies the user (say, their email address) and do the following to it, in order:

      1) Hash it with SHA1
      2) Encrypt it using the private key from your key-pair.
      3) Base-64 encode it so that the result can be displayed as text.

      The result of those three steps is the serial number that you hand to the customer. Then, in your app, you use the public key from the key-pair to reverse the process and get back to the user’s email address. At that point, you simply check to see if the email address you obtained by decrypting the serial number is the same as the email address the user has entered to register your app.

  • Akshay

    Expected something better from creator of CodeKit :P

    Stripe.com is only a good option if you have a bank account in US, why not go for PayPal instead?

    • http://terencedevine.com Terence

      using PayPal takes the user away from the website, Stripe does not.

      I actually had to make some last minute e-commerce solutions for a client last week and using PayPal was the quick and fast approach but wasn’t exactly what I wanted. I’ll be returning to the project soon and updating it to stripe.js, just needed a little more time.

      • Tom

        There is also PayPal Website Payments Pro. You can have all of your money dumping through the same company. People can pay traditionally through PayPal, or they can pay seamlessly by credit card through PayPal. I typically tell my clients to choose this option when they have to deal with multiple currencies and for convenience of having one less company to deal with.

      • Pierlo

        not true. AFAIK with Paypal Payments Pro you can process transactions transparently:
        http://net.tutsplus.com/tutorials/php/how-to-process-credit-cards-with-paypal-payments-pro-using-php/

      • Pierlo

        Gosh! Paypal Payments Pro is also only available for US and UK. D’oh!

    • http://incident57.com/codekit Bryan
      Author

      I hate PayPal with an eternal passion undying. If I could wave my hand and zap one company off the face of the planet, PayPal would be it. Here’s a great reason why: http://articles.businessinsider.com/2012-01-04/strategy/30587786_1_paypal-counterfeit-goods-antique-violin

      • http://community-auth.com/ Brian Temecula

        I hate PayPal too. They are not like a real payment gateway. Instead, they sit in the middle and decide what is right and wrong. I’ve had at least 3 or 4 situations where they held my money and made me prove that I had delivered what I sold. One of my first problems was when I sold a $4000 sewing machine, and I never thought I was going to get my money. They offer very little protection for a seller, and so I’ve been using Google for my payments. The only problem with Google is that customers have to leave my website. They probably have a better solution, but I never had the time to figure it out. I am going to give stripe a try. I’ve been checking it out all afternoon.

      • http://kriix.com Kristijan

        Same here. In my country (Croatia) PayPal holds all transactions for 30 days (they state security reason). The first time it was ok for me, I tought it will be only once, but even when the payments repeated from the same buyers/clients they didn’t change the limitation.

  • Sasa

    That’s all nice, but they don’t accept accounts outside USA

  • http://www.leachcreative.com Andrew

    Great tutorial, I think that this would be very useful in some cases, but in a lot of cases people don’t want to have to pay for SSL, which would make Paypal integration a better option.

    Anyways thanks for your hard work.

  • xbonez

    Couldn’t have asked for a better timing with this tutorial. I was just looking into Stripe last week. Thanks.

  • Tim

    Keep in mind that if you collect payment card information on your site as described in this tutorial, you become subject to the PCI Data Security Standard:

    https://www.pcisecuritystandards.org/security_standards/index.php

    • http://incident57.com/codekit Bryan Jones
      Author

      Hi Tim,

      According to Stripe, if you follow the token process outlined in the article, you ARE compliant with PCI. I linked to the documentation page explaining it in a comment a little farther down.

      • Tim

        I know PCI DSS leaves room for interpretation, but I don’t see how the server that hosts buy.php and pay.php is not in scope. What if someone were to gain unauthorized access? They could modify one or both of those scripts to log or e-mail themselves payment card data, regardless of how you’re integrating with Stripe.

  • http://ba.rrypark.in Barry

    Might be an edge case in these days (~1% of users), but I’d be tempted to put in there some html to handle users without JavaScript enabled. A simple text block that is stripped out onload by JS would suffice and would prevent a scenario whereby a user has completed the form but nothing happens when they try to submit (no feedback). A case where they might consider your site “broken” and go elsewhere.

    It’s a 2 minute fix tops.

    • http://incident57.com Bryan Jones
      Author

      Absolutely.

      I stripped it out to keep the demo code simple and focused on interacting with Stripe. But if you take a look at the example page I link to in the last paragraph, you’ll see that it does follow progressive enhancement.

      The way to do it is to build the page so that the form is hidden and a “enable javascript, you dolt” message is displayed. Then in the script you hide the no-javascript message and bring in the form instead.

  • Pierlo

    Pleeeeeeease make it available for EU merchants!!!

  • http://www.Khawaib.co.uk Khawaib

    Not available outside US, I have been looking into Dwolla https://www.dwolla.com/, did not get time to implement it yet, will be looking into it soon.

  • Jordan

    If your doing anything over $2000 per month in revenue the 2.9% (much higher than any gateway / merchant account). Starts adding up to a lot, case in point if you’re looking to go big, stick with a gateway like authnet which can get you rates under 2% for most credit cards.

    • http://incident57.com/codekit Bryan Jones
      Author

      I certainly don’t dispute that there are lower rates out there and that if you’re operatin above a certain scale, they may well be worth the headaches of dealing with the financial transaction system directly.

      However, the hidden fees really add up with these services. 35 cents per failed transaction (just because a user mistyped their card number!) plus batch fees and monthly/annual fees offset at least a good portion of the lower rate.

      Stripe does away with all the ancilliary fees and charges just one thing instead. I don’t know if that’s a perfect fit for huge operations, but for smaller folks selling stuff like themes, apps or the like, it’s a dream. Plus, I’m perfectly willing to pay a little more to never have to deal with Authorize.net. My time is better spent elsewhere.

    • John M

      Not true – add up additional fees charged by the big boys (recurring charges, customer card ‘bank’s so you can rerun charges at a later time, failed tran fees, the various additional transaction fees charged for bonus, business, and AMEX cards, etc). I have been with PayPal for a very long time and their pricing model (which Stripe mimics) is so much easier to deal with and understand.

      Also, with PayPal and hopefully Stripe to come soon, discounts are available with higher volume.

  • http://eandjdesign.com Eric Ritchey

    Thanks for the great post here — CC Processing has always been a bit of a…PITA, this article breaks it down quite well..

    One question though – Why not auto-detect the CC type (AMEX/Disc/Visa/Mastercard) by what the user typed, thereby removing the need for that select box? Seems like that could lead to a better UX as there’s one less thing the user needs to fill out..

  • http://blog.asmor.com Asmor

    You might want to double check the URLs in your example for secure vs. nonsecure requests. Both URLs you supplied are insecure, but it looks like you prepended the URLs you wanted with some other URL…

    Anyways, there’s an even easier way: protocols are optional. //www.example.com/images/foo.jpg will use the same protocol as the page it’s currently on. If you’re on http://www.example.com/, it will request foo.jpg via http; if you’re on https://www.example.com/checkout/billing.aspx, it’ll request the image via https.

    • http://incident57.com/codekit Bryan
      Author

      Bryan here. The nettuts website is doing something funny with the urls in those image tags. It looks like it’s prepending some CDN stuff to them. If you scroll to the right, you’ll see that the first one uses http and the second uses https. That said, a few people below have pointed out that you can use protocol-free urls instead.

  • Devin Dombrowski

    I can also vouch for Stripe. It’s nice and easy.

    Side note: Hilarious article!
    “$5.00 daddy-needs-a-new-porsche fee” too funny!

  • http://netvinyas.com Prabhakar Bhat

    “The only drawback is that Stripe is currently unavailable outside of the United States.”

    This “only” drawback makes it completely irrelevant for people like me (I am from India).

    • james

      So why not keep it on your radar? If you had looked into the scenario, you would have found out that they are working on an international scheme, which is not an easy endeavor.

  • http://www.webmaster-source.com redwall_hp

    You can use protocol-relative URLs instead, so the same URLs will work whether a page is served over HTTP or HTTPS.

    “http://example.org/lolcat.jpg” -> “//example.org/lolcat.jpg”

  • peter

    “The only drawback is that Stripe is currently unavailable outside of the United States.” If you put that in the first line I’d have saved 5 minutes of my time…

    • http://incident57.com/codekit Bryan
      Author

      Excellent point. Fortunately, for only $30 I will sell you the right to use my time machine to go back and reclaim these tragically lost five minutes of your life. And, if you call in the next hour, I’ll even throw in the 45 seconds it took you to write this comment — for free! Act now, this deal won’t last forever!

      • http://ignitepixels.com Fillip

        Hahaha loved this article and love the retorts! Keep up the great work Bryan!

  • James

    I use stripe. Tested out the API for php and python. I can say it is perhaps the easiest API/payment system that you will come across. The documentation is easy to follow. REST assured.(pun intended)

  • http://www.butlerpc.net Michael Butler

    If you’re considering accepting credit cards online, please also consider bitcoins (http://www.bitcoin.org) the free to use, secure, impossible to corrupt, open source, decentralized online currency system!

  • http://www.jarrydcrawford.com.au/ Jarryd

    Almost everything that is good is only available within the US… Stupid restrictions! Damn you!

  • http://www.metacrash.com.au Marc Loney

    It’s really great seeing developer friendly alternatives to having a merchant account pop up as there are many cases when it’s just not feasable for an online business to be paying those fees.

    Due to the legalities it’s unlikely you’ll be able to use a service like this in *insert your country here* any time soon, but for those of you in Australia keep an eye on http://www.pin.net.au, which is a similar service in beta at the moment.

  • David D’hont

    I like the “one does not simply” reference.

  • Techeese

    Title is a bit misleading this should be somewhat titled as “Accepting credit cards online with Stripe” or “Intro to payment gateway Stripe” :s

    thanks for the tut,though still gonna go with paypal for now since you know, it has global availability
    gonna bookmark stripe link check on it after a month/year or so, if it’s available for global.

  • http://last-child.com Ted Drake

    This is 2012, not 1998. Don’t make code samples that are not accessible. this is basic HTML.
    old code:
    First Name:
    proper code:
    First Name:

    After seeing that basic error, I lost interest in the rest of your article.

    • http://last-child.com Ted Drake

      my code samples got stripped out
      Your sample code:
      First Name:

      Accessible code:
      First Name:

    • http://last-child.com Ted Drake

      This code snippet should work.

      Your sample code
      <p class=”form-label”>First Name:</p>

      Accessible Code
      <label for=”first-name” >First Name:</label>

      • Bryan

        Sorry Ted, after seeing your basic error above (twice), I lost interest in the rest of your comment.

        Seriously, though: your point is a good one. Thanks for making it in a constructive, positive way.

  • Ed Croteau

    Can you comment on the PCI-DSS compliance issue with regards to this ?

    Does this method exempt your server from being compliant (which appears difficult) ?

    We are using a hosted shopping cart at the moment and the PCI compliance is on them but I wonder about the Stripe implementation above … anyone have any comments ?

  • Yuri Peixoto

    This is a great tool. I will stay tuned to see when tey will appear in Brazil!

  • Michael

    Nice Tutorial.. Hope it will be available in Switzerland soon.

    With Paypal Pro you can accept credit cards too, and they dont need to create an account.

  • http://www.elimcmakin.com Eli McMakin

    Hopefully, Stripe works well. I may give it a shot. Had a customer try to buy something with PayPal and couldn’t due to security features. Apparently, PayPal is so secure, no one can buy things with it, including the person who’s name is on the card.

  • Potado

    I like how this article is written; with lots of pizazz.

  • http://www.bloginity.com Daniel

    Well, now I have to create a store.

  • Les

    You show the test keys on the client (via Javascript), and mention to keep those details “secret” but how would that be possible if you [would] need to keep the live keys on the client too? Unless of course, I have missed something?

    I see that they [live keys] are used on the server which is fine, but I’m assuming if you are [to] use the test keys on the client, then you would need to use the live keys on the client too.

    Otherwise, I would use this API however they would need to come to the UK as I’m not going to the US simply to register my interest :p

    • Bryan Jones
      Author

      The live PUBLISHABLE key is the one that we include on the client-side. That key is designed to be public. It’s the live SECRET key that you must protect and keep secret. That key gets included only in the server-side script, so it is not publicly available.

      The same logic applies to the test keys as well. The PUBLISHABLE keys do not need to be safeguarded. The SECRET keys, however, do. Because anyone that has BOTH keys can issue transactions to Stripe that will affect your account. Therefore, you better keep those secret keys safe!

      • Osikani

        Your pay.php script includes the secret key as plain text. What about the site scraper tools which can download your entire website? (No link provided, but Google will show many).

        How do you prevent the download of the pay.php script? Alternatively, is there any way to encode that information and call a decrypter function that is in a directory that cannot be accessed. other than by the script on the server.

        Forgive me if my question is elementary, but I am still trying to understand this completely.

      • http://www.facebook.com/plopeanu Adrian Plopeanu

        Man, any scraper tool in the world can’t read your server side PHP file. If you request a PHP the server will respond with HTML .If you made some echo or print statements inside the PHP file only these will be printed out as HTML. No echo, no content, means the browser or scraper tool will see an….empty file!!! This is elementary web programming!!!!

  • http://webduos.com Webduos

    What’s up to every one, it’s truly a fastidious for me to pay a visit this site, it includes priceless Information.
    Thanks for this

  • https://secure.karelia.com/buy_now/ Dan W.

    I recently discovered Stripe, and implemented it on our web store. I really like it! It was pretty easy to implement; I also brought in a bunch of other techniques such as jquery validation, helping to catch some typos in email addresses, and so forth.

    Stripe’s support is very nice; the only criticism I have is that they don’t (yet) accept currencies besides the US Dollar. Note on our payment page (linked from my name) that we are still accepting PayPal as well.

    If you are going to build your own credit card form, be sure to take a look at a lot of online stores, and see how they are done. What kinds of help do they offer, how do they deal with errors, how many separate form-submission steps must you go through, and so forth.

  • Carsten Dreesbach

    Great article! Couple of things I would point out though.

    First, having the price set but the JavaScript strikes me as a pretty had idea. I could easily go in with Firebug, change that code before submitting and get away with paying 1 cent for your product… better to set that on the server.

    Secondly I would put the private keys into a file that lives outside my www directory on the server that the PHP script can then load. That way there is no chance of an accidentally misconfigured .htaccess file exposing your keys. Granted, in that case you’d have other issues, but at least you wouldn’t have exposed your keys on top of everything else… ;]

    Overall, like this a lot – thanks for sharing.

    • John M

      agreed. The payment information in its entirety should be picked up on the page load with PHP. For example:

      $amount,
      “currency” => “usd”,
      “card” => $token,
      “description” => “payinguser@example.com”)
      );

      if (!isset($charge))
      throw new Exception(“Could not charge”);
      else
      $success = ‘Your payment was successful.’;

      }
      catch (Exception $e) {
      $error = $e->getMessage();
      }
      }
      ?>

    • John M

      agreed. The payment information in its entirety should be picked up on the page load with PHP. For example:

      $amount,
      “currency” => “usd”,
      “card” => $token,
      “description” => “payinguser@example.com”)
      );

      if (!isset($charge))
      throw new Exception(“Could not charge”);
      else
      $success = ‘Your payment was successful.’;

      }
      catch (Exception $e) {
      $error = $e->getMessage();
      }
      }
      ?>

    • AskarH

      Isn’t the dollar amount that you mention on the Stripe.js button is purely for display purposes?

  • http://mrfloris.com Floris

    Form validation for customer details should in my opinion be rock solid, server-side where possible, should work without javascript, and html5 support with js only as a fallback. Just my opinion.

    • http://www.facebook.com/lupodbutcher Matt Thompson

      stop coddling idiots that are scared of javascript in 2013.

  • http://www.blog.prabhustudio.com Prabhu Studio

    WOW, that is amazing.

  • Rahul

    ↑ upvote for using mimes :)

  • http://www.ans2ques.com Rafiul Alom

    Hello Bryan Jones,

    I was looking for such a great guide. Thanks a lot. Hope we will get much more articles like the important topic you writes.
    Appreciated….

  • http://accustomtips.com/ rlyn

    nice.. thanks for this tutorial. ill try this….

  • http://www.printcompcards.com Kosal

    We’re using Stripe to process our payment and man, was it super easy to implement. Literally, it took us about 5 minutes to set it all up and there wasn’t any hassle at all in doing so. Their backend interface is very easy as well. I give Stripe 5/5.

  • Rolf

    Yeah, I’m also curious how you think this is PCI DSS compliant. Looking forward to your answer to Simon.

  • SJ

    Using Stripe or anyother gateway like WePay is not at possible for Indians. PayPal is almost useless for Indians too(We can’t make transactions within India and too many restrictions for International transactions)

    We have only a few gateways like ccAvenue and EBS.In. Hope some one would help us to implement them

    Thanks !

  • http://eway.co.uk/ The eWAY Team

    Merchants in the UK, Australia or New Zealand who want to accept payments with similar ease can use eWAY. We have 180+ pre-integrated carts, free 24/7 phone support and flexible, powerful payment APIs to help turn a start-up into an eCommerce juggernaut.

    You can visit us at eway.co.uk, eway.com.au or eway.co.nz.

    Kind regards,
    The eWAY Team

    • Martin

      You neglect to tell everyone that you don’t provide the use of your merchant account (like Stripe allow you to do). With eWay it appears you must acquire a bank issued merchant account (or bring your own). Pity.

  • Sanket Chauhan

    I don’t have a lot of experience with PHP, and my site is a simple HTML. Can I just use your source files directly, and expect it to work by making changes to how much it would be charged?

  • Cameron

    How would one go about storing the form values into a database sans payment information? I’m a programming novice, but can .js pass the values into mysql?