Sur une marketplace où les objets échangés peuvent avoir une valeur élevée, la facture d'achat est une pièce qui rassure tout le monde : l'acheteur sur l'authenticité, le modérateur sur la légitimité de l'annonce, l'organisation sur sa propre exposition. On ne l'exige pas systématiquement, mais on voulait un signal fiable pour savoir quand la demander, et une façon de tracer cette demande qui survive à nos propres règles de conservation des données. Voici comment c'est construit.
Un niveau d'exigence calculé, pas déclaré à la main
Chaque catégorie du catalogue porte un champ invoice_requirement, avec trois valeurs possibles : aucune attente particulière, facture recommandée, ou facture vivement conseillée. C'est un réglage produit : une catégorie sensible aux contrefaçons peut être configurée plus strictement qu'une catégorie générique, sans toucher au code.
Une règle vient s'ajouter par-dessus ce réglage, indépendamment de la catégorie : dès que le prix d'achat déclaré par le vendeur dépasse 300 €, l'annonce passe automatiquement au niveau le plus strict, même si sa catégorie est configurée en dessous. Le seuil de prix prime toujours sur le paramétrage de catégorie.
Ce calcul est centralisé dans une seule classe, appelée dans deux contextes différents : côté vendeur, pour afficher une fourchette de prix de marché suggérée au moment de la mise en vente ; côté modérateur, pour enrichir la revue d'annonce avec ce même signal de prix et, en plus, le niveau d'exigence de facture. Les deux usages partagent le même calcul de hiérarchie de catégories, sans requête dupliquée.
Le prix d'achat déclaré sert aussi à cadrer la suggestion
Quand un vendeur renseigne le prix auquel il a lui-même acheté l'objet, ce prix ne sert pas qu'à déclencher l'exigence de facture au-delà de 300 €. Il vient aussi plafonner la fourchette de prix de marché qu'on lui suggère : la logique de base ne devrait pas proposer de revendre plus cher que ce qui a été payé à l'origine. Si le prix minimum observé sur le marché dépasse déjà le prix d'achat déclaré, la suggestion entière est supprimée plutôt que d'afficher une fourchette qui n'aurait pas de sens dans ce cas précis. Si le prix d'achat se situe à l'intérieur de la fourchette observée, elle est simplement écrêtée à ce plafond.
Côté modérateur, ce même plafonnement est recalculé et affiché comme information distincte : il permet de voir en un coup d'œil si le prix demandé par le vendeur a été contraint par son propre prix d'achat, ou s'il correspond à la fourchette de marché brute. C'est un sous-produit du même calcul, pas un mécanisme séparé : la fonction qui prépare la revue backstage renvoie un seul objet portant à la fois la fourchette de prix, le niveau d'exigence de facture et, le cas échéant, ce plafond.
Un bouton, une seule fois
Sur la page de vérification d'annonce du backstage, quand une facture est vivement conseillée et que le vendeur n'en a pas encore fourni, un bouton « Demander » apparaît à côté de l'option d'import manuel. Le modérateur clique, confirme, et un message est envoyé au vendeur : rédigé automatiquement, dans sa langue, avec son prénom et le titre de l'annonce.
Le serveur ne fait confiance à aucun état affiché à l'écran. Avant d'envoyer quoi que ce soit, l'endpoint recalcule lui-même le niveau d'exigence à partir du prix et de la catégorie de l'annonce, vérifie qu'une facture n'a pas déjà été fournie entre-temps, et vérifie qu'aucune demande n'a déjà été envoyée pour cette même annonce. Cette dernière garde existe pour un cas précis : un double clic, ou un onglet backstage resté ouvert sur un état périmé pendant qu'un autre modérateur traite la même annonce. Une seconde tentative renvoie une erreur au lieu de renvoyer un deuxième message.
Le corps du message n'est pas construit avec le mécanisme de traduction habituel. Ce mécanisme échappe les valeurs injectées pour un rendu HTML, alors que le fil de messagerie affiche du texte brut. Utiliser la traduction standard ici aurait fait apparaître les caractères échappés tels quels dans la conversation. Le prénom du vendeur et le titre de l'annonce sont donc substitués directement dans le gabarit du message avant l'envoi.
Deux effets de bord accompagnent l'envoi. Le vendeur reçoit une notification email l'informant qu'un nouveau message l'attend, avec le contenu du message repris dans le corps de l'email — il n'a pas besoin d'être connecté à la plateforme pour être averti. Et un message est posté sur un canal Slack interne dédié aux échanges de messagerie, pour que l'équipe garde une visibilité sur le volume de demandes de facture sans avoir à interroger la base.
Pourquoi la trace ne pouvait pas vivre dans les messages
La demande envoyée au vendeur est un message comme un autre, stocké dans le fil de conversation support. Mais le backstage a besoin de savoir, en permanence, si une demande a déjà été faite pour une annonce donnée : pour afficher « Demandée le… » à la place du bouton, et pour bloquer une relance. Déduire cet état en cherchant dans l'historique des messages aurait été la solution la plus simple à écrire.
C'est aussi la solution qui ne tient pas dans la durée. Les messages de la plateforme sont soumis à une politique de rétention RGPD de trois ans : passé ce délai, ils sont purgés. Une trace de modération qui disparaîtrait avec le message source cesserait de faire son travail au bout de trois ans, silencieusement, sans qu'on s'en aperçoive avant qu'un modérateur redemande une facture déjà réclamée.
La solution retenue sépare les deux : la demande reste un message ordinaire, soumis à la même politique de rétention que n'importe quelle conversation, pendant que l'état « demandé » est porté par une colonne dédiée sur la table des annonces elle-même, avec un simple horodatage. Cette colonne ne dépend d'aucune table tierce et n'a donc aucune raison d'être purgée avec les messages.
Les messages de la plateforme sont par ailleurs chiffrés au repos, avec une lecture double transitoire : les messages antérieurs à l'introduction du chiffrement restent lisibles tels quels jusqu'à l'expiration de leur propre délai de rétention, pendant que les nouveaux messages passent tous par le chiffrement dès l'écriture. Ce n'est pas la contrainte qui a motivé le choix de sortir la trace de facture des messages, mais elle illustre la même logique : le fil de messagerie est traité comme une donnée personnelle vivante, avec un cycle de vie qui lui est propre, pas comme un registre de conformité.
Ce que voit le modérateur qui rejette une annonce
Le pendant naturel de la demande de facture est le rejet d'annonce. Le formulaire de rejet backstage proposait déjà des motifs prêts à l'emploi pour les problèmes de photo — qualité, contenu, absence de photo générale. Il manquait deux cas fréquents dans la pratique des modérateurs : l'absence totale de photo, et l'absence de facture. Deux motifs de rejet ont été ajoutés à l'énumération existante, chacun avec :
- Un libellé court, affiché dans la liste déroulante des motifs de rejet.
- Un message pré-rédigé, dans les quatre langues de la plateforme, injecté automatiquement dans la zone de texte dès que le modérateur sélectionne le motif.
Aucune modification de vue ni de script n'a été nécessaire pour les intégrer : la liste déroulante des motifs et les gabarits de message sont déjà générés dynamiquement à partir de l'énumération. Ajouter un motif, c'est ajouter un cas et ses traductions ; l'interface suit sans qu'on y touche.
Ce que ça ne couvre pas
Cette mécanique trace la demande, pas la réponse. Rien n'empêche aujourd'hui un vendeur de laisser la demande sans suite : il n'y a pas de relance automatique, pas d'expiration, pas de blocage de l'annonce tant que la facture n'arrive pas. La décision reste entièrement entre les mains du modérateur, qui peut approuver, rejeter ou relancer manuellement en fonction du contexte.
Le seuil de 300 € est un signal de conformité interne, pas une preuve d'authenticité. Une facture fournie peut être fausse ; l'absence de facture n'implique pas nécessairement une contrefaçon. Ce mécanisme donne au modérateur une information de plus au moment de la revue, il ne remplace pas son jugement.