logoPay4SaaS
消费管理

积分消费

积分系统用于 积分模式配额订阅模式。如果你的产品只使用无限订阅或买断制,可以跳过这一页。

积分如何工作

Pay4SaaS 支持购买积分、月度配额和赠送活动。后端负责决定这些积分如何合并、如何扣减,所以前端通常只需要展示可用余额,并调用模板提供的消费接口。

当用户执行付费动作时,Pay4SaaS 会优先使用最适合当前场景的可用积分,并把购买积分尽量留到最后。如果用户没有可用积分,付费动作会被拒绝。

useCredits Hook

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

function CreditsDisplay() {
  const { balance, loading, refreshBalance, useCredit } = useCredits()

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

  return (
    <div>
      <p>可用积分: {balance.availableCredits}</p>
      <p>已使用: {balance.usedCredits}</p>
    </div>
  )
}

余额字段

大多数页面展示 availableCredits 就够了。如果需要展示用量,可以加上 usedCredits。除非你的产品明确需要拆分展示,否则不要围绕内部积分来源设计公开 UI。

消费积分

const { useCredit } = useCredits()

async function handleUse() {
  const result = await useCredit(
    'image_generation',      // 服务类型
    '生成了一张图片'            // 描述,可选
  )

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

useCredituseService 走的是同一套后端消费流程。积分展示页面可以用 useCredit,具体付费功能页面更适合用 useService,因为它会同时刷新访问状态。

扣减数量由服务端 config/payment.ts 中的 SERVICE_COSTS 决定。不要从前端传递可信价格或积分成本。

刷新余额

积分购买成功、领取赠送积分成功,或者页面切换后需要最新余额时,调用 refreshBalance()

const { refreshBalance } = useCredits()

await refreshBalance()

如果只是为了让已成功的本地动作马上反馈到界面,也可以做乐观更新:

const { addCredits } = useCredits()

addCredits(50)

乐观更新只用于显示。真正的访问判断和扣减仍然以服务端为准。

服务端积分操作

自定义 API route 或 server action 时,使用支付模块提供的 helper,不要直接写数据库表:

import {
  grantPurchasedCreditsToUser,
  addBonusCredits,
  refreshUserCreditBalance,
} from '@/lib/payment/credits'
import { getBonusExpiryDate } from '@/config/payment'

await grantPurchasedCreditsToUser(userId, 50)

const grant = await addBonusCredits(userId, 10, getBonusExpiryDate(30))
if (!grant.success) {
  console.log(grant.error)
}

const balance = await refreshUserCreditBalance(userId)

这些 helper 会保持积分发放、过期和余额刷新逻辑与模板其他地方一致。

赠送积分

赠送积分适合用于注册奖励、活动赠送、订阅附赠和客服补偿。

Demo 里的赠送按钮只用于交付预览。生产环境中,赠送积分应该来自明确的业务规则,例如一次性领取活动、注册奖励、支付事件、管理后台或定时任务。

领取活动

config/payment.ts 中配置用户可领取的赠送活动:

export const BONUS_CAMPAIGNS = {
  hero_welcome_bonus: {
    amount: 10,
    expiresInDays: 7,
    oncePerUser: true,
    enabled: true,
  },
}

前端只发送活动 key。赠送数量、过期时间和防重复领取规则都由后端决定:

const { claimBonus } = useAccess()

const res = await claimBonus('hero_welcome_bonus')

手动赠送

后端主动赠送时,在 API route 或 server action 中调用 addBonusCredits()

import { addBonusCredits } from '@/lib/payment/credits'
import { getBonusExpiryDate } from '@/config/payment'

await addBonusCredits(userId, 20, getBonusExpiryDate(30))
await addBonusCredits(userId, 50, getBonusExpiryDate(7), 'campaign')

扣减安全

积分扣减在后端原子完成。并发请求不会重复消费同一份余额,每次成功扣减也会记录到用量历史里。

文档首页

回到完整实施目录。

价格方案

查看订阅、积分和终身买断方案。

博客

阅读更多 SaaS 支付和增长经验。

On this page