PhysicalCardApiController
extends AbstractController
in package
Points d'entrée REST du parcours de commande des cartes de visite physiques.
Le contrôleur expose le catalogue (produits Card non masqués, modèles CardModel actifs),
la vérification de l'éligibilité à la carte offerte pour les abonnés Pro, la création d'une
commande à partir d'une carte virtuelle existante, la consultation des cartes physiques de
l'utilisateur connecté, la mise à jour administrative du statut, ainsi que le paiement Stripe
(création puis confirmation d'un PaymentIntent).
Toutes les routes sont préfixées par /api/physical-cards, nommées api_physical_cards_*,
réservées aux utilisateurs pleinement authentifiés (IS_AUTHENTICATED_FULLY, JWT) et
regroupées sous le tag OpenAPI « Physical Cards ». Sauf mention contraire, les lectures et
écritures sont systématiquement filtrées sur l'utilisateur courant.
Attributes
- #[IsGranted]
- 'IS_AUTHENTICATED_FULLY'
- #[Route]
- '/api/physical-cards'
- $name: 'api_physical_cards_'
- #[Tag]
- $name: 'Physical Cards'
- $description: 'Gestion des cartes physiques'
Table of Contents
Properties
- $businessCardRepository : BusinessCardAizeRepository
- $cardModelRepository : CardModelRepository
- $cardRepository : CardRepository
- $defaultProCardService : DefaultProCardService
- $entityManager : EntityManagerInterface
- $mailerService : MailerService
- $physicalCardRepository : PhysicalCardRepository
- $stripeService : StripeService
- $subscriptionService : SubscriptionService
- $uploaderService : UploaderService
Methods
- __construct() : mixed
- checkFreeCardEligibility() : JsonResponse
- Indique si l'utilisateur connecté peut encore bénéficier de la carte physique offerte.
- confirmPayment() : JsonResponse
- Confirme le paiement après succès côté client Flutter
- createOrder() : JsonResponse
- Crée une carte physique et la commande associée à partir d'une carte virtuelle de l'utilisateur.
- createPaymentIntent() : JsonResponse
- Crée un PaymentIntent Stripe pour Flutter (paiement côté client)
- getModels() : JsonResponse
- Renvoie les modèles graphiques de carte actuellement activés.
- getProducts() : JsonResponse
- Renvoie le catalogue des produits de carte physique commercialisables.
- getUserOrders() : JsonResponse
- Liste les cartes physiques commandées par l'utilisateur connecté.
- show() : JsonResponse
- Renvoie le détail d'une carte physique appartenant à l'utilisateur connecté.
- updateStatus() : JsonResponse
- Modifie le statut de suivi d'une carte physique, réservé aux administrateurs.
- createOrderForPhysicalCard() : Order
- Crée une commande pour une carte physique
- isValidHexColor() : bool
- Valide qu'une couleur est au format hexadécimal valide
- parseMoney() : float
- Convertit un montant textuel en nombre à virgule flottante exploitable pour un calcul.
- validateImageFile() : void
- Valide un fichier image uploadé
- validateLogoFile() : void
- Valide un fichier logo uploadé
Properties
$businessCardRepository read-only
private
BusinessCardAizeRepository
$businessCardRepository
$cardModelRepository read-only
private
CardModelRepository
$cardModelRepository
$cardRepository read-only
private
CardRepository
$cardRepository
$defaultProCardService read-only
private
DefaultProCardService
$defaultProCardService
$entityManager read-only
private
EntityManagerInterface
$entityManager
$mailerService read-only
private
MailerService
$mailerService
$physicalCardRepository read-only
private
PhysicalCardRepository
$physicalCardRepository
$stripeService read-only
private
StripeService
$stripeService
$subscriptionService read-only
private
SubscriptionService
$subscriptionService
$uploaderService read-only
private
UploaderService
$uploaderService
Methods
__construct()
public
__construct(EntityManagerInterface $entityManager, PhysicalCardRepository $physicalCardRepository, CardRepository $cardRepository, CardModelRepository $cardModelRepository, BusinessCardAizeRepository $businessCardRepository, SubscriptionService $subscriptionService, DefaultProCardService $defaultProCardService, UploaderService $uploaderService, StripeService $stripeService, MailerService $mailerService) : mixed
Parameters
- $entityManager : EntityManagerInterface
-
Gestionnaire Doctrine : accès direct aux dépôts
Card,CardModel,CardColorOptionetCardPrintOption, persistance et flush. - $physicalCardRepository : PhysicalCardRepository
-
Dépôt des cartes physiques : comptage par utilisateur, listage et recherche par identifiant.
- $cardRepository : CardRepository
-
Dépôt des produits de carte, utilisé pour résoudre le
cardProductIddemandé. - $cardModelRepository : CardModelRepository
-
Dépôt des modèles de carte, utilisé pour résoudre le
cardModelIdoptionnel. - $businessCardRepository : BusinessCardAizeRepository
-
Dépôt des cartes virtuelles, utilisé pour vérifier la propriété de la carte source.
- $subscriptionService : SubscriptionService
-
Détermine l'accès aux fonctionnalités premium (abonnement Pro).
- $defaultProCardService : DefaultProCardService
-
Fournit la carte Pro offerte configurée et l'état « première commande ».
- $uploaderService : UploaderService
-
Enregistre sur disque les images recto/verso et le logo transmis.
- $stripeService : StripeService
-
Crée et récupère les PaymentIntent Stripe.
- $mailerService : MailerService
-
Envoie l'e-mail de confirmation de commande de carte physique.
checkFreeCardEligibility()
Indique si l'utilisateur connecté peut encore bénéficier de la carte physique offerte.
public
checkFreeCardEligibility() : JsonResponse
Trois informations sont combinées : l'accès aux fonctionnalités premium
(SubscriptionService::hasAccessToPremiumFeatures()), le fait qu'aucune carte physique ne
soit déjà rattachée à l'utilisateur (DefaultProCardService::isFirstPhysicalCardOrder())
et l'existence d'une carte Pro par défaut configurée globalement. Le champ eligible vaut
true uniquement si l'abonnement premium est actif ET s'il s'agit de la première commande ;
il ne tient donc pas compte de la présence effective de la carte offerte, contrairement à
shouldUseDefaultProduct qui exige en plus qu'une carte par défaut soit paramétrée.
Le champ reason est un libellé explicatif hiérarchisé : « Abonnement Pro requis » si
l'abonnement manque, sinon « Première commande déjà utilisée » si une carte physique
existe déjà, sinon « Première commande Pro ». defaultProduct décrit la carte offerte
(identifiant, nom, description, prix en chaîne, isActive = négation de isHide()) ou vaut
null si aucune n'est configurée. Méthode de lecture seule, sans effet de bord.
Route : /api/physical-cards/eligibility/free-card (GET), nom
api_physical_cards_eligibility_free_card. Authentification pleine requise.
Réponses : 200 dans tous les cas, y compris lorsque l'utilisateur n'est pas éligible ; 401 si le jeton est absent ou invalide.
Attributes
- #[Get]
- $path: '/api/physical-cards/eligibility/free-card'
- $description: 'Vérifie si l\'utilisateur est éligible à une carte physique gratuite'
- $summary: 'Vérifier l\'éligibilité à une carte gratuite'
- $tags: ['Physical Cards']
- $responses: [new OA\Response(response: 200, description: 'Statut d\'éligibilité', content: new OA\JsonContent(properties: [new OA\Property(property: 'eligible', type: 'boolean', example: true), new OA\Property(property: 'reason', type: 'string', example: 'Première commande Pro'), new OA\Property(property: 'hasProSubscription', type: 'boolean', example: true), new OA\Property(property: 'shouldUseDefaultProduct', type: 'boolean', example: true), new OA\Property(property: 'defaultProduct', properties: [new OA\Property(property: 'id', type: 'integer', example: 1), new OA\Property(property: 'name', type: 'string', example: 'Carte Pro Offerte'), new OA\Property(property: 'description', type: 'string', example: 'Produit offert par défaut'), new OA\Property(property: 'price', type: 'string', example: '29999'), new OA\Property(property: 'isActive', type: 'boolean', example: true)], type: 'object', nullable: true)])), new OA\Response(response: 401, description: 'Non authentifié')]
- #[Route]
- '/eligibility/free-card'
- $name: 'eligibility_free_card'
- $methods: ['GET']
Return values
JsonResponse —Objet JSON {eligible, reason, hasProSubscription, shouldUseDefaultProduct, defaultProduct}.
confirmPayment()
Confirme le paiement après succès côté client Flutter
public
confirmPayment(int $id, Request $request) : JsonResponse
Parameters
- $id : int
- $request : Request
Attributes
- #[Post]
- $path: '/api/physical-cards/{id}/confirm-payment'
- $description: 'Confirme le paiement après succès côté client Flutter'
- $summary: 'Confirmer le paiement Flutter'
- $requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(properties: [new OA\Property(property: 'payment_intent_id', description: 'ID du PaymentIntent Stripe', type: 'string', example: 'pi_1234567890'), new OA\Property(property: 'payment_method_id', description: 'ID de la méthode de paiement (optionnel)', type: 'string', example: 'pm_1234567890')]))
- $tags: ['Physical Cards']
- $parameters: [new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))]
- $responses: [new OA\Response(response: 200, description: 'Paiement confirmé avec succès', content: new OA\JsonContent(properties: [new OA\Property(property: 'status', type: 'string', example: 'confirmed'), new OA\Property(property: 'message', type: 'string', example: 'Paiement confirmé avec succès'), new OA\Property(property: 'payment_status', type: 'string', example: 'succeeded'), new OA\Property(property: 'card_status', type: 'string', example: 'confirmed'), new OA\Property(property: 'order_id', type: 'integer', example: 123)])), new OA\Response(response: 400, description: 'PaymentIntent invalide ou paiement échoué'), new OA\Response(response: 401, description: 'Non authentifié'), new OA\Response(response: 404, description: 'Carte physique non trouvée')]
- #[Route]
- '/{id}/confirm-payment'
- $name: 'confirm_payment'
- $methods: ['POST']
Return values
JsonResponsecreateOrder()
Crée une carte physique et la commande associée à partir d'une carte virtuelle de l'utilisateur.
public
createOrder(Request $request) : JsonResponse
Le corps est lu en multipart/form-data (champs texte via $request->request, fichiers via
$request->files), et non en JSON. Le déroulement est le suivant :
détermination de l'éligibilité à la gratuité ($eligibleForFreeBase = aucune carte physique
déjà comptée pour l'utilisateur ET accès premium ET carte Pro par défaut configurée) ;
validation des champs obligatoires et des longueurs (adresse ≤ 500, frontText et
backText ≤ 500, designerInstructions ≤ 1000) ; vérification que la carte virtuelle
appartient bien à l'utilisateur ; résolution du produit — la carte Pro par défaut est
imposée par le serveur lorsque cardProductId est omis et que l'utilisateur est éligible,
ce que signale le drapeau serverEnforcedProduct de la réponse. Les options couleur et
impression fournies doivent être actives et rattachées au produit choisi ; à défaut de
colorOptionId, la première option couleur active est retenue, et à défaut de
printOptionId, la première option d'impression active marquée par défaut, sinon la
première option d'impression active. Les fichiers envoyés sont validés puis stockés via
UploaderService ; toute erreur d'upload est accumulée et interrompt la création.
Le prix est la somme du prix du produit (remplacé par 0 si la base est offerte, c'est-à-dire
si l'utilisateur est éligible et que le produit retenu est bien la carte Pro par défaut) et
des suppléments couleur et impression, chaque valeur passant par parseMoney(). Si ce total
est nul et que la base est offerte, la carte passe en STATUS_CONFIRMED avec un Payment
de 0,00 en méthode PRO_SUBSCRIPTION et une commande en Order::EN_TRAITEMENT, puis un
e-mail de confirmation est tenté (les échecs d'envoi sont seulement journalisés). Sinon la
carte passe en STATUS_PENDING_PAYMENT avec une commande en Order::DRAFT, en attente du
paiement Stripe. Dans les deux cas la carte est initialement créée en STATUS_PENDING.
Route : /api/physical-cards/create-order (POST), nom api_physical_cards_create_order.
Authentification requise (IS_AUTHENTICATED en plus de la contrainte de classe).
Corps attendu (multipart) : {"virtualCardId": "int — carte virtuelle source, obligatoire", "shippingAddress": "string — adresse de livraison, obligatoire, 500 caractères maximum", "cardProductId": "int — produit commandé, obligatoire sauf si la carte offerte s'applique", "cardModelId": "int — modèle graphique actif, optionnel", "designerInstructions": "string — consignes au graphiste, 1000 caractères maximum", "frontText": "string — texte recto, 500 caractères maximum", "backText": "string — texte verso, 500 caractères maximum", "colorOptionId": "int — option couleur active du produit, optionnel", "printOptionId": "int — option d'impression active du produit, optionnel", "fontFamily": "string — police retenue, optionnel", "customFrontImage": "fichier — visuel recto personnalisé, optionnel", "customBackImage": "fichier — visuel verso personnalisé, optionnel", "logoFile": "fichier — logo, optionnel"}
Réponses : 201 lorsque la carte et la commande sont créées, que ce soit en carte offerte ou
en attente de paiement ; 400 si virtualCardId ou shippingAddress manque, si
cardProductId manque hors cas de gratuité, si une longueur maximale est dépassée, si
l'option couleur ou d'impression est inconnue, inactive ou étrangère au produit, ou si un
upload échoue ; 404 si la carte virtuelle est introuvable ou n'appartient pas à
l'utilisateur, si le produit est introuvable ou masqué, ou si le modèle est introuvable ou
inactif ; 500 si une exception survient pendant le traitement ; 401 si le jeton est absent
ou invalide.
Parameters
- $request : Request
-
Requête HTTP portant les champs multipart et les fichiers téléversés.
Attributes
- #[IsGranted]
- 'IS_AUTHENTICATED'
- #[Post]
- $path: '/api/physical-cards/create-order'
- $description: 'Créer une nouvelle commande de carte physique'
- $summary: 'Créer une commande de carte physique'
- $security: [['bearerAuth' => []]]
- $requestBody: new OA\RequestBody(description: 'Données de la commande', required: true, content: new OA\MediaType(mediaType: 'multipart/form-data', schema: new OA\Schema(required: ['virtualCardId', 'shippingAddress'], properties: ['virtualCardId' => new OA\Property(property: 'virtualCardId', description: 'ID de la carte virtuelle', type: 'integer'), 'cardProductId' => new OA\Property(property: 'cardProductId', description: 'ID du produit de carte', type: 'integer'), 'cardModelId' => new OA\Property(property: 'cardModelId', description: 'ID du modèle de carte (optionnel)', type: 'integer'), 'shippingAddress' => new OA\Property(property: 'shippingAddress', description: 'Adresse de livraison', type: 'string'), 'designerInstructions' => new OA\Property(property: 'designerInstructions', description: 'Instructions pour le designer', type: 'string'), 'frontText' => new OA\Property(property: 'frontText', description: 'Texte pour la face avant', type: 'string'), 'backText' => new OA\Property(property: 'backText', description: 'Texte pour la face arrière', type: 'string'), 'colorOptionId' => new OA\Property(property: 'colorOptionId', description: 'ID de l\'option couleur', type: 'integer'), 'printOptionId' => new OA\Property(property: 'printOptionId', description: 'ID de l\'option d\'impression', type: 'integer'), 'fontFamily' => new OA\Property(property: 'fontFamily', description: 'Police de caractères', type: 'string'), 'customFrontImage' => new OA\Property(property: 'customFrontImage', description: 'Image personnalisée recto', type: 'string', format: 'binary'), 'customBackImage' => new OA\Property(property: 'customBackImage', description: 'Image personnalisée verso', type: 'string', format: 'binary'), 'logoFile' => new OA\Property(property: 'logoFile', description: 'Fichier logo', type: 'string', format: 'binary')])))
- $tags: ['Physical Cards']
- $responses: [new OA\Response(response: 201, description: 'Commande créée avec succès', content: new OA\JsonContent(properties: ['serverEnforcedProduct' => new OA\Property(property: 'serverEnforcedProduct', type: 'boolean'), 'message' => new OA\Property(property: 'message', type: 'string'), 'physicalCard' => new OA\Property(property: 'physicalCard', properties: ['id' => new OA\Property(property: 'id', type: 'integer'), 'status' => new OA\Property(property: 'status', type: 'string'), 'price' => new OA\Property(property: 'price', type: 'string'), 'isFree' => new OA\Property(property: 'isFree', type: 'boolean'), 'orderId' => new OA\Property(property: 'orderId', type: 'integer'), 'usedProduct' => new OA\Property(property: 'usedProduct', properties: ['id' => new OA\Property(property: 'id', type: 'integer'), 'name' => new OA\Property(property: 'name', type: 'string'), 'description' => new OA\Property(property: 'description', type: 'string'), 'price' => new OA\Property(property: 'price', type: 'string'), 'isDefault' => new OA\Property(property: 'isDefault', type: 'boolean')], type: 'object')], type: 'object')])), new OA\Response(response: 400, description: 'Données invalides'), new OA\Response(response: 403, description: 'Accès refusé ou offre PRO requise'), new OA\Response(response: 404, description: 'Ressource non trouvée')]
- #[Route]
- '/create-order'
- $name: 'create_order'
- $methods: ['POST']
Return values
JsonResponse —Objet JSON {serverEnforcedProduct, message, physicalCard} où
physicalCard contient l'identifiant, le statut, le prix, isFree,
l'identifiant de commande et le produit effectivement utilisé ; ou un
objet {error, details?} en cas d'échec.
createPaymentIntent()
Crée un PaymentIntent Stripe pour Flutter (paiement côté client)
public
createPaymentIntent(int $id, Request $request) : JsonResponse
Parameters
- $id : int
- $request : Request
Attributes
- #[Post]
- $path: '/api/physical-cards/{id}/create-payment-intent'
- $description: 'Crée un PaymentIntent Stripe pour le paiement côté client Flutter'
- $summary: 'Créer un PaymentIntent pour Flutter Stripe'
- $requestBody: new OA\RequestBody(required: false, content: new OA\JsonContent(properties: [new OA\Property(property: 'currency', description: 'Devise (par défaut: gnf)', type: 'string', example: 'gnf')]))
- $tags: ['Physical Cards']
- $parameters: [new OA\Parameter(name: 'id', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))]
- $responses: [new OA\Response(response: 200, description: 'PaymentIntent créé avec succès', content: new OA\JsonContent(properties: [new OA\Property(property: 'client_secret', type: 'string', example: 'pi_1234567890_secret_abcdef'), new OA\Property(property: 'payment_intent_id', type: 'string', example: 'pi_1234567890'), new OA\Property(property: 'amount', type: 'integer', example: 50000), new OA\Property(property: 'currency', type: 'string', example: 'gnf'), new OA\Property(property: 'status', type: 'string', example: 'requires_payment_method'), new OA\Property(property: 'card_info', properties: [new OA\Property(property: 'id', type: 'integer', example: 123), new OA\Property(property: 'product_name', type: 'string', example: 'Carte Premium'), new OA\Property(property: 'price', type: 'string', example: '500.00'), new OA\Property(property: 'description', type: 'string', example: 'Carte de visite physique personnalisée')], type: 'object')])), new OA\Response(response: 400, description: 'Carte gratuite ou données invalides'), new OA\Response(response: 401, description: 'Non authentifié'), new OA\Response(response: 404, description: 'Carte physique non trouvée'), new OA\Response(response: 403, description: 'Paiement déjà effectué ou statut invalide')]
- #[Route]
- '/{id}/create-payment-intent'
- $name: 'create_payment_intent'
- $methods: ['POST']
Return values
JsonResponsegetModels()
Renvoie les modèles graphiques de carte actuellement activés.
public
getModels() : JsonResponse
Charge les entités CardModel filtrées sur isActive = true et les sérialise avec leur
identifiant, leur nom, leur type et les chemins des visuels recto (frontImage) et verso
(backImage). Comme pour le catalogue produit, la vérification d'abonnement Pro est
commentée : l'utilisateur courant est lu mais aucun filtrage ni refus n'en découle.
Route : /api/physical-cards/models (GET), nom api_physical_cards_get_models.
Authentification pleine requise.
Réponses : 200 toujours, avec la liste (éventuellement vide) ; 401 si le jeton est absent ou invalide.
Attributes
- #[Get]
- $path: '/api/physical-cards/models'
- $description: 'Récupère la liste des modèles de cartes physiques disponibles'
- $summary: 'Lister les modèles de cartes disponibles'
- $tags: ['Physical Cards']
- $responses: [new OA\Response(response: 200, description: 'Liste des modèles de cartes', content: new OA\JsonContent(type: 'array', items: new OA\Items(properties: [new OA\Property(property: 'id', type: 'integer', example: 1), new OA\Property(property: 'name', type: 'string', example: 'Modèle Élégant'), new OA\Property(property: 'type', type: 'string', example: 'Business'), new OA\Property(property: 'frontImage', type: 'string', example: '/uploads/models/elegant-front.jpg'), new OA\Property(property: 'backImage', type: 'string', example: '/uploads/models/elegant-back.jpg'), new OA\Property(property: 'isActive', type: 'boolean', example: true)]))), new OA\Response(response: 401, description: 'Non authentifié'), new OA\Response(response: 403, description: 'Accès refusé - Abonnement Pro requis')]
- #[Route]
- '/models'
- $name: 'get_models'
- $methods: ['GET']
Return values
JsonResponse —Tableau JSON de array<string, mixed> décrivant chaque modèle actif.
getProducts()
Renvoie le catalogue des produits de carte physique commercialisables.
public
getProducts() : JsonResponse
Charge toutes les entités Card dont le drapeau isHide vaut false et les projette en
un tableau JSON plat (identifiant, nom, description, prix converti en chaîne, isActive
calculé comme la négation de isHide()). L'utilisateur courant est récupéré mais n'est
plus utilisé : le contrôle d'abonnement Pro est présent en commentaire et donc inactif,
de sorte que le statut 403 documenté dans l'attribut OpenAPI n'est jamais renvoyé.
Route : /api/physical-cards/products (GET), nom api_physical_cards_get_products.
Authentification pleine requise (IS_AUTHENTICATED_FULLY au niveau de la classe).
Réponses : 200 toujours, avec la liste (éventuellement vide) ; 401 si le jeton est absent ou invalide.
Attributes
- #[Get]
- $path: '/api/physical-cards/products'
- $description: 'Récupère la liste des produits de cartes physiques disponibles'
- $summary: 'Lister les produits de cartes disponibles'
- $tags: ['Physical Cards']
- $responses: [new OA\Response(response: 200, description: 'Liste des produits de cartes', content: new OA\JsonContent(type: 'array', items: new OA\Items(properties: [new OA\Property(property: 'id', type: 'integer', example: 1), new OA\Property(property: 'name', type: 'string', example: 'Carte Standard'), new OA\Property(property: 'description', type: 'string', example: 'Carte de visite standard en papier de qualité'), new OA\Property(property: 'price', type: 'string', example: '29.99'), new OA\Property(property: 'isActive', type: 'boolean', example: true)]))), new OA\Response(response: 401, description: 'Non authentifié'), new OA\Response(response: 403, description: 'Accès refusé - Abonnement Pro requis')]
- #[Route]
- '/products'
- $name: 'get_products'
- $methods: ['GET']
Return values
JsonResponse —Tableau JSON de array<string, mixed> décrivant chaque produit visible.
getUserOrders()
Liste les cartes physiques commandées par l'utilisateur connecté.
public
getUserOrders() : JsonResponse
Interroge PhysicalCardRepository sur le seul utilisateur courant, tri décroissant sur
createdAt, et projette chaque PhysicalCard (et non l'entité Order) en un objet
contenant l'identifiant, le statut, le prix exposé sous la clé totalPrice avec la valeur
de repli '0.00', la date de création au format ISO 8601 (format('c')) et la date de mise
à jour, null tant que la carte n'a jamais été modifiée.
Route : /api/physical-cards/orders (GET), nom api_physical_cards_user_orders.
Authentification pleine requise.
Réponses : 200 toujours, avec un tableau éventuellement vide ; 401 si le jeton est absent ou invalide.
Attributes
- #[Get]
- $path: '/api/physical-cards/orders'
- $description: 'Récupère toutes les commandes de cartes physiques de l\'utilisateur'
- $summary: 'Récupérer les commandes utilisateur'
- $tags: ['Physical Cards']
- $responses: [new OA\Response(response: 200, description: 'Liste des commandes', content: new OA\JsonContent(type: 'array', items: new OA\Items(properties: [new OA\Property(property: 'id', type: 'integer', example: 1), new OA\Property(property: 'status', type: 'string', example: 'pending'), new OA\Property(property: 'totalPrice', type: 'string', example: '29.99'), new OA\Property(property: 'createdAt', type: 'string', format: 'date-time'), new OA\Property(property: 'updatedAt', type: 'string', format: 'date-time')]))), new OA\Response(response: 401, description: 'Non authentifié')]
- #[Route]
- '/orders'
- $name: 'user_orders'
- $methods: ['GET']
Return values
JsonResponse —Tableau JSON reflétant une list<PhysicalCard> sérialisée.
show()
Renvoie le détail d'une carte physique appartenant à l'utilisateur connecté.
public
show(int $id) : JsonResponse
La recherche combine l'identifiant et l'utilisateur courant dans un unique findOneBy() :
une carte existante mais appartenant à un autre utilisateur est donc traitée comme
introuvable et provoque un 404, jamais un 403. La charge utile reprend l'identifiant, le
statut, le prix sous la clé totalPrice avec la valeur de repli '0.00', la date de
création au format ISO 8601 et la date de mise à jour, null si elle n'a jamais été
renseignée.
Route : /api/physical-cards/{id} (GET), nom api_physical_cards_show.
Authentification pleine requise.
Réponses : 200 si la carte appartient à l'utilisateur ; 404 si elle est inexistante ou détenue par un autre utilisateur ; 401 si le jeton est absent ou invalide. Le 403 documenté dans l'attribut OpenAPI n'est pas produit par le code.
Parameters
- $id : int
-
Identifiant de la carte physique demandée, extrait de l'URL.
Attributes
- #[Get]
- $path: '/api/physical-cards/{id}'
- $description: 'Récupère les détails d\'une carte physique spécifique'
- $summary: 'Récupérer une carte physique'
- $tags: ['Physical Cards']
- $parameters: [new OA\Parameter(name: 'id', description: 'ID de la carte physique', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))]
- $responses: [new OA\Response(response: 200, description: 'Détails de la carte physique', content: new OA\JsonContent(properties: [new OA\Property(property: 'id', type: 'integer', example: 1), new OA\Property(property: 'status', type: 'string', example: 'pending'), new OA\Property(property: 'totalPrice', type: 'string', example: '29.99'), new OA\Property(property: 'createdAt', type: 'string', format: 'date-time'), new OA\Property(property: 'updatedAt', type: 'string', format: 'date-time')])), new OA\Response(response: 401, description: 'Non authentifié'), new OA\Response(response: 403, description: 'Accès refusé'), new OA\Response(response: 404, description: 'Carte non trouvée')]
- #[Route]
- '/{id}'
- $name: 'show'
- $methods: ['GET']
Return values
JsonResponse —Objet JSON {id, status, totalPrice, createdAt, updatedAt} ou
{error} si la carte est introuvable.
updateStatus()
Modifie le statut de suivi d'une carte physique, réservé aux administrateurs.
public
updateStatus(int $id, Request $request) : JsonResponse
La carte est recherchée par son seul identifiant, sans filtre sur le propriétaire : un
administrateur peut donc agir sur la carte de n'importe quel utilisateur. Le statut soumis
doit appartenir à la liste blanche STATUS_PENDING, STATUS_PROCESSING, STATUS_SHIPPED
et STATUS_DELIVERED ; les statuts STATUS_PENDING_PAYMENT et STATUS_CONFIRMED, pilotés
par le parcours de paiement (createPaymentIntent(), confirmPayment(), ou la création
d'une carte offerte), sont volontairement refusés ici. Aucune transition n'est contrôlée :
n'importe quel statut autorisé peut succéder à n'importe quel autre, y compris en retour
arrière. En cas de succès, updatedAt est repositionné à l'instant courant et la
modification est écrite via un flush(). Le message d'erreur en cas de statut invalide
énumère les valeurs acceptées.
Route : /api/physical-cards/{id}/status (PATCH), nom api_physical_cards_update_status.
Rôle ROLE_ADMIN requis.
Corps attendu : {"status": "string — nouveau statut, parmi pending, processing, shipped, delivered"}
Réponses : 200 si le statut est appliqué ; 400 si la clé status est absente du corps JSON
ou si sa valeur n'est pas dans la liste blanche ; 404 si aucune carte physique ne porte cet
identifiant ; 403 si l'appelant n'est pas administrateur ; 401 si le jeton est absent ou
invalide.
Parameters
- $id : int
-
Identifiant de la carte physique à mettre à jour, extrait de l'URL.
- $request : Request
-
Requête HTTP dont le corps JSON porte le nouveau statut.
Attributes
- #[IsGranted]
- 'ROLE_ADMIN'
- #[Patch]
- $path: '/api/physical-cards/{id}/status'
- $description: 'Met à jour le statut d\'une carte physique (admin uniquement)'
- $summary: 'Mettre à jour le statut'
- $requestBody: new OA\RequestBody(required: true, content: new OA\JsonContent(properties: [new OA\Property(property: 'status', type: 'string', enum: ['pending', 'processing', 'shipped', 'delivered', 'cancelled'], example: 'processing')]))
- $tags: ['Physical Cards']
- $parameters: [new OA\Parameter(name: 'id', description: 'ID de la carte physique', in: 'path', required: true, schema: new OA\Schema(type: 'integer'))]
- $responses: [new OA\Response(response: 200, description: 'Statut mis à jour avec succès', content: new OA\JsonContent(properties: [new OA\Property(property: 'id', type: 'integer', example: 1), new OA\Property(property: 'status', type: 'string', example: 'processing'), new OA\Property(property: 'updatedAt', type: 'string', format: 'date-time')])), new OA\Response(response: 400, description: 'Statut invalide'), new OA\Response(response: 401, description: 'Non authentifié'), new OA\Response(response: 403, description: 'Accès refusé - Admin requis'), new OA\Response(response: 404, description: 'Carte non trouvée')]
- #[Route]
- '/{id}/status'
- $name: 'update_status'
- $methods: ['PATCH']
Return values
JsonResponse —Objet JSON {id, status, updatedAt} après mise à jour, ou {error}.
createOrderForPhysicalCard()
Crée une commande pour une carte physique
private
createOrderForPhysicalCard(PhysicalCard $physicalCard) : Order
Parameters
- $physicalCard : PhysicalCard
Return values
OrderisValidHexColor()
Valide qu'une couleur est au format hexadécimal valide
private
isValidHexColor(string $color) : bool
Parameters
- $color : string
Return values
boolparseMoney()
Convertit un montant textuel en nombre à virgule flottante exploitable pour un calcul.
private
parseMoney(string|null $value) : float
Une valeur null — cas d'une option couleur ou d'impression absente ou sans supplément —
donne 0.0, ce qui permet de sommer directement les prix optionnels. Sinon, les espaces
sont supprimés et les virgules remplacées par des points afin d'accepter les écritures
localisées du type 1 234,50, puis la chaîne normalisée est convertie par transtypage
(float). Ce transtypage n'échoue jamais : une chaîne vide ou non numérique produit 0.0
et une chaîne partiellement numérique n'est lue que jusqu'au premier caractère invalide.
Attention, un séparateur de milliers écrit avec un point (1.234,50) est mal interprété,
les deux points se retrouvant dans la chaîne normalisée.
Parameters
- $value : string|null
-
Montant brut issu d'une entité (
Card::getPrice(), prix d'option).
Return values
float —Montant numérique, 0.0 en l'absence de valeur exploitable.
validateImageFile()
Valide un fichier image uploadé
private
validateImageFile(UploadedFile $file) : void
Parameters
- $file : UploadedFile
validateLogoFile()
Valide un fichier logo uploadé
private
validateLogoFile(UploadedFile $file) : void
Parameters
- $file : UploadedFile