I'm testing making payments using the Stripe api in the Symfony framework.
Currently when I make a legitimate call payment is made and Stripe returns a status 200.
The problems start when I use one of the test cards (or just a made up one) to force Stripe to return an error, card has expired for example, and an exception is thrown (Stripe_CardError). The response code from the api is 402 but even when I put a catch block in for that exception Symfony throws a 500 internal server error.
Here's part of the code:
require_once('../vendor/stripe/stripe-php/lib/Stripe/Stripe.php');
try{
\Stripe::setApiKey($this->container->getParameter('stripe_api_key'));
$charge = \Stripe_Charge::create(array(
"amount" => 1599,
"currency" => "usd",
"description" => "This is a test payment from post data",
"card" => array(
"number" => $data['cardNumber'],
"exp_month" => $data['expMonth'],
"exp_year" => $data['expYear']
)));
} catch(Stripe_CardError $e) {
Do error checking stuff here...
}catch (Exception $e) {
print_r($e->getMessage());
} catch (ErrorException $e) {
print_r($e->getMessage());
}
Like I say, this code works fine until an exception is thrown but when one is I seem unable to catch it and act upon it.
In the Symfony debug screen shows the correct error message is returned from the api but it does not have the correct code (should be 402):
Your card was declined.
500 Internal Server Error - Stripe_CardError
I feel this is a Symfony config issue but numerous searches have not provided me with any solutions. Any ideas?
P.s. I am also using the FOSUserBundle.
It's What Pazi said. The Stripe error class wasn't registered in the current namespace and so needed the slash in front of it like when the create method was called.
I can't believe I missed it but that's what you get for cut and paste!
Thanks Pazi.
Related
I asked a very specific question located here: Laravel 5 catching PayPal PHP API 400 errors on localhost:8000
As nobody could help I want to make it a more open question about catching a 400 error from the PayPal API.
I am making requests to PayPal, when a successful request is made everything works perfectly and I get a lovely response object, which I can work with and action accordingly.
When for instance incorrect card details are entered Laravel throws a 400 error and fails to catch the error for me to action accordingly.
Snippet:
try {
// ### Create Payment
// Create a payment by posting to the APIService
// using a valid ApiContext
// The return object contains the status;
$payment->create($this->_apiContext);
//will not catch here :( throws Laravel 400 error! want to redirect with message!
} catch (\PPConnectionException $ex) {
return Redirect::back()->withErrors([$ex->getMessage() . PHP_EOL]);
}
//if we get an approved payment ! This fires perfectly when succesful!!! woo!!
if($payment->state == 'approved') {
//if success we hit here fine!!!
} else {
//won't get here, dies before catch.
}
Here is the error in Laravel debug mode:
When I look in the PayPal API sandbox logs I should be getting a nice object so I can action accordingly.
{
"status": 400,
"duration_time": 60,
"body": {
"message": "Invalid request. See details.",
"information_link": "https://developer.paypal.com/webapps/developer/docs/api/#VALIDATION_ERROR",
"details": [
{
"field": "payer.funding_instruments[0].credit_card.number",
"issue": "Value is invalid."
}
],
"name": "VALIDATION_ERROR",
"debug_id": "XXXXXXXXXXXXXX"
},
"additional_properties": {},
"header": {
"Date": "Thu, 25 May 2017 14:44:43 GMT",
"paypal-debug-id": "2f88f18d519c3",
"APPLICATION_ID": "APP-XXXXXXXXXXXX",
"Content-Language": "*",
"CALLER_ACCT_NUM": "XXXXXXXXXXXXX"
}
}
If any Laravel wizard can help you would be my hero.
Nick.
Right guys,
It would appear that Laravel's default Exception method was interfering with the PayPal API PayPalConnectionException. So I modified the code to catch general Exception errors only as it contained all required error objects. The \ before Exception was critical! as it needs the correct namespace (in my case anyway, your application may be different).
try {
// ### Create Payment
// Create a payment by posting to the APIService
// using a valid ApiContext
// The return object contains the status;
$payment->create($this->_apiContext);
} catch (\Exception $ex) {
return Redirect::back()->withErrors([$ex->getData()])->withInput(Input::all());
}
This link that #rchatburn posted was highly useful, the application always seemed to catch at the point \Exception and NOT \PayPalConnectionException once I had everything namespaced correctly.
In my investigations I came across app/Exceptions/Handler.php. Here you can extend the render method to grab a PayPalConnectionException and handle the errors uniquely to that specific exception . See code:
//Be sure to include the exception you want at the top of the file
use PayPal\Exception\PayPalConnectionException;//pull in paypal error exception to work with
public function render($request, Exception $e)
{
//check the specific exception
if ($e instanceof PayPalConnectionException) {
//return with errors and with at the form data
return Redirect::back()->withErrors($e->getData())->withInput(Input::all());
}
return parent::render($request, $e);
}
Either work great, but for me it felt neater to just change the catch method to a lookout for a general Exception, where I am testing if a payment was successful.
Hope this helps anyone facing similar issues :D!!!
Nick.
I use laravel 5.4. When in my tests I do something like:
$this->get("/api/test")->assertStatus(422)->assertJson([
"status" => "ok"
]);
I get an HttpException when status code is 422, even though I'm totally ready for this -- and I'm even expecting it. My problem is that exception prevents my asserts, so that assertJson just doesn't work.
$this->expectException(HttpException::class) doesn't help since it catches this exception and stops, so this begins to pass:
$this->expectException(HttpException::class)
$this->get("/api/test")->assertStatus(422)->assertJson([
"status" => "ok"
]);
$this->assertTrue(false);
I see I can catch this exception myself, but then I won't be able to apply my asserts to the response in an easy way. How can I work around that trouble?
The validation errors only get converted to json response on a json or ajax request. Try this.
$this->getJson("/api/test")->assertStatus(422)->assertJson([
"status" => "ok"
]);
I'm currently working on an open source personal project that provides a nice backend api for game developers. I'm in the early stages of development, but I plan to write tests as I go along, which is where I've hit a snag.
Through out the system when an error occurs such as incorrect api credentials or missing credentials, I throw a custom exception which stores a bit of extra data so that I can catch it and give a JSON encoded response.
The tests work fine for those thrown in my BaseController, but I also capture a few Laravel Exceptions so I can respond with my own, or at least, output JSON like below:
app/start/global.php
App::error(function(Exception $exception, $code) {
Log::error($exception);
});
App::missing(function(Exception $exception) {
return BaseController::error(
Config::get('response.method.code'),
Config::get('response.method.http'),
'Method not found'
);
});
App::error(function(Viper\Exception $exception) {
return BaseController::error(
$exception->getCode(),
$exception->getStatusCode(),
$exception->getMessage()
);
});
I'm using the try { } catch() { } approach as I need to check an extra value that isn't in the normal Exceptions.
public function testNoMethodGET() {
$config = Config::get('response.method');
try {
$this->call('GET', '/');
} catch(\Viper\Exception $e) {
$this->assertEquals($e->getCode(), $config['code']);
$this->assertEquals($e->getStatusCode(), $config['http']);
}
$this->fail('Exception not thrown');
}
This is all good and well, but I want to check a few things on the actual response, like for example, whether or not the json is valid, whether or not the response structure matches and whether or not the response values are correct.
If I set the return value of $this->call() to a variable, I'd be unable to access that variable within the catch block, so the question is this, how can I test the return value of $this->call() once the Exception has been caught?
According to Taylor Otwell:
"this can be solved by de-coupling your
test. You really want to test the handler and that the exception is
thrown totally separately anyways [sic] to isolate your tests. For
instance:
App::error(function(ErrorType $e)
{
App::make('ErrorTypeHandler')->handle($e);
});
Now you can write test cases for ErrorTypeHandler class separately
from the rest of your application. Then check that proper exceptions
are thrown by your app with #expectedException."
see How do you test your App::error implementations?
In your case, you already have isolated your error handler in BaseController::error(), so you can test the responses directly in separate unit tests, without the use of $this->call(). Instead, just call $response = BaseController::error() with the desired parameters and then inspect the response and apply relevant assertions.
I'm experiencing something eerily similar to this question about an uncatchable PHP error thrown by the Facebook PHP SDK except for the fact that I'm not using PHP namespaces at all. This other question is also close, but doesn't explain why the error is uncatchable. Further, in my case, I have a Facebook app that issues a Facebook Graph API call against an object that the current user has blocked. This is certainly awkward, but legal for the purposes of this particular app. That means I need to catch the error, not prevent the user from making the search in the first place.
The fatal error's output in my development environment looks like this:
Fatal error: Uncaught GraphMethodException: Unsupported get request. thrown in /path/to/apps/lib/facebook/src/base_facebook.php on line 1271
So, Facebook's Graph API correctly returns an error as a result of the API call, citing "Unsupported get request." However, the Facebook PHP SDK seems to throw this as an uncatchable error, and I don't know why.
I've tried code like the following catch blocks with no success:
try {
$response = $facebook->api("/$some_id_of_object_current_user_has_blocked");
} catch (FacebookApiException $e) {
// Why does this never get caught?
} catch (Exception $e) {
// Similarly, this also never gets caught!
} catch (GraphMethodException $e) {
// Still can't catch this exception, and I don't grok why. :(
}
For the sake of ridiculous completeness, I've also tried namespaces including things like this:
try {
$response = $facebook->api("/$some_id_of_object_current_user_has_blocked");
} catch (\FacebookApiException $e) {
} catch (\Exception $e) {
} catch (\FacebookApiException\GraphMethodException $e) {
} catch (\GraphMethodException $e) {
} catch (... $e) {
}
Further investigation lead me to try catching this in the base_facebook.php file itself, where it seems to get thrown, in the protected Facebook::_graph method. And sure enough, it is catchable there. The original code at about line 879 of base_facebook.php is:
if (is_array($result) && isset($result['error'])) {
$this->throwAPIException($result);
// #codeCoverageIgnoreStart
}
Wrapping this call to throwAPIException() with a try...catch block works:
if (is_array($result) && isset($result['error'])) {
try {
$this->throwAPIException($result);
// #codeCoverageIgnoreStart
} catch (Exception $e) {
// WORKS!
}
}
So if it works there, why can't I catch this exception from my own scripts? Am I missing something fundamental about the way PHP error handling works?
Alternatively, is there a way for a Facebook app to get a list of all the objects a Facebook user has blocked, such as other Facebook users a user has blocked? I'm familiar with Graph API enough to know that there's a way for an app to access a list of all users a page has blocked, but that's specifically not what I'm looking for.
Thanks for your time.
It's apparently uncatchable because it relates to the permissions your app uses.
In your case, it looks like you were trying to GET the same thing as me, which requires the permission: read_stream
It makes sense that they would make this sort of thing uncatchable - but you'd think the facebook devs could do something a little more friendly...
Adding the try/catch around $this->throwAPIException($result); works in suppressing the error message but I would recommend checking the inputs to your functions to ensure they exist and are valid.
For example, check to see if $SESSION['fb<your_app_id>_access_token'] exists and is not null before passing this to any functions. If this is not set or is null, none of the functions that rely on it will work and you can catch the potential issue before communicating with Facebook's servers, which speeds up your application by skipping a function you can determine ahead of time will fail, and resolve issues quicker by asking for corrections proactively instead of re-actively.
I also was seeing this error which apparently was caused by my app not having the appropriate permissions, as #rm-vanda stated. Because the app id does not have the permissions, a token is not returned which results in the error you are seeing.
Hope that helps!
I have below code in error.php, which is triggered using App::abort(404, $error) in my controller. Still my response status code is 200(ok). I tried with various error codes like 400, 403
// NotFoundException handler
App::error(function(NotFoundException $e)
{
$default_message = 'The requested resource was not found';
return Response::json(array(
'error' => $e->getMessage() ?: $default_message,
), 404);
});
For anyone still googling this problem:
I was struggling with this problem for hours. For me the problem was caused by an issue with one of my controllers.
Check all of your controllers and make sure there are no spaces in front of the <?php tag. The <?php tag should be the very first thing in the file. A single space in front of the <?php tag in any of your controllers that are routed as such:
Route::controller('example', 'ExampleController');
Will cause all status codes to be 200.
I believe, regardless, you should receive a 404 response, so there might be something else happening that's the result of code not included in your question.
That being said, the Exception class that is thrown for 404 is NotFoundHttpException rather than NotFoundException.
Since Laravel 4 uses Symfony's HttpKernal, that Exception is here.
You can see here where App::abort() throws NotFoundHttpException when a 404 is triggered.
Therefore, your code should look like:
// NotFoundHttpException handler
App::error(function(\Symfony\Component\HttpKernel\Exception\NotFoundHttpException $e)
{
$default_message = 'The requested resource was not found';
return Response::json(array(
'error' => $e->getMessage() ?: $default_message,
), 404);
});
Important: This will only fire for a 404 status, as that's the corresponding code to NotFoundHttpException. Other status codes return other Exception classes. To capture all HTTP status error codes exceptions, type hint for HttpException like so:
// HttpException handler
App::error(function(\Symfony\Component\HttpKernel\Exception\HttpException $e)
{
return Response::json(array(
'error' => $e->getMessage(),
), $e-> getStatusCode());
});
Lastly, consider using a bit of Content Negotiation when deciding to return JSON or HTML.
The solution didn't worked for me, so in case anyone is still looking for an answer, I thought it be best to put it here instead of creating another question.
After some time I had this problem too, in my app/Exceptions/Handler.php I had:
if ($e instanceof ModelNotFoundException) {
if ($request->ajax()) {
return response()
->json(['error' => ['No results']])
->header('status', 422);
}
}
This worked in my local environment, however, in the homolog environment (which reproduces the production environment, just to be clear) it didn't returned the correct status code.
After another look I started looking at Laravel's docs, and I changed the call to the following:
return response()
->json(['error' => ['No results.']], 422);
And that did the trick. Hope this can help.
In my case I found some space in front of <?php
Remove dump & other print functions
I was actively debugging when I noticed this issue. It was caused because I had dump(...) calls in the code at that time.
When I removed all my debug dump calls, the status code was correctly 404 again (using abort(404).