Shopping : le compte client 3/3
Dans cet article on va terminer le codage du compte du client en lui permettant de consulter la liste de ses commandes et le détail pour chacune d’elles. Dans le cas d’un paiement par carte non abouti on va lui donner aussi la possibilité d’effectuer ce paiement avec Stripe.
Vous pouvez télécharger un ZIP du projet ici.
La liste des commandes
Pour gérer la liste des commandes on va ajouter une méthode index au contrôleur OrdersController :
public function index(Request $request) { $orders = $request->user()->orders()->with('state')->get(); return view('account.orders.index', compact('orders')); }
On récupère dans la base toutes les commandes du client connecté accompagnées de leur état.
On ajoute la route et on prévoit aussi celle pour voir le détail d’une commande :
Route::middleware('auth')->group(function () { // Gestion du compte Route::prefix('compte')->group(function () { ... Route::resource('commandes', 'OrdersController')->only(['index', 'show'])->parameters(['commandes' => 'order']); });
On ajoute le lien dans la vue views/account/index.blade.php :
<div class="col s12 m6"><a href="{{ route('commandes.index') }}" class="btn-large @unless($orders) disabled @endif"><i class="material-icons left">shopping_cart</i>Mes Commandes</a></div>
On crée la vue :
@extends('layouts.app') @section('content') <div class="container"> <h2>Historique de mes commandes</h2> <div class="row"> <div class="col s12"> <div class="card"> <div class="card-content"> <table class="striped highlight responsive-table"> <thead> <tr> <th>Référence</th> <th>Date</th> <th>Prix total</th> <th>Paiement</th> <th>État</th> <th></th> </tr> </thead> <tbody> @foreach($orders as $order) <tr> <td>{{ $order->reference }}</td> <td>{{ $order->created_at->calendar() }}</td> <td>{{ number_format($order->total_order, 2, ',', ' ') }} €</td> <td>{{ $order->payment_text }}</td> <td><span class="badge new {{ $order->state->color }}" data-badge-caption="{{ $order->state->name }}"></span></td> <td><a href="{{ route('commandes.show', $order->id) }}" class="waves-effect waves-light btn-small">Détails</a></td> </tr> @endforeach </tbody> </table> </div> </div> </div> </div> <div class="row"> <a class="waves-effect waves-light btn" href="{{ route('account') }}"> <i class="material-icons left">chevron_left</i>Retour à mon compte</a> </div> </div> @endsection
Et on obtient la liste :
Je n’ai pas prévu de pagination parce que je ne pense pas que ça soit vraiment utile sauf pour un client très assidu !
On a les renseignements essentiels de la commande :
- référence
- date de création
- mode de paiement
- état
On dispose aussi d’un bouton pour accéder aux détails.
Le détail des commandes
Pour afficher le détail d’un commande on crée la méthode show dans le contrôleur OrdersController :
public function show(Request $request, $id) { $order = Order::with('products', 'state', 'adresses', 'adresses.country')->findOrFail($id); $this->authorize('manage', $order); $data = $this->data($request, $order); return view('account.orders.show', $data); }
On récupère toutes les information de la commande dans la base :
- produits
- état
- adresses (avec le pays associé)
On vérifie que le client est bien propriétaire de la commande.
On utilise la méthode data qu’on avait déjà créée pour la confirmation puisqu’on a besoin des mêmes informations.
On crée la vue :
@extends('layouts.app') @section('css') <style> .StripeElement { box-sizing: border-box; height: 40px; padding: 10px 12px; border: 1px solid transparent; border-radius: 4px; background-color: white; box-shadow: 0 1px 3px 0 #e6ebf1; -webkit-transition: box-shadow 150ms ease; transition: box-shadow 150ms ease; } .StripeElement--focus { box-shadow: 0 1px 3px 0 #cfd7df; } .StripeElement--invalid { border-color: #fa755a; } .StripeElement--webkit-autofill { background-color: #fefde5 !important; } .loader { margin: auto; border: 16px solid #e3e3e3; border-radius: 50%; border-top: 16px solid #1565c0; width: 120px; height: 120px; animation: spin 2s linear infinite; } @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } } </style> @endsection @section('content') <div class="container"> <h2>Détails de ma commande</h2> <div class="row"> <div class="col s12"> <div class="card"> <div class="card-content"> <p><strong>Commande n° {{ $order->reference }}</strong></p> @if($order->purchase_order) <p><strong>Bon de commande n° {{ $order->purchase_order }}</strong></p> @endif <p><strong>Date :</strong> {{ $order->created_at->calendar() }}</p> </div> </div> </div> <div class="col s12"> <div class="card"> <div class="card-content"> @include('command.partials.detail', [ 'content' => $order->products, 'shipping' => $order->shipping, 'tax' => $order->tax, 'total' => $order->total, ]) </div> </div> </div> <div class="col s12"> <div class="card"> <div class="card-content"> <p><strong>Moyen de paiement :</strong> {{ $order->payment_text }}</p> <p><strong>Etat : </strong>{{ $order->state->name }}</p> @if($order->state->slug === 'carte' || $order->state->slug === 'erreur') @include('command.partials.stripe') @endif @if($order->invoice_id) <br> <td><a href="{{ route('invoice', $order->id) }}" class="waves-effect waves-light btn-small">Télécharger la facture</a></td> @endif </div> </div> </div> <div class="col s12"> <div id="payment-ok" class="card center-align white-text green darken-4 hide"> <div class="card-content"> <span class="card-title">Votre paiement a été validé</span> @if($order->pick) <p>Vous pouvez venir chercher votre commande</p> @else <p>Votre commande va vous être envoyée</p> @endif </div> </div> </div> <div class="col s12"> <div class="card"> <div class="card-content"> <span class="card-title">Adresse de facturation @if($order->adresses->count() === 1) et de livraison @endif</span> @include('account.addresses.partials.address', ['address' => $order->adresses->first()]) </div> </div> </div> @if($order->adresses->count() === 2) <div class="col s12"> <div class="card"> <div class="card-content"> <span class="card-title">Adresse de livraison</span> @include('account.addresses.partials.address', ['address' => $order->adresses->get(1)]) </div> </div> </div> @endif @if($order->pick) <div class="col s12"> <div class="card"> <div class="card-content"> <p>J'ai choisi de venir chercher ma commande sur place.</p> </div> </div> </div> @endif </div> <div class="row"> <a class="waves-effect waves-light btn" href="{{ route('commandes.index') }}"> <i class="material-icons left">chevron_left</i>Retour à mes commandes</a> </div> </div> @endsection @section('javascript') @include('command.partials.stripejs') @endsection
On affiche la référence et la date :
Puis le détail des produits et du coût :
Puis le moyen de paiement et l’état. Si une facture a été créée on prévoit un bouton pour la télécharger (pour le moment il est inactif) :
Si c’est un paiement par carte qui n’a pas encore été validé on affiche le même formulaire que celui qu’on avait prévu dans la confirmation de la commande :
Ensuite on a la ou les adresses :
Et enfin une information sur l’expédition :
Le téléchargement de la facture
L’API de VosFactures prévoit le téléchargement d’une facture en format PDF :
curl https://votrecompte.vosfactures.fr/invoices/100.pdf?api_token=API_TOKEN
Mais on ne peut pas utiliser ce lien tel quel sans afficher le token, ce qu’on ne désire évidemment pas, alors on va devoir ruser…
On va prévoir un disque pour placer les factures de façon temporaire (config.filesystems) :
'disks' => [ ... 'invoices' => [ 'driver' => 'local', 'root' => storage_path('app/invoices'), ],
On crée un contrôleur :
php artisan make:controller InvoiceController
Avec ce code :
<?php namespace App\Http\Controllers; use App\Models\Order; use Illuminate\Support\Facades\Storage; class InvoiceController extends Controller { /** * Get invoice pdf * * @param \App\Models\Order $order * @return \Illuminate\Http\Response */ public function __invoke(Order $order) { $this->authorize('manage', $order); // Récupération du pdf $url = config('invoice.url') . 'invoices/' . (string)$order->invoice_id . '.pdf?api_token=' . config('invoice.token'); $contents = file_get_contents($url); $name = (string)$order->invoice_id . '.pdf'; Storage::disk('invoices')->put($name, $contents); // Envoi return response()->download(storage_path('app/invoices/' . $name))->deleteFileAfterSend(); } }
On commence par créer l’url de téléchargement :
$url = config('invoice.url') . 'invoices/' . (string)$order->invoice_id . '.pdf?api_token=' . config('invoice.token');
On récupère la facture :
$contents = file_get_contents($url);
On crée un nom avec l’identifiant :
$name = (string)$order->invoice_id . '.pdf';
Et on enregistre le fichier sur le disque :
Storage::disk('invoices')->put($name, $contents);
On peut alors envoyer le fichier au client avec une réponse download :
return response()->download(storage_path('app/invoices/' . $name))->deleteFileAfterSend();
On supprime le fichier dès qu’il a été téléchargé (deleteFileAfterSend).
On ajoute la route :
Route::middleware('auth')->group(function () { // Gestion du compte Route::prefix('compte')->group(function () { ... Route::name('invoice')->get('commandes/{order}/invoice', 'InvoiceController');
Le lien dans le bouton :
<td><a href="{{ route('invoice', $order->id) }}" class="waves-effect waves-light btn-small">Télécharger la facture</a></td>
Et là normalement la facture doit se télécharger quand on clique !
Conclusion
On en a ainsi terminé avec le compte du client. Dans le prochain article on créera les différents emails (inscription, commande…).
5 commentaires
DIM
mais je n’ai pas vu là où tu as configuré la lettre d’information , dans quelle partie du cours stp ?
bestmomo
Déjà dans la table users on a une colonne newsletter.
Pour la création du compte dans le formulaire, on a prévu une case à cocher.
D’autre part, dans le compte client, il peut modifier cette option.
Donc, ça serait bien de procéder de la même façon pour l’information d’un nouvel article.
DIM
un email j’allais dire
DIM
Bonjour best ,
par rapport à la newsletter j’ai l’impression que tu n’as pas gérer cet aspect.
j’aimerai envoyé une notification aux utilisateurs du site à chaque fois qu’un nouvel article a été créeé.
C’est un blog .
merci
bestmomo
Salut,
Pour faire ça il faudrait faire exactement comme pour la lettre d’information, demander l’autorisation pour la notification pour un nouvel article (ne jamais oublier le RGPD). Donc case à cocher, colonne dans la table, etc…