JavaRush /Курсы /ChatGPT Apps /ACP / Instant Checkout: стандарт и его реализация в ChatG...

ACP / Instant Checkout: стандарт и его реализация в ChatGPT

ChatGPT Apps
14 уровень , 3 лекция
Открыта

1. Зачем нужен ACP и почему это не просто «ещё один REST API»

Если смотреть цинично, ACP выглядит как набор обычных HTTP‑эндпоинтов и JSON‑структур: какой‑то /checkout_sessions, какие‑то webhooks, какие‑то токены. Легко подумать: «Окей, это ещё один кастомный API от очередной платформы». Но идея ACP глубже.

ACP задуман как открытый протокол взаимодействия между тремя участниками: платформой ИИ (например, ChatGPT), вашим commerce‑backend’ом и платёжным провайдером. Его цель — стандартизировать, как описывать продукты и цены, как ИИ объявляет о намерении пользователя купить, как создаётся сессия checkout, как исполняется платёж и как все участники узнают итоговый статус.

Ключевая мысль: один и тот же backend мерчанта, реализующий ACP, потенциально может работать не только с ChatGPT, но и с другими LLM‑платформами, которые поддержат этот стандарт. То есть вы не пишете «специальный API для ChatGPT», вы реализуете протокол commerce‑интеграции следующего поколения.

Instant Checkout в ChatGPT — это первая крупная реализация стандарта ACP. ChatGPT соблюдает этот протокол, вызывая ваши ACP‑эндпоинты и показывая пользователю красивый UI, но сами правила игры описаны в спецификациях ACP, а не зашиты «магией GPT» куда‑то внутрь чёрного ящика.

2. Три кита ACP: Product Feed, Agentic Checkout, Delegated Payment

У ACP есть три основные спецификации, которые мы будем постоянно упоминать:

Спецификация За что отвечает Где это проявляется в GiftGenius
Product Feed Spec Формат и поля товарного фида (SKU, цены, наличие, ссылки, флаги). JSON/CSV фид с подарками, который индексирует OpenAI.
Agentic Checkout REST‑контракт для checkout_session: создание, обновление, финал. Наш ACP backend: эндпоинты /checkout_sessions и webhooks.
Delegated Payment Как платёжные данные передаются мерчанту в виде делегированного токена. Работа со Stripe Shared Payment Token при завершении платежа.

Product Feed мы уже разобрали в предыдущих лекциях. Теперь нас интересуют два последних блока: Agentic Checkout и Delegated Payment.

Важно разделять три уровня:

  1. Стандарт (SPEC). Официальные документы описывают, какие поля и эндпоинты должны быть, какие статусы легальны и какие гарантии вы обязуетесь давать.
  2. Архитектурный паттерн (ARCH). Например, решение хранить SKU и заказы в отдельных таблицах, завести сервис‑обёртку вокруг ACP или использовать очередь для webhooks. Это хорошие практики, но не часть стандарта.
  3. Конкретная реализация (пример GiftGenius). Это наш учебный проект: структура наших таблиц, точные имена типов в TypeScript, как мы логируем заказы и т.п. Всё это пример, а не нормативный документ.

Мы будем постоянно подчеркивать, где заканчивается SPEC и начинается ваша архитектура — чтобы не вышло «я увидел в лекции поле persona_tags и решил, что это часть официальной спеки».

3. Checkout session изнутри: структура и статусы

Центральный объект Agentic Checkout Spec — это checkout_session на вашем бэкенде. Логически это состояние покупки: какие товары, на какую сумму, с какими вариантами доставки и в каком статусе сейчас находится попытка оплаты.

Спека описывает обязательные поля checkout_session примерно так (формулировки упрощены и частично сокращены по сравнению с оригиналом):

  • id — строковый идентификатор сессии, который вы генерируете и возвращаете. ChatGPT будет использовать его во всех последующих вызовах.
  • buyer — информация о покупателе: имя, email, телефон, иногда адрес. В реальной спека этот объект структурирован, чтобы PSP и ваши системы могли его надёжно использовать.
  • status — строковый enum, отражающий текущее состояние покупки. Базовые статусы:
    • not_ready_for_payment — ещё нельзя платить (например, не выбран вариант доставки или не пересчитаны налоги).
    • ready_for_payment — всё готово, можно запрашивать платёжный токен и списывать деньги.
    • completed — платёж прошёл успешно, заказ создан.
    • canceled — покупка отменена (по инициативе пользователя или по ошибке).
  • currency — код валюты в формате ISO 4217 в нижнем регистре ("usd", "eur" и т.п.).
  • line_items — список позиций в корзине, каждая со своим SKU, количеством и вычисленной стоимостью.
  • fulfillment_address — адрес доставки (если релевантно).
  • fulfillment_options и fulfillment_option_id — возможные варианты доставки (или исполнения) и текущий выбранный вариант.
  • totals — агрегированные суммы: стоимость товаров, налоги, доставка, итоговая сумма.
  • order — объект, описывающий заказ, который будет создан после успешного завершения сессии.
  • messages — список пользовательских сообщений, которые ChatGPT может показать покупателю: например, предупреждения или ошибки.
  • links — список ссылок, например, на политику возвратов, Privacy Policy и Terms of Service.

Нам не обязательно реализовывать в демо все поля, но важно понимать идею: checkout_session — это «история и текущее состояние одной попытки покупки», и ChatGPT ожидает видеть в нём всё, что нужно для корректного UX.

Чтобы было проще, давайте введём в нашем учебном коде упрощённый тип:

// Упрощённая модель checkout_session для GiftGenius (не полная SPEC)
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;
};

Эта модель заведомо проще официальной, но отлично подходит для практики: учиться держать в голове статусы и переходы, не тоня в сотне полей.

4. Жизненный цикл checkout_session

Спецификация Agentic Checkout описывает несколько операций над checkout_session. В упрощённом виде жизненный цикл выглядит так:

  1. Создание сессии: POST /checkout_sessions.
  2. Обновление сессии: POST /checkout_sessions/{id}.
  3. Завершение сессии (complete): POST /checkout_sessions/{id}/complete.
  4. (Иногда) Отмена: отдельный cancel‑эндпоинт или перевод в canceled через обновление.

Глядя со стороны стейтов, можно нарисовать такую диаграмму:

stateDiagram-v2
    [*] --> not_ready_for_payment
    not_ready_for_payment --> ready_for_payment: расчёт доставки/налогов
выбор опций ready_for_payment --> completed: успешный POST /complete ready_for_payment --> canceled: отмена пользователем или ошибка not_ready_for_payment --> canceled: ошибка, несовместимые данные

Создание checkout_session обычно начинает её в состоянии not_ready_for_payment или сразу ready_for_payment, если всё, что нужно для оплаты, уже известно (например, цифровой товар без доставки и налогов). Обновления используются, чтобы добавить данные (адрес, промокоды, вариант доставки) и пересчитать суммы. Завершение — это момент, когда в ход идёт Delegated Payment и деньги действительно списываются.

Здесь важно понимать разделение ролей:

  • ChatGPT инициирует создание, обновления и завершение сессии, основываясь на диалоге с пользователем.
  • Ваш backend (мерчант) отвечает за корректную бизнес‑логику: проверку SKU, доступности, расчёт цен и налогов, смену статусов, создание заказов.
  • PSP (Stripe и др.) проводит реальный платёж и выдаёт Shared Payment Token, который мерчант использует для списания средств.

Чуть дальше мы наложим на эту стейт‑диаграмму конкретные HTTP‑запросы и крошечные примеры кода.

5. Создание checkout_session: что именно ждёт от нас ChatGPT

Когда ChatGPT (или агент) решил, что пользователь реально хочет что‑то купить, он формирует набор line items на основе Product Feed: список SKU, количество, предполагаемая валюта и, возможно, дополнительные пожелания к доставке. Затем он вызывает ваш эндпоинт POST /checkout_sessions.

На стороне мерчанта в этот момент нужно:

  1. Провалидировать входные данные: убеждаемся, что все SKU существуют, доступны к продаже, не нарушают политику (например, нет алкоголя для несовершеннолетнего).
  2. Рассчитать цены и налоги на основе собственных правил.
  3. Подготовить варианты доставки (fulfillment options), если товар физический.
  4. Вернуть корректный checkout_session со статусом и суммами.

Простейший обработчик на Express для GiftGenius может выглядеть так:

// Псевдокод: создание упрощённой checkout_session
app.post('/checkout_sessions', async (req, res) => {
  const items = req.body.lineItems as GGLineItem[];  // skuId + quantity
  const pricedItems = await priceItems(items);       // считаем total по каждому SKU
  const grandTotal = sum(pricedItems.map(i => i.total));

  const session: GGCheckoutSession = {
    id: generateId(),
    status: 'ready_for_payment', // для цифровых подарков можно сразу готовить к оплате
    currency: 'usd',
    lineItems: pricedItems,
    grandTotal,
  };

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

Здесь мы делаем несколько вещей:

  • Не доверяем входным ценам от клиента (ChatGPT) и пересчитываем их по своим данным — это критично для безопасности commerce.
  • Генерируем собственный id сессии (например, префикс gg_chk_...).
  • Возвращаем статус ready_for_payment, если у нас нет дополнительных шагов (нет доставки, автоматические налоги, простая модель).

В реальном ACP‑совместимом backend’е вы дополнительно вернёте messages, links и составной объект totals, а также заполните order (хотя бы в черновом виде), как это описано в спецификации.

6. Обновление checkout_session и идемпотентность

После создания сессии ChatGPT может попросить пользователя о дополнительных деталях: адрес доставки, применение купона, смена варианта исполнения. Когда эти данные появляются, платформа делает вызов POST /checkout_sessions/{id}, чтобы вы обновили расчёты.

С точки зрения кода это очень похоже на создание, но вместо генерации новой сессии вы:

  • находите существующую по id;
  • применяете изменения (например, меняете fulfillment_option_id или добавляете скидку);
  • пересчитываете суммы;
  • возвращаете обновлённый checkout_session.

Важно, что спецификация допускает повторные вызовы (из‑за сетевых сбоев или повторов со стороны ChatGPT). Поэтому, как и в более ранних модулях, где мы говорили об идемпотентности инструментов и webhooks, здесь рекомендуется использовать Idempotency-Key в заголовках запроса и обрабатывать повторы аккуратно.

Условный обработчик обновления мог бы выглядеть так:

app.post('/checkout_sessions/:id', async (req, res) => {
  const id = req.params.id;
  const key = req.header('Idempotency-Key'); // один и тот же key => один эффект
  const existing = await loadSessionWithIdempotency(id, key, req.body);

  // applyUpdates внутри может пересчитать цены, доставку и т.п.
  const updated = await applyUpdates(existing, req.body);
  await saveSession(updated, key);

  res.json(updated);
});

Здесь мы не жёстко следуем конкретной структуре SPEC, а показываем идею: на входе — изменения и идемпотентный ключ, на выходе — консистентное состояние checkout_session. Если к вам придёт такой же запрос с тем же ключом, вы должны вернуть тот же результат, не создавая лишних заказов или дублей в логах.

7. Завершение checkout_session и Delegated Payment: как работает Shared Payment Token

Самый интересный и нервный момент — завершение checkout_session, когда деньги действительно списываются. Здесь вступает в игру вторая спецификация: Delegated Payment.

Идея Delegated Payment

Пользователь вводит или выбирает платёжные данные в интерфейсе ChatGPT (карта, кошелёк, сохранённый метод оплаты). Платформа не шлёт эти данные вам напрямую — вместо этого она запрашивает у PSP (например, Stripe) специальный токен, Shared Payment Token (SPT), который:

  • однозначно связан с мерчантом и конкретной сессией;
  • ограничен по сумме и времени жизни;
  • не раскрывает вам реальный номер карты.

В результате получается такая картина:

Актор Видит платёжные реквизиты карты Видит Shared Payment Token Видит детали заказа (SKU, суммы)
Пользователь Да (вводит их в UI) Нет (не нужно) Частично (что покупает и за сколько)
ChatGPT/OpenAI Да (в процессе оплаты) Да Да
PSP (Stripe) Да Да В рамках платежа
Мерчант Нет Да Да

Такой дизайн позволяет мерчанту избежать хранения платёжных реквизитов и сосредоточиться на бизнес‑логике заказа, оставив compliance‑проблемы PSP и платформе.

Insight

Смысл Shared Payment Token в том, чтобы скрыть от вашего бэкенда данные карты, но платеж проводили именно вы. Но можно смотреть на него и немного по другому.

Думаю вы сталкивались с ситуацией когда магазин или отель сначала hold'ит деньги на вашей карте, а затем через время списывает их. Так вот: рассматривайте Shared Payment Token как hold-токен. ChatGPT захолдил деньги на счету пользователя, но не списывал их. Он передал вам этот hold-токен и теперь вы можете переслать его Stripe и списать деньги.

Тут есть два важных нюанса:

  • суммы hold и списания не должны сильно отличаться, а лучше вообще должны совпадать.
  • вы можете продать через ChatGPT первый месяц подписки за $1, а потом списывать каждый месяц по $49.99

Запрос POST /checkout_sessions/{id}/complete

Когда пользователь нажимает кнопку подтверждения оплаты в Instant Checkout, ChatGPT:

  1. Запрашивает SPT у PSP (например, через Stripe ACP API).
  2. Отправляет этот токен в ваш backend через POST /checkout_sessions/{id}/complete вместе с данными покупателя.

Спека описывает тело запроса примерно так (ниже адаптированный и сокращённый пример из официальной документации):

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"
  }
}

Ваш backend на это должен:

  1. Найти checkout_session с id checkout_session_123.
  2. Проверить, что статус позволяет завершение (обычно ready_for_payment).
  3. Создать платёж у PSP, используя токен spt_123 (способ зависит от PSP, в случае Stripe — определённый endpoint и тип payment method).
  4. Дождаться подтверждения платёжной операции.
  5. Обновить checkout_session до completed, создать и сохранить заказ, заполнить поле order в структуре сессии.
  6. Вернуть актуальный checkout_session в ответе.

В очень упрощённом TypeScript‑псевдокоде это могло бы выглядеть так:

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);
});

В реальном мире между этими строками спрячутся обработка ошибок, повторные попытки, логирование и интеграция с вашей моделью заказов.

Если что‑то идёт не так (например, платёж отклонён), вы должны вернуть checkout_session со статусом not_ready_for_payment или canceled и заполнить messages так, чтобы ChatGPT мог корректно объяснить пользователю, что произошло.

8. Instant Checkout в ChatGPT: как всё собирается в один поток

Теперь давайте сложим эти кусочки в цельный сценарий «от намерения до оплаты» в ChatGPT. Лекцию можно воспринимать как «раскодирование» того, что скрывается за одной кнопкой «Купить» в виджете.

Упрощённый сценарий:

  1. Пользователь пишет: «Подбери цифровой подарок другу до $50 и сразу оформи покупку».
  2. Агент (или сам ChatGPT App) использует Product Feed, чтобы найти подходящие SKU в пределах бюджета.
  3. ChatGPT показывает в чате несколько карточек подарков (через ваш виджет GiftGenius) и предлагает выбрать один.
  4. После выбора ChatGPT формирует line items и вызывает POST /checkout_sessions на вашем ACP backend’е, получая checkout_session с суммами и статусом.
  5. В UI Instant Checkout пользователь видит итоговую сумму, название товара, политику возврата и кнопку подтверждения.
  6. При подтверждении ChatGPT получает Shared Payment Token у PSP и вызывает POST /checkout_sessions/{id}/complete, как мы обсуждали выше.
  7. Ваш backend проводит платёж, создаёт заказ, возвращает checkout_session со статусом completed.
  8. ChatGPT показывает пользователю подтверждение, а ваш backend (через webhooks по Agentic Checkout Spec) может отправить событие обратно в OpenAI, чтобы платформа знала о судьбе заказа.

В виде sequence‑диаграммы это выглядит так:

sequenceDiagram
    actor U as Пользователь
    participant GPT as ChatGPT
    participant GG as GiftGenius ACP backend
    participant PSP as Stripe (PSP)

    U->>GPT: Хочу подарок до $50 и купить прямо здесь
    GPT->>GG: POST /checkout_sessions (line_items)
    GG-->>GPT: checkout_session (ready_for_payment)
    GPT->>U: Показывает Instant Checkout (товар, цена, ToS)
    U->>GPT: Нажимает «Подтвердить оплату»
    GPT->>PSP: Запрос SPT для суммы и мерчанта
    PSP-->>GPT: Shared Payment Token (spt_xxx)
    GPT->>GG: POST /checkout_sessions/{id}/complete (token + buyer)
    GG->>PSP: Платёж с SPT
    PSP-->>GG: Платёж успешен
    GG-->>GPT: checkout_session (completed + order)
    GPT-->>U: Показывает подтверждение покупки

В этом сценарии нигде не появляется «произвольный» вызов вашей базы или странных внутренних эндпоинтов. Всё укладывается в строго описанный контракт ACP, где каждый участник знает свою роль.

9. Мини‑практика: упрощённый ACP backend для GiftGenius

Чтобы эта лекция не осталась чистой теорией, важно ментально «прокрутить» реализацию ACP‑слоя для нашего учебного проекта.

Представьте, что у GiftGenius уже есть:

  • База SKU и цен, на основе которой мы формируем Product Feed (мы это моделировали в прошлых лекциях).
  • Простая модель заказов: таблица orders с полями id, userId, skuId, amount, currency, status, createdAt.
  • Интерфейс ChatGPT App и MCP‑слой, который умеет рекомендовать подарки (мы строили это в предыдущих модулях курса).

Теперь ваша задача — добавить поверх этого ещё один маленький сервис gg-acp:

  • Эндпоинт POST /checkout_sessions:
    • Принимает список SKU и количество.
    • Пересчитывает суммы на основе вашей БД.
    • Создаёт черновой заказ (например, со статусом pending) и checkout_session со статусом ready_for_payment.
    • Возвращает checkout_session.
  • Эндпоинт POST /checkout_sessions/{id}:
    • Находит сессию и заказ.
    • Применяет изменения (например, поддержка промокода, который уменьшает итоговую сумму).
    • Возвращает обновлённый checkout_session.
  • Эндпоинт POST /checkout_sessions/{id}/complete:
    • Получает SPT, сумму и данные покупателя.
    • В демо‑версии может просто помечать заказ как «оплачен» без реального интеграционного вызова к PSP (или вы можете симулировать Stripe).
    • Обновляет checkout_session в статус completed и привязывает к нему order_id.

Весь этот сервис можно реализовать в одном маленьком Node/Express‑приложении или в эндпоинтах Next.js App Router. Главное — соблюдать контракт по формату и статусам, даже если вы эмулируете платёж.

Условная модель заказа в TypeScript может выглядеть так:

// Упрощённая модель заказа GiftGenius
type GGOrderStatus = 'pending' | 'paid' | 'canceled';

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

В продакшене поверх этого появятся связи с вашим Auth/Identity (чтобы знать, каким пользователем является чат), webhooks к OpenAI и более сложные сценарии возвратов. Но как учебный шаг в рамках этой лекции достаточно научиться уверенно ходить по кругу: создать сессию → обновить → завершить, не теряя при этом деньги и здравый смысл.

10. Типичные ошибки при проектировании ACP / Instant Checkout

Ошибка №1: смешивание ролей («ChatGPT — это мой магазин»).
Иногда разработчики мысленно назначают ChatGPT «центральной системой учёта» и пытаются хранить бизнес‑состояние заказа на стороне платформы: «ну там же есть checkout_session, значит и историю заказов я буду читать из OpenAI». Это путь в никуда. checkout_session — это объект протокола, а не источник правды о заказах. Источник правды — ваш commerce backend: именно там должны жить заказы, статусы, возвраты и отчёты. ChatGPT в этой схеме всего лишь доверенный «фронтенд в чате».

Ошибка №2: доверие входным ценам от ChatGPT.
Легко подумать: «агент уже подобрал SKU и даже подсчитал сумму, давайте просто примем эту сумму и спишем деньги». Так делать нельзя. Вход из ChatGPT (line items, предполагаемые цены) нужно воспринимать как предложение, а не как приказ. Ваш backend обязан сам проверить SKU, цены, наличие, применимость скидок и т.п., сравнив это с Product Feed и своей БД. Иначе у вас появится весёлый класс багов «пользователь купил товар за $0.01, потому что модель решила округлить».

Ошибка №3: игнорирование статусов и state‑машины.
В ранних прототипах часто делают «дырявую» реализацию: статус сессии всегда completed, или просто ok, а любые расхождения с фактическим платёжным состоянием скрывают внутри. В итоге ChatGPT не может корректно отобразить пользователю, что происходит: платёж ещё в пути, уже завершился или был отменён. Гораздо надёжнее честно реализовать стейт‑машину not_ready_for_paymentready_for_paymentcompleted/canceled и возвращать реальный статус из backend’а, а не выдумывать свои ad‑hoc поля.

Ошибка №4: использование Shared Payment Token как «многоразовой карты».
SPT по задумке — одноразовый или строго ограниченный токен: он привязан к конкретной операции, сумме и мерчанту. Пытаться кэшировать его «на всякий случай» или использовать повторно для другой покупки — плохая идея. В лучшем случае PSP откажет во второй попытке; в худшем — вы запутаете учёт платежей и заказы. Для каждого checkout_session.complete должен быть свой свежий токен, а если платёж не удался — нужно запрашивать новый.

Ошибка №5: отсутствие идемпотентности в /checkout_sessions и webhooks.
В реальной сети запросы могут дублироваться: ChatGPT может повторить POST /checkout_sessions после таймаута, PSP может повторно отправить webhook после временной ошибки. Если ваша реализация каждый раз создаёт новый заказ и новую запись в базе, вы быстро получите хаос: двойные списания, дубли заказов и странные расхождения между системами. Использование Idempotency-Key, проверка повторов и хранение результатов предыдущих вызовов — это не «опциональная оптимизация», а необходимый элемент надёжной ACP‑интеграции.

Ошибка №6: забыли про связь с Product Feed.
Иногда ACP‑слой проектируют «в вакууме»: SKU и цены берут из каких‑то внутренних таблиц, которые не совпадают с тем, что попадает в Product Feed. В результате ChatGPT показывает пользователю одно (по фиду), а в checkout через ACP прокатывается совсем другое. Чтобы избежать таких сюрпризов, важно, чтобы ваша модель SKU и цен была единой: feed, ACP backend и внутренняя база должны смотреть на один и тот же источник правды, даже если поверх него есть разные проекции и кэши.

1
Задача
ChatGPT Apps, 14 уровень, 3 лекция
Недоступна
Кнопка, которая открывает Instant Checkout (requestCheckout)
Кнопка, которая открывает Instant Checkout (requestCheckout)
1
Задача
ChatGPT Apps, 14 уровень, 3 лекция
Недоступна
ACP create-session — сервер сам считает цены и поддерживает Idempotency-Key
ACP create-session — сервер сам считает цены и поддерживает Idempotency-Key
Комментарии
ЧТОБЫ ПОСМОТРЕТЬ ВСЕ КОММЕНТАРИИ ИЛИ ОСТАВИТЬ КОММЕНТАРИЙ,
ПЕРЕЙДИТЕ В ПОЛНУЮ ВЕРСИЮ