Skip to content

Commit a96d84e

Browse files
committed
feat(api): create pricing configuration file (SSC-16)
- Define subscription plans (FREE, PREMIUM, STUDENT_PLUS) - Set monthly and yearly pricing ($9.99, $14.99) - Configure Stripe price IDs from environment - Define feature limits per tier - Implement helper functions for pricing logic - Add student discount configuration (20% off) - Include annual savings calculation - Support trial periods configuration This centralizes all pricing and feature configuration. Relates to SSC-16: Payment Processing & Subscription Management
1 parent c5016b1 commit a96d84e

1 file changed

Lines changed: 195 additions & 0 deletions

File tree

apps/api/src/config/pricing.ts

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/**
2+
* Pricing Configuration
3+
* Defines subscription tiers, pricing, and feature limits
4+
*/
5+
6+
export interface SubscriptionPlan {
7+
id: string;
8+
name: string;
9+
tier: 'FREE' | 'PREMIUM' | 'STUDENT_PLUS' | 'UNIVERSITY';
10+
description: string;
11+
monthlyPrice: number; // in USD
12+
yearlyPrice: number; // in USD (with discount)
13+
stripePriceIdMonthly?: string; // Stripe price ID for monthly billing
14+
stripePriceIdYearly?: string; // Stripe price ID for yearly billing
15+
trialDays?: number;
16+
features: {
17+
maxCourses: number | null; // null = unlimited
18+
maxFlashcardSets: number | null;
19+
maxQuizzes: number | null;
20+
aiFlashcards: boolean;
21+
aiQuizzes: boolean;
22+
knowledgeGraph: boolean;
23+
examPrediction: boolean;
24+
analytics: boolean;
25+
assignmentHelp: boolean;
26+
prioritySupport: boolean;
27+
mobileApp: boolean;
28+
exportData: boolean;
29+
};
30+
}
31+
32+
// Subscription Plans Configuration
33+
export const SUBSCRIPTION_PLANS: Record<string, SubscriptionPlan> = {
34+
FREE: {
35+
id: 'free',
36+
name: 'Free',
37+
tier: 'FREE',
38+
description: 'Perfect for trying out StudySync',
39+
monthlyPrice: 0,
40+
yearlyPrice: 0,
41+
features: {
42+
maxCourses: 1,
43+
maxFlashcardSets: 3,
44+
maxQuizzes: 5,
45+
aiFlashcards: true,
46+
aiQuizzes: true,
47+
knowledgeGraph: false,
48+
examPrediction: false,
49+
analytics: false,
50+
assignmentHelp: false,
51+
prioritySupport: false,
52+
mobileApp: true,
53+
exportData: false,
54+
},
55+
},
56+
PREMIUM: {
57+
id: 'premium',
58+
name: 'Premium',
59+
tier: 'PREMIUM',
60+
description: 'Unlimited courses and advanced features',
61+
monthlyPrice: 9.99,
62+
yearlyPrice: 99.99, // 2 months free
63+
stripePriceIdMonthly: process.env.STRIPE_PRICE_PREMIUM_MONTHLY,
64+
stripePriceIdYearly: process.env.STRIPE_PRICE_PREMIUM_YEARLY,
65+
features: {
66+
maxCourses: null,
67+
maxFlashcardSets: null,
68+
maxQuizzes: null,
69+
aiFlashcards: true,
70+
aiQuizzes: true,
71+
knowledgeGraph: true,
72+
examPrediction: false,
73+
analytics: true,
74+
assignmentHelp: false,
75+
prioritySupport: true,
76+
mobileApp: true,
77+
exportData: true,
78+
},
79+
},
80+
STUDENT_PLUS: {
81+
id: 'student_plus',
82+
name: 'Student Plus',
83+
tier: 'STUDENT_PLUS',
84+
description: 'All Premium features plus AI tutoring and exam prediction',
85+
monthlyPrice: 14.99,
86+
yearlyPrice: 149.99, // 2 months free
87+
stripePriceIdMonthly: process.env.STRIPE_PRICE_STUDENT_PLUS_MONTHLY,
88+
stripePriceIdYearly: process.env.STRIPE_PRICE_STUDENT_PLUS_YEARLY,
89+
trialDays: 7,
90+
features: {
91+
maxCourses: null,
92+
maxFlashcardSets: null,
93+
maxQuizzes: null,
94+
aiFlashcards: true,
95+
aiQuizzes: true,
96+
knowledgeGraph: true,
97+
examPrediction: true,
98+
analytics: true,
99+
assignmentHelp: true,
100+
prioritySupport: true,
101+
mobileApp: true,
102+
exportData: true,
103+
},
104+
},
105+
};
106+
107+
/**
108+
* Get plan by tier
109+
*/
110+
export function getPlanByTier(tier: string): SubscriptionPlan | null {
111+
return SUBSCRIPTION_PLANS[tier] || null;
112+
}
113+
114+
/**
115+
* Get plan by Stripe price ID
116+
*/
117+
export function getPlanByPriceId(priceId: string): SubscriptionPlan | null {
118+
for (const plan of Object.values(SUBSCRIPTION_PLANS)) {
119+
if (plan.stripePriceIdMonthly === priceId || plan.stripePriceIdYearly === priceId) {
120+
return plan;
121+
}
122+
}
123+
return null;
124+
}
125+
126+
/**
127+
* Check if feature is available for tier
128+
*/
129+
export function hasFeature(
130+
tier: string,
131+
feature: keyof SubscriptionPlan['features']
132+
): boolean {
133+
const plan = getPlanByTier(tier);
134+
return plan ? plan.features[feature] as boolean : false;
135+
}
136+
137+
/**
138+
* Get feature limit for tier
139+
*/
140+
export function getFeatureLimit(
141+
tier: string,
142+
feature: 'maxCourses' | 'maxFlashcardSets' | 'maxQuizzes'
143+
): number | null {
144+
const plan = getPlanByTier(tier);
145+
return plan ? plan.features[feature] : 0;
146+
}
147+
148+
/**
149+
* Check if user can access feature
150+
*/
151+
export function canAccessFeature(
152+
tier: string,
153+
feature: keyof SubscriptionPlan['features']
154+
): boolean {
155+
return hasFeature(tier, feature);
156+
}
157+
158+
/**
159+
* Get all available plans for display
160+
*/
161+
export function getAllPlans(): SubscriptionPlan[] {
162+
return Object.values(SUBSCRIPTION_PLANS);
163+
}
164+
165+
/**
166+
* Calculate annual savings
167+
*/
168+
export function getAnnualSavings(tier: string): number {
169+
const plan = getPlanByTier(tier);
170+
if (!plan) return 0;
171+
const monthlyTotal = plan.monthlyPrice * 12;
172+
return monthlyTotal - plan.yearlyPrice;
173+
}
174+
175+
/**
176+
* Calculate annual savings percentage
177+
*/
178+
export function getAnnualSavingsPercentage(tier: string): number {
179+
const plan = getPlanByTier(tier);
180+
if (!plan || plan.monthlyPrice === 0) return 0;
181+
const savings = getAnnualSavings(tier);
182+
return Math.round((savings / (plan.monthlyPrice * 12)) * 100);
183+
}
184+
185+
/**
186+
* Student discount configuration (.edu emails)
187+
*/
188+
export const STUDENT_DISCOUNT_PERCENTAGE = 20; // 20% off for students
189+
190+
/**
191+
* Apply student discount to price
192+
*/
193+
export function applyStudentDiscount(price: number): number {
194+
return price * (1 - STUDENT_DISCOUNT_PERCENTAGE / 100);
195+
}

0 commit comments

Comments
 (0)