Retour aux projets
Plateforme SaaS de création de boutiques en ligne

GoStore

Un équivalent de Shopify pensé pour les commerçants algériens : des boutiques en arabe et en français, des moyens de paiement locaux et des intégrations de livraison qui fonctionnent vraiment en Algérie.

Voir le site
Next.jsTypeScriptPostgreSQLpg-bossCaddyAdonisJS (v1)

Pourquoi un énième créateur de boutiques en ligne ?

Shopify ne prend pas en charge les moyens de paiement algériens. WooCommerce demande un hébergement et des compétences techniques que la plupart des commerçants locaux n'ont pas. Quant aux rares alternatives locales, elles sont soit dépassées (des scripts PHP datant de 2015), soit trop chères pour ce qu'elles proposent.

GoStore comble ce manque : une plateforme hébergée sur laquelle un commerçant peut s'inscrire, ajouter ses produits et commencer à vendre en moins d'une heure, avec les moyens de paiement dont ses clients disposent réellement.

Ce qui fait la différence

Bilingue par défaut

Chaque boutique est livrée en arabe et en français. Il ne s'agit pas d'un simple sélecteur de langue : cela impacte toute la mise en page.

  • L'arabe active une mise en page RTL (de droite à gauche) sur l'ensemble de la boutique
  • Les fiches produits peuvent être rédigées dans les deux langues, avec un repli sur la langue principale
  • Le panneau d'administration est lui aussi bilingue : le commerçant peut passer de l'arabe au français dans les paramètres
  • Les e-mails de notification (confirmation de commande, suivi d'expédition) sont envoyés dans la langue du navigateur du client

La gestion du RTL a été délicate. Le préfixe rtl: de Tailwind couvre la plupart des cas, mais certains composants (galeries d'images, carrousels, barres de progression) ont nécessité des ajustements RTL manuels. J'ai développé un composable useDirection() qui renvoie le sens d'écriture courant et l'applique au niveau de chaque composant.

Paiements locaux intégrés

  • SlickPay : cartes CIB et Edahabia sur une page de paiement hébergée. Le client paie, revient sur la boutique, et la commande est confirmée auprès de SlickPay avant de passer « payée »
  • Paiement à la livraison : l'option par défaut. La commande est marquée cod et le commerçant gère l'encaissement lui-même

Chaque moyen de paiement a son propre format de callback et ses propres délais de confirmation. J'ai conçu une interface PaymentGateway qui fait abstraction de ces différences :

interface PaymentGateway {
  createPayment(order: Order): Promise<PaymentResponse>
  verifyPayment(reference: string): Promise<PaymentStatus>
  handleWebhook(payload: unknown): WebhookEvent | null
}

Ajouter un nouveau prestataire de paiement revient à implémenter cette interface et à l'enregistrer dans la configuration. Aucune modification du parcours de commande n'est nécessaire.

Suivi des livraisons

Intégration avec les sociétés de livraison locales :

  • ZR Express et Maystro : intégrations via API pour créer les expéditions et suivre les colis
  • Livraison manuelle : pour les commerçants qui assurent eux-mêmes leurs livraisons

Le commerçant peut définir un transporteur par défaut et des tarifs d'expédition par zone (wilaya). Au moment de la commande, le client voit un délai de livraison estimé en fonction de sa localisation.

Architecture technique

La stack : AdonisJS d'abord, Next.js aujourd'hui

La première version tournait sur AdonisJS avec Vue et Inertia. Je l'avais choisi pour avoir un backend TypeScript avec une structure proche de Laravel : des conventions familières, et du typage de la base de données jusqu'à l'interface.

J'ai depuis réécrit GoStore en Next.js, sur une seule base PostgreSQL, avec un worker de file d'attente pg-boss dans un processus séparé. L'application et le worker tournent sous PM2 sur un VPS, derrière Caddy, qui génère à la demande les certificats TLS des domaines personnalisés des commerçants : le commerçant pointe son domaine vers la plateforme et le HTTPS fonctionne tout seul.

Le déploiement reste volontairement simple : une seule instance, des releases versionnées derrière un lien symbolique, et un workflow GitHub Actions qui envoie la release, la compile, exécute les migrations Prisma et recharge PM2. Un déploiement blue/green ajouterait des pièces dont ce trafic n'a pas encore besoin.

Multi-tenant

Chaque commerçant dispose de son propre sous-domaine (storename.gostoredz.com). L'isolation entre tenants est assurée au niveau de la base de données :

-- Every table has a tenant_id column
ALTER TABLE products ADD COLUMN tenant_id UUID NOT NULL;
CREATE INDEX idx_products_tenant ON products(tenant_id);

-- Row-level security policy (PostgreSQL)
CREATE POLICY tenant_isolation ON products
  USING (tenant_id = current_setting('app.current_tenant')::uuid);

La variable de session app.current_tenant est définie par un middleware à chaque requête. Ainsi, même si un bug passe à travers la couche applicative, la base de données garantit l'isolation.

J'ai envisagé un schéma séparé par tenant (le pattern « schema-per-tenant »), mais le nombre de commerçants prévu (500 à 2000) ne justifiait pas cette complexité opérationnelle. Avec un schéma par tenant, les migrations deviennent pénibles, puisqu'il faut les exécuter sur chaque schéma, et le gain de performance sur les requêtes est minime à cette échelle.

Rendu des boutiques

Les boutiques sont rendues côté serveur pour le référencement (SEO), mais s'appuient sur l'hydratation côté client pour l'interactivité (ajout au panier, recherche, filtres). Le pipeline de rendu :

  1. La requête arrive sur le StorefrontMiddleware, qui identifie le tenant à partir du sous-domaine
  2. Le contexte du tenant est défini dans la session de base de données
  3. La page est rendue côté serveur avec le thème du commerçant (logo, couleurs, préférences de mise en page)
  4. Côté client, Vue hydrate les éléments interactifs

Les thèmes reposent sur des modèles : le commerçant choisit parmi plusieurs mises en page et personnalise les couleurs et les polices. Je n'ai pas développé de moteur de thèmes complet, car la plupart des commerçants veulent simplement une boutique d'aspect professionnel, sans rien avoir à configurer.

Défis

Optimisation des images. Les commerçants envoient les photos de leurs produits depuis leur téléphone, souvent des JPEG de plus de 5 Mo. J'ai mis en place un pipeline de traitement d'images qui :

  1. Accepte le fichier original
  2. Génère des miniatures (300x300, 600x600, 1200x1200) avec Sharp
  3. Convertit les images en WebP, avec un repli en JPEG
  4. Stocke les originaux sur un stockage compatible S3 et sert les miniatures via un CDN

Résultat : le temps de chargement moyen des pages de listing produits est passé de 8 secondes à moins de 2 secondes.

Recherche. Une recherche plein texte sur plus de 10 000 produits par commerçant. J'ai utilisé l'index plein texte de la base de données avec un classement personnalisé qui met en avant :

  • Les correspondances exactes sur le titre
  • Les produits que le commerçant a marqués comme « mis en avant »
  • Les produits avec les niveaux de stock les plus élevés

Pas besoin d'Elasticsearch à cette échelle.

Résultats

  • Plus de 200 commerçants actifs en 6 mois
  • Temps moyen de mise en place d'une boutique : 45 minutes
  • Prise en charge de catalogues allant jusqu'à 10 000 références (SKU) par commerçant
  • Temps de chargement moyen des pages inférieur à 2 secondes (après optimisation des images)
  • 99,5 % de disponibilité sur l'ensemble des boutiques

Vous voulez lancer votre boutique ?

Si vous souhaitez vendre en ligne en Algérie avec le paiement SlickPay et une livraison intégrée, découvrez mon offre d'intégration du paiement en ligne en Algérie ou contactez-moi pour en parler.

Vous avez un projet similaire ?

Expliquez-moi où vous en êtes et ce qui vous bloque. Réponse rapide sur WhatsApp, devis gratuit.