On April 10, ITEM-6684 started as a prototype problem. Tournament event pricing for HomeTeamsONLINE: how should a team registration add cost to a tournament? I figured the design work was the hard part. The implementation would follow.

By the end of the day I had pushed that model through hybrid checkout, feature gates, proration math, unpaid-state handling, and architecture docs spread across four clones (hto, hto2, hto3, hto4). The prototype had been easy. The real thing was something else.

two layers, not one

The first instinct for tournament pricing was flat per-team pricing. Each team that enters pays X dollars. Clean, predictable, easy to explain to a tournament director. The second instinct was credits: pre-purchased blocks that deduct on each registration.

Both lost for the same reason. Neither maps cleanly onto what HomeTeamsONLINE already sells.

Every league and tournament on the platform is run by an organization that already has a membership tier. That membership tier controls what the organization can do. Adding event pricing on top of a flat-per-team model means the software has two separate concepts of “what this org paid for” running side by side, with no formal relationship between them.

The model that survived is two layers. The base layer is the existing membership, with its price and its access surface. The second layer is a bundle ceiling: an event can carry a price cap that defines how many team registrations are included in the membership’s cost. Above that ceiling, additional registrations cost more. Below it, the event is covered.

This is not elegant in the abstract. It is correct in practice, because it maps to what tournament directors actually negotiate. This tier gets up to 32 teams included, more costs extra.

the prototype was allowed to ignore proration

The hybrid checkout piece is where the prototype lied to me about what was hard.

HomeTeamsONLINE has a membership billing system. It is ASP classic, it is old, and it works. A tournament event cart is a separate concept, a one-time purchase tied to a specific event, not a recurring membership cycle. When a tournament director registers for an event mid-billing-cycle, the checkout needs to produce one coherent total that includes any prorated membership adjustment and the event registration cost together.

The prototype was allowed to assume a clean billing boundary. The real implementation was not.

Proration math here is not complicated in isolation. You take the remaining days in the billing period, divide by the total days, apply that ratio to the membership delta. Fifteen lines of arithmetic. The complication is that this math has to run in the legacy billing path, produce a line item the event cart can consume, and not double-charge if the org already settled that billing period in a different session.

That last constraint did not exist in the prototype. It showed up the moment I tried to wire hybrid checkout into an actual test org that had paid last month and was renewing next week.

I had not thought about it until that moment. The prototype had one org, one event, one happy path. The constraint never surfaced.

where the gates actually live

Feature gates in HomeTeamsONLINE are not a single table. The surface I was wiring into is tournamentFeatureAccess, a function that returns a bitmask of capabilities for a given org-tournament pair. Whether the org can create bracket rounds, publish a roster page, or use the event cart at all. All of it flows through this one surface.

Wiring tournament event pricing into tournamentFeatureAccess took most of an afternoon. Not because the code was hard, but because the unlocks catalog had not been designed for bundle-ceiling semantics. minPkg-style gating, the pattern already used for a few other feature tiers, assumes a fixed threshold. This feature is available at membership tier 3 and above. Bundle ceilings are not a threshold. They are a quantity that varies per event configuration.

The unlock logic had to learn a new pattern. Does this event have a ceiling defined, and if so, how many slots remain. That is a different kind of gate than any that existed before. Getting it into the catalog cleanly, without special-casing tournamentFeatureAccess for this one pricing mode, added a day I had not planned for.

the work that was not in the prototype

Everything above was architecture. What follows was product.

Unpaid banners. When an org’s account is overdue, tournament registrations need a hard lock, not a warning. A warning can be dismissed. An overdue hard lock cannot. The distinction matters because tournament directors sometimes navigate to the registration flow from a bookmark that predates the overdue notice, and a dismissible banner disappears before they see the price.

Pre-cap warnings. When an org is within five registrations of their bundle ceiling, the event cart shows a warning before they commit. This does not exist in any other HomeTeamsONLINE checkout flow. The shopping cart metaphor has no ceiling concept. Building the warning meant adding a line item type that is informational, not transactional, and making sure it does not appear on the invoice sent to the tournament director.

Downgrade behavior with preserved excess credit. If an org downgrades their membership tier after registering for an event covered by the higher tier, the registrations already paid do not get clawed back. The excess credit from the event is preserved as a line item balance against the org’s account. Writing that logic required touching the membership billing path in a way I had been deliberately avoiding.

None of this was in the prototype. The prototype had one org, one event, one happy path.

what the architecture doc is for

The implementation spans four clones because HomeTeamsONLINE development is split across hto, hto2, hto3, and hto4 by function. Admin flows live in one place, public registration in another, billing in a third. A feature that touches all three lives in all three clones simultaneously, and each clone has its own branch cut point.

I wrote the architecture document not because I needed to think it through again, but because future me will not remember why bundle ceiling won over flat per-team. In three months, when a tournament director asks for a credits model, the document is the only thing that will stop me from re-litigating the entire choice from scratch.

The document is 47 lines. It covers the two-layer model, the proration contract, the gate semantics, and the three edge cases that shaped the design: mid-cycle checkout, overdue hard locks, and downgrade-with-credit. That is enough to reconstruct the reasoning. It is not a spec. It is a decision record.

A pricing model is not real until it survives your legacy checkout, your feature gates, and the awkward states customers actually get stuck in.