Skip to content

BMC membership journey: the rules

This documents the rules of BMC membership — when you can join, the renew window, when a membership lapses, and the early-bird discount — together with where each rule is configured and how it's decided in the code. The rules were revised to give members a longer renew window and to restrict the early-bird discount to continuing members.

The rules at a glance

When can I join?

  • BMC membership covers a calendar year (1 Jan – 31 Dec).
  • A new member joins and their membership runs to the end of the current membership year — unless they join on or after 1 September, in which case it runs to the end of the following year (and starts on the day they join).

When can I renew?

  • You can renew when your last BMC membership is still within the renew window — that is, it has not lapsed (see below) and you're renewing inside the ordinary renewal window in the weeks before the year ends (45 days before 31 Dec).
  • A renewal inside the window extends the membership to the next year end; outside it, to the current year end.

When does a membership lapse?

  • A membership lapses when it has slipped out of the three-membership-year renew window. In practice: if your last BMC membership covered, say, 2025, you can still renew through 2026 and 2027, and only have to re-join (rather than renew) when you would renew for 2028.
  • Once lapsed you can't renew online; you go through the join process as a new member instead.

Early-bird discount

  • Renewals only (never a new join), by 31 March, and only if you held a BMC membership in the immediately previous membership year (renewing for 2027 needs a 2026 membership). New joiners, and renewers whose last membership was more than one season ago, do not get the early-bird price — they pay the standard £40 rate.

Prices

  • Standard — £40 for one annual (calendar-year) membership.
  • Early-bird — £35 — renew and confirm payment by 31 March and, as a continuing member, you pay £35 instead of £40.
  • New members — £45 for the first year, which includes a BMC vest (can take 4+ weeks to arrive). A join is never given the early-bird discount.
  • Joining later in the year — a new join from 1 September covers the following calendar year, and the standard £40 yearly rate applies.
  • Overseas — £50.

The rule changes

Two rules were changed to tighten/relax these:

  1. Longer renew window (renew vs join). A member's right to renew (rather than re-join) now stays open for three membership years after their last membership, not two. A member who expired 31 Dec 2025 is still treated as renewable (not lapsed) all through 2026 and 2027, and only lapses when they would renew for 2028.
  2. Early-bird discount is for continuing renewals only. It now requires all of: (a) a renewal, not a new join; (b) the early-bird window open (renewing by 31 Mar); and (c) a membership in the immediately previous season. A new joiner, or a renewer whose last membership was more than one season ago, pays full price.

Where the rules live

Config on the BMC federation and organisation (fixtures in project/federation/fixtures/bmc/bmc.json, and in the admin):

  • On the Organisation: membership_year_end_day/month = 12 / 31 (calendar-year membership); renewal_window_days = 45 (the renew window).
  • On the Federation: membership_joining_window_days = 122 (the new-member joining window — makes 1 September the first day of the following-year window: 1 Sep → 121 days to 31 Dec, 31 Aug → 122); membership_discount_day/month = 31 / 3 and membership_discount_amount = -5.00 (the early-bird cutoff and amount).

How the code decides

  • Not lapsed / renew-vs-join: Person.membership_has_lapsed(org_code, start, end) (reference/models.py) is not-lapsed iff start <= date_to <= end, where the window comes from Federation.membership_not_lapsed_window(today) — spanning three consecutive season ends ([2 season-ends ago … current season end]). All four BMC call sites in federation/views.py were updated to use it; other federations keep the old two-season window.
  • New-member joining window: Organisation.new_member_dates(when) returns the (date_from, date_to) for a join; make_member(..., is_join=True) uses it. The path decides (a lapsed-old-member is still "joining"), and the join URL quotes the same end date in the Stripe description.
  • Early-bird: Federation.early_bird_discount(...) delegates to a per-federation pricing mixin (project/federation/pricing.py, registered in _MIXINS_BY_CODE). The BMC mixin returns 0 unless it's a renewal and the person held a MEMBER/REGISTERED role on the BMC org whose date_to year is the season before the one being bought. BMC's £45 categories carry renew_fee_modifier = -5.00, so a renewal costs £40 (the standard rate) — that modifier is NOT window-gated and applies to every renewal. When the early-bird window is open and the member qualifies, org_renew_pay adds the federation membership_discount_amount = -5.00 on top of that modifier, giving the £35 early-bird price. So the modifier is the always-on standard rate and the early-bird is a window-gated extra; neither applies to a new join. Overseas categories are £50.

Emails

After a successful payment the member can get a confirmation email; this is gated on a non-empty athlete_membership_email on the Org (see notify_athlete in federation/utils.py). Not part of the membership rules.

Tests

project/federation/tests/test_bmc.py:

  • BMCEarlyBirdDiscountTests — renew + previous-season membership → discount; renew without it → none; join → none; window closed → none; generic federation → discounts joins and renewals; membership_not_lapsed_window(today=2027-06-01) → [2025-12-31, 2027-12-31].
  • BMCOverseasLogicTest — join dates (from 1 September → following year, before September → this year end, long-lapsed re-join → still following year), an end-to-end join payment through org-join-pay, and a category change keeping the season dates.
  • Renewal prices — test_renewal_after_the_early_bird_date_pays_the_full_fee and test_renewal_price_stacks_category_and_early_bird_discounts fetch org-renew-pay in offline-payment mode and read the quoted amount, plus the page-level January/prior-season cases in BMCEarlyBirdRenewPageTests.

The _FederationStub in that file models the federation surface; the DB-backed cases use the bmc/bmc.json fixture and real Person/Role rows.

Adding another federation's early-bird rule

  1. Subclass EarlyBirdPricingMixin, override early_bird_discount(...).
  2. Register the class in _MIXINS_BY_CODE keyed by the federation's code.
  3. get_federation_pricing picks it up automatically; the model method and the two price blocks need no change.