Access Control
Access control answers one practical question: can this user use this paid feature right now?
Pay4SaaS gives you two tools:
- General access with
useAccess()andcheckUserAccess() - Plan-level feature gates with
useTierAccess()andhasTierAccess()
The internal rules differ by pricing model, but most products only need the examples below.
Pricing Modes
| Mode | General access means | Consumption behavior |
|---|---|---|
| Credits | The user has usable credits | Deducts credits by SERVICE_COSTS |
| Unlimited subscription | The user has valid subscription access | No credit deduction |
| Quota subscription | The user has subscription quota or usable credits | Deducts quota or credits by SERVICE_COSTS |
| Lifetime | The user has a lifetime purchase | No credit deduction |
Frontend Check
Use useAccess() when you need to show or hide paid UI.
import { useAccess } from '@/hooks/useAccess'
function PaidPanel() {
const { status, loading } = useAccess()
if (loading) return <div>Loading...</div>
if (!status.allowed) {
return <a href="/pricing">Upgrade to continue</a>
}
return <div>Your paid feature</div>
}status.allowed is a general yes/no result. If you need display values, use the fields that match your pricing model, such as status.details.availableCredits, status.details.hasSubscription, or status.details.hasLifetime.
Frontend checks are for UX only. Always protect paid work on the server too.
Server Check
Use checkUserAccess() when an API route only needs to know whether the user can access a paid feature.
import { checkUserAccess } from '@/lib/payment/access'
import { getAuthUserId } from '@/lib/auth'
export async function POST() {
const userId = await getAuthUserId()
if (!userId) {
return Response.json({ error: 'Unauthorized' }, { status: 401 })
}
const access = await checkUserAccess(userId)
if (!access.allowed) {
return Response.json({ error: 'No access' }, { status: 403 })
}
return Response.json({ ok: true })
}Consuming a Paid Service
For usage-based features such as generation, analysis, export, or API calls, call useService() from the frontend or fastConsumeService() on the server. This checks access and records usage in one flow.
import { useAccess } from '@/hooks/useAccess'
function GenerateButton() {
const { useService } = useAccess()
async function handleGenerate() {
const result = await useService('article_generation')
if (!result.success) {
alert(result.message)
return
}
// Run your feature after access is confirmed.
}
return <button onClick={handleGenerate}>Generate</button>
}The client only sends a serviceType. The actual cost comes from SERVICE_COSTS in config/payment.ts, so users cannot change the deduction amount from the browser.
// config/payment.ts
export const SERVICE_COSTS: Record<string, number> = {
'demo-consume': 25,
'article_generation': 10,
}Backend Consumption
import { fastConsumeService } from '@/lib/payment/access'
import { SERVICE_COSTS } from '@/config/payment'
const serviceType = 'article_generation'
const amount = SERVICE_COSTS[serviceType]
const result = await fastConsumeService(userId, amount, serviceType)
if (!result.success) {
return Response.json({ error: result.message }, { status: 403 })
}Plan-Level Gates
Use tier access when a feature requires a specific subscription plan, such as Pro-only exports or Max-only analytics.
import { useTierAccess } from '@/hooks/useTierAccess'
function ProFeatureButton() {
const canUsePro = useTierAccess('pro')
if (!canUsePro) {
return <a href="/pricing?plan=pro">Upgrade to Pro</a>
}
return <button>Use Pro feature</button>
}Server-side equivalent:
import { hasTierAccess } from '@/lib/payment/access'
if (!await hasTierAccess(userId, 'pro')) {
return Response.json({ error: 'tier_required', required: 'pro' }, { status: 403 })
}Use status.allowed for general access. Use useTierAccess() or hasTierAccess() when the feature requires a minimum subscription tier.
Docs home
Return to the full implementation guide.
Pricing
Review subscriptions, credits, and lifetime options.
Blog
Read more notes on SaaS payments and growth.