src/Entity/Order.php line 21

Open in your IDE?
  1. <?php
  2. declare(strict_types=1);
  3. namespace App\Entity;
  4. use DateTimeImmutable;
  5. use DateTimeInterface;
  6. use Doctrine\ORM\Mapping as ORM;
  7. use DomainException;
  8. /**
  9. * Commande de paiement Stripe.
  10. *
  11. * L'entité porte les données métier structurées de fulfillment
  12. * (`type`, `credits`, `price`, `demandeIds`) pour éviter un payload JSON polymorphe
  13. * ainsi qu'un hash de token de retour pour les routes success/cancel stateless.
  14. */
  15. #[ORM\Entity]
  16. #[ORM\Table(name: 'orders')]
  17. class Order
  18. {
  19. public const string STATUS_PENDING = 'pending';
  20. public const string STATUS_PAID = 'paid';
  21. public const string STATUS_FAILED = 'failed';
  22. public const string STATUS_CANCELED = 'canceled';
  23. public const string TYPE_PACK = 'pack';
  24. public const string TYPE_DEMANDE = 'demande';
  25. public const string TYPE_CART = 'cart';
  26. #[ORM\Id]
  27. #[ORM\GeneratedValue(strategy: 'AUTO')]
  28. #[ORM\Column(type: 'integer')]
  29. private ?int $id = null;
  30. #[ORM\Column(name: 'created_at', type: 'datetime_immutable')]
  31. private DateTimeImmutable $createdAt;
  32. #[ORM\Column(name: 'checkout_session_id', type: 'string', length: 255, unique: true)]
  33. private string $checkoutSessionId;
  34. #[ORM\Column(name: 'checkout_expires_at', type: 'datetime_immutable', nullable: true)]
  35. private ?DateTimeImmutable $checkoutExpiresAt = null;
  36. #[ORM\Column(type: 'string', length: 20, options: ['default' => self::STATUS_PENDING])]
  37. private string $status = self::STATUS_PENDING;
  38. #[ORM\Column(name: 'stripe_event_id', type: 'string', length: 255, unique: true, nullable: true)]
  39. private ?string $stripeEventId = null;
  40. #[ORM\Column(name: 'order_type', type: 'string', length: 20, options: ['default' => self::TYPE_PACK])]
  41. private string $type = self::TYPE_PACK;
  42. #[ORM\Column(type: 'integer', nullable: true)]
  43. private ?int $credits = null;
  44. #[ORM\Column(type: 'integer', options: ['default' => 0])]
  45. private int $price = 0;
  46. #[ORM\Column(name: 'return_token_hash', type: 'string', length: 64, nullable: true)]
  47. private ?string $returnTokenHash = null;
  48. /**
  49. * Snapshot immuable des identifiants de demandes associés à la commande.
  50. *
  51. * @var list<int>
  52. */
  53. #[ORM\Column(name: 'demande_ids', type: 'json')]
  54. private array $demandeIds = [];
  55. #[ORM\ManyToOne(targetEntity: Prestataire::class)]
  56. #[ORM\JoinColumn(name: 'prestataire_id', referencedColumnName: 'id', nullable: false)]
  57. private Prestataire $prestataire;
  58. public function __construct()
  59. {
  60. $this->createdAt = new DateTimeImmutable();
  61. }
  62. public function getId(): ?int
  63. {
  64. return $this->id;
  65. }
  66. public function getCreatedAt(): DateTimeImmutable
  67. {
  68. return $this->createdAt;
  69. }
  70. public function setCreatedAt(DateTimeImmutable $createdAt): self
  71. {
  72. $this->createdAt = $createdAt;
  73. return $this;
  74. }
  75. public function getCheckoutSessionId(): string
  76. {
  77. return $this->checkoutSessionId;
  78. }
  79. public function setCheckoutSessionId(string $checkoutSessionId): self
  80. {
  81. $this->checkoutSessionId = $checkoutSessionId;
  82. return $this;
  83. }
  84. public function getCheckoutExpiresAt(): ?DateTimeImmutable
  85. {
  86. return $this->checkoutExpiresAt;
  87. }
  88. public function setCheckoutExpiresAt(?DateTimeImmutable $checkoutExpiresAt): self
  89. {
  90. $this->checkoutExpiresAt = $checkoutExpiresAt;
  91. return $this;
  92. }
  93. public function isCheckoutExpired(?DateTimeInterface $reference = null): bool
  94. {
  95. if (!$this->checkoutExpiresAt instanceof DateTimeImmutable) {
  96. return false;
  97. }
  98. $effectiveReference = $reference ?? new DateTimeImmutable();
  99. return $this->checkoutExpiresAt <= $effectiveReference;
  100. }
  101. public function getStatus(): string
  102. {
  103. return $this->status;
  104. }
  105. public function setStatus(string $status): self
  106. {
  107. $this->status = $status;
  108. return $this;
  109. }
  110. public function getStripeEventId(): ?string
  111. {
  112. return $this->stripeEventId;
  113. }
  114. public function setStripeEventId(?string $stripeEventId): self
  115. {
  116. $this->stripeEventId = $stripeEventId;
  117. return $this;
  118. }
  119. public function getType(): string
  120. {
  121. return $this->type;
  122. }
  123. /**
  124. * @param string $type Type métier de la commande (`pack`, `demande`, `cart`).
  125. *
  126. * @throws DomainException Si le type n'est pas supporté.
  127. */
  128. public function setType(string $type): self
  129. {
  130. if (!in_array($type, [self::TYPE_PACK, self::TYPE_DEMANDE, self::TYPE_CART], true)) {
  131. throw new DomainException(sprintf('Unsupported order type "%s".', $type));
  132. }
  133. $this->type = $type;
  134. return $this;
  135. }
  136. /**
  137. * @return int|null Nombre de crédits achetés si type `pack`.
  138. */
  139. public function getCredits(): ?int
  140. {
  141. return $this->credits;
  142. }
  143. /**
  144. * @param int|null $credits Nombre de crédits achetés si type `pack`.
  145. */
  146. public function setCredits(?int $credits): self
  147. {
  148. $this->credits = $credits;
  149. return $this;
  150. }
  151. /**
  152. * @return int Montant payé figé au moment de la transaction.
  153. */
  154. public function getPrice(): int
  155. {
  156. return $this->price;
  157. }
  158. /**
  159. * @param int $price Montant payé figé au moment de la transaction.
  160. *
  161. * @throws DomainException Si le montant n'est pas strictement positif.
  162. */
  163. public function setPrice(int $price): self
  164. {
  165. if ($price <= 0) {
  166. throw new DomainException(sprintf('Invalid order price "%d".', $price));
  167. }
  168. $this->price = $price;
  169. return $this;
  170. }
  171. /**
  172. * @return string|null Hash SHA-256 du token de retour success/cancel.
  173. */
  174. public function getReturnTokenHash(): ?string
  175. {
  176. return $this->returnTokenHash;
  177. }
  178. /**
  179. * @param string|null $returnTokenHash Hash SHA-256 du token de retour.
  180. */
  181. public function setReturnTokenHash(?string $returnTokenHash): self
  182. {
  183. $this->returnTokenHash = $returnTokenHash;
  184. return $this;
  185. }
  186. /**
  187. * @return list<int>
  188. */
  189. public function getDemandeIds(): array
  190. {
  191. return $this->demandeIds;
  192. }
  193. /**
  194. * @param list<int> $demandeIds
  195. */
  196. public function setDemandeIds(array $demandeIds): self
  197. {
  198. $normalizedIds = [];
  199. foreach ($demandeIds as $demandeId) {
  200. $normalizedId = (int) $demandeId;
  201. if ($normalizedId <= 0) {
  202. throw new DomainException(sprintf('Invalid demande id "%s".', (string) $demandeId));
  203. }
  204. $normalizedIds[$normalizedId] = $normalizedId;
  205. }
  206. $snapshot = array_values($normalizedIds);
  207. if ($this->demandeIds !== [] && $this->demandeIds !== $snapshot) {
  208. throw new DomainException('Order demande ids snapshot is immutable once initialized.');
  209. }
  210. $this->demandeIds = $snapshot;
  211. return $this;
  212. }
  213. public function getPrestataire(): Prestataire
  214. {
  215. return $this->prestataire;
  216. }
  217. public function setPrestataire(Prestataire $prestataire): self
  218. {
  219. $this->prestataire = $prestataire;
  220. return $this;
  221. }
  222. }