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:
Section titled “Archivo:”src/api/checkout/controllers/checkout.ts
Checkout Controller
Section titled “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
Section titled “createCheckoutSession: explicación paso a paso”1. Desestructuración del payload del cliente
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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
Section titled “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);