Handling Stripe Errors

January 30, 2013
This entry is part 7 of 8 in the series Processing Payments with Stripe

If you’ve sequentially followed this series on implementing payments with Stripe, you now know everything you need to be processing payments with Stripe today. In theory. This series, the six posts to this point, has already walked through all of the code needed for the system to work, assuming everything does work. Which is to say I have yet to explain how to watch for and handle the errors and problems that will inevitably occur. That’s the focus in this post.

Note that this article assumes that you have read the previous articles and are comfortable with PHP.

What Could Go Wrong?

In order to best watch for, and handle, errors that might occur, it helps to think about what those could be. Using the code I’ve already walked through in previous posts…

  • The form data is validated on the client-side using JavaScript and the Stripe.js JavaScript library
  • The form data is validated on the server-side using PHP

Really, the only thing that could go wrong at this point in time is if the Stripe charge attempt fails. Which, is a pretty big failure. E-commerce without successful payments is just…well…a short-lived business.

Now, you may wonder how the charge attempt could fail when the Stripe.js library provides a handy-dandy way to securely handle the payment information. (In case you haven’t read the whole series, or don’t know the secret sauce, Stripe.js sends the payment information directly to Stripe and gets an associated, unique token in return. That token then gets submitted to your server and is used to actually charge the customer.)

Well, if you are wondering how a charge attempt could still fail, then truth be told, you should know that the Stripe.js process really does only two things:

  1. Gets the payment information to Stripe in a secure way (limiting your liability)
  2. Verifies that the payment information looks usable

In terms of watching for errors, it’s this second factor that comes into play. Stripe will confirm that the card information looks valid, but that doesn’t mean it can be used for a payment. Only when the charge is actually attempted will Stripe, and you, discover that this might be a debit card with insufficient funds, a credit card over the limit, or that an invalid CVV value was provided. These are the kinds of errors you must now watch for.

Exception Types

The Stripe PHP library uses object-oriented programming (OOP), naturally, so watching for and handling the errors that might occur is a matter of catching exceptions. The exception object’s structure in Stripe is a bit convoluted, so let’s start at the top. (The error structure, and documentation of errors in Stripe, is one of the few places in which Stripe falls flat IMHO.)

Note that all the syntax is for version 2 of the Stripe PHP library, which uses namespaces. See the previous post for more on the Stripe PHP library versions.

The \Stripe\Error\Base class is the base error class in Stripe (and it extends PHP’s Exception class). Beyond the attributes inherited from Exception, \Stripe\Error\Base adds:

  • httpStatus
  • httpBody
  • jsonBody

The httpStatus attribute will store a code that roughly maps to HTTP status codes. In short, codes in the 200’s indicate success, codes in the 400’s indicate failure because of something you or the customer did, and codes in the 500’s means Stripe’s servers are acting up (and the people at Stripe are currently panicking). You might be inclined to write your code to use the returned error code, but there’s an easier way. Instead, just watch the type of exception thrown:

  • If the charge went through, no exceptions will be thrown. Whoohoo!
  • If the charge was attempted but declined, a \Stripe\Error\Card exception is thrown. This is the most common non-successful result.
  • If your site did not send the correct information to Stripe, a \Stripe\Error\InvalidRequest exception is thrown. If you do your job properly and validate, validate, validate, you should not see any of these (of course, “should not” !== “never will”).
  • If a connection cannot be made to Stripe’s servers (i.e., there’s a network problem), a \Stripe\Error\ApiConnection exception is thrown.
  • If there’s a problem with Stripe’s servers, a \Stripe\Error\Api exception is thrown (and people are panicking in downtown San Francisco).
  • If you provide an invalid API key, a \Stripe\Error\Authentication exception is thrown.
  • If some other type of problem occurred, you can watch for \Stripe\Error\Base exceptions. You’d mostly see these if you screwed up, such as failed to provide the Stripe library with your API key or failed to provide an array to the create() method.

TIP: Internally, the Stripe library throws different types of exceptions based upon the status code returned.

Catching Exceptions

I put forth this code in the previous post:

require_once('vendor/autoload.php');
try {
    \Stripe\Stripe::setApiKey(STRIPE_PRIVATE_KEY);
    $charge = \Stripe\Charge::create(array(
        'amount' => $amount, // Amount in cents!
        'currency' => 'usd',
        'source' => $token,
        'description' => $email
    ));
} catch (\Stripe\Error\Card $e) {
}

Knowing what kinds of exceptions might occur, you can expand this to watch for the various types, from the most common (\Stripe\Error\Card) to a catch-all (\Stripe\Error\Base):

require_once('vendor/autoload.php');
try {
    \Stripe\Stripe::setApiKey(STRIPE_PRIVATE_KEY);
    $charge = \Stripe\Charge::create(array(
        'amount' => $amount, // Amount in cents!
        'currency' => 'usd',
        'source' => $token,
        'description' => $email
    ));
} catch (\Stripe\Error\ApiConnection $e) {
    // Network problem, perhaps try again.
} catch (\Stripe\Error\InvalidRequest $e) {
    // You screwed up in your programming. Shouldn't happen!
} catch (\Stripe\Error\Api $e) {
    // Stripe's servers are down!
} catch (\Stripe\Error\Card $e) {
    // Card was declined.
}

For each of these exception types, you’ll want to take different actions:

  • For network problems or Stripe server problems (i.e., \Stripe\Error\ApiConnection and \Stripe\Error\Api), you could immediately try another attempt. If that attempt also fails, you would then notify the customer of an internal problem, beg the customer’s forgiveness, and hope they try again (immediately or in a bit).
  • For the invalid request error, you need to fix your code, pronto!
  • For card declined errors, you’ll need to report that to the customer, which I’ll explain next.
  • For all other errors, you’ll need to apologize to the customer and fix the problem. How you do that depends upon what actually happened, which leads me to…

Any time an exception occurs, I would strongly recommend that you email the administrator (or the developer, or yourself). You should also log the problem. For both, you’ll want to provide explicit details of the exception (e.g., a dump of the exception message), and perhaps some or all of the customer details that you have). It’s only by being made aware that the problem occurred, and being provided with as many details as possible, that you can fix the problem quickly and have any chance of preventing that problem from happening again in the future.

TIP: Professional applications use logs and notifications to make debugging and site maintenance much, much easier.

The only exception (ha!) to this suggestion is that sending emails and logging \Stripe\Error\Card exceptions is probably unnecessary and impractical: only the customer can fix those problems.

Handling Declined Cards

Handling a declined card is a bit more complicated, because you need to find out why the card was declined and provide that information to the customer so he or she can correct the problem. The goal is to get the specific reason for the decline from the exception. This is a multistep process:

  • Get the total response, in JSON format, from the exception
  • Get the error body from the response
  • Get the specific message from the error body

Here’s that code, in context of the exception catching:

} catch (\Stripe\Error\Card $e) {
    // Card was declined.
    $e_json = $e->getJsonBody();
    $error = $e_json['error'];
    // Use $error['message'].

Stripe does promise that the value ($error[‘message’] in the above) will be informative and presentable to the customer. Some actual values include:

  • The card number is incorrect
  • The card number is not a valid credit card number
  • The card’s expiration month is invalid
  • The card’s expiration year is invalid
  • The card’s security code is invalid
  • The card has expired
  • The card’s security code is incorrect
  • The card was declined

So after executing the code above, just use $error[‘message’] where appropriate to present the specific problem to the customer and allow the customer to try again (or just hope that he or she does).

Conclusion

Watching for, and handling, exceptions that might occur during the payment process is vitally important to a successful e-commerce site. Moreover, using proper logging and notifications (i.e., emails) for when problems do occur is the only way you can quickly and efficiently fix the problems and hopefully prevent more transactions from failing.

The errors section of the Stripe documentation is sadly, and surprisingly, unclear in my experience, so hopefully this post has provided you with the information needed to properly handle exceptions in your e-commerce site. Of course, the code I’ve presented has to be properly put within the context of your site, but hopefully you’re able to do that (or else tackling e-commerce may be a bit of a stretch for you at this time).

If you have any questions or comments, please do let me know.

In the next and final post in this series, I’ll mention a few other tips and tricks when it comes to using Stripe to handle payments.