neonFin
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

FieldTypeDescription
balancenumberCurrent balance for the selected product.
creditUnitstring | nullUnit label, like minutes or images.
productIdstring | nullResolved product id.
loadingbooleanTrue while balances are loading.
errorstring | nullBalance loading error message.
confirmingbooleanTrue while checkout started from this page is in progress.
refresh() => Promise<void>Re-fetch balances.
hasCredits(amount: number) => booleanLocal balance check.
deduct(amount, opts?) => Promise<DeductResult>Deduct credits and refresh.

Deduct options

OptionTypeDescription
idempotencyKeystringStable key for retries. Recommended for paid work.
metaRecord<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.