Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 83 additions & 0 deletions PRICING_INTEGRATION.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
# Autumn Pricing Integration

This document outlines the pricing integration implemented for the LLMScore application.

## Pricing Structure

### Credit Packages
- **Starter Pack**: $5.00 USD = 1 credit
- **Growth Pack**: $20.00 USD = 5 credits (saves $5.00)
- **Pro Pack**: $50.00 USD = 15 credits (saves $25.00)

### Scan Costs
- **Basic Scan**: 1 credit per scan (includes website map, AI files check, and evaluation)
- **Premium Scan**: 3 credits per scan (coming soon - advanced features)

## Implementation Details

### Database Schema
- `user_credits`: Stores user credit balances and totals
- `credit_transactions`: Records all purchases and consumption
- Updated existing tables to track credits consumed per scan

### API Endpoints
- `GET /api/credits`: Get user credit balance, stats, and pricing info
- `POST /api/credits`: Purchase credit packages (mock implementation)
- `GET /api/credits/transactions`: Get transaction history

### Credit Consumption
All scan operations now consume credits:
- `/api/evaluate`: Consumes credits for evaluation scans
- `/api/map`: Consumes credits for website mapping
- `/api/check-files`: Consumes credits for AI file checks

### Frontend Components
- `CreditsDisplay`: Shows current balance in header
- `PricingPage`: Credit purchase interface
- Insufficient credits handling with purchase prompts
- Dashboard integration showing credit stats

### Features Implemented
✅ Credit balance tracking
✅ Automatic credit consumption
✅ Insufficient credit validation
✅ Purchase flow (demo implementation)
✅ Transaction history
✅ Credit usage statistics
✅ Low balance warnings
✅ Pricing tiers with bulk discounts

### Future Premium Features (Coming Soon)
- Premium scans with SEO suggestions
- LLM text generation capabilities
- LLM text quality checking
- Advanced analytics and reporting
- Priority support

## Usage Flow

1. User signs up and starts with 0 credits
2. User attempts to scan a website
3. System checks if user has sufficient credits (1 credit for basic scan)
4. If insufficient, user is prompted to purchase credits
5. User selects a credit package and completes purchase
6. Credits are added to account and scan proceeds
7. Credits are consumed upon successful scan completion
8. User can view credit history and stats in dashboard

## Payment Integration

Currently implemented as a demo with mock payment processing. To integrate with real payment processors:

1. Replace mock payment in `/api/credits` POST endpoint
2. Integrate with Stripe, PayPal, or similar service
3. Add proper error handling for payment failures
4. Implement webhooks for payment confirmation
5. Add refund capabilities

## Security Considerations

- Credit consumption happens after successful API calls to prevent abuse
- All credit operations are server-side only
- User authentication required for all credit-related operations
- Transaction logging for audit purposes
41 changes: 40 additions & 1 deletion app/api/check-files/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,27 @@ export async function POST(request: NextRequest) {
);
}

const { url } = await request.json();
const { url, scanType = 'basic' } = await request.json();

// Check if user has enough credits
const creditCheck = await convex.query(api.credits.checkCreditsForScan, {
userId,
scanType,
});

if (!creditCheck.hasEnoughCredits) {
return NextResponse.json(
{
error: 'Insufficient credits',
details: {
required: creditCheck.requiredCredits,
available: creditCheck.availableCredits,
shortfall: creditCheck.shortfall,
}
},
{ status: 402 } // Payment Required
);
}

if (!url) {
return NextResponse.json(
Expand Down Expand Up @@ -183,13 +203,32 @@ export async function POST(request: NextRequest) {
return foundIndicators.length >= 2;
}

// Consume credits for the scan
try {
await convex.mutation(api.credits.consumeCredits, {
userId,
credits: creditCheck.requiredCredits,
scanType,
scanUrl: baseUrl.href,
description: `${scanType.charAt(0).toUpperCase() + scanType.slice(1)} AI files check`,
});
} catch (creditError) {
console.error('Error consuming credits:', creditError);
return NextResponse.json(
{ error: 'Failed to consume credits' },
{ status: 500 }
);
}

// Save to Convex
try {
await convex.mutation(api.aiFiles.saveAIFiles, {
userId: userId as any,
url: baseUrl.href,
domain: baseUrl.hostname,
files: fileChecks,
credits_consumed: creditCheck.requiredCredits,
scan_type: scanType,
});
} catch (convexError) {
console.error('Error saving AI files to Convex:', convexError);
Expand Down
154 changes: 154 additions & 0 deletions app/api/credits/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import { NextRequest, NextResponse } from 'next/server';
import { api } from '../../../convex/_generated/api';
import { ConvexHttpClient } from 'convex/browser';
import { getCurrentUserId } from '@/lib/auth-utils';

const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

// Get user's credit balance and stats
export async function GET(request: NextRequest) {
try {
// Check authentication
let userId: string;
try {
userId = await getCurrentUserId(request);
} catch {
return NextResponse.json(
{ error: 'Authentication required' },
{ status: 401 }
);
}

// Initialize user credits if they don't exist
await convex.mutation(api.credits.initializeUserCredits, { userId });

// Get user credits and stats
const [credits, stats, transactions] = await Promise.all([
convex.query(api.credits.getUserCredits, { userId }),
convex.query(api.credits.getCreditStats, { userId }),
convex.query(api.credits.getTransactionHistory, { userId, limit: 10 }),
]);

return NextResponse.json({
success: true,
credits,
stats,
recent_transactions: transactions,
pricing: {
free_plan: {
name: "Free Plan",
credits: 1,
price: 0.00,
description: "Get started with 1 free credit - no payment required!"
},
packages: {
starter: {
name: "Starter Pack",
credits: 1,
price: 5.00,
description: "Perfect for testing our service"
},
growth: {
name: "Growth Pack",
credits: 5,
price: 20.00,
description: "Best value for regular users",
savings: 5.00
},
pro: {
name: "Pro Pack",
credits: 15,
price: 50.00,
description: "For power users and agencies",
savings: 25.00
}
},
scan_costs: {
basic: 1,
premium: 3
}
}
});

} catch (error) {
console.error('Error fetching credits:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}

// Purchase credits (mock implementation - would integrate with payment processor)
export async function POST(request: NextRequest) {
try {
// Check authentication
let userId: string;
try {
userId = await getCurrentUserId(request);
} catch {
return NextResponse.json(
{ error: 'Authentication required' },
{ status: 401 }
);
}

const { packageType, paymentToken } = await request.json();

if (!packageType) {
return NextResponse.json(
{ error: 'Package type is required' },
{ status: 400 }
);
}

// Validate package type
const packages = {
starter: { credits: 1, price: 500 }, // USD cents
growth: { credits: 5, price: 2000 },
pro: { credits: 15, price: 5000 }
};

const selectedPackage = packages[packageType as keyof typeof packages];
if (!selectedPackage) {
return NextResponse.json(
{ error: 'Invalid package type' },
{ status: 400 }
);
}

// TODO: Integrate with payment processor (Stripe, etc.)
// For now, we'll simulate a successful payment
if (!paymentToken || paymentToken !== 'demo_success') {
return NextResponse.json(
{ error: 'Payment processing failed' },
{ status: 402 }
);
}

// Add credits to user account
const newBalance = await convex.mutation(api.credits.addCredits, {
userId,
credits: selectedPackage.credits,
packageType,
pricePaid: selectedPackage.price,
description: `Purchased ${packageType} package (${selectedPackage.credits} credits)`,
});

return NextResponse.json({
success: true,
message: 'Credits purchased successfully',
credits_added: selectedPackage.credits,
new_balance: newBalance,
package: packageType,
amount_paid: selectedPackage.price / 100, // Convert to dollars
});

} catch (error) {
console.error('Error purchasing credits:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
46 changes: 46 additions & 0 deletions app/api/credits/transactions/route.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import { NextRequest, NextResponse } from 'next/server';
import { api } from '../../../../convex/_generated/api';
import { ConvexHttpClient } from 'convex/browser';
import { getCurrentUserId } from '@/lib/auth-utils';

const convex = new ConvexHttpClient(process.env.NEXT_PUBLIC_CONVEX_URL!);

export async function GET(request: NextRequest) {
try {
// Check authentication
let userId: string;
try {
userId = await getCurrentUserId(request);
} catch {
return NextResponse.json(
{ error: 'Authentication required' },
{ status: 401 }
);
}

// Get query parameters
const { searchParams } = new URL(request.url);
const limit = parseInt(searchParams.get('limit') || '50');
const type = searchParams.get('type') || undefined;

// Get transaction history
const transactions = await convex.query(api.credits.getTransactionHistory, {
userId,
limit,
type,
});

return NextResponse.json({
success: true,
transactions,
total: transactions.length,
});

} catch (error) {
console.error('Error fetching transaction history:', error);
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
Loading