UNPKG

sysrot-hub

Version:

CLI de nueva generaciΓ³n para proyectos Next.js 14+ con IA multi-modelo, Web3 integration, internacionalizaciΓ³n completa y roadmap realista 2025-2026

373 lines (305 loc) β€’ 10.9 kB
import { NextApiRequest, NextApiResponse } from 'next'; import { buffer } from 'micro'; import Stripe from 'stripe'; import { PrismaClient } from '@prisma/client'; const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, { apiVersion: '2023-10-16' }); const prisma = new PrismaClient(); // Disable body parsing for webhook verification export const config = { api: { bodyParser: false, }, }; async function sendOrderConfirmationEmail(order: any) { // TODO: Implement email sending logic using your preferred service // Examples: SendGrid, AWS SES, Nodemailer, etc. console.log(`πŸ“§ Sending order confirmation email for order ${order.orderNumber}`); // Example implementation using a hypothetical email service /* await emailService.send({ to: order.user.email, template: 'order-confirmation', data: { orderNumber: order.orderNumber, customerName: order.user.name, items: order.items, total: order.total, shippingAddress: order.shippingAddress } }); */ } async function updateInventory(orderId: string) { try { // Get order items const order = await prisma.order.findUnique({ where: { id: orderId }, include: { items: true } }); if (!order) { console.error('Order not found for inventory update:', orderId); return; } // Update stock for each item for (const item of order.items) { await prisma.product.update({ where: { id: item.productId }, data: { stock: { decrement: item.quantity } } }); console.log(`πŸ“¦ Updated inventory for product ${item.productId}: -${item.quantity}`); } } catch (error) { console.error('Error updating inventory:', error); } } async function handlePaymentIntentSucceeded(paymentIntent: Stripe.PaymentIntent) { try { console.log('πŸ’³ Payment succeeded:', paymentIntent.id); // Find the order by payment intent ID const order = await prisma.order.findFirst({ where: { paymentId: paymentIntent.id }, include: { user: true, items: { include: { product: true } } } }); if (!order) { console.error('Order not found for payment intent:', paymentIntent.id); return; } // Update order status const updatedOrder = await prisma.order.update({ where: { id: order.id }, data: { paymentStatus: 'PAID', status: order.status === 'PENDING' ? 'CONFIRMED' : order.status, paidAt: new Date() }, include: { user: true, items: { include: { product: true } } } }); console.log(`βœ… Order ${order.orderNumber} marked as paid`); // Update inventory await updateInventory(order.id); // Send confirmation email await sendOrderConfirmationEmail(updatedOrder); // Log analytics event console.log(`πŸ“Š Analytics: Order completed - ${order.orderNumber}, Amount: $${order.total}`); } catch (error) { console.error('Error handling payment success:', error); } } async function handlePaymentIntentPaymentFailed(paymentIntent: Stripe.PaymentIntent) { try { console.log('❌ Payment failed:', paymentIntent.id); // Find the order by payment intent ID const order = await prisma.order.findFirst({ where: { paymentId: paymentIntent.id } }); if (!order) { console.error('Order not found for failed payment:', paymentIntent.id); return; } // Update order status await prisma.order.update({ where: { id: order.id }, data: { paymentStatus: 'FAILED', status: 'CANCELLED' } }); console.log(`❌ Order ${order.orderNumber} marked as failed`); // TODO: Send payment failure notification email // TODO: Log analytics event for failed payment } catch (error) { console.error('Error handling payment failure:', error); } } async function handleChargeDispute(charge: Stripe.Charge) { try { console.log('⚠️ Charge dispute created:', charge.id); // Find the order by charge ID const paymentIntent = await stripe.paymentIntents.retrieve(charge.payment_intent as string); const order = await prisma.order.findFirst({ where: { paymentId: paymentIntent.id } }); if (!order) { console.error('Order not found for disputed charge:', charge.id); return; } // Update order status await prisma.order.update({ where: { id: order.id }, data: { paymentStatus: 'DISPUTED', notes: `${order.notes || ''}\n\nDispute created on ${new Date().toISOString()}` } }); console.log(`⚠️ Order ${order.orderNumber} marked as disputed`); // TODO: Send notification to admin about dispute // TODO: Log analytics event for dispute } catch (error) { console.error('Error handling charge dispute:', error); } } async function handleInvoicePaymentSucceeded(invoice: Stripe.Invoice) { try { console.log('πŸ’° Invoice payment succeeded:', invoice.id); // Handle subscription payments or recurring billing if (invoice.subscription) { console.log(`πŸ“… Subscription payment: ${invoice.subscription}`); // TODO: Update subscription status in database // TODO: Send subscription renewal confirmation } } catch (error) { console.error('Error handling invoice payment:', error); } } async function handleCustomerSubscriptionUpdated(subscription: Stripe.Subscription) { try { console.log('πŸ“… Subscription updated:', subscription.id); // TODO: Update subscription status in database // Handle subscription changes, cancellations, etc. } catch (error) { console.error('Error handling subscription update:', error); } } async function handleRefund(refund: Stripe.Refund) { try { console.log('πŸ’Έ Refund processed:', refund.id); // Get the charge and payment intent const charge = await stripe.charges.retrieve(refund.charge as string); const paymentIntent = await stripe.paymentIntents.retrieve(charge.payment_intent as string); // Find the order const order = await prisma.order.findFirst({ where: { paymentId: paymentIntent.id }, include: { items: true } }); if (!order) { console.error('Order not found for refund:', refund.id); return; } // Determine refund status const isFullRefund = refund.amount === order.total * 100; // Stripe uses cents const newPaymentStatus = isFullRefund ? 'REFUNDED' : 'PARTIALLY_REFUNDED'; const newOrderStatus = isFullRefund ? 'REFUNDED' : order.status; // Update order await prisma.order.update({ where: { id: order.id }, data: { paymentStatus: newPaymentStatus, status: newOrderStatus, refundedAt: new Date(), refundAmount: (order.refundAmount || 0) + (refund.amount / 100) } }); console.log(`πŸ’Έ Order ${order.orderNumber} refund processed: $${refund.amount / 100}`); // If full refund, restore inventory if (isFullRefund) { for (const item of order.items) { await prisma.product.update({ where: { id: item.productId }, data: { stock: { increment: item.quantity } } }); } console.log(`πŸ“¦ Inventory restored for order ${order.orderNumber}`); } // TODO: Send refund confirmation email // TODO: Log analytics event } catch (error) { console.error('Error handling refund:', error); } } export default async function handler(req: NextApiRequest, res: NextApiResponse) { if (req.method !== 'POST') { res.setHeader('Allow', 'POST'); return res.status(405).end('Method Not Allowed'); } const signature = req.headers['stripe-signature'] as string; const webhookSecret = process.env.STRIPE_WEBHOOK_SECRET; if (!webhookSecret) { console.error('Missing STRIPE_WEBHOOK_SECRET environment variable'); return res.status(500).json({ error: 'Webhook secret not configured' }); } let event: Stripe.Event; try { // Get raw body as buffer const rawBody = await buffer(req); // Verify webhook signature event = stripe.webhooks.constructEvent(rawBody, signature, webhookSecret); } catch (error) { console.error('Webhook signature verification failed:', error); return res.status(400).json({ error: 'Invalid signature' }); } console.log(`πŸ”” Received webhook: ${event.type}`); try { // Handle different event types switch (event.type) { case 'payment_intent.succeeded': await handlePaymentIntentSucceeded(event.data.object as Stripe.PaymentIntent); break; case 'payment_intent.payment_failed': await handlePaymentIntentPaymentFailed(event.data.object as Stripe.PaymentIntent); break; case 'charge.dispute.created': await handleChargeDispute(event.data.object as Stripe.Charge); break; case 'invoice.payment_succeeded': await handleInvoicePaymentSucceeded(event.data.object as Stripe.Invoice); break; case 'customer.subscription.updated': await handleCustomerSubscriptionUpdated(event.data.object as Stripe.Subscription); break; case 'charge.refunded': // Get the refund from the charge object const charge = event.data.object as Stripe.Charge; if (charge.refunds?.data?.[0]) { await handleRefund(charge.refunds.data[0]); } break; // Handle other important events case 'payment_method.attached': console.log('πŸ’³ Payment method attached'); break; case 'customer.created': console.log('πŸ‘€ Customer created'); break; case 'checkout.session.completed': console.log('πŸ›’ Checkout session completed'); // TODO: Handle checkout session completion if using Stripe Checkout break; default: console.log(`ℹ️ Unhandled event type: ${event.type}`); } // Respond with success res.status(200).json({ received: true, type: event.type }); } catch (error) { console.error('Error processing webhook:', error); res.status(500).json({ error: 'Webhook processing failed', type: event.type }); } finally { await prisma.$disconnect(); } }