IEE Edu’s subscription system gives students unlimited access to all published courses for a fixed period. When a subscription payment is approved by an admin, the platform automatically enrolls the student in every published course. When the subscription expires or is cancelled, that bulk access is quietly revoked — without deleting any progress the student has made. The entire lifecycle is managed by a single service class (Documentation Index
Fetch the complete documentation index at: https://mintlify.com/RigbySawGame/ieeEdu_Wen/llms.txt
Use this file to discover all available pages before exploring further.
SubscriptionAccessService) to ensure consistent behavior across every trigger point: payment approval, admin toggle, expiry command, and observer events.
Plans Overview
Three subscription plans are available. Their prices are read from environment variables at runtime:| Plan | Env Variable | Default Price | Duration |
|---|---|---|---|
| Trimestral | IIE_PLAN_TRIMESTRAL_PRICE | S/ 350 | 3 months |
| Semestral | IIE_PLAN_SEMESTRAL_PRICE | S/ 600 | 6 months |
| Anual | IIE_PLAN_ANUAL_PRICE | S/ 990 | 12 months |
subscription_plans table and can be edited by an admin at Settings → Plans (/admin/settings/plans). The SubscriptionPlan model provides a toPublicConfig() method that shapes the data for the public /planes page.
To update a plan’s price without a code deploy, set the environment variable and restart the application:
Subscription Model
ASubscription record is created (or updated via updateOrCreate) when a subscription payment is approved. The model’s fillable fields and status constants are:
start_date and end_date are automatically cast to Carbon instances. Each user has at most one subscription record; approving a new subscription payment calls Subscription::updateOrCreate keyed on user_id, which extends an existing subscription or creates a new one.
Access Grant Flow
When a subscription payment is approved,PaymentService::approve() calls SubscriptionAccessService::sync(), which in turn calls grantAccess():
Subscription record created
Subscription::updateOrCreate(['user_id' => ...], ['status' => 'activa', 'end_date' => now()->addMonths($months)]) is executed inside PaymentService::approve().SubscriptionAccessService::sync() is called directly
PaymentService::approve() calls app(SubscriptionAccessService::class)->sync($payment->user_id) immediately after the Subscription::updateOrCreate call. Because the Subscription model is also decorated with #[ObservedBy([SubscriptionObserver::class])], the observer’s updated() hook fires as a side effect of the same updateOrCreate and calls sync() again — the two calls are idempotent and safe.grantAccess() enrolls all published courses
SubscriptionAccessService::grantAccess($userId) fetches every Course where status = 'PUBLICADO' and creates or updates an Enrollment row for each one. Existing progress is always preserved.Access Revoke Flow
When a subscription expires or is cancelled,revokeAccess() is called:
Trigger
The revoke can be triggered by: the
subscriptions:sync-expired Artisan command, the SubscriptionObserver::updated() event when status changes to cancelada or expirada, or a manual call to SubscriptionAccessService::sync().Individual purchases protected
The service first collects course IDs the user has approved individual payments for. These are never touched.
Masterclass exception
Enrollments for masterclass/event-type courses that were
subscription_granted = true are converted to permanent access: subscription_granted is set to false and subscription_active stays true.Masterclass Exception
Courses of typeevento or masterclass retain access permanently — even after a subscription ends. This is enforced by the Course::retainsAccessAfterSubscriptionEnds() method:
grantAccess(), masterclass courses are enrolled via ensurePermanentEnrollment(), which sets subscription_granted = false from the start — meaning revokeAccess() will never touch them.
SubscriptionObserver
TheSubscriptionObserver is registered on the Subscription model via the #[ObservedBy] attribute and handles two lifecycle events:
| Event | Condition | Action |
|---|---|---|
updated | status field changed | Calls SubscriptionAccessService::sync($subscription->user_id) — grants access if now active, revokes if now cancelled/expired. |
deleted | Subscription record deleted | Calls SubscriptionAccessService::sync($subscription->user_id) to revoke all subscription-based access for that user. |
Sync Command
A scheduled Artisan command checks for subscriptions that have passed theirend_date and marks them as expired:
status = 'activa' and end_date < now(), sets each to status = 'expirada', and calls SubscriptionAccessService::sync() for each affected user. It is scheduled to run daily in routes/console.php: