Checkout Controller
Este controlador se encarga de gestionar la creación de sesiones de pago a través de Stripe en un entorno gestionado con Strapi. Utiliza información del usuario registrada en la base de datos y permite crear metadatos personalizados para enriquecer las transacciones. También valida que la transacción pertenezca a un vertical permitido.
Archivo:
src/api/checkout/controllers/checkout.ts
Checkout Controller
import { CheckoutPayload } from "type/checkout";import { FIELD_ROOT } from "../../../constants/structures/collections/user";import { Stripe } from "stripe";import { validateVertical } from "../../../utils/verify-vertical";import { PROD, LAAB, CHECKOUT, CHECKOUT_STRIPE_SERVICES, CHECKOUT_VALIDATION_KEYS,} from "../../../constants/structures/apis/checkout";import { selectStripeCrm, syncCustomerWithStripe,} from "../../../utils/stripe-crm";import { ERROR_VERTICAL } from "../../../constants/errors";import { validateKeys, validateRequiredFields,} from "../../../utils/api-validation-queries";
export default { async createCheckoutSession(ctx) { try { validateKeys(ctx.request.body, CHECKOUT_VALIDATION_KEYS, FIELD_ROOT); validateRequiredFields(ctx.request.body, CHECKOUT_VALIDATION_KEYS);
const { payment_method_types, line_items, mode, success_url, cancel_url, customer_email, payment_intent_data, metadata, vertical, origin, }: CheckoutPayload = ctx.request.body;
const stripe = await selectStripeCrm(origin);
let customer: Stripe.Customer; customer = await syncCustomerWithStripe(stripe, customer_email, origin);
payment_intent_data.metadata = {}; await strapi .service(CHECKOUT) .updateMetadataUnified( line_items, PROD, null, payment_intent_data, vertical, origin );
await strapi .service(CHECKOUT) .updateMetadataUnified( null, LAAB, metadata.laab_fetch, payment_intent_data, vertical, origin );
const validVertical = validateVertical(vertical); if (!validVertical) ctx.throw(400, ERROR_VERTICAL);
const checkoutSession = await strapi .service(CHECKOUT_STRIPE_SERVICES) .createCheckoutSession( customer, payment_method_types, line_items, mode, success_url, cancel_url, payment_intent_data, validVertical, origin );
ctx.send(checkoutSession, 200); } catch (error) { return ctx.badRequest(error); } },};- Este es el checkout controller, el que se encarga de toda la logica del checkout
createCheckoutSession: explicación paso a paso
1. Desestructuración del payload del cliente
Se extraen todos los campos necesarios desde ctx.request.body, tipado con CheckoutPayload. Esto incluye los datos del cliente, la información del pago, metadatos, vertical y el origen (CRM).
const { payment_method_types, line_items, mode, success_url, cancel_url, customer_email, payment_intent_data, metadata, vertical, origin,}: CheckoutPayload = ctx.request.body;2. Validación de campos obligatorios
Antes de continuar, se asegura que todos los campos requeridos estén presentes. Así como no se incluyas campos que no están permitidos, para mas información vease Aquí.
validateKeys(ctx.request.body, CHECKOUT_VALIDATION_KEYS, FIELD_ROOT);validateRequiredFields(ctx.request.body, CHECKOUT_VALIDATION_KEYS);3. Selección dinámica del cliente de Stripe
Con selectStripeCrm, se selecciona la instancia correspondiente de Stripe (permite trabajar con múltiples cuentas Stripe).
const stripe = await selectStripeCrm(origin);4. Sincronización del cliente con Stripe
Se llama a la función syncCustomerWithStripe, que se encarga de verificar si el cliente ya existe en Stripe y, si no, lo crea. Esta función también actualiza el stripeID en Strapi.
Se consulta si el cliente ya existe en la base de datos de Stripe (vinculada a ese CRM).
export async function syncCustomerWithStripe( stripe: stripeType, email: string, origin: StripeCrmName): Promise<stripeType.Customer> { const userStrapi = await findOne( USER, FIELDS_USER, { email: email }, COMPONENTS_USER ); if (userStrapi === NOT_FOUND) throw new Error(ERROR_USER_NOT_FOUND);
let customer: stripeType.Customer;
const user = await getStripeIdByCrm(email, origin); const customer_stripe: stripeType.ApiList<stripeType.Customer> = await strapi .service(USER_STRIPE_SERVICE) .searchCustomer(stripe, email);
if (user?.stripeID === null && customer_stripe.data.length === 0) { customer = await strapi .service(USER_STRIPE_SERVICE) .createCustomer(stripe, user);
updateStripeID(customer.id, origin, userStrapi); } else if (user?.stripeID === null && customer_stripe.data.length !== 0) { customer = customer_stripe.data[0];
updateStripeID(customer.id, origin, userStrapi); } else { customer = customer_stripe.data[0]; }
return customer;}Se evalúan los casos:
- Si no tiene
stripeIDy no existe en Stripe → se crea un nuevo cliente. - Si no tiene
stripeIDpero sí existe en Stripe → se actualiza elstripeIDen Strapi. - Si ya tiene
stripeID, se reutiliza el cliente.
if (...) { customer = await strapi.service(...).createCustomer(...); updateStripeID(...);} else if (...) { customer = customer_stripe.data[0]; updateStripeID(...);} else { customer = customer_stripe.data[0];}La función updateStripeID se encarga de actualizar los identificadores de Stripe dentro del objeto del usuario en Strapi.
Esto retorna una lista con los resultados de clientes encontrados.
5. Inicialización de metadatos
Antes de añadir metadatos, se inicializa el objeto metadata del payment_intent_data.
payment_intent_data.metadata = {};6. Agregado de metadatos personalizados
Usando la función updateMetadataUnified, se agregan productos y laabs (información personalizada) como metadatos con prefijos (PROD y LAAB) dentro de payment_intent_data.metadata.
await strapi .service(CHECKOUT) .updateMetadataUnified( line_items, PROD, null, payment_intent_data, vertical, origin );
await strapi .service(CHECKOUT) .updateMetadataUnified( null, LAAB, metadata.laab_fetch, payment_intent_data, vertical, origin );Esta función convierte los elementos a JSON y los guarda con claves como PROD1, PROD2, etc.
7. Validación de vertical
Valida que el vertical que se envía exista en la lista de verticales permitidos definida en constantes. Si no es válido, lanza un error 400.
const validVertical = validateVertical(vertical);if (!validVertical) ctx.throw(400, ERROR_VERTICAL);La función validateVertical busca si el nombre enviado se encuentra dentro del arreglo VERTICAL.
8. Creación de sesión de checkout en Stripe
Se invoca el servicio createCheckoutSession con todos los parámetros reunidos anteriormente, incluyendo cliente, datos del pago, URLs y metadatos.
const checkoutSession = await strapi .service(CHECKOUT_STRIPE_SERVICES) .createCheckoutSession( customer, payment_method_types, line_items, mode, success_url, cancel_url, payment_intent_data, validVertical, origin );El servicio interno de Stripe genera la sesión utilizando su SDK, con detalles como:
- Campos personalizados (
custom_fields), - Métodos de pago,
- Dirección de facturación,
- Metadatos,
- Configuración para guardar métodos de pago.
- Origin para determinar el API token que usará
9. Envío de la respuesta
Si todo va bien, se retorna la sesión de Stripe al cliente con estado HTTP 200. En caso de error, se captura y se envía igualmente.
ctx.send(checkoutSession, 200);