DEV Community

GraceSoft
GraceSoft

Posted on

Day 2 — Wiring Billing Into the Story (and Bringing the UI Back)

Today started deep in the backend… and ended with the UI finally catching up.


The Problem

GraceSoft Story started as a way to turn commits into timelines.

But timelines alone aren’t a product.

To make this real, I needed:

  • billing
  • plans
  • feature access control

Without turning the codebase into a mess of if (plan === 'pro').


Step 1 — Plans Shouldn’t Live in Code

First thing I removed:

refactor(plans): remove implicit plan seeding
Enter fullscreen mode Exit fullscreen mode

Plans don’t belong in migrations or seeders.

They belong in Stripe.

So instead of defining plans internally, I made Stripe the source of truth.


Step 2 — Sync Stripe → App

feat(stripe): store product details when syncing catalog
Enter fullscreen mode Exit fullscreen mode

Now products and pricing live locally after sync.

This unlocks:

  • fast queries
  • feature mapping
  • no runtime dependency on Stripe API

Step 3 — Subscription Modeling

feat(billing): integrate account-plan-subscription schemas
Enter fullscreen mode Exit fullscreen mode

I avoided the typical shortcut (user.plan).

Instead:

  • accounts
  • plans
  • subscriptions

This keeps things flexible for:

  • teams
  • shared billing
  • multiple repos per account

Step 4 — The Stripe Mapping Problem

fix(stripe): persist subscriptions using metadata tier mapping
Enter fullscreen mode Exit fullscreen mode

Stripe doesn’t know your app’s logic.

So I used metadata to map:

  • Stripe products → internal tiers

This avoids brittle assumptions like name matching.


Step 5 — Feature Gating as Data

feat(plans): add pricing-gated feature schema
feat(stripe): refresh plan gates from catalog tier updates
Enter fullscreen mode Exit fullscreen mode

Instead of hardcoding:

if ($user->plan === 'pro')
Enter fullscreen mode Exit fullscreen mode

I now have:

  • feature definitions
  • tied to plans
  • dynamically synced

So the system can answer:

“Does this account have access to this feature?”


Step 6 — Event-Driven Billing

feat(api): add stripe and postmark webhook endpoints
Enter fullscreen mode Exit fullscreen mode

Billing state is now:

  • async
  • event-driven
  • consistent

No more relying on user-triggered updates.


Step 7 — Document Before It Gets Messy

docs(readme): replace template with project documentation
Enter fullscreen mode Exit fullscreen mode

Captured:

  • architecture
  • billing flow
  • schema decisions

Step 8 — Design Direction First

Before jumping into UI, I set the tone:

chore(ui): add design inspiration
Enter fullscreen mode Exit fullscreen mode

Leaning towards:

  • pre-glass macOS feel
  • structured, calm interfaces
  • narrative-first layout

Step 9 — UI Catches Up

feat(ui): implement app shell and redesign core story pages
Enter fullscreen mode Exit fullscreen mode

Finally:

  • app shell in place
  • story pages redesigned
  • layout aligns with the “timeline as narrative” idea

What Changed Today

  • Billing is no longer an afterthought
  • Plans are externalized
  • Features are structured data
  • UI is catching up to the system

More importantly:

The system now understands who gets to see what part of the story.


Closing Thought

Today wasn’t about flashy features.

It was about removing shortcuts.

Because the moment you introduce billing,
you’re forced to answer:

  • What is valuable?
  • What is restricted?
  • What is part of the story?

Tomorrow:

  • enforce feature gates across UI + API
  • connect usage → pricing
  • refine the UX around locked features

If you're building something similar,
how are you structuring feature gating without polluting your domain logic?

Top comments (0)