Nextjs & Stripe Webhooks Tutorial
Requerimientos
Fundamentos de Next.js: Se requiere un entendimiento básico de Next.js para seguir este tutorial. Next.js es un framework de React utilizado para construir aplicaciones web modernas del lado del servidor. Sin este conocimiento previo, puede resultar difícil comprender y aplicar los conceptos presentados en el tutorial.
Cuenta activa en Stripe: Stripe es un servicio de procesamiento de pagos en línea ampliamente utilizado. Para seguir este tutorial y trabajar con los webhooks de Stripe, es necesario tener una cuenta activa en Stripe. Esto permitirá configurar y probar los webhooks dentro del entorno de desarrollo.
Visual Studio Code instalado: Visual Studio Code es un editor de código fuente altamente popular y versátil que ofrece numerosas funcionalidades y extensiones útiles para el desarrollo de aplicaciones web. Y en este tutorial usaremos principalmente su capacidad para poder exponer un puerto HTTP a traves de un tunel (o dominio con SSL), esto servirá para que Stripe nos notifique cuando ya registro el pago.
Creacion de proyecto de stripe
Pagina para Listar Productos
en app/page.jsx:
import { products } from "./products";
function App() {
return (
<div className="px-44">
<h1 className="text-3xl font-bold text-center my-10">Products</h1>
<div className="grid grid-cols-3 gap-10">
{products.map((product) => (
<div
key={product.id}
className="bg-slate-800 text-center m- p-4 rounded-md"
>
<h2 className="font-bold text-lg">{product.name}</h2>
<p className="text-3xl font-bold">${product.price}</p>
<img src={product.image} alt={product.name} className="w-full" />
<button className="bg-green-600 w-full text-white p-2 rounded-md mt-2">
Buy
</button>
</div>
))}
</div>
</div>
);
}
export default App;
Stripe para Nodejs
Primero instalaremos el modulo de stripe:
npm install stripe
Luego crearemos un archivo para guardar nuestras variables de entorno en la raiz del proyecto con el nombre .env con el siguiente contenido
STRIPE_SECRET_KEY=sk_test_51HbqDqLOpfkDUZDILPJuQ0wblg8AawN4SE9YQlcIwni7d6bCsG3XayUiPQIM879qJlI9xb.....
Debes reemplazar el valor del API Key con el tuyo
crea un archivo en app/api/checkout/route.js
import { NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function POST(request) {
const session = await stripe.checkout.sessions.create({
success_url: "http://localhost:3000/success",
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: "T-shirt",
images: ["https://i.imgur.com/EHyR2nP.png"],
},
unit_amount: 2000,
},
quantity: 1,
},
],
mode: "payment",
});
return NextResponse.json(session);
}
Y en cuanto al Frontend, usara este codigo para ejecutar la API:
"use client";
import { products } from "./products";
function App() {
const handlePayment = async (product) => {
try {
const response = await fetch("/api/checkout", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify(product),
});
const session = await response.json();
console.log(session);
window.location = session.url;
} catch (error) {
console.error("Error:", error);
}
};
return (
<div className="px-44">
<h1 className="text-3xl font-bold text-center my-10">Products</h1>
<div className="grid grid-cols-3 gap-10">
{products.map((product) => (
<div
key={product.id}
className="bg-slate-800 text-center m- p-4 rounded-md"
>
<h2 className="font-bold text-lg">{product.name}</h2>
<p className="text-3xl font-bold">${product.price}</p>
<img src={product.image} alt={product.name} className="w-full" />
<button
className="bg-green-600 w-full text-white p-2 rounded-md mt-2"
onClick={() => handlePayment(product)}
>
Buy
</button>
</div>
))}
</div>
</div>
);
}
export default App;
Crea un archivo de Exito cuando se Pague
Cuando se haga un pago correctamente Stripe espera enviar a una pagina, es por eso que crearemos la siguiente en src/app/success/page.jsx:
export default function SuccessPage() {
return (
<div className="text-3xl font-bold text-center py-10 h-screen flex items-center justify-center">
<div>
<span className="text-7xl">🎉</span>
<h1>Gracias por tu Compra</h1>
<a href="/" className="text-blue-500 block mt-4">
Volver a la tienda
</a>
</div>
</div>
);
}
Actualizaremos los Datos
Ahora tambien esperaremos que el frontend nos envie los datos:
import { NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
export async function POST(request) {
const body = await request.json();
console.log(body);
const session = await stripe.checkout.sessions.create({
success_url: "http://localhost:3000/success",
line_items: [
{
price_data: {
currency: "usd",
product_data: {
name: body.name,
images: [body.image],
},
unit_amount: body.price,
},
quantity: 1,
},
],
mode: "payment",
});
return NextResponse.json(session);
}
Añadiendo un Webhook usando Visual Studio Code
Lo primero que debemos hacer es ejecutar nuestro proyecto con HTTPS, para eso vamos a usar Visual Studio Code y su funcionalidad para crear Puertos Publicos.
Una vez creados nos dara una URL como esta: https://3hsl7jxz-3000.brs.devtunnels.ms/
Luego vamos a añadir esto a Stripe:
https://3hsl7jxz-3000.brs.devtunnels.ms/api/webhook
Luego buscaremos el evento checkout.session.completed:
Luego nos dara un codigo de ejemplo para Nodejs y Express, pero nosotros lo adaptaremos a Nextjs:
El dato de STRIPE_WEBHOOK_SECRET lo debes añadir en el archivo .env, y el valor se obtiene desde la pagina de Stripe en donde se ha añadido el webhook anterior
Crearemos un archivo en src/app/api/webhook.js
import { headers } from "next/headers";
import { NextResponse } from "next/server";
import Stripe from "stripe";
const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);
// This is your Stripe CLI webhook secret for testing your endpoint locally.
const endpointSecret = process.env.STRIPE_WEBHOOK_SECRET;
export async function POST(request) {
const body = await request.text();
const headerList = headers();
const sig = headerList.get("stripe-signature");
console.log(sig);
let event;
try {
event = stripe.webhooks.constructEvent(body, sig, endpointSecret);
} catch (err) {
console.log(err);
return NextResponse.json({ error: err.message }, { status: 400 });
}
console.log(event);
// Handle the event
switch (event.type) {
case "checkout.session.completed":
const checkoutSessionCompleted = event.data.object;
console.log({ checkoutSessionCompleted });
// Then define and call a function to handle the event checkout.session.completed
break;
// ... handle other event types
default:
console.log(`Unhandled event type ${event.type}`);
}
// Return a 200 response to acknowledge receipt of the event
return new Response(null, { status: 200 });
}
Otras opciones para crear un tunel HTTP es usar software como Ngrok o cloudflare tunnel