A typical integration with a payment gateway such as Stripe looks like this:

In the error handling block, you may decide to email the customer, block his access to your website, etc. Hopefully, you also keep track of these errors and generate a daily report on how many transactions fail: after all, these directly impact your bottom line. Good enough? Unfortunately, as we will see below, this approach is often too simplistic.

In this particular example, the exception caught is only one of several possibilities. Looking at Stripe’s documentation specifically, CardError is thrown only for 402 – Request Failed. Other exceptions can occur, such as RateLimitError, when 429 – Too Many Requests is returned: in this case, you went over your API quota and you will want such requests to be re-submitted later.

Let’s now assume we fix our code above to look like:

Errors like InvalidRequestError, AuthenticationError or APIError should be closely monitored after each deployment, as they may be triggered by a recent code or configuration change. What about the black box of CardError? Let’s take a look at how we can categorize further such payment failures.

On one hand, some transactions may be blocked by your payment gateway, depending on your configuration settings. Braintree for instance refers to them as Gateway Rejections. A typical example is CVV rejections: when triggering an authorization, the issuing bank will tell the gateway if the supplied CVV matches. If it doesn’t, most gateways can be configured to automatically void the transaction to prevent fraud. Another typical scenario is when your provider offers fraud detection tools: if it determines that the transaction is suspicious, it can block it right away. It’s important to keep track of these rejections separately, and tweak your gateway settings according to the type of goods and services you offer, country of business, etc., to avoid fraud while making sure your good customers can still check-out.

On the other hand, the transaction can be blocked by the actual payment processor (most gateways are actually API wrappers to payment processors, who will in turn contact the issuing banks through the payment networks). Going back to Braintree, they are referred to as Declines. These can be further broken down into two categories: hard declines and soft declines. Hard declines (for example Invalid Credit Card Number) should never be retried, as such transactions cannot succeed. Soft declines (such as Insufficient Funds) could be retried at a later point.

Understanding these processor errors can really help increase your acceptance rates, i.e. minimize the number of transactions rejected. Let’s take a look at CyberSource (gateway) integrated with Chase Paymentech (processor) for example. Chase errors 540 Declined – Under 18 Years Old and 542 Declined – Bill To Not Equal Ship To are both returned as CyberSource error 203 General decline of the card: while the general error is cryptic, the actual error gives you a lot of information. By asking the customer to verify his personal information (such as billing address and age) early in the purchase funnel, you can minimize the number of transactions rejected by Chase Paymentech because of errors 540 and 542 (something you wouldn’t have been able to do by simply by looking at the CyberSource data, since it labels such errors simply as 203 General decline of the card).

Accessing these raw error codes isn’t always easy unfortunately (in case of Paymentech specifically, you can get them by looking at the Stratus documentation, the underlying processing platform) and this needs to be done for each payment processor you are using: as you support more and more payment methods in various countries, you will most likely integrate with more than one (Maestro UK not being supported with Chase Paymentech via CyberSource, you would need to also configure Barclays in CyberSource to accept these cards).

Doing all of this low-level work is one of the features that Kill Bill Payments platform offers, and why you would want to avoid integrating with a gateway directly. Each payment transaction has a specific state:

  • SUCCESS, for when the payment went through
  • PAYMENT_FAILURE, for errors tied to the card (e.g. insufficient funds): these are the hard ones to understand, and often involve business discussions with your gateway, bank, etc. You may also need to try out a new processor for a subset of your traffic to optimize them
  • PLUGIN_FAILURE, for errors not tied to the card (configuration issue, gateway down, etc.): you should monitor them closely as these indicate problems that can be more easily fixed (e.g. lower fraud checks thresholds)
  • UNKNOWN, for everything else

Kill Bill communicates with payment gateways via plugins, which implement the mapping of these errors to these states. This separation of concern lets Kill Bill implement retries in a generic way. Similarly, the Analytics plugin can generate reports that are gateway agnostic.

Note that you should have very few UNKNOWN transactions: this is where Kill Bill doesn’t know if the payment went through. This can happen when receiving a timeout from the gateway. If the provider supports it, Kill Bill will attempt to fix the state automatically by fetching the payment state using the unique reference code it sent along with the transaction (we will cover this feature in greater length in a dedicated post).

To summarize, integrating with a payment API is difficult, as there are often a lot of intermediaries in the chain. One of the goals of Kill Bill is to simplify this integration, regardless if you integrate with one or several gateway(s), directly with processor(s) or any combination thereof. While this post was primarily focused on credit cards, similar unification is done for any type of payment methods (ACH, Bitcoin, Hosted Payment Pages, e-wallets, etc.).