---
title: "Why a 47-Line Architecture Doc Outlived Our Pricing Prototype"
canonical: https://dxdev.com/blog/pricing-real-when-gates-shipped/
datePublished: 2026-04-10
---
On April 10, a ticket started as a prototype problem. Event pricing for the production app: how should a participant group registration add cost to an event? We figured the design work was the hard part and the implementation would follow. By end of day the design was the only part that had gone to plan.

We ended up pushing that model through hybrid checkout, feature gates, proration math, unpaid-state handling, and architecture docs spread across multiple separate codebases. The prototype had been easy.

## Two layers

Our first instinct for event pricing was flat per-group pricing. Each participant group that enters pays X dollars. Clean, predictable, easy to explain to an event 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 the production app already sells.

Every ongoing activity and every event on the platform is run by an organization that already has a membership tier, and that tier controls what the organization can do. Flat per-group pricing on top of that means the software holds two separate concepts of "what this org paid for" side by side, with no formal relationship between them.

The model we selected used two layers. The base layer remained the existing membership, with its price and access surface. The second layer was a bundle ceiling: an event could define how many participant group registrations were included in the membership cost, with additional registrations priced separately.

That structure fit because it connected the existing membership model to the way event capacity was configured. It was a product decision for this platform, and I wouldn't call it a universal pricing pattern.

## The prototype was allowed to ignore proration

Hybrid checkout is where the prototype lied to us about what was hard.

The production app has a membership billing system. It is old, and it works. An [event cart](/blog/reuse-story-died/) is a separate concept: a one-time purchase tied to a specific event, with no recurring membership cycle behind it. When an event director registers for an event mid-billing-cycle, checkout has to produce one coherent total that includes any prorated membership adjustment and the event registration cost together.

The prototype got to assume a clean billing boundary. The real implementation did not.

The arithmetic was not the central difficulty. The hard part was applying the chosen proration policy through the legacy billing path, producing a line item the event cart could consume, and avoiding an inconsistent charge when the organization had already settled part of the period through another session.

That last constraint did not exist in the prototype. It showed up the moment we wired hybrid checkout into an actual test org that had paid last month and was renewing next week. We had not thought about it until then.

## Where the gates actually live

Feature gates in the production app are not a single table. The surface we were wiring into is a feature-access resolver, a function that returns a bitmask of capabilities for a given org-event pair. Whether the org can create rounds, publish a participant list page, or use the event cart at all, all of it flows through this one surface.

Wiring event pricing into the resolver took most of an afternoon. The code was not hard, but the unlocks catalog had not been designed for bundle-ceiling semantics. The minimum-package gating pattern already used for a few other feature tiers assumes a fixed threshold: this feature is available at membership tier 3 and above. A bundle ceiling is not a threshold. It is a quantity that varies per event configuration.

So the unlock logic had to learn a new pattern: does this event have a ceiling defined, and if so, how many slots remain. No gate that existed before asked that question. Getting it into the catalog cleanly, without special-casing the resolver for this one pricing mode, added a day we had not planned for.

## The work that was not in the prototype

Everything above was architecture. What follows was product.

For this product, an overdue account required a hard registration lock rather than a dismissible warning. That was a business-rule decision. A bookmark could bypass the context of an earlier notice, so the registration flow itself needed to enforce the account-state rule.

Then there were pre-cap warnings. As an organization approached its bundle ceiling, the event cart needed to show that context before the organization committed. The shopping-cart model had no existing ceiling concept, so the warning needed an informational representation that would not be mistaken for a transactional invoice line.

Last was downgrade behavior with preserved excess credit. If an org downgrades its 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 meant touching the membership billing path in a way we 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 multiple separate codebases because development is split 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 at once, and each has its own branch cut point.

We wrote the architecture document because we will not remember why bundle ceiling won over flat per-group. In three months, when an event director asks for a credits model, the document is the only thing that will stop us from re-litigating the entire choice from scratch.

It 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. Forty-seven lines is enough to reconstruct the reasoning, and not a line more. It is a decision record.

A pricing model becomes credible only after its assumptions have been checked against the real checkout path, the entitlement system, and the account states the product actually supports. With one org, one event and one happy path, our prototype hid mid-cycle checkout, overdue hard locks and downgrade-with-credit.
