Laravel 7

Shopping : la commande

Notre boutique sait afficher des produits et gérer un panier. Dans cet article on va aborder la commande.

Vous pouvez télécharger un ZIP du projet ici.

Contrôleur et route

On crée un contrôleur pour la création d’une commande :

php artisan make:controller OrderController --resource

On ne va conserver que les méthodes create et store.

Pour les routes on veut que ce soit seulement accessible aux utilisateurs authentifiés :

// Utilisateur authentifié
Route::middleware('auth')->group(function () {
  // Commandes
  Route::prefix('commandes')->group(function () {
      Route::resource('/', 'OrderController')->names([
          'create' => 'commandes.create',
          'store' => 'commandes.store',
      ])->only(['create', 'store']);
  });
});

On est obligé de spécifier les noms parce que celui de la ressource est vide.

J’espère que le mélange anglais/français dans les appellations ne vous dérange pas parce que je mélange souvent les deux au gré du code…

Par contre je m’attache à créer toutes les urls en français pour cette application et là ce n’est pas le cas !

Dans la méthode boot de AppServiceProvider on va ajouter ce code pour la traduction automatique :

use Illuminate\Support\Facades\Route;

...

Route::resourceVerbs([
    'edit' => 'modification',
    'create' => 'creation',
]);

Maintenant ça va mieux :

On peut maintenant ajouter le lien dans la vue views/cart/index.blade.php :

<a href="{{ route('commandes.create') }}">Commander</a>

D’autre part lorsque l’utilisateur n’est pas authentifié et qu’il clique sur le bouton « Commander » dans le panier il est renvoyé sur la page de connexion. Il est judicieux alors de lui afficher un message explicatif. On complète la vue views/auth/loging.blade.php :

<form  method="POST" action="{{ route('login') }}">
  @if(url()->previous() === route('panier.index'))
    <div class="col s12">
      <div class="card purple darken-3">
        <div class="card-content white-text center-align">
          Vous devez être connecté pour passer une commande, si vous n'avez pas encore de compte vous pouvez en créer un en utilisant le lien sous ce formulaire.
        </div>
      </div>
    </div>
  @endif

Un service pour les frais d’envoi

Il va falloir calculer plusieurs fois les frais d’envoi en plusieurs emplacements, on va créer un service pour ça :

Avec ce code :

<?php

namespace App\Services;
use App\Models\Range;
use Cart;

class Shipping
{
    public function compute($country_id)
    {
        $items = Cart::getContent();

        $weight = $items->sum(function ($item) {
            return $item->quantity * $item->model->weight;
        });

        $range = Range::orderBy('max')->where('max', '>=', $weight)->first();
        
        return $range->countries()->where('countries.id', $country_id)->first()->pivot->price;
    }
}

On commence par récupérer le contenu du panier actif.

Ensuite on calcule le poids total des produits en utilisant la méthode sum de la collection.

La plage est ensuite définie à partir du poids.

Enfin le tarif est déterminé en fonction du pays prévu pour l’expédition.

Les données

Dans le contrôleur OrderController on va récupérer toutes les données utiles et ouvrir la vue :

public function create(Request $request, Shipping $ship)
{        
    $addresses = $request->user()->addresses()->get();

    if($addresses->isEmpty()) {
        // Là il faudra renvoyer l'utilisateur sur son compte quand on l'aura créé
    }

    $country_id = $addresses->first()->country_id;

    $shipping = $ship->compute($country_id);
    
    $content = Cart::getContent();

    $total = Cart::getTotal();

    $tax = Country::findOrFail($country_id)->tax;

    return view('command.index', compact('addresses', 'shipping', 'content', 'total', 'tax'));
}

Il nous faut toutes ces informations :

  • les adresses de l’utilisateur
  • l’identifiant du pays de la première adresse pour initialiser les frais de ports et la TVA
  • les frais d’expédition
  • les contenu du panier
  • le coût total du panier
  • la valeur de ta TVA

On va aussi partager les données de la boutique dans AppServiceProvider :

use App\Models\Shop;

...

public function boot()
{
    View::share('shop', Shop::firstOrFail());

Les vues

On va créer 3 vues pour la commande :

Les adresses

On aura pas mal de vues pour le compte de l’utilisateur, on prépare le terrain avec une vue partielle pour afficher une adresse :

Avec ce code :

<ul class="list-unstyled">
  @isset($address->name)
    <li>{{ "$address->civility $address->name $address->firstname" }}</li>
  @endif
  @if($address->company)
    <li>{{ $address->company }}</li>
  @endif            
  <li>{{ $address->address }}</li>
  @if($address->addressbis)
    <li>{{ $address->addressbis }}</li>
  @endif
  @if($address->bp)
    <li>{{ $address->bp }}</li>
  @endif
  <li>{{ "$address->postal $address->city" }}</li>
  <li>{{ $address->country->name }}</li>
  <li>{{ $address->phone }}</li>
</ul>

Maintenant on crée une vue pour afficher les adresses dans le formulaire de création de commande (partials/addresses.blade.php) qui utilise la vue partielle créée ci-dessus :

@if(!$addresses->count())
  <p>Vous n'avez pas encore créé d'adresse dans votre compte, vous devez en créer au moins une.</p>
  <br>
  <p><a href="#" style="width: 100%" class="btn waves-effect waves-light">Je crée une adresse</a>
  </p>
@else
  <div class="row">
    @foreach($addresses as $address)
      <div class="col m12 l6">
        <div class="card">                          
          <div class="card-content address">
            <p><label><input name="{{ $name }}" value="{{ $address->id }}" type="radio" @if($loop->first) checked @endif><span></span></label></p>
            @include('account.addresses.partials.address')
          </div>
        </div>
      </div>
    @endforeach
  </div>                  
@endif

Le détail de la commande

On crée aussi une vue partielle pour afficher le détail de la commande (partials/detail.blade.php) :

<h5>Détails de ma commande</h5>
@foreach ($content as $item)
  <hr><br>
  <div class="row">
    <div class="col m6 s12">
      {{ $item->name }} ({{ $item->quantity }} @if($item->quantity > 1) exemplaires) @else exemplaire) @endif
    </div>
    <div class="col m6 s12"><strong>{{ number_format($item->total_price_gross ?? ($tax > 0 ? $item->price : $item->price / 1.2) * $item->quantity, 2, ',', ' ') }} €</strong></div>
  </div>
@endforeach
<hr><br>
<div class="row" style="background-color: lightgrey">
  <div class="col s6">
    Livraison en Colissimo
  </div>
  <div class="col s6">
    <strong>{{ number_format($shipping, 2, ',', ' ') }} €</strong>
  </div>
</div>
@if($tax > 0)
  <div class="row" style="background-color: lightgrey">
    <div class="col s6">
      TVA à {{ $tax * 100 }}%
    </div>
    <div class="col s6">
      <strong>{{ number_format($total / (1 + $tax) * $tax, 2, ',', ' ') }} €</strong>
    </div>
  </div>
@endif
<div class="row" style="background-color: lightgrey">
  <div class="col s6">
    Total TTC
  </div>
  <div class="col s6">
    <strong>{{ number_format($total + $shipping, 2, ',', ' ') }} €</strong>
  </div>
</div>

Le formulaire

Enfin on a la vue du formulaire (command/index.blade.php) :

@extends('layouts.app')

@section('content')
<div class="container">
  <form id="form" action="{{ route('commandes.store') }}" method="POST">
    @csrf      
    @if(session()->has('message'))
      <h5 class="center-align red-text">{{ session('message') }}</h5>
      <br>
    @endif
    <ul class="collection with-header">

      <li class="collection-header"><h4>Ma commande</h4></li>

      <div id="wrapper">

      <li class="collection-item">
        <h5>Adresse de facturation <span id="solo">et de livraison</span></h5>
        @include('command.partials.addresses', ['name' => 'facturation'])
        <div class="row">
          <div class="col s12">
            <a href="#" class="btn" style="width: 100%"><i class="material-icons left">location_on</i>Gérer mes Adresses</a>
          </div>
        </div>
        <div class="row">
          <div class="col s12">
            <label>
              <input id="different" name="different" type="checkbox" @if($addresses->count() === 1)  disabled="disabled" @endif>
              <span>
                @if($addresses->count() === 1)
                  Vous n'avez qu'une adresse enregistrée, si vous voulez une adresse différente pour la livraison <a href="{{ route('adresses.create') }}">vous pouvez en créer une autre</a>.
                @else
                  Mon adresse de livraison est différente de mon adresse de facturation
                @endif
              </span>
            </label>
          </div>
        </div>
      </li>

      <li id="liLivraison" class="collection-item hide">
        <h5>Adresse de livraison</h5>
        @include('command.partials.addresses', ['name' => 'livraison'])      
      </li>

      <li class="collection-item">
        <h5>Mode de livraison</h5>
        <p>
          <label>
            <input name="expedition" type="radio" value="colissimo" checked>
            <span>Colissimo</span>
          </label>
        </p>
        <p>
          <label>
            <input name="expedition" type="radio" value="retrait">
            <span>Retrait sur place</span>
          </label>
        </p>
      </li>

      <li class="collection-item">
        <h5>Paiement</h5>
        @if($shop->card)
          <p>
            <label>
              <input class="payment" name="payment" type="radio" value="carte" checked>
              <span>Carte bancaire</span>
            </label>
          </p>
          <p style="margin-left: 40px" class="hide">
            Vous devrez renseigner un formulaire de paiement sur la page de confirmation de cette commande.
          </p>
        @endif
        @if($shop->mandat)
          <p>
            <label>
              <input class="payment" name="payment" type="radio" value="mandat">
              <span>Mandat administratif</span>
            </label>
          </p>
          <p style="margin-left: 40px" class="hide">
            Envoyez un bon de commande avec la mention "Bon pour accord". Votre commande sera expédiée dès réception de ce bon de commande. N'oubliez pas de préciser la référence de la commande dans votre bon.
          </p>
        @endif
        @if($shop->transfer)         
          <p>
            <label>
              <input class="payment" name="payment" type="radio" value="virement">
              <span>Virement bancaire</span>
            </label>
          </p>
          <p style="margin-left: 40px" class="hide">
            Il vous faudra transférer le montant de la commande sur notre compte bancaire. Vous recevrez votre confirmation de commande comprenant nos coordonnées bancaires et le numéro de commande. Les biens seront mis de côté 30 jours pour vous et nous traiterons votre commande dès la réception du paiement. 
          </p>
        @endif
        @if($shop->check)
          <p>
            <label>
              <input class="payment" name="payment" type="radio" value="cheque">
              <span>Chèque</span>
            </label>
          </p>
          <p style="margin-left: 40px" class="hide">
            Il vous faudra nous envoyer un chèque du montant de la commande. Vous recevrez votre confirmation de commande comprenant nos coordonnées bancaires et le numéro de commande. Les biens seront mis de côté 30 jours pour vous et nous traiterons votre commande dès la réception du paiement. 
          </p>
        @endif
      </li>

      <li id="detail" class="collection-item">
        @include('command.partials.detail')      
      </li> 
        
      <li class="collection-item">
        <h5>Veuillez vérifier votre commande avant le paiement !</h5>
        <br>
        <div class="row">
          <div class="col s12">
            <label>
              <input id="ok" name="ok" type="checkbox">
              <span>J'ai lu <a href="#" target="_blank">les conditions générales de vente et les conditions d'annulation</a> et j'y adhère sans réserve. </span>
            </label>
          </div>
        </div>
      </li>

      </div>

      <div id="loader" class="hide">
        <div class="loader"></div>
      </div>

    </ul>
    <div class="row">
      <div class="col s12">
        <button id="commande" type="submit" class="btn disabled" style="width: 100%">Commande avec obligation de paiement</button>
      </div>
    </div>
  </form>
</div>
@endsection

@section('javascript')
  <script>

    const changePayment = () => {
      document.querySelectorAll('.payment').forEach(payment => {
        const list = payment.parentNode.parentNode.nextElementSibling.classList;
        if(payment.checked) {
          list.remove('hide');
        } else {
          list.add('hide');
        } 
      });      
    };

    const getDetails = async () => {
      document.querySelector('#wrapper').classList.add('hide');
      document.querySelector('#loader').classList.remove('hide');
      const response = await fetch('#', { 
        method: 'POST',
        headers: { 
          'X-CSRF-TOKEN': '{{ csrf_token() }}', 
          'Content-Type': 'application/json' 
        },
        body: JSON.stringify({ 
          facturation: document.querySelector('input[type=radio][name=facturation]:checked').value, 
          livraison: document.querySelector('input[type=radio][name=livraison]:checked').value,
          different: document.querySelector('#different').checked,
          pick: document.querySelector('input[type=radio][name=expedition]:checked').value == 'retrait'
        })
      });
      const data = await response.json();
      document.querySelector('#detail').innerHTML = data.view;
      document.querySelector('#loader').classList.add('hide');
      document.querySelector('#wrapper').classList.remove('hide');      
    };

    document.addEventListener('DOMContentLoaded', () => {
      
      document.querySelector('#different').checked = false;
      
      document.querySelector('#ok').checked = false;
      
      document.querySelector('#different').addEventListener('change', () => {
        document.querySelector('#liLivraison').classList.toggle('hide');
        document.querySelector('#solo').classList.toggle('hide');
        getDetails();
      });

      document.querySelectorAll('.payment').forEach(payment => {
        payment.addEventListener('change', () => changePayment());
      });
      
      document.querySelector('#ok').addEventListener('change', () => document.querySelector('#commande').classList.toggle('disabled'));
      
      document.querySelectorAll('input[type=radio][name=facturation]').forEach(input => {
        input.addEventListener('change', () => getDetails());
      });

      document.querySelectorAll('input[type=radio][name=livraison]').forEach(input => {
        input.addEventListener('change', () => getDetails());
      });

      document.querySelectorAll('input[type=radio][name=expedition]').forEach(input => {
        input.addEventListener('change', () => {
          if(document.querySelector('input[type=radio][name=expedition][value=retrait]').checked) {            
            if(document.querySelector('#different').checked) {
              document.querySelector('#different').checked = false;              
              document.querySelector('#liLivraison').classList.toggle('hide');
            }
            document.querySelector('#different').disabled = true;  
            document.querySelector('#solo').classList.add('hide');          
          }
          if(document.querySelector('input[type=radio][name=expedition][value=colissimo]').checked) {            
            document.querySelector('#different').disabled = false;
            if(document.querySelector('#different').checked) {
              document.querySelector('#solo').classList.add('hide');
            } else {
              document.querySelector('#solo').classList.remove('hide');
            }          
          } 
          getDetails()
        });
      });

      document.querySelector('#form').addEventListener('submit', () => {
        const button = document.querySelector('#commande');
        button.classList.toggle('disabled');
        button.textContent = 'Confirmation de la commande en cours, ne fermez pas cette fenêtre...'
      });

      changePayment();
    });

  </script>
@endsection

C’est un peu chargé forcément parce qu’il y a pas mal d’informations et que j’ai opté pour un affichage global sur une seule page.

Dans la partie supérieure on a les adresses :

On pourra préciser l’adresse de livraison différente. On verra ça plus loin, la case à cocher est en place. Le bouton de gestion des adresses n’est pas actif non plus parce qu’on n’a pas créé le compte client.

Au-dessous on a le mode de livraison :

On a deux options : livraison en Colissimo ou retrait sur place.

Ensuite on a le mode de paiement :

On peut déjà cliquer pour changer d’option, ça affiche à chaque fois un petit texte explicatif.

Vient ensuite le détail de la commande qui doit être calculé dynamiquement selon les options choisies :

Enfin dans la partie inférieure la case à cocher pour confirmer la lecture des conditions générales de vente et le bouton d’envoi actif si la case est cochée :

Calcul du détail

Le détail de la commande change si :

  • l’adresse d’expédition change (frais de port et éventuellement TVA différente)
  • le mode de livraison change (pas de frais de port en cas de retrait sur place)

Il faut donc prévoir un calcul dynamique. Le Javascript est déjà en place dans la vue, je n’ai juste pas précisé la route pour éviter une erreur d’exécution.

On crée un contrôleur pour ce calcul :

php artisan make:controller DetailsController

On n’aura qu’une fonction :

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use App\Services\Shipping;
use App\Models\ { Address, Country };
use Cart;

class DetailsController extends Controller
{
    /**
     * Show the order details
     *
     * @param  \Illuminate\Http\Request  $request
     * @param  \App\Services\Shipping  $shipping
     * @return \Illuminate\Http\Response
     */
    public function __invoke(Request $request, Shipping $ship)
    {
        // Facturation
        $country_facturation = Address::findOrFail($request->facturation)->country;

        // Livraison
        $country_livraison = $request->different ? Address::findOrFail($request->livraison)->country : $country_facturation;
        $shipping = $request->pick ? 0 : $ship->compute($country_livraison->id);

        // TVA
        $tvaBase = Country::whereName('France')->first()->tax;
        $tax = $request->pick ? $tvaBase : $country_livraison->tax; 
        
        // Panier
        $content = Cart::getContent();
        $total = $tax > 0 ? Cart::getTotal() : Cart::getTotal() / (1 + $tvaBase);              

        return response()->json([ 
            'view' => view('command.partials.detail', compact('shipping', 'content', 'total', 'tax'))->render(), 
        ]);
    }
}

On génère et renvoie le HTML pour la partie du détail.

On ajoute la route :

// Utilisateur authentifié
Route::middleware('auth')->group(function () {
  // Commandes
  Route::prefix('commandes')->group(function () {
      Route::name('commandes.details')->post('details', 'DetailsController');

Il ne reste plus qu’à ajouter la route dans la vue command/index.blade.php au niveau du Javascript :

const response = await fetch('{{ route("commandes.details") }}', {

Fonctionnement

Maintenant tout doit fonctionner dans le formulaire de commande.

On peut préciser une adresse de livraison différente (s’il y a au moins deux adresses) :

A chaque changement l’affichage total du formulaire est remplacé par une image d’attente animée (la même qu’on a déjà utilisée précédemment tant qu’à faire) jusqu’à réponse du serveur. Ca évite que l’utilisateur ne clique encore ailleurs et lui indique que le traitement de son action est en cours.

En cas de changement de mode de livraison on a aussi un recalcul et une mise à jour. D’autre part si on choisit le retrait sur place le choix éventuel d’une adresse de livraison différente est supprimé et la case à cocher correspondante inactivée.

On va aussi prévoir à la fin du Javascript le déclenchement systématique du calcul du détail en cas de rechargement de la page alors que des options ont été changées :

  changePayment();
  getDetails();
});

Je ne rentre pas dans le détail du code pour ne pas trop alourdir cet article.

La création de la commande

Maintenant qu’on a notre formulaire il ne reste plus qu’à enregistrer la commande lors de la soumission. On code la méthode create du contrôleur OrderController :

use App\Models\ { Address, Country, State, Shop, Product, User };

...

/**
 * Store a newly created resource in storage.
 *
 * @param  \Illuminate\Http\Request  $request
 * @param  \App\Services\Shipping  $ship
 * @return \Illuminate\Http\Response
 */
public function store(Request $request, Shipping $ship)
{
    // Vérification du stock
    $items = Cart::getContent();
    foreach($items as $row) {
        $product = Product::findOrFail($row->id);
        if($product->quantity < $row->quantity) {
            $request->session()->flash('message', 'Nous sommes désolés mais le produit "' . $row->name . '" ne dispose pas d\'un stock suffisant pour satisfaire votre demande. Il ne nous reste plus que ' . $product->quantity . ' exemplaires disponibles.');
            return back();
        }
    }

    // Client
    $user = $request->user();

    // Facturation
    $address_facturation = Address::with('country')->findOrFail($request->facturation);

    // Livraison
    $address_livraison = $request->different ? Address::with('country')->findOrFail($request->livraison) : $address_facturation;
    $shipping = $request->expedition === 'colissimo' ? $ship->compute($address_livraison->country->id) : 0;

    // TVA
    $tvaBase = Country::whereName('France')->first()->tax;
    $tax = $request->expedition === 'colissimo' ? $address_livraison->country->tax : $tvaBase;

    // Enregistrement commande
    $order = $user->orders()->create([
        'reference' => strtoupper(Str::random(8)),
        'shipping' => $shipping,
        'tax' => $tax,
        'total' => $tax > 0 ? Cart::getTotal() : Cart::getTotal() / (1 + $tvaBase),
        'payment' => $request->payment,
        'pick' => $request->expedition === 'retrait',
        'state_id' => State::whereSlug($request->payment)->first()->id,
    ]);

    // Enregistrement adresse de facturation
    $order->adresses()->create($address_facturation->toArray());

    // Enregistrement éventuel adresse de livraison
    if($request->different) {
        $address_livraison->facturation = false;
        $order->adresses()->create($address_livraison->toArray());
    }

    // Enregistrement des produits
    foreach($items as $row) {
        $order->products()->create(
            [
                'name' => $row->name,
                'total_price_gross' => ($tax > 0 ? $row->price : $row->price / (1 + $tvaBase)) * $row->quantity,
                'quantity' => $row->quantity,
            ]
        );        
        // Mise à jour du stock
        $product = Product::findOrFail($row->id);
        $product->quantity -= $row->quantity;
        $product->save();
        // Alerte stock
        if($product->quantity <= $product->quantity_alert) {
            // Notifications à prévoir pour les administrateurs 
        }
    }

    // On vide le panier
    Cart::clear();
    Cart::session($request->user())->clear();

    // Notifications à prévoir pour les administrateurs et l'utilisateur

    //return redirect(route('commandes.confirmation', $order->id));
}

Je viens de me rendre compte que je n’avais pas prévu encore la propriété $fillable dans le modèle Order, on va l’ajouter :

protected $fillable = [
    'shipping', 'tax', 'user_id', 'state_id', 'payment', 'reference', 'pick', 'total',
];

On accomplit un certain nombre d’actions dans le contrôleur :

  • vérification du stock, si c’est insuffisant on retourne le formulaire avec une alerte :

  • on récupère l’adresse de facturation
  • on détermine l’adresse de livraison et on calcule les frais de port
  • on détermine le taux TVA
  • on enregistre la commande avec :
    • une référence générée aléatoirement
    • les frais de port
    • le taux de TVA
    • le coût total
    • le mode de paiement
    • le mode de livraison
    • l’état selon le mode de paiement
  • on enregistre l’adresse de facturation
  • on enregistre une éventuelle adresse de livraison
  • on enregistre les produits
  • on met à jour le stock
  • on vide le panier
  • on verra plus tard les notifications (alerte stock, commande créée pour les administrateurs et le client)

Chez moi ça fonctionne, la commande :

Les adresses :

Les produits :

Au retour on renverra une vue de confirmation de commande.

Conclusion

On a avancé dans la développement de la boutique. On peut maintenant passer une commande avec toutes les options disponibles. Dans le prochain article on verra la confirmation de la commande et le paiement par carte bancaire avec Stripe.

 

Print Friendly, PDF & Email

57 commentaires

  • Boy

    Bonjour best momo merci pour ce tuto, j’ai une préocupation, quand je clique sur le bouton commander je recois cette erreur:

    Call to a member function communes() on null

    j’ai essayé de parcourir mais je n’ai pas trouver cette, svp votre aide merci

      • Boy

        Oui desolé best momo, j’avais mal vu c’était countries(), j’ai corrigé mais il me genère encore une autre erreur au niveau de la classe chipping:

        return $range->countries()->where(‘countries.id’, $country_id)->first()->pivot->price; on me dit que :

        Attempt to read property « pivot » on null ça peut être quelle erreur merci

  • Boy

    Bonjour best momo je suis buté à un grand problème qui me tarde à avancer, c’est juste que toute les requettes javascripte et ajax que j’essaie d’exécuter cela n’est pas pris en charge donc ça me donne aucun résultat, j’ai même suprimer toutes les caches et les cookies mes rien ne marche : voici quand meme l’erreur que j’ai pu voir dans les outis de navigation:

    Refused to apply style from ‘https://cdn.jsdelivr.net/npm/bootstrap@5.3.0-alpha3/dist/js/bootstrap.bundle.min.js’ because its MIME type (‘application/javascript’) is not a supported stylesheet MIME type, and strict MIME checking is enabled.

    Vraiment je me demande qu’est ce qui ne va pas si vous pouver m’aider merci

  • Gersoni

    Bonjour best momo merci pour ce tutoriel qui m’a beaucoup aidé,
    J’ai rencontré une erreur si je clic sur le bouton commande avec obligation de paiement pour enregistrer la commande dans la base de donnée je reçois cette erreur:

    The GET methode is not supported for this route. Supported methodes: POST

    • bestmomo

      Bonjour,

      Le bouton de commande se contente de faire la soumission du formulaire qui est délaré avec une méthode POST et donc tu devrais envoyer du POST. Il faudrait vérifier ce qui se passe en ligne avec les outils de développement du navigateur.

      • Gersoni

        bonsoir bestmomo, l’erreur persiste toujout j’ai bien tous mes routes dont voici:
        // Utilisateur authentifié
        Route::middleware('auth')->group(function () {
        // Commandes
        Route::prefix('commandes')->group(function () {
        Route::name('commandes.details')->post('details', 'DetailsController');
        Route::name('commandes.confirmation')->get('confirmation/{order}', 'OrdersController@confirmation');
        Route::resource('/', 'OrderController')->names([
        'create' => 'commandes.create',
        'store' => 'commandes.store',
        ])->only(['create', 'store']);
        });
        });

        dans le formulaire de ma vue command.index j’ai bien le formulaire avec la methode POST :

        @csrf

        c’est quand je clik sur le bouton commande avec obligation de paiement j’ai reçois cette erreur

        Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException
        The GET method is not supported for this route. Supported methods: POST.

        Illuminate\Routing\AbstractRouteCollection::methodNotAllowed
        E:\MES PROJECTS\sho-ecommerce\vendor\laravel\framework\src\Illuminate\Routing\AbstractRouteCollection.php:117

        je ne sais pas d’où provient cette erreur

        • bestmomo

          Salut,

          Je renouvelle ma précédente réponse. Regarde avec les outils développeur du navigateur la requête qui part au serveur si c’est effectivement du GET. Si c’est le cas contrôle la syntaxe du formulaire parce que la méthode déclarée est POST.

    • softcode

      Bonjour best momo, j’évolu bon sur le projet du tout début mais j’ai rencontré un petit soucis quand je clik sur l’un de radio Button pour changer l’adresse ou mode de livraison, bien-sûr l’animation réapparaîs pour effectuer le traitement mais cela ne cesse de tourné en boucle cela est dû à quoi merci pour votre aide

  • Nanourgo

    Bonsoir !!
    Je suis un débutant au cours de Laravel et votre tuto m’aide vraiment. Cependant je rencontre un problème au niveau de la commande. Lorsque j’accède à mon panier et que je clic sur le bouton COMMANDER, j’ai cette érreur
    Trying to get property ‘pivot’ of non-object
    que je n’arrive pas à résoudre malgré mes recherches.

  • Thibaut

    Bjr @BESTMOMO, merci pour tes tutos tjr aussi explicite que pertinent , je veux integre les frais de livraison dans mon ecommerce. j’utilise laravel Voyager comme systeme de backend, voici a quoi ressemble ma table:https://app.dbdesigner.id?action=open&shared=public&uuid=f9055498-9f44-4347-8535-98a870e7b3a2.
    les reste est identique a ton tuto, et dans mon checkoutController jai:
    if ($request->has(‘livInterne’)){
    $shipping = $request->input(‘livInterne’);
    }elseif ($request->has(‘livExterne’)){
    $country_id = $request->country_id;
    $shipping = $ship->compute($country_id);
    dd($shipping);
    }
    mais j’ai une erreur:ErrorException
    Trying to get property ‘pivot’ of non-object. j’ai deja chercher fatigué merci d’avance!

    • bestmomo

      Salut,

      Ça doit être dans un cas où le poids des produits dépasse ce qui est prévu dans la table ranges. Je suis aussi tombé là dessus et j’ai changé un peu le code dans la classe Shipping pour la méthode compute :
      return $range ? $range->countries()->where('countries.id', $country_id)->first()->pivot->price : false;
      Ensuite je fais un test dans le contrôleur si ça retourne false.

  • guru

    Salut bestmomo tout d’abord félicitation pour le tuto. Pour ma part, au clic du bouton commander, il m’affiche cette erreur:  » Trying to get property ‘country_id’ of non-object »
    que puisse je faire. merci d’avance.

  • nicolas

    Hello 🙂

    Très bon cours ! Merci Bestmomo pour le partage de vos connaissances, c’est grâce à ce site que je fais du Laravel depuis un certains temps maintenant et que j’adore ce framework !

    Je viens de terminer le cours et en lançant la commande php artisan migrate j’obtiens cette erreur

    SQLSTATE[42S02]: Base table or view not found: 1146 Table ‘shopping.shops’ doesn’t exist (SQL: select * from `shops` limit 1)

    Après un peu de recherche, l’erreur vient de l’appel à View::share(‘shop’, Shop::firstOrFail()); dans le fichier fichier App/Services/AppServiceProvider.php

    J’ai obtenu cette erreur en relançant le projet sur un environnement, pour la corriger j’ai englobé tout le contenu de la méthode boot dans cette condition

    if(!$this->app->runningInConsole()) {

    }

    Si il y en a dans le même cas que moi j’espère que ça aidera 🙂

    Bon dev à tous 🙂

  • julien59

    Salut bestmomo tout d’abord félicitation pour ce tuto … Quel travail …
    Pour ma part, au clic du bouton commander, il m’affiche cette erreur:
    Trying to get property ‘name’ of non-object
    Ayant déjà eu l’erreur ( Trying to get property ‘country_id’ of non-object) je me suis connecté via un profil déjà présent dans la bdd mais rien a faire l’erreur persiste.

  • fatou

    Bonsoir bestmomo le problème de ma page 404 not found vient au niveau de AppServiceProvider quand j’ajoute View::share(‘shop’, Shop::firstOrFail()); dans la function boot () mais quand j’enlève ça marche la page vient. Est ce que si je mets cette partie ça pourra poser des problèmes plus tard avec l’application c’est à dire est ce que obligatoire de le mettre ?

    • fatou

      bestmomo je comprends maintenant pourquoi avec View::share(‘shop’, Shop::firstOrFail()); ça faisait 404 not found
      en voyant la définition de firstOrFaill qui envoie le premier enregistrement trouvé dans la base de données. Si aucun modèle correspondant n’existe, il renvoie une erreur donc j’ai insérer un enregistrement dans la table shops directement dans phpMyAdmin j’aimerais savoir si on pouvait faire des enregistrement sans aller dans phpMyAdmin sans faire la partie backend

  • fatou

    Bonjour je débute sur laravel je suis entrain de suivre vos cours sur laravel 7 j’ai tout fais du début arrivé au niveau des panier mes produits ne s’affiche pas sur la page et depuis que je suis arrivé sur la partie commande rien se s’affiche sur ma page quand je tape localhost/shopping/public on me renvoit 404 not found j’ai même télécharger le zip du projet toujours pareille on m’affiche 404 not found si vous pouvez m’aider. Merci

  • Mody

    bonjour !
    j’ai une question mais avant tout je salue votre véritable geste et expérience , franchement parlant ce projet de shopping m’a aidé.
    comment la classe Services/Shipping a-t-il été crée par une cammande ou manuellement? car je l’ai faite manuellement mais après les importations j’ai eu l’erreur suivante : Trying to get property ‘country_id’ of non-object. Or cet attribut a été déclaré sur cette classe

Laisser un commentaire