Pricing Implementation Plan & Testing Requirements¶
Transparent Fair Pricing Model - December 2025
📋 OVERVIEW¶
This document outlines the complete implementation plan for CodeSlick's new transparent pricing model, including: - Technical implementation requirements - Credit management system - Testing requirements - Launch checklist
🎯 PRICING MODEL SUMMARY¶
Philosophy¶
"Pay for the platform. Choose your AI model."
Core Principles¶
- Transparency: Users see exact costs (€0.02 per AI fix)
- Choice: Users choose AI provider/model
- Fairness: Pay for what you use
- Control: Real-time credit tracking
Pricing Structure¶
FREE TIER¶
- €0/month
- 20 PR analyses/month
- Unlimited pattern-based fixes
- Bring own API key for unlimited AI
TEAM TIER¶
- €39/month base (down from €99)
- Unlimited analyses & pattern fixes
- AI Options:
- 🔑 Own key: +€0/mo (unlimited AI)
- 📦 Credits: +€10/mo (500 fixes)
- ♾️ Unlimited: +€60/mo (unlimited server AI)
ENTERPRISE TIER¶
- €129/month base (down from €299)
- Everything in Team + unlimited repos/members
- AI Options:
- 🔑 Own key: +€0/mo (unlimited AI)
- 📦 Credits: +€25/mo (1,000 fixes)
- ♾️ Unlimited: +€120/mo (unlimited server AI)
🛠️ TECHNICAL IMPLEMENTATION¶
Phase 1: Database Schema (Week 1)¶
1.1 New Tables¶
-- Credit tracking table
CREATE TABLE ai_credits (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
team_id UUID NOT NULL REFERENCES teams(id) ON DELETE CASCADE,
user_id UUID REFERENCES users(id),
-- Credit configuration
credit_type VARCHAR(20) NOT NULL, -- 'own-key', 'pack', 'unlimited'
monthly_limit INTEGER, -- NULL for unlimited
credits_used INTEGER DEFAULT 0,
credits_remaining INTEGER, -- Computed field
-- Billing period
period_start TIMESTAMP NOT NULL,
period_end TIMESTAMP NOT NULL,
-- API key management (encrypted)
own_api_key_encrypted TEXT, -- For 'own-key' type
own_api_provider VARCHAR(50), -- 'openrouter', 'anthropic', etc.
own_api_model VARCHAR(100),
-- Timestamps
created_at TIMESTAMP DEFAULT NOW(),
updated_at TIMESTAMP DEFAULT NOW(),
-- Indexes
INDEX idx_team_period (team_id, period_start, period_end),
INDEX idx_user_period (user_id, period_start, period_end)
);
-- Credit usage log (audit trail)
CREATE TABLE ai_credit_usage (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
team_id UUID NOT NULL REFERENCES teams(id),
user_id UUID REFERENCES users(id),
credit_record_id UUID REFERENCES ai_credits(id),
-- Usage details
file_path VARCHAR(500),
issue_type VARCHAR(100),
fix_success BOOLEAN DEFAULT TRUE,
tokens_used INTEGER,
cost_eur DECIMAL(10, 4), -- Actual cost in euros
-- Metadata
ai_provider VARCHAR(50),
ai_model VARCHAR(100),
response_time_ms INTEGER,
-- Timestamps
created_at TIMESTAMP DEFAULT NOW(),
-- Indexes
INDEX idx_team_date (team_id, created_at),
INDEX idx_user_date (user_id, created_at)
);
-- Credit alerts (warnings)
CREATE TABLE credit_alerts (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
team_id UUID NOT NULL REFERENCES teams(id),
alert_type VARCHAR(20) NOT NULL, -- '50_percent', '75_percent', '90_percent', 'exhausted'
credits_remaining INTEGER,
sent_at TIMESTAMP DEFAULT NOW(),
acknowledged BOOLEAN DEFAULT FALSE,
INDEX idx_team_alerts (team_id, sent_at)
);
1.2 Stripe Product/Price Updates¶
// New Stripe Products
const STRIPE_PRODUCTS = {
team_base: {
name: 'CodeSlick Team - Base Platform',
price: 3900, // €39 in cents
interval: 'month',
description: 'Unlimited static analysis + pattern fixes'
},
team_credits: {
name: 'CodeSlick Team - AI Credit Pack',
price: 1000, // €10 in cents
interval: 'month',
description: '500 AI-powered fixes per month'
},
team_unlimited: {
name: 'CodeSlick Team - Unlimited AI',
price: 6000, // €60 in cents
interval: 'month',
description: 'Unlimited server-powered AI fixes'
},
enterprise_base: {
name: 'CodeSlick Enterprise - Base Platform',
price: 12900, // €129 in cents
interval: 'month',
description: 'Unlimited everything + advanced features'
},
enterprise_credits: {
name: 'CodeSlick Enterprise - AI Credit Pack',
price: 2500, // €25 in cents
interval: 'month',
description: '1,000 AI-powered fixes per month'
},
enterprise_unlimited: {
name: 'CodeSlick Enterprise - Unlimited AI',
price: 12000, // €120 in cents
interval: 'month',
description: 'Unlimited server-powered AI fixes'
}
};
Phase 2: Credit Management API (Week 2)¶
2.1 Core API Endpoints¶
// GET /api/teams/[id]/credits
// Returns current credit status
interface CreditStatusResponse {
creditType: 'own-key' | 'pack' | 'unlimited';
monthlyLimit: number | null; // null for unlimited
creditsUsed: number;
creditsRemaining: number;
percentageUsed: number;
periodStart: string;
periodEnd: string;
daysRemaining: number;
// Own-key configuration (if applicable)
ownKey?: {
provider: string;
model: string;
keyConfigured: boolean; // Don't send actual key
};
// Usage this month
usageThisMonth: {
totalFixes: number;
estimatedCost: number; // In euros
topFiles: Array<{
path: string;
fixes: number;
}>;
};
// Warnings
warnings: Array<{
type: '50_percent' | '75_percent' | '90_percent' | 'exhausted';
message: string;
actionRequired: boolean;
}>;
}
// POST /api/teams/[id]/credits/configure
// Configure AI option
interface ConfigureCreditsRequest {
creditType: 'own-key' | 'pack' | 'unlimited';
// For 'own-key' type
ownKey?: {
apiKey: string;
provider: 'openrouter' | 'anthropic' | 'openai' | 'groq' | 'together';
model: string;
};
}
// POST /api/teams/[id]/credits/test-connection
// Test user's own API key
interface TestConnectionRequest {
apiKey: string;
provider: string;
model: string;
}
interface TestConnectionResponse {
success: boolean;
message: string;
estimatedCostPerFix?: number;
}
// GET /api/teams/[id]/credits/usage-history
// Historical usage
interface UsageHistoryResponse {
history: Array<{
date: string;
fixesUsed: number;
cost: number;
topFile: string;
}>;
totalThisMonth: number;
averagePerDay: number;
projectedMonthEnd: number;
}
2.2 Credit Tracking Logic¶
// src/lib/credits/credit-manager.ts
export class CreditManager {
/**
* Check if team has credits available
* @throws Error if no credits remaining
*/
async checkCredits(teamId: string): Promise<void> {
const credits = await this.getCurrentCredits(teamId);
// Unlimited: always pass
if (credits.creditType === 'unlimited') {
return;
}
// Own-key: always pass (user pays)
if (credits.creditType === 'own-key') {
return;
}
// Credit pack: check limit
if (credits.creditType === 'pack') {
if (credits.creditsRemaining <= 0) {
throw new Error(
`No AI credits remaining. You've used ${credits.creditsUsed}/${credits.monthlyLimit} fixes this month. ` +
`Options: Wait ${credits.daysRemaining} days for reset, upgrade to Unlimited, or bring your own API key.`
);
}
// Warn if low
if (credits.creditsRemaining <= 50 && !credits.warned50) {
await this.sendLowCreditWarning(teamId, '50_percent', credits.creditsRemaining);
}
}
}
/**
* Consume a credit (atomic operation)
*/
async consumeCredit(teamId: string, userId: string, metadata: CreditUsageMetadata): Promise<void> {
const credits = await this.getCurrentCredits(teamId);
// Skip for unlimited and own-key
if (credits.creditType === 'unlimited' || credits.creditType === 'own-key') {
// Still log usage for analytics
await this.logUsage(teamId, userId, metadata);
return;
}
// Atomic increment (prevents race conditions)
await db.query(`
UPDATE ai_credits
SET credits_used = credits_used + 1,
credits_remaining = monthly_limit - (credits_used + 1),
updated_at = NOW()
WHERE team_id = $1
AND period_start <= NOW()
AND period_end > NOW()
AND credits_remaining > 0
RETURNING *
`, [teamId]);
// Log usage
await this.logUsage(teamId, userId, metadata);
}
/**
* Reset credits at period end (cron job)
*/
async resetMonthlyCredits(): Promise<void> {
// Find expired periods
const expiredCredits = await db.query(`
SELECT * FROM ai_credits
WHERE period_end <= NOW()
AND credit_type = 'pack'
`);
for (const credit of expiredCredits) {
// Create new period
await db.query(`
INSERT INTO ai_credits (
team_id, credit_type, monthly_limit,
credits_used, credits_remaining,
period_start, period_end
) VALUES (
$1, $2, $3, 0, $3,
NOW(), NOW() + INTERVAL '1 month'
)
`, [credit.team_id, credit.credit_type, credit.monthly_limit]);
}
}
}
Phase 3: Dashboard UI (Week 3)¶
3.1 Credit Dashboard Component¶
// src/components/dashboard/CreditDashboard.tsx
export function CreditDashboard() {
const { data: credits } = useQuery('/api/teams/[id]/credits');
return (
<div className="bg-white rounded-lg shadow p-6">
{/* Header */}
<div className="flex justify-between items-center mb-6">
<h2 className="text-2xl font-bold">AI Fix Credits</h2>
<button onClick={() => setShowConfig(true)}>
⚙️ Configure
</button>
</div>
{/* Credit Meter */}
{credits.creditType === 'pack' && (
<div className="mb-6">
<div className="flex justify-between mb-2">
<span className="font-medium">Credits Remaining</span>
<span className="font-bold text-blue-600">
{credits.creditsRemaining} / {credits.monthlyLimit}
</span>
</div>
{/* Progress Bar */}
<div className="w-full bg-gray-200 rounded-full h-3">
<div
className={`h-3 rounded-full transition-all ${
credits.percentageUsed > 90 ? 'bg-red-600' :
credits.percentageUsed > 75 ? 'bg-yellow-600' :
'bg-blue-600'
}`}
style={{ width: `${100 - credits.percentageUsed}%` }}
/>
</div>
<p className="text-sm text-gray-600 mt-2">
Resets in {credits.daysRemaining} days
</p>
</div>
)}
{/* Usage This Month */}
<div className="grid grid-cols-3 gap-4 mb-6">
<div className="bg-blue-50 rounded-lg p-4">
<p className="text-sm text-gray-600">Fixes Used</p>
<p className="text-2xl font-bold text-blue-600">
{credits.usageThisMonth.totalFixes}
</p>
</div>
<div className="bg-green-50 rounded-lg p-4">
<p className="text-sm text-gray-600">Estimated Cost</p>
<p className="text-2xl font-bold text-green-600">
€{credits.usageThisMonth.estimatedCost.toFixed(2)}
</p>
</div>
<div className="bg-purple-50 rounded-lg p-4">
<p className="text-sm text-gray-600">Top File</p>
<p className="text-sm font-mono text-purple-600 truncate">
{credits.usageThisMonth.topFiles[0]?.path || 'N/A'}
</p>
</div>
</div>
{/* Warnings */}
{credits.warnings.length > 0 && (
<div className="bg-yellow-50 border border-yellow-300 rounded-lg p-4">
{credits.warnings.map((warning, idx) => (
<div key={idx} className="flex items-start gap-2">
<AlertCircle className="text-yellow-600 flex-shrink-0" />
<div>
<p className="font-semibold text-yellow-900">{warning.message}</p>
{warning.actionRequired && (
<button className="text-sm text-blue-600 underline mt-1">
Upgrade Now
</button>
)}
</div>
</div>
))}
</div>
)}
{/* Configuration Modal */}
{showConfig && (
<CreditConfigModal
currentType={credits.creditType}
onClose={() => setShowConfig(false)}
/>
)}
</div>
);
}
✅ TESTING REQUIREMENTS¶
Test Suite 1: Credit Tracking (Critical)¶
describe('Credit Management', () => {
it('should consume 1 credit per AI fix', async () => {
const team = await createTestTeam({ creditType: 'pack', monthlyLimit: 500 });
// Use 1 fix
await generateAIFix(team.id, mockIssue);
const credits = await getCredits(team.id);
expect(credits.creditsUsed).toBe(1);
expect(credits.creditsRemaining).toBe(499);
});
it('should prevent fix when credits exhausted', async () => {
const team = await createTestTeam({ creditType: 'pack', monthlyLimit: 1 });
// Use the only credit
await generateAIFix(team.id, mockIssue);
// Attempt 2nd fix
await expect(
generateAIFix(team.id, mockIssue)
).rejects.toThrow('No AI credits remaining');
});
it('should allow unlimited fixes for own-key users', async () => {
const team = await createTestTeam({
creditType: 'own-key',
ownApiKey: 'test-key'
});
// Use 1000 fixes
for (let i = 0; i < 1000; i++) {
await generateAIFix(team.id, mockIssue);
}
const credits = await getCredits(team.id);
expect(credits.creditsRemaining).toBe(null); // Unlimited
});
it('should reset credits on new period', async () => {
const team = await createTestTeam({ creditType: 'pack', monthlyLimit: 500 });
// Use all credits
for (let i = 0; i < 500; i++) {
await generateAIFix(team.id, mockIssue);
}
// Simulate period end
await timeTravelToNextMonth();
await resetMonthlyCredits();
const credits = await getCredits(team.id);
expect(credits.creditsUsed).toBe(0);
expect(credits.creditsRemaining).toBe(500);
});
it('should send warning at 50% usage', async () => {
const team = await createTestTeam({ creditType: 'pack', monthlyLimit: 100 });
// Use 50 credits
for (let i = 0; i < 50; i++) {
await generateAIFix(team.id, mockIssue);
}
const alerts = await getAlerts(team.id);
expect(alerts).toContainEqual(
expect.objectContaining({ alert_type: '50_percent' })
);
});
});
Test Suite 2: Own API Key Management¶
describe('Own API Key', () => {
it('should encrypt API key in database', async () => {
const team = await createTestTeam();
await configureOwnKey(team.id, {
apiKey: 'sk-test-12345',
provider: 'openrouter',
model: 'anthropic/claude-3.5-sonnet'
});
// Check database directly
const record = await db.query('SELECT * FROM ai_credits WHERE team_id = $1', [team.id]);
expect(record.own_api_key_encrypted).not.toBe('sk-test-12345');
expect(record.own_api_key_encrypted).toContain('encrypted:');
});
it('should validate API key before saving', async () => {
const team = await createTestTeam();
await expect(
configureOwnKey(team.id, {
apiKey: 'invalid-key',
provider: 'openrouter',
model: 'test'
})
).rejects.toThrow('Invalid API key');
});
it('should use user API key for fixes', async () => {
const team = await createTestTeam({
creditType: 'own-key',
ownApiKey: 'sk-test-user-key'
});
const mockFetch = jest.spyOn(global, 'fetch');
await generateAIFix(team.id, mockIssue);
expect(mockFetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': 'Bearer sk-test-user-key'
})
})
);
});
});
Test Suite 3: Stripe Integration¶
describe('Stripe Billing', () => {
it('should create subscription with base + credits', async () => {
const team = await createTestTeam();
const subscription = await createSubscription(team.id, {
tier: 'team',
aiOption: 'credits'
});
expect(subscription.items).toHaveLength(2);
expect(subscription.items[0].price.id).toBe(STRIPE_PRODUCTS.team_base.priceId);
expect(subscription.items[1].price.id).toBe(STRIPE_PRODUCTS.team_credits.priceId);
expect(subscription.amount_total).toBe(4900); // €49
});
it('should update subscription when changing AI option', async () => {
const team = await createTestTeam({ tier: 'team', aiOption: 'credits' });
// Switch to unlimited
await updateSubscription(team.id, { aiOption: 'unlimited' });
const subscription = await getSubscription(team.id);
expect(subscription.amount_total).toBe(9900); // €99
});
it('should handle downgrade to own-key', async () => {
const team = await createTestTeam({ tier: 'team', aiOption: 'unlimited' });
// Switch to own-key
await updateSubscription(team.id, { aiOption: 'own-key' });
const subscription = await getSubscription(team.id);
expect(subscription.items).toHaveLength(1); // Only base
expect(subscription.amount_total).toBe(3900); // €39
});
});
Test Suite 4: Edge Cases¶
describe('Edge Cases', () => {
it('should handle concurrent fix requests (race condition)', async () => {
const team = await createTestTeam({ creditType: 'pack', monthlyLimit: 10 });
// Attempt 20 simultaneous fixes
const promises = Array(20).fill(null).map(() =>
generateAIFix(team.id, mockIssue)
);
const results = await Promise.allSettled(promises);
// Only 10 should succeed
const successful = results.filter(r => r.status === 'fulfilled');
expect(successful.length).toBe(10);
const credits = await getCredits(team.id);
expect(credits.creditsUsed).toBe(10);
});
it('should handle API key rotation', async () => {
const team = await createTestTeam({
creditType: 'own-key',
ownApiKey: 'sk-old-key'
});
// Update to new key
await configureOwnKey(team.id, {
apiKey: 'sk-new-key',
provider: 'openrouter',
model: 'anthropic/claude-3.5-sonnet'
});
// Next fix should use new key
const mockFetch = jest.spyOn(global, 'fetch');
await generateAIFix(team.id, mockIssue);
expect(mockFetch).toHaveBeenCalledWith(
expect.any(String),
expect.objectContaining({
headers: expect.objectContaining({
'Authorization': 'Bearer sk-new-key'
})
})
);
});
it('should handle period transition gracefully', async () => {
const team = await createTestTeam({ creditType: 'pack', monthlyLimit: 100 });
// Use 99 credits
for (let i = 0; i < 99; i++) {
await generateAIFix(team.id, mockIssue);
}
// Time travel to last day of period
await timeTravelToDays(29);
// Use last credit
await generateAIFix(team.id, mockIssue);
// Should fail
await expect(
generateAIFix(team.id, mockIssue)
).rejects.toThrow();
// Time travel to new period
await timeTravelToDays(2);
await resetMonthlyCredits();
// Should succeed now
await expect(
generateAIFix(team.id, mockIssue)
).resolves.toBeDefined();
});
});
🚀 LAUNCH CHECKLIST¶
Pre-Launch (Week 4)¶
- Database Migration
- Create
ai_creditstable - Create
ai_credit_usagetable - Create
credit_alertstable - Add indexes for performance
-
Test rollback procedure
-
Stripe Configuration
- Create new products in Stripe
- Create price points (€39, €10, €60, €129, €25, €120)
- Test webhook handling for subscription changes
- Configure prorated billing
-
Test annual billing (17% discount)
-
API Development
- Implement credit checking middleware
- Build credit configuration endpoints
- Create usage tracking API
- Add real-time credit updates (WebSocket)
-
Implement alert system
-
Dashboard UI
- Build credit dashboard component
- Create configuration modal (own-key setup)
- Add usage charts/analytics
- Implement low-credit warnings
-
Add upgrade/downgrade flows
-
Testing
- Run 568+ existing tests (ensure no regressions)
- Add 50+ new credit management tests
- Load test: 1,000 concurrent credit checks
- Security audit: API key encryption
- End-to-end user flows
Launch Day¶
- Deploy
- Deploy database migrations
- Deploy API changes
- Deploy UI updates
-
Monitor error rates
-
Communication
- Email existing users (pricing update)
- Publish blog post: "Fair Pricing Revolution"
- Update pricing page
-
Update documentation
-
Monitoring
- Set up credit usage alerts
- Monitor API error rates
- Track conversion rates (free → paid)
- Monitor Stripe webhooks
Post-Launch (First Week)¶
- Metrics Tracking
- Own-key adoption rate (target: 60%)
- Credit pack adoption (target: 30%)
- Unlimited AI adoption (target: 10%)
- Average revenue per user (target: €48/mo)
-
LTV:CAC ratio (target: >4x)
-
User Feedback
- Survey users on pricing clarity (target: >90% "fair")
- Monitor NPS score (target: >60)
- Track support tickets about pricing
-
Collect testimonials
-
A/B Testing
- Test €39 vs €49 base price
- Test credit pack sizes (500 vs 750 vs 1,000)
- Test free tier limits (20 vs 30 analyses/month)
📊 SUCCESS METRICS¶
Week 1 Goals¶
- 100% existing users migrated to new pricing
- <5% churn rate (users canceling due to pricing)
- >50% of new signups choose own-key option
- Zero critical bugs in credit tracking
Month 1 Goals¶
- 200 paying customers (up from 0)
- €9,600 MRR (200 teams × €48 avg)
- 60% own-key adoption
- >90% "pricing is fair" survey rating
- <2% monthly churn
Month 3 Goals¶
- 500 paying customers
- €24,000 MRR
- 4.5x LTV:CAC ratio
- >65 NPS score
- 1% monthly churn (sticky pricing)
🔒 SECURITY REQUIREMENTS¶
API Key Encryption¶
// Use AES-256-GCM for API key encryption
import crypto from 'crypto';
const ENCRYPTION_KEY = process.env.CREDIT_ENCRYPTION_KEY; // 32 bytes
const ALGORITHM = 'aes-256-gcm';
export function encryptAPIKey(apiKey: string): string {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
let encrypted = cipher.update(apiKey, 'utf8', 'hex');
encrypted += cipher.final('hex');
const authTag = cipher.getAuthTag();
return `encrypted:${iv.toString('hex')}:${encrypted}:${authTag.toString('hex')}`;
}
export function decryptAPIKey(encryptedKey: string): string {
const [prefix, ivHex, encrypted, authTagHex] = encryptedKey.split(':');
if (prefix !== 'encrypted') {
throw new Error('Invalid encrypted key format');
}
const iv = Buffer.from(ivHex, 'hex');
const authTag = Buffer.from(authTagHex, 'hex');
const decipher = crypto.createDecipheriv(ALGORITHM, ENCRYPTION_KEY, iv);
decipher.setAuthTag(authTag);
let decrypted = decipher.update(encrypted, 'hex', 'utf8');
decrypted += decipher.final('utf8');
return decrypted;
}
Rate Limiting¶
// Prevent credit abuse
const RATE_LIMITS = {
ai_fix: {
max: 10, // Max 10 AI fixes per minute
window: 60 * 1000 // 1 minute
},
credit_check: {
max: 100, // Max 100 credit checks per minute
window: 60 * 1000
}
};
🎯 NEXT STEPS¶
- Week 1: Implement database schema + Stripe products
- Week 2: Build credit management API
- Week 3: Create dashboard UI + testing
- Week 4: QA, security audit, launch preparation
- Week 5: Soft launch to beta users
- Week 6: Full public launch + marketing
Created: December 7, 2025 Status: Ready for implementation Owner: Vitor (Founder)