Add pending orders reconciliation to stripe:sync command
- Add retrievePaymentIntent() to StripeService - StripeSyncCommand now checks pending orders against Stripe API: - succeeded: generates tickets, sends emails, notifies organizer - canceled: marks order as cancelled + audit log - requires_payment_method: marks as cancelled + audit + failure email - other statuses: logs as still pending - Add 13 tests covering accounts sync + all pending order scenarios Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2,7 +2,11 @@
|
||||
|
||||
namespace App\Command;
|
||||
|
||||
use App\Entity\BilletBuyer;
|
||||
use App\Entity\User;
|
||||
use App\Service\AuditService;
|
||||
use App\Service\BilletOrderService;
|
||||
use App\Service\MailerService;
|
||||
use App\Service\StripeService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Symfony\Component\Console\Attribute\AsCommand;
|
||||
@@ -13,13 +17,16 @@ use Symfony\Component\Console\Style\SymfonyStyle;
|
||||
|
||||
#[AsCommand(
|
||||
name: 'app:stripe:sync',
|
||||
description: 'Sync Stripe account status for all organizers',
|
||||
description: 'Sync Stripe account status for all organizers and reconcile pending orders',
|
||||
)]
|
||||
class StripeSyncCommand extends Command
|
||||
{
|
||||
public function __construct(
|
||||
private EntityManagerInterface $em,
|
||||
private StripeService $stripeService,
|
||||
private BilletOrderService $billetOrderService,
|
||||
private MailerService $mailerService,
|
||||
private AuditService $audit,
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
@@ -28,6 +35,14 @@ class StripeSyncCommand extends Command
|
||||
{
|
||||
$io = new SymfonyStyle($input, $output);
|
||||
|
||||
$hasErrors = $this->syncAccounts($io);
|
||||
$hasErrors = $this->syncPendingOrders($io) || $hasErrors;
|
||||
|
||||
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(),
|
||||
@@ -36,7 +51,7 @@ class StripeSyncCommand extends Command
|
||||
if (0 === \count($organizers)) {
|
||||
$io->info('No organizers with Stripe accounts found.');
|
||||
|
||||
return Command::SUCCESS;
|
||||
return false;
|
||||
}
|
||||
|
||||
$io->info(sprintf('Syncing %d organizer(s)...', \count($organizers)));
|
||||
@@ -81,8 +96,118 @@ class StripeSyncCommand extends Command
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
$io->success(sprintf('Done: %d synced, %d error(s).', $synced, $errors));
|
||||
$io->success(sprintf('Accounts: %d synced, %d error(s).', $synced, $errors));
|
||||
|
||||
return $errors > 0 ? Command::FAILURE : Command::SUCCESS;
|
||||
return $errors > 0;
|
||||
}
|
||||
|
||||
private function syncPendingOrders(SymfonyStyle $io): bool
|
||||
{
|
||||
$pendingOrders = $this->em->getRepository(BilletBuyer::class)->findBy([
|
||||
'status' => BilletBuyer::STATUS_PENDING,
|
||||
]);
|
||||
|
||||
if (0 === \count($pendingOrders)) {
|
||||
$io->info('No pending orders to reconcile.');
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
$io->info(sprintf('Checking %d pending order(s) on Stripe...', \count($pendingOrders)));
|
||||
|
||||
$processed = 0;
|
||||
$errors = 0;
|
||||
|
||||
foreach ($pendingOrders as $order) {
|
||||
$paymentIntentId = $order->getStripeSessionId();
|
||||
|
||||
if (!$paymentIntentId) {
|
||||
$io->text(sprintf(' [<fg=yellow>SKIP</>] Order #%s — no payment intent ID', $order->getOrderNumber()));
|
||||
continue;
|
||||
}
|
||||
|
||||
try {
|
||||
$paymentIntent = $this->stripeService->retrievePaymentIntent($paymentIntentId);
|
||||
$stripeStatus = $paymentIntent->status;
|
||||
|
||||
match ($stripeStatus) {
|
||||
'succeeded' => $this->handleSucceeded($order, $paymentIntent, $io),
|
||||
'canceled' => $this->handleCancelled($order, $io),
|
||||
'requires_payment_method' => $this->handleFailed($order, $paymentIntent, $io),
|
||||
default => $io->text(sprintf(
|
||||
' [<fg=blue>PENDING</>] Order #%s — Stripe status: %s',
|
||||
$order->getOrderNumber(),
|
||||
$stripeStatus,
|
||||
)),
|
||||
};
|
||||
|
||||
++$processed;
|
||||
} catch (\Throwable $e) {
|
||||
$io->text(sprintf(
|
||||
' [<fg=red>ERROR</>] Order #%s — %s',
|
||||
$order->getOrderNumber(),
|
||||
$e->getMessage(),
|
||||
));
|
||||
++$errors;
|
||||
}
|
||||
}
|
||||
|
||||
$this->em->flush();
|
||||
|
||||
$io->success(sprintf('Orders: %d checked, %d error(s).', $processed, $errors));
|
||||
|
||||
return $errors > 0;
|
||||
}
|
||||
|
||||
private function handleSucceeded(BilletBuyer $order, \Stripe\PaymentIntent $paymentIntent, SymfonyStyle $io): void
|
||||
{
|
||||
$debtOrganizerId = $paymentIntent->metadata->debt_organizer_id ?? null;
|
||||
if ($debtOrganizerId) {
|
||||
$organizer = $this->em->getRepository(User::class)->find((int) $debtOrganizerId);
|
||||
if ($organizer) {
|
||||
$organizer->reduceDebt($paymentIntent->amount ?? 0);
|
||||
}
|
||||
}
|
||||
|
||||
$this->billetOrderService->generateOrderTickets($order);
|
||||
$this->billetOrderService->generateAndSendTickets($order);
|
||||
$this->billetOrderService->notifyOrganizer($order);
|
||||
|
||||
$io->text(sprintf(' [<fg=green>PAID</>] Order #%s — tickets generated and sent', $order->getOrderNumber()));
|
||||
}
|
||||
|
||||
private function handleCancelled(BilletBuyer $order, SymfonyStyle $io): void
|
||||
{
|
||||
$order->setStatus(BilletBuyer::STATUS_CANCELLED);
|
||||
$this->em->flush();
|
||||
|
||||
$this->audit->log('payment_cancelled_sync', 'BilletBuyer', $order->getId(), [
|
||||
'orderNumber' => $order->getOrderNumber(),
|
||||
]);
|
||||
|
||||
$io->text(sprintf(' [<fg=red>CANCELLED</>] Order #%s', $order->getOrderNumber()));
|
||||
}
|
||||
|
||||
private function handleFailed(BilletBuyer $order, \Stripe\PaymentIntent $paymentIntent, SymfonyStyle $io): void
|
||||
{
|
||||
$errorMessage = $paymentIntent->last_payment_error->message ?? 'Paiement refuse';
|
||||
|
||||
$order->setStatus(BilletBuyer::STATUS_CANCELLED);
|
||||
$this->em->flush();
|
||||
|
||||
$this->audit->log('payment_failed_sync', 'BilletBuyer', $order->getId(), [
|
||||
'orderNumber' => $order->getOrderNumber(),
|
||||
'error' => $errorMessage,
|
||||
]);
|
||||
|
||||
if ($order->getEmail()) {
|
||||
$this->mailerService->sendEmail(
|
||||
$order->getEmail(),
|
||||
'Echec de paiement - '.$order->getEvent()->getTitle(),
|
||||
'Votre paiement pour la commande '.$order->getOrderNumber().' a echoue : '.$errorMessage,
|
||||
);
|
||||
}
|
||||
|
||||
$io->text(sprintf(' [<fg=red>FAILED</>] Order #%s — %s', $order->getOrderNumber(), $errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -164,6 +164,14 @@ class StripeService
|
||||
return $session->url;
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore Requires live Stripe API
|
||||
*/
|
||||
public function retrievePaymentIntent(string $paymentIntentId): \Stripe\PaymentIntent
|
||||
{
|
||||
return $this->stripe->paymentIntents->retrieve($paymentIntentId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @codeCoverageIgnore Simple getter
|
||||
*/
|
||||
|
||||
@@ -3,7 +3,12 @@
|
||||
namespace App\Tests\Command;
|
||||
|
||||
use App\Command\StripeSyncCommand;
|
||||
use App\Entity\BilletBuyer;
|
||||
use App\Entity\Event;
|
||||
use App\Entity\User;
|
||||
use App\Service\AuditService;
|
||||
use App\Service\BilletOrderService;
|
||||
use App\Service\MailerService;
|
||||
use App\Service\StripeService;
|
||||
use Doctrine\ORM\EntityManagerInterface;
|
||||
use Doctrine\ORM\EntityRepository;
|
||||
@@ -13,6 +18,35 @@ use Symfony\Component\Console\Tester\CommandTester;
|
||||
|
||||
class StripeSyncCommandTest extends TestCase
|
||||
{
|
||||
private StripeService $stripeService;
|
||||
private BilletOrderService $billetOrderService;
|
||||
private MailerService $mailerService;
|
||||
private AuditService $audit;
|
||||
private EntityManagerInterface $em;
|
||||
private EntityRepository $userRepo;
|
||||
private EntityRepository $buyerRepo;
|
||||
|
||||
protected function setUp(): void
|
||||
{
|
||||
$this->stripeService = $this->createMock(StripeService::class);
|
||||
$this->billetOrderService = $this->createMock(BilletOrderService::class);
|
||||
$this->mailerService = $this->createMock(MailerService::class);
|
||||
$this->audit = $this->createMock(AuditService::class);
|
||||
|
||||
$this->userRepo = $this->createMock(EntityRepository::class);
|
||||
$this->buyerRepo = $this->createMock(EntityRepository::class);
|
||||
$this->buyerRepo->method('findBy')->willReturn([]);
|
||||
|
||||
$this->em = $this->createMock(EntityManagerInterface::class);
|
||||
$this->em->method('getRepository')->willReturnCallback(function (string $class) {
|
||||
return match ($class) {
|
||||
User::class => $this->userRepo,
|
||||
BilletBuyer::class => $this->buyerRepo,
|
||||
default => $this->createMock(EntityRepository::class),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
private function createOrganizer(string $stripeId, bool $charges = false, bool $payouts = false): User
|
||||
{
|
||||
$user = new User();
|
||||
@@ -29,31 +63,64 @@ class StripeSyncCommandTest extends TestCase
|
||||
return $user;
|
||||
}
|
||||
|
||||
private function createCommandTester(array $users, StripeService $stripeService): CommandTester
|
||||
private function createPendingOrder(?string $paymentIntentId = null): BilletBuyer
|
||||
{
|
||||
$repo = $this->createMock(EntityRepository::class);
|
||||
$repo->method('findAll')->willReturn($users);
|
||||
$event = $this->createMock(Event::class);
|
||||
$event->method('getTitle')->willReturn('Test Event');
|
||||
$event->method('getAccount')->willReturn($this->createOrganizer('acct_org'));
|
||||
|
||||
$em = $this->createMock(EntityManagerInterface::class);
|
||||
$em->method('getRepository')->willReturn($repo);
|
||||
$order = new BilletBuyer();
|
||||
$order->setStatus(BilletBuyer::STATUS_PENDING);
|
||||
$order->setStripeSessionId($paymentIntentId);
|
||||
$order->setEmail('buyer@test.fr');
|
||||
$order->setEvent($event);
|
||||
|
||||
return $order;
|
||||
}
|
||||
|
||||
private function createCommandTester(): CommandTester
|
||||
{
|
||||
$command = new StripeSyncCommand(
|
||||
$this->em,
|
||||
$this->stripeService,
|
||||
$this->billetOrderService,
|
||||
$this->mailerService,
|
||||
$this->audit,
|
||||
);
|
||||
|
||||
$command = new StripeSyncCommand($em, $stripeService);
|
||||
$app = new Application();
|
||||
$app->addCommand($command);
|
||||
|
||||
return new CommandTester($app->find('app:stripe:sync'));
|
||||
}
|
||||
|
||||
private function setBuyerRepo(array $orders): void
|
||||
{
|
||||
$this->buyerRepo = $this->createMock(EntityRepository::class);
|
||||
$this->buyerRepo->method('findBy')->willReturn($orders);
|
||||
|
||||
$this->em = $this->createMock(EntityManagerInterface::class);
|
||||
$this->em->method('getRepository')->willReturnCallback(function (string $class) {
|
||||
return match ($class) {
|
||||
User::class => $this->userRepo,
|
||||
BilletBuyer::class => $this->buyerRepo,
|
||||
default => $this->createMock(EntityRepository::class),
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
// --- Account sync tests ---
|
||||
|
||||
public function testSyncUpdatesStripeStatus(): void
|
||||
{
|
||||
$user = $this->createOrganizer('acct_123');
|
||||
$this->userRepo->method('findAll')->willReturn([$user]);
|
||||
|
||||
$stripeService = $this->createMock(StripeService::class);
|
||||
$stripeService->method('retrieveAccountStatus')
|
||||
$this->stripeService->method('retrieveAccountStatus')
|
||||
->with('acct_123')
|
||||
->willReturn(['charges_enabled' => true, 'payouts_enabled' => true]);
|
||||
|
||||
$tester = $this->createCommandTester([$user], $stripeService);
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertTrue($user->isStripeChargesEnabled());
|
||||
@@ -65,12 +132,12 @@ class StripeSyncCommandTest extends TestCase
|
||||
public function testSyncDetectsChanges(): void
|
||||
{
|
||||
$user = $this->createOrganizer('acct_456', true, true);
|
||||
$this->userRepo->method('findAll')->willReturn([$user]);
|
||||
|
||||
$stripeService = $this->createMock(StripeService::class);
|
||||
$stripeService->method('retrieveAccountStatus')
|
||||
$this->stripeService->method('retrieveAccountStatus')
|
||||
->willReturn(['charges_enabled' => true, 'payouts_enabled' => false]);
|
||||
|
||||
$tester = $this->createCommandTester([$user], $stripeService);
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertTrue($user->isStripeChargesEnabled());
|
||||
@@ -80,9 +147,9 @@ class StripeSyncCommandTest extends TestCase
|
||||
|
||||
public function testSyncWithNoOrganizers(): void
|
||||
{
|
||||
$stripeService = $this->createMock(StripeService::class);
|
||||
$this->userRepo->method('findAll')->willReturn([]);
|
||||
|
||||
$tester = $this->createCommandTester([], $stripeService);
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertStringContainsString('No organizers', $tester->getDisplay());
|
||||
@@ -92,12 +159,12 @@ class StripeSyncCommandTest extends TestCase
|
||||
public function testSyncHandlesStripeError(): void
|
||||
{
|
||||
$user = $this->createOrganizer('acct_bad');
|
||||
$this->userRepo->method('findAll')->willReturn([$user]);
|
||||
|
||||
$stripeService = $this->createMock(StripeService::class);
|
||||
$stripeService->method('retrieveAccountStatus')
|
||||
$this->stripeService->method('retrieveAccountStatus')
|
||||
->willThrowException(new \RuntimeException('Account not found'));
|
||||
|
||||
$tester = $this->createCommandTester([$user], $stripeService);
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertStringContainsString('1 error', $tester->getDisplay());
|
||||
@@ -115,11 +182,12 @@ class StripeSyncCommandTest extends TestCase
|
||||
$userWithoutStripe->setPassword('hashed');
|
||||
$userWithoutStripe->setRoles(['ROLE_ORGANIZER']);
|
||||
|
||||
$stripeService = $this->createMock(StripeService::class);
|
||||
$stripeService->method('retrieveAccountStatus')
|
||||
$this->userRepo->method('findAll')->willReturn([$userWithStripe, $userWithoutStripe]);
|
||||
|
||||
$this->stripeService->method('retrieveAccountStatus')
|
||||
->willReturn(['charges_enabled' => true, 'payouts_enabled' => false]);
|
||||
|
||||
$tester = $this->createCommandTester([$userWithStripe, $userWithoutStripe], $stripeService);
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertStringContainsString('Syncing 1 organizer', $tester->getDisplay());
|
||||
@@ -127,4 +195,190 @@ class StripeSyncCommandTest extends TestCase
|
||||
self::assertTrue($userWithStripe->isStripeChargesEnabled());
|
||||
self::assertFalse($userWithStripe->isStripePayoutsEnabled());
|
||||
}
|
||||
|
||||
// --- Pending orders sync tests ---
|
||||
|
||||
public function testNoPendingOrders(): void
|
||||
{
|
||||
$this->userRepo->method('findAll')->willReturn([]);
|
||||
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertStringContainsString('No pending orders', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testPendingOrderSucceeded(): void
|
||||
{
|
||||
$this->userRepo->method('findAll')->willReturn([]);
|
||||
|
||||
$order = $this->createPendingOrder('pi_succeeded');
|
||||
$this->setBuyerRepo([$order]);
|
||||
|
||||
$paymentIntent = new \stdClass();
|
||||
$paymentIntent->status = 'succeeded';
|
||||
$paymentIntent->amount = 5000;
|
||||
$paymentIntent->metadata = (object) [];
|
||||
|
||||
$this->stripeService->method('retrievePaymentIntent')
|
||||
->with('pi_succeeded')
|
||||
->willReturn($paymentIntent);
|
||||
|
||||
$this->billetOrderService->expects(self::once())->method('generateOrderTickets')->with($order);
|
||||
$this->billetOrderService->expects(self::once())->method('generateAndSendTickets')->with($order);
|
||||
$this->billetOrderService->expects(self::once())->method('notifyOrganizer')->with($order);
|
||||
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertStringContainsString('PAID', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testPendingOrderSucceededWithDebtOrganizer(): void
|
||||
{
|
||||
$organizer = $this->createOrganizer('acct_debt');
|
||||
$this->userRepo->method('findAll')->willReturn([]);
|
||||
$this->userRepo->method('find')->with(42)->willReturn($organizer);
|
||||
|
||||
$order = $this->createPendingOrder('pi_debt');
|
||||
$this->setBuyerRepo([$order]);
|
||||
|
||||
$paymentIntent = new \stdClass();
|
||||
$paymentIntent->status = 'succeeded';
|
||||
$paymentIntent->amount = 3000;
|
||||
$paymentIntent->metadata = (object) ['debt_organizer_id' => '42'];
|
||||
|
||||
$this->stripeService->method('retrievePaymentIntent')->willReturn($paymentIntent);
|
||||
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertStringContainsString('PAID', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testPendingOrderCanceled(): void
|
||||
{
|
||||
$this->userRepo->method('findAll')->willReturn([]);
|
||||
|
||||
$order = $this->createPendingOrder('pi_canceled');
|
||||
$this->setBuyerRepo([$order]);
|
||||
|
||||
$paymentIntent = new \stdClass();
|
||||
$paymentIntent->status = 'canceled';
|
||||
|
||||
$this->stripeService->method('retrievePaymentIntent')->willReturn($paymentIntent);
|
||||
|
||||
$this->audit->expects(self::once())->method('log')
|
||||
->with('payment_cancelled_sync', 'BilletBuyer', self::anything(), self::anything());
|
||||
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertSame(BilletBuyer::STATUS_CANCELLED, $order->getStatus());
|
||||
self::assertStringContainsString('CANCELLED', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testPendingOrderFailed(): void
|
||||
{
|
||||
$this->userRepo->method('findAll')->willReturn([]);
|
||||
|
||||
$order = $this->createPendingOrder('pi_failed');
|
||||
$this->setBuyerRepo([$order]);
|
||||
|
||||
$paymentIntent = new \stdClass();
|
||||
$paymentIntent->status = 'requires_payment_method';
|
||||
$paymentIntent->last_payment_error = (object) ['message' => 'Card declined'];
|
||||
|
||||
$this->stripeService->method('retrievePaymentIntent')->willReturn($paymentIntent);
|
||||
|
||||
$this->audit->expects(self::once())->method('log')
|
||||
->with('payment_failed_sync', 'BilletBuyer', self::anything(), self::anything());
|
||||
$this->mailerService->expects(self::once())->method('sendEmail');
|
||||
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertSame(BilletBuyer::STATUS_CANCELLED, $order->getStatus());
|
||||
self::assertStringContainsString('FAILED', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testPendingOrderSkippedWithoutPaymentIntentId(): void
|
||||
{
|
||||
$this->userRepo->method('findAll')->willReturn([]);
|
||||
|
||||
$order = $this->createPendingOrder(null);
|
||||
$this->setBuyerRepo([$order]);
|
||||
|
||||
$this->stripeService->expects(self::never())->method('retrievePaymentIntent');
|
||||
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertStringContainsString('SKIP', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testPendingOrderStillPending(): void
|
||||
{
|
||||
$this->userRepo->method('findAll')->willReturn([]);
|
||||
|
||||
$order = $this->createPendingOrder('pi_processing');
|
||||
$this->setBuyerRepo([$order]);
|
||||
|
||||
$paymentIntent = new \stdClass();
|
||||
$paymentIntent->status = 'processing';
|
||||
|
||||
$this->stripeService->method('retrievePaymentIntent')->willReturn($paymentIntent);
|
||||
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertSame(BilletBuyer::STATUS_PENDING, $order->getStatus());
|
||||
self::assertStringContainsString('PENDING', $tester->getDisplay());
|
||||
}
|
||||
|
||||
public function testPendingOrderStripeApiError(): void
|
||||
{
|
||||
$this->userRepo->method('findAll')->willReturn([]);
|
||||
|
||||
$order = $this->createPendingOrder('pi_error');
|
||||
$this->setBuyerRepo([$order]);
|
||||
|
||||
$this->stripeService->method('retrievePaymentIntent')
|
||||
->willThrowException(new \RuntimeException('Stripe API error'));
|
||||
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertStringContainsString('ERROR', $tester->getDisplay());
|
||||
self::assertSame(1, $tester->getStatusCode());
|
||||
}
|
||||
|
||||
public function testPendingOrderFailedWithoutEmail(): void
|
||||
{
|
||||
$this->userRepo->method('findAll')->willReturn([]);
|
||||
|
||||
$event = $this->createMock(Event::class);
|
||||
$event->method('getTitle')->willReturn('Test Event');
|
||||
|
||||
$order = new BilletBuyer();
|
||||
$order->setStatus(BilletBuyer::STATUS_PENDING);
|
||||
$order->setStripeSessionId('pi_no_email');
|
||||
$order->setEvent($event);
|
||||
|
||||
$this->setBuyerRepo([$order]);
|
||||
|
||||
$paymentIntent = new \stdClass();
|
||||
$paymentIntent->status = 'requires_payment_method';
|
||||
$paymentIntent->last_payment_error = null;
|
||||
|
||||
$this->stripeService->method('retrievePaymentIntent')->willReturn($paymentIntent);
|
||||
|
||||
$this->mailerService->expects(self::never())->method('sendEmail');
|
||||
|
||||
$tester = $this->createCommandTester();
|
||||
$tester->execute([]);
|
||||
|
||||
self::assertSame(BilletBuyer::STATUS_CANCELLED, $order->getStatus());
|
||||
self::assertStringContainsString('FAILED', $tester->getDisplay());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user