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

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

ChatGPT Apps
Рівень 14 , Лекція 3
Відкрита

1. Навіщо потрібен ACP і чому це не просто «ще один REST API»

Якщо дивитися прагматично, ACP може здатися набором звичайних HTTP‑ендпоінтів і JSON‑структур: якийсь /checkout_sessions, якісь вебхуки, якісь токени. Легко подумати: «Гаразд, це ще один нестандартний API від чергової платформи». Але ідея ACP значно глибша.

ACP задумано як відкритий протокол взаємодії між трьома учасниками: платформою ШІ (наприклад, ChatGPT), вашим commerce‑бекендом і платіжним провайдером. Мета протоколу — стандартизувати, як описувати товари й ціни, як ШІ повідомляє про намір користувача купити, як створюється checkout‑сесія, як виконується платіж і як усі учасники дізнаються підсумковий статус.

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

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‑бекенд: ендпоінти /checkout_sessions і вебхуки.
Delegated Payment Як платіжні дані передаються мерчанту у вигляді делегованого токена. Робота зі Stripe Shared Payment Token під час завершення платежу.

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

Важливо розрізняти три рівні:

  1. Стандарт (SPEC). Офіційні документи описують, які поля й ендпоінти мають бути, які статуси є допустимими та які гарантії ви зобовʼязуєтеся надавати.
  2. Архітектурний підхід (ARCH). Наприклад: рішення зберігати SKU і замовлення в окремих таблицях, зробити сервіс‑обгортку навколо ACP або використовувати чергу для вебхуків. Це хороші практики, але вони не є частиною стандарту.
  3. Конкретна реалізація (приклад GiftGenius). Це наш навчальний проєкт: структура таблиць, точні назви типів у TypeScript, як ми ведемо логування замовлень тощо. Усе це — приклад, а не нормативний документ.

Ми постійно підкреслюватимемо, де закінчується SPEC і починається ваша архітектура. Це важливо, щоб не вийшло так: «я побачив у лекції поле persona_tags і вирішив, що це частина офіційної специфікації».

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

Центральний обʼєкт Agentic Checkout Spec — це checkout_session на вашому бекенді. По суті, це стан покупки: які товари обрано, на яку суму, які є варіанти доставки та в якому статусі зараз перебуває спроба оплати.

Специфікація описує обовʼязкові поля checkout_session приблизно так (формулювання спрощені й частково скорочені порівняно з оригіналом):

  • id — рядковий ідентифікатор сесії, який ви генеруєте та повертаєте. ChatGPT використовуватиме його в усіх наступних викликах.
  • buyer — інформація про покупця: імʼя, електронна пошта, телефон, інколи адреса. У реальній специфікації цей обʼєкт структуровано так, щоб 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 — список посилань, наприклад, на політику повернень, політику конфіденційності та умови користування.

Нам не обовʼязково реалізовувати в демо всі поля. Але важливо розуміти саму ідею: 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 ініціює створення, оновлення й завершення сесії, спираючись на діалог із користувачем.
  • Ваш бекенд (мерчант) відповідає за коректну бізнес‑логіку: перевірку 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) і перераховуємо їх за своїми даними — це критично для безпеки електронної комерції.
  • Генеруємо власний id сесії (наприклад, префікс gg_chk_...).
  • Повертаємо статус ready_for_payment, якщо нам не потрібні додаткові кроки (немає доставки, автоматичні податки, проста модель).

У реальному ACP‑сумісному бекенді ви додатково повернете messages, links і зібраний обʼєкт totals, а також заповните order (хоча б у чернетковому вигляді) — так, як це описано у специфікації.

6. Оновлення checkout_session та ідемпотентність

Після створення сесії ChatGPT може попросити користувача про додаткові деталі: адресу доставки, застосування купона, зміну варіанта виконання. Коли ці дані зʼявляються, платформа викликає POST /checkout_sessions/{id}, щоб ви оновили розрахунки.

З погляду коду це дуже схоже на створення, але замість генерації нової сесії ви:

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

Важливо, що специфікація допускає повторні виклики — наприклад, через мережеві збої або повторні спроби з боку ChatGPT. Тому, як і в попередніх модулях, де ми говорили про ідемпотентність інструментів і вебхуків, тут рекомендують використовувати 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) Так Так У межах платежу
Мерчант Ні Так Так

Такий дизайн дає змогу мерчанту не зберігати платіжні реквізити й зосередитися на бізнес‑логіці замовлення, а питання відповідності вимогам — залишити PSP та платформі.

Інсайт

Сенс Shared Payment Token у тому, щоб приховати від вашого бекенду дані картки, але щоб платіж проводили саме ви. Водночас на це можна подивитися й трохи інакше.

Ймовірно, ви стикалися із ситуацією, коли магазин або готель спочатку резервує кошти на картці, а вже потім, через певний час, списує їх. Розглядайте Shared Payment Token як токен резервування. ChatGPT зарезервував кошти на рахунку користувача, але ще не списав їх. Він передав вам цей токен — і тепер ви можете надіслати його до Stripe та списати гроші.

Тут є два важливі нюанси:

  • суми резервування та списання не повинні суттєво відрізнятися; у ідеалі вони мають збігатися;
  • водночас ви можете продати через ChatGPT перший місяць підписки за $1, а далі списувати щомісяця по $49,99.

Запит POST /checkout_sessions/{id}/complete

Коли користувач натискає кнопку підтвердження оплати в Instant Checkout, ChatGPT:

  1. Запитує SPT у PSP (наприклад, через Stripe ACP API).
  2. Надсилає цей токен у ваш бекенд через 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"
  }
}

Ваш бекенд у відповідь має:

  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) використовує Product Feed, щоб знайти відповідні SKU у межах бюджету.
  3. ChatGPT показує в чаті кілька карток подарунків (через ваш віджет GiftGenius) і пропонує вибрати один.
  4. Після вибору ChatGPT формує line items і викликає POST /checkout_sessions на вашому ACP‑бекенді, отримуючи checkout_session із сумами та статусом.
  5. В UI Instant Checkout користувач бачить підсумкову суму, назву товару, політику повернення і кнопку підтвердження.
  6. Після підтвердження ChatGPT отримує Shared Payment Token у PSP і викликає POST /checkout_sessions/{id}/complete, як ми обговорювали вище.
  7. Ваш бекенд проводить платіж, створює замовлення та повертає checkout_session зі статусом completed.
  8. ChatGPT показує користувачеві підтвердження. А ваш бекенд (через вебхуки за 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 (щоб знати, ким є користувач у чаті), вебхуки до OpenAI та складніші сценарії повернень. Але як навчального кроку в межах цієї лекції достатньо впевнено пройти коло: створити сесію → оновити → завершити, не втрачаючи при цьому гроші й здоровий глузд.

10. Типові помилки під час проєктування ACP / Instant Checkout

Помилка № 1: змішування ролей («ChatGPT — це мій магазин»).
Іноді розробники подумки призначають ChatGPT «центральною системою обліку» й намагаються зберігати бізнес‑стан замовлення на боці платформи: «ну там же є checkout_session, значить і історію замовлень я читатиму з OpenAI». Це шлях у нікуди. checkout_session — обʼєкт протоколу, а не джерело правди про замовлення. Джерело правди — ваш commerce‑бекенд: саме там мають жити замовлення, статуси, повернення й звіти. ChatGPT у цій схемі — лише довірений «фронтенд у чаті».

Помилка № 2: довіра вхідним цінам від ChatGPT.
Легко подумати: «агент уже підібрав SKU і навіть підрахував суму — давайте просто приймемо її та спишемо гроші». Так робити не можна. Вхід із ChatGPT (line items, очікувані ціни) варто сприймати як пропозицію, а не як наказ. Ваш бекенд зобовʼязаний сам перевірити SKU, ціни, наявність, застосовність знижок тощо, звіривши це з Product Feed і своєю БД. Інакше у вас зʼявиться веселий клас багів: «користувач купив товар за $0,01, бо модель вирішила округлити».

Помилка № 3: ігнорування статусів і машини станів.
У ранніх прототипах часто роблять «діряву» реалізацію: статус сесії завжди completed або просто ok, а будь‑які розбіжності з фактичним платіжним станом ховають усередині. У результаті ChatGPT не може коректно показати користувачеві, що відбувається: платіж ще в процесі, уже завершився чи був скасований. Значно надійніше чесно реалізувати машину станів not_ready_for_paymentready_for_paymentcompleted/canceled і повертати реальний статус із бекенду, а не вигадувати свої ad‑hoc поля.

Помилка № 4: використання Shared Payment Token як «багаторазової картки».
SPT за задумом — одноразовий або суворо обмежений токен: він привʼязаний до конкретної операції, суми й мерчанта. Намагатися кешувати його «про всяк випадок» або використовувати повторно для іншої покупки — погана ідея. У кращому разі PSP відмовить у другій спробі. У гіршому — ви заплутаєте облік платежів і замовлень. Для кожного checkout_session.complete має бути свій свіжий токен. А якщо платіж не вдався — потрібно запитувати новий.

Помилка № 5: відсутність ідемпотентності в /checkout_sessions і вебхуках.
У реальній мережі запити можуть дублюватися: ChatGPT може повторити POST /checkout_sessions після тайм‑ауту, а PSP може повторно надіслати вебхук після тимчасової помилки. Якщо ваша реалізація щоразу створює нове замовлення і новий запис у базі, ви швидко отримаєте хаос: подвійні списання, дублікати замовлень і дивні розбіжності між системами. Використання Idempotency-Key, перевірка повторів і збереження результатів попередніх викликів — це не «необовʼязкова оптимізація», а необхідний елемент надійної ACP‑інтеграції.

Помилка № 6: забули про звʼязок із Product Feed.
Іноді ACP‑шар проєктують «у вакуумі»: SKU і ціни беруть із внутрішніх таблиць, які не збігаються з тим, що потрапляє в Product Feed. У результаті ChatGPT показує користувачеві одне (за фідом), а в checkout через ACP проходить зовсім інше. Щоб уникнути таких сюрпризів, важливо, щоб ваша модель SKU і цін була єдиною: фід, ACP‑бекенд і внутрішня база мають дивитися на одне й те саме джерело правди, навіть якщо поверх нього є різні проєкції та кеші.

Коментарі
ЩОБ ПОДИВИТИСЯ ВСІ КОМЕНТАРІ АБО ЗАЛИШИТИ КОМЕНТАР,
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ