Block organizer features when Stripe Connect account is not validated

- Hide organizer tabs (events, subaccounts, payouts) if Stripe not ready
- Redirect organizer tab content and all organizer routes to /mon-compte
- Add requireStripeReady() guard on all ROLE_ORGANIZER routes
- Force default tab to 'tickets' when Stripe is not validated
- Update test fixtures: approved organizers get Stripe enabled by default
- Add tests for blocked tabs and blocked event creation without Stripe

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-24 11:06:39 +01:00
parent 7d81fa3604
commit f9788adab3
3 changed files with 146 additions and 7 deletions

View File

@@ -43,9 +43,6 @@ class AccountController extends AbstractController
/** @var User $user */
$user = $this->getUser();
$isOrganizer = $this->isGranted('ROLE_ORGANIZER');
$defaultTab = $isOrganizer ? 'events' : 'tickets';
$tab = $request->query->getString('tab', $defaultTab);
if ($isOrganizer && $user->getStripeAccountId() && (!$user->isStripeChargesEnabled() || !$user->isStripePayoutsEnabled())) {
try { // @codeCoverageIgnoreStart
$account = $stripeService->getClient()->accounts->retrieve($user->getStripeAccountId());
@@ -57,6 +54,15 @@ class AccountController extends AbstractController
} // @codeCoverageIgnoreEnd
}
$stripeReady = $user->isStripeChargesEnabled() && $user->isStripePayoutsEnabled();
$organizerTabs = ['events', 'subaccounts', 'payouts'];
$defaultTab = ($isOrganizer && $stripeReady) ? 'events' : 'tickets';
$tab = $request->query->getString('tab', $defaultTab);
if (\in_array($tab, $organizerTabs, true) && !$stripeReady) {
$tab = $defaultTab;
}
$payouts = [];
$subAccounts = [];
$events = [];
@@ -328,6 +334,9 @@ class AccountController extends AbstractController
public function createEvent(Request $request, EntityManagerInterface $em, EventIndexService $eventIndex, AuditService $audit): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -362,6 +371,9 @@ class AccountController extends AbstractController
public function editEvent(Event $event, Request $request, EntityManagerInterface $em, EventIndexService $eventIndex, PaginatorInterface $paginator, OrderIndexService $orderIndex, AuditService $audit): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -458,6 +470,9 @@ class AccountController extends AbstractController
public function addCategory(Event $event, Request $request, EntityManagerInterface $em, AuditService $audit): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -507,6 +522,9 @@ class AccountController extends AbstractController
public function editCategory(Event $event, int $categoryId, Request $request, EntityManagerInterface $em, AuditService $audit): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -563,6 +581,9 @@ class AccountController extends AbstractController
public function deleteCategory(Event $event, int $categoryId, EntityManagerInterface $em, AuditService $audit): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -587,6 +608,9 @@ class AccountController extends AbstractController
public function reorderCategories(Event $event, Request $request, EntityManagerInterface $em): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -612,6 +636,9 @@ class AccountController extends AbstractController
public function addBillet(Event $event, int $categoryId, Request $request, EntityManagerInterface $em, StripeService $stripeService, AuditService $audit): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -658,6 +685,9 @@ class AccountController extends AbstractController
public function editBillet(Event $event, int $billetId, Request $request, EntityManagerInterface $em, StripeService $stripeService, AuditService $audit): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -699,6 +729,9 @@ class AccountController extends AbstractController
public function deleteBillet(Event $event, int $billetId, EntityManagerInterface $em, StripeService $stripeService, AuditService $audit): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -729,6 +762,9 @@ class AccountController extends AbstractController
public function reorderBillets(Event $event, Request $request, EntityManagerInterface $em): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -754,6 +790,9 @@ class AccountController extends AbstractController
public function createInvitation(Event $event, Request $request, EntityManagerInterface $em, BilletOrderService $billetOrderService): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -828,6 +867,9 @@ class AccountController extends AbstractController
public function resendInvitation(Event $event, int $orderId, EntityManagerInterface $em, BilletOrderService $billetOrderService): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -851,6 +893,9 @@ class AccountController extends AbstractController
public function cancelOrder(Event $event, int $orderId, EntityManagerInterface $em, AuditService $audit, BilletOrderService $billetOrderService): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -891,6 +936,9 @@ class AccountController extends AbstractController
public function refundOrder(Event $event, int $orderId, EntityManagerInterface $em, StripeService $stripeService, AuditService $audit, BilletOrderService $billetOrderService): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -939,6 +987,9 @@ class AccountController extends AbstractController
public function billetPreview(Event $event, Request $request): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -963,6 +1014,9 @@ class AccountController extends AbstractController
public function saveBilletDesign(Event $event, Request $request, EntityManagerInterface $em): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -991,6 +1045,9 @@ class AccountController extends AbstractController
public function toggleEventOnline(Event $event, EntityManagerInterface $em, EventIndexService $eventIndex): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -1018,6 +1075,9 @@ class AccountController extends AbstractController
public function toggleEventSecret(Event $event, EntityManagerInterface $em, EventIndexService $eventIndex): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -1039,6 +1099,9 @@ class AccountController extends AbstractController
public function deleteEvent(Event $event, EntityManagerInterface $em, EventIndexService $eventIndex, AuditService $audit): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -1064,6 +1127,9 @@ class AccountController extends AbstractController
public function eventQrCode(Event $event, UrlGeneratorInterface $urlGenerator): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -1159,6 +1225,18 @@ class AccountController extends AbstractController
]);
}
private function requireStripeReady(): ?Response
{
/** @var User $user */
$user = $this->getUser();
if (!$user->isStripeChargesEnabled() || !$user->isStripePayoutsEnabled()) {
return $this->redirectToRoute('app_account');
}
return null;
}
/**
* @codeCoverageIgnore Requires live Stripe API
*/
@@ -1198,6 +1276,9 @@ class AccountController extends AbstractController
public function export(int $year, int $month, ExportService $exportService): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();
@@ -1216,6 +1297,9 @@ class AccountController extends AbstractController
public function exportPdf(int $year, int $month, ExportService $exportService): Response
{
$this->denyAccessUnlessGranted('ROLE_ORGANIZER');
if ($redirect = $this->requireStripeReady()) {
return $redirect;
}
/** @var User $user */
$user = $this->getUser();

View File

@@ -77,9 +77,11 @@
{% endif %}
{% endif %}
{% set stripeReady = app.user.stripeChargesEnabled and app.user.stripePayoutsEnabled %}
<div class="flex flex-wrap overflow-x-auto mb-8">
{% set isSubAccount = app.user.parentOrganizer is not null %}
{% if isOrganizer %}
{% if isOrganizer and stripeReady %}
{% if not isSubAccount or app.user.hasPermission('events') %}
<a href="{{ path('app_account', {tab: 'events'}) }}" class="flex-1 min-w-[100px] text-center py-3 border-3 border-gray-900 border-r-0 {{ tab == 'events' ? 'bg-yellow-400' : 'bg-white' }} font-black uppercase text-xs tracking-widest transition-all">Evenements / Brocantes</a>
{% endif %}
@@ -245,7 +247,7 @@
{% endif %}
</div>
{% elseif tab == 'events' %}
{% elseif tab == 'events' and stripeReady %}
<div class="flex flex-wrap items-center justify-between gap-4 mb-6">
<a href="{{ path('app_account_create_event') }}" class="btn-brutal font-black uppercase text-xs tracking-widest hover:bg-indigo-600 hover:text-white transition-all">
+ Creer un evenement
@@ -339,7 +341,7 @@
</div>
{% endif %}
{% elseif tab == 'subaccounts' %}
{% elseif tab == 'subaccounts' and stripeReady %}
<div class="card-brutal p-6 mb-8">
<h2 class="text-sm font-black uppercase tracking-widest mb-4">Creer un sous-compte</h2>
@@ -427,7 +429,7 @@
{% endif %}
</div>
{% elseif tab == 'payouts' %}
{% elseif tab == 'payouts' and stripeReady %}
<div class="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-6 gap-4 mb-6">
<div class="card-brutal p-4 text-center">
<div class="text-[10px] font-black uppercase tracking-widest text-gray-500 mb-1">Encaisse</div>

View File

@@ -217,7 +217,12 @@ class AccountControllerTest extends WebTestCase
public function testOrganizerWithoutStripeShowsSetupMessage(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createUser(['ROLE_ORGANIZER'], true);
$user->setStripeAccountId(null);
$user->setStripeChargesEnabled(false);
$user->setStripePayoutsEnabled(false);
$em->flush();
$client->loginUser($user);
$crawler = $client->request('GET', '/mon-compte');
@@ -232,6 +237,8 @@ class AccountControllerTest extends WebTestCase
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createUser(['ROLE_ORGANIZER'], true);
$user->setStripeAccountId('acct_pending');
$user->setStripeChargesEnabled(false);
$user->setStripePayoutsEnabled(false);
$em->flush();
$client->loginUser($user);
@@ -241,6 +248,47 @@ class AccountControllerTest extends WebTestCase
self::assertSelectorTextContains('body', 'en cours de verification');
}
public function testOrganizerWithoutStripeRedirectsFromOrganizerTabs(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createUser(['ROLE_ORGANIZER'], true);
$user->setStripeAccountId(null);
$user->setStripeChargesEnabled(false);
$user->setStripePayoutsEnabled(false);
$em->flush();
$client->loginUser($user);
$client->request('GET', '/mon-compte?tab=events');
self::assertResponseIsSuccessful();
self::assertSelectorTextContains('body', 'Configuration Stripe requise');
$client->request('GET', '/mon-compte?tab=subaccounts');
self::assertResponseIsSuccessful();
self::assertSelectorTextContains('body', 'Configuration Stripe requise');
$client->request('GET', '/mon-compte?tab=payouts');
self::assertResponseIsSuccessful();
self::assertSelectorTextContains('body', 'Configuration Stripe requise');
}
public function testOrganizerWithoutStripeBlocksEventCreation(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createUser(['ROLE_ORGANIZER'], true);
$user->setStripeAccountId(null);
$user->setStripeChargesEnabled(false);
$user->setStripePayoutsEnabled(false);
$em->flush();
$client->loginUser($user);
$client->request('GET', '/mon-compte/evenement/creer');
self::assertResponseRedirects('/mon-compte');
}
public function testOrganizerWithStripeActiveShowsSuccess(): void
{
$client = static::createClient();
@@ -2170,6 +2218,11 @@ class AccountControllerTest extends WebTestCase
if ($approved) {
$user->setIsApproved(true);
if (\in_array('ROLE_ORGANIZER', $roles, true)) {
$user->setStripeAccountId('acct_test_'.uniqid());
$user->setStripeChargesEnabled(true);
$user->setStripePayoutsEnabled(true);
}
}
$em->persist($user);