- 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>
97 lines
3.1 KiB
PHP
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;
|
|
}
|
|
|
|
}
|