BCard - Documentation technique

SubscriptionApiController extends AbstractController
in package

Contrôleur d'API des abonnements et de leur souscription payante.

Il expose le catalogue des plans actifs, l'abonnement courant de l'utilisateur, puis le tunnel de passage au plan Pro : création (ou reprise) d'un abonnement et d'un paiement en attente, ouverture d'une transaction Djomy dont l'URL de paiement est renvoyée au client mobile, puis vérification du statut du paiement. L'abonnement n'est activé que par PaymentFulfillmentService (webhook ou vérification de statut). La devise est le franc guinéen (GNF).

Préfixe de route : /api/subscription. Toutes les actions exigent IS_AUTHENTICATED_FULLY (jeton JWT).

Attributes
#[IsGranted]
'IS_AUTHENTICATED_FULLY'
#[Route]
'/api/subscription'
#[Tag]
$name: 'Subscription API'

Table of Contents

Properties

$djomyCheckoutService  : DjomyCheckoutService
$entityManager  : EntityManagerInterface
$featuresService  : PredefinedFeaturesService
$logger  : LoggerInterface
$mobilePaymentService  : MobilePaymentService
$subscriptionService  : SubscriptionService

Methods

__construct()  : mixed
confirmPayment()  : JsonResponse
Vérifie le statut du paiement d'abonnement (route conservée pour la compatibilité)
getCurrentSubscription()  : JsonResponse
Récupère l'abonnement actuel de l'utilisateur
getPlans()  : JsonResponse
Récupère tous les plans disponibles
upgradeToProProcess()  : JsonResponse
Initie le passage au plan Pro et démarre le paiement Djomy
findReusablePendingPayment()  : Payment|null
Retrouve le paiement en attente d'une précédente tentative pour le même plan et le même cycle.

Properties

Methods

__construct()

public __construct(EntityManagerInterface $entityManager, SubscriptionService $subscriptionService, DjomyCheckoutService $djomyCheckoutService, MobilePaymentService $mobilePaymentService, LoggerInterface $logger, PredefinedFeaturesService $featuresService) : mixed
Parameters
$entityManager : EntityManagerInterface

Gestionnaire d'entités Doctrine, utilisé pour les dépôts, la persistance et la transaction d'upgrade.

$subscriptionService : SubscriptionService

Service métier des abonnements (éligibilité à l'upgrade, quota de cartes, activation).

$djomyCheckoutService : DjomyCheckoutService

Ouvre la transaction Djomy d'un paiement.

$mobilePaymentService : MobilePaymentService

Réponses de démarrage, d'échec et de statut communes à l'API mobile.

$logger : LoggerInterface

Journalise les échecs de démarrage, sans donnée sensible.

$featuresService : PredefinedFeaturesService

Service de résolution des fonctionnalités d'un plan en libellés et icônes.

confirmPayment()

Vérifie le statut du paiement d'abonnement (route conservée pour la compatibilité)

public confirmPayment(Request $request) : JsonResponse

Lit payment_id dans le corps, retrouve le paiement d'abonnement de l'utilisateur authentifié (404 sinon, y compris pour le paiement d'un autre utilisateur) puis relit son statut chez Djomy via MobilePaymentService::checkStatus(), comme GET /api/payments/{id}/status. Si Djomy confirme le succès, l'abonnement est activé par PaymentFulfillmentService. Plus aucun identifiant Stripe n'est lu.

Route : /api/subscription/confirm-payment (POST), nom api_subscription_confirm_payment. Requiert IS_AUTHENTICATED_FULLY (hérité de la classe).

Corps attendu : {"payment_id": "int, obligatoire"}

Réponses : 200 {success, data: {payment_id, status, subscription_status, order_status}}, 400 payment_id manquant, 401 non authentifié, 404 paiement introuvable.

Parameters
$request : Request

Requête HTTP dont le corps JSON porte payment_id.

Attributes
#[Post]
$path: '/api/subscription/confirm-payment'
$summary: 'Vérifie le statut du paiement d\'abonnement (équivalent de GET /api/payments/{id}/status)'
$tags: ['Subscription API']
#[RequestBody]
$required: true
$content: new OA\JsonContent(required: ['payment_id'], properties: [new OA\Property(property: 'payment_id', type: 'integer', example: 42)], type: 'object')
#[Response]
$response: 200
$description: 'Statut du paiement'
$content: new OA\JsonContent(properties: [new OA\Property(property: 'success', type: 'boolean', example: true), new OA\Property(property: 'data', properties: [new OA\Property(property: 'payment_id', type: 'integer', example: 42), new OA\Property(property: 'status', type: 'string', enum: ['COMPLETED', 'PENDING', 'FAILED'], example: 'COMPLETED'), new OA\Property(property: 'subscription_status', type: 'string', nullable: true, example: 'ACTIVE'), new OA\Property(property: 'order_status', type: 'string', nullable: true, example: null)], type: 'object')], type: 'object')
#[Response]
$response: 400
$description: 'payment_id manquant'
#[Response]
$response: 401
$description: 'Non authentifié'
#[Response]
$response: 404
$description: 'Paiement introuvable'
#[Route]
'/confirm-payment'
$name: 'api_subscription_confirm_payment'
$methods: ['POST']
Return values
JsonResponse —

Statut du paiement, ou objet d'erreur.

getCurrentSubscription()

Récupère l'abonnement actuel de l'utilisateur

public getCurrentSubscription() : JsonResponse

Lit l'abonnement actif porté par l'utilisateur. En l'absence d'abonnement, la réponse reste un succès avec data à null et un message explicite plutôt qu'une 404. Sinon la charge utile agrège les données de l'abonnement (dates formatées au format ISO 8601, null si non renseignées) enrichies de deux valeurs calculées : l'éligibilité à un upgrade via SubscriptionService::canUpgrade() et le nombre de cartes restantes via getRemainingCardsForUser().

Route : /api/subscription/current (GET), nom api_subscription_current. Requiert IS_AUTHENTICATED_FULLY (hérité de la classe).

Réponses : 200 abonnement renvoyé, 200 aucun abonnement actif (data à null), 500 erreur interne ({success: false, message, error}), 401 requête non authentifiée.

Attributes
#[Get]
$path: '/api/subscription/current'
$summary: 'Récupère l\'abonnement actuel de l\'utilisateur'
$tags: ['Subscription API']
#[Response]
$response: 200
$description: 'Abonnement actuel de l\'utilisateur'
$content: new OA\JsonContent(properties: [new OA\Property(property: 'success', type: 'boolean', example: true), new OA\Property(property: 'data', properties: [new OA\Property(property: 'id', type: 'integer', example: 1), new OA\Property(property: 'plan_name', type: 'string', example: 'Free'), new OA\Property(property: 'status', type: 'string', example: 'active'), new OA\Property(property: 'billing_cycle', type: 'string', example: 'monthly'), new OA\Property(property: 'amount', type: 'string', example: '0.00'), new OA\Property(property: 'start_date', type: 'string', example: '2024-01-01T00:00:00+00:00'), new OA\Property(property: 'end_date', type: 'string', example: null), new OA\Property(property: 'next_billing_date', type: 'string', example: null), new OA\Property(property: 'can_upgrade', type: 'boolean', example: true), new OA\Property(property: 'cards_remaining', type: 'integer', example: 1)], type: 'object')], type: 'object')
#[Route]
'/current'
$name: 'api_subscription_current'
$methods: ['GET']
Return values
JsonResponse —

Objet JSON {success: bool, data: array<string, mixed>|null}.

getPlans()

Récupère tous les plans disponibles

public getPlans() : JsonResponse

Charge uniquement les plans marqués isActive = true et les met en forme manuellement. Les fonctionnalités brutes du plan sont enrichies en libellés et icônes par PredefinedFeaturesService::getSelectedFeaturesWithIcons(), avec repli sur un tableau vide. Les champs is_popular et badge sont renvoyés bien qu'ils ne figurent pas dans le schéma OpenApi déclaré. Toute exception est interceptée et convertie en réponse 500 incluant le message technique.

Route : /api/subscription/plans (GET), nom api_subscription_plans. Requiert IS_AUTHENTICATED_FULLY (hérité de la classe).

Réponses : 200 catalogue renvoyé ({success: true, data: [...]}), 500 erreur lors du chargement ({success: false, message, error}), 401 requête non authentifiée.

Attributes
#[Get]
$path: '/api/subscription/plans'
$summary: 'Récupère tous les plans disponibles'
$tags: ['Subscription API']
#[Response]
$response: 200
$description: 'Liste des plans disponibles'
$content: new OA\JsonContent(properties: [new OA\Property(property: 'success', type: 'boolean', example: true), new OA\Property(property: 'data', type: 'array', items: new OA\Items(properties: [new OA\Property(property: 'id', type: 'integer', example: 1), new OA\Property(property: 'name', type: 'string', example: 'Pro'), new OA\Property(property: 'description', type: 'string', example: 'Plan professionnel'), new OA\Property(property: 'monthly_price', type: 'string', example: '50000'), new OA\Property(property: 'yearly_price', type: 'string', example: '500000'), new OA\Property(property: 'cards_included', type: 'integer', example: 10), new OA\Property(property: 'features', type: 'array', items: new OA\Items(type: 'string')), new OA\Property(property: 'is_active', type: 'boolean', example: true)], type: 'object'))], type: 'object')
#[Route]
'/plans'
$name: 'api_subscription_plans'
$methods: ['GET']
Return values
JsonResponse —

Objet JSON {success: bool, data: list<array<string, mixed>>} où chaque plan porte id, name, description, monthly_price, yearly_price, cards_included, features, is_active, is_popular, badge.

upgradeToProProcess()

Initie le passage au plan Pro et démarre le paiement Djomy

public upgradeToProProcess(Request $request) : JsonResponse

Valide le moyen de paiement (seule la valeur djomy est acceptée), le cycle de facturation (monthly par défaut, ou yearly) et le téléphone du payeur (payer_phone, normalisé par DjomyService::normalizePhone()), puis l'éligibilité de l'utilisateur via SubscriptionService::canUpgrade(). Le plan est retrouvé par son nom « Pro ». Comme sur le web, le Payment PENDING lié à un Subscription PENDING d'une tentative précédente (même plan, même cycle) est réutilisé ; sinon l'abonnement et le paiement sont créés dans une transaction. La transaction Djomy est ensuite ouverte par DjomyCheckoutService::start(). L'abonnement n'est activé que par PaymentFulfillmentService (webhook ou vérification de statut), jamais ici. Un échec de démarrage annule la création et est traduit par MobilePaymentService::failureResponse().

Route : /api/subscription/upgrade-to-pro (POST), nom api_subscription_upgrade_to_pro. Requiert IS_AUTHENTICATED_FULLY (hérité de la classe).

Corps attendu : {"billing_cycle": "monthly|yearly (défaut monthly)", "payment_method": "djomy", "payer_phone": "string, obligatoire"}

Réponses : 201 {success, data: {payment_id, subscription_id, redirect_url, status, amount, currency}}, 400 moyen de paiement, cycle, téléphone invalide ou abonnement déjà actif, 401 non authentifié, 404 plan « Pro » absent, 409 paiement déjà en cours ou déjà payé, 503 Djomy non configuré ou indisponible.

Parameters
$request : Request

Requête HTTP dont le corps JSON porte le moyen de paiement, le cycle et le téléphone.

Attributes
#[Post]
$path: '/api/subscription/upgrade-to-pro'
$summary: 'Démarre le paiement Djomy de l\'abonnement Pro'
$tags: ['Subscription API']
#[RequestBody]
$required: true
$content: new OA\JsonContent(required: ['payment_method', 'payer_phone'], properties: [new OA\Property(property: 'billing_cycle', type: 'string', enum: ['monthly', 'yearly'], example: 'monthly'), new OA\Property(property: 'payment_method', type: 'string', enum: ['djomy'], example: 'djomy'), new OA\Property(property: 'payer_phone', type: 'string', example: '+224 622 00 00 00')], type: 'object')
#[Response]
$response: 201
$description: 'Paiement Djomy démarré : ouvrir redirect_url dans un navigateur'
$content: new OA\JsonContent(properties: [new OA\Property(property: 'success', type: 'boolean', example: true), new OA\Property(property: 'data', properties: [new OA\Property(property: 'payment_id', type: 'integer', example: 42), new OA\Property(property: 'subscription_id', type: 'integer', example: 123), new OA\Property(property: 'redirect_url', type: 'string', example: 'https://pay.djomy.africa/...'), new OA\Property(property: 'status', type: 'string', example: 'PENDING'), new OA\Property(property: 'amount', type: 'integer', example: 50000), new OA\Property(property: 'currency', type: 'string', example: 'GNF')], type: 'object')], type: 'object')
#[Response]
$response: 400
$description: 'Données invalides (moyen de paiement, cycle, téléphone) ou abonnement déjà actif'
#[Response]
$response: 401
$description: 'Non authentifié'
#[Response]
$response: 404
$description: 'Plan Pro indisponible'
#[Response]
$response: 409
$description: 'Un paiement est déjà en cours ou a déjà été confirmé'
#[Response]
$response: 503
$description: 'Paiement en ligne indisponible'
#[Route]
'/upgrade-to-pro'
$name: 'api_subscription_upgrade_to_pro'
$methods: ['POST']
Return values
JsonResponse —

Réponse JSON de succès ou d'erreur.

findReusablePendingPayment()

Retrouve le paiement en attente d'une précédente tentative pour le même plan et le même cycle.

private findReusablePendingPayment(User $user, Plan $plan, string $cycle) : Payment|null

Seuls les paiements Pro en libre-service (sans commande ni entreprise) dont l'abonnement est encore en attente sont repris, s'ils n'ont pas encore de transaction ou si leur prestataire est Djomy. Même règle que le parcours web.

Parameters
$user : User

Utilisateur authentifié.

$plan : Plan

Plan Pro.

$cycle : string

Subscription::BILLING_MONTHLY ou Subscription::BILLING_YEARLY.

Return values
Payment|null —

Paiement réutilisable, ou null.


        
On this page

Search results