- Added Stripe environment variables to docker-compose.yml for secret key, webhook secret, and base URL. - Created PAYMENTS.md to document the setup and usage of the Stripe payment system. - Updated webhook signature handling in stripe.js to use a new secret key. Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
227 lines
5.8 KiB
JavaScript
227 lines
5.8 KiB
JavaScript
import Stripe from 'stripe';
|
|
import { SUBSCRIPTION_PLANS } from '../config/subscriptionPlans.js';
|
|
|
|
let stripeClient = null;
|
|
|
|
// Inicializar Stripe
|
|
export function initStripe() {
|
|
let stripeSecretKey = process.env.STRIPE_SECRET_KEY;
|
|
|
|
if (!stripeSecretKey) {
|
|
stripeSecretKey = 'sk_test_51SrpOfH73CrYqhOp2NfijzzU07ADADmwigscMVdLGzKu9zA83dsrODhfsaY1X4EFTSihhIB0lVtDQ2HpeOfMWTur00YLuuktSL';
|
|
}
|
|
|
|
if (!stripeSecretKey) {
|
|
console.warn('⚠️ STRIPE_SECRET_KEY no configurado. Los pagos estarán deshabilitados.');
|
|
return null;
|
|
}
|
|
|
|
try {
|
|
stripeClient = new Stripe(stripeSecretKey, {
|
|
apiVersion: '2024-12-18.acacia',
|
|
});
|
|
console.log('✅ Stripe inicializado correctamente');
|
|
return stripeClient;
|
|
} catch (error) {
|
|
console.error('Error inicializando Stripe:', error.message);
|
|
return null;
|
|
}
|
|
}
|
|
|
|
// Obtener cliente de Stripe
|
|
export function getStripeClient() {
|
|
return stripeClient;
|
|
}
|
|
|
|
// Crear sesión de checkout de Stripe
|
|
export async function createCheckoutSession({ planId, billingPeriod, email, userId }) {
|
|
if (!stripeClient) {
|
|
throw new Error('Stripe no está configurado');
|
|
}
|
|
|
|
const plan = SUBSCRIPTION_PLANS[planId];
|
|
if (!plan) {
|
|
throw new Error('Plan no válido');
|
|
}
|
|
|
|
if (planId === 'free') {
|
|
throw new Error('El plan gratuito no requiere pago');
|
|
}
|
|
|
|
// Precio según período de facturación
|
|
const priceAmount = billingPeriod === 'yearly' ? plan.price.yearly : plan.price.monthly;
|
|
const priceCents = Math.round(priceAmount * 100); // Convertir a centavos
|
|
|
|
// Crear precio en Stripe (o usar precio existente si ya lo tienes)
|
|
const priceId = await getOrCreatePrice(planId, billingPeriod, priceCents);
|
|
|
|
// URL de éxito y cancelación
|
|
const baseUrl = process.env.BASE_URL || 'http://localhost';
|
|
const successUrl = `${baseUrl}/dashboard/?session_id={CHECKOUT_SESSION_ID}&payment_success=true`;
|
|
const cancelUrl = `${baseUrl}/dashboard/?payment_cancelled=true`;
|
|
|
|
// Crear sesión de checkout
|
|
const session = await stripeClient.checkout.sessions.create({
|
|
mode: 'subscription',
|
|
line_items: [
|
|
{
|
|
price: priceId,
|
|
quantity: 1,
|
|
},
|
|
],
|
|
success_url: successUrl,
|
|
cancel_url: cancelUrl,
|
|
customer_email: email,
|
|
client_reference_id: userId,
|
|
metadata: {
|
|
userId,
|
|
planId,
|
|
billingPeriod,
|
|
},
|
|
subscription_data: {
|
|
metadata: {
|
|
userId,
|
|
planId,
|
|
billingPeriod,
|
|
},
|
|
},
|
|
});
|
|
|
|
return session;
|
|
}
|
|
|
|
// Obtener o crear precio en Stripe
|
|
async function getOrCreatePrice(planId, billingPeriod, priceCents) {
|
|
const plan = SUBSCRIPTION_PLANS[planId];
|
|
|
|
// Buscar precio existente (usando lookup_key)
|
|
const lookupKey = `${planId}_${billingPeriod}`;
|
|
|
|
try {
|
|
const prices = await stripeClient.prices.list({
|
|
lookup_keys: [lookupKey],
|
|
limit: 1,
|
|
});
|
|
|
|
if (prices.data.length > 0) {
|
|
return prices.data[0].id;
|
|
}
|
|
} catch (error) {
|
|
console.log('Precio no encontrado, creando nuevo...');
|
|
}
|
|
|
|
// Si no existe, crear producto y precio
|
|
let product;
|
|
try {
|
|
// Buscar producto existente
|
|
const products = await stripeClient.products.list({
|
|
limit: 100,
|
|
});
|
|
product = products.data.find(p => p.metadata.planId === planId);
|
|
|
|
// Si no existe, crear producto
|
|
if (!product) {
|
|
product = await stripeClient.products.create({
|
|
name: `Wallabicher ${plan.name}`,
|
|
description: plan.description,
|
|
metadata: {
|
|
planId,
|
|
},
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Error creando/buscando producto:', error);
|
|
throw error;
|
|
}
|
|
|
|
// Crear precio
|
|
const price = await stripeClient.prices.create({
|
|
product: product.id,
|
|
unit_amount: priceCents,
|
|
currency: 'eur',
|
|
recurring: {
|
|
interval: billingPeriod === 'yearly' ? 'year' : 'month',
|
|
},
|
|
lookup_key: lookupKey,
|
|
metadata: {
|
|
planId,
|
|
billingPeriod,
|
|
},
|
|
});
|
|
|
|
return price.id;
|
|
}
|
|
|
|
// Crear portal del cliente para gestionar suscripción
|
|
export async function createCustomerPortalSession(customerId) {
|
|
if (!stripeClient) {
|
|
throw new Error('Stripe no está configurado');
|
|
}
|
|
|
|
const baseUrl = process.env.BASE_URL || 'http://localhost';
|
|
const returnUrl = `${baseUrl}/dashboard/`;
|
|
|
|
const session = await stripeClient.billingPortal.sessions.create({
|
|
customer: customerId,
|
|
return_url: returnUrl,
|
|
});
|
|
|
|
return session;
|
|
}
|
|
|
|
// Cancelar suscripción
|
|
export async function cancelSubscription(subscriptionId) {
|
|
if (!stripeClient) {
|
|
throw new Error('Stripe no está configurado');
|
|
}
|
|
|
|
// Cancelar al final del período
|
|
const subscription = await stripeClient.subscriptions.update(subscriptionId, {
|
|
cancel_at_period_end: true,
|
|
});
|
|
|
|
return subscription;
|
|
}
|
|
|
|
// Reactivar suscripción cancelada
|
|
export async function reactivateSubscription(subscriptionId) {
|
|
if (!stripeClient) {
|
|
throw new Error('Stripe no está configurado');
|
|
}
|
|
|
|
const subscription = await stripeClient.subscriptions.update(subscriptionId, {
|
|
cancel_at_period_end: false,
|
|
});
|
|
|
|
return subscription;
|
|
}
|
|
|
|
// Obtener suscripción por ID
|
|
export async function getSubscription(subscriptionId) {
|
|
if (!stripeClient) {
|
|
throw new Error('Stripe no está configurado');
|
|
}
|
|
|
|
return await stripeClient.subscriptions.retrieve(subscriptionId);
|
|
}
|
|
|
|
// Verificar webhook signature
|
|
export function verifyWebhookSignature(payload, signature) {
|
|
if (!stripeClient) {
|
|
throw new Error('Stripe no está configurado');
|
|
}
|
|
|
|
let webhookSecret = process.env.STRIPE_WEBHOOK_SECRET;
|
|
|
|
if (!webhookSecret) {
|
|
webhookSecret = 'whsec_n6tTKRSG38WJQDRX8jLjZTs7kPKxbdNP';
|
|
}
|
|
|
|
if (!webhookSecret) {
|
|
throw new Error('STRIPE_WEBHOOK_SECRET no configurado');
|
|
}
|
|
|
|
return stripeClient.webhooks.constructEvent(payload, signature, webhookSecret);
|
|
}
|
|
|