Laravel 7

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…).

 

Print Friendly, PDF & Email

Leave a Reply