diff --git a/migrations/Version20260319194243.php b/migrations/Version20260319194243.php new file mode 100644 index 0000000..76c2bc4 --- /dev/null +++ b/migrations/Version20260319194243.php @@ -0,0 +1,33 @@ +addSql('ALTER TABLE "user" ADD stripe_charges_enabled BOOLEAN NOT NULL DEFAULT FALSE'); + $this->addSql('ALTER TABLE "user" ADD stripe_payouts_enabled BOOLEAN NOT NULL DEFAULT FALSE'); + } + + public function down(Schema $schema): void + { + // this down() migration is auto-generated, please modify it to your needs + $this->addSql('ALTER TABLE "user" DROP stripe_charges_enabled'); + $this->addSql('ALTER TABLE "user" DROP stripe_payouts_enabled'); + } +} diff --git a/src/Controller/StripeWebhookController.php b/src/Controller/StripeWebhookController.php index 9d69ca7..dec2e43 100644 --- a/src/Controller/StripeWebhookController.php +++ b/src/Controller/StripeWebhookController.php @@ -2,7 +2,9 @@ namespace App\Controller; +use App\Entity\User; use App\Service\StripeService; +use Doctrine\ORM\EntityManagerInterface; use Symfony\Bundle\FrameworkBundle\Controller\AbstractController; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; @@ -11,7 +13,7 @@ use Symfony\Component\Routing\Attribute\Route; class StripeWebhookController extends AbstractController { #[Route('/stripe/webhook', name: 'app_stripe_webhook', methods: ['POST'])] - public function webhook(Request $request, StripeService $stripeService): Response + public function webhook(Request $request, StripeService $stripeService, EntityManagerInterface $em): Response { $payload = $request->getContent(); $signature = $request->headers->get('Stripe-Signature', ''); @@ -22,6 +24,21 @@ class StripeWebhookController extends AbstractController return new Response('Invalid signature', 400); } + if ('account.updated' === $event->type) { + $account = $event->data->object; + $accountId = $account->id ?? null; + + if ($accountId) { + $user = $em->getRepository(User::class)->findOneBy(['stripeAccountId' => $accountId]); + + if ($user) { + $user->setStripeChargesEnabled((bool) ($account->charges_enabled ?? false)); + $user->setStripePayoutsEnabled((bool) ($account->payouts_enabled ?? false)); + $em->flush(); + } + } + } + return new Response('OK', 200); } } diff --git a/src/Entity/User.php b/src/Entity/User.php index 102cf89..36ddec3 100644 --- a/src/Entity/User.php +++ b/src/Entity/User.php @@ -88,6 +88,12 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface #[ORM\Column(length: 255, nullable: true)] private ?string $stripeAccountId = null; + #[ORM\Column] + private bool $stripeChargesEnabled = false; + + #[ORM\Column] + private bool $stripePayoutsEnabled = false; + #[ORM\Column(length: 64, nullable: true)] private ?string $emailVerificationToken = null; @@ -311,6 +317,30 @@ class User implements UserInterface, PasswordAuthenticatedUserInterface return $this; } + public function isStripeChargesEnabled(): bool + { + return $this->stripeChargesEnabled; + } + + public function setStripeChargesEnabled(bool $stripeChargesEnabled): static + { + $this->stripeChargesEnabled = $stripeChargesEnabled; + + return $this; + } + + public function isStripePayoutsEnabled(): bool + { + return $this->stripePayoutsEnabled; + } + + public function setStripePayoutsEnabled(bool $stripePayoutsEnabled): static + { + $this->stripePayoutsEnabled = $stripePayoutsEnabled; + + return $this; + } + public function getResetCode(): ?string { return $this->resetCode; diff --git a/src/Service/StripeService.php b/src/Service/StripeService.php index 0195be8..6725cf1 100644 --- a/src/Service/StripeService.php +++ b/src/Service/StripeService.php @@ -2,6 +2,7 @@ namespace App\Service; +use App\Entity\User; use Stripe\Event; use Stripe\Exception\SignatureVerificationException; use Stripe\StripeClient; @@ -75,6 +76,38 @@ class StripeService } } + public function createAccountConnect(User $user): string + { + $account = $this->stripe->accounts->create([ + 'type' => 'express', + 'country' => 'FR', + 'email' => $user->getEmail(), + 'capabilities' => [ + 'card_payments' => ['requested' => true], + 'transfers' => ['requested' => true], + ], + 'business_type' => \in_array('ROLE_ORGANIZER', $user->getRoles(), true) ? 'company' : 'individual', + 'business_profile' => [ + 'name' => $user->getCompanyName(), + 'url' => $this->outsideUrl, + ], + ]); + + return $account->id; + } + + public function createAccountLink(string $accountId): string + { + $link = $this->stripe->accountLinks->create([ + 'account' => $accountId, + 'refresh_url' => $this->outsideUrl.'/stripe/connect/refresh', + 'return_url' => $this->outsideUrl.'/stripe/connect/return', + 'type' => 'account_onboarding', + ]); + + return $link->url; + } + public function getClient(): StripeClient { return $this->stripe; diff --git a/templates/email/organizer_approved.html.twig b/templates/email/organizer_approved.html.twig index 21da7ef..88ebfb2 100644 --- a/templates/email/organizer_approved.html.twig +++ b/templates/email/organizer_approved.html.twig @@ -6,6 +6,10 @@

Felicitations {{ firstName }} !

Votre demande de compte organisateur a ete approuvee par l'equipe E-Ticket.

Vous pouvez desormais vous connecter et commencer a creer vos evenements.

+
+

Important :

+

Une fois connecte a votre compte, vous devrez effectuer la verification Stripe pour pouvoir recevoir les paiements de vos evenements. Cette etape est obligatoire pour activer les virements sur votre compte bancaire.

+

Se connecter

diff --git a/tests/Controller/StripeWebhookControllerTest.php b/tests/Controller/StripeWebhookControllerTest.php index 92938d0..e0d8ff4 100644 --- a/tests/Controller/StripeWebhookControllerTest.php +++ b/tests/Controller/StripeWebhookControllerTest.php @@ -2,8 +2,11 @@ namespace App\Tests\Controller; +use App\Entity\User; use App\Service\StripeService; +use Doctrine\ORM\EntityManagerInterface; use Stripe\Event; +use Stripe\StripeObject; use Symfony\Bundle\FrameworkBundle\Test\WebTestCase; class StripeWebhookControllerTest extends WebTestCase @@ -38,4 +41,68 @@ class StripeWebhookControllerTest extends WebTestCase self::assertResponseStatusCodeSame(400); } + + public function testWebhookAccountUpdatedSetsFlags(): void + { + $client = static::createClient(); + $em = static::getContainer()->get(EntityManagerInterface::class); + + $user = new User(); + $user->setEmail('test-stripe-'.uniqid().'@example.com'); + $user->setFirstName('Stripe'); + $user->setLastName('Test'); + $user->setPassword('$2y$13$hashed'); + $user->setStripeAccountId('acct_test123'); + $em->persist($user); + $em->flush(); + + $account = StripeObject::constructFrom([ + 'id' => 'acct_test123', + 'charges_enabled' => true, + 'payouts_enabled' => true, + ]); + + $event = new Event(); + $event->type = 'account.updated'; + $event->data = StripeObject::constructFrom(['object' => $account]); + + $stripeService = $this->createMock(StripeService::class); + $stripeService->method('verifyWebhookSignature')->willReturn($event); + static::getContainer()->set(StripeService::class, $stripeService); + + $client->request('POST', '/stripe/webhook', [], [], [ + 'HTTP_STRIPE_SIGNATURE' => 'valid', + ], '{}'); + + self::assertResponseIsSuccessful(); + + $em->refresh($user); + self::assertTrue($user->isStripeChargesEnabled()); + self::assertTrue($user->isStripePayoutsEnabled()); + } + + public function testWebhookAccountUpdatedUnknownAccount(): void + { + $client = static::createClient(); + + $account = StripeObject::constructFrom([ + 'id' => 'acct_unknown', + 'charges_enabled' => true, + 'payouts_enabled' => true, + ]); + + $event = new Event(); + $event->type = 'account.updated'; + $event->data = StripeObject::constructFrom(['object' => $account]); + + $stripeService = $this->createMock(StripeService::class); + $stripeService->method('verifyWebhookSignature')->willReturn($event); + static::getContainer()->set(StripeService::class, $stripeService); + + $client->request('POST', '/stripe/webhook', [], [], [ + 'HTTP_STRIPE_SIGNATURE' => 'valid', + ], '{}'); + + self::assertResponseIsSuccessful(); + } } diff --git a/tests/Entity/UserTest.php b/tests/Entity/UserTest.php index 31086cd..064cd36 100644 --- a/tests/Entity/UserTest.php +++ b/tests/Entity/UserTest.php @@ -146,15 +146,22 @@ class UserTest extends TestCase self::assertSame(1.5, $user->getCommissionRate()); } - public function testStripeAccountIdField(): void + public function testStripeFields(): void { $user = new User(); self::assertNull($user->getStripeAccountId()); + self::assertFalse($user->isStripeChargesEnabled()); + self::assertFalse($user->isStripePayoutsEnabled()); + + $result = $user->setStripeAccountId('acct_1234567890') + ->setStripeChargesEnabled(true) + ->setStripePayoutsEnabled(true); - $result = $user->setStripeAccountId('acct_1234567890'); self::assertSame($user, $result); self::assertSame('acct_1234567890', $user->getStripeAccountId()); + self::assertTrue($user->isStripeChargesEnabled()); + self::assertTrue($user->isStripePayoutsEnabled()); } public function testEmailVerificationFields(): void