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.
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
bestmomo
Salut,
Je ne trouve nulle part dans le code de « communes », peux-tu localiser l’erreur ?
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
bestmomo
Salut,
Ca veut dire que $range->countries()->where(‘countries.id’, $country_id) ne te renvoie aucun enregistrement. Il n’y a donc aucun pays associé à ce range dont l’identifiant est $country_id.
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
bestmomo
Salut, apparemment, tu essaies de charger un fichier JavaScript en tant que feuille de style dans ton code HTML. Tu trouveras les codes corrects ici.
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
bestmomo
Salut,
Il faudrait utiliser les outils développeur du navigateur pour voir ce qui se passe en ligne.
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.
bestmomo
Salut,
C’est peut-être parce qu’il n’y a pas encore d’adresse sur le compte de celui qui commande.
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.
Thibaut
probleme resolus, j’avais une erreur dans le request venant de ma view blade,
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.
bestmomo
Salut,
Ca arrive sans doute parce qu’il n’y a pas encore d’adresse sur le compte de celui qui commande.
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 🙂
bestmomo
Salut,
Oui il faut vérifier si on est en mode console 😉
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.
bestmomo
Salut,
Comme la propriété name est présente en pas mal d’endroit il faudrait voir sur quelle partie du code exactement ça se produit.
fatou
oh Désolé vraiment Bestmomo c’est après que je me suis rendu compte que j’avais sauté quelques parties. Je viens de recommencer le tuto je vois que ça marche.
Excusez-moi et Merci bien
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
bestmomo
Salut,
Pourquoi ne pas lancer la population (seed) telle que je l’ai prévue ?
fatou
oh Désolé vraiment Bestmomo c’est après que je me suis rendu compte que j’avais sauté quelques parties. Je viens de recommencer le tuto je vois que ça marche.
Excusez-moi et Merci bien
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
bestmomo
Bonjour,
Normalement il ne faut pas préciser « public » dans l’url.
fatou
svp quel est l’url exacte
bestmomo
C’est quoi comme serveur local ?
fatou
j’utilise wampserver
bestmomo
Le mieux est d’utiliser Laragon.
fatou
j’ai mis sur laragon toujours la même erreur 404 not found pour pouvoir y accéder L’URL c’est localhost/shopping/public non ?
bestmomo
Laragon crée des hôtes avec un suffixe spécial, donc l’url c’est shopping.oo (là il faut utiliser le suffixe de Laragon, c’est peut-être test par défaut, je sais plus).
fatou
oui j’ai regarder un peu la documentation de laragon on dit qu’il faut faire {name du projet}.test quand je fais shopping.test on m’affiche la page d’accueil de laragon n’importe quel dossier si je tape nom_du_projet.test on m’affiche ça si vous pouvez m’aider parce que je me sens perdu. Merdi
fatou
j’ai pu ouvrir le dossier avec laravel avec l’url shopping.test on m’affiche toujours même erreur 404 not found
bestmomo
Mais il marche Laragon avec juste localhost ?
fatou
avec localhost ça marche non plus toujours not found c’est depuis que je suis arrivée de la partie commande que ça affiche 404 not found sinon du début juste à la partie panier ça vient juste les produits qui s’affiche pas
Rosisse25
Bonsoir ,
J’ai un problème, les commande ne peuvent pas enregistrer dans la table Orders.
bestmomo
Salut,
Il faudrait plus de précision sur le problème…
Rosisse25
Merci, pour votre réponse, j’ai copié votre projet normalement c’est réglé.
golli
Bonjour bestmomo
J’ai cette erreur « Trying to get property ‘country_id’ of non-object »
Malgré que j’ai suivi exactement tes codes !
golli
c bon j’ai réglé le probleme merci
il fallait juste ajouté mon id que j’ai créer pour me connecté dans la table addresses ou modifié une ligne par mon id
Julienmivo
Bonjour,
je rencontre ce problème « Route [adresses.create] not defined. » dois-je créer une route?
bestmomo
Salut,
Cette route sera créée dans un article ultérieur, il faut supprimer le lien pour le moment.
Julienmivo
Merci beaucoup
Ce projet que nous faisons, peut être considérer comment un projet de fin de formation à quel niveau (licence ou master) en France ou ailleurs?
bestmomo
Salut,
Je n’en ai aucune idée, je n’appartiens pas au monde universitaire.
dev237
Salut à tout
apparemment il y a une classe qui manque Class ‘App\Http\Controllers\ProductAlert’ not found ///
la ligne qui envoie les mails au admin
bestmomo
Salut,
Oui effectivement là j’ai un peu anticipé l’envoi de l’email mais sur le ZIP le fichier est correct. J’ai aussi mis à jour dans l’article.
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
bestmomo
Salut,
Il n’y a pas de commande Artisan pour créer un service, il faut le créer manuellement. Quant à l’erreur il semblerait que l’utilisateur n’ait pas créé d’adresse et comme on n’a pas encore créé la redirection vers son compte ça lance une erreur.
Mody
Merci davantage avec tes merveilleux tutos .
dev237
j’ai eu la même erreur … il faut juste utiliser un compte qui se trouve deja dans ta bdd pour te connecter …
Julienmivo
Bonjour,
Mais comment faire pour trouver le mot de passe.
bestmomo
Salut
Tout le monde a le mot de passe password