Aller au contenu

Remboursement (refund)

Le remboursement d'un achat primaire est porté par refundPrimaryPurchase dans primary-purchase.service.ts, avec une finalisation asynchrone côté Lemonway via le worker PlayedLemonwayP2P et PrimaryPurchaseRefundService.

Statuts remboursables

Remboursable Bloqué (WALLET_TRANSACTION_STATE_NOT_CONFIRMED_OR_WAITING)
waiting_for_assignation declined
waiting_for_auto_invest_investor_confirmation refunded
waiting_for_p2p_creation waiting_for_reservation_confirmation
waiting_for_contract_creation
confirmed

Un achat est remboursable dès qu'une WT d'achat existe — c'est elle qui matérialise l'argent à rendre. waiting_for_reservation_confirmation n'en a pas encore (le paiement carte n'a pas abouti), declined et refunded sont terminaux.

Éligibilité

Initiateur Conditions
Investor (performedByCustomer) Funding non clôturé et now < refundableUntil
Admin (performedByAdmin) Funding non clôturé
Script funding-failure (propertyNotFinanced) Toujours

refundableUntil est stocké dans le contexte de la WT d'achat à sa création : date d'achat + businessRules.primaryMarket.retractDelayDays (4 jours), arrondie à la fin de journée Europe/Paris. La confirmation d'un achat auto-invest le recalcule à partir de la date de confirmation.

Sinon : Err('primary-purchase.refund.not-allowed').

Déclencheurs (5)

Déclencheur Reason Qui
POST /wallet-transactions/:walletTransactionId/refund performedByCustomer Investor (rétractation ≤ J+4)
POST /investor/investment-plan/purchase/cancel performedByCustomer Investor (annulation d'un achat auto-invest non confirmé)
POST /administration/transactions/:transactionId/refund performedByAdmin Admin (uniquement kind PRIMARY_PURCHASE)
Worker PlayedLemonwayP2P p2pFailed Automatique, quand le P2P d'achat échoue chez Lemonway
refund-investors.script.ts propertyNotFinanced Remboursement de masse quand le funding échoue

Mécanique

Tout s'exécute dans un safePgTransaction (verrou sur le purchase et sur la WT d'achat) :

  1. WT d'achat → CANCELED, avec context.refund = { walletTransactionId, reason, at }.
  2. WT de refund créée — kind PRIMARY_PURCHASE_REFUND (ou PRIMARY_REFUND_PROPERTY_NOT_FINANCED), qui re-crédite la balance classique et la part de gift balance utilisée. Son statut initial dépend du P2P d'achat :
    • P2P d'achat exécuté avec succès chez Lemonway → l'argent est sur le wallet de la SPV : la WT de refund naît WAITING avec un P2P inverse (SPV → investor). Il est joué par PendingLemonwayP2P, puis PlayedLemonwayP2P finalise via handleSucceededLemonwayP2PRefund (WT de refund → CONFIRMED, refund.at stampé).
    • Sinon (P2P jamais joué ou échoué) → rien à rapatrier : la WT de refund naît directement CONFIRMED (lemonwayTransactionId: -1).
  3. Bricks libérées — sauf en waiting_for_assignation (pas encore assignées). En propertyNotFinanced, variante closeFundingAndReleaseBricksForBricksIds qui clôture aussi le funding.
  4. Si l'achat était confirmed : le contrat d'obligation est marqué refunded et les referral rewards encore pending sont annulés.
  5. Compteurs Redis décrémentés (bricks vendues de la propriété, + compteur auto-invest le cas échéant), avec compensation setOnRollback.
  6. Purchase → refunded avec refund: { wtId, reason, at }.
flowchart TD
    Trigger["refundPrimaryPurchase"]:::action
    CheckStatus{Statut remboursable<br/>et éligible ?}
    Cancel["WT d'achat → CANCELED"]:::action
    P2PCheck{P2P d'achat réussi<br/>chez Lemonway ?}
    RefundWaiting["WT refund WAITING<br/>+ P2P inverse SPV → investor"]:::status
    RefundConfirmed["WT refund CONFIRMED<br/>immédiatement"]:::success
    Played["PendingLemonwayP2P joue le P2P<br/>PlayedLemonwayP2P → WT CONFIRMED"]:::action
    Refunded([purchase refunded]):::success
    Rejected["Err — not-allowed /<br/>NOT_CONFIRMED_OR_WAITING"]:::failure

    Trigger --> CheckStatus
    CheckStatus -- "Non" --> Rejected
    CheckStatus -- "Oui" --> Cancel
    Cancel --> P2PCheck
    P2PCheck -- "Oui" --> RefundWaiting
    P2PCheck -- "Non" --> RefundConfirmed
    RefundWaiting --> Played
    Played --> Refunded
    RefundConfirmed --> Refunded

    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

declined vs refunded

declined refunded
Quand L'achat n'a jamais abouti (expiration, sur-vente, échec WT) L'achat avait une WT d'achat débitée
Argent La WT d'achat, si elle existe, passe DECLINED — aucune WT supplémentaire WT de refund dédiée + WT d'achat CANCELED
Lemonway Jamais sollicité P2P inverse si l'argent avait atteint la SPV