Season Memberships
A recurring plan bills on each member’s own anniversary. That is the wrong shape for an organisation whose membership year is a fixed calendar window — a club running July to June, where everyone should expire together on 30 June no matter when they joined. A fixed season plan is that shape.
Creating a Season Plan
Under Headcount → Plans, set Term Type to Fixed season. The billing interval fields are replaced by the season window:
| Field | What it does |
|---|---|
| Season Starts | The first day of the membership year. |
| Season Ends | The last day. Members keep access through this day — a season ending 30 June is still valid at 6pm on 30 June — then expire together. |
| Repeats Every Year | On by default. The window rolls forward a year on its own once the season finishes, so next season sells itself without anyone editing the plan. |
| Pro-rata Mid-season Joins | Off by default. When on, scales the price by how much of the season is left. |
A repeating season must be shorter than a year, otherwise it would overlap its own next occurrence and “which season is this member in” would stop having one answer. Turn Repeats Every Year off for a one-off season, which simply stops selling once it ends.
Pro-rata Pricing
Two bases, both counting the join month or day itself — somebody joining on the 20th still gets the rest of that month:
| Basis | Example on a £120 July–June season |
|---|---|
| Whole months remaining (default) | Joining in October leaves nine months of twelve — £90. Familiar, and it is what most clubs mean by “half a season”. |
| Days remaining | Joining on 1 January leaves 181 days of 365 — £59.51. Exact, but produces prices nobody would print on a form. |
Anyone buying before the season opens pays full price and their membership starts on the first day of the season, not on the day they paid.
How Payment Works
A season membership is a one-off Stripe payment, not a subscription — there is no billing cycle and nothing renews. Two consequences worth knowing before you build a pricing page:
- Season plans have no stored Stripe Price. A pro-rata plan charges a different amount to everybody who joins on a different day, and a stored Price can only hold one number, so the line item is built per checkout.
headcount/sync/plansskips season plans. - PayPal cannot be used. Its Subscriptions API only bills on a cycle. Checkout refuses a PayPal season purchase rather than quietly signing the member up to something recurring — offer card payment for these plans.
Coupons and Stripe promotion codes work as normal.
Expiry
Nothing expires on its own. Run the expiry command daily — it retires finished season terms as expired and removes members from the plan’s user group, while a membership the member actually cancelled becomes canceled:
# Daily
php craft headcount/subscriptions/expire
# Daily — warns members before their season runs out
php craft headcount/subscriptions/remind
The reminder is worth setting up for season plans in particular: nothing renews automatically, so that email is the only thing between a member and quietly losing access on 1 July. Set how far ahead it goes out under Headcount → Settings → Emails.
In Templates
A season plan exposes its current window and the price a member would pay if they joined right now:
{% set plan = craft.headcount.plan('club-membership') %}
{% if plan.isFixedTerm() %}
{% set window = plan.getSeasonWindow() %}
<h3>{{ plan.name }}</h3>
<p>Season: {{ window.start|date('j M Y') }} to {{ window.end|date('j M Y') }}</p>
{# What this visitor would actually be charged today #}
<p>{{ plan.currency|upper }} {{ plan.getProratedPrice()|number_format(2) }}</p>
{% if plan.getProratedPrice() < plan.price %}
<p class="note">Reduced — the season is already under way.</p>
{% endif %}
{% endif %}
The same fields are on the REST API’s plans endpoint as termType, seasonStart, seasonEnd and currentPrice.
Season Cards
Season memberships pair naturally with wallet cards: the card carries the season’s end date, so it greys out on the member’s phone on 1 July whether or not their phone has spoken to your site since.