DEV Community

Cover image for From Side Project to Paid SaaS, Adding Subscriptions & Usage Tiers to my AI App
Shola Jegede
Shola Jegede Subscriber

Posted on • Edited on

From Side Project to Paid SaaS, Adding Subscriptions & Usage Tiers to my AI App

In Part 2, we gave Learnflow AI a solid foundation — authentication, protected routes, and credit-based usage.

Now comes the most important phase for sustainability: turning Learnflow AI into a real product.

This means:

  • Pricing tiers
  • Usage caps per plan
  • Upgrade flows
  • Real billing integration

Kinde Billing + Convex gave me the stack to launch subscriptions in hours, not days — and tightly tied into the infrastructure I’d already built.

Why Subscriptions?

Talking to GPT-4 with your voice is magical — but also expensive.

Every user session involves real-time audio, transcription, and GPT-4 tokens. If I wanted to avoid burning cash at scale, I needed a billing model that made sense.

Here’s what was non-negotiable:

  • Stripe-powered payments
  • Tiers with clear limits
  • Upgrade/downgrade support
  • Backend feature gating

Kinde Billing delivered all of that — fully integrated with the existing auth layer from Part 2. No custom Stripe server. No fragile webhooks. Just config, plans, and secure checkout.

Learnflow AI’s Pricing Model

For launch, I kept pricing simple:

Tier Credits Access
Free 10 Limited voice sessions
Pro 100+ Unlimited GPT sessions, faster voice latency

Every logged-in user already had a credits field and plan in Convex. Now I just needed to plug billing on top.

Step 1: Enable Billing in Kinde

Kinde handles Stripe on your behalf. Inside the Kinde dashboard:

  1. Go to "Billing" → click Enable billing
  2. Connect your Stripe account
  3. Define your plans (free, pro)
  4. Add pricing table keys and plan slugs

This gives you:

  • Hosted checkout pages
  • Subscription syncing
  • Webhook support

You don’t have to spin up a Stripe backend — it’s all managed by Kinde.

You can read this post for a complete walkthrough on setting this up.

Step 2: Access Billing Info in User Sessions

Once a user logs in, Kinde injects their billing tier directly into their session. You can fetch it like this:

import { getKindeServerSession } from "@kinde-oss/kinde-auth-nextjs/server";

export async function getCurrentUser() {
  const { getUser } = getKindeServerSession();
  const user = await getUser();

  return {
    email: user?.email ?? "",
    name: user?.given_name ?? "",
    tier: user?.user_metadata?.plan || "free",
  };
}
Enter fullscreen mode Exit fullscreen mode

You now have access to a user’s billing tier (free, pro, etc.) in any page, layout, or server action.

Step 3: Sync Plan into Convex

If you want full control or offline logic, store the billing tier in Convex too.

export const syncUserPlan = mutation({
  args: {
    email: v.string(),
    plan: v.string(),
  },
  handler: async (ctx, args) => {
    const user = await ctx.db
      .query("users")
      .filter((q) => q.eq(q.field("email"), args.email))
      .unique();

    if (!user) return null;

    return await ctx.db.patch(user._id, { plan: args.plan });
  },
});
Enter fullscreen mode Exit fullscreen mode

You can call this during login, onboarding, or even inside a webhook handler.

Step 4: Add an Upgrade Page with Billing Portal Access

Once your users hit their usage limit, you want them to see a clear path to upgrade — not just a “you’re out of credits” error.

To support this, we created a dedicated upgrade screen. Kinde makes this possible with a hosted billing portal, powered by Stripe and tied to the user’s existing Kinde account.

Here’s how we implemented it.

"use client";

import { PortalLink } from "@kinde-oss/kinde-auth-react";

export default function UpgradePage() {
  return (
    <div className="p-6">
      <h1 className="text-2xl font-bold mb-4">Upgrade your plan</h1>
      <p className="mb-6">
        Get more credits and unlock advanced features like voice-first learning and longer sessions.
      </p>
      <PortalLink path="organization_billing">
        <button className="btn btn-primary">Upgrade to Pro</button>
      </PortalLink>
    </div>
  );
}
Enter fullscreen mode Exit fullscreen mode

This redirects the user to a Kinde-hosted portal under the hood.

UX Tip: Show This Prompt When Users Hit a Limit

If a user runs out of credits or tries to access a Pro-only feature, use a helpful banner or modal to direct them here:

<div className="p-4 bg-yellow-50 text-yellow-700 text-sm rounded">
  You’ve used all your credits. Upgrade to continue learning.
  <Link href="/dashboard/upgrade" className="underline ml-2">
    Upgrade now →
  </Link>
</div>
Enter fullscreen mode Exit fullscreen mode

This kind of prompt makes it easy to convert trial users into paying customers at the moment of friction — without needing a separate email flow or reminder.

Once enabled, users can:

  • View or change their billing plan
  • See usage and payment history
  • Cancel or resume subscriptions
  • Trigger plan upgrades in real time (reflected in your app via Kinde session metadata)

And you don’t have to manage any of that yourself — it’s all Stripe + Kinde.

Step 5: Gate Features by Plan (in UI + Server)

Once the plan is available in Convex or your session, gating is easy.

Backend check:

if (user.plan !== "pro" && sessionType === "voice") {
  throw new Error("Upgrade to Pro to use voice sessions.");
}
Enter fullscreen mode Exit fullscreen mode

Frontend lock:

{user?.plan === "free" && (
  <div className="p-4 bg-yellow-50 text-yellow-700 text-sm rounded">
    Voice companions are available on the Pro plan.
    <Link href="/dashboard/upgrade" className="underline ml-2">
      Upgrade now →
    </Link>
  </div>
)}
Enter fullscreen mode Exit fullscreen mode

This helps nudge users without blocking the entire experience.

Step 6: Tier-Specific Credit Handling

Let’s say you want Pro users to have unlimited or discounted usage. You can enforce that per request:

const creditCost = user.plan === "pro" ? 0 : 1;

if (user.credits < creditCost) {
  throw new Error("You’re out of credits. Please upgrade.");
}

await ctx.db.patch(user._id, {
  credits: user.credits - creditCost,
});
Enter fullscreen mode Exit fullscreen mode

You can also:

  • Give monthly credit resets
  • Add bonus credits on upgrade
  • Vary cost per feature

What You’ve Just Built

  • Subscription billing via Kinde + Stripe
  • Checkout page with upgrade logic
  • Tier-based feature locking
  • Flexible credit deductions by plan
  • Convex-powered backend logic

You’ve now got:

  • A real monetization engine
  • A scalable way to support GPT costs
  • The infrastructure to go from hobby → business

What’s Next?

You’re ready to:

  • Onboard your first users
  • Get real usage data
  • Ship a polished V1

With auth, access, usage tracking, and billing — you’re now operating like a real SaaS.

Final Thoughts

It’s never been easier to build real AI-first products.

The combination of:

...makes it possible to go from idea to paid product in 3 weekends.

If you’ve got something valuable, don’t stop at a prototype.

Don’t just ship it. Sell it.

Top comments (0)