Le rapport de paiement indique 150,00. L’export analytique aussi. Chacun contient trois identifiants de commande distincts. On pourrait en conclure que l’intégration fonctionne.
Pourtant, les rapports peuvent décrire des commandes différentes.
Voici un petit contre-exemple exécutable. Les données sont inventées. Il ne s’agit ni d’un incident client ni d’une affirmation sur un fournisseur d’analytics. Le but est de montrer ce qu’une comparaison de totaux ne prouve pas, puis de préciser la vérification suivante.
Des sommes égales ne prouvent pas que les lignes correspondent
Une boutique a trois commandes payées : O101 pour 40,00, O102 pour 60,00 et O103 pour 50,00. Un export d’événements d’achat bruts contient O101 deux fois, O102 une fois et une O999 sans correspondance pour 10,00. Tous les montants utilisent la même monnaie fictive à deux décimales.
| Identifiant | Montant de la commande payée | Montants des événements bruts | Lignes d’événement |
|---|---|---|---|
| O101 | 40,00 | 40,00 + 40,00 | 2 |
| O102 | 60,00 | 60,00 | 1 |
| O103 | 50,00 | Aucun événement | 0 |
| O999 | Aucune commande dans cet exemple | 10,00 | 1 |
| Total | 150,00 | 150,00 | 4 |
Le doublon ajoute 40,00, et la ligne sans correspondance 10,00. Ensemble, ils compensent exactement les 50,00 manquants. Chaque côté compte aussi trois identifiants distincts, mais seulement deux sont communs.
Le nombre de lignes d’événement révèle ici une différence. La somme et le nombre d’identifiants distincts ne la révèlent pas. Aucun de ces agrégats ne désigne la commande manquante.

Définir ce que les deux exports doivent représenter
Un événement d’achat du navigateur consigne ce que l’instrumentation a signalé. Une commande payée représente l’état métier maintenu par le système de commande ou de paiement. Ils répondent à des questions différentes. Aucun ne doit remplacer implicitement l’autre.
Avant de comparer, définissez le contrat : boutique ou compte, monnaie, identifiant commun, état de paiement inclus, définition du montant et période. Choisissez une comparaison par commande, tentative de paiement ou ligne de commande. Une commande peut légitimement comporter plusieurs paiements ou articles.
Valeur brute des commandes, paiements encaissés, chiffre d’affaires net des remboursements et versements après frais ne sont pas interchangeables. Les dates d’événement, de paiement, d’ingestion et de versement non plus. Convenez d’une date d’arrêté et tenez compte du délai réel de traitement. Une ligne récente qui n’est pas encore arrivée n’est pas forcément perdue.
Le périmètre de cet exemple est volontairement étroit : un instantané synthétique complet, trois commandes déjà payées, un événement d’achat attendu par commande, sans remboursements, taxes, frais, paiements fractionnés ni conversion monétaire. Ce sont des événements bruts, pas une simulation des règles de déduplication d’un fournisseur.
Regrouper les événements avant la jointure
Exécutez cet exemple avec Python 3 et son module sqlite3. Il crée une base en mémoire, sans requête réseau ni modification de données de production. Les montants sont des entiers dans la plus petite unité de la monnaie fictive. La comparaison ne dépend donc pas de calculs binaires en virgule flottante.
import sqlite3
db = sqlite3.connect(":memory:")
db.executescript("""
CREATE TABLE orders (order_id TEXT PRIMARY KEY, cents INTEGER NOT NULL);
CREATE TABLE events (event_id TEXT PRIMARY KEY, order_id TEXT NOT NULL,
cents INTEGER NOT NULL);
INSERT INTO orders VALUES ('O101', 4000), ('O102', 6000), ('O103', 5000);
INSERT INTO events VALUES ('E1', 'O101', 4000), ('E2', 'O101', 4000),
('E3', 'O102', 6000), ('E4', 'O999', 1000);
""")
for table in ("orders", "events"):
total, ids = db.execute(
f"SELECT SUM(cents), COUNT(DISTINCT order_id) FROM {table}"
).fetchone()
print(f"{table}: cents={total}, distinct_order_ids={ids}")
query = """
WITH observed AS (
SELECT order_id, COUNT(*) AS event_rows, SUM(cents) AS event_cents
FROM events GROUP BY order_id
), ids AS (
SELECT order_id FROM orders UNION SELECT order_id FROM events
)
SELECT ids.order_id, o.cents, COALESCE(e.event_rows, 0), e.event_cents
FROM ids
LEFT JOIN orders o ON o.order_id = ids.order_id
LEFT JOIN observed e ON e.order_id = ids.order_id
ORDER BY ids.order_id
"""
for row in db.execute(query):
print(row)
db.close()
Résultat :
orders: cents=15000, distinct_order_ids=3
events: cents=15000, distinct_order_ids=3
('O101', 4000, 2, 8000)
('O102', 6000, 1, 6000)
('O103', 5000, 0, None)
('O999', None, 1, 1000)
La requête regroupe d’abord les événements en une ligne par identifiant de commande. Sinon, joindre directement une commande à plusieurs événements peut répéter son montant et fausser le rapprochement. L’union des identifiants conserve aussi les lignes sans correspondance des deux côtés. Une jointure interne les masquerait.
O103 n’a aucune ligne d’événement. O999 n’a pas de commande correspondante dans cet exemple. O101 a deux lignes d’événement. Ce sont trois observations différentes, pas une unique différence de total inexpliquée.

Dans un export réel, vérifiez aussi les identifiants manquants avant de regrouper. Ne les remplacez pas discrètement par une valeur commune comme unknown : des lignes indépendantes sembleraient appartenir à la même commande. Limitez les identifiants à la bonne boutique, au bon compte et au bon environnement. Si les exports utilisent des IDs différents, établissez une correspondance plutôt qu’une jointure sur des montants identiques ou des dates proches.
Un écart lance l’enquête, il ne pose pas le diagnostic
Dans cet exemple construit, les défauts sont connus parce que nous les avons introduits. En production, un identifiant sans correspondance peut aussi résulter d’une autre limite de journée, d’un environnement de test, d’un export incomplet ou d’un état métier légitime exclu d’un des rapports. Gardez ces hypothèses ouvertes jusqu’à l’examen des lignes.
La transmission par navigateur et la confirmation d’un paiement ont des modes de défaillance différents. Le guide Stripe sur l’exécution des commandes Checkout explique qu’un paiement réussi ne garantit pas une visite sur la page de retour. Un événement sur cette seule page ne constitue donc pas un mécanisme complet de confirmation de paiement.
La transmission côté serveur demande aussi de la rigueur. Stripe documente les livraisons répétées de webhooks. Recevoir une notification ne garantit pas que son effet métier sera appliqué une seule fois. Un identifiant d’événement du prestataire et un identifiant de commande résolvent des problèmes différents : plusieurs événements distincts peuvent concerner la même commande. Cet article n’implémente pas de gestionnaire de paiement et ne prescrit pas de clé de déduplication universelle.
De même, sendBeacon() renvoyant true signifie que le navigateur a mis les données en file pour transmission, pas que l’application analytique a accepté et enregistré un achat. C’est le contrat documenté de cette valeur de retour, pas la preuve qu’un collecteur précis a perdu un événement.

Rapprocher les identités avant d’interpréter l’attribution
Un rapprochement utile conserve des listes séparées : commandes payées sans événement attendu, événements sans commande payée correspondante, lignes répétées et écarts de montant ou de monnaie. Suivez ces catégories avec un contrat stable. Gardez le chiffre d’affaires non attribué visible plutôt que d’inventer un canal pour équilibrer un graphique.
Corriger la couverture de collecte ne prouve pas qu’un canal a causé un achat. L’attribution pose une autre question : comment répartir le crédit. Séparez-la de la question de savoir si les données d’achat concordent.
La leçon n’est pas que tous les tableaux de bord sont faux. Des totaux identiques constituent simplement une preuve plus faible qu’il n’y paraît. Conservez cette vérification, mais ajoutez une comparaison ligne par ligne avant de déclarer une intégration de chiffre d’affaires rapprochée.



