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 nullSELLING) 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) — 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, ou revente clôturée)
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 PENDINGRESERVED), 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
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 = SELLINGnull). 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