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:
@@ -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();
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user