Add Stripe integration: webhook controller, service, sync command, Connect account

- Create StripeService: webhook sync, signature verification, save secret to .env.local
- Create StripeWebhookController with signature verification (400 on invalid)
- Create stripe:sync command to auto-create webhook endpoint via Stripe API
- Webhook listens to all events (configurable later)
- Save webhook secret automatically to .env.local on creation
- Add stripeAccountId field to User entity for Stripe Connect + migration
- Tests: StripeServiceTest (5), StripeWebhookControllerTest (2), StripeSyncCommandTest (1), UserTest updated

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Serreau Jovann
2026-03-19 20:37:16 +01:00
parent 100ff96c70
commit 65887fb3da
9 changed files with 331 additions and 0 deletions

View File

@@ -0,0 +1,19 @@
<?php
namespace App\Tests\Command;
use App\Command\StripeSyncCommand;
use App\Service\StripeService;
use PHPUnit\Framework\TestCase;
class StripeSyncCommandTest extends TestCase
{
public function testCommandIsConfigured(): void
{
$stripeService = $this->createMock(StripeService::class);
$command = new StripeSyncCommand($stripeService);
self::assertSame('stripe:sync', $command->getName());
self::assertSame('Create or update the Stripe webhook endpoint', $command->getDescription());
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace App\Tests\Controller;
use App\Service\StripeService;
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);
}
}

View File

@@ -146,6 +146,17 @@ class UserTest extends TestCase
self::assertSame(1.5, $user->getCommissionRate());
}
public function testStripeAccountIdField(): void
{
$user = new User();
self::assertNull($user->getStripeAccountId());
$result = $user->setStripeAccountId('acct_1234567890');
self::assertSame($user, $result);
self::assertSame('acct_1234567890', $user->getStripeAccountId());
}
public function testEmailVerificationFields(): void
{
$user = new User();

View File

@@ -0,0 +1,63 @@
<?php
namespace App\Tests\Service;
use App\Service\StripeService;
use PHPUnit\Framework\TestCase;
class StripeServiceTest extends TestCase
{
public function testGetWebhookUrl(): void
{
$service = new StripeService('sk_test', 'whsec_test', 'https://example.com', '/tmp');
self::assertSame('https://example.com/stripe/webhook', $service->getWebhookUrl());
}
public function testGetWebhookUrlTrimsTrailingSlash(): void
{
$service = new StripeService('sk_test', 'whsec_test', 'https://example.com/', '/tmp');
self::assertSame('https://example.com/stripe/webhook', $service->getWebhookUrl());
}
public function testVerifyWebhookSignatureReturnsNullOnInvalid(): void
{
$service = new StripeService('sk_test', 'whsec_test', 'https://example.com', '/tmp');
self::assertNull($service->verifyWebhookSignature('{}', 'invalid'));
}
public function testSaveWebhookSecretCreatesEntry(): void
{
$tmpDir = sys_get_temp_dir().'/stripe_test_'.uniqid();
mkdir($tmpDir);
file_put_contents($tmpDir.'/.env.local', "APP_ENV=test\n");
$service = new StripeService('sk_test', 'whsec_test', 'https://example.com', $tmpDir);
$service->saveWebhookSecret('whsec_new123');
$content = file_get_contents($tmpDir.'/.env.local');
self::assertStringContainsString('STRIPE_WEBHOOK_SECRET=whsec_new123', $content);
unlink($tmpDir.'/.env.local');
rmdir($tmpDir);
}
public function testSaveWebhookSecretUpdatesExisting(): void
{
$tmpDir = sys_get_temp_dir().'/stripe_test_'.uniqid();
mkdir($tmpDir);
file_put_contents($tmpDir.'/.env.local', "APP_ENV=test\nSTRIPE_WEBHOOK_SECRET=old_secret\n");
$service = new StripeService('sk_test', 'whsec_test', 'https://example.com', $tmpDir);
$service->saveWebhookSecret('whsec_updated');
$content = file_get_contents($tmpDir.'/.env.local');
self::assertStringContainsString('STRIPE_WEBHOOK_SECRET=whsec_updated', $content);
self::assertStringNotContainsString('old_secret', $content);
unlink($tmpDir.'/.env.local');
rmdir($tmpDir);
}
}