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.
Важливо розрізняти три рівні:
- Стандарт (SPEC). Офіційні документи описують, які поля й ендпоінти мають бути, які статуси є допустимими та які гарантії ви зобовʼязуєтеся надавати.
- Архітектурний підхід (ARCH). Наприклад: рішення зберігати SKU і замовлення в окремих таблицях, зробити сервіс‑обгортку навколо ACP або використовувати чергу для вебхуків. Це хороші практики, але вони не є частиною стандарту.
- Конкретна реалізація (приклад 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. У спрощеному вигляді життєвий цикл виглядає так:
- Створення сесії: POST /checkout_sessions.
- Оновлення сесії: POST /checkout_sessions/{id}.
- Завершення сесії (complete): POST /checkout_sessions/{id}/complete.
- (Іноді) Скасування: окремий ендпоінт для 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.
На боці мерчанта в цей момент потрібно:
- Перевірити вхідні дані: переконатися, що всі SKU існують, доступні до продажу та не порушують політик (наприклад, немає алкоголю для неповнолітнього).
- Розрахувати ціни й податки на основі власних правил.
- Підготувати варіанти доставки (fulfillment options), якщо товар фізичний.
- Повернути коректний 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:
- Запитує SPT у PSP (наприклад, через Stripe ACP API).
- Надсилає цей токен у ваш бекенд через 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"
}
}
Ваш бекенд у відповідь має:
- Знайти checkout_session з id checkout_session_123.
- Перевірити, що статус дозволяє завершення (зазвичай ready_for_payment).
- Створити платіж у PSP, використовуючи токен spt_123 (спосіб залежить від PSP; у випадку Stripe — певний endpoint і тип payment method).
- Дочекатися підтвердження платіжної операції.
- Оновити checkout_session до completed, створити та зберегти замовлення, заповнити поле order у структурі сесії.
- Повернути актуальний 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. Цю лекцію можна сприймати як «розкодування» того, що ховається за однією кнопкою «Купити» у віджеті.
Спрощений сценарій:
- Користувач пише: «Підбери цифровий подарунок другу до $50 і відразу оформи покупку».
- Агент (або сам застосунок ChatGPT) використовує Product Feed, щоб знайти відповідні SKU у межах бюджету.
- ChatGPT показує в чаті кілька карток подарунків (через ваш віджет GiftGenius) і пропонує вибрати один.
- Після вибору ChatGPT формує line items і викликає POST /checkout_sessions на вашому ACP‑бекенді, отримуючи checkout_session із сумами та статусом.
- В UI Instant Checkout користувач бачить підсумкову суму, назву товару, політику повернення і кнопку підтвердження.
- Після підтвердження ChatGPT отримує Shared Payment Token у PSP і викликає POST /checkout_sessions/{id}/complete, як ми обговорювали вище.
- Ваш бекенд проводить платіж, створює замовлення та повертає checkout_session зі статусом completed.
- 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_payment → ready_for_payment → completed/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‑бекенд і внутрішня база мають дивитися на одне й те саме джерело правди, навіть якщо поверх нього є різні проєкції та кеші.
ПЕРЕЙДІТЬ В ПОВНУ ВЕРСІЮ