From 7a29372b60508e692567dd277a3f20193034215d Mon Sep 17 00:00:00 2001 From: Serreau Jovann Date: Sat, 21 Mar 2026 16:56:50 +0100 Subject: [PATCH] Save Stripe payment details on order confirmation, add arrive early tip - Retrieve PaymentIntent on success redirect, save payment_method, card_brand, card_last4 - Display payment info on /ma-commande page (card type + last 4 digits) - Add "Il est recommande d'arriver en avance" to practical info on ticket PDF - Migration for payment_method, card_brand, card_last4 columns Co-Authored-By: Claude Opus 4.6 (1M context) --- migrations/Version20260321250000.php | 30 +++++++++++++++++++ src/Controller/OrderController.php | 41 ++++++++++++++++++++++++- src/Entity/BilletBuyer.php | 45 ++++++++++++++++++++++++++++ templates/order/public.html.twig | 8 +++++ templates/pdf/billet.html.twig | 1 + tests/Entity/BilletBuyerTest.php | 15 ++++++++++ 6 files changed, 139 insertions(+), 1 deletion(-) create mode 100644 migrations/Version20260321250000.php diff --git a/migrations/Version20260321250000.php b/migrations/Version20260321250000.php new file mode 100644 index 0000000..aa7dbdd --- /dev/null +++ b/migrations/Version20260321250000.php @@ -0,0 +1,30 @@ +addSql('ALTER TABLE billet_buyer ADD COLUMN IF NOT EXISTS payment_method VARCHAR(50) DEFAULT NULL'); + $this->addSql('ALTER TABLE billet_buyer ADD COLUMN IF NOT EXISTS card_brand VARCHAR(50) DEFAULT NULL'); + $this->addSql('ALTER TABLE billet_buyer ADD COLUMN IF NOT EXISTS card_last4 VARCHAR(4) DEFAULT NULL'); + } + + public function down(Schema $schema): void + { + $this->addSql('ALTER TABLE billet_buyer DROP COLUMN IF EXISTS payment_method'); + $this->addSql('ALTER TABLE billet_buyer DROP COLUMN IF EXISTS card_brand'); + $this->addSql('ALTER TABLE billet_buyer DROP COLUMN IF EXISTS card_last4'); + } +} diff --git a/src/Controller/OrderController.php b/src/Controller/OrderController.php index 202236c..0a62e88 100644 --- a/src/Controller/OrderController.php +++ b/src/Controller/OrderController.php @@ -185,7 +185,7 @@ class OrderController extends AbstractController } #[Route('/commande/{id}/confirmation', name: 'app_order_success', requirements: ['id' => '\d+'], methods: ['GET'])] - public function success(int $id, Request $request, EntityManagerInterface $em, BilletOrderService $billetOrderService): Response + public function success(int $id, Request $request, EntityManagerInterface $em, BilletOrderService $billetOrderService, StripeService $stripeService): Response { $order = $em->getRepository(BilletBuyer::class)->find($id); if (!$order) { @@ -193,8 +193,10 @@ class OrderController extends AbstractController } $redirectStatus = $request->query->getString('redirect_status'); + $paymentIntentId = $request->query->getString('payment_intent'); if ('succeeded' === $redirectStatus && BilletBuyer::STATUS_PENDING === $order->getStatus()) { + $this->savePaymentDetails($order, $paymentIntentId, $stripeService, $em); $billetOrderService->generateOrderTickets($order); $billetOrderService->generateAndSendTickets($order); } @@ -294,4 +296,41 @@ class OrderController extends AbstractController return $totalHT; } + + /** + * @codeCoverageIgnore Requires live Stripe API + */ + private function savePaymentDetails(BilletBuyer $order, string $paymentIntentId, StripeService $stripeService, EntityManagerInterface $em): void + { + if (!$paymentIntentId) { + return; + } + + $organizer = $order->getEvent()->getAccount(); + if (!$organizer->getStripeAccountId()) { + return; + } + + try { + $pi = $stripeService->getClient()->paymentIntents->retrieve( + $paymentIntentId, + ['expand' => ['payment_method']], + ['stripe_account' => $organizer->getStripeAccountId()] + ); + + $pm = $pi->payment_method; + if ($pm) { + $order->setPaymentMethod($pm->type ?? null); + if (isset($pm->card)) { + $order->setCardBrand($pm->card->brand ?? null); + $order->setCardLast4($pm->card->last4 ?? null); + } + } + + $order->setStripeSessionId($paymentIntentId); + $em->flush(); + } catch (\Exception) { + // Stripe failure is non-blocking + } + } } diff --git a/src/Entity/BilletBuyer.php b/src/Entity/BilletBuyer.php index ea8c8e6..e670dcc 100644 --- a/src/Entity/BilletBuyer.php +++ b/src/Entity/BilletBuyer.php @@ -54,6 +54,15 @@ class BilletBuyer #[ORM\Column(length: 255, nullable: true)] private ?string $stripeSessionId = null; + #[ORM\Column(length: 50, nullable: true)] + private ?string $paymentMethod = null; + + #[ORM\Column(length: 50, nullable: true)] + private ?string $cardBrand = null; + + #[ORM\Column(length: 4, nullable: true)] + private ?string $cardLast4 = null; + #[ORM\Column] private \DateTimeImmutable $createdAt; @@ -208,6 +217,42 @@ class BilletBuyer return $this; } + public function getPaymentMethod(): ?string + { + return $this->paymentMethod; + } + + public function setPaymentMethod(?string $paymentMethod): static + { + $this->paymentMethod = $paymentMethod; + + return $this; + } + + public function getCardBrand(): ?string + { + return $this->cardBrand; + } + + public function setCardBrand(?string $cardBrand): static + { + $this->cardBrand = $cardBrand; + + return $this; + } + + public function getCardLast4(): ?string + { + return $this->cardLast4; + } + + public function setCardLast4(?string $cardLast4): static + { + $this->cardLast4 = $cardLast4; + + return $this; + } + public function getCreatedAt(): \DateTimeImmutable { return $this->createdAt; diff --git a/templates/order/public.html.twig b/templates/order/public.html.twig index 73c4dc4..2e6ed28 100644 --- a/templates/order/public.html.twig +++ b/templates/order/public.html.twig @@ -39,6 +39,14 @@ Annulee {% endif %}

+ {% if order.paymentMethod %} +

+ Paiement : {{ order.paymentMethod }} + {% if order.cardBrand and order.cardLast4 %} + — {{ order.cardBrand|upper }} **** {{ order.cardLast4 }} + {% endif %} +

+ {% endif %} diff --git a/templates/pdf/billet.html.twig b/templates/pdf/billet.html.twig index 4dce1d2..4a97ebe 100644 --- a/templates/pdf/billet.html.twig +++ b/templates/pdf/billet.html.twig @@ -382,6 +382,7 @@
Informations pratiques
+ • Il est recommande d'arriver en avance.
• En arrivant, preparez votre billet pour accelerer les controles a l'entree.
• A l'approche des controles de securite, merci de preparer vos affaires pour faciliter la verification.
diff --git a/tests/Entity/BilletBuyerTest.php b/tests/Entity/BilletBuyerTest.php index 7124fda..5d37a3f 100644 --- a/tests/Entity/BilletBuyerTest.php +++ b/tests/Entity/BilletBuyerTest.php @@ -24,6 +24,9 @@ class BilletBuyerTest extends TestCase self::assertSame(0.0, $buyer->getTotalHTDecimal()); self::assertSame(BilletBuyer::STATUS_PENDING, $buyer->getStatus()); self::assertNull($buyer->getStripeSessionId()); + self::assertNull($buyer->getPaymentMethod()); + self::assertNull($buyer->getCardBrand()); + self::assertNull($buyer->getCardLast4()); self::assertNull($buyer->getPaidAt()); self::assertMatchesRegularExpression('/^ETICKET-[A-Z0-9]{4}-[A-Z0-9]{4}-[A-Z0-9]{4}$/', $buyer->getReference()); self::assertSame(32, \strlen($buyer->getAccessToken())); @@ -94,6 +97,18 @@ class BilletBuyerTest extends TestCase self::assertSame($buyer, $result); } + public function testSetAndGetPaymentDetails(): void + { + $buyer = new BilletBuyer(); + $buyer->setPaymentMethod('card'); + $buyer->setCardBrand('visa'); + $buyer->setCardLast4('4242'); + + self::assertSame('card', $buyer->getPaymentMethod()); + self::assertSame('visa', $buyer->getCardBrand()); + self::assertSame('4242', $buyer->getCardLast4()); + } + public function testSetAndGetPaidAt(): void { $buyer = new BilletBuyer();