Files
e-ticket/src/Command/StripeSyncCommand.php
Serreau Jovann cda80990c7 Remove pending orders sync from StripeSyncCommand and add pessimistic lock to webhook payment handler
- Remove syncPendingOrders and its helpers (handleSucceeded, handleCancelled, handleFailed) from StripeSyncCommand
- Clean up unused dependencies (BilletOrderService, MailerService, AuditService, BilletBuyer)
- Add PESSIMISTIC_WRITE lock in handlePaymentIntentSucceeded to prevent duplicate ticket generation when Stripe sends concurrent webhook calls
- Update tests to match simplified command

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-04 10:19:40 +02:00

97 lines
3.1 KiB
PHP

<?php
namespace App\Command;
use App\Entity\User;
use App\Service\StripeService;
use Doctrine\ORM\EntityManagerInterface;
use Symfony\Component\Console\Attribute\AsCommand;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
#[AsCommand(
name: 'app:stripe:sync',
description: 'Sync Stripe account status for all organizers',
)]
class StripeSyncCommand extends Command
{
public function __construct(
private EntityManagerInterface $em,
private StripeService $stripeService,
) {
parent::__construct();
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$hasErrors = $this->syncAccounts($io);
return $hasErrors ? Command::FAILURE : Command::SUCCESS;
}
private function syncAccounts(SymfonyStyle $io): bool
{
$organizers = array_filter(
$this->em->getRepository(User::class)->findAll(),
fn (User $u) => \in_array('ROLE_ORGANIZER', $u->getRoles(), true) && null !== $u->getStripeAccountId(),
);
if (0 === \count($organizers)) {
$io->info('No organizers with Stripe accounts found.');
return false;
}
$io->info(sprintf('Syncing %d organizer(s)...', \count($organizers)));
$synced = 0;
$errors = 0;
foreach ($organizers as $user) {
try {
$status = $this->stripeService->retrieveAccountStatus($user->getStripeAccountId());
$chargesBefore = $user->isStripeChargesEnabled();
$payoutsBefore = $user->isStripePayoutsEnabled();
$user->setStripeChargesEnabled($status['charges_enabled']);
$user->setStripePayoutsEnabled($status['payouts_enabled']);
$changed = $chargesBefore !== $user->isStripeChargesEnabled()
|| $payoutsBefore !== $user->isStripePayoutsEnabled();
$status = $changed ? '<fg=yellow>UPDATED</>' : '<fg=green>OK</>';
$io->text(sprintf(
' [%s] %s (%s) — charges: %s, payouts: %s',
$status,
$user->getCompanyName() ?? $user->getEmail(),
$user->getStripeAccountId(),
$user->isStripeChargesEnabled() ? 'yes' : 'no',
$user->isStripePayoutsEnabled() ? 'yes' : 'no',
));
++$synced;
} catch (\Throwable $e) {
$io->text(sprintf(
' [<fg=red>ERROR</>] %s (%s) — %s',
$user->getCompanyName() ?? $user->getEmail(),
$user->getStripeAccountId(),
$e->getMessage(),
));
++$errors;
}
}
$this->em->flush();
$io->success(sprintf('Accounts: %d synced, %d error(s).', $synced, $errors));
return $errors > 0;
}
}