Lemonway P2P — Vue d'ensemble¶
Un P2P est un transfert d'argent wallet à wallet chez Lemonway (notre PSP) : débit d'un wallet, crédit d'un autre. C'est la brique de base de tous les mouvements d'argent internes à la plateforme — achat de bricks (investisseur → SPV), topup par carte (compte technique → investisseur), récompense de parrainage, gift card, remboursement…
Côté API, un P2P n'est pas une entité autonome : c'est un objet JSON porté par une wallet transaction (colonne lemonwayP2P de la table wallet_transactions). La wallet transaction décrit le mouvement métier ; le P2P décrit son exécution réelle chez Lemonway.
Sources
- Module :
projects/api/src/__new/modules/lemonway-p2p/ - Domain (statuts) :
lemonway-proto-p2p.ts - Service (appel Lemonway) :
lemonway-p2p.service.ts - Repository (sélection & persistance) :
lemonway-p2p.repository.ts
Le pipeline en trois temps¶
Le cycle de vie d'un P2P est entièrement asynchrone, porté par trois workers qui communiquent uniquement via Postgres :
flowchart LR
Services[Services métier<br/>+ worker bricks-purchase-p2p]:::action
Pending[("WT waiting<br/>P2P pending")]:::status
LW{POST /p2p<br/>Lemonway}:::endpoint
Succeeded[("P2P succeeded")]:::success
Failed[("P2P failed")]:::failure
Done[WT confirmed<br/>confirmations métier]:::success
Refund[WT declined<br/>refund / decline métier]:::failure
Services -- createPendingP2P --> Pending
Pending -- worker<br/>pending-lemonway-p2p --> LW
LW -- succès --> Succeeded
LW -. erreur transitoire<br/>retry + backoff .-> Pending
LW -- erreur définitive --> Failed
Succeeded -- worker<br/>played-lemonway-p2p --> Done
Failed -- worker<br/>played-lemonway-p2p --> Refund
classDef endpoint fill:#4f46e5,color:#fff,stroke:#3730a3
classDef action fill:#0ea5e9,color:#fff,stroke:#0369a1
classDef status fill:#fef3c7,color:#78350f,stroke:#d97706
classDef success fill:#10b981,color:#fff,stroke:#047857
classDef failure fill:#ef4444,color:#fff,stroke:#b91c1c
- Création — un service métier attache un P2P
pendingà sa wallet transaction (WalletTransaction.createPendingP2P). Pour les achats de bricks, c'est le workerbricks-purchase-p2pqui le fait en batch. - Exécution — le worker
pending-lemonway-p2psélectionne les P2Ppendingéligibles (en respectant l'ordre des débits par compte) et les joue chez Lemonway. Succès →succeeded, erreur transitoire → retry avec backoff, erreur définitive →failed. - Conséquences — le worker
played-lemonway-p2préconcilie : il prend les wallet transactionswaitingdont le P2P est joué et déclenche la suite métier (confirmation d'achat, remboursement, decline…), puis passe la WT enconfirmedoudeclined.
Détail des statuts dans Statuts & transitions, détail des workers dans Runtime — workers & exécution.
Modèle de données¶
Le P2P est une discriminated union sur status (lemonway-p2p.ts), stockée en jsonb sur wallet_transactions.lemonwayP2P (migration 058-wt-p2p-column-and-json-index.sql) :
| Champ | Type | Rôle |
|---|---|---|
status |
pending | succeeded | failed |
Statut d'exécution chez Lemonway |
debitAccountId / creditAccountId |
string |
Wallets Lemonway débité / crédité (id customer ou compte technique) |
amount |
Cents |
Montant en centimes, de bout en bout (aucune conversion) |
comment |
string |
Libellé visible chez Lemonway |
walletTransactionId |
WalletTransactionId |
WT porteuse — sert aussi de référence d'idempotence chez Lemonway |
attemptNumber / attemptAfter |
PositiveInteger / Date |
Compteur de tentatives et date de prochaine éligibilité |
attempts |
Array<{ attemptedAt, rawError?, attemptAfter }> |
Historique complet des tentatives (debug) |
lemonwayP2PId |
PositiveInteger |
Si succeeded : id de la transaction chez Lemonway (recopié dans wallet_transactions.lemonwayTransactionId) |
businessErrorType |
enum | Si failed : cause métier (voir Statuts & transitions) |
La factory WalletTransaction.createPendingP2P initialise status: 'pending', attemptNumber: 1, attemptAfter: now. Le montant par défaut est |withdrawableBalance| + |giftBalance| de la WT.
Qui crée des P2P — les flux métier¶
| Flux | WT kind | Débit → Crédit |
|---|---|---|
| Achat primaire de bricks (primary-purchase) | PRIMARY_PURCHASE |
investisseur → SPV de la propriété |
| Remboursement d'achat | PRIMARY_PURCHASE_REFUND |
SPV → investisseur |
| Topup par carte (checkout) | TOPUP_CHECKOUT |
Bricks-Checkout.com → investisseur |
| Achat de gift card | GIFT_CARD_PURCHASE |
acheteur → gift_card_waiting_to_be_claimed |
| Réclamation de gift card | GIFT_CARD_CREDIT |
gift_card_waiting_to_be_claimed → bénéficiaire |
| Remboursement de gift card (admin) | GIFT_CARD_REFUND |
gift_card_waiting_to_be_claimed → acheteur |
| Récompense de parrainage | REWARD_REFEREE / REWARD_REFERRER |
Referrals → investisseur |
| Gain de solde boosté | BOOSTED_BALANCE_GAIN |
SoldeBooste → investisseur |
| Remboursement de retenue à la source (admin) | REFUND_WITHHOLDING_TAX |
CollecteDGFIP → investisseur |
| Suppression de compte (reprise du solde gift) | ADJUSTMENT_NEG |
investisseur → compte principal Bricks |
D'autres kinds transitent aussi par le pipeline (BUYING_DEAL marketplace, REVENUE_OBLIGATION_COUPON, WITHHOLDING_TAX, CREDIT_FOR_WALLET_ASSET_TRANSFER…) — la liste de référence des conséquences par kind est dans Runtime.
Les comptes non-customer (Bricks-Checkout.com, Referrals, etc.) sont des wallets techniques déclarés dans businessRules.
Usage hors pipeline
L'échéancier projet (back-office) joue aussi des P2P, mais directement via attemptP2PAtLemonway avec une référence project-ptba-…, sans wallet transaction ni workers — voir property-payment-schedule.controller.ts.
Exemple de bout en bout — achat de bricks¶
sequenceDiagram
autonumber
participant PP as primary-purchase
participant W1 as bricks-purchase-p2p (worker)
participant W2 as pending-lemonway-p2p (worker)
participant LW as Lemonway
participant W3 as played-lemonway-p2p (worker)
Note over PP: achat en waiting_for_p2p_creation
W1->>PP: setP2PForPurchases (batch ≤ 1000)
W1->>W1: WT.lemonwayP2P = pending<br/>achat → waiting_for_contract_creation
W2->>W2: sélection priorisée + lock (SKIP LOCKED)
W2->>LW: POST /p2p (reference = wtId)
LW-->>W2: transaction.id
W2->>W2: P2P → succeeded<br/>WT.lemonwayTransactionId = id
W3->>W3: détecte WT waiting + P2P succeeded
W3->>PP: confirmPrimaryPurchase
W3->>W3: WT → confirmed
Si le P2P échoue définitivement (failed), le worker played-lemonway-p2p déclenche à l'inverse le remboursement de l'achat (refundPrimaryPurchase, reason p2pFailed).
Pour aller plus loin¶
- Statuts & transitions — machine à états, classification des erreurs Lemonway, stratégies de retry.
- Runtime — workers & exécution — sélection priorisée, ordre des débits, idempotence, conséquences par kind, monitoring.