Pricing and Access Models
Every Learnomy course carries two linked settings: an access type (the coarse "how is this course reached" value) and a pricing model (the richer "how a student pays" value). Together they decide whether a course shows a price, a Buy button, a membership path, or nothing at all.
What you can do
- Offer a course for free, so any signed-in student can enrol with one click and no payment.
- Sell a course for a one-time price, with an optional timed sale price.
- Sell time-limited access, where a purchase grants entry for a fixed number of days and then expires.
- Sell a course as a recurring subscription, billed per month or per year.
- Gate a course behind a membership plan, so only members whose plan includes it can enrol.
- Offer both paths at once: let students either buy the course outright or reach it through their membership.
- Run a closed course that is never sold on your site, granting access from an outside system instead. See Closed Course Pricing.

How to use it
Step 1 -- open the Pricing & Access section
Edit the course and open the Pricing & Access section of the course editor. You will see the Pricing model control and the price fields.
Step 2 -- pick a pricing model
Learnomy supports seven pricing models. Each maps to one of five access types under the hood, so the catalog, checkout, and membership surfaces all agree:
| Pricing model | What the student does | Access type stored |
|---|---|---|
| Free | Enrols for free, no payment | free |
| One-time | Buys once, keeps lifetime access | paid |
| Fixed-term | Buys once, access expires after a set number of days | paid |
| Recurring | Subscribes per month or per year to keep access | paid |
| Members only | Enrols only if their membership plan includes the course | membership |
| One-time or members | Buys the course outright OR reaches it through membership | paid_or_membership |
| Closed | Cannot buy here; you grant access externally | closed |
Step 3 -- set the price fields
The visible price fields depend on the model you chose:
- One-time and fixed-term courses use the regular Price, with an optional Sale price inside a Sale starts / Sale ends window.
- Fixed-term courses also read an access-duration value (in days); when set, each enrollment is stamped with an expiry date that many days out.
- Recurring courses use a recurring amount plus an interval of
monthoryear. - Free, members only, and closed courses show no price -- switching a course to free also zeroes any lingering price and clears the sale price automatically.
Step 4 -- save
On save, Learnomy reconciles the two columns so they never drift: the model you picked sets the matching access type, and a free course can never keep a stale price.
Settings and options
- Sales. A sale price only counts as active when it is positive, below the regular price, and the current time falls inside the sale window. Every surface (single course page, catalog card, price facet) reads the same rule, so they cannot show different prices.
- Fixed-term expiry. Access expiry is computed from the course's access-duration days at enrollment time. Only fixed-term courses expire; every other model grants lifetime access by default.
- Pricing lock after purchase. Once at least one student holds a non-pending paid or subscription enrollment, the course form refuses to rewrite the pricing model. Existing paying customers must not be moved into a half-state; clone the course to relaunch it with new pricing. Free enrollments do not lock the model.
- Access-type filter. The catalog can be filtered by access type, so students can narrow to free or paid courses.
For developers
Model (Learnomy\Models\Course) is the single source of truth for the pricing vocabulary -- never hardcode the maps:
Course::ACCESS_TYPES/Course::PRICING_MODELS-- the two canonical option lists.Course::MODEL_ACCESS_MAP/Course::ACCESS_PRIMARY_MODEL-- the maps between the two dimensions.Course::model_for_access( $access_type )/Course::access_for_model( $model )-- convert between them.Course::pricing_model( $course )-- the resolved model for a course row, guarding against a stalepricing_model='free'column.Course::is_free()/is_one_time()/is_fixed_term()/is_recurring()/is_members_only()/has_member_path()/requires_purchase()/is_closed()-- predicate helpers that take astdClasscourse row.Course::is_on_sale( $course )/Course::effective_price( $course )-- sale state and the price the buyer actually pays.Course::compute_access_expiry( $course, $from = null )-- the expiryDateTimeImmutablefor a fixed-term course (null for every other model).Course::recurring_amount( $course )/Course::recurring_interval( $course )-- per-period price andmonth/yearinterval for recurring courses.Course::lock_pricing_model_if_purchased( $course_id )-- true when a paid/subscription enrollment locks the model.
REST: the courses controller accepts access_type (enum including paid_or_membership) and the pricing fields on create/update.