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.
Related
I am building api in laravel and I am catching exceptions if they occur and I am returning them as response. The problem is that I only want to show them in my dev enviroment. Setting APP_DEBUG=false doesn't solve the problem, error message is still in response.
public function foo()
{
try{
$this->bar();
}catch(\Exception $e){
return $this->error($e->getMessage(),'Something went wrong', 403);
}
}
This returns json for example:
{
"error": "No query results for model [App\\Models\\User].",
"message": "Something went wrong"
}
I want to have this in my dev enviroment, but on production I would like to have only the message like:
{
"error": "500 Server Error",
"message": "Something went wrong"
}
How do I achieve this and is this a good practice to do? I haven't found any solution yet, except to overwrite message if APP_ENV=production, but I have a feeling that there is a better way to do this.
protected function error($error, $message = '', $code = 400)
{
$this->response['error'] = $error;
$this->response['message'] = $message;
if(env('APP_ENV') === 'production'){
$this->response['message'] = '500 Server Error'
}
return response()->json($this->response, $code);
}
Agree with #dummy.learn's answer, the best way to handle exception is inside App\Exceptions\Handler, laravel's all exception will pass through App\Exceptions\Handler before it render or report, even those you didn't catched.
You can handle exception like this:
$this->renderable(function (Throwable $e, $request) {
return new ExceptionResource($e);
});
ExceptionResource is an API resources from Illuminate\Http\Resources\Json\JsonResource which you can see document from here: https://laravel.com/docs/9.x/eloquent-resources.
And I prefer to check enviroment from app helper not env or config instead, the helper will return a bool, that I think readability is better.
Example in ExceptionResource, showing error if environment is local dev or unit test:
public function toArray($request)
{
if(app()->environment(['local', 'testing']))
$error = $this->resource->getMessage();
else
$error = 'Server Error';
return [
'error' => $error,
'message' => 'Something went wrong'
];
}
You can catch it inside App\Exceptions\Handler
Use the render function to intercept all exceptions with json as response (for api only)
And within add check which env is it currently being deployed.
This is the only way to make sure your error overwrite will work only in production.
Also use config(app.env) === production is best practice instead of using env.
Now you can throw any kind (custom) exceptions , and all can be "filtered" by this code.
Extra note about config as best place as pointed out by #dbf (thanks mate)
This is based on how mr Otwell himself use config() instead of env(), in core and all of his laravel related oackages
dont forget also that config is cached, meaning faster in load time compare to raw env,
have a look at this link https://github.com/alexeymezenin/laravel-best-practices#do-not-get-data-from-the-env-file-directly ... As one of many recommended way to use config() instead of env()
I have Symfony application and I use FOSRestBundle with AngularJS. My Symfony application haven't any views.
I want to show in AngularJS messages about information received from server, with ngToast module.
If I create something or update it it's easy to show. But If server throw some exception? For example Angular client trying get item with wrong ID or this user have no acces to do this action? In this case server will throw exception, but I want to show appropriate message.
Can symfony catch this exception and for example translate it to Response object?
For example - if I have no access exception symfony should catch it and make something like this:
return new Response(400, "You don't have permission to acces this route");
and Angular will get:
{
"code": 400,
"message": "You don't have permission to acces this route"
}
Is it possible? How should I do it? Maybe I should do it other way.
I would suggest to go for more FOSRestBundle approach, which is to configure the Exceptions and if should or not show the messages:
fos_rest:
exception:
enabled: true
codes:
'Symfony\Component\Routing\Exception\ResourceNotFoundException': HTTP_FORBIDDEN
messages:
'Symfony\Component\Routing\Exception\ResourceNotFoundException': true
Lets say you have a custom AccessDeniedException for a certain action, you can create the exception and then put it in the configuration.
<?php
class YouCantSummonUndeadsException extends \LogicException
{
}
Wherever you throw it:
<?php
throw new YouCantSummonUndeadsException('Denied!');
You can configure it:
codes:
'My\Custom\YouCantSummonUndeadsException': HTTP_FORBIDDEN
messages:
'My\Custom\YouCantSummonUndeadsException': true
And get a result like:
{
"code": 403,
"message": "Denied!"
}
I hope this makes it clearer!
Yes of course this is possible. I suggest you implement a simple Exception listener. And make all your exception classes extend a BaseException or implement a BaseException, so you will know which exceptions are from "your" code.
class ExceptionListener
{
public function onKernelException(GetResponseForExceptionEvent $event)
{
// You get the exception object from the received event
$exception = $event->getException();
// Do your stuff to create the response, for example response status code can be exception code, and exception message can be in the body or serialized in json/xml.
$event->setResponse($response);
return;
}
}
Register it in the container:
<service id="your_app.listener.exception" class="App\ExceptionListener">
<tag name="kernel.event_listener" event="kernel.exception" method="onKernelException" />
</service>
I have a repository that throws an exception if it can't find a record in the database. Rather than redirect to another page I just want to display a warning alert as the record is not critical to the page but is an "exceptional event".
It's probably best to demonstrate with code:
// FxRateRepositoy
public function getRate(/** args **/)
{
$rate = FxRate::where(.... //query to get the rate
if (!rate)
throw new NonExistentCurrencyException(//message);
return $rate;
}
In my start/global.php I have a handler:
App::error(function(NonExistentCurrencyException $e)
{
Session::flash('alert', $e->getMessage());
return \\ ??
});
What to return? I must return a response or the exception continue uncaught. I want to continue to the intended page but with the alert flashed in the session. Is this possible without having to use try catch blocks in every place this method is called?
Ass an additional question, assuming this exception may be thrown multiple times in one request, what's the best way to accumulate alert messages and display them? I'm thinking something akin to the validation messageBag. Can I just use the global $errors variable or should I create a new, specific messagebag for this purpose?
The problem is that if you return nothing from App::error Laravel will display it's default error page. On the other side you can't return a response because you don't know what response it should be in the error handler.
I suggest you handle it in the controller itself.
You can catch the exception there and flash the message or don't throw an exception at all:
$rate = FxRate::where(.... //query to get the rate
if (!rate){
Session::flash('alert', 'Whoops');
}
Also the findOrFail() and firstOrFail methods might be of use. They throw an ModelNotFoundException if the query yields no results:
try {
$rate = FxRate::where(....)->firstOrFail()
// and so on
} catch (Illuminate\Database\Eloquent\ModelNotFoundException $e){
Session::flash('alert', 'Whoops');
}
As for a messages system, take a look at the laracasts/flash package
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 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.