2
votes

I am trying to build a React application backed with Laravel API, so essentially using a wildcard route for client-side routing, and then simply an API route group to process data.

This is my routes/web.php file:

<?php

/*
|--------------------------------------------------------------------------
| Web Routes
|--------------------------------------------------------------------------
|
| Here is where you can register web routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| contains the "web" middleware group. Now create something great!
|
*/

Route::get('/payment/redirect/{orderId}', ['as' => 'mollie.redirect', 'uses' => 'Controller@index']);
Route::get('/{any}', ['as' => 'index', 'uses' => 'Controller@index'])->where('any', '.*');

And this is my routes/api.php file:

<?php

use Illuminate\Http\Request;

/*
|--------------------------------------------------------------------------
| API Routes
|--------------------------------------------------------------------------
|
| Here is where you can register API routes for your application. These
| routes are loaded by the RouteServiceProvider within a group which
| is assigned the "api" middleware group. Enjoy building your API!
|
*/

Route::post('/orders', ['as' => 'orders.store', 'uses' => 'OrdersController@store']);
Route::post('/payment/webhook', ['as' => 'mollie.webhook', 'uses' => 'OrdersController@webhook']);

which results in:

enter image description here

But, whenever I try to make a request at POST api/orders this is what I get in Postman:

enter image description here

Which is what Controller@index should respond, not OrdersController@store, which should be a JSON response.

This is my OrdersController code:

<?php 

namespace Http\Controllers;

use Customer;
use Http\Requests\OrderCreateRequest;
use Order;
use Product;
use Services\CountryDetector;
use Services\LanguageService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Route;

class OrdersController extends Controller
{
    const ERROR_PRODUCT_COUNTRY_UNAVAIALBLE = 'errors.products.country_unavailable';

    public function store(OrderCreateRequest $request, LanguageService $language, Order $orders, Customer $customers, Product $products)
    {
        $customer = $customers->firstOrCreate(['email' => $request->input('customer.email')], [
            'name' => $request->input('customer.fullname'),
            'email' => $request->input('customer.email'),
            'phone' => $request->input('customer.phone'),
            'country' => $language->getCurrentCountry(),
            'company_name' => $request->input('customer.company_name'),
            'city' => $request->input('customer.city'),
            'optin_newsletter' => $request->input('customer.newsletter')
        ]);

        $product = $products->find($request->input('cart.product_id'));

        $pricing = $product->getCountryPrice($language->getCurrentCountry());

        if (! $pricing)
        {
            return response()->json([
                'error' => trans(self::ERROR_PRODUCT_COUNTRY_UNAVAILABLE, ['productName' => $product->name])
            ], 422);
        }

        $order = $orders->create([
            'customer_id'        => $customer->id,
            'product_id'         => $product->id,
            'product_flavor'     => $request->input('cart.flavor'),
            'amount'             => $pricing->amount,
            'vat_amount'         => $pricing->vat_amount,
            'currency'           => $pricing->currency,
            'carehome_selection' => $request->input('carehome.custom'),
            'carehome_name'      => $request->input('carehome.name'),
            'carehome_type'      => $request->input('carehome.type'),
            'carehome_address'   => $request->input('carehome.address'),
            'carehome_city'      => $request->input('carehome.city'),
            'carehome_notes'     => $request->input('carehome.notes'),
            'custom_message'     => $request->input('gifting_options.message'),
            'is_anonymous'       => $request->input('gifting_options.anonymous'),
            'wants_certificate'  => $request->input('gifting_options.certificate'),
            'status'             => Order::STATUS_PENDING,
            'type'               => $request->input('payment_type')
        ]);

        $mollie = $order->getOrCreateMollie();

        return response()->json([
            'mollie_redirect' => $mollie->getCheckoutUrl()
        ]);
    }
}

Also, if I try to remove the API routes temporarily, and still attempt to access them, I weirdly enough get a 404, which means Laravel is able to detect the route, but it's using the wrong Controller's response.

How do I fix this?

4
Could you post part of your OrdersController code? Because you were able to get the 404, I'm assuming you restarted the web service after changing the Routes. I have had to run php artisan clear:cache or php artisan route:cache before to clear.Daniel Gale
Done @DanielGaleGiamPy
I also cleared any kind of cache, result does not change.GiamPy
You are calling the Url in the response for Controllers@Index ` return response()->json([ 'mollie_redirect' => $mollie->getCheckoutUrl() ]);` Does it have anything to do with that?Daniel Gale
That is in the OrdersController, not the Controller@index. That one is just return view('index') essentially.GiamPy

4 Answers

2
votes

I've tried setting up the two headers like suggested above but it wasn't working for me. Instead I've modified the route regex to match urls that doesn't begin with api and it worked:

Route::view('/{any?}', 'app')
->where('any', '^(?!api).*');
1
votes

Similar to what @Marcin Nabialek said, it was an issue with one of the headers that should have supposedly been sent with the request. However it was not the Content-Type but rather Accept.

You must use Accept: application/json in order to receive a JSON response for APIs, at least this is how it behaves in Laravel 5.7.6.

0
votes

First of all - when you remove the api routes, there is no route for POST method (because your wildcard "catch-all" route is only for GET or HEAD requests). This is why you get a HTTP 404 - no route found for this request.

If you add the api routes as described in question - the response provided seems to be a raw twig view (possibly a layout). I assume you triple checked that there is no way that your OrdersController would respond that way - if not, try adding return '{}'; as the first line of your controller and see what happens.

Regardless - it may have something to do with the request type (you set the request header to application/x-www-form-urlencoded) - the RouteServiceProvider or perhaps the api middleware have something to do with it. Try setting the request header to application/json for instance, and dig into the RouteServiceProvider and api middleware.

0
votes

What I think is going here is a mix of 2 things:

  • You don't set here Content-Type to application/json so app "thinks" this normal form is sent
  • You use custom validation class OrderCreateRequest and probably validation failes. That's why if you put dd('test'); into your controller method it won't be executed at all

In case validation failes validator throws ValidationException exception and implementation what happens in such case looks like this:

protected function convertValidationExceptionToResponse(ValidationException $e, $request)
{
    if ($e->response) {
        return $e->response;
    }

    return $request->expectsJson()
                ? $this->invalidJson($request, $e)
                : $this->invalid($request, $e);
}

So in case it's AJAX request or Content-Type is set to application/json (more or less), then it will return JSON response with failed validation otherwise redirection is made.