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();
|