WP Manifestindependent plugin directory
manifest / ecommerce / woocommerce-course-pricing

WooCommerce Course Pricing

Pro-rata pricing for WooCommerce course products: the price is scaled by the share of sessions still to come, with holidays excluded

by Alex Mochulskyi · github.com/alexskybrain/woocommerce-course-pricing

★ 0stars
0forks

Install

No release zip yet. The repository archive installs, but the folder name will carry the branch suffix and updates will not flow:

wp plugin install https://github.com/alexskybrain/woocommerce-course-pricing/archive/refs/heads/main.zip

A term course sold at a fixed price is not worth the same in week six as it was in week one. A school that keeps charging the full amount either turns away late enrolments or issues refunds by hand, every time.

This plugin prices a course by the share of sessions the student will actually attend. It was built for a language school running term courses as WooCommerce products, where late enrolments were being adjusted manually on every order.

How the price is worked out

Given a course that runs weekly on a fixed weekday between two dates:

  1. Build the list of dates the class meets, starting at the first matching weekday on or after the start date.
  2. Remove any date inside a holiday period.
  3. Count how many of the remaining dates are in the past. A session happening today counts as upcoming, because it has not happened yet.
  4. Scale the price: full price × remaining ÷ total, rounded to the store's price precision.

A course whose sessions have all passed prices at zero. A schedule with no sessions at all is treated as a misconfiguration and leaves the price untouched, rather than silently making the course free.

Worked example

A ten-week Monday course at £300, with one week of half term removed, is nine sessions at £33.33 each. Enrol after the sixth session and you pay for the three that are left: £100.

Requirements

  • WordPress 6.4+
  • WooCommerce
  • Advanced Custom Fields, for the schedule fields
  • PHP 8.2+

Both dependencies are checked on load, and the plugin tells you in the admin and leaves prices alone rather than failing.

Setup

Add an ACF group named course_dates to your course products:

Field Type Purpose
start_date Date picker First day of the term
end_date Date picker Last day of the term
apply_price_calculator True / False Switches pro-rata pricing on for this product
holidays Repeater of start_date + end_date Periods with no session

The meeting day is read from a product attribute such as Day and Time, with a value like Monday 19:00-20:00. The variation is checked first and the parent product second.

By default the plugin only touches products in the courses category, and only the variation whose attribute value is full — a trial lesson costs the same whenever you buy it.

Filters

Filter Default Purpose
course_pricing_categories ['courses'] Product categories in scope. Empty array means all.
course_pricing_full_terms ['full'] Attribute values that get pro-rata pricing
course_pricing_excluded_terms ['trial'] Attribute values that never do
course_pricing_day_attributes ['day-and-time', 'pa_day-and-time', 'Day and Time'] Attribute names checked for the meeting day
course_pricing_date_format d/m/Y Format the ACF date picker returns

Architecture

src/
  Schedule/DateRange.php        an inclusive, self-normalising range of days
  Schedule/CourseSchedule.php   dates, weekday, holidays
  Schedule/SessionPlanner.php   schedule to list of session dates
  Pricing/ProRataCalculator.php the rule, with no WordPress in it
  Pricing/Quote.php             price plus the counts behind it
  WooCommerce/ScheduleSource.php   interface: where dates come from
  WooCommerce/AcfScheduleSource.php  the ACF implementation
  WooCommerce/VariationMatcher.php   which variation qualifies
  WooCommerce/PriceApplier.php       hooks into cart and display

The calculation is free of WordPress calls, and the schedule arrives through an interface, so the pricing rules can be tested with nothing but a PHP binary and the storage can be swapped without touching them.

Quote carries the session counts alongside the amount, not just the number. "Why am I paying £100 and not £300" is the first question a parent asks, and the answer is shown as a line under the cart item rather than recomputed somewhere else.

Tests

php tests/pro-rata-test.php

Sixteen checks covering session planning, holiday handling, the boundaries at both ends of a term, rounding, and the guards against an impossible schedule.

Notes on the rewrite

This is a rewrite of code that ran in production, and two behaviours were deliberately changed:

The price is now applied in the cart, not only at checkout. The original hooked woocommerce_before_calculate_totals but returned early unless is_checkout(), so the cart displayed the full term price and the total dropped on the next screen. The variation picker and the displayed price now come from the same calculation as the amount charged.

A session happening today is no longer counted as passed. The original comment said today should not count, and the comparison used <=, which counted it. Every parent enrolling on a class morning was charged for a lesson that had not started.

License

GPL-2.0-or-later — see LICENSE.