- 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>
440 lines
16 KiB
PHP
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);
|
|
}
|
|
}
|