Files
Momento/memento-note/lib/billing/stripe-prices.ts
Antigravity 4fc5e3ce04
All checks were successful
CI / Lint, Unit Tests & Build (push) Successful in 7m5s
CI / Deploy production (on server) (push) Successful in 24s
fix(ui): tarifs publics unifiés et prix annuel lisible
La page tarifs reprend la barre du site public. En annuel, Pro affiche
8,25 € par mois (99 € l’année), plus le 99 € comme gros chiffre.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-09-01 17:43:05 +00:00

110 lines
3.9 KiB
TypeScript

import type { SubscriptionTier } from '@/lib/plan-entitlements';
import { stripe } from '@/lib/stripe';
import { getConfigValue } from '@/lib/config';
import {
DEFAULT_PRICES,
formatBillingAmount,
type BillingInterval,
type BillingTier,
type DynamicPrice,
} from '@/lib/billing/price-catalog';
export type { BillingInterval, BillingTier, DynamicPrice };
export { DEFAULT_PRICES, formatBillingAmount };
export async function isBillingEnabled(): Promise<boolean> {
const flag = await getConfigValue('BILLING_ENABLED', '');
if (flag === 'true') return true;
if (flag === 'false') return false;
return process.env.NEXT_PUBLIC_FEATURE_BILLING_ENABLED === 'true'
|| process.env.NODE_ENV === 'development';
}
export async function getDynamicPrices(): Promise<Record<BillingTier, Record<BillingInterval, DynamicPrice>>> {
const isMock = !process.env.STRIPE_SECRET_KEY || process.env.STRIPE_SECRET_KEY === 'sk_test_placeholder';
if (isMock) {
return DEFAULT_PRICES;
}
const result: Record<BillingTier, Record<BillingInterval, DynamicPrice>> = {
PRO: {
month: { ...DEFAULT_PRICES.PRO.month },
year: { ...DEFAULT_PRICES.PRO.year },
},
BUSINESS: {
month: { ...DEFAULT_PRICES.BUSINESS.month },
year: { ...DEFAULT_PRICES.BUSINESS.year },
},
};
const retrieveAndFormatPrice = async (tier: BillingTier, interval: BillingInterval) => {
try {
const priceId = await resolvePriceId(tier, interval);
const price = await stripe.prices.retrieve(priceId);
if (price.unit_amount !== null && price.unit_amount !== undefined) {
const amount = price.unit_amount / 100;
const currency = price.currency.toUpperCase();
result[tier][interval] = {
display: formatBillingAmount(amount, currency),
amount,
currency,
};
}
} catch (err) {
console.error(`[stripe-prices] Failed to retrieve price for ${tier}/${interval}:`, err);
}
};
await Promise.all([
retrieveAndFormatPrice('PRO', 'month'),
retrieveAndFormatPrice('PRO', 'year'),
retrieveAndFormatPrice('BUSINESS', 'month'),
retrieveAndFormatPrice('BUSINESS', 'year'),
]);
return result;
}
const PRICE_ENV_KEYS: Record<BillingTier, Record<BillingInterval, string>> = {
PRO: {
month: 'STRIPE_PRICE_PRO_MONTHLY',
year: 'STRIPE_PRICE_PRO_ANNUAL',
},
BUSINESS: {
month: 'STRIPE_PRICE_BUSINESS_MONTHLY',
year: 'STRIPE_PRICE_BUSINESS_ANNUAL',
},
};
export async function resolvePriceId(tier: BillingTier, interval: BillingInterval): Promise<string> {
const configKey = PRICE_ENV_KEYS[tier][interval];
const fromDb = await getConfigValue(configKey, '');
const priceId = fromDb || process.env[configKey] || '';
if (priceId) return priceId;
const isMock = !process.env.STRIPE_SECRET_KEY || process.env.STRIPE_SECRET_KEY === 'sk_test_placeholder';
if (isMock && process.env.NODE_ENV !== 'test') {
return `price_mock_${tier.toLowerCase()}_${interval}`;
}
throw new Error(`No Stripe price ID configured for ${tier}/${interval}`);
}
export async function priceIdToTier(priceId: string): Promise<SubscriptionTier | null> {
if (priceId && priceId.startsWith('price_mock_')) {
if (priceId.includes('pro')) return 'PRO';
if (priceId.includes('business')) return 'BUSINESS';
return 'BASIC';
}
const entries: Array<[string, SubscriptionTier]> = [
[(await getConfigValue('STRIPE_PRICE_PRO_MONTHLY', '')) || process.env.STRIPE_PRICE_PRO_MONTHLY || '', 'PRO'],
[(await getConfigValue('STRIPE_PRICE_PRO_ANNUAL', '')) || process.env.STRIPE_PRICE_PRO_ANNUAL || '', 'PRO'],
[(await getConfigValue('STRIPE_PRICE_BUSINESS_MONTHLY', '')) || process.env.STRIPE_PRICE_BUSINESS_MONTHLY || '', 'BUSINESS'],
[(await getConfigValue('STRIPE_PRICE_BUSINESS_ANNUAL', '')) || process.env.STRIPE_PRICE_BUSINESS_ANNUAL || '', 'BUSINESS'],
];
for (const [envPriceId, tier] of entries) {
if (envPriceId && envPriceId === priceId) return tier;
}
return null;
}