108 lines
2.6 KiB
TypeScript
108 lines
2.6 KiB
TypeScript
import Stripe from 'stripe';
|
|
import {
|
|
FREE_DYNAMIC_QR_LIMIT,
|
|
PRO_DYNAMIC_QR_LIMIT,
|
|
BUSINESS_DYNAMIC_QR_LIMIT,
|
|
} from '@/lib/plans';
|
|
|
|
// Use a placeholder during build time, real key at runtime
|
|
const stripeKey = process.env.STRIPE_SECRET_KEY || 'sk_test_placeholder_for_build';
|
|
|
|
export const stripe = new Stripe(stripeKey, {
|
|
apiVersion: '2025-10-29.clover',
|
|
typescript: true,
|
|
});
|
|
|
|
// Runtime validation (will throw when actually used in production if not set)
|
|
export function validateStripeKey() {
|
|
if (!process.env.STRIPE_SECRET_KEY) {
|
|
throw new Error('STRIPE_SECRET_KEY is not set');
|
|
}
|
|
}
|
|
|
|
export const STRIPE_PLANS = {
|
|
FREE: {
|
|
name: 'Free / Starter',
|
|
price: 0,
|
|
currency: 'EUR',
|
|
interval: 'month',
|
|
features: [
|
|
`${FREE_DYNAMIC_QR_LIMIT} dynamische QR-Codes`,
|
|
'Basis-Tracking (Scans + Standort)',
|
|
'Einfache Designs',
|
|
'Unbegrenzte statische QR-Codes',
|
|
],
|
|
limits: {
|
|
dynamicQRCodes: FREE_DYNAMIC_QR_LIMIT,
|
|
staticQRCodes: -1, // unlimited
|
|
teamMembers: 1,
|
|
},
|
|
priceId: null, // No Stripe price for free plan
|
|
},
|
|
PRO: {
|
|
name: 'Pro',
|
|
price: 9,
|
|
priceYearly: 90,
|
|
currency: 'EUR',
|
|
interval: 'month',
|
|
features: [
|
|
'50 QR Codes',
|
|
'Branding (Colors)',
|
|
'Detailed Analytics (Date, Device, City)',
|
|
'CSV Export',
|
|
'SVG/PNG Download',
|
|
],
|
|
limits: {
|
|
dynamicQRCodes: PRO_DYNAMIC_QR_LIMIT,
|
|
staticQRCodes: -1,
|
|
teamMembers: 1,
|
|
},
|
|
priceId: process.env.STRIPE_PRICE_ID_PRO_MONTHLY,
|
|
priceIdYearly: process.env.STRIPE_PRICE_ID_PRO_YEARLY,
|
|
},
|
|
BUSINESS: {
|
|
name: 'Business',
|
|
price: 29,
|
|
priceYearly: 290,
|
|
currency: 'EUR',
|
|
interval: 'month',
|
|
features: [
|
|
'500 QR-Codes',
|
|
'Everything from Pro',
|
|
'Bulk QR Generation (up to 1,000)',
|
|
'Priority Support',
|
|
],
|
|
limits: {
|
|
dynamicQRCodes: BUSINESS_DYNAMIC_QR_LIMIT,
|
|
staticQRCodes: -1,
|
|
teamMembers: 1,
|
|
},
|
|
priceId: process.env.STRIPE_PRICE_ID_BUSINESS_MONTHLY,
|
|
priceIdYearly: process.env.STRIPE_PRICE_ID_BUSINESS_YEARLY,
|
|
},
|
|
} as const;
|
|
|
|
export type PlanType = keyof typeof STRIPE_PLANS;
|
|
|
|
export function getPlanFromStripePriceId(priceId?: string | null): Exclude<PlanType, 'FREE'> | null {
|
|
if (!priceId) {
|
|
return null;
|
|
}
|
|
|
|
if (
|
|
priceId === process.env.STRIPE_PRICE_ID_BUSINESS_MONTHLY ||
|
|
priceId === process.env.STRIPE_PRICE_ID_BUSINESS_YEARLY
|
|
) {
|
|
return 'BUSINESS';
|
|
}
|
|
|
|
if (
|
|
priceId === process.env.STRIPE_PRICE_ID_PRO_MONTHLY ||
|
|
priceId === process.env.STRIPE_PRICE_ID_PRO_YEARLY
|
|
) {
|
|
return 'PRO';
|
|
}
|
|
|
|
return null;
|
|
}
|