Member perks

A perk links a Headcount plan to something another module sells. Today that means a Stub service: hold this plan and the service is cheaper, or restricted to you, or both. It is the one feature neither plugin could ship alone, and the reason the bundle is worth more than the sum of its parts.

Why it lives in Showtime

Stub knows nothing about membership plans. Headcount knows nothing about bookable services. Neither could own this relationship without depending on the other, and that dependency would end both of them shipping standalone. So the relationship lives in the host, in the one table Showtime owns itself — showtime_perks — and is implemented entirely as listeners on the modules’ own events. Neither plugin learns that the other exists.

Creating a perk

Go to Showtime → Member perks and click New perk. You’ll need at least one Headcount plan and one Stub service to exist first.

FieldWhat it does
PlanThe Headcount plan a member must hold for this perk to apply.
TargetWhat the perk applies to. Currently a Stub service (stub:service).
Members onlyNon-members can’t book this service at all.
Discount percentA percentage off the service price, e.g. 20.
Discount amountA flat amount off, in the site currency.
EnabledTurn the perk off without deleting it.

A perk can be access-only (members-only, no discount), discount-only, or both.

How the price is worked out

Percentage first, then the flat amount, floored at zero:

price = service price
price = price - (price × discountPercent / 100)
price = price - discountAmount
price = max(0, price)

So “20% off, then $5 off” reads the way it is written. A discount larger than the price means free, never a refund.

Overlapping plans

If a member holds two plans that both discount the same service, the perk leaving the lowest resulting price wins. Holding an extra membership is never a penalty. Access-only perks qualify a member for entry but don’t compete on price.

The bit that matters: members are charged less, not shown less

The discount is applied from Stub’s EVENT_BEFORE_SAVE_BOOKING event, which fires before the booking element is saved and before Stub derives the Stripe payment amount from the booking price. So the adjusted figure is what actually reaches Stripe.

Members-only works the same way. Rather than merely hiding the service from the booking form — which a hand-crafted POST would walk straight past — access is checked when the booking is created, and a non-member’s booking is refused with a validation error on the service field. The booking form surfaces it like any other error.

Who counts as a member

Stub’s customers are their own records and only optionally linked to a Craft user, so a booker is resolved to a Craft user in two steps:

  1. If the Stub customer is linked to a Craft user, that’s the user.
  2. Otherwise, match on email address — someone who books as a guest using the same address their membership is under is still that member.

Their active Headcount subscriptions then determine which plans they hold. A booker with no matching user, or with no active subscription, is treated as a non-member.

Worked example

A yoga studio sells a Studio Member plan at $49/month. Members get 25% off a 60-minute private session (normally $80) and exclusive access to small-group workshops.

  • Perk 1 — plan: Studio Member, target: 60-minute private session, discount percent: 25. Members pay $60; the public pays $80. Stripe charges $60.
  • Perk 2 — plan: Studio Member, target: Small-group workshop, members only: on. Non-members are refused at booking time, even if they get to the form.

Roadmap

The targetType column is namespaced (stub:service) precisely so more things can slot in without a schema change. Owl event tickets are the next target — member pricing on tickets, and members-only events.