Guide technique

AMYEVOLUTION — Mise en ligne sur hébergement mutualisé

Guide pas à pas pour développeur débutant : prérequis, base de données, serveur, emails, paiement, API, chatbot IA, tâches planifiées, sécurité et dépannage.

Version du projet : V65 (dossier ecommerceV64, archive ecommerceV65.zip) · Laravel 11/12 + PostgreSQL · Document mis à jour le 8 novembre 2026

Nouveau en V65 : HTTPS obligatoire et sécurité renforcée Le site renvoie maintenant automatiquement http:// vers https://. Avant la mise en ligne, le certificat SSL doit être actif, sinon les visiteurs verront une alerte de sécurité. Suivez la section 11. HTTPS et nom de domaine puis la section 21 bis. Sécurité renforcée V65.
  1. Comprendre le projet
  2. Prérequis de l'hébergement
  3. Outils sur votre ordinateur
  4. 3 pièges à connaître avant de commencer
  5. Préparer le projet sur votre ordinateur
  6. Créer la base PostgreSQL
  7. Le fichier .env (tous les réglages)
  8. Installer la base et le premier administrateur
  9. Envoyer les fichiers chez l'hébergeur
  10. Le fichier .htaccess (Apache)
  11. HTTPS et nom de domaine (certificat SSL, redirection http → https)
  12. Emails (SMTP)
  13. Tâche planifiée (cron)
  14. Paiement en ligne (Flouci, Konnect, ClicToPay)
  15. Chatbot IA (facultatif)
  16. API publique de la boutique
  17. API d'administration
  18. Connecteurs API (ERP, facturation)
  19. Référencement Google (SEO)
  20. Performances et cache
  21. Sécurité : la liste à cocher · 21 bis. Sécurité renforcée V65
  22. Sauvegardes
  23. Mettre à jour le site plus tard
  24. Dépannage : erreurs fréquentes
  25. Commandes utiles (aide-mémoire)
  26. Liste finale avant ouverture

1. Comprendre le projet

Le projet est fait de deux parties qui doivent tourner sur le même nom de domaine :

PartieContenuTechnologie
frontend/
la boutique + le back office
Pages HTML (accueil, boutique, fiche produit, panier, commande, compte, blog, FAQ, pages légales…), admin.html (back office), scripts js/, styles css/, icônes, service worker (application installable).HTML, CSS, JavaScript (aucune compilation nécessaire)
backend/
le serveur
Contrôleurs, modèles, migrations (tables), emails, paiements, SEO, connecteurs API. Ce n'est pas un projet Laravel complet : ce sont les fichiers à ajouter dans un projet Laravel neuf.PHP 8.2+, Laravel 11 ou 12, PostgreSQL, Laravel Sanctum

Comment les deux parties travaillent ensemble

Arborescence finale visée chez l'hébergeur

/home/votrecompte/
├── amyevolution/              ← projet Laravel (PAS accessible depuis le web)
│   ├── app/  bootstrap/  config/  database/  routes/  resources/  storage/  vendor/
│   ├── .env                   ← vos réglages secrets
│   └── artisan
└── public_html/               ← racine web du domaine (= dossier public/ de Laravel)
    ├── index.php              ← porte d'entrée de Laravel (OBLIGATOIRE)
    ├── .htaccess              ← fusionné (voir section 10)
    ├── index.html, shop.html, product.html, admin.html … ← contenu de frontend/
    ├── css/  js/  icon/
    ├── storage → ../amyevolution/storage/app/public   ← lien des images importées
    ├── frontend/              ← copie de frontend/ (voir piège n°2)
    └── backend/login.html     ← page de connexion du back office

2. Prérequis de l'hébergement

Avant d'acheter ou de choisir une offre, vérifiez chaque ligne avec le support de l'hébergeur.

ÉlémentObligatoire ?Détail
PHP 8.2 ou plus (8.3 conseillé)OuiLaravel 11 et 12 refusent PHP 8.1. Le choix de version se fait souvent dans cPanel > « Sélectionner une version de PHP ».
PostgreSQL (version 13 ou plus)OuiLe projet utilise des fonctions propres à PostgreSQL (colonnes jsonb, séquence des numéros de commande, recherche ILIKE). MySQL/MariaDB ne fonctionne pas. Beaucoup d'offres mutualisées ne proposent que MySQL : voir l'encadré ci-dessous.
Extensions PHPOuipdo_pgsql, pgsql, mbstring, openssl, curl, fileinfo, tokenizer, xml, ctype, json, bcmath. Conseillées : intl, gd, zip, opcache.
Accès SSH (terminal)Fortement conseilléIndispensable pour lancer php artisan (migrations, création de l'administrateur, vidage du cache). Sans SSH, voir la variante en section 9.
Composer 2Conseillé sur le serveurSouvent disponible en SSH (composer -V). Sinon, on installe les dépendances sur votre ordinateur et on envoie le dossier vendor/.
Choix de la racine web du domaineOuiLe domaine doit pointer vers le dossier public/ de Laravel, ou bien on place le contenu de public/ dans public_html (section 9).
Apache avec mod_rewriteOuiPresque toujours présent en mutualisé. Nginx fonctionne aussi (voir section 10).
Certificat SSL (HTTPS)OuiLet's Encrypt gratuit dans cPanel (« SSL/TLS Status » ou « AutoSSL »). Le paiement, la connexion et les connecteurs API exigent HTTPS.
Tâches planifiées (cron)Oui si connecteurs APIUne tâche chaque minute (section 13). Certaines offres limitent à une tâche toutes les 5 ou 15 minutes : cela reste acceptable.
Boîte email du domaine (SMTP)OuiPour les confirmations de commande, les changements de statut, le mot de passe oublié et les alertes. Voir section 12.
Espace disque1 Go minimumLe projet + vendor/ pèse environ 80 Mo. Prévoyez de la place pour les images importées et les sauvegardes.
Mémoire PHP (memory_limit)256 Mo conseillésLes imports Excel et le catalogue complet consomment de la mémoire.
Taille d'envoi (upload_max_filesize, post_max_size)8 Mo minimumLes images importées sont envoyées au serveur (jusqu'à environ 4 Mo chacune).
Appels sortants HTTPSOui si paiement, IA ou connecteursLe serveur doit pouvoir appeler Flouci, Konnect, ClicToPay, OpenAI et vos logiciels partenaires. Certains hébergeurs bloquent les appels sortants : à vérifier.
Votre offre ne propose que MySQL ? Trois solutions, de la plus simple à la plus technique :
  1. Choisir une offre mutualisée qui inclut PostgreSQL (posez la question au support avant de payer).
  2. Garder l'hébergement pour les fichiers et utiliser une base PostgreSQL hébergée ailleurs (service « PostgreSQL managé »). Dans le .env : l'adresse fournie dans DB_HOST et DB_SSLMODE=require. Vérifiez que l'hébergeur autorise la connexion sortante sur le port 5432.
  3. Passer sur un petit serveur VPS : plus de liberté, mais il faut l'administrer soi-même.
Une adaptation du code à MySQL est possible, mais c'est un vrai chantier (requêtes, migrations et tests à reprendre).

3. Outils sur votre ordinateur

OutilÀ quoi il sertOù le trouver
PHP 8.3Préparer le projet et lancer artisan en localWindows : Laragon ou XAMPP · Mac : Herd ou Homebrew
Composer 2Installer Laravel et ses dépendancesgetcomposer.org
PostgreSQL (facultatif)Tester en local avant la mise en lignepostgresql.org (avec pgAdmin)
Client FTP/SFTPEnvoyer les fichiers chez l'hébergeurFileZilla, WinSCP
Client SSHSe connecter au terminal du serveurTerminal (Mac/Linux), PowerShell ou PuTTY (Windows)
Éditeur de codeModifier le .env et les fichiersVisual Studio Code
7-ZipOuvrir l'archive du projet7-zip.org

4. Trois pièges à connaître avant de commencer

Version 47 : ces trois pièges sont corrigés dans le code. Le .htaccess de la boutique contient déjà les règles de Laravel, la connexion se fait par /admin-login.html (aucune copie supplémentaire de dossier), et la migration 1 complète la table users d'un Laravel neuf. Les explications ci-dessous restent valables pour les versions précédentes.

En lisant le code, trois points bloquent une installation « à la lettre » du fichier INSTALLATION.txt. Voici les solutions. Elles sont reprises dans les étapes suivantes.

Piège n°1 — Le fichier .htaccess de la boutique écrase celui de Laravel frontend/.htaccess ne contient que des règles de cache et de compression. Si vous copiez le contenu de frontend/ dans public/ en écrasant les fichiers, le .htaccess de Laravel disparaît : toutes les adresses /api/... renvoient alors « 404 Not Found » et la boutique reste en mode démo.
Solution : utiliser le .htaccess fusionné donné en section 10.
Piège n°2 — Les liens entre le back office et la page de connexion Les fichiers utilisent des chemins relatifs du type ../backend/login.html et ../frontend/admin.html. Une fois frontend/ copié à la racine du site, ces adresses pointent vers /backend/login.html et /frontend/admin.html, qui n'existent pas.
Solution temporaire, sans toucher au code : Adresse de connexion au back office : https://www.votre-domaine.tn/admin-login.html.
Correction définitive : remplacer ces chemins par des chemins absolus (/admin.html, /login-admin.html…). C'est une petite modification du code, prévue dans la liste des correctifs.
Piège n°3 — Les migrations par défaut de Laravel entrent en conflit Un Laravel neuf contient déjà 0001_01_01_000000_create_users_table.php. Elle crée une table users avec une seule colonne name. La migration du projet voit que la table existe, n'y touche pas, et il manque alors first_name, last_name, role… La connexion admin et l'inscription des clients échouent.
Solution : supprimer les 3 migrations d'origine de Laravel avant le premier php artisan migrate (section 5, étape 3).

5. Préparer le projet sur votre ordinateur

On prépare tout en local, puis on envoie le résultat. C'est plus sûr et plus rapide que de tout faire sur le serveur.

1Créer un projet Laravel neuf

composer create-project laravel/laravel amyevolution
cd amyevolution
php artisan install:api        # installe Laravel Sanctum (jetons de connexion) et active routes/api.php

Si la commande pose une question sur les migrations, répondez non : on lancera les migrations plus tard.

2Copier les fichiers du dossier backend/

Copiez dans le projet Laravel (en remplaçant les fichiers existants) :

Depuis ecommerceV40/backend/Vers amyevolution/
app/ (tout le dossier)app/
bootstrap/app.phpbootstrap/app.php
config/mail.php, config/services.php, config/shop.phpconfig/
database/migrations/*, database/seeders/* (avec data/catalog.json)database/
resources/views/emails/*resources/views/emails/
routes/api.php, routes/web.phproutes/
env.example.txtrenommé en .env (section 7)
Note sur config/services.php Le fichier du projet remplace celui de Laravel et ne contient que la partie IA. Si vous utilisez d'autres services Laravel (Mailgun, Postmark…), recopiez leurs blocs depuis le fichier d'origine.

3Supprimer les migrations d'origine de Laravel (piège n°3)

Dans database/migrations/, supprimez :

0001_01_01_000000_create_users_table.php
0001_01_01_000001_create_cache_table.php
0001_01_01_000002_create_jobs_table.php

Gardez la migration ..._create_personal_access_tokens_table.php ajoutée par Sanctum : celle du projet la détecte et ne la recrée pas. Il doit rester les 16 migrations 2025_01_01_0000xx_... du projet.

4Installer les dépendances pour la production

composer install --no-dev --optimize-autoloader

5Préparer le dossier public/

  1. Dans public/ de Laravel, supprimez robots.txt et index.html s'ils existent (le robots.txt est généré par le serveur). Ne supprimez jamais index.php.
  2. Copiez le contenu de frontend/ dans public/, sauf .htaccess.
  3. Remplacez public/.htaccess par la version fusionnée de la section 10.
  4. Piège n°2 : copiez aussi le dossier frontend/ complet dans public/frontend/, et créez public/backend/login.html (copie de backend/login.html).

6Remplacer le nom de domaine dans les pages fixes

Les pages Contact, FAQ, Blog, Livraison, Retours, Garantie, CGV, Mentions légales, Confidentialité et Cookies contiennent l'adresse https://www.amyevolution.tn/ (adresse officielle pour Google et aperçu sur les réseaux sociaux). Si votre domaine est différent, faites un « Rechercher / Remplacer dans les fichiers » avec VS Code dans public/.

6. Créer la base PostgreSQL

Avec cPanel (cas le plus courant)

  1. cPanel > Bases de données PostgreSQL.
  2. Créez une base, par exemple compte_amyevolution. Le préfixe du compte est ajouté automatiquement.
  3. Créez un utilisateur avec un mot de passe solide (au moins 16 caractères).
  4. Ajoutez l'utilisateur à la base avec tous les privilèges.
  5. Notez : nom de la base, utilisateur, mot de passe, hôte (souvent localhost) et port (5432).

Avec un accès en ligne de commande (VPS ou base dédiée)

sudo -u postgres psql
CREATE USER amyevolution WITH PASSWORD 'un-mot-de-passe-tres-solide';
CREATE DATABASE amyevolution OWNER amyevolution ENCODING 'UTF8';
\q
La base du site reste séparée Le site garde sa propre base, distincte de celle de l'ERP (Amy ERP). Les deux logiciels échangent uniquement par l'API (section 18), jamais en accès direct à la base.

7. Le fichier .env (tous les réglages)

Le fichier .env se trouve à la racine du projet Laravel. Il ne doit jamais être accessible depuis le web ni envoyé à qui que ce soit. Après chaque modification : php artisan config:clear (ou config:cache en production).

Modèle complet commenté

# ---------- Application ----------
APP_NAME="AMY EVOLUTION"
APP_ENV=production                 # production = robots.txt ouvert à Google, erreurs masquées
APP_KEY=                           # rempli par : php artisan key:generate  (NE JAMAIS LE CHANGER ENSUITE)
APP_DEBUG=false                    # true seulement pour chercher une erreur, puis remettre false
APP_URL=https://www.votre-domaine.tn   # adresse exacte, sans / final (sert aux emails et au SEO)
APP_LOCALE=fr
APP_TIMEZONE=Africa/Tunis

# ---------- Base PostgreSQL ----------
DB_CONNECTION=pgsql
DB_HOST=127.0.0.1                  # ou localhost, ou l'adresse de la base externe
DB_PORT=5432
DB_DATABASE=compte_amyevolution
DB_USERNAME=compte_amyuser
DB_PASSWORD=MOT_DE_PASSE_DE_LA_BASE
DB_SCHEMA=public
DB_SSLMODE=prefer                  # require pour une base externe chiffrée

# ---------- Sécurité (NOUVEAU V65) ----------
FORCE_HTTPS=true                   # http:// renvoie vers https:// (false = couper temporairement, ex. certificat en panne)
SESSION_SECURE_COOKIE=true         # cookie de connexion envoyé seulement en HTTPS
SESSION_HTTP_ONLY=true             # cookie illisible par JavaScript (protège contre le vol de session)
SESSION_SAME_SITE=lax              # bloque l'envoi du cookie depuis un autre site
SESSION_ENCRYPT=true               # contenu de la session chiffré en base
SESSION_LIFETIME=120               # déconnexion après 120 minutes d'inactivité
LOG_LEVEL=warning                  # journal sans détails inutiles
BCRYPT_ROUNDS=12                   # solidité du chiffrement des mots de passe

# ---------- Sessions, cache, file d'attente ----------
SESSION_DRIVER=database
CACHE_STORE=database               # OBLIGATOIRE (database ou redis) : sert à l'anti-rejeu des connecteurs API
QUEUE_CONNECTION=sync              # emails envoyés tout de suite ; une panne SMTP ne bloque jamais une commande

# ---------- Emails (peut être remplacé depuis le back office) ----------
MAIL_MAILER=smtp
MAIL_HOST=mail.votre-domaine.tn
MAIL_PORT=465
MAIL_ENCRYPTION=ssl
MAIL_USERNAME=contact@votre-domaine.tn
MAIL_PASSWORD=MOT_DE_PASSE_DE_LA_BOITE
MAIL_FROM_ADDRESS=contact@votre-domaine.tn
MAIL_FROM_NAME="${APP_NAME}"
MAIL_ADMIN_ADDRESS=                # reçoit les alertes « nouvelle commande / nouveau message » (vide = email boutique)

# ---------- Chatbot IA (facultatif) ----------
AI_API_KEY=                        # vide = chatbot local sans IA (aucune erreur)
AI_BASE_URL=https://api.openai.com/v1
AI_MODEL=gpt-4o-mini

# ---------- Boutique (config/shop.php) ----------
SHOP_NAME=AMYEVOLUTION
SHOP_TAGLINE="Électronique Premium en Tunisie"
SHOP_PHONE="+216 71 234 567"
SHOP_EMAIL=contact@votre-domaine.tn
SHOP_STREET="Centre Commercial Lac 2"
SHOP_CITY=Tunis
SHOP_POSTAL_CODE=1053
SHOP_SOCIAL_URLS=https://facebook.com/...,https://instagram.com/...
SHOP_LOGO=/icon/icon-512.png
SHOP_DEFAULT_IMAGE=/icon/icon-512.png
SHOP_CLEAN_URLS=true               # adresses propres /produit/..., /categorie/..., /marque/...
SHOP_FRONTEND_PATH=                # vide = dossier public/ de Laravel (voir section 9, cas public_html)
SHOP_FRONTEND_URL=/

# Livraison (le serveur recalcule toujours le total de la commande)
SHOP_SHIPPING_PRICE=7
SHOP_EXPRESS_PRICE=15
SHOP_FREE_SHIPPING_FROM=150
SHOP_RETURNS_DAYS=30

# Modes de paiement acceptés (false = retiré)
SHOP_PAY_COD=true                  # paiement à la livraison (choix par défaut)
SHOP_PAY_CARD=true
SHOP_PAY_D17=true
SHOP_PAY_FLOUCI=true
SHOP_PAY_VIREMENT=true
Ne jamais changer APP_KEY après la mise en ligne Cette clé chiffre les mots de passe SMTP enregistrés dans l'admin, les clés des kits de paiement et les secrets des connecteurs API. Si elle change, tout cela devient illisible : il faut alors ressaisir le SMTP et les kits de paiement, et renouveler toutes les clés API. Gardez une copie du .env dans un endroit sûr.
Mise à jour d'un site déjà en ligne (V64 ou avant) vers V65 Votre .env existant n'est pas remplacé. Ouvrez backend/env.example.txt, copiez toute la rubrique # --- Sécurité (VERSION 65) --- (de FORCE_HTTPS à BCRYPT_ROUNDS) et collez-la dans le .env du serveur, puis lancez php artisan config:clear. Vérifiez au passage APP_ENV=production, APP_DEBUG=false et APP_URL=https://....
Réglages du .env ou réglages du back office ? Le .env donne les valeurs par défaut. Les réglages enregistrés dans le back office (livraison, zones, moyens de paiement, SMTP, textes…) les remplacent ensuite. Exemple : un SMTP saisi dans Paramètres > Email SMTP passe avant les lignes MAIL_... du .env.

8. Installer la base et le premier administrateur

À lancer dans le dossier du projet Laravel, en local (si votre base locale est la même que celle du serveur) ou directement sur le serveur en SSH.

php artisan key:generate                       # une seule fois, au tout début
php artisan migrate --force                    # crée toutes les tables
php artisan db:seed --class=CatalogSeeder --force   # facultatif : produits de démonstration
php artisan db:seed --class=CouponSeeder --force    # facultatif : codes BIENVENUE10 et LIVRAISON
php artisan storage:link                       # rend visibles les images importées
php artisan amy:create-admin vous@votre-domaine.tn "Votre Nom"
# → le mot de passe est demandé (8 caractères minimum, lettre + chiffre), il ne s'affiche pas
# → la même commande remet un nouveau mot de passe si vous l'oubliez
php artisan config:cache
php artisan route:cache
php artisan view:cache

Tables créées par les migrations

DomaineTables
Comptes et techniqueusers, password_reset_tokens, sessions, personal_access_tokens, cache, cache_locks, jobs, failed_jobs
Cataloguecategories (3 niveaux), brands, products (avec vente flash, nouveauté, déstockage, produits liés, index de performance)
Ventescoupons, addresses, orders, order_items, order_histories, carts, séquence order_number_seq
Relation clientreviews, wishlist_items, newsletter_subscribers, contact_messages, product_views, customers
Contenu et réglagessettings, media, blog_posts
Chatbotchatbot_questions, chatbot_documents
Connecteurs APIapi_keys, api_request_logs, webhook_endpoints, webhook_deliveries
Ne pas utiliser l'ancienne méthode « tinker » La fin de INSTALLATION.txt (étape 1) crée un administrateur avec le rôle admin via php artisan tinker. Utilisez plutôt amy:create-admin : elle crée un Super Admin (seul rôle qui peut gérer les utilisateurs, les connecteurs API et les réglages sensibles) et vérifie la solidité du mot de passe.

9. Envoyer les fichiers chez l'hébergeur

Cas A — Vous pouvez choisir la racine du domaine (recommandé)

  1. Envoyez tout le projet Laravel (avec vendor/) dans /home/votrecompte/amyevolution/, par SFTP ou en archive .zip à décompresser avec le gestionnaire de fichiers de cPanel.
  2. cPanel > Domaines > votre domaine > Racine du document : amyevolution/public.

Cas B — Le domaine principal pointe obligatoirement vers public_html

  1. Envoyez le projet Laravel dans /home/votrecompte/amyevolution/ (hors de public_html).
  2. Déplacez le contenu de amyevolution/public/ dans public_html/.
  3. Modifiez public_html/index.php : remplacez les chemins __DIR__.'/../ par __DIR__.'/../amyevolution/. Il y a 3 lignes à changer :
    if (file_exists($maintenance = __DIR__.'/../amyevolution/storage/framework/maintenance.php')) { ... }
    require __DIR__.'/../amyevolution/vendor/autoload.php';
    $app = require_once __DIR__.'/../amyevolution/bootstrap/app.php';
  4. Indiquez à Laravel où se trouvent les pages HTML, dans le .env :
    SHOP_FRONTEND_PATH=/home/votrecompte/public_html
  5. Dans amyevolution/bootstrap/app.php, indiquez le nouveau dossier public (sinon storage:link et certaines adresses visent le mauvais dossier). Remplacez return Application::configure(...) par $app = Application::configure(...), laissez tout le reste identique jusqu'à ->create();, puis ajoutez à la fin :
    $app->usePublicPath('/home/votrecompte/public_html');
    return $app;
  6. Créez le lien des images en SSH :
    ln -s /home/votrecompte/amyevolution/storage/app/public /home/votrecompte/public_html/storage
    Sans SSH : créez une tâche cron unique avec cette même commande, puis supprimez-la.

Droits des dossiers

chmod -R 775 storage bootstrap/cache
chmod 640 .env

Variante sans accès SSH

10. Le fichier .htaccess (Apache)

Ce fichier remplace public/.htaccess (ou public_html/.htaccess). Il réunit les règles indispensables de Laravel et les règles de performance de la boutique (piège n°1).

Version V65 : copie exacte du fichier frontend/.htaccess livré dans l’archive (HTTPS forcé, fichiers sensibles bloqués, en-têtes de sécurité). Copiez le fichier tel quel plutôt que de le retaper.

# ============================================================
# AMYEVOLUTION — .htaccess (VERSION 47 : fusion Laravel + boutique)
# Peut maintenant écraser public/.htaccess de Laravel sans risque (piège n°1 réglé).
# Sans Laravel (dossier frontend seul, mode démo) : les règles Laravel sont ignorées
# automatiquement car index.php n'existe pas.
# ============================================================

DirectoryIndex index.php index.html

<IfModule mod_rewrite.c>
    <IfModule mod_negotiation.c>
        Options -MultiViews -Indexes
    </IfModule>
    RewriteEngine On

    # En-têtes d'autorisation (connexion admin, comptes clients, clés API)
    RewriteCond %{HTTP:Authorization} .
    RewriteRule .* - [E=HTTP_AUTHORIZATION:%{HTTP:Authorization}]
    RewriteCond %{HTTP:x-xsrf-token} .
    RewriteRule .* - [E=HTTP_X_XSRF_TOKEN:%{HTTP:X-XSRF-Token}]

    # VERSION 65 : HTTPS FORCÉ — un visiteur qui tape http:// est renvoyé vers https:// (redirection 301 définitive)
    # Fonctionne aussi derrière Cloudflare / un proxy (en-tête X-Forwarded-Proto).
    # Les adresses locales (localhost) ne sont pas redirigées, pour pouvoir tester sans certificat.
    RewriteCond %{HTTPS} !=on
    RewriteCond %{HTTP:X-Forwarded-Proto} !https [NC]
    RewriteCond %{HTTP_HOST} !^(localhost|127\.0\.0\.1)(:\d+)?$ [NC]
    RewriteRule ^ https://%{HTTP_HOST}%{REQUEST_URI} [L,R=301]

    # Facultatif : forcer aussi « www. » (une seule adresse pour Google). Retirez les # des 2 lignes :
    # RewriteCond %{HTTP_HOST} ^amyevolution\.tn$ [NC]
    # RewriteRule ^ https://www.amyevolution.tn%{REQUEST_URI} [L,R=301]

    # Fichiers cachés (.env, .git, .htpasswd…) : interdits, sauf .well-known (certificat SSL Let's Encrypt)
    RewriteRule (^|/)\.(?!well-known/) - [F,L]
    # Aucun script PHP ne peut s'exécuter depuis le dossier des images envoyées
    RewriteRule ^storage/.*\.(php\d?|phtml|phar)$ - [F,L,NC]

    # Les règles ci-dessous ne s'appliquent que si Laravel est présent (public/index.php)
    RewriteCond %{DOCUMENT_ROOT}/index.php !-f
    RewriteRule ^ - [L]

    # Pages SEO servies par Laravel (titre, description, données Google), même si le .html existe
    # VERSION 62 : les anciennes adresses .html des pages passent aussi par Laravel (redirection 301 vers /livraison, /contact…)
    RewriteRule ^(index|product|shop|page|blog|destockage|contact|faq|shipping|returns|warranty|cgv|legal|privacy|cookies)\.html$ index.php [L]

    # Retirer le / final
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_URI} (.+)/$
    RewriteRule ^ %1 [L,R=301]

    # Tout le reste : fichier existant, sinon Laravel (/api/..., /produit/..., /sitemap.xml, /robots.txt)
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteRule ^ index.php [L]
</IfModule>

# Interdire la lecture des fichiers sensibles (réglages, sauvegardes, bases, journaux, scripts)
<FilesMatch "^\.|^(artisan|composer\.(json|lock)|package(-lock)?\.json|INSTALLATION\.txt|env\.example\.txt)$|\.(log|sql|sqlite|bak|backup|old|orig|swp|ini|sh|zip|7z|tar|gz|rar)$">
    Require all denied
</FilesMatch>

# Masquer la version du serveur et de PHP
ServerSignature Off

# Compression des pages, scripts et styles (fichiers ~70 % plus légers)
<IfModule mod_deflate.c>
  AddOutputFilterByType DEFLATE text/html text/css text/plain application/javascript text/javascript application/json image/svg+xml application/manifest+json
</IfModule>
<IfModule mod_brotli.c>
  AddOutputFilterByType BROTLI_COMPRESS text/html text/css text/plain application/javascript text/javascript application/json image/svg+xml
</IfModule>

# Mise en cache navigateur : images 30 jours, CSS/JS 7 jours, pages HTML toujours fraîches
<IfModule mod_expires.c>
  ExpiresActive On
  ExpiresByType image/png "access plus 30 days"
  ExpiresByType image/jpeg "access plus 30 days"
  ExpiresByType image/webp "access plus 30 days"
  ExpiresByType image/avif "access plus 30 days"
  ExpiresByType image/svg+xml "access plus 30 days"
  ExpiresByType font/woff2 "access plus 1 year"
  ExpiresByType text/css "access plus 7 days"
  ExpiresByType application/javascript "access plus 7 days"
  ExpiresByType text/html "access plus 0 seconds"
</IfModule>

# Pages HTML, service worker et manifeste : toujours revérifiés (les nouvelles versions s'affichent tout de suite)
# CSS/JS : gardés en cache, mais chaque version porte un numéro (?v=47.0) qui force le rechargement
<IfModule mod_headers.c>
  <FilesMatch "\.(html|webmanifest)$|^sw\.js$">
    Header set Cache-Control "no-cache, must-revalidate"
  </FilesMatch>
  # ---- VERSION 65 : PROTECTIONS DU NAVIGATEUR ----
  Header always set X-Content-Type-Options "nosniff"
  Header always set Referrer-Policy "strict-origin-when-cross-origin"
  Header always set X-Frame-Options "SAMEORIGIN"
  # HSTS : le navigateur retient d'utiliser TOUJOURS https:// pendant 1 an (envoyé seulement en HTTPS)
  Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" "expr=%{HTTPS} == 'on' || %{HTTP:X-Forwarded-Proto} == 'https'"
  # Caméra, micro, paiement par navigateur… désactivés ; position autorisée seulement pour le site
  Header always set Permissions-Policy "camera=(), microphone=(), payment=(), usb=(), interest-cohort=(), geolocation=(self)"
  Header always set Cross-Origin-Opener-Policy "same-origin-allow-popups"
  Header always set X-Permitted-Cross-Domain-Policies "none"
  Header always unset X-Powered-By
  Header unset X-Powered-By
  # Politique de contenu : seules les sources utilisées par la boutique sont autorisées
  # (polices Google, Font Awesome, Swiper, Chart.js, cartes Google). Bloque les plug-ins, l'intégration
  # du site dans un autre site, et transforme automatiquement tout lien http:// restant en https://.
  Header always set Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline' https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; style-src 'self' 'unsafe-inline' https://fonts.googleapis.com https://cdn.jsdelivr.net https://cdnjs.cloudflare.com; font-src 'self' data: https://fonts.gstatic.com https://cdnjs.cloudflare.com; img-src 'self' data: blob: https:; media-src 'self' https:; connect-src 'self' https:; frame-src 'self' https://www.google.com https://maps.google.com https://www.youtube.com https://www.youtube-nocookie.com; worker-src 'self'; manifest-src 'self'; object-src 'none'; base-uri 'self'; form-action 'self'; frame-ancestors 'self'; upgrade-insecure-requests"
</IfModule>
Pourquoi la ligne « Pages SEO servies par Laravel » ? Sans elle, Apache envoie directement index.html, product.html et shop.html, sans passer par Laravel. Google ne reçoit alors ni le titre ni la description de chaque produit, et les anciennes adresses ne sont pas redirigées vers les adresses propres (/produit/...).

Si l'hébergeur utilise Nginx

Nginx ne lit pas le fichier .htaccess. Le modèle complet est fourni dans docs/nginx-securite.conf : redirection http → https, redirection sans www → www, certificat Let’s Encrypt, en-têtes de sécurité et fichiers interdits. Remplacez le domaine, les chemins et la version de PHP, puis testez avec sudo nginx -t && sudo systemctl reload nginx. Même sans ce fichier, Laravel force le HTTPS et ajoute les en-têtes (fichier SecureHeaders.php).

# ============================================================
# AMYEVOLUTION — VERSION 65 : exemple nginx (HTTPS obligatoire + sécurité)
# À utiliser seulement si l'hébergeur fonctionne avec nginx (nginx ne lit pas le .htaccess).
# Remplacez les chemins et le domaine, puis : sudo nginx -t && sudo systemctl reload nginx
# ============================================================

# 1) Tout ce qui arrive en http:// part vers https:// (301 définitive)
server {
    listen 80;
    listen [::]:80;
    server_name amyevolution.tn www.amyevolution.tn;

    # Renouvellement du certificat Let's Encrypt
    location ^~ /.well-known/acme-challenge/ { root /var/www/letsencrypt; }

    location / { return 301 https://www.amyevolution.tn$request_uri; }
}

# 2) Sans « www » en https -> avec « www » (une seule adresse pour Google)
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name amyevolution.tn;
    ssl_certificate     /etc/letsencrypt/live/amyevolution.tn/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/amyevolution.tn/privkey.pem;
    return 301 https://www.amyevolution.tn$request_uri;
}

# 3) Le site
server {
    listen 443 ssl;
    listen [::]:443 ssl;
    http2 on;
    server_name www.amyevolution.tn;
    root /var/www/amyevolution/public;
    index index.php index.html;

    ssl_certificate     /etc/letsencrypt/live/amyevolution.tn/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/amyevolution.tn/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers off;
    ssl_session_cache shared:SSL:10m;
    ssl_session_tickets off;

    server_tokens off;
    client_max_body_size 8m;

    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-Frame-Options "SAMEORIGIN" always;
    add_header Referrer-Policy "strict-origin-when-cross-origin" always;
    add_header Permissions-Policy "camera=(), microphone=(), payment=(), usb=(), interest-cohort=(), geolocation=(self)" always;

    # Fichiers cachés et sensibles interdits (sauf .well-known)
    location ~ /\.(?!well-known) { deny all; }
    location ~* \.(sql|sqlite|bak|backup|old|orig|swp|log|ini|sh|zip|7z|tar|gz|rar)$ { deny all; }
    location ~* ^/(artisan|composer\.(json|lock)|package(-lock)?\.json)$ { deny all; }
    # Aucun PHP dans le dossier des images envoyées
    location ~* ^/storage/.*\.(php\d?|phtml|phar)$ { deny all; }

    location / { try_files $uri $uri/ /index.php?$query_string; }

    location ~ \.php$ {
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name;
        fastcgi_param HTTPS on;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_hide_header X-Powered-By;
    }
}

11. HTTPS et nom de domaine

Ordre obligatoire (V65) 1) activer le certificat SSL → 2) vérifier que https:// s'ouvre avec le cadenas → 3) seulement ensuite envoyer le .htaccess V65 et la rubrique « Sécurité » du .env. Si la redirection est active sans certificat, les visiteurs voient « Votre connexion n'est pas privée ».

1Faire pointer le domaine

Chez le vendeur du domaine, faites pointer les DNS vers l'hébergeur (enregistrements A pour @ et www, ou les serveurs DNS de l'hébergeur). La propagation peut prendre de 1 à 24 heures.

2Activer le certificat SSL (Let's Encrypt, gratuit)

Type d'hébergementComment faire
cPanel (mutualisé)cPanel > SSL/TLS Status > cocher le domaine et www > Run AutoSSL. Renouvellement automatique.
PleskSites web et domaines > SSL/TLS Certificates > « Let's Encrypt » > cocher « Inclure www ».
VPS Apachesudo apt install certbot python3-certbot-apache puis sudo certbot --apache -d votre-domaine.tn -d www.votre-domaine.tn
VPS nginxsudo apt install certbot python3-certbot-nginx puis sudo certbot --nginx -d votre-domaine.tn -d www.votre-domaine.tn. Ensuite, reprenez docs/nginx-securite.conf (section 10).
Cloudflare devant le siteSSL/TLS > mode Full (strict) (jamais « Flexible », qui crée une boucle de redirection). Le site reconnaît Cloudflare grâce à l'en-tête X-Forwarded-Proto.

Test du renouvellement automatique (VPS) : sudo certbot renew --dry-run

3Vérifier le cadenas

Ouvrez https://www.votre-domaine.tn : le cadenas doit apparaître, sans alerte.

4Activer la redirection http → https (V65)

  1. Envoyer le .htaccess V65 (section 10) dans public/ (ou public_html/). Les lignes « HTTPS FORCÉ » sont déjà actives.
  2. Copier la rubrique « Sécurité » de backend/env.example.txt dans le .env du serveur (section 7).
  3. .env : APP_URL doit être exactement l'adresse finale, avec https et www si vous l'utilisez.
  4. php artisan config:clear && php artisan route:clear puis php artisan config:cache.
  5. Facultatif : pour une seule adresse avec « www », retirer les # des 2 lignes « forcer aussi www » du .htaccess (adapter le domaine).

5Tester la redirection

# Doit répondre « 301 » et « Location: https://... »
curl -I http://www.votre-domaine.tn
curl -I http://www.votre-domaine.tn/shop.html

# Doit répondre « 200 » avec la ligne strict-transport-security
curl -I https://www.votre-domaine.tn

Sans terminal : tapez http://www.votre-domaine.tn dans le navigateur, l'adresse doit devenir https://. Puis notez la sécurité sur https://securityheaders.com (note A attendue) et https://www.ssllabs.com/ssltest/ (note A attendue).

Comment fonctionne la double protection Apache : le .htaccess redirige avant même que PHP démarre (le plus rapide).
Laravel : le fichier app/Http/Middleware/SecureHeaders.php refait la même vérification sur toutes les pages et l'API. Il fonctionne aussi sous nginx. Une page est redirigée en 301 ; un envoi de formulaire en 308 pour ne jamais perdre les données.
Navigateur : l'en-tête HSTS lui fait retenir le HTTPS pendant 1 an ; il ne tente même plus http://.
Les adresses localhost et 127.0.0.1 ne sont jamais redirigées (tests sur l'ordinateur).

12. Emails (SMTP)

Emails envoyés par le site

EmailQuandDestinataire
Confirmation de commandeAprès une commandeClient
Changement de statutCommande en cours, expédiée, livrée ou annulée, si la case « Prévenir le client » est cochéeClient
Commande modifiéeL'administrateur modifie les articles ou l'adresse, avec la case cochéeClient
Mot de passe oubliéLe client clique sur « Mot de passe oublié » (le lien ouvre mot-de-passe.html)Client
Alerte administrateurNouvelle commande, nouveau message de contact, demande de rappel du chatbotMAIL_ADMIN_ADDRESS ou email de la boutique
Email d'essaiBouton de test du back office ou php artisan amy:mail-testAdresse saisie

Les modèles d'emails se trouvent dans resources/views/emails/*.blade.php.

Réglages courants

FournisseurServeurPort / chiffrementIdentifiant
Boîte cPanel de votre domaine (conseillé)mail.votre-domaine.tn465 / SSLadresse email complète
OVH (Zimbra / MX Plan)ssl0.ovh.net465 / SSLadresse email complète
Gmail / Google Workspacesmtp.gmail.com587 / TLSadresse + mot de passe d'application (validation en 2 étapes obligatoire)
Microsoft 365smtp.office365.com587 / TLSadresse complète (SMTP authentifié à activer)

Tester

php artisan amy:mail-test vous@gmail.com
# → « Email d'essai envoyé » ou l'erreur exacte du serveur SMTP

Pour tester sans rien envoyer : MAIL_MAILER=log. Les emails sont alors écrits dans storage/logs/laravel.log.

Pour que les emails n'arrivent pas en spam

13. Tâche planifiée (cron)

Elle relance les envois automatiques ratés vers vos logiciels partenaires et nettoie le journal des connecteurs (journal API gardé 90 jours, envois gardés 30 jours).

cPanel > Tâches Cron > « Une fois par minute » (ou toutes les 5 minutes si l'offre l'impose) :

cd /home/votrecompte/amyevolution && /usr/local/bin/php artisan schedule:run >> /dev/null 2>&1

Le chemin de PHP varie selon l'hébergeur (/usr/local/bin/php, /opt/cpanel/ea-php83/root/usr/bin/php…). En SSH, la commande which php le donne. Vérifiez que c'est bien la version 8.2 ou plus.

Les articles de blog programmés n'ont pas besoin du cron : ils apparaissent tout seuls à leur date (dans les 5 minutes, à cause du cache).

14. Paiement en ligne (Flouci, Konnect, ClicToPay)

Par défaut, le paiement à la livraison fonctionne sans rien configurer. Le serveur recalcule toujours prix, stock, code promo, livraison et total : un client ne peut pas modifier les prix depuis son navigateur.

KitClés à obtenir auprès du prestataireMode test
FlouciClé publique (app public) + clé privée (app secret)Case « Mode test » du moyen de paiement
KonnectIdentifiant du portefeuille (wallet ID) + clé API (x-api-key)Bac à sable api.sandbox.konnect.network
ClicToPay (SMT)Identifiant API marchand + mot de passe APIBac à sable test.clictopay.com
  1. Ouvrez un compte marchand chez le prestataire. Le contrat et la validation peuvent prendre plusieurs jours.
  2. Dans le back office : Paramètres > Paiement > Kits de paiement en ligne, collez les clés. Elles sont chiffrées sur le serveur et ne sont jamais renvoyées au navigateur.
  3. Testez en mode test, avec une vraie commande de bout en bout.
  4. Décochez le mode test et faites un vrai paiement de faible montant.

Au retour de la page du prestataire, le site appelle GET /api/v1/payments/verify : le serveur vérifie lui-même le paiement auprès du prestataire avant de marquer la commande comme payée. Un moyen de paiement en ligne n'apparaît au client que lorsque ses clés sont enregistrées.

Ajouter un autre prestataire : créer une classe qui implémente app/Payments/PaymentGateway.php et l'inscrire dans PaymentGatewayRegistry.php.

15. Chatbot IA (facultatif)

Sans réglage, le chatbot AMYbot fonctionne entièrement dans le navigateur. Il lit le catalogue, les prix, le stock, la FAQ et les pages Livraison, Retours, Garantie et CGV. Il comprend aussi le tounsi, l'anglais, l'écriture SMS et les fautes de frappe.

Avec une clé d'IA, il formule des réponses plus naturelles. Le serveur lui fournit seulement les produits trouvés en base, les documents et les réglages : il ne peut pas inventer de prix ni de stock.

AI_API_KEY=sk-...                         # clé du fournisseur
AI_BASE_URL=https://api.openai.com/v1     # tout fournisseur compatible OpenAI (Mistral, Groq, OpenRouter…)
AI_MODEL=gpt-4o-mini                      # modèle économique conseillé

16. API publique de la boutique

Base : https://www.votre-domaine.tn/api/v1. Réponses en JSON. Les limites (« 10/min ») sont par visiteur.

AdresseRôleLimite
GET /catalog · /catalog.jsCatalogue allégé pour les listes (cache serveur + réponse 304)—
GET /productsFiltres : cat, brand, q, min, max, in_stock=1, sort=newest|price_asc|price_desc|popular|rating|name, per_page—
GET /products/featured · flash-sale · new · bestsellersListes de la page d'accueil—
GET /products/{slug ou id}Fiche complète—
GET /products/{id}/relatedProduits similaires—
GET · POST /products/{id}/reviewsLire / déposer un avis (publié après modération)5/min
GET /categories · /categories/{slug} · /brandsCatégories et marques—
GET /search?q= · /search/suggestions?q=Recherche (nom, référence, description complète)60/min
POST /ordersPasser une commande (total recalculé par le serveur)10/min
POST /orders/trackSuivi par téléphone + numéro de commande30/min
GET /payments/verifyRetour de la page de paiement30/min
POST /coupons/check{ code, subtotal } → validité du code promo20/min
GET /blogArticles publiés—
POST /contact · /newsletter/subscribeFormulaire de contact / inscription newsletter (champ piège anti-robots)5 · 10/min
POST /chatbot/ask · GET /chatbot/knowledge · POST /chatbot/log · PATCH /chatbot/log/{id} · POST /chatbot/handoffChatbot : réponse IA, documents, questions pour l'apprentissage, pouce haut/bas, demande de rappel12 · — · 30 · 30 · 5/min
POST /auth/register · login · forgot-password · reset-passwordComptes clients10/min
POST /auth/logout · GET /auth/me · PUT /auth/profile · PUT /auth/password · GET /me/orders · /me/wishlist…Avec Authorization: Bearer <jeton client> (valable 30 jours)—

Adresses SEO servies par Laravel : /, /boutique, /produit/{slug}, /categorie/{slug}, /marque/{slug}, /sitemap.xml, /robots.txt. Contrôle de santé : /up.

17. API d'administration

Connexion : POST /api/v1/admin/login { email, password } → { token, user }. 5 essais par minute au maximum. Le jeton « back-office » est valable 12 heures. Un jeton client de la boutique ne donne pas accès à l'admin.

Toutes les adresses ci-dessous commencent par /api/v1/admin et exigent Authorization: Bearer <jeton admin>.

DomaineAdresses
SessionPOST /logout · GET /me
UtilisateursGET · POST /users · PUT · DELETE /users/{id} (modification : Super Admin)
CommandesGET /orders (500 plus récentes) · PATCH /orders/{numéro} { status } · PUT /orders/{numéro} · GET /orders/{numéro}/history
ProduitsGET · POST /products · PUT · DELETE /products/{id} · PATCH /products/{id}/stock
CatégoriesGET · POST /categories · PATCH /categories/reorder · PUT · DELETE /categories/{id}
MarquesGET · POST /brands · PUT · DELETE /brands/{id}
CouponsGET · POST /coupons · PUT · DELETE /coupons/{id} — corps : { code, type: percent|fixed|shipping, value, min, max, expires: AAAA-MM-JJ, active }
BlogGET · POST /blog · PUT · DELETE /blog/{id}
Messages et newsletterGET /messages · PATCH · DELETE /messages/{id} · GET /newsletter · DELETE /newsletter/{id}
Avis et clientsGET /reviews · PATCH · DELETE /reviews/{id} · GET · POST /customers · PUT · DELETE /customers/{id}
RéglagesGET · PUT /settings · PUT /settings/mail · POST /settings/mail/test · POST /settings/smtp/test · PUT /settings/gateways/{kit}
ImagesPOST /media → { ok, url } (fichier rangé dans storage/app/public/uploads)
ChatbotGET /chatbot · POST /chatbot/questions/bulk · PATCH · DELETE /chatbot/questions/{id} · POST /chatbot/documents · DELETE /chatbot/documents/{id}
ConnecteursGET /connectors · clés : POST /connectors/keys, PATCH · DELETE /connectors/keys/{id}, POST /connectors/keys/{id}/rotate · destinataires : POST /connectors/webhooks, PUT · DELETE /connectors/webhooks/{id}, POST …/{id}/test, POST …/{id}/rotate

18. Connecteurs API (ERP, facturation, stock)

Les connecteurs font travailler le site avec vos autres logiciels, dans les deux sens. Ils exigent HTTPS, CACHE_STORE=database (ou redis) et la tâche cron.

« Consommer » : un logiciel partenaire lit et écrit chez vous

Base : https://www.votre-domaine.tn/api/connect/v1. La clé se crée dans Back office > Connecteurs API. Elle n'est affichée qu'une seule fois.

En-têteValeur
AuthorizationBearer amy_live_…
X-Amy-TimestampHeure actuelle en secondes UTC (refusée au-delà de 5 minutes d'écart)
X-Amy-Nonce16 à 64 caractères aléatoires, différents à chaque requête (anti-rejeu)
X-Amy-SignatureHMAC-SHA256 en hexadécimal, calculé avec le code de signature

Texte à signer, une valeur par ligne :

horodatage
nonce
MÉTHODE
/api/connect/v1/orders?created_from=2026-11-01
sha256(corps)          ← corps vide = sha256 de ""
ActionAdressePermission
Lister / lire les clientsGET /customers · GET /customers/{id}customers:read
Créer / modifier un clientPOST /customers · PUT /customers/{id}customers:write
Lister / lire les produitsGET /products · GET /products/{référence}products:read
Modifier prix, stock, désignationPATCH /products/{référence} · PUT /products (lot de 500)products:write
Lister / lire les commandesGET /orders · GET /orders/{numéro}orders:read
Créer une commandePOST /orders (external_ref unique : pas de doublon si renvoyée)orders:write

Filtres : ?created_from=2026-11-01&created_to=2026-11-30 ou ?updated_since=2026-11-01T00:00:00Z. Pages : ?page=2&per_page=100 (200 maximum). Montants en millimes (3 799,000 TND = 3799000). Dates ISO 8601 en UTC.

« Envoyer » : le site prévient vos logiciels

Événements : customer.created, customer.updated, nouvelle commande, commande modifiée. En-têtes reçus : X-Amy-Event, X-Amy-Delivery, X-Amy-Timestamp, X-Amy-Signature = HMAC-SHA256(secret, horodatage + "." + corps). Le logiciel doit répondre avec un code 2xx. En cas d'échec, le site réessaie après 1 min, 5 min, 15 min, 1 h, 4 h et 12 h (grâce au cron).

19. Référencement Google (SEO)

À faire après la mise en ligne

  1. Vérifier /produit/un-produit > clic droit > « Code source » : le titre du produit doit apparaître dans le code.
  2. Tester une fiche sur search.google.com/test/rich-results.
  3. Google Search Console : ajouter le site, puis envoyer /sitemap.xml.
  4. Créer la fiche Google Business Profile (adresse du magasin) pour apparaître sur Google Maps.
  5. Back office > Agent SEO : lancer l'analyse et corriger les fiches signalées.

20. Performances et cache

21. Sécurité : la liste à cocher

21 bis. Sécurité renforcée V65 (détail technique)

Fichiers concernés

FichierRôle
frontend/.htaccessRedirection http → https, fichiers sensibles bloqués, aucun PHP dans storage/, en-têtes de sécurité (Apache)
backend/app/Http/Middleware/SecureHeaders.php (nouveau)Même protection côté Laravel, pour Apache et nginx
backend/bootstrap/app.phpActive SecureHeaders sur toutes les réponses ; liens générés en https:// en production
backend/config/shop.phpNouveau réglage force_https (lit FORCE_HTTPS du .env)
backend/env.example.txtNouvelle rubrique « Sécurité » à copier dans le .env
docs/nginx-securite.conf (nouveau)Modèle complet pour les serveurs nginx

En-têtes de sécurité envoyés

En-têteProtection
Strict-Transport-SecurityLe navigateur utilise toujours HTTPS pendant 1 an (envoyé seulement en HTTPS)
Content-Security-PolicySeules les sources de la boutique peuvent charger scripts, styles et polices (site, Google Fonts, Font Awesome, Swiper, Chart.js, cartes Google, YouTube). Plug-ins interdits, liens http:// restants passés en https://
X-Frame-Options + frame-ancestorsLe site ne peut pas être affiché dans un autre site (anti « clickjacking »)
X-Content-Type-OptionsLe navigateur ne devine pas le type d'un fichier (anti faux fichier image)
Referrer-PolicyLes adresses complètes ne sont pas transmises aux autres sites
Permissions-PolicyCaméra, micro, USB et paiement navigateur désactivés ; position autorisée seulement pour le site
Cross-Origin-Opener-PolicyIsole les fenêtres (les pop-ups de paiement restent possibles)
Cache-Control: no-storeCompte client, commandes, paiements, admin et connecteurs jamais gardés en cache
X-Powered-By / ServerSignatureRetirés : les versions d'Apache et de PHP ne sont plus affichées

Fichiers bloqués au public

Fichiers cachés (.env, .git, .htpasswd…) sauf .well-known (certificat), sauvegardes et archives (.sql .sqlite .bak .old .zip .7z .tar .gz .rar), journaux (.log), .ini .sh, artisan, composer.json/lock, package.json, INSTALLATION.txt, env.example.txt. Les fichiers .php placés dans storage/ ne s'exécutent jamais.

Ajouter un nouveau service externe (vidéo, statistiques, chat…) La politique de contenu bloque tout ce qui n'est pas prévu. Si un nouveau script ou une nouvelle vidéo ne s'affiche pas, ouvrez la console du navigateur (F12) : un message « Content Security Policy » indique l'adresse bloquée. Ajoutez-la aux deux endroits : la ligne Content-Security-Policy du .htaccess et la fonction csp() de SecureHeaders.php (même liste). Exemple Google Analytics : ajouter https://www.googletagmanager.com dans script-src.

22. Sauvegardes

QuoiCommentFréquence
Base PostgreSQLcPanel > Sauvegarde > base PostgreSQL, ou pg_dump -Fc -U utilisateur base > amy-$(date +%F).dumpChaque jour
Images importéesstorage/app/public/Chaque semaine
Fichier .envCopie chiffrée dans un coffre (gestionnaire de mots de passe)À chaque changement
CodeArchive du projet (ou dépôt Git privé)À chaque mise à jour

Restauration de la base : pg_restore -c -U utilisateur -d base amy-2026-11-08.dump. Testez une restauration sur une base vide au moins une fois.

23. Mettre à jour le site plus tard

  1. Faire une sauvegarde de la base et des fichiers.
  2. Mettre le site en maintenance : php artisan down --secret="un-code" (vous gardez l'accès via /un-code).
  3. Envoyer les fichiers modifiés indiqués dans INSTALLATION.txt pour chaque version. Pour frontend/ : penser aussi à la copie dans public_html/frontend/ (piège n°2) et ne jamais écraser le .htaccess fusionné.
  4. php artisan migrate --force si la version ajoute une migration.
  5. php artisan optimize:clear puis php artisan config:cache && php artisan route:cache && php artisan view:cache
  6. php artisan up, puis vérifier la boutique, une commande test et le back office.

24. Dépannage : erreurs fréquentes

SymptômeCause probableSolution
Page blanche ou « 500 Server Error »Erreur PHP, droits, .envLire storage/logs/laravel.log. Vérifier les droits de storage/ et bootstrap/cache. Passer APP_DEBUG=true quelques minutes seulement.
/api/v1/catalog renvoie 404 et la boutique reste en mode démo.htaccess de Laravel écrasé (piège n°1)Remettre le .htaccess fusionné (section 10).
« could not find driver »Extension pdo_pgsql absenteL'activer dans le sélecteur PHP de cPanel.
« SQLSTATE[08006] connection refused »Hôte, port ou mot de passe de la baseVérifier DB_*, essayer localhost au lieu de 127.0.0.1, puis config:clear.
« column first_name does not exist »Migration users de Laravel lancée (piège n°3)Base vide : supprimer les tables, retirer les 3 migrations d'origine, relancer migrate.
Connexion admin : page introuvable après l'identificationChemins relatifs (piège n°2)Copie de frontend/ dans public_html/frontend/ et backend/login.html dans public_html/backend/.
Connexion admin : « 401 » alors que le mot de passe est justeEn-tête Authorization perdu par ApacheLignes HTTP_AUTHORIZATION du .htaccess fusionné.
Images importées invisiblesLien storage absentphp artisan storage:link (ou la commande ln -s du cas B).
Emails non reçusSMTP, port bloqué, spamphp artisan amy:mail-test, lire l'erreur, vérifier SPF/DKIM, essayer le port 465 puis 587.
Les modifications n'apparaissent pasCache serveur ou navigateurphp artisan optimize:clear, augmenter le ?v= des fichiers, rechargement forcé (Ctrl+F5).
Google ne voit pas les produitsAPP_ENV différent de production, ou pages HTML servies sans LaravelVérifier /robots.txt et la règle « Pages SEO » du .htaccess.
« 429 Too Many Requests »Limite anti-robots atteinteAttendre une minute (normal en cas d'essais répétés).
Connecteur API : « signature invalide »Horloge, texte signé, corps différentHeure UTC exacte, chemin avec paramètres, sha256 du corps exact envoyé.
Kits de paiement ou SMTP illisibles après un déplacementAPP_KEY modifiéeRemettre l'ancienne clé, ou ressaisir les mots de passe et renouveler les clés.
« Votre connexion n'est pas privée » (V65)Redirection HTTPS active mais certificat SSL absent ou expiréActiver le certificat (section 11). En urgence : FORCE_HTTPS=false dans .env + config:clear, et remettre le # devant les lignes « HTTPS FORCÉ » du .htaccess.
« Trop de redirections » / ERR_TOO_MANY_REDIRECTSCloudflare en mode « Flexible », ou proxy qui n'envoie pas X-Forwarded-ProtoCloudflare : SSL/TLS en Full (strict). Proxy nginx : ajouter proxy_set_header X-Forwarded-Proto $scheme;.
Une police, une vidéo ou un script ne s'affiche plus (V65)Bloqué par la politique de contenu (CSP)F12 > Console : lire l'adresse bloquée et l'ajouter (section 21 bis).
Cadenas barré / « contenu mixte »Image ou lien saisi en http:// dans l'adminCorriger l'adresse en https:// (la CSP tente déjà de la passer en https automatiquement).
Erreur 500 juste après l'envoi du .htaccessModule Apache mod_headers ou mod_rewrite absent, ou directive refusée par l'hébergeurDemander l'activation à l'hébergeur ; en attendant, mettre un # devant la ligne ServerSignature Off.

25. Commandes utiles (aide-mémoire)

CommandeEffet
php artisan amy:create-admin email "Nom"Créer un Super Admin ou réinitialiser son mot de passe
php artisan amy:mail-test emailEnvoyer un email d'essai
php artisan amy:connectors-maintenanceRelancer les envois ratés et nettoyer le journal (lancée par le cron)
php artisan migrate --forceCréer les nouvelles tables
php artisan migrate:statusVoir les migrations déjà passées
php artisan db:seed --class=CatalogSeederProduits de démonstration (sans doublons)
php artisan db:seed --class=CouponSeederCodes BIENVENUE10 et LIVRAISON
php artisan storage:linkLien des images importées
php artisan optimize:clearVider tous les caches
php artisan config:cache · route:cache · view:cacheAccélérer le site en production
php artisan route:list --path=apiLister les adresses de l'API
php artisan down --secret="code" / php artisan upMaintenance on / off
tail -n 100 storage/logs/laravel.logLire les dernières erreurs

26. Liste finale avant ouverture