Components
useCredits
Read, check, refresh, and deduct credits from React components.
useCredits() is the main hook for app UI. With no argument, it uses the first
product balance. That is ideal for single-product apps.
Loading...
import { useCredits } from "@/components/neonfin/provider";
import { Button } from "@/components/ui/button";
export function SpendCreditsButton() {
const { balance, deduct, loading } = useCredits();
return (
<Button onClick={() => deduct(10)}>
Use 10 credits
</Button>
);
}Usage
import { useCredits } from "@/components/neonfin/provider";
export function ProcessButton() {
const { balance, creditUnit, hasCredits, deduct, loading } = useCredits();
async function process() {
await deduct(10, { idempotencyKey: "job_123" });
}
return (
<button
// FYI: You probably want to just use the "CreditGate"
// component instead of manually checking hasCredits.
disabled={loading || !hasCredits(10)}
onClick={process}
>
Process - {balance} {creditUnit}
</button>
);
}For multi-product projects, pass a product id:
const images = useCredits("prod_images");
const minutes = useCredits("prod_minutes");Return value
| Field | Type | Description |
|---|---|---|
balance | number | Current balance for the selected product. |
creditUnit | string | null | Unit label, like minutes or images. |
productId | string | null | Resolved product id. |
loading | boolean | True while balances are loading. |
error | string | null | Balance loading error message. |
confirming | boolean | True while checkout started from this page is in progress. |
refresh | () => Promise<void> | Re-fetch balances. |
hasCredits | (amount: number) => boolean | Local balance check. |
deduct | (amount, opts?) => Promise<DeductResult> | Deduct credits and refresh. |
Deduct options
| Option | Type | Description |
|---|---|---|
idempotencyKey | string | Stable key for retries. Recommended for paid work. |
meta | Record<string, unknown> | Optional metadata stored with the deduction. |
Use a stable idempotencyKey for operations that can retry. For example, use a
job id or request id instead of generating a new value for each retry.
Common pattern
const COST = 25;
const { hasCredits, deduct } = useCredits();
async function runPaidAction(jobId: string) {
if (!hasCredits(COST)) return;
await deduct(COST, {
idempotencyKey: jobId,
meta: { feature: "export" },
});
// Start the paid work after the deduction succeeds.
}Not metering? Use features instead
useCredits is for spending a balance. For "does this user have access?" -
subscription tiers and one-time unlocks - reach for features:
import { useFeature } from "@/components/neonfin/provider";
const { enabled } = useFeature("analytics");useSubscription(productId?) returns the wallet's active subscription for a
product (tier label, status, currentPeriodEnd) - handy for a "current plan"
badge. See Access & features and
FeatureGate.