- Full nixpanel-website monorepo: web (React/Vite) + api (Fastify) - Add /install page with one-liner, manual steps, requirements, software table, firewall ports, service management, and update/uninstall - Add Install link to header nav and footer - Add 'Install guide' CTA button to home page hero - Add 'outline' variant to Button component - Add .gitignore and .gitattributes Co-Authored-By: Claude Sonnet 4.6 <[email protected]>
111 lines
3.3 KiB
TypeScript
111 lines
3.3 KiB
TypeScript
import Stripe from 'stripe';
|
|
import { config } from '../config';
|
|
import { query } from '../db';
|
|
|
|
const stripe = new Stripe(config.stripe.secretKey, { apiVersion: '2024-06-20' });
|
|
|
|
// Annual plans are 20% off (pay 10 months, get 12)
|
|
export const PLAN_PRICES: Record<string, { monthly: number; annual: number; label: string }> = {
|
|
free: { monthly: 0, annual: 0, label: 'Free' },
|
|
solo: { monthly: 9.99, annual: 95.90, label: 'Solo' },
|
|
host: { monthly: 29.99, annual: 287.90, label: 'Host' },
|
|
agency: { monthly: 79.99, annual: 767.90, label: 'Agency' },
|
|
};
|
|
|
|
export async function getOrCreateStripeCustomer(data: {
|
|
customerId: string;
|
|
email: string;
|
|
name: string;
|
|
stripeCustomerId?: string | null;
|
|
}): Promise<string> {
|
|
if (data.stripeCustomerId) return data.stripeCustomerId;
|
|
|
|
const stripeCustomer = await stripe.customers.create({
|
|
email: data.email,
|
|
name: data.name,
|
|
metadata: { nixpanel_customer_id: data.customerId },
|
|
});
|
|
|
|
await query(
|
|
'UPDATE customers SET stripe_customer_id = ? WHERE id = ?',
|
|
[stripeCustomer.id, data.customerId]
|
|
);
|
|
|
|
return stripeCustomer.id;
|
|
}
|
|
|
|
export async function createCheckoutSession(data: {
|
|
stripeCustomerId: string;
|
|
priceId: string;
|
|
planKey: string;
|
|
interval: 'monthly' | 'annual';
|
|
serverIp: string;
|
|
customerId: string;
|
|
successUrl: string;
|
|
cancelUrl: string;
|
|
}): Promise<string> {
|
|
const session = await stripe.checkout.sessions.create({
|
|
customer: data.stripeCustomerId,
|
|
mode: 'subscription',
|
|
line_items: [{ price: data.priceId, quantity: 1 }],
|
|
success_url: data.successUrl,
|
|
cancel_url: data.cancelUrl,
|
|
metadata: {
|
|
nixpanel_customer_id: data.customerId,
|
|
plan_key: data.planKey,
|
|
billing_interval: data.interval,
|
|
server_ip: data.serverIp,
|
|
},
|
|
subscription_data: {
|
|
metadata: {
|
|
nixpanel_customer_id: data.customerId,
|
|
plan_key: data.planKey,
|
|
server_ip: data.serverIp,
|
|
},
|
|
},
|
|
});
|
|
|
|
return session.url!;
|
|
}
|
|
|
|
export async function createBillingPortalSession(
|
|
stripeCustomerId: string,
|
|
returnUrl: string
|
|
): Promise<string> {
|
|
const session = await stripe.billingPortal.sessions.create({
|
|
customer: stripeCustomerId,
|
|
return_url: returnUrl,
|
|
});
|
|
return session.url;
|
|
}
|
|
|
|
export async function cancelSubscription(stripeSubscriptionId: string): Promise<void> {
|
|
await stripe.subscriptions.update(stripeSubscriptionId, {
|
|
cancel_at_period_end: true,
|
|
});
|
|
await query(
|
|
'UPDATE subscriptions SET cancel_at_period_end = 1 WHERE stripe_subscription_id = ?',
|
|
[stripeSubscriptionId]
|
|
);
|
|
}
|
|
|
|
export async function changeSubscriptionPlan(data: {
|
|
stripeSubscriptionId: string;
|
|
newPriceId: string;
|
|
}): Promise<void> {
|
|
const sub = await stripe.subscriptions.retrieve(data.stripeSubscriptionId);
|
|
await stripe.subscriptions.update(data.stripeSubscriptionId, {
|
|
items: [{ id: sub.items.data[0].id, price: data.newPriceId }],
|
|
proration_behavior: 'create_prorations',
|
|
});
|
|
}
|
|
|
|
export async function constructWebhookEvent(
|
|
payload: Buffer,
|
|
signature: string
|
|
): Promise<Stripe.Event> {
|
|
return stripe.webhooks.constructEvent(payload, signature, config.stripe.webhookSecret);
|
|
}
|
|
|
|
export { stripe };
|