feat: unified credits and its cost calculations

This commit is contained in:
DESKTOP-RTLN3BA\$punk 2026-05-02 14:34:23 -07:00
parent 451a98936e
commit ae9d36d77f
61 changed files with 5835 additions and 272 deletions

View file

@ -127,7 +127,7 @@ const FAQ_ITEMS = [
{
question: "What happens after I use my free tokens?",
answer:
"After your free tokens, create a free SurfSense account to unlock 3 million more premium tokens. Additional tokens can be purchased at $1 per million. Non-premium models remain unlimited for registered users.",
"After your free tokens, create a free SurfSense account to unlock $5 of premium credit. Additional credit can be topped up at $1 for $1 of credit, billed at the actual provider cost. Non-premium models remain unlimited for registered users.",
},
{
question: "Is Claude AI available without login?",
@ -329,7 +329,7 @@ export default async function FreeHubPage() {
<section className="max-w-3xl mx-auto text-center">
<h2 className="text-2xl font-bold mb-3">Want More Features?</h2>
<p className="text-muted-foreground mb-6 leading-relaxed">
Create a free SurfSense account to unlock 3 million tokens, document uploads with
Create a free SurfSense account to unlock $5 of premium credit, document uploads with
citations, team collaboration, and integrations with Slack, Google Drive, Notion, and
30+ more tools.
</p>

View file

@ -5,7 +5,7 @@ import { BreadcrumbNav } from "@/components/seo/breadcrumb-nav";
export const metadata: Metadata = {
title: "Pricing | SurfSense - Free AI Search Plans",
description:
"Explore SurfSense plans and pricing. Start free with 500 pages & 3M premium tokens. Use ChatGPT, Claude AI, and premium AI models. Pay-as-you-go tokens at $1 per million.",
"Explore SurfSense plans and pricing. Start free with 500 pages & $5 of premium credit. Use ChatGPT, Claude AI, and premium AI models. Pay as you go at provider cost — $1 buys $1 of credit.",
alternates: {
canonical: "https://surfsense.com/pricing",
},

View file

@ -8,7 +8,7 @@ import { cn } from "@/lib/utils";
const TABS = [
{ id: "pages", label: "Pages" },
{ id: "tokens", label: "Premium Tokens" },
{ id: "tokens", label: "Premium Credit" },
] as const;
type TabId = (typeof TABS)[number]["id"];

View file

@ -28,6 +28,12 @@ type UnifiedPurchase = {
kind: PurchaseKind;
created_at: string;
status: PagePurchaseStatus;
/**
* Granted units. Interpretation depends on ``kind``:
* - ``"pages"`` integer number of indexed pages.
* - ``"tokens"`` integer micro-USD of credit (1_000_000 = $1.00).
* The ``Granted`` column formats accordingly.
*/
granted: number;
amount_total: number | null;
currency: string | null;
@ -58,7 +64,7 @@ const KIND_META: Record<
iconClass: "text-sky-500",
},
tokens: {
label: "Premium Tokens",
label: "Premium Credit",
icon: Coins,
iconClass: "text-amber-500",
},
@ -97,12 +103,25 @@ function normalizeTokenPurchase(p: TokenPurchase): UnifiedPurchase {
kind: "tokens",
created_at: p.created_at,
status: p.status,
granted: p.tokens_granted,
granted: p.credit_micros_granted,
amount_total: p.amount_total,
currency: p.currency,
};
}
function formatGranted(p: UnifiedPurchase): string {
if (p.kind === "tokens") {
const dollars = p.granted / 1_000_000;
// Premium credit packs are always whole dollars at the moment, but
// future fractional grants (refunds, partial top-ups) shouldn't
// silently round to "$0".
if (dollars >= 1) return `$${dollars.toFixed(2)} of credit`;
if (dollars > 0) return `$${dollars.toFixed(3)} of credit`;
return "$0 of credit";
}
return p.granted.toLocaleString();
}
export function PurchaseHistoryContent() {
const results = useQueries({
queries: [
@ -143,7 +162,7 @@ export function PurchaseHistoryContent() {
<ReceiptText className="h-8 w-8 text-muted-foreground" />
<p className="text-sm font-medium">No purchases yet</p>
<p className="text-xs text-muted-foreground">
Your page and premium token purchases will appear here after checkout.
Your page and premium credit purchases will appear here after checkout.
</p>
</div>
);
@ -177,7 +196,7 @@ export function PurchaseHistoryContent() {
</div>
</TableCell>
<TableCell className="text-right tabular-nums text-sm">
{p.granted.toLocaleString()}
{formatGranted(p)}
</TableCell>
<TableCell className="text-right tabular-nums text-sm">
{formatAmount(p.amount_total, p.currency)}