Payment Authorization Tutorial

Learn how to restrict access to features based on subscription plans using the planAuth middleware.

Overview

ZapStart comes with a powerful subscription-based access control system that allows you to limit features based on a user's plan. This tutorial explains how to use the planAuth middleware to create paywalled features and manage user access to different subscription tiers.

The previous tutorial was about user authentication and authorization (how to protect routes from unauthenticated users), this tutorial is about how to protect routes from users with the wrong plan.

Made for Monetization

The payment authorization system is designed to help you monetize your SaaS by creating tiered access to features.

Plan-Based Access [Frontend]

Check if a user has access to your product, using the user.plan.hasAccess property, the has access property is not restricted to the paid plans, it can be used to check if the user has access to any plan, whether free or paid, because you may don't want to the user to have access to a free product unless they click on (try for free) button for example, and this is automatically handled for you by default in the boilerplate:

Example of using the hasAccess property:
import { useAuth } from "@/context/AuthContext";

function DashboardPage() {
  const { user } = useAuth();
  
  // Check if user has access to this feature
  const hasAccess = user?.plan?.hasAccess;

  if (!user) {
    return router.push('/login');
  }

  return (
    <div>
      {hasAccess ? (
        <div>Dashboard Page Content</div>
    ) : (
        <div>Choose a plan first to get access to the product</div>
      )}
    </div>
  );
}
Example of checking plan type/name currentPlanName:
import { useAuth } from "@/context/AuthContext";

function DashboardPage() {
  const { user } = useAuth();

  // Check if user has access to this feature
  const hasAccess = user?.plan?.hasAccess;
  const isPro = user?.plan?.currentPlanName === 'Pro';

  if (isPro) {
    // User has Pro plan access
    return <div>Pro Plan Content</div>
  } else {
    // User does not have Pro plan access
    return <div>Choose a plan first to get access to the product</div>
  }
}

Using planAuth Middleware [Backend]

The planAuth middleware restricts API access based on a user's subscription level. It's used in combination with the authentication middleware to protect premium features.

Implementation Example

// In your route file
const express = require('express');
const router = express.Router();
const authenticateJWT = require('../middleware/auth');
const planAuth = require('../middleware/planAuth');
const productController = require('../controllers/productController');

// Feature available to all authenticated users (Free and Pro plans)
router.post('/counter/free', 
  authenticateJWT,                 // First verify user is logged in
  planAuth(['Free', 'Pro']),       // Then verify user has appropriate plan (free and pro users can access this feature)
  productController.handleFreeCounter
);

// Premium feature restricted to paid users only
router.post('/counter/pro', 
  authenticateJWT,                 // First verify user is logged in
  planAuth(['Pro']),               // Only Pro users can access this feature
  productController.handleProCounter
);

The middleware ensures that only users with the specified plan can access the route. If a user with a different plan attempts to access it, they will receive a 403 Forbidden response.

One-time vs Subscription Payment

ZapStart supports both one-time payments and recurring subscriptions through configuration.

One-time Payments

Set oneTime: true in your configurations to enable one-time payments. One-time payments are simpler for customers to pay and ideal for digital products or lifetime access, you can start with one-time payments proudcts and then switch to subscriptions later.

Subscription Payments

Set oneTime: false in configurations to enable recurring billing through Stripe. Better for SaaS products with ongoing costs.

Managing User Access

When a user makes a payment, you'll need to update their plan information in the database. The webhook handler in the subscription controller is where this happens automatically.

// This code is simplified for demonstration
// Real implementation is in your subscriptionController.js

// When a successful payment is detected in the webhook:
user.plan = {
  ...user.plan,
  currentPlanName: plan.name,  // e.g., 'Pro'
  priceId: plan.priceId,       // Stripe price ID
  hasAccess: true,             // Grant access
  // Other plan details...
};
Check the backend/src/models/userModel.js to know what properties are critical to update when changing or updating user plan.

Adding a new plan

To add a new plan, you need to make a few updates:

  • First add the plan to the backend/src/models/userModel.js file in the currentPlanName array.
  • Then add the plan to the backend/configBack.js file in the plans array with its name and priceId.
  • Then add the plan to the frontend/configFront.js file in the stripe.plans array with its priceId.
  • Create features in the backend and limit access to the new plan using the planAuth middleware. Also make changes in the frontend code to use the new plan.
  • If you have more than one paid plan, you may need to handle the plan upgrade (switching between plans) in the frontend and backend. Becuase each plan according to stripe will be treated as a new product and upgrading to a new plan should mean cancelling the old plan and creating a new one, but this won't be handled automatically and that may cause issues. In other words each plan will trigger webhooks and cause unexpected behavior.

    Start simple to speed up your product launch, and then add more plans later if needed.

Best Practices

  • Require Authentication First: Always use authenticateJWT before planAuth to ensure the user is logged in before checking their subscription status.
  • Start Simple: Consider beginning with one-time payments which are simpler to implement and manage. This works well for digital products, courses, or tools that don't have significant ongoing server costs.
  • Clear Upgrades: Provide clear upgrade paths when users hit paywall restrictions.
  • Graceful Failures: When users try to access premium features without the right plan, show helpful messages rather than generic errors.
  • Test Payment Flows: Thoroughly test the complete payment and access control flow using Stripe's test mode.

With ZapStart's payment system, you can have subscription or one-time payment features up and running in minutes instead of weeks. Let us see your app in action!

Use The Documentation

All payment-related files in ZapStart are well-documented with comments explaining their purpose and usage. Refer to these files when implementing your payment system.