Payout system:
- Create Payout entity (stripePayoutId, status, amount, currency, destination, arrivalDate)
- Webhook handles payout.created/updated/paid/failed/canceled with email notification
- Payout list in /mon-compte virements tab with status badges
- PDF attestation on paid payouts with email attachment
PDF attestation:
- dompdf with DejaVu Sans font, yellow-orange gradient background
- Orange centered title bar, E-Cosplay logo, emitter/beneficiary info blocks
- QR code linking to /attestation/check/{payoutId} for authenticity verification
- Public verification page: shows payout details if valid, error if altered
- Legal disclaimer and CGV reference
- Button visible only when status is paid, opens in new tab
Sub-accounts:
- Add parentOrganizer (self-referencing ManyToOne) and subAccountPermissions (JSON) to User
- Permissions: scanner (validate tickets), events (CRUD), tickets (free invitations)
- Create sub-account with random password, send email with credentials
- Edit page with name/email/permissions checkboxes
- Delete with confirmation
- hasPermission() helper method
Account improvements:
- Block entire page for unapproved organizers with validation pending message
- Display stripeStatus in Stripe Connect banners
- Remove test payout button
Webhook v2 Connect events:
- v2.core.account.created/updated/closed → update stripeStatus
- capability_status_updated → sync charges/payouts enabled from capabilities
- PayoutPdfService for reusable PDF generation
Migrations: stripeStatus, Payout table, sub-account fields, drop pdfPath
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
284 lines
8.1 KiB
PHP
284 lines
8.1 KiB
PHP
<?php
|
|
|
|
namespace App\Tests\Controller;
|
|
|
|
use App\Entity\User;
|
|
use Doctrine\ORM\EntityManagerInterface;
|
|
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
|
|
|
|
class AccountControllerTest extends WebTestCase
|
|
{
|
|
public function testAccountRedirectsWhenNotAuthenticated(): void
|
|
{
|
|
$client = static::createClient();
|
|
$client->request('GET', '/mon-compte');
|
|
|
|
self::assertResponseRedirects();
|
|
}
|
|
|
|
public function testAccountReturnsSuccessWhenAuthenticated(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser();
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/mon-compte');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
}
|
|
|
|
public function testAccountTicketsTab(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser();
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/mon-compte?tab=tickets');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
}
|
|
|
|
public function testAccountPurchasesTab(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser();
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/mon-compte?tab=purchases');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
}
|
|
|
|
public function testAccountInvoicesTab(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser();
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/mon-compte?tab=invoices');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
}
|
|
|
|
public function testAccountSettingsTab(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser();
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/mon-compte?tab=settings');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
}
|
|
|
|
public function testAccountSettingsSubmit(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser();
|
|
|
|
$client->loginUser($user);
|
|
$client->request('POST', '/mon-compte/parametres', [
|
|
'first_name' => 'Updated',
|
|
'last_name' => 'Name',
|
|
'email' => $user->getEmail(),
|
|
'phone' => '0699887766',
|
|
'address' => '1 rue Test',
|
|
'postal_code' => '75001',
|
|
'city' => 'Paris',
|
|
]);
|
|
|
|
self::assertResponseRedirects('/mon-compte?tab=settings');
|
|
}
|
|
|
|
public function testOrganizerEventsTab(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/mon-compte?tab=events');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
}
|
|
|
|
public function testOrganizerSubaccountsTab(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/mon-compte?tab=subaccounts');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
}
|
|
|
|
public function testOrganizerPayoutsTab(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/mon-compte?tab=payouts');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
}
|
|
|
|
public function testOrganizerSettingsDisablesNameFields(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
|
|
$client->loginUser($user);
|
|
$client->request('POST', '/mon-compte/parametres', [
|
|
'email' => $user->getEmail(),
|
|
'phone' => '0699887766',
|
|
]);
|
|
|
|
self::assertResponseRedirects('/mon-compte?tab=settings');
|
|
}
|
|
|
|
public function testOrganizerNotApprovedShowsBlockingMessage(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], false);
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/mon-compte');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
self::assertSelectorTextContains('body', 'en cours de validation');
|
|
}
|
|
|
|
public function testOrganizerDefaultTabIsEvents(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/mon-compte');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
}
|
|
|
|
public function testStripeConnectRedirectsForNonOrganizer(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser();
|
|
|
|
$client->loginUser($user);
|
|
$client->request('POST', '/mon-compte/stripe-connect');
|
|
|
|
self::assertResponseRedirects('/mon-compte');
|
|
}
|
|
|
|
public function testOrganizerWithoutStripeShowsSetupMessage(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
|
|
$client->loginUser($user);
|
|
$crawler = $client->request('GET', '/mon-compte');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
self::assertSelectorTextContains('body', 'Configuration Stripe requise');
|
|
}
|
|
|
|
public function testOrganizerWithStripePendingShowsMessage(): void
|
|
{
|
|
$client = static::createClient();
|
|
$em = static::getContainer()->get(EntityManagerInterface::class);
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
$user->setStripeAccountId('acct_pending');
|
|
$em->flush();
|
|
|
|
$client->loginUser($user);
|
|
$crawler = $client->request('GET', '/mon-compte');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
self::assertSelectorTextContains('body', 'en cours de verification');
|
|
}
|
|
|
|
public function testOrganizerWithStripeActiveShowsSuccess(): void
|
|
{
|
|
$client = static::createClient();
|
|
$em = static::getContainer()->get(EntityManagerInterface::class);
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
$user->setStripeAccountId('acct_active');
|
|
$user->setStripeChargesEnabled(true);
|
|
$user->setStripePayoutsEnabled(true);
|
|
$em->flush();
|
|
|
|
$client->loginUser($user);
|
|
$crawler = $client->request('GET', '/mon-compte');
|
|
|
|
self::assertResponseIsSuccessful();
|
|
self::assertSelectorTextContains('body', 'Stripe Connect actif');
|
|
}
|
|
|
|
public function testStripeConnectReturn(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/stripe/connect/return');
|
|
|
|
self::assertResponseRedirects('/mon-compte');
|
|
}
|
|
|
|
public function testStripeConnectRefresh(): void
|
|
{
|
|
$client = static::createClient();
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
|
|
$client->loginUser($user);
|
|
$client->request('GET', '/stripe/connect/refresh');
|
|
|
|
self::assertResponseRedirects('/mon-compte/stripe-connect');
|
|
}
|
|
|
|
public function testStripeCancelResetsAccount(): void
|
|
{
|
|
$client = static::createClient();
|
|
$em = static::getContainer()->get(EntityManagerInterface::class);
|
|
$user = $this->createUser(['ROLE_ORGANIZER'], true);
|
|
$user->setStripeAccountId('acct_cancel');
|
|
$em->flush();
|
|
|
|
$client->loginUser($user);
|
|
$client->request('POST', '/mon-compte/stripe-cancel');
|
|
|
|
self::assertResponseRedirects('/mon-compte');
|
|
|
|
$em->refresh($user);
|
|
self::assertNull($user->getStripeAccountId());
|
|
}
|
|
|
|
/**
|
|
* @param list<string> $roles
|
|
*/
|
|
/**
|
|
* @param list<string> $roles
|
|
*/
|
|
private function createUser(array $roles = [], bool $approved = false): User
|
|
{
|
|
$em = static::getContainer()->get(EntityManagerInterface::class);
|
|
|
|
$user = new User();
|
|
$user->setEmail('test-account-'.uniqid().'@example.com');
|
|
$user->setFirstName('Test');
|
|
$user->setLastName('User');
|
|
$user->setPassword('$2y$13$hashed');
|
|
$user->setRoles($roles);
|
|
|
|
if ($approved) {
|
|
$user->setIsApproved(true);
|
|
}
|
|
|
|
$em->persist($user);
|
|
$em->flush();
|
|
|
|
return $user;
|
|
}
|
|
}
|