BCard - Documentation technique

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

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, CardColorOption et CardPrintOption, 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 cardProductId demandé.

$cardModelRepository : CardModelRepository

Dépôt des modèles de carte, utilisé pour résoudre le cardModelId optionnel.

$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
JsonResponse

createOrder()

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}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
JsonResponse

getModels()

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}.

isValidHexColor()

Valide qu'une couleur est au format hexadécimal valide

private isValidHexColor(string $color) : bool
Parameters
$color : string
Return values
bool

parseMoney()

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

        
On this page

Search results