The Product/Price Migration We’d Repeat (and What It Buys You Over a Billing Config File)
A code-level walkthrough of our product/price split, how it compares to MakerKit and Supastarter’s config catalogs, and why rows and pins hold up better once pricing, packaging, and entitlements all have to move.
FAQ
- Do I need to redeploy to change pricing or packaging?
Not for most catalog work. Packages, scopes, limits, and trial flags are edited in Super Admin. Changing a list price still means a new
pricesrow (Stripe immutability), not editing a number in place, and there isn’t a one-click “rotate price” flow in the UI yet.- How do you rotate prices in Refract?
Add a new
pricesrow (new Stripe Price), promote it as the default, retire the old one. Pinnedsubscriptions.price_idkeeps existing customers on the old rate. Product vs Price.- How is this different from MakerKit or Supastarter?
Both document a TypeScript payments config you redeploy. Neither enforces one active default price in the database. Grandfathering is usually a
hiddenflag on a product or plan in config, not a per-subscription row pin. Supastarter supports multiple currencies per plan in config; MakerKit’s examples often put currency on the product. We keep the catalog in Postgres with constraints and optionalprice_idpins. See the comparison table in the article (vendor docs change).- Can Refract do regional pricing or monthly vs annual on one plan?
The model supports sibling
pricesrows under one product (currency, interval, metadata on each row). We haven’t filled out a big multi-currency catalog yet; the insert path is what we shipped for.- Why is usage-based billing still one database row if Stripe has two prices?
Base Stripe price id on the row; metered id in
prices.metadata.externalUsageId. Subscription creation expands to both. One commercial SKU in admin; we may split rows later if we need to grandfather base and meter separately.- Does Refract include A/B pricing or price experiments?
The schema can hold multiple live prices and pin cohorts via
price_id. We have not shipped assignment, traffic splitting, or experiment analytics. That’s on you on top of the primitives.- How is this article different from “The Pricing Capabilities You'll Need Before You Think You Need Them”?
That one is when pricing capabilities matter. This one is what we built, how it compares to two common boilerplates, and which implementation choices we rejected.
- If someone edits prices in the Stripe Dashboard, does Refract sync?
No catalog sync from Stripe product/price webhooks. Postgres is the catalog source of truth; webhooks reconcile subscription line items against ids we already know, including retired prices.