Files
e-ticket/tests/Controller/StripeWebhookControllerTest.php
Serreau Jovann 8257615c32 Add flash messages to login page, French security translations, fix CS
- Add success flash messages display on login page (reset password, email verified)
- Create translations/security.fr.yaml with French security messages
- Set default_locale to fr in translation.yaml
- Remove unused StripeObject import (CS Fixer)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-03-20 08:55:43 +01:00

440 lines
16 KiB
PHP

<?php
namespace App\Tests\Controller;
use App\Entity\Payout;
use App\Entity\User;
use App\Service\MailerService;
use App\Service\PayoutPdfService;
use App\Service\StripeService;
use Doctrine\ORM\EntityManagerInterface;
use Stripe\Event;
use Symfony\Bundle\FrameworkBundle\Test\WebTestCase;
class StripeWebhookControllerTest extends WebTestCase
{
public function testWebhookWithValidSignature(): void
{
$client = static::createClient();
$stripeService = $this->createMock(StripeService::class);
$stripeService->method('verifyWebhookSignature')->willReturn(new Event());
static::getContainer()->set(StripeService::class, $stripeService);
$client->request('POST', '/stripe/webhook', [], [], [
'HTTP_STRIPE_SIGNATURE' => 'valid',
], '{}');
self::assertResponseIsSuccessful();
self::assertSame('OK', $client->getResponse()->getContent());
}
public function testWebhookWithInvalidSignature(): void
{
$client = static::createClient();
$stripeService = $this->createMock(StripeService::class);
$stripeService->method('verifyWebhookSignature')->willReturn(null);
static::getContainer()->set(StripeService::class, $stripeService);
$client->request('POST', '/stripe/webhook', [], [], [
'HTTP_STRIPE_SIGNATURE' => 'invalid',
], '{}');
self::assertResponseStatusCodeSame(400);
}
public function testAccountCreatedSetsStartedStatus(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createOrgaUser($em, 'acct_created');
$event = new Event();
$event->type = 'v2.core.account.created';
$this->mockStripe($client, $event);
$payload = json_encode(['related_object' => ['id' => 'acct_created']]);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], $payload);
self::assertResponseIsSuccessful();
$freshEm = static::getContainer()->get(EntityManagerInterface::class);
$updated = $freshEm->getRepository(User::class)->findOneBy(['stripeAccountId' => 'acct_created']);
self::assertSame('started', $updated->getStripeStatus());
}
public function testAccountClosedDisablesFlags(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createOrgaUser($em, 'acct_closed');
$user->setStripeChargesEnabled(true);
$user->setStripePayoutsEnabled(true);
$em->flush();
$event = new Event();
$event->type = 'v2.core.account.closed';
$this->mockStripe($client, $event);
$payload = json_encode(['related_object' => ['id' => 'acct_closed']]);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], $payload);
self::assertResponseIsSuccessful();
$freshEm = static::getContainer()->get(EntityManagerInterface::class);
$updated = $freshEm->getRepository(User::class)->findOneBy(['stripeAccountId' => 'acct_closed']);
self::assertSame('closed', $updated->getStripeStatus());
self::assertFalse($updated->isStripeChargesEnabled());
self::assertFalse($updated->isStripePayoutsEnabled());
}
public function testAccountUpdatedSetsStatus(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$this->createOrgaUser($em, 'acct_upd');
$event = new Event();
$event->type = 'v2.core.account.updated';
$this->mockStripe($client, $event);
$payload = json_encode(['related_object' => ['id' => 'acct_upd']]);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], $payload);
self::assertResponseIsSuccessful();
$freshEm = static::getContainer()->get(EntityManagerInterface::class);
$updated = $freshEm->getRepository(User::class)->findOneBy(['stripeAccountId' => 'acct_upd']);
self::assertSame('updated', $updated->getStripeStatus());
}
public function testAccountStatusUnknownUser(): void
{
$client = static::createClient();
$event = new Event();
$event->type = 'v2.core.account.created';
$this->mockStripe($client, $event);
$payload = json_encode(['related_object' => ['id' => 'acct_nonexistent']]);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], $payload);
self::assertResponseIsSuccessful();
}
public function testAccountStatusNoRelatedObject(): void
{
$client = static::createClient();
$event = new Event();
$event->type = 'v2.core.account.created';
$this->mockStripe($client, $event);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], '{}');
self::assertResponseIsSuccessful();
}
public function testMerchantCapabilityCardPaymentsActive(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$this->createOrgaUser($em, 'acct_merchant');
$event = new Event();
$event->type = 'v2.core.account[configuration.merchant].capability_status_updated';
$this->mockStripe($client, $event);
$payload = json_encode([
'related_object' => ['id' => 'acct_merchant'],
'changes' => ['after' => ['configuration' => [
'merchant' => ['capabilities' => [
'card_payments' => ['status' => 'active'],
]],
]]],
]);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], $payload);
self::assertResponseIsSuccessful();
$freshEm = static::getContainer()->get(EntityManagerInterface::class);
$updated = $freshEm->getRepository(User::class)->findOneBy(['stripeAccountId' => 'acct_merchant']);
self::assertTrue($updated->isStripeChargesEnabled());
}
public function testRecipientCapabilityPayoutsActive(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$this->createOrgaUser($em, 'acct_recipient');
$event = new Event();
$event->type = 'v2.core.account[configuration.recipient].capability_status_updated';
$this->mockStripe($client, $event);
$payload = json_encode([
'related_object' => ['id' => 'acct_recipient'],
'changes' => ['after' => ['configuration' => [
'recipient' => ['capabilities' => [
'stripe_balance' => ['payouts' => ['status' => 'active']],
]],
]]],
]);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], $payload);
self::assertResponseIsSuccessful();
$freshEm = static::getContainer()->get(EntityManagerInterface::class);
$updated = $freshEm->getRepository(User::class)->findOneBy(['stripeAccountId' => 'acct_recipient']);
self::assertTrue($updated->isStripePayoutsEnabled());
}
public function testCapabilityBothActiveSetStatusActive(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createOrgaUser($em, 'acct_both');
$user->setStripeChargesEnabled(true);
$em->flush();
$event = new Event();
$event->type = 'v2.core.account[configuration.merchant].capability_status_updated';
$this->mockStripe($client, $event);
$payload = json_encode([
'related_object' => ['id' => 'acct_both'],
'changes' => ['after' => ['configuration' => [
'merchant' => ['capabilities' => [
'stripe_balance' => ['payouts' => ['status' => 'active']],
]],
]]],
]);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], $payload);
self::assertResponseIsSuccessful();
$freshEm = static::getContainer()->get(EntityManagerInterface::class);
$updated = $freshEm->getRepository(User::class)->findOneBy(['stripeAccountId' => 'acct_both']);
self::assertSame('active', $updated->getStripeStatus());
}
public function testCapabilityUnknownUser(): void
{
$client = static::createClient();
$event = new Event();
$event->type = 'v2.core.account[configuration.merchant].capability_status_updated';
$this->mockStripe($client, $event);
$payload = json_encode([
'related_object' => ['id' => 'acct_ghost'],
'changes' => ['after' => ['configuration' => []]],
]);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], $payload);
self::assertResponseIsSuccessful();
}
public function testPayoutCreatedSavesAndSendsEmail(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createOrgaUser($em, 'acct_payout');
$event = Event::constructFrom([
'type' => 'payout.created',
'account' => 'acct_payout',
'data' => ['object' => [
'id' => 'po_test_wh_'.uniqid(),
'status' => 'pending',
'amount' => 5000,
'currency' => 'eur',
'destination' => 'ba_xxx',
]],
]);
$stripeService = $this->createMock(StripeService::class);
$stripeService->method('verifyWebhookSignature')->willReturn($event);
static::getContainer()->set(StripeService::class, $stripeService);
$mailer = $this->createMock(MailerService::class);
$mailer->expects(self::once())->method('sendEmail');
static::getContainer()->set(MailerService::class, $mailer);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], '{}');
self::assertResponseIsSuccessful();
}
public function testPayoutPaidSendsEmailWithPdf(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createOrgaUser($em, 'acct_paid');
$payoutId = 'po_paid_'.uniqid();
$event = Event::constructFrom([
'type' => 'payout.paid',
'account' => 'acct_paid',
'data' => ['object' => [
'id' => $payoutId,
'status' => 'paid',
'amount' => 10000,
'currency' => 'eur',
'destination' => 'ba_yyy',
'arrival_date' => time() + 86400,
]],
]);
$stripeService = $this->createMock(StripeService::class);
$stripeService->method('verifyWebhookSignature')->willReturn($event);
static::getContainer()->set(StripeService::class, $stripeService);
$mailer = $this->createMock(MailerService::class);
$mailer->expects(self::once())->method('sendEmail');
static::getContainer()->set(MailerService::class, $mailer);
$pdfService = $this->createMock(PayoutPdfService::class);
$pdfService->expects(self::once())->method('generateToFile')->willReturn('/tmp/fake.pdf');
static::getContainer()->set(PayoutPdfService::class, $pdfService);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], '{}');
self::assertResponseIsSuccessful();
$freshEm = static::getContainer()->get(EntityManagerInterface::class);
$payout = $freshEm->getRepository(Payout::class)->findOneBy(['stripePayoutId' => $payoutId]);
self::assertNotNull($payout);
self::assertSame('paid', $payout->getStatus());
self::assertSame(10000, $payout->getAmount());
}
public function testPayoutUpdatedExistingPayout(): void
{
$client = static::createClient();
$em = static::getContainer()->get(EntityManagerInterface::class);
$user = $this->createOrgaUser($em, 'acct_upd_po');
$payout = new Payout();
$payout->setOrganizer($user);
$payout->setStripePayoutId('po_existing_'.uniqid());
$payout->setStatus('pending');
$payout->setAmount(3000);
$payout->setCurrency('eur');
$payout->setStripeAccountId('acct_upd_po');
$em->persist($payout);
$em->flush();
$event = Event::constructFrom([
'type' => 'payout.updated',
'account' => 'acct_upd_po',
'data' => ['object' => [
'id' => $payout->getStripePayoutId(),
'status' => 'in_transit',
'amount' => 3000,
'currency' => 'eur',
'destination' => 'ba_zzz',
]],
]);
$stripeService = $this->createMock(StripeService::class);
$stripeService->method('verifyWebhookSignature')->willReturn($event);
static::getContainer()->set(StripeService::class, $stripeService);
$mailer = $this->createMock(MailerService::class);
$mailer->expects(self::once())->method('sendEmail');
static::getContainer()->set(MailerService::class, $mailer);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], '{}');
self::assertResponseIsSuccessful();
$freshEm = static::getContainer()->get(EntityManagerInterface::class);
$updated = $freshEm->getRepository(Payout::class)->findOneBy(['stripePayoutId' => $payout->getStripePayoutId()]);
self::assertSame('in_transit', $updated->getStatus());
}
public function testPayoutNoPayoutId(): void
{
$client = static::createClient();
$event = Event::constructFrom([
'type' => 'payout.created',
'data' => ['object' => []],
]);
$stripeService = $this->createMock(StripeService::class);
$stripeService->method('verifyWebhookSignature')->willReturn($event);
static::getContainer()->set(StripeService::class, $stripeService);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], '{}');
self::assertResponseIsSuccessful();
}
public function testPayoutNoUser(): void
{
$client = static::createClient();
$event = Event::constructFrom([
'type' => 'payout.created',
'account' => 'acct_nouser',
'data' => ['object' => [
'id' => 'po_nouser_'.uniqid(),
'status' => 'pending',
'amount' => 1000,
'currency' => 'eur',
]],
]);
$stripeService = $this->createMock(StripeService::class);
$stripeService->method('verifyWebhookSignature')->willReturn($event);
static::getContainer()->set(StripeService::class, $stripeService);
$client->request('POST', '/stripe/webhook', [], [], ['HTTP_STRIPE_SIGNATURE' => 'v'], '{}');
self::assertResponseIsSuccessful();
}
private function createOrgaUser(EntityManagerInterface $em, string $stripeAccountId): User
{
$user = new User();
$user->setEmail('test-wh-'.uniqid().'@example.com');
$user->setFirstName('WH');
$user->setLastName('Test');
$user->setPassword('$2y$13$hashed');
$user->setRoles(['ROLE_ORGANIZER']);
$user->setStripeAccountId($stripeAccountId);
$em->persist($user);
$em->flush();
return $user;
}
private function mockStripe(\Symfony\Bundle\FrameworkBundle\KernelBrowser $client, Event $event): void
{
$stripeService = $this->createMock(StripeService::class);
$stripeService->method('verifyWebhookSignature')->willReturn($event);
static::getContainer()->set(StripeService::class, $stripeService);
}
}