CodeGym /Cursos /ChatGPT Apps /ACP / Instant Checkout: el estándar y su implementación e...

ACP / Instant Checkout: el estándar y su implementación en ChatGPT

ChatGPT Apps
Nivel 14 , Lección 3
Disponible

1. Para qué sirve ACP y por qué no es «otro API REST más»

Si lo miramos con cinismo, ACP parece un conjunto de endpoints HTTP normales y estructuras JSON: un /checkout_sessions, algunos webhooks, ciertos tokens. Es fácil pensar: «Vale, es otro API personalizado de otra plataforma». Pero la idea de ACP va más allá.

ACP está concebido como un protocolo abierto de interacción entre tres participantes: la plataforma de IA (por ejemplo, ChatGPT), tu backend de comercio y el proveedor de pagos. Su objetivo es estandarizar cómo describir productos y precios, cómo la IA declara la intención de compra del usuario, cómo se crea una sesión de checkout, cómo se ejecuta el pago y cómo todos los participantes se enteran del estado final.

La idea clave: el mismo backend del comerciante que implemente ACP potencialmente puede funcionar no solo con ChatGPT, sino también con otras plataformas LLM que adopten este estándar. Es decir, no escribes «un API especial para ChatGPT», implementas el protocolo de integración de comercio de la siguiente generación.

Instant Checkout en ChatGPT es la primera gran implementación del estándar ACP. ChatGPT respeta este protocolo, llamando a tus endpoints ACP y mostrando al usuario una UI cuidada, pero las reglas del juego están descritas en las especificaciones de ACP y no «ocultas por la magia de GPT» dentro de una caja negra.

2. Los tres pilares de ACP: Product Feed, Agentic Checkout, Delegated Payment

ACP tiene tres especificaciones principales que mencionaremos constantemente:

Especificación De qué se encarga Dónde aparece en GiftGenius
Product Feed Spec Formato y campos del feed de productos (SKU, precios, stock, enlaces, flags). Feed JSON/CSV con regalos, que indexa OpenAI.
Agentic Checkout Contrato REST para checkout_session: creación, actualización, finalización. Nuestro backend ACP: endpoints /checkout_sessions y webhooks.
Delegated Payment Cómo se transmiten los datos de pago al comerciante en forma de token delegado. Trabajo con Stripe Shared Payment Token al finalizar el pago.

El Product Feed ya lo vimos en lecciones anteriores. Ahora nos interesan los dos últimos bloques: Agentic Checkout y Delegated Payment.

Es importante diferenciar tres niveles:

  1. Estándar (SPEC). Los documentos oficiales describen qué campos y endpoints deben existir, qué estados son válidos y qué garantías te comprometes a dar.
  2. Patrón arquitectónico (ARCH). Por ejemplo, decidir guardar SKU y pedidos en tablas separadas, crear un servicio envoltorio alrededor de ACP o usar una cola para webhooks. Son buenas prácticas, pero no parte del estándar.
  3. Implementación concreta (ejemplo GiftGenius). Es nuestro proyecto didáctico: la estructura de nuestras tablas, los nombres exactos de los tipos en TypeScript, cómo registramos pedidos, etc. Todo esto es un ejemplo, no un documento normativo.

Subrayaremos constantemente dónde termina la SPEC y dónde empieza tu arquitectura, para que no ocurra «vi en la lección un campo persona_tags y supuse que era parte de la especificación oficial».

3. La checkout_session por dentro: estructura y estados

El objeto central de Agentic Checkout Spec es checkout_session en tu backend. Lógicamente es el estado de la compra: qué artículos, por qué importe, con qué opciones de entrega y en qué estado se encuentra el intento de pago.

La especificación describe los campos obligatorios de checkout_session aproximadamente así (formulaciones simplificadas y parcialmente recortadas con respecto al original):

  • id: identificador de sesión en forma de cadena que tú generas y devuelves. ChatGPT lo usará en todas las llamadas posteriores.
  • buyer: información del comprador: nombre, email, teléfono y a veces dirección. En la especificación real este objeto está estructurado para que el PSP y tus sistemas puedan usarlo de forma fiable.
  • status: enum de cadena que refleja el estado actual de la compra. Estados básicos:
    • not_ready_for_payment: aún no se puede pagar (por ejemplo, no se eligió una opción de envío o no se recalcularon impuestos).
    • ready_for_payment: todo listo; se puede solicitar el token de pago y cargar el importe.
    • completed: el pago se realizó con éxito; el pedido fue creado.
    • canceled: la compra fue cancelada (por iniciativa del usuario o por error).
  • currency: código de moneda en formato ISO 4217 en minúsculas ("usd", "eur", etc.).
  • line_items: lista de posiciones del carrito, cada una con su SKU, cantidad y coste calculado.
  • fulfillment_address: dirección de entrega (si aplica).
  • fulfillment_options y fulfillment_option_id: opciones de entrega (o ejecución) disponibles y la opción actualmente elegida.
  • totals: importes agregados: coste de artículos, impuestos, envío, importe total.
  • order: objeto que describe el pedido que se creará tras completar la sesión con éxito.
  • messages: lista de mensajes al usuario que ChatGPT puede mostrar al comprador: por ejemplo, advertencias o errores.
  • links: lista de enlaces, por ejemplo, a la política de devoluciones, Privacy Policy y Terms of Service.

No es obligatorio implementar todos los campos en el demo, pero es importante entender la idea: checkout_session es la «historia y estado actual de un intento de compra», y ChatGPT espera ver en ella todo lo necesario para un UX correcto.

Para simplificar, introduzcamos en nuestro código didáctico un tipo simplificado:

// Modelo simplificado de checkout_session para GiftGenius (no es la SPEC completa)
type GGCheckoutStatus = 'not_ready_for_payment' | 'ready_for_payment' | 'completed' | 'canceled';

type GGLineItem = { skuId: string; quantity: number; total: number };

type GGCheckoutSession = {
  id: string;
  status: GGCheckoutStatus;
  currency: 'usd';
  lineItems: GGLineItem[];
  grandTotal: number;
};

Este modelo es deliberadamente más simple que el oficial, pero resulta perfecto para practicar: aprender a tener presentes los estados y transiciones sin ahogarse en cien campos.

4. Ciclo de vida de la checkout_session

La especificación Agentic Checkout describe varias operaciones sobre checkout_session. En forma simplificada, el ciclo de vida se ve así:

  1. Creación de la sesión: POST /checkout_sessions.
  2. Actualización de la sesión: POST /checkout_sessions/{id}.
  3. Finalización de la sesión (complete): POST /checkout_sessions/{id}/complete.
  4. (A veces) Cancelación: endpoint cancel separado o cambio a canceled mediante actualización.

Visto desde los estados, podríamos dibujar el siguiente diagrama:

stateDiagram-v2
    [*] --> not_ready_for_payment
    not_ready_for_payment --> ready_for_payment: cálculo de envío/impuestos
selección de opciones ready_for_payment --> completed: POST /complete satisfactorio ready_for_payment --> canceled: cancelación por el usuario o error not_ready_for_payment --> canceled: error, datos incompatibles

La creación de la checkout_session suele iniciarla en estado not_ready_for_payment o directamente ready_for_payment si ya se conoce todo lo necesario para el pago (por ejemplo, producto digital sin envío ni impuestos). Las actualizaciones se usan para añadir datos (dirección, cupones, opción de envío) y recalcular importes. La finalización es el momento en el que entra en juego Delegated Payment y se carga el dinero realmente.

Aquí es importante entender la separación de roles:

  • ChatGPT inicia la creación, actualización y finalización de la sesión, basándose en el diálogo con el usuario.
  • Tu backend (comerciante) responde de la lógica de negocio correcta: validación de SKU, disponibilidad, cálculo de precios e impuestos, cambio de estados, creación de pedidos.
  • El PSP (Stripe, etc.) ejecuta el pago real y emite el Shared Payment Token, que el comerciante utiliza para cargar los fondos.

Un poco más adelante superpondremos a este diagrama de estados las peticiones HTTP concretas y pequeños ejemplos de código.

5. Creación de la checkout_session: qué espera exactamente ChatGPT

Cuando ChatGPT (o un agente) decide que el usuario realmente quiere comprar algo, forma los line items basándose en el Product Feed: lista de SKU, cantidad, moneda prevista y, posiblemente, preferencias adicionales de envío. Luego llama a tu endpoint POST /checkout_sessions.

En el lado del comerciante en ese momento hay que:

  1. Validar los datos de entrada: asegurarse de que todos los SKU existen, están disponibles para la venta y no violan políticas (por ejemplo, nada de alcohol para menores).
  2. Calcular precios e impuestos según tus propias reglas.
  3. Preparar opciones de entrega (fulfillment options), si el producto es físico.
  4. Devolver una checkout_session correcta con estado e importes.

Un manejador sencillo en Express para GiftGenius podría verse así:

// Pseudocódigo: creación de una checkout_session simplificada
app.post('/checkout_sessions', async (req, res) => {
  const items = req.body.lineItems as GGLineItem[];  // skuId + quantity
  const pricedItems = await priceItems(items);       // calculamos total por cada SKU
  const grandTotal = sum(pricedItems.map(i => i.total));

  const session: GGCheckoutSession = {
    id: generateId(),
    status: 'ready_for_payment', // para regalos digitales se puede dejar lista para pago de inmediato
    currency: 'usd',
    lineItems: pricedItems,
    grandTotal,
  };

  res.status(201).json(session);
});

Aquí hacemos varias cosas:

  • No confiamos en los precios de entrada del cliente (ChatGPT) y los recalculamos con nuestros datos; esto es crítico para la seguridad del comercio.
  • Generamos nuestro propio id de sesión (por ejemplo, con el prefijo gg_chk_...).
  • Devolvemos el estado ready_for_payment si no hay pasos adicionales (sin envío, impuestos automáticos, modelo simple).

En un backend compatible con ACP real, además devolverías messages, links y el objeto compuesto totals, así como rellenar order (al menos en borrador), tal y como lo describe la especificación.

6. Actualización de la checkout_session e idempotencia

Tras crear la sesión, ChatGPT puede pedir al usuario detalles adicionales: dirección de entrega, aplicación de cupón, cambio de opción de ejecución. Cuando estos datos aparecen, la plataforma hace una llamada POST /checkout_sessions/{id} para que actualices los cálculos.

Desde el punto de vista del código es muy parecido a la creación, pero en lugar de generar una nueva sesión tú:

  • encuentras la existente por id;
  • aplicas los cambios (por ejemplo, cambias fulfillment_option_id o añades un descuento);
  • recalculas los importes;
  • devuelves la checkout_session actualizada.

Es importante que la especificación admite llamadas repetidas (por fallos de red o repeticiones por parte de ChatGPT). Por ello, como en módulos anteriores donde hablamos de idempotencia de herramientas y webhooks, aquí se recomienda usar Idempotency-Key en las cabeceras de la petición y manejar cuidadosamente las repeticiones.

Un manejador hipotético de actualización podría verse así:

app.post('/checkout_sessions/:id', async (req, res) => {
  const id = req.params.id;
  const key = req.header('Idempotency-Key'); // la misma key => el mismo efecto
  const existing = await loadSessionWithIdempotency(id, key, req.body);

  // applyUpdates por dentro puede recalcular precios, envío, etc.
  const updated = await applyUpdates(existing, req.body);
  await saveSession(updated, key);

  res.json(updated);
});

Aquí no seguimos estrictamente una estructura concreta de la SPEC, sino que mostramos la idea: en la entrada — cambios y clave idempotente; en la salida — estado consistente de la checkout_session. Si te llega la misma petición con la misma clave, debes devolver el mismo resultado, sin crear pedidos de más ni duplicados en los logs.

7. Finalización de la checkout_session y Delegated Payment: cómo funciona Shared Payment Token

El momento más interesante y tenso es la finalización de la checkout_session, cuando realmente se carga el dinero. Aquí entra en juego la segunda especificación: Delegated Payment.

La idea de Delegated Payment

El usuario introduce o selecciona el método de pago en la interfaz de ChatGPT (tarjeta, wallet, método guardado). La plataforma no te envía estos datos directamente: en su lugar solicita al PSP (por ejemplo, Stripe) un token especial, Shared Payment Token (SPT), que:

  • está vinculado inequívocamente al comerciante y a la sesión concreta;
  • está limitado por importe y tiempo de vida;
  • no te revela el número real de la tarjeta.

El resultado es el siguiente panorama:

Actor Ve los datos de la tarjeta Ve el Shared Payment Token Ve los detalles del pedido (SKU, importes)
Usuario Sí (los introduce en la UI) No (no es necesario) Parcialmente (qué compra y por cuánto)
ChatGPT/OpenAI Sí (en el proceso de pago)
PSP (Stripe) En el contexto del pago
Comerciante No

Este diseño permite al comerciante evitar almacenar datos de pago y centrarse en la lógica de negocio del pedido, dejando los temas de cumplimiento al PSP y a la plataforma.

Insight

El sentido de Shared Payment Token es ocultar de tu backend los datos de la tarjeta, pero que el pago lo ejecutes tú. Pero puede verse también de otra manera.

Seguro que te has encontrado con la situación en la que una tienda u hotel primero bloquea (hold) el dinero en tu tarjeta y luego lo carga más tarde. Pues bien: piensa en el Shared Payment Token como un token de retención (hold). ChatGPT ha retenido fondos en la cuenta del usuario, pero no los ha cobrado. Te ha entregado ese token de retención y ahora puedes enviarlo a Stripe y cargar el dinero.

Hay dos matices importantes:

  • los importes de retención y de cobro no deberían diferir mucho; lo ideal es que coincidan.
  • puedes vender a través de ChatGPT el primer mes de una suscripción por $1 y luego cobrar cada mes $49.99

Petición POST /checkout_sessions/{id}/complete

Cuando el usuario pulsa el botón de confirmación de pago en Instant Checkout, ChatGPT:

  1. Solicita el SPT al PSP (por ejemplo, mediante el API ACP de Stripe).
  2. Envía ese token a tu backend mediante POST /checkout_sessions/{id}/complete junto con los datos del comprador.

La especificación describe el cuerpo de la petición aproximadamente así (abajo un ejemplo adaptado y abreviado de la documentación oficial):

POST /checkout_sessions/checkout_session_123/complete

{
  "buyer": {
    "first_name": "John",
    "last_name": "Smith",
    "email": "johnsmith@mail.com"
  },
  "payment_data": {
    "token": "spt_123",
    "provider": "stripe"
  }
}

Tu backend debe entonces:

  1. Encontrar la checkout_session con id checkout_session_123.
  2. Comprobar que el estado permite la finalización (normalmente ready_for_payment).
  3. Crear el pago en el PSP usando el token spt_123 (el método depende del PSP; en el caso de Stripe, un endpoint y tipo de método de pago concretos).
  4. Esperar la confirmación de la operación de pago.
  5. Actualizar la checkout_session a completed, crear y guardar el pedido, y rellenar el campo order en la estructura de la sesión.
  6. Devolver la checkout_session actualizada en la respuesta.

En TypeScript muy simplificado podría verse así:

app.post('/checkout_sessions/:id/complete', async (req, res) => {
  const { id } = req.params;
  const { buyer, payment_data } = req.body;
  const session = await loadSession(id);

  await chargeWithSharedToken(payment_data.token, session.grandTotal);
  const completed = await markSessionCompleted(session, buyer);

  res.json(completed);
});

En el mundo real, entre estas líneas se esconden el manejo de errores, reintentos, logging e integración con tu modelo de pedidos.

Si algo sale mal (por ejemplo, el pago es rechazado), debes devolver la checkout_session con estado not_ready_for_payment o canceled y rellenar messages de forma que ChatGPT pueda explicar correctamente al usuario lo que ocurrió.

8. Instant Checkout en ChatGPT: cómo se ensambla todo en un solo flujo

Ahora juntemos estas piezas en un escenario completo «de la intención al pago» en ChatGPT. Puedes entender la lección como «descodificar» lo que hay detrás del botón «Comprar» en el widget.

Escenario simplificado:

  1. El usuario escribe: «Elige un regalo digital para un amigo por hasta $50 y realiza la compra ahora mismo».
  2. El agente (o la propia ChatGPT App) usa el Product Feed para encontrar SKU adecuados dentro del presupuesto.
  3. ChatGPT muestra en el chat varias tarjetas de regalos (a través de tu widget GiftGenius) y propone elegir uno.
  4. Tras la elección, ChatGPT forma los line items y llama a POST /checkout_sessions en tu backend ACP, recibiendo una checkout_session con importes y estado.
  5. En la UI de Instant Checkout el usuario ve el importe final, el nombre del producto, la política de devoluciones y el botón de confirmación.
  6. Al confirmar, ChatGPT obtiene el Shared Payment Token del PSP y llama a POST /checkout_sessions/{id}/complete, como comentamos arriba.
  7. Tu backend procesa el pago, crea el pedido y devuelve la checkout_session con estado completed.
  8. ChatGPT muestra la confirmación al usuario y tu backend (mediante webhooks según Agentic Checkout Spec) puede enviar un evento de vuelta a OpenAI para que la plataforma conozca el destino del pedido.

En forma de diagrama de secuencia se ve así:

sequenceDiagram
    actor U as Usuario
    participant GPT as ChatGPT
    participant GG as GiftGenius ACP backend
    participant PSP as Stripe (PSP)

    U->>GPT: Quiero un regalo de hasta $50 y comprarlo aquí mismo
    GPT->>GG: POST /checkout_sessions (line_items)
    GG-->>GPT: checkout_session (ready_for_payment)
    GPT->>U: Muestra Instant Checkout (producto, precio, ToS)
    U->>GPT: Pulsa «Confirmar el pago»
    GPT->>PSP: Solicita SPT para el importe y el comerciante
    PSP-->>GPT: Shared Payment Token (spt_xxx)
    GPT->>GG: POST /checkout_sessions/{id}/complete (token + buyer)
    GG->>PSP: Pago con SPT
    PSP-->>GG: Pago exitoso
    GG-->>GPT: checkout_session (completed + order)
    GPT-->>U: Muestra confirmación de compra

En este escenario no aparece ninguna llamada «arbitraria» a tu base de datos ni endpoints internos extraños. Todo encaja en el contrato estrictamente descrito por ACP, donde cada participante conoce su papel.

9. Mini práctica: backend ACP simplificado para GiftGenius

Para que esta lección no se quede en pura teoría, es importante «correr mentalmente» la implementación de la capa ACP para nuestro proyecto didáctico.

Imagina que GiftGenius ya tiene:

  • Base de SKU y precios con la que formamos el Product Feed (lo modelamos en lecciones anteriores).
  • Modelo simple de pedidos: tabla orders con campos id, userId, skuId, amount, currency, status, createdAt.
  • Interfaz de ChatGPT App y capa MCP que sabe recomendar regalos (lo construimos en módulos anteriores del curso).

Tu tarea ahora es añadir encima otro pequeño servicio gg-acp:

  • Endpoint POST /checkout_sessions:
    • Recibe la lista de SKU y cantidades.
    • Recalcula los importes a partir de tu BD.
    • Crea un pedido en borrador (por ejemplo, con estado pending) y una checkout_session con estado ready_for_payment.
    • Devuelve la checkout_session.
  • Endpoint POST /checkout_sessions/{id}:
    • Encuentra la sesión y el pedido.
    • Aplica cambios (por ejemplo, soporte de cupón que reduce el total).
    • Devuelve la checkout_session actualizada.
  • Endpoint POST /checkout_sessions/{id}/complete:
    • Recibe el SPT, el importe y los datos del comprador.
    • En la versión demo puede marcar el pedido como «pagado» sin una llamada real al PSP (o puedes simular Stripe).
    • Actualiza la checkout_session al estado completed y la vincula con order_id.

Todo este servicio puede implementarse en una pequeña aplicación Node/Express o en endpoints de Next.js App Router. Lo principal es respetar el contrato de formato y estados, incluso si simulas el pago.

Un modelo de pedido en TypeScript podría verse así:

// Modelo simplificado de pedido de GiftGenius
type GGOrderStatus = 'pending' | 'paid' | 'canceled';

type GGOrder = {
  id: string;
  userId: string;
  skuId: string;
  amount: number;
  currency: 'usd';
  status: GGOrderStatus;
};

En producción, sobre esto aparecerán integraciones con tu Auth/Identity (para saber qué usuario es el del chat), webhooks a OpenAI y escenarios más complejos de devoluciones. Pero como paso didáctico en el marco de esta lección, basta con aprender a recorrer con seguridad el ciclo: crear sesión → actualizar → finalizar, sin perder dinero ni la cordura.

10. Errores típicos al diseñar ACP / Instant Checkout

Error n.º 1: mezclar roles («ChatGPT es mi tienda»).
A veces los desarrolladores mentalmente nombran a ChatGPT «sistema central de registro» e intentan guardar el estado de negocio del pedido del lado de la plataforma: «como existe checkout_session, leeré el historial de pedidos desde OpenAI». Es un callejón sin salida. checkout_session es un objeto del protocolo, no la fuente de la verdad de los pedidos. La fuente de la verdad es tu backend de comercio: ahí deben vivir los pedidos, estados, devoluciones e informes. ChatGPT en este esquema es solo un «frontend de confianza en el chat».

Error n.º 2: confiar en los precios de entrada de ChatGPT.
Es fácil pensar: «el agente ya seleccionó SKU e incluso calculó el total, aceptemos ese importe y cobremos». No se puede. La entrada desde ChatGPT (line items, precios previstos) debe tratarse como una propuesta, no una orden. Tu backend está obligado a verificar por sí mismo los SKU, precios, stock, aplicabilidad de descuentos, etc., comparándolo con el Product Feed y tu BD. De lo contrario, aparecerá una divertida clase de bugs: «el usuario compró un producto por $0.01 porque el modelo decidió redondear».

Error n.º 3: ignorar los estados y la máquina de estados.
En los primeros prototipos a menudo se hace una implementación «con agujeros»: el estado de la sesión siempre es completed, o simplemente ok, y cualquier discrepancia con el estado real del pago se oculta por dentro. Al final, ChatGPT no puede mostrar correctamente al usuario lo que pasa: si el pago sigue en curso, ya terminó o fue cancelado. Es mucho más fiable implementar honestamente la máquina de estados not_ready_for_paymentready_for_paymentcompleted/canceled y devolver el estado real desde el backend, en lugar de inventar campos ad hoc.

Error n.º 4: usar Shared Payment Token como «tarjeta reutilizable».
Por diseño, el SPT es un token de un solo uso o estrictamente limitado: está vinculado a una operación, un importe y un comerciante concretos. Intentar cachearlo «por si acaso» o reutilizarlo para otra compra es mala idea. En el mejor caso el PSP rechazará el segundo intento; en el peor, confundirás la contabilidad de pagos y pedidos. Para cada checkout_session.complete debe haber un token nuevo; si el pago falla, hay que solicitar otro.

Error n.º 5: ausencia de idempotencia en /checkout_sessions y webhooks.
En una red real las peticiones pueden duplicarse: ChatGPT puede repetir POST /checkout_sessions tras un timeout, el PSP puede reenviar un webhook tras un error temporal. Si tu implementación crea cada vez un pedido nuevo y un nuevo registro en la base, enseguida tendrás caos: cargos dobles, pedidos duplicados y discrepancias extrañas entre sistemas. Usar Idempotency-Key, comprobar repeticiones y guardar resultados de llamadas anteriores no es «una optimización opcional», sino un elemento necesario de una integración ACP fiable.

Error n.º 6: olvidar la conexión con el Product Feed.
A veces se diseña la capa ACP «en el vacío»: SKU y precios se toman de tablas internas que no coinciden con lo que entra en el Product Feed. Como resultado, ChatGPT muestra una cosa (según el feed) y en el checkout vía ACP se cuela otra distinta. Para evitar estas sorpresas, es importante que tu modelo de SKU y precios sea único: el feed, el backend ACP y la base interna deben mirar a la misma fuente de la verdad, aunque por encima haya diferentes proyecciones y cachés.

Comentarios
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION