payments with stripe
Signed-off-by: Omar Sánchez Pizarro <omar.sanchez@pistacero.net>
This commit is contained in:
@@ -1331,6 +1331,7 @@ export async function updateUserSubscription(username, subscriptionData) {
|
||||
subscription: {
|
||||
planId: subscriptionData.planId || 'free',
|
||||
status: subscriptionData.status || 'active',
|
||||
billingPeriod: subscriptionData.billingPeriod || 'monthly',
|
||||
currentPeriodStart: subscriptionData.currentPeriodStart || new Date(),
|
||||
currentPeriodEnd: subscriptionData.currentPeriodEnd || null,
|
||||
cancelAtPeriodEnd: subscriptionData.cancelAtPeriodEnd || false,
|
||||
|
||||
226
web/backend/services/stripe.js
Normal file
226
web/backend/services/stripe.js
Normal file
@@ -0,0 +1,226 @@
|
||||
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_8ebec8c2aa82a791aa9f2cd68211e297a5d172aea62ebd7b771d230e3a597aa8';
|
||||
}
|
||||
|
||||
if (!webhookSecret) {
|
||||
throw new Error('STRIPE_WEBHOOK_SECRET no configurado');
|
||||
}
|
||||
|
||||
return stripeClient.webhooks.constructEvent(payload, signature, webhookSecret);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user