You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
senzapaura_es/src/lib/server/stripe.ts

109 lines
3.1 KiB

/**
* Pagos con Stripe.
*
* Se usa Checkout alojado en Stripe: la web no ve nunca los datos de la
* tarjeta, no hay formulario de pago propio que mantener y el cumplimiento de
* PCI queda del lado de Stripe.
*
* Configuracion (ver .env.example):
* STRIPE_SECRET_KEY clave secreta de la cuenta
* STRIPE_WEBHOOK_SECRET secreto del endpoint /api/stripe/webhook
*/
import Stripe from 'stripe';
import { env } from '$env/dynamic/private';
import { MONEDA, PRECIO_PACK_CENTIMOS, CANCIONES_POR_PACK } from '$lib/tienda';
export class ErrorDePago extends Error {
constructor(mensaje: string) {
super(mensaje);
this.name = 'ErrorDePago';
}
}
let cliente: Stripe | null = null;
/** `true` si la tienda puede cobrar en este entorno. */
export function pagosConfigurados(): boolean {
return Boolean(env.STRIPE_SECRET_KEY);
}
function stripe(): Stripe {
if (!env.STRIPE_SECRET_KEY) {
throw new ErrorDePago('Los pagos no están configurados en este entorno.');
}
cliente ??= new Stripe(env.STRIPE_SECRET_KEY);
return cliente;
}
export interface PeticionDePago {
packs: number;
email: string;
usuarioId: string;
urlExito: string;
urlCancelacion: string;
/** Slugs comprados, para poder reconstruir el pedido desde el webhook. */
slugs: string[];
}
/**
* Crea la sesion de pago y devuelve su id y la URL a la que redirigir.
*
* El importe no se envia como total: se envia el precio del pack y la
* cantidad, para que la factura de Stripe diga exactamente lo mismo que el
* carrito de la web.
*/
export async function crearSesionDePago(peticion: PeticionDePago): Promise<{
id: string;
url: string;
}> {
const sesion = await stripe().checkout.sessions.create({
mode: 'payment',
customer_email: peticion.email,
client_reference_id: peticion.usuarioId,
line_items: [
{
quantity: peticion.packs,
price_data: {
currency: MONEDA,
unit_amount: PRECIO_PACK_CENTIMOS,
product_data: {
name: `Pack de ${CANCIONES_POR_PACK} canciones`,
description: 'Descarga en MP3 de las canciones elegidas, con todas sus versiones.'
}
}
}
],
metadata: {
usuarioId: peticion.usuarioId,
// Metadato informativo: la fuente de verdad son las filas de
// pedido_item, no esta cadena.
canciones: peticion.slugs.join(',').slice(0, 480)
},
success_url: peticion.urlExito,
cancel_url: peticion.urlCancelacion
});
if (!sesion.url) {
throw new ErrorDePago('Stripe no ha devuelto una URL de pago.');
}
return { id: sesion.id, url: sesion.url };
}
/** Comprueba la firma del webhook y devuelve el evento ya verificado. */
export function verificarEventoDeWebhook(cuerpo: string, firma: string | null): Stripe.Event {
if (!env.STRIPE_WEBHOOK_SECRET) {
throw new ErrorDePago('Falta STRIPE_WEBHOOK_SECRET.');
}
if (!firma) {
throw new ErrorDePago('La petición no lleva firma de Stripe.');
}
return stripe().webhooks.constructEvent(cuerpo, firma, env.STRIPE_WEBHOOK_SECRET);
}
/** Recupera una sesion de pago para comprobar su estado. */
export async function obtenerSesionDePago(id: string): Promise<Stripe.Checkout.Session> {
return stripe().checkout.sessions.retrieve(id);
}

Powered by TurnKey Linux.