Aller au contenu

Diagnostic de deal marketplace bloqué

Workflow pour diagnostiquer un deal marketplace bloqué (statut reserved, P2P en échec), en utilisant le MCP prod readonly.

Lifecycle d'un deal

pending → reserved (achat par le buyer, P2P créé)
reserved → confirmed (P2P réussi, bricks transférées)
reserved → pending (P2P échoué, deal remis en vente)
pending → canceled (annulé par le seller)

Seul le buying_deal WT porte le P2P Lemonway (debitAccountId = buyer, creditAccountId = seller). Le selling_deal WT du vendeur n'a pas de P2P propre. Pas de timeout automatique sur les deals reserved.

Étape 1 : Trouver le deal

À partir d'un email, customer ID, ou deal ID.

Par email du vendeur ou acheteur :

SELECT md.id as deal_id, md.status, md."createdAt", md."updatedAt",
       md."sellerId", md."buyerId",
       md."priceWithoutCommission", md."brickCount", md."sellingBrickPriceValue",
       md."propertyId",
       seller.email as seller_email,
       buyer.email as buyer_email
FROM "market-deal" md
LEFT JOIN customers seller ON seller.id = md."sellerId"
LEFT JOIN customers buyer ON buyer.id = md."buyerId"
WHERE (seller.email = '{EMAIL}' OR buyer.email = '{EMAIL}')
  AND md.status = 'reserved'
ORDER BY md."createdAt" DESC
LIMIT 10

Par customer ID (vendeur ou acheteur) :

SELECT md.id as deal_id, md.status, md."createdAt", md."updatedAt",
       md."sellerId", md."buyerId",
       md."priceWithoutCommission", md."brickCount",
       md."purchaseWalletTransactionId", md."saleWalletTransactionId"
FROM "market-deal" md
WHERE (md."sellerId" = '{CUSTOMER_ID}' OR md."buyerId" = '{CUSTOMER_ID}')
  AND md.status = 'reserved'
ORDER BY md."createdAt" DESC
LIMIT 10

Étape 2 : Wallet transactions liées

Récupérer les WT du deal. Le purchaseWalletTransactionId est le WT porteur du P2P.

SELECT wt.id, wt."customerId", wt.kind, wt.value, wt.status,
       wt."createdAt", wt."updatedAt",
       wt."lemonwayP2P"->>'status' as p2p_status,
       wt."lemonwayP2P"->>'attemptNumber' as attempt_count,
       wt."lemonwayP2P"->>'attemptAfter' as next_attempt,
       wt."lemonwayP2P"->>'debitAccountId' as debit_account,
       wt."lemonwayP2P"->>'creditAccountId' as credit_account,
       wt."lemonwayTransactionId",
       wt."index"
FROM wallet_transactions wt
WHERE wt.id IN ('{PURCHASE_WT_ID}', '{SALE_WT_ID}')

Si status = 'waiting' et p2p_status = 'pending' sur le buying_deal : le P2P n'a pas abouti.

Étape 3 : Diagnostic P2P (attempts)

Distinguer erreurs POST vs GET

La présence de rawError.context indique une erreur du GET (findExistingP2PReferenceAtLw), pas du POST (playP2P). Le GET peut masquer la vraie erreur (code 1 au lieu de code 110).

SELECT attempt->>'attemptedAt' as attempted_at,
       attempt->'rawError'->'error'->>'code' as error_code,
       attempt->'rawError'->'error'->>'message' as error_msg,
       CASE WHEN attempt->'rawError' ? 'context'
            THEN 'GET (masked)'
            ELSE 'POST (real)'
       END as error_source
FROM wallet_transactions wt,
     jsonb_array_elements(wt."lemonwayP2P"->'attempts') as attempt
WHERE wt.id = '{PURCHASE_WT_ID}'
ORDER BY (attempt->>'attemptedAt')::timestamptz ASC

Synthèse des erreurs

Pour un résumé rapide sans lister toutes les attempts :

SELECT attempt->'rawError'->'error'->>'code' as error_code,
       attempt->'rawError'->'error'->>'message' as error_msg,
       CASE WHEN attempt->'rawError' ? 'context' THEN 'GET' ELSE 'POST' END as source,
       COUNT(*) as occurrences,
       MIN(attempt->>'attemptedAt') as first_seen,
       MAX(attempt->>'attemptedAt') as last_seen
FROM wallet_transactions wt,
     jsonb_array_elements(wt."lemonwayP2P"->'attempts') as attempt
WHERE wt.id = '{PURCHASE_WT_ID}'
GROUP BY error_code, error_msg, source
ORDER BY occurrences DESC

Classification des erreurs

Code Type Retry Signification
110 Business 1h Solde insuffisant (wallet débiteur)
187 Business 1h Solde insuffisant (variante)
111 Business fail Compte débiteur bloqué
167 Business fail Blocage réglementaire débiteur
168 Business fail Compte créditeur bloqué
146 Business fail L'un des deux comptes bloqué
1 Technique backoff exp. Erreur générique (souvent GET masquant le POST)
348 Technique backoff exp. Référence P2P dupliquée
101 Technique backoff exp. Limite P2P atteinte pour le receveur

Pour les erreurs business de type "insufficient balance" sur un débit : le worker retente toutes les heures s'il existe des pendingCredit, sinon il fail le P2P (alerte Slack).

Étape 4 : Balance de l'acheteur

SELECT c.id, c.email,
       c."withdrawableBalances", c."giftBalances",
       c."lemonwayStatus", c."transactionRights", c."blockedTemporarilyAtLemonway",
       c.lemonway->'wallet'->>'isBlocked' as lw_wallet_blocked
FROM customers c
WHERE c.id = '{BUYER_ID}'

Solde réel du wallet Lemonway ~ withdrawableBalances.current (les WT confirmed ont eu leur P2P joué, les waiting non). Pour savoir si le P2P buying_deal peut passer : current >= montant du P2P.

La colonne lemonwayBalance est un alias legacy de availableWithdrawableBalance_view, ce n'est PAS le solde réel Lemonway.

Étape 5 : Pending credits de l'acheteur

Identifier pourquoi le solde Lemonway est insuffisant et quand il sera suffisant.

SELECT wt.id, wt.kind, wt.value, wt.status, wt."createdAt",
       wt."lemonwayP2P"->>'status' as p2p_status,
       wt.context as wt_context
FROM wallet_transactions wt
WHERE wt."customerId" = '{BUYER_ID}'
  AND wt.status = 'waiting'
  AND wt.value > 0
ORDER BY wt."createdAt" ASC

Interprétation des pending credits :

Kind lemonwayP2P Signification
topup_checkout null Wire-in Checkout pas encore arrivé chez LW (jusqu'à 3 jours ouvrés pour Apple Pay / carte via Checkout.com)
topup_checkout pending Wire-in arrivé, P2P Checkout -> customer en cours
topup_card n/a Exclu du pendingCredit. Ne pas compter
revenue_*, obligation_* pending P2P credit bloqué (vérifier les attempts)

Si le seul pending credit est un topup_checkout avec lemonwayP2P = null : le paiement Checkout est en transit. Résolution automatique sous 3 jours ouvrés.

Étape 6 : Ordering des débits (race condition)

Vérifier si un débit plus récent a été exécuté avant le buying_deal bloqué.

SELECT wt.id, wt.kind, wt.value, wt.status, wt."createdAt",
       wt."lemonwayP2P"->>'status' as p2p_status,
       wt."lemonwayTransactionId",
       wt."index"
FROM wallet_transactions wt
WHERE wt."customerId" = '{BUYER_ID}'
  AND wt.value < 0
  AND wt."index" > {BUYING_DEAL_INDEX}
  AND wt.status = 'confirmed'
ORDER BY wt."index" ASC

Si des résultats existent avec un lemonwayTransactionId non null : un débit plus récent a consommé le solde Lemonway avant le buying_deal. C'est le symptôme de la race condition d'ordering corrigée par PR #4679 (déployée 26/02/2026). Avant ce fix, les P2P en backoff étaient exclus du ranking CTE, permettant aux débits plus récents de prendre rn=1.

Étape 7 : Comptes vendeur et acheteur

SELECT c.id, c.email,
       c."transactionRights",
       c."blockedTemporarilyAtLemonway",
       c."lemonwayStatus",
       c.lemonway->'wallet'->>'isBlocked' as lw_wallet_blocked,
       c.lemonway->'wallet'->>'status' as lw_wallet_status,
       c."withdrawableBalances", c."giftBalances"
FROM customers c
WHERE c.id IN ('{BUYER_ID}', '{SELLER_ID}')

Le P2P est bloqué si pour le buyer OU le seller : - transactionRights != 'all' - blockedTemporarilyAtLemonway = true - lemonwayStatus != '6'

Causes racines courantes

Situation Diagnostic Résolution
Erreur 110 + topup_checkout pending (lemonwayP2P null) Wire-in Apple Pay / carte en transit Attendre 3 jours ouvrés. Pas de bug
Erreur 110 + aucun pending credit Le worker aurait dû fail le P2P (alerte Slack envoyée) Vérifier l'alerte Slack. Annulation manuelle si nécessaire
Erreurs code 1 en boucle (avec context dans rawError) GET masque la vraie erreur POST Corrigé par PR #4679. Si antérieur au deploy (26/02/2026), c'est attendu
Débit plus récent confirmé avant le buying_deal Race condition ordering dans le worker Corrigé par PR #4679. Si antérieur au deploy, c'est la cause
Compte buyer ou seller bloqué transactionRights, blockedTemporarilyAtLemonway, ou lemonwayStatus Débloquer le compte ou contacter Lemonway
Deal reserved depuis très longtemps Pas de timeout automatique sur les deals reserved Le deal reste réservé tant que le P2P n'a pas abouti ou échoué
Balance OK mais P2P en backoff long Backoff exponentiel technique (5min * 2^n, cap ~10.67h) Attendre le prochain attempt (voir attemptAfter)