logoPay4SaaS
Consumption

Access Control

Access control answers the question: After a user has paid, how do you determine whether they have permission to use your service?

Pay4SaaS encapsulates this logic into the useAccess Hook and the useService method — just call them from the frontend.

The Hero section on the homepage (components/landing/Hero.tsx) contains complete frontend examples, including permission checks, consuming credits, and granting bonus credits.

useAccess Hook

import { useAccess } from '@/hooks/useAccess'

function MyComponent() {
  const { status, loading, error, refreshStatus, useService } = useAccess()

  if (loading) return <div>Loading...</div>

  if (!status.allowed) {
    return <div>Please subscribe or purchase credits first</div>
  }

  return <div>Welcome!</div>
}

status Object

status contains the complete user access information:

{
  allowed: boolean,          // Whether the user has access
  accessType: AccessType,    // Access type
  details: {
    hasSubscription: boolean,        // Whether the user has a subscription
    subscriptionPlan: string,        // Subscription plan (basic/pro/max)
    subscriptionStatus: string,      // Subscription status
    subscriptionBillingCycle: string, // Billing cycle (monthly/yearly)
    subscriptionEndDate: string,     // Expiry date
    subscriptionProvider: string,    // Payment provider
    hasLifetime: boolean,            // Whether the user has lifetime access
    lifetimePlan: string,            // Lifetime plan
    availableCredits: number,        // Available credits
    quota: {                         // Quota info (subscription_quota mode only)
      monthlyLimit: number,
      used: number,
      remaining: number,
      resetDate: string
    },
    isUnlimited: boolean,           // Whether usage is unlimited
    hasUsedTrial: boolean,          // Whether trial has been used
  }
}

accessType Values

ValueDescription
subscription_unlimitedAccess via unlimited subscription
subscription_quotaAccess via quota subscription
lifetimeAccess via lifetime purchase
creditsAccess via credits
noneNo access

useService — Consume One Service Unit

When a user clicks "Generate", "Use", etc., call useService to record one consumption:

const { useService } = useAccess()

async function handleGenerate() {
  const result = await useService(
    'article_generation',    // Service type (custom string)
    1,                       // Consumption amount (optional, default 1)
    'Generated a blog post', // Description (optional)
    'article-123'            // Related ID (optional)
  )

  if (result.success) {
    // Consumption successful, continue with business logic
    console.log('Remaining credits:', result.remainingCredits)
  } else {
    // Consumption failed
    console.log('Error:', result.message)
  }
}

useService return value:

{
  success: boolean,
  accessType: AccessType,     // The actual access type used
  remainingCredits: number,   // Remaining credits
  message: string,            // Status message
  error?: string              // Error code
}

Behavior Differences Across Four Modes

Credits Mode

  • allowed = credits > 0
  • useService deducts 1 credit
  • When credits run out, allowed becomes false

Unlimited Subscription (subscription_unlimited)

  • allowed = has an active subscription (including trial period)
  • useService only logs usage, does not deduct credits
  • isUnlimited = true

Quota Subscription (subscription_quota)

  • allowed = has remaining quota or has available credits
  • useService deducts in priority order: quota → bonus credits → purchased credits
  • Quota resets monthly

Lifetime

  • allowed = has a lifetime purchase record
  • useService only logs usage, does not deduct credits
  • isUnlimited = true

Frontend vs Backend Comparison

Check Access

Frontend page:

const { status } = useAccess()

if (!status.allowed) {
  return <div>Please subscribe or purchase credits first</div>
}

Backend API Route / Server Action:

import { checkUserAccess } from '@/lib/payment/access'

const access = await checkUserAccess(userId)

if (!access.allowed) {
  return Response.json({ error: 'No access' }, { status: 403 })
}

Consume Service (Check Access + Deduct Credits)

Frontend page:

const { useService } = useAccess()

const result = await useService('article_generation', 1)

if (result.success) {
  console.log('Remaining credits:', result.remainingCredits)
}

Backend API Route / Server Action:

import { fastConsumeService } from '@/lib/payment/access'

const result = await fastConsumeService(
  userId,
  1,                    // Consumption amount
  'article_generation', // Service type
  'Generate article',   // Description (optional)
  relatedId             // Related ID (optional)
)

if (!result.success) {
  return Response.json({ error: result.message }, { status: 403 })
}

fastConsumeService is an optimized fast path that atomically deducts credits/quota directly via a database RPC for better performance.

On this page