
Notre boutique sait afficher des produits et gérer un panier. Nous allons à présent aborder un sujet un peu plus délicat, ce lui du passage de la commande. Le client doit avoir un compte et bien renseigner toutes les données nécessaires pour sa commande : mode de paiement, adresse le livraison et de facturation, mode de livraison...
Vous pouvez trouver le code dans ce dépôt Github.
Les emails
Nous allons avoir besoin de quelques emails concernant la commande :
- pour alerter les administrateurs qu'une commande a été passée
- pour alerter éventuellement les administrateurs en cas d'atteinte de la limite inférieure de stock pour un produit
- pour confirmer la commande au client avec tous les renseignements nécessaires
Alerte des administrateurs pour une nouvelle commande
On génère la classe pour cet email :
php artisan make:mail NewOrder
Avec ce code :
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Mail\Mailables\Address;
use App\Models\{ Shop, Order };
class NewOrder extends Mailable
{
use Queueable, SerializesModels;
public Shop $shop;
public Order $order;
/**
* Create a new message instance.
*/
public function __construct(Shop $shop, Order $order)
{
$this->shop = $shop;
$this->order = $order;
}
/**
* Get the message envelope.
*/
public function envelope(): Envelope
{
return new Envelope(
from: new Address($this->shop->email, $this->shop->name),
subject: trans('New order'),
);
}
/**
* Get the message content definition.
*/
public function content(): Content
{
return new Content(
view: 'mails.neworder',
);
}
/**
* Get the attachments for the message.
*
* @return array<int, \Illuminate\Mail\Mailables\Attachment>
*/
public function attachments(): array
{
return [];
}
}
Et on crée la vue associée :
Je ne copie pas le code un peu trop volumineux, vous pouvez le trouver dans le dépôt Github.
On ajoute les traductions pour cet email :
"New order": "Nouvelle commande",
"from": "de",
"Order details": "Détails de la commande",
Alerte des administrateurs pour le stock
On génère la classe pour cet email :
php artisan make:mail ProductAlert
Avec ce code :
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Mail\Mailables\Address;
use App\Models\{ Shop, Product };
class ProductAlert extends Mailable
{
use Queueable, SerializesModels;
public Shop $shop;
public Product $product;
public function __construct(Shop $shop, Product $product)
{
$this->shop = $shop;
$this->product = $product;
}
public function envelope(): Envelope
{
return new Envelope(
from: new Address($this->shop->email, $this->shop->name),
subject: trans('Stock alert'),
);
}
public function content(): Content
{
return new Content(
view: 'mails.productalert',
);
}
public function attachments(): array
{
return [];
}
}
Et on crée la vue associée :
Là encore, vous pouvez récupérer le fichier dans le dépôt Github.
On ajoute les traductions :
"Stock alert": "Alerte de stock",
"of product": "du produit",
"Remaining quantity": "Quantité restante",
Confirmation de commande pour le client
On génère la classe pour cet email :
php artisan make:mail Ordered
Avec ce code :
<?php
namespace App\Mail;
use Illuminate\Bus\Queueable;
use Illuminate\Mail\Mailable;
use Illuminate\Mail\Mailables\Content;
use Illuminate\Mail\Mailables\Envelope;
use Illuminate\Queue\SerializesModels;
use Illuminate\Mail\Mailables\Address;
use App\Models\{ Shop, Order };
class Ordered extends Mailable
{
use Queueable, SerializesModels;
public Shop $shop;
public Order $order;
public function __construct(Shop $shop, Order $order)
{
$this->shop = $shop;
$this->order = $order;
}
public function envelope(): Envelope
{
return new Envelope(
from: new Address($this->shop->email, $this->shop->name),
subject: trans('Your order'),
);
}
public function content(): Content
{
return new Content(
view: 'mails.ordered',
);
}
public function attachments(): array
{
return [];
}
}
Et on crée la vue associée :
Là encore, vous pouvez récupérer le fichier dans le dépôt Github.
On ajoute les traductions :
"Thank you for ordering": "Merci pour votre commande",
"Order reference": "Référence de ma commande",
"Please send us a cheque with:": "Veuillez nous envoyer un chèque avec :",
"You have chosen to pay by mandat administratif. This type of payment is reserved for government agencies. Please send your money order to:": "Vous avez choisi de payer par mandat administratif. Ce type de paiement est réservé aux administrations. Vous devez envoyer votre mandat administratif à :",
"Please make a bank transfer to our account:": "Veuillez effectuer un virement sur notre compte :",
"You have chosen to pay by credit card.": "Vous avez choisi de payer par carte bancaire.",
"Your payment has been validated.": "Votre paiement a été validé.",
"Your order will be shipped soon.": "Votre commande sera expédiée bientôt.",
"amount of payment:": "montant du règlement :",
"Amount of payment:": "Montant du règlement :",
"payable to the order of": "payable à l'ordre de",
"Payable to the order of": "Payable à l'ordre de",
"To be sent to": "À envoyer à",
"to be sent to": "à envoyer à",
"Do not forget to indicate your order reference": "N'oubliez pas d'indiquer votre référence de commande",
"do not forget to indicate your order reference": "n'oubliez pas d'indiquer votre référence de commande",
"You must send your administrative mandate to:": "Vous devez envoyer votre mandat administratif à :",
"You can also send it to us by email at this address:": "Vous pouvez aussi nous le transmettre par e-mail à cette adresse :",
"Please make a transfer to our account:": "Veuillez effectuer un virement sur notre compte :",
"Amount of transfer:": "Montant du virement :",
"Account holder:": "Titulaire :",
"amount of transfer:": "montant du virement :",
"account holder:": "titulaire :",
"BIC:": "BIC :",
"IBAN:": "IBAN :",
"Bank:": "Banque :",
"Bank address:": "Adresse banque :",
"bank:": "banque :",
"bank address:": "adresse banque :",
"You can come and pick up your order as soon as the payment is received.": "Vous pourrez venir chercher votre commande dès réception du paiement.",
"Your order will be shipped as soon as the payment is received.": "Votre commande vous sera envoyée dès réception du paiement.",
"Your order": "Votre commande",
"Phone us": "Nous téléphoner",
On va avoir aussi besoin de quelques vues partielles pour le code commun :
Là aussi, vous pouvez aller récupérer ces fichiers dans le dépôt.
Nous verrons plus loin l'aspect de tous ces emails.
Le paiement par carte bancaire avec Stripe
De toute évidence, le paiement par carte bancaire est certainement le moyen le plus utilisé sur Internet. Pour le mettre en place, il faut choisir un prestataire.
Stripe est une solution de paiement en ligne. C'est une entreprise gigantesque qui a son siège social aux États-Unis, mais qui possède des bureaux dans la plupart des pays. Je vous propose dans cet article de l'utiliser pour notre boutique, mais c'est évidemment adaptable avec une autre solution de paiement, mais avec peut-être plus de difficultés pour utiliser l'API. Vous pouvez ouvrir un compte gratuit pour essayer la solution et ainsi récupérer vos clés API (une secrète et une publique).
Il existe le package Cashier pour utiliser Stripe avec Laravel. Mais pour de simples paiements, je ne trouve pas que ça vaut le coup de l'utiliser. Je vais vous montrer comment effectuer le paiement assez simplement sans ce package. Par contre, on a besoin du package de Stripe :
composer require stripe/stripe-php
La documentation de l'API est très complète. Mais elle est tellement fournie qu'on a parfois du mal à trouver l'information qu'on cherche ! La fonctionnalité la plus pratique est le Checkout, qui permet en particulier de diriger le client sur une page de paiement hébergée par Stripe. Le processus est simple :
- Lorsque le client valide sa commande, on crée une session Checkout (on précise les produits, les taxes, les frais de livraison, pour renseigner le formulaire généré par Stripe)
- On fournit à la session Checkout une URL en cas de succès du paiement et une autre en cas d'échec
- La session Checkout fournit une URL pour rediriger le client sur la page de paiement de Stripe
- Sur la page de Stripe, le client effectue son paiement
On a ainsi plus qu'à attendre le retour.
Pour communiquer avec L'API de Stripe, vous aurez besoin de deux clés, vous les mémorisez dans la configuration de la boutique. Déjà dans .env :
STRIPE_PUBLISHABLE_KEY=pk_test_***
STRIPE_SECRET_KEY=sk_test_***
Et on crée un fichier de configuration :
Dans lequel on récupère les clés :
<?php
return [
'publishable_key' => env('STRIPE_PUBLISHABLE_KEY'),
'secret_key' => env('STRIPE_SECRET_KEY'),
];
Récemment, Stripe a créé les Sandboxes. C'est un environnement de test complètement isolé de l'espace principal, ce qui est bien pratique !
On est désormais prêts à utiliser Stripe !
Le composant pour la commande
Nous arrivons aux choses sérieuses en générant le composant pour la commande :
php artisan make:volt order/index --class
Avec pas mal de code parce qu'il y a de nombreuses choses à traiter :
<?php
use Livewire\Volt\Component;
use Livewire\Attributes\Title;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Auth;
use App\Models\{ Country, Range, Product, Page, State, Shop, User };
use Darryldecode\Cart\CartCollection;
use Illuminate\Support\Facades\Mail;
use App\Mail\{ NewOrder, ProductAlert, Ordered };
use Stripe\Stripe;
use Stripe\TaxRate;
use Stripe\Checkout\Session as CheckoutSession;
new #[Title('Order')]
class extends Component {
public Collection $addresses;
public bool $hasMultipleAddresses = false;
public $selectedBillingAddress;
public $selectedDeliveryAddress;
public bool $sameAddress = true;
public int $selectedDeliveryOption = 1;
public string $selectedPaymentOption = 'carte';
public CartCollection $content;
public float $total;
public float $tax;
public bool $pick = false;
public float $shipping;
public float $tvaBase;
public Shop $shop;
public function mount()
{
if(Cart::isEmpty()) abort(404);
session()->forget('checkout_session_id');
$this->shop = Shop::firstOrFail();
$this->addresses = Auth::user()->addresses()->get();
if($this->addresses->isEmpty()) {
return redirect()->route('addresses.create');
}
$this->hasMultipleAddresses = $this->addresses->count() > 1;
$this->selectedBillingAddress = $this->addresses->first()->id;
$this->selectedDeliveryAddress = $this->addresses->first()->id;
$this->shipping = $this->calculateShipping();
if (!$this->shipping) {
return redirect()->route('cart')->with('message', trans('The weight of your order exceeds the processing capacity of our store. Please contact us or reduce the quantity.'));
}
$this->calculateDetail();
}
public function toggleSameAddress(): void
{
$this->sameAddress = !$this->sameAddress;
$this->updateDeliveryAddress();
}
public function updatedselectedBillingAddress($value): void
{
$this->updateDeliveryAddress();
}
private function updateDeliveryAddress(): void
{
$this->selectedDeliveryAddress = $this->sameAddress
? $this->selectedBillingAddress
: $this->addresses->where('id', '!=', $this->selectedBillingAddress)->first()->id ?? $this->selectedBillingAddress;
$this->shipping = $this->calculateShipping();
$this->calculateDetail();
}
public function updatedselectedDeliveryOption($value): void
{
$this->pick = $value == '2';
if ($this->pick) {
$this->sameAddress = true;
$this->selectedDeliveryAddress = $this->selectedBillingAddress;
$this->shipping = 0;
}
$this->shipping = $this->calculateShipping();
$this->calculateDetail();
}
public function updatedselectedDeliveryAddress(): void
{
$this->shipping = $this->calculateShipping();
$this->calculateDetail();
}
private function calculateShipping(): float|false
{
if ($this->pick) {
return 0;
}
$address = $this->addresses->firstWhere('id', $this->selectedDeliveryAddress);
$items = Cart::getContent();
$weight = $items->sum(function ($item) {
return $item->quantity * $item->model->weight;
});
$range = Range::orderBy('max')->where('max', '>=', $weight)->first();
return $range ? $range->countries()->where('countries.id', $address->country_id)->first()->pivot->price : false;
}
private function calculateDetail(): void
{
$country_delivery = $this->addresses->firstWhere('id', $this->selectedDeliveryAddress)->country;
$this->tvaBase = Country::where('tax', '>', 0)->first()->tax;
$this->tax = $this->pick ? $this->tvaBase : $country_delivery->tax;
$this->content = Cart::getContent();
$this->total = $this->tax > 0 ? Cart::getTotal() : Cart::getTotal() / (1 + $this->tvaBase);
}
public function saveOrder()
{
$items = Cart::getContent();
// Vérification du stock
foreach($items as $row) {
$product = Product::findOrFail($row->id);
if($product->quantity < $row->quantity) {
return redirect()->route('cart')->with('message', trans("We're sorry, but the product :name does not have enough stock to satisfy your request. We only have :quantity available.", ['name' => $product->name, 'quantity' => $product->quantity]));
}
}
// Adresses
$address_facturation = $this->addresses->firstWhere('id', $this->selectedBillingAddress);
$address_livraison = $this->addresses->firstWhere('id', $this->selectedDeliveryAddress);
// Enregistrement commande
$order = Auth::user()->orders()->create([
'reference' => strtoupper(Str::random(8)),
'shipping' => $this->shipping,
'tax' => $this->tax,
'total' => $this->tax > 0 ? Cart::getTotal() : Cart::getTotal() / (1 + $this->tvaBase),
'payment' => $this->selectedPaymentOption,
'pick' => $this->pick,
'state_id' => State::whereSlug($this->selectedPaymentOption)->first()->id,
]);
// Enregistrement adresse de facturation
$order->addresses()->create($address_facturation->toArray());
// Enregistrement éventuel adresse de livraison si différente
if(!$this->sameAddress) {
$address_livraison->facturation = false;
$order->addresses()->create($address_livraison->toArray());
}
// Enregistrement des produits
foreach($items as $row) {
$order->products()->create(
[
'name' => $row->name,
'total_price_gross' => ($this->tax > 0 ? $row->price : $row->price / (1 + $this->tvaBase)) * $row->quantity,
'quantity' => $row->quantity,
]
);
$product = Product::findOrFail($row->id);
$product->quantity -= $row->quantity;
$product->save();
if($product->quantity + $row->quantity > $product->quantity_alert && $product->quantity <= $product->quantity_alert) {
$admins = User::whereAdmin(true)->get();
foreach($admins as $admin) {
Mail::to($admin)->send(new ProductAlert($this->shop, $product));
}
}
}
// On vide le panier et la session
Cart::clear();
Cart::session(Auth::user())->clear();
// Notification à l'administrateur
$admins = User::whereAdmin(true)->get();
foreach($admins as $admin) {
Mail::to($admin)->send(new NewOrder($this->shop, $order));
}
// Si par carte on crée le checkout
if($this->selectedPaymentOption === 'carte') {
Stripe::setApiKey(config('stripe.secret_key'));
// Crée un objet de taxe si le taux est supérieur à zéro
$tax_rate = null;
if ($this->tax > 0) {
$tax_rate = TaxRate::create([
'display_name' => 'TVA',
'percentage' => $this->tax * 100,
'inclusive' => true,
]);
}
// Produits
$products = [];
foreach($items as $row) {
$products[] = [
'name' => $row->name,
'price' => (int)(($this->tax > 0 ? $row->price : price_without_vat($row->price)) * 100),
'quantity' => $row->quantity,
];
}
$line_items = [];
foreach ($products as $product) {
$line_items[] = [
'price_data' => [
'currency' => 'eur',
'product_data' => [
'name' => $product['name'],
],
'unit_amount' => $product['price'],
'tax_behavior' => 'inclusive',
],
'quantity' => $product['quantity'],
'tax_rates' => $tax_rate ? [$tax_rate->id] : [],
];
}
// Ajoute les frais de port éventuels
if ($this->shipping > 0) {
$line_items[] = [
'price_data' => [
'currency' => 'eur',
'product_data' => [
'name' => 'Frais de port',
],
'unit_amount' => (int)($this->shipping * 100),
],
'quantity' => 1,
'tax_rates' => [],
];
}
// Crée la session
$checkout_session = CheckoutSession::create([
'success_url' => route('order.card', $order->id),
'line_items' => $line_items,
'mode' => 'payment',
'customer_email' => Auth::user()->email,
]);
session(['checkout_session_id' => $checkout_session->id,]);
return redirect()->away($checkout_session->url);
} else {
// Notification au client
Mail::to(Auth::user())->send(new Ordered($this->shop, $order));
}
return redirect()->route('order.confirmation', $order->id);
}
public function with(): array
{
$paymentOptions = [];
if ($this->shop->card) {
$paymentOptions[] = [
'id' => 'carte',
'name' => trans('Credit card'),
'text' => trans('You will be directed to the Stripe payment page after your order has been validated.')
];
}
if ($this->shop->mandat) {
$paymentOptions[] = [
'id' => 'mandat',
'name' => trans('Administrative mandate'),
'text' => trans('Send us a purchase order. Your order will be shipped upon receipt of this order. Do not forget to specify in your order the reference which will be specified when validating your order.')
];
}
if ($this->shop->transfer) {
$paymentOptions[] = [
'id' => 'virement',
'name' => trans('Bank transfer'),
'text' => trans('You will need to transfer the order amount to our bank account. You will receive your order confirmation including our bank details and order number. The goods will be held for 30 days for you and we will process your order as soon as payment is received.')
];
}
if ($this->shop->check) {
$paymentOptions[] = [
'id' => 'cheque',
'name' => trans('Check'),
'text' => trans('You will need to send us a check for the amount of the order. You will receive your order confirmation including our bank details and the order number. The goods will be held for 30 days for you and we will process your order as soon as payment is received.')
];
}
return [
'deliveryOptions' => [
['id' => '1', 'name' => trans('Colissimo')],
['id' => '2', 'name' => trans('Pick-up on site')],
],
'paymentOptions' => $paymentOptions
];
}
}; ?>
<div>
<x-card class="flex items-center justify-center mt-6 bg-gray-100 w-full lg:max-w-[80%] lg:mx-auto" title="{{ __('My order') }}" shadow separator>
@if (!$hasMultipleAddresses)
<x-card class="w-full sm:min-w-[50vw]" title="{!! $pick || !$sameAddress ? trans('Billing') : trans('Billing & Delivery') !!}" shadow separator >
<div class="grid grid-cols-2 gap-6">
<x-card class="w-full bg-green-100 shadow-md shadow-gray-500" title=" ">
<x-address :address="$addresses->first()" />
</x-card>
</div>
<x-slot:actions>
<x-button label="{{ trans('Manage my addresses') }}" class="btn-primary" link="{{ route('addresses') }}" />
</x-slot:actions>
</x-card>
@else
<x-card class="w-full sm:min-w-[50vw]" title="{{ trans('Addresses') }}" shadow separator >
<div class="grid grid-cols-1 gap-6 items-start md:grid-cols-2 lg:grid-cols-3">
@foreach ($addresses as $address)
<label class="inline-flex items-start">
<x-card class="w-full ml-2 shadow-md shadow-gray-500 transition duration-300 {{ $selectedBillingAddress == $address->id ? 'bg-blue-100' : '' }}" title=" ">
<input type="radio" class="form-radio" name="billingAddress" wire:model="selectedBillingAddress" value="{{ $address->id }}" wire:change="$refresh">
<span class="ml-2 font-bold">{{ ($pick || !$sameAddress) ? __('Billing') : __('Billing & Delivery') }}</span>
<x-address :address="$address" />
</x-card>
</label>
@endforeach
</div>
@if (!$pick && !$sameAddress)
<br><hr>
<div class="grid gap-6 items-start mt-4 md:grid-cols-2 lg:grid-cols-4">
@foreach ($addresses as $address)
@if($address->id != $selectedBillingAddress)
<label class="inline-flex items-start">
<x-card class="w-full ml-2 shadow-md shadow-gray-500 transition duration-300 {{ $selectedDeliveryAddress == $address->id ? 'bg-blue-100' : '' }}" title=" ">
<input type="radio" class="form-radio" name="deliveryAddress" wire:model="selectedDeliveryAddress" value="{{ $address->id }}" wire:change="$refresh">
<span class="ml-2 font-bold">{{ trans('Delivery') }}</span>
<x-address :address="$address" />
</x-card>
</label>
@endif
@endforeach
</div>
@endif
<x-slot:actions>
<div class="flex flex-col justify-start items-start space-y-2 w-full sm:flex-row sm:space-y-0 sm:space-x-2">
@if (!$pick)
<x-button wire:click="toggleSameAddress" label="{{ $sameAddress ? trans('Use different delivery address') : trans('Use same address for billing and delivery') }}" class="btn-primary" />
@endif
<x-button label="{{ trans('Manage my addresses') }}" class="btn-primary" link="{{ route('addresses') }}" />
</div>
</x-slot:actions>
</x-card>
@endif
<br>
<x-card class="w-full sm:min-w-[50vw]" title="{{ trans('Delivery mode') }}" shadow separator >
<x-radio :options="$deliveryOptions" wire:model="selectedDeliveryOption" wire:change="$refresh" />
</x-card>
<br>
<x-card class="w-full sm:min-w-[50vw]" title="{{ trans('Mode of payment') }}" shadow separator >
<x-radio :options="$paymentOptions" wire:model="selectedPaymentOption" wire:change="$refresh" />
<br>
<p>{{ collect($paymentOptions)->firstWhere('id', $selectedPaymentOption)['text'] }}</p>
</x-card>
<br>
<x-card x-data="{ isChecked: false }" class="w-full sm:min-w-[50vw]" title="{{ __('My order details') }}" shadow separator >
<x-details
:content="$content"
:shipping="$shipping"
:tax="$tax"
:total="$total"
:pick="$pick"
/>
<hr><br>
<p class="mb-2 text-xl">{{ trans('Please check your order before validation!') }}</p>
<input id="ok" name="ok" type="checkbox" x-model="isChecked" />
<span>{{ trans('I have read the general terms and conditions of sale and the cancellation policy and accept them unreservedly.') }}</span>
<x-slot:actions>
<x-button label="{{ trans('Order with payment obligation') }}" wire:click="saveOrder" class="mt-2 w-full btn-primary" x-bind:disabled="!isChecked" spinner />
</x-slot:actions>
</x-card>
</x-card>
</div>
Comme d'habitude, on ajoute les traductions :
"The weight of your order exceeds the processing capacity of our store. Please contact us or reduce the quantity.": "Le poids de votre commande excède les capacités de traitement de notre boutique. Veuillez nous contacter ou réduisez les quantités.",
"We're sorry, but the product :name does not have enough stock to satisfy your request. We only have :quantity available.": "Nous sommes d'accord, mais le produit :name n'a pas la quantité suffisante pour satisfaire votre requête. Nous avons seulement :quantity disponible.",
"You will be directed to the Stripe payment page after your order has been validated.": "Vous serez redirigé vers la page de paiement de Stripe après validation de votre commande.",
"Send us a purchase order. Your order will be shipped upon receipt of this order. Do not forget to specify in your order the reference which will be specified when validating your order.": "Envoyez nous un bon de commande. Votre commande sera expédiée dès réception de ce bon. N'oubliez pas de préciser dans votre bon la référence qui sera précisée lors de la validation de votre commande.",
"You will need to transfer the order amount to our bank account. You will receive your order confirmation including our bank details and order number. The goods will be held for 30 days for you and we will process your order as soon as payment is received.": "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 après reception du paiement.",
"You will need to send us a check for the amount of the order. You will receive your order confirmation including our bank details and the order number. The goods will be held for 30 days for you and we will process your order as soon as payment is received.": "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 après reception du paiement.",
"Manage my addresses": "Gérer mes adresses",
"Billing": "Facturation",
"Mode of payment": "Moyen de paiement",
"Billing & Delivery": "Facturation & Livraison",
"Addresses": "Adresses",
"My order": "Ma commande",
"Use different delivery address": "Utiliser une adresse différente pour la livraison",
"Use same address for billing and delivery": "Utiliser la même adresse pour la facturation et la livraison",
"Delivery": "Livraison",
"Delivery": "Livraison",
"Delivery mode": "Mode de livraison",
"Pick-up on site": "Retrait sur place",
"Credit card": "Carte bancaire",
"Bank transfer": "Virement bancaire",
"Check": "Chèque",
"Payment": "Paiement",
"My order details": "Details de ma commande",
"Please check your order before validation!": "Veuillez vérifier votre commande avant la validation !",
"I have read the general terms and conditions of sale and the cancellation policy and accept them unreservedly.": "J'ai lu les conditions générales de vente et les conditions d'annulation et j'y adhère sans réserve.",
"Order with payment obligation": "Commande avec obligation de paiement",
On ajoute la route :
Route::middleware('auth')->group(function () {
...
Route::prefix('order')->group(function () {
Volt::route('/creation', 'order.index')->name('order.index');
});
Il ne nous reste plus qu'à ajouter le lien dans le bouton du panier :
<x-button label="{{ auth()->check() ? __('I order') : __('Log in to order') }}" icon-right="c-arrow-right" link="{{ route('order.index') }}" class="btn-primary btn-sm" />
On va quand même prendre la précaution de ne pas afficher le panier dans la barre de navigation à cette étape :
@if($CartItems > 0 && $url !== route('cart') && $url !== route('order.index'))
Les adresses
Dans la partie supérieure, on a les adresses et le mode de livraison :
Toutes les adresses disponibles pour le client apparaissent ici. Par défaut, on propose une adresse commune pour la facturation et la livraison parce que c'est la situation la plus fréquente. Mais un bouton "Utiliser une adresse différente pour la livraison" est à disposition (s'il y a plus d'une adresse). Dans ce cas, une double proposition apparaît :
Et un bouton "Utiliser la même adresse pour la facturation et la livraison" permet de revenir à la situation initiale. Il y a aussi un bouton pour que le client puisse aller directement gérer ses adresses.
Évidemment, dans le cas d'un retrait sur place, une seule adresse est demandée pour la facturation :
Le moyen de paiement
On propose 4 moyens de paiement avec par défaut la carte bancaire :
Pour chaque cas, le texte adapté au moyen de paiement apparaît :
Le détail et la commande effective
Dans la partie inférieure, on présente le détail de la commande et le bouton pour confirmer la commande :
Les emails
Les administrateurs reçoivent un email pour les informer qu'une commande a été passée :
Le client reçoit un email récapitulatif (le cas du paiement par carte est particulier et sera traité dans un prochain article) :
Dans la partie supérieure, les références et le détail :
Au-dessous les informations générales et aussi selon le mode de paiement choisi :
D'autre part, les administrateurs sont avisés lorsqu'on atteint la limite inférieure de stock :
Conclusion
On a bien avancé la partie commande. Le client dispose d'une page récapitulative sur laquelle il peut choisir l'adresse de facturation et celle de livraison (à moins qu'il opte pour le retrait sur place). Il peut choisir son moyen de paiement. Il a aussi accès au récapitulatif de sa commande : produits, frais de port, TVA éventuelle. Pour le moment le bouton de validation de la commande crée bien la commande, mais vous allez ensuite tomber sur une erreur parce qu'on n'a pas encore codé la partie concernant la confirmation.
On a aussi créé le code pour les emails relatifs à la commande. Enfin, on a préparé le paiement par carte bancaire avec Stripe.
Par bestmomo
Aucun commentaire