Aller au contenu

Deal

Contrôleur HTTP de gestion des annonces du marché secondaire (création, achat, annulation, vérification du quota quotidien). Pour la consultation des annonces, voir Marketplace. Source : deal.controller.ts.

Tous les endpoints requièrent un JWT investisseur valide (JwtAuthGuard) et le rôle CUSTOMER (UserRoleGuard). Le flag customer.canSeeRoyalty = true est exigé (sinon 403 marketplace-forbidden) sur la création, l'achat et l'annulation, mais pas sur GET /marketplace/deal/can-sell-today.

Quota quotidien de vente : businessRules.secondaryMarket.dailyDealCreationMax = 30 annonces par jour et par customer (compteur Redis avec expiration à minuit, cf. clé customer-daily-sale-count:<customerId>). Le compte company.customerId est exempté.

GET /marketplace/deal/can-sell-today

Indique si l'investisseur peut encore créer une annonce aujourd'hui (sous le quota). Le compte company retourne toujours true.

Pré-conditions

  • JWT investisseur valide
  • Rôle CUSTOMER

Response

{
  "canSellToday": true
}

POST /marketplace/deal

Crée une annonce de revente de bricks (statut PENDING). Verrouille pessimistiquement les bricks de l'investisseur sur la property (status null → SELLING) et incrémente le compteur quotidien. En cas d'erreur dans la transaction, le compteur est décrémenté.

Pré-conditions

  • JWT investisseur valide
  • Rôle CUSTOMER
  • customer.canSeeRoyalty === true
  • Droits transactionnels actifs (Customer.assertTransactionRights('all'))
  • Property éligible : investorContractType === 'royalty', achetée (purchasedDate != null), pas en revente clôturée (resale.completedAt == null), marketplace ouvert (isMarketplaceAllowed) — cf. isMarketplaceAllowedForProperty
  • Quota quotidien non dépassé (≤ 30 annonces / jour)
  • L'investisseur possède au moins amount bricks libres (status = null) sur cette property
  • Prix de vente dans [businessRules.secondaryMarket.minimumBrickPrice, officialBrickPrice] (en cents) — cf. brickPriceIsSalable

Request body

{
  "propertyId": "<uuid>",
  "amount": 10,
  "price": 950
}

Validé via dealCreationValidator (deal.controller.ts#L42). amount est un entier positif (nombre de bricks à vendre), price est en cents par brick.

Response

L'entité MarketDeal créée :

{
  "id": "<uuid>",
  "propertyId": "<uuid>",
  "status": "pending",
  "bricksIds": ["<uuid>", "..."],
  "sellerId": "<customerId>",
  "brickCount": 10,
  "officialBrickPriceValue": 1000,
  "sellingBrickPriceValue": 950,
  "priceWithoutCommission": 9500,
  "profitability": 5.50,
  "revenueYield": 4.20,
  "brickPriceVariation": -5.0
}

Erreurs

Code Statut Cause
marketplace-forbidden 403 customer.canSeeRoyalty === false
customer.rights.account-is-being-verified 403 Compte en cours de vérification (Lemonway ou interne)
customer.rights.account-not-completed 403 Identité investisseur non vérifiée
marketplace.sell.MAX_SELLING_QUOTA_REACHED 403 Quota quotidien atteint
marketplace.sell.PROPERTY_NOT_FOUND 404 Property inexistante
marketplace.sell.property-forbidden 403 Property non éligible (non-royalty, pas achetée, revente clôturée, offre totale, ou marketplace fermé)
marketplace.sell.PROPERTY_BRICKS_PRICE_NOT_FOUND 404 Aucun BrickPrice officiel pour cette property
marketplace.sell.PRICE_NOT_WITHIN_RANGE 400 Prix demandé hors [minimumBrickPrice, officialBrickPrice]
marketplace.sell.NOT_ENOUGH_BRICKS 400 Moins de bricks disponibles (status = null) que demandé

POST /marketplace/deal/:dealId/purchase

Achète une annonce existante. Réserve le deal (status PENDING → RESERVED), crée deux WalletTransaction (achat côté acheteur en BUYING_DEAL, vente côté vendeur en SELLING_DEAL, statut WAITING). Le solde n'est débité/crédité qu'à la confirmation Lemonway via MarketDealService.confirmDealPurchase (déclenchée par le worker wallet-transactions).

Pré-conditions

  • JWT investisseur valide
  • Rôle CUSTOMER
  • customer.canSeeRoyalty === true
  • Onboarding investisseur complété (Customer.assertCompletedInvestorOnboarding)
  • Droits transactionnels actifs (Customer.assertTransactionRights('all'))
  • Le deal est en statut PENDING et n'appartient pas à l'acheteur (sellerId != buyerId)
  • Property toujours éligible (isMarketplaceAllowedForProperty)
  • Solde Lemonway ≥ priceWithoutCommission

Path param

  • dealId — UUIDv4

Response

undefined (pas de body retourné). L'effet est asynchrone : le deal passe en RESERVED, deux WTs sont créées en WAITING ; la finalisation (passage à CONFIRMED, transfert des bricks, email de confirmation Customer.io) est faite par le worker.

Erreurs

Code Statut Cause
marketplace-forbidden 403 customer.canSeeRoyalty === false
customer.investor-onboarding-not-completed 403 Onboarding investisseur incomplet
customer.rights.account-is-being-verified 403 Compte en cours de vérification
customer.rights.account-not-completed 403 Identité non vérifiée
marketplace.buy.DEAL_NOT_FOUND 404 Deal introuvable, non PENDING, ou possédé par l'acheteur
marketplace.sell.property-forbidden 403 Property non éligible au moment de l'achat (offre totale ou marketplace fermé)
marketplace.buy.BUYER_NOT_FOUND 404 Acheteur introuvable au lock (cas dégradé)
marketplace.buy.SELLER_NOT_FOUND 404 Vendeur introuvable au lock (cas dégradé)
marketplace.buy.USER_NOT_ENOUGH_MONEY 400 Solde Lemonway insuffisant

DELETE /marketplace/deal/:dealId

Annule une annonce en cours appartenant à l'investisseur. Supprime le MarketDeal et libère les bricks (status = SELLING → null). Lock pessimiste sur le deal pendant la transaction.

Pré-conditions

  • JWT investisseur valide
  • Rôle CUSTOMER
  • customer.canSeeRoyalty === true
  • Le deal est en statut PENDING et appartient au customer (sellerId === customer.id)

Path param

  • dealId — UUIDv4

Response

undefined (pas de body).

Erreurs

Code Statut Cause
marketplace-forbidden 403 customer.canSeeRoyalty === false
marketplace.deletion.not-found 404 Deal introuvable, non PENDING, ou n'appartenant pas au customer

Liens