Credits & Consumption
Credits are used by Credits mode and Quota Subscription mode. If your product uses Unlimited Subscription or Lifetime mode only, you can skip this page.
How Credits Work
Pay4SaaS supports credits from purchases, monthly quota, and bonus campaigns. The backend decides how credits are combined and deducted, so the frontend only needs to show the available balance and call the provided consumption API.
When a paid action runs, Pay4SaaS uses the most appropriate available credits first and keeps purchased credits for last. If the user has no usable credits left, the paid action is rejected.
useCredits Hook
import { useCredits } from '@/hooks/useCredits'
function CreditsDisplay() {
const { balance, loading, refreshBalance, useCredit } = useCredits()
if (loading) return <div>Loading...</div>
return (
<div>
<p>Available credits: {balance.availableCredits}</p>
<p>Used: {balance.usedCredits}</p>
</div>
)
}Balance Values
Most screens should display availableCredits and, if useful, usedCredits. Avoid designing public UI around internal credit buckets unless your product explicitly needs that breakdown.
Consume Credits
const { useCredit } = useCredits()
async function handleUse() {
const result = await useCredit(
'image_generation', // Service type
'Generated an image' // Description, optional
)
if (result.success) {
console.log('Remaining:', result.remainingCredits)
} else {
console.log(result.message)
}
}useCredit and useService both call the same backend consumption flow. Use useCredit when the screen is mainly about credits, and use useService when the screen is a paid feature that should also refresh access status.
The deduction amount is decided on the server by
SERVICE_COSTSinconfig/payment.ts. Do not send trusted prices or credit costs from the frontend.
Refreshing Balance
After a successful credit purchase, a bonus claim, or a screen transition where you need the latest balance, call refreshBalance():
const { refreshBalance } = useCredits()
await refreshBalance()For instant UI feedback after a known successful local action, you can update the displayed balance optimistically:
const { addCredits } = useCredits()
addCredits(50)Only use optimistic updates for display. The server remains the source of truth for real access and deduction.
Server-Side Credit Operations
For custom API routes or server actions, use the payment helpers instead of writing directly to database tables:
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)These helpers keep credit granting, expiry, and balance refresh behavior consistent with the rest of the template.
Bonus Credits
Bonus credits are suitable for sign-up rewards, promotional campaigns, subscription perks, and manual customer support grants.
The demo bonus button is for delivery preview only. In production, bonus credits should come from an explicit business rule, such as a one-time claim campaign, sign-up reward, payment event, admin panel, or scheduled job.
Claim Campaigns
Configure user-claimable bonus campaigns in config/payment.ts:
export const BONUS_CAMPAIGNS = {
hero_welcome_bonus: {
amount: 10,
expiresInDays: 7,
oncePerUser: true,
enabled: true,
},
}The frontend only sends the campaign key. The backend decides the amount, expiry, and duplicate-claim rule:
const { claimBonus } = useAccess()
const res = await claimBonus('hero_welcome_bonus')Manual Grants
For backend-only grants, call addBonusCredits() from an API route or server action:
import { addBonusCredits } from '@/lib/payment/credits'
import { getBonusExpiryDate } from '@/config/payment'
await addBonusCredits(userId, 20, getBonusExpiryDate(30))
await addBonusCredits(userId, 50, getBonusExpiryDate(7), 'campaign')Deduction Safety
Credit deduction is handled atomically on the backend. Multiple requests cannot spend the same credit balance twice, and every successful deduction is recorded for later usage history.
Docs home
Return to the full implementation guide.
Pricing
Review subscriptions, credits, and lifetime options.
Blog
Read more notes on SaaS payments and growth.