logoPay4SaaS
消费管理

访问控制

访问控制解决的问题是:用户付完钱了,怎么判断他有没有权限使用你的服务?

Pay4SaaS 把这套逻辑封装成了 useAccess Hook 和 useService 方法,前端直接调用即可。

首页 Hero 区(components/landing/Hero.tsx)有完整的前端调用示例,包括权限判断、消费积分、赠送积分三种场景。

useAccess Hook

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

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

  if (loading) return <div>加载中...</div>

  if (!status.allowed) {
    return <div>请先订阅或购买积分</div>
  }

  return <div>欢迎使用!</div>
}

status 对象

status 包含完整的用户访问信息:

{
  allowed: boolean,          // 是否有权限使用
  accessType: AccessType,    // 权限类型
  details: {
    hasSubscription: boolean,        // 是否有订阅
    subscriptionPlan: string,        // 订阅计划(basic/pro/max)
    subscriptionStatus: string,      // 订阅状态
    subscriptionBillingCycle: string, // 计费周期(monthly/yearly)
    subscriptionEndDate: string,     // 到期时间
    subscriptionProvider: string,    // 支付方式
    hasLifetime: boolean,            // 是否为买断用户
    lifetimePlan: string,            // 买断计划
    availableCredits: number,        // 可用积分
    quota: {                         // 配额信息(仅配额订阅模式)
      monthlyLimit: number,
      used: number,
      remaining: number,
      resetDate: string
    },
    isUnlimited: boolean,           // 是否无限使用
    hasUsedTrial: boolean,          // 是否已使用过试用
  }
}

accessType 值

说明
subscription_unlimited用户通过无限订阅获得权限
subscription_quota用户通过配额订阅获得权限
lifetime用户通过买断获得权限
credits用户通过积分获得权限
none无权限

useService —— 消费一次服务

当用户点击"生成"、"使用"等按钮时,调用 useService 来记录一次消费:

const { useService } = useAccess()

async function handleGenerate() {
  const result = await useService(
    'article_generation',    // 服务类型(自定义字符串)
    1,                       // 消费数量(可选,默认 1)
    '生成了一篇博客文章',       // 描述(可选)
    'article-123'            // 关联 ID(可选)
  )

  if (result.success) {
    // 消费成功,继续业务逻辑
    console.log('剩余积分:', result.remainingCredits)
  } else {
    // 消费失败
    console.log('错误:', result.message)
  }
}

useService 的返回值:

{
  success: boolean,
  accessType: AccessType,     // 实际使用的权限类型
  remainingCredits: number,   // 剩余积分
  message: string,            // 提示信息
  error?: string              // 错误码
}

四种模式下的行为差异

积分模式(credits)

  • allowed = 积分 > 0
  • useService 扣 1 个积分
  • 积分用完 allowed 变为 false

无限订阅(subscription_unlimited)

  • allowed = 有活跃订阅(包括试用期)
  • useService 只记录用量,不扣积分
  • isUnlimited = true

配额订阅(subscription_quota)

  • allowed = 有配额剩余 有可用积分
  • useService 按优先级扣减:配额 → 赠送积分 → 购买积分
  • 配额每月自动重置

买断制(lifetime)

  • allowed = 有买断记录
  • useService 只记录用量,不扣积分
  • isUnlimited = true

前端 vs 后端 对照

判断权限

前端页面:

const { status } = useAccess()

if (!status.allowed) {
  return <div>请先订阅或购买积分</div>
}

后端 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 })
}

消费服务(判断权限 + 扣积分)

前端页面:

const { useService } = useAccess()

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

if (result.success) {
  console.log('剩余积分:', result.remainingCredits)
}

后端 API Route / Server Action:

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

const result = await fastConsumeService(
  userId,
  1,                    // 消费数量
  'article_generation', // 服务类型
  '生成文章',            // 描述(可选)
  relatedId             // 关联 ID(可选)
)

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

fastConsumeService 是优化过的快速路径,直接通过数据库 RPC 原子扣减积分/配额,性能更好。

On this page