Files
e-ticket/src/Entity/User.php
Serreau Jovann 531c7da051 Add debt system: track refunds/disputes, redirect payments until cleared
- Add nullable debt field to User entity with addDebt/reduceDebt helpers
- On refund webhook: add refunded amount to organizer debt
- On dispute webhook (charge.dispute.created): add disputed amount to debt
- OrderController: if organizer has debt > 0, payment goes to main Stripe
  account instead of connected account, debt reduced on payment success
- Display debt amount on organizer dashboard with warning message
- Add dispute notification email template
- Migration for debt column on user table

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-25 10:15:01 +01:00

720 lines
16 KiB
PHP

<?php
namespace App\Entity;
use App\Repository\UserRepository;
use Doctrine\ORM\Mapping as ORM;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;
use Symfony\Component\HttpFoundation\File\File;
use Symfony\Component\Security\Core\User\PasswordAuthenticatedUserInterface;
use Symfony\Component\Security\Core\User\UserInterface;
use Symfony\Component\Validator\Constraints as Assert;
use Vich\UploaderBundle\Mapping\Attribute as Vich;
#[ORM\Entity(repositoryClass: UserRepository::class)]
#[ORM\Table(name: '`user`')]
#[UniqueEntity(fields: ['email'], message: 'Un compte existe déjà avec cet email.')]
#[Vich\Uploadable]
class User implements UserInterface, PasswordAuthenticatedUserInterface
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
private ?int $id = null;
#[ORM\Column(length: 180, unique: true)]
#[Assert\Email(message: 'L\'adresse email n\'est pas valide.')]
#[Assert\NotBlank(message: 'L\'email est requis.')]
private ?string $email = null;
#[ORM\Column(length: 255)]
private ?string $firstName = null;
#[ORM\Column(length: 255)]
private ?string $lastName = null;
/** @var list<string> */
#[ORM\Column]
private array $roles = [];
#[ORM\Column]
private ?string $password = null;
#[ORM\Column(length: 255, unique: true, nullable: true)]
private ?string $keycloakId = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $companyName = null;
#[ORM\Column(length: 14, nullable: true)]
private ?string $siret = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $address = null;
#[ORM\Column(length: 10, nullable: true)]
private ?string $postalCode = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $city = null;
#[ORM\Column(length: 20, nullable: true)]
private ?string $phone = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $website = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $facebook = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $instagram = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $twitter = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $tiktok = null;
#[Vich\UploadableField(mapping: 'organizer_logo', fileNameProperty: 'logoName')]
private ?File $logoFile = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $logoName = null;
#[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $updatedAt = null;
#[ORM\Column(length: 6, nullable: true)]
private ?string $resetCode = null;
#[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $resetCodeExpiresAt = null;
#[ORM\Column]
private bool $isVerified = false;
#[ORM\Column]
private bool $isApproved = false;
#[ORM\Column(nullable: true)]
private ?bool $isSuspended = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $offer = null;
#[ORM\Column(nullable: true)]
private ?float $commissionRate = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $stripeAccountId = null;
#[ORM\Column(length: 50, nullable: true)]
private ?string $stripeStatus = null;
#[ORM\Column]
private bool $stripeChargesEnabled = false;
#[ORM\Column]
private bool $stripePayoutsEnabled = false;
#[ORM\Column(nullable: true)]
private ?bool $isBilling = null;
#[ORM\Column(nullable: true)]
private ?int $billingAmount = null;
#[ORM\Column(length: 20, nullable: true)]
private ?string $billingState = null;
#[ORM\Column(length: 255, nullable: true)]
private ?string $billingStripeSubscriptionId = null;
#[ORM\Column(nullable: true)]
private ?int $debt = null;
#[ORM\ManyToOne(targetEntity: self::class)]
#[ORM\JoinColumn(nullable: true, onDelete: 'SET NULL')]
private ?self $parentOrganizer = null;
/** @var list<string> */
#[ORM\Column(nullable: true)]
private ?array $subAccountPermissions = null;
#[ORM\Column(length: 64, nullable: true)]
private ?string $emailVerificationToken = null;
#[ORM\Column(nullable: true)]
private ?\DateTimeImmutable $emailVerifiedAt = null;
#[ORM\Column]
private \DateTimeImmutable $createdAt;
public function __construct()
{
$this->createdAt = new \DateTimeImmutable();
}
public function getId(): ?int
{
return $this->id;
}
public function getEmail(): ?string
{
return $this->email;
}
public function setEmail(string $email): static
{
$this->email = $email;
return $this;
}
public function getFirstName(): ?string
{
return $this->firstName;
}
public function setFirstName(string $firstName): static
{
$this->firstName = $firstName;
return $this;
}
public function getLastName(): ?string
{
return $this->lastName;
}
public function setLastName(string $lastName): static
{
$this->lastName = $lastName;
return $this;
}
public function getUserIdentifier(): string
{
return (string) $this->email;
}
/** @return list<string> */
public function getRoles(): array
{
$roles = $this->roles;
$roles[] = 'ROLE_USER';
return array_unique($roles);
}
/** @param list<string> $roles */
public function setRoles(array $roles): static
{
$this->roles = $roles;
return $this;
}
public function getPassword(): ?string
{
return $this->password;
}
public function setPassword(string $password): static
{
$this->password = $password;
return $this;
}
public function getCreatedAt(): \DateTimeImmutable
{
return $this->createdAt;
}
public function getCompanyName(): ?string
{
return $this->companyName;
}
public function setCompanyName(?string $companyName): static
{
$this->companyName = $companyName;
return $this;
}
public function getSiret(): ?string
{
return $this->siret;
}
public function setSiret(?string $siret): static
{
$this->siret = $siret;
return $this;
}
public function getAddress(): ?string
{
return $this->address;
}
public function setAddress(?string $address): static
{
$this->address = $address;
return $this;
}
public function getPostalCode(): ?string
{
return $this->postalCode;
}
public function setPostalCode(?string $postalCode): static
{
$this->postalCode = $postalCode;
return $this;
}
public function getCity(): ?string
{
return $this->city;
}
public function setCity(?string $city): static
{
$this->city = $city;
return $this;
}
public function getPhone(): ?string
{
return $this->phone;
}
public function setPhone(?string $phone): static
{
$this->phone = $phone;
return $this;
}
public function getWebsite(): ?string
{
return $this->website;
}
public function setWebsite(?string $website): static
{
$this->website = $website;
return $this;
}
public function getFacebook(): ?string
{
return $this->facebook;
}
public function setFacebook(?string $facebook): static
{
$this->facebook = $facebook;
return $this;
}
public function getInstagram(): ?string
{
return $this->instagram;
}
public function setInstagram(?string $instagram): static
{
$this->instagram = $instagram;
return $this;
}
public function getTwitter(): ?string
{
return $this->twitter;
}
public function setTwitter(?string $twitter): static
{
$this->twitter = $twitter;
return $this;
}
public function getTiktok(): ?string
{
return $this->tiktok;
}
public function setTiktok(?string $tiktok): static
{
$this->tiktok = $tiktok;
return $this;
}
public function getLogoFile(): ?File
{
return $this->logoFile;
}
public function setLogoFile(?File $logoFile = null): static
{
$this->logoFile = $logoFile;
if (null !== $logoFile) {
$this->updatedAt = new \DateTimeImmutable();
}
return $this;
}
public function getLogoName(): ?string
{
return $this->logoName;
}
public function setLogoName(?string $logoName): static
{
$this->logoName = $logoName;
return $this;
}
public function getUpdatedAt(): ?\DateTimeImmutable
{
return $this->updatedAt;
}
public function getCommissionRate(): ?float
{
return $this->commissionRate;
}
public function setCommissionRate(?float $commissionRate): static
{
$this->commissionRate = $commissionRate;
return $this;
}
public function getStripeAccountId(): ?string
{
return $this->stripeAccountId;
}
public function setStripeAccountId(?string $stripeAccountId): static
{
$this->stripeAccountId = $stripeAccountId;
return $this;
}
public function getStripeStatus(): ?string
{
return $this->stripeStatus;
}
public function setStripeStatus(?string $stripeStatus): static
{
$this->stripeStatus = $stripeStatus;
return $this;
}
public function isStripeChargesEnabled(): bool
{
return $this->stripeChargesEnabled;
}
public function setStripeChargesEnabled(bool $stripeChargesEnabled): static
{
$this->stripeChargesEnabled = $stripeChargesEnabled;
return $this;
}
public function isStripePayoutsEnabled(): bool
{
return $this->stripePayoutsEnabled;
}
public function setStripePayoutsEnabled(bool $stripePayoutsEnabled): static
{
$this->stripePayoutsEnabled = $stripePayoutsEnabled;
return $this;
}
public function isBilling(): ?bool
{
return $this->isBilling;
}
public function setIsBilling(?bool $isBilling): static
{
$this->isBilling = $isBilling;
return $this;
}
public function getBillingAmount(): ?int
{
return $this->billingAmount;
}
public function setBillingAmount(?int $billingAmount): static
{
$this->billingAmount = $billingAmount;
return $this;
}
public function getBillingState(): ?string
{
return $this->billingState;
}
public function setBillingState(?string $billingState): static
{
$this->billingState = $billingState;
return $this;
}
public function getBillingStripeSubscriptionId(): ?string
{
return $this->billingStripeSubscriptionId;
}
public function setBillingStripeSubscriptionId(?string $billingStripeSubscriptionId): static
{
$this->billingStripeSubscriptionId = $billingStripeSubscriptionId;
return $this;
}
public function getDebt(): ?int
{
return $this->debt;
}
public function setDebt(?int $debt): static
{
$this->debt = $debt;
return $this;
}
public function addDebt(int $amount): static
{
$this->debt = ($this->debt ?? 0) + $amount;
return $this;
}
public function reduceDebt(int $amount): static
{
$this->debt = max(0, ($this->debt ?? 0) - $amount);
return $this;
}
public function getParentOrganizer(): ?self
{
return $this->parentOrganizer;
}
public function setParentOrganizer(?self $parentOrganizer): static
{
$this->parentOrganizer = $parentOrganizer;
return $this;
}
/**
* @return list<string>|null
*/
public function getSubAccountPermissions(): ?array
{
return $this->subAccountPermissions;
}
/**
* @param list<string>|null $subAccountPermissions
*/
public function setSubAccountPermissions(?array $subAccountPermissions): static
{
$this->subAccountPermissions = $subAccountPermissions;
return $this;
}
public function hasPermission(string $permission): bool
{
return null !== $this->subAccountPermissions && \in_array($permission, $this->subAccountPermissions, true);
}
public function getResetCode(): ?string
{
return $this->resetCode;
}
public function setResetCode(?string $resetCode): static
{
$this->resetCode = $resetCode;
return $this;
}
public function getResetCodeExpiresAt(): ?\DateTimeImmutable
{
return $this->resetCodeExpiresAt;
}
public function setResetCodeExpiresAt(?\DateTimeImmutable $resetCodeExpiresAt): static
{
$this->resetCodeExpiresAt = $resetCodeExpiresAt;
return $this;
}
public function isApproved(): bool
{
return $this->isApproved;
}
public function setIsApproved(bool $isApproved): static
{
$this->isApproved = $isApproved;
return $this;
}
public function isSuspended(): ?bool
{
return $this->isSuspended;
}
public function setIsSuspended(?bool $isSuspended): static
{
$this->isSuspended = $isSuspended;
return $this;
}
public function getOffer(): ?string
{
return $this->offer;
}
public function setOffer(?string $offer): static
{
$this->offer = $offer;
return $this;
}
public function isVerified(): bool
{
return $this->isVerified;
}
public function setIsVerified(bool $isVerified): static
{
$this->isVerified = $isVerified;
return $this;
}
public function getEmailVerificationToken(): ?string
{
return $this->emailVerificationToken;
}
public function setEmailVerificationToken(?string $emailVerificationToken): static
{
$this->emailVerificationToken = $emailVerificationToken;
return $this;
}
public function getEmailVerifiedAt(): ?\DateTimeImmutable
{
return $this->emailVerifiedAt;
}
public function setEmailVerifiedAt(?\DateTimeImmutable $emailVerifiedAt): static
{
$this->emailVerifiedAt = $emailVerifiedAt;
return $this;
}
public function getKeycloakId(): ?string
{
return $this->keycloakId;
}
public function setKeycloakId(?string $keycloakId): static
{
$this->keycloakId = $keycloakId;
return $this;
}
public function eraseCredentials(): void
{
// Required by UserInterface — no temporary credentials to clear
}
public function getSlug(): string
{
$name = $this->companyName ?? $this->firstName.' '.$this->lastName;
$slug = mb_strtolower(trim($name));
$slug = transliterator_transliterate('Any-Latin; Latin-ASCII', $slug) ?: $slug;
$slug = (string) preg_replace('/[^a-z0-9]+/', '-', $slug);
$slug = trim($slug, '-');
return '' === $slug ? 'organisateur' : $slug;
}
/**
* @return array<string, mixed>
*/
public function __serialize(): array
{
return [
'id' => $this->id,
'email' => $this->email,
'password' => $this->password,
'roles' => $this->roles,
];
}
/**
* @param array<string, mixed> $data
*/
public function __unserialize(array $data): void
{
$this->id = $data['id'] ?? null;
$this->email = $data['email'] ?? null;
$this->password = $data['password'] ?? null;
$this->roles = $data['roles'] ?? [];
}
}