6

I am spinning up an application using the Stripe.js elements. Using the test card numbers provided by Stripe, I have no issues. All works as expected.

I'm now focusing on error handling. When using the test card 4000 0000 0000 0002 to handle a card that is intentionally declined, I get this error:

Fatal error: Uncaught Stripe\Error\Card: Your card was declined. in C:\Apache24\htdocs...\vendor\stripe\stripe-php\lib\ApiRequestor.php:128 from API request {token}....

Now I'm assuming this is not a PHP Fatal Error (which can not be handled in a try/catch block), so I searched and found this example and implemented it into my code like this:

\Stripe\Stripe::setApiKey(self::APIKEY);

    $charge_arr = array(
        "key" => value, etc ... )

        try {               
            $charge = \Stripe\Charge::create($charge_arr);
            if($charge->paid == true) { 
                echo '<br>the card was successfully charged!';
            }

        } catch (\Stripe\Error\Base $e) {
            echo '<br>base';
            echo ($e->getMessage());

        } catch (\Stripe\Error\Card $e) {
            $body = $e->getJsonBody();
            $err  = $body['error'];

            print('Status is:' . $e->getHttpStatus() . "\n");
            print('Type is:' . $err['type'] . "\n");
            print('Code is:' . $err['code'] . "\n");                

            echo ($e->getMessage()); 

        } catch (\Stripe\Error\Authentication $e) {
            echo '<br>Auth';                
            // a good one to catch
        } catch (\Stripe\Error\InvalidRequest $e) {
            echo '<br>Invalid Request';             
            // and catch this one just in case
        } catch (Exception $e) {
            //catch any non-stripe exceptions
        }

This does not, however, catch the error. The message continues to be displayed as it was before I had the catch block.

Any clues why I'm getting the Fatal Error? I, of course, expected the card to be declined, but I expected the result of the decline to be something I could handle in the try/catch block.

EDITS

I should add that I'm using composer to include the Stripe php libraries.

Further note: It may not be relevant, but the Fatal Error message appears for all test cards that are supposed to be declined. The reason for the decline is stated clearly in the error message for each card like (not verbatim) zip code failed validation, CVC not correct, card expired, etc.

As @Ywain rightfully notes, this actually isn't a try/catch block problem. The Fatal Error is generated by the charge transaction in the first place. In other words the call to \Stripe\Charge should return a JSON array with appropriate flags or field values, not a Fatal Error.

globalSchmidt
  • 1,329
  • 16
  • 28
  • `\Stripe\Error\Base` is the parent class for all Stripe errors, so if you catch it first, none of the other `\Stripe\Error\...\` catch clauses will be executed. You should only catch it second-to-last (before `Exception` but after all other catch clauses). That said, if none of the catch clauses are executed despite the error message, the most likely explanation is that the code that is run is different from the one you're looking at and copied here. – Ywain Jul 31 '17 at 10:27
  • @Ywain - I have tried the code many different ways. I get the same result without ANY try/catch as well. So you raise a good point: while the try/catch block may not be to "spec", it's really not the issue. I will re-edit the post to reflect this. Any clues? – globalSchmidt Jul 31 '17 at 12:16
  • @globalSchmidt did you happen to work it out? I am having the exact same issue! – Lachie Mar 29 '18 at 01:51

2 Answers2

5

Stripe has many exceptions it can throw in the course of a transaction. Their API has excellent sample code to provide a template of catching the various exceptions Stripe API can throw.

try {
  // Use Stripe's library to make requests...
} catch(\Stripe\Error\Card $e) {
  // Since it's a decline, \Stripe\Error\Card will be caught
  $body = $e->getJsonBody();
  $err  = $body['error'];

  print('Status is:' . $e->getHttpStatus() . "\n");
  print('Type is:' . $err['type'] . "\n");
  print('Code is:' . $err['code'] . "\n");
  // param is '' in this case
  print('Param is:' . $err['param'] . "\n");
  print('Message is:' . $err['message'] . "\n");
} catch (\Stripe\Error\RateLimit $e) {
  // Too many requests made to the API too quickly
} catch (\Stripe\Error\InvalidRequest $e) {
  // Invalid parameters were supplied to Stripe's API
} catch (\Stripe\Error\Authentication $e) {
  // Authentication with Stripe's API failed
  // (maybe you changed API keys recently)
} catch (\Stripe\Error\ApiConnection $e) {
  // Network communication with Stripe failed
} catch (\Stripe\Error\Base $e) {
  // Display a very generic error to the user, and maybe send
  // yourself an email
} catch (Exception $e) {
  // Something else happened, completely unrelated to Stripe
}

Also keep in mind that once you catch the exception, unless you do something to specifically end your script or change execution path, that your script will continue to run the rest of the code blissfully unaware there was any exception raised. Consider this overly simple example.

try {
    $charge = \Stripe\Charge::create($charge_arr);
} catch (\Stripe\Error\Card $e) {
    echo("I had an error!");
}
echo ("I completed Successfully");

The card is declined and the api throws \Stripe\Error\Card. You then receive the following output.

I had an error!I completed successfully

This matters if you have multiple API calls in your execution path. You could very well catch the exception thrown just to have another one thrown by a later call that you were not expecting to run. You should have all calls to the API that could raise an exception wrapped in try/catch. You will also need to pay attention to your execution path if you are expecting errors caught from earlier calls to keep other code from executing. Overly simple example.

try {
    $charge = \Stripe\Charge::create($charge_arr);
    $declined = false;
} catch (\Stripe\Error\Card $e) {
    echo("I had an error!");
    $declined = true;
}
if(!$declined) {
    echo ("I completed Successfully");
}
Symeon Quimby
  • 820
  • 11
  • 17
  • That's exactly where I started... none of these catch the "Fatal Error". It's very frustrating. – globalSchmidt Jul 30 '17 at 22:01
  • Do you happen to have other calls to stripes api that are not in a try/catch block? I mention it cause I had someone the other day with same issue. They caught the exception on the first call, but never exited their script on the error and that caused a second call further down the scrip to cause the error. – Symeon Quimby Jul 30 '17 at 22:07
  • Good to know, but no. This is the first error case I started focusing on. No other calls have try/catch blocks. – globalSchmidt Jul 30 '17 at 22:11
  • @SymeonQuimby I am using the last example you have given to this question, but when the variable `!$declined` goes from being `false` to `true` it shows this error message: ***Undefined variable: declined*** –  Nov 05 '17 at 22:09
1

I think you need to catch the different types of tossed errors.

\Stripe\Stripe::setApiKey(self::APIKEY);

    $charge_arr = array(
        "key" => value, etc ... )

    try {

        $charge = \Stripe\Charge::create($charge_arr);

    } catch (\Stripe\Error\Base $e) {       
        echo ($e->getMessage());
    } catch (\Stripe\Error\Card $e) {
        echo ($e->getMessage()); // I think this is the missing one
    } catch (\Stripe\Error\Authentication $e) {
        // a good one to catch
    } catch (\Stripe\Error\InvalidRequest $e) {
        // and catch this one just in case
    } catch (Exception $e) {
        //catch any non-stripe exceptions
    }
John C
  • 666
  • 4
  • 8
  • Good catch! No change though. Still getting the same Fatal Error message. – globalSchmidt Jul 30 '17 at 22:00
  • This may be a dumb question, but is the path \Stripe\Error\* valid? Want to make sure its able to see those classes. – John C Jul 30 '17 at 22:03
  • Well, Stripe is included via composer so I haven't moved or touched any of the files. I assume (because it's in the Stripe documentation) that \Stripe\Error\{whatever] are valid references. The error does refer to stripe\stripe-php\lib\ApiRequestor.php line 128 which is handling a type 402 error like this: `case 402: return new Error\Card($msg, $param, $code, $rcode, $rbody, $resp, $rheaders);` Doesn't help much other than to say "Error\Card" is a thing. – globalSchmidt Jul 30 '17 at 22:08
  • `\Stripe\Error\Card` is the correct exception type to catch per the [source code](https://github.com/stripe/stripe-php/blob/master/lib/ApiRequestor.php#L128). Perhaps you should post your _full_ updated code? – fubar Jul 30 '17 at 22:25