CodeGym /課程 /ChatGPT Apps /ACP / Instant Checkout:標準以及在 ChatGPT 中的實作

ACP / Instant Checkout:標準以及在 ChatGPT 中的實作

ChatGPT Apps
等級 14 , 課堂 3
開放

1. 為什麼需要 ACP,且它為什麼不只是「另一個 REST API」

如果用比較犬儒的角度看,ACP 就像一組普通的 HTTP 端點與 JSON 結構:某個 /checkout_sessions、一些 webhooks、一些 token。很容易想:「OK,這又是某個平台的客製 API。」但 ACP 的想法更深。

ACP 被設計成在三方之間的開放式協定:AI 平台(例如 ChatGPT)、你的商家後端(commerce backend),以及支付服務提供商。它的目標是標準化:如何描述產品與價格、AI 如何宣告使用者的購買意圖、如何建立 checkout 工作階段、如何執行付款,以及所有參與者如何得知最終狀態。

關鍵觀念:同一個實作 ACP 的商家後端,理論上不僅能與 ChatGPT 協作,也能與其他支援此標準的 LLM 平台協作。也就是說,你不是在寫「ChatGPT 專用 API」,而是在實作下一代的商務整合協定。

ChatGPT 的 Instant Checkout 是 ACP 標準的第一個大型實作。 ChatGPT 遵循此協定,呼叫你的 ACP 端點並向使用者呈現漂亮的 UI,但遊戲規則寫在 ACP 規範裡,而不是某種「GPT 魔法」藏在黑箱之中。

2. ACP 的三大支柱:Product Feed、Agentic Checkout、Delegated Payment

ACP 有三個我們會不斷提到的核心規範:

規範 職責 在 GiftGenius 中的體現
Product Feed Spec 商品 feed 的格式與欄位(SKU、價格、可用性、連結、旗標)。 由 OpenAI 索引的禮物 JSON/CSV feed。
Agentic Checkout 針對 checkout_session 的 REST 合約:建立、更新、完成。 我們的 ACP 後端:/checkout_sessions 端點與 webhooks。
Delegated Payment 付款資料如何以委派的 token 形式傳遞給商家。 在完成付款時與 Stripe 的 Shared Payment Token 協作。

Product Feed 我們已在前一講解析。接下來關注後兩塊:Agentic CheckoutDelegated Payment

重要的是劃分三個層級:

  1. 標準(SPEC)。 官方文件描述必備欄位與端點、哪些狀態合法,以及你所承諾的保證。
  2. 架構樣式(ARCH)。 例如把 SKU 與訂單放在獨立資料表、為 ACP 設計服務包裝層、或用佇列處理 webhooks。這些是良好實務,但不屬於標準本身。
  3. 具體實作(GiftGenius 範例)。 我們的教學專案:資料表結構、TypeScript 中精確的型別名稱、如何記錄訂單等。這些是示例,而非規範文件。

我們會不斷強調 SPEC 與你自家架構的分界——避免出現「我在講義裡看到欄位 persona_tags,就以為它是官方規範的一部分」這種誤解。

3. 從內部看 checkout_session:結構與狀態

Agentic Checkout 規範的核心物件,是你後端上的 checkout_session。邏輯上它代表一次購買的狀態:有哪些商品、金額多少、有哪些配送選項,以及目前付款嘗試所處的狀態。

規範大致這樣描述 checkout_session 的必要欄位(表述相對簡化,與原文略有出入):

  • id — 由你產生並返回的字串識別碼。ChatGPT 會在之後的所有呼叫中使用它。
  • buyer — 購買者資訊:姓名、email、電話,有時包含地址。在正式規範中,這個物件是結構化的,以便 PSP 與你的系統能可靠地使用。
  • status — 反映當前購買狀態的字串 enum。基礎狀態:
    • not_ready_for_payment — 尚未可付款(例如,未選擇配送方案或未重算稅金)。
    • ready_for_payment — 一切就緒,可以請求付款 token 並扣款。
    • completed — 付款成功,訂單已建立。
    • canceled — 購買已取消(使用者主動或因錯誤)。
  • currency — 以小寫表示的 ISO 4217 貨幣代碼("usd""eur" 等)。
  • line_items — 購物車項目清單,每項包含 SKU、數量與計算後的金額。
  • fulfillment_address — 配送地址(若相關)。
  • fulfillment_optionsfulfillment_option_id — 可用的配送(或履約)選項與當前選中的選項。
  • totals — 聚合金額:商品金額、稅金、運費、總額。
  • order — 描述成功完成會話後將建立之訂單的物件。
  • messages — 可顯示給買家的訊息清單:例如警告或錯誤。
  • links — 連結清單,例如退貨政策、Privacy Policy 與 Terms of Service。

在示範中不一定要實作所有欄位,但要理解核心概念:checkout_session 是「一次購買嘗試的歷史與當前狀態」,而 ChatGPT 期待其中包含提供良好 UX 所需的一切。

為了更容易理解,讓我們在教學程式碼中引入簡化型別:

// GiftGenius 的簡化版 checkout_session 模型(非完整 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(或代理)判斷使用者真的想購買時,它會根據 Product Feed 形成 line items:SKU 清單、數量、預期貨幣,可能還有配送需求。接著它會呼叫你的端點 POST /checkout_sessions

在商家端這時需要:

  1. 驗證輸入:確認所有 SKU 存在、可銷售、且不違反政策(例如未成年者不得購買酒精)。
  2. 依你的規則計算價格與稅金。
  3. 準備配送選項(若為實體商品)。
  4. 回傳正確的 checkout_session(包含狀態與金額)。

GiftGenius 的最簡 Express 處理器可能長這樣:

// 偽代碼:建立簡化的 checkout_session
app.post('/checkout_sessions', async (req, res) => {
  const items = req.body.lineItems as GGLineItem[];  // skuId + quantity
  const pricedItems = await priceItems(items);       // 依每個 SKU 計算 total
  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 的後端中,你還會回傳 messageslinks 與組合的 totals 物件,並填好 order(至少先建立草稿),如規範所述。

6. 更新 checkout_session 與冪等性

建立會話後,ChatGPT 可能會向使用者詢問更多細節:配送地址、套用優惠券、變更履約方案。當這些資料出現時,平台會呼叫 POST /checkout_sessions/{id},以便你更新計算結果。

就程式碼而言這很像建立,但你會改為:

  • id 找到現有會話;
  • 套用變更(例如變更 fulfillment_option_id 或加入折扣);
  • 重算金額;
  • 回傳更新後的 checkout_session

重要的是規範允許重複呼叫(因網路故障或 ChatGPT 重試)。 因此,如同我們在更早的模組中談到工具與 webhook 的冪等性,這裡建議使用請求表頭中的 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)請求特殊 token:Shared Payment Token(SPT),它:

  • 唯一綁定商家與特定會話;
  • 對金額與有效期有限制;
  • 不向你揭露真實卡號。

於是形成這樣的分工:

角色 能否看到卡片支付資訊 能否看到 Shared Payment Token 能否看到訂單明細(SKU、金額)
使用者 是(在 UI 中輸入) 否(不需要) 部分(買什麼與金額)
ChatGPT/OpenAI 是(在付款流程中)
PSP (Stripe) 在付款範圍內
商家

這種設計讓商家無須保存支付卡資料,專注於訂單業務邏輯,合規問題留給 PSP 與平台處理。

洞見

Shared Payment Token 的意義在於:不把卡片資料暴露給你的後端,但付款由你發動。也可以換個角度理解它。

你大概碰過這種情況:商店或飯店先在你的卡上做預授權(hold),之後再扣款。把 Shared Payment Token 當成 hold token:ChatGPT 已先在使用者帳上做了預授權,但尚未實際扣款。它把這個 hold token 交給你,你再把它轉交給 Stripe 完成扣款。

這裡有兩個關鍵細節:

  • 預授權金額與實扣金額不應差太多,最好完全一致。
  • 你可以透過 ChatGPT 以 $1 賣出首月訂閱,之後每月再扣 $49.99。

請求 POST /checkout_sessions/{id}/complete

當使用者在 Instant Checkout 中按下確認付款:

  1. ChatGPT 會向 PSP(例如透過 Stripe ACP API)請求 SPT。
  2. 接著它透過 POST /checkout_sessions/{id}/complete,把這個 token 與買家資料傳給你的後端。

規範大致這樣描述請求本文(以下為自官方文件調整縮寫的示例):

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. 找到 id 為 checkout_session_123checkout_session
  2. 檢查當前狀態是否允許完成(通常為 ready_for_payment)。
  3. 在 PSP 建立付款,使用 spt_123(方式取決於 PSP;以 Stripe 為例,需要指定端點與付款方法類型)。
  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);
});

在真實世界中,這幾行之間還會有錯誤處理、重試、記錄與你自家訂單模型的整合。

若發生問題(例如付款被拒),你應回傳狀態為 not_ready_for_paymentcanceledcheckout_session,並填好 messages,讓 ChatGPT 能正確向使用者解釋發生了什麼事。

8. ChatGPT 的 Instant Checkout:如何串成一條流程

現在把這些拼成在 ChatGPT 中「從意圖到付款」的完整情境。可以把本講視為對小工具上那顆「購買」按鈕背後機制的解碼。

簡化劇本:

  1. 使用者輸入:「幫我挑一個給朋友的數位禮物,預算不超過 $50,並直接完成購買」。
  2. 代理(或 ChatGPT App)使用 Product Feed 在預算內尋找合適的 SKU。
  3. ChatGPT 透過你的 GiftGenius 小工具在對話裡顯示多張禮物卡片,並請使用者選擇其一。
  4. 選定後,ChatGPT 形成 line items 並呼叫 POST /checkout_sessions 到你的 ACP 後端,取得包含金額與狀態的 checkout_session
  5. 在 Instant Checkout 的 UI 中,使用者會看到總額、商品名稱、退貨政策與確認按鈕。
  6. 點擊確認時,ChatGPT 向 PSP 取得 Shared Payment Token,並呼叫 POST /checkout_sessions/{id}/complete,如前所述。
  7. 你的後端完成付款、建立訂單,並回傳狀態為 completedcheckout_session
  8. ChatGPT 向使用者顯示確認,而你的後端(依 Agentic Checkout 規範的 webhooks)可以把事件回推給 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. 小實作:GiftGenius 的簡化版 ACP 後端

為避免本講停留在純理論,重要的是在腦中「走一遍」我們教學專案的 ACP 層實作。

假設 GiftGenius 已經有:

  • SKU 與價格資料庫,據此我們產生 Product Feed(前幾講已建模)。
  • 簡單的訂單模型:資料表 orders,欄位 iduserIdskuIdamountcurrencystatuscreatedAt
  • ChatGPT App 介面與 MCP 層,能推薦禮物(我們在先前模組中已建立)。

現在你的任務——再加上一個小服務 gg-acp

  • 端點 POST /checkout_sessions
    • 接收 SKU 清單與數量。
    • 依你的資料庫重算金額。
    • 建立草稿訂單(例如狀態 pending)與狀態為 ready_for_paymentcheckout_session
    • 回傳 checkout_session
  • 端點 POST /checkout_sessions/{id}
    • 找到會話與訂單。
    • 套用變更(例如支援折扣碼,降低總額)。
    • 回傳更新後的 checkout_session
  • 端點 POST /checkout_sessions/{id}/complete
    • 取得 SPT、金額與買家資料。
    • 在 demo 版中,可以不實際呼叫 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 的 webhooks,以及更複雜的退款情境。但作為本講的學習步驟,能夠穩健地跑一圈:建立會話 → 更新 → 完成,同時不丟錢也不失理性,就足夠了。

10. ACP / Instant Checkout 設計中的常見錯誤

錯誤 #1:角色混淆(「ChatGPT 就是我的商店」)。
有時開發者把 ChatGPT 當成「核心記帳系統」,試圖把訂單的業務狀態放在平台端:「既然有 checkout_session,那我就從 OpenAI 讀訂單歷史吧」。這是死路。checkout_session 是協定物件,不是訂單的真實來源。真實來源是你的商務後端:訂單、狀態、退款與報表都應該在那裡。ChatGPT 在此架構中只是可信的「對話前端」。

錯誤 #2:信任來自 ChatGPT 的輸入價格。
很容易想:「代理已選好 SKU 還算出總額,就照單全收吧。」不可以。來自 ChatGPT 的輸入(line items、預估價格)只能視為建議,而非命令。你的後端必須自行檢查 SKU、價格、可用性、折扣適用性等,並與 Product Feed 與自家資料庫比對。否則你可能遇到各種有趣的錯誤:「使用者以 $0.01 買到商品,因為模型決定做了四捨五入」。

錯誤 #3:忽略狀態與狀態機。
早期原型常見「漏水」實作:會話狀態永遠是 completed,或只是 ok,而真實的付款狀態差異被藏在內部。結果 ChatGPT 無法正確地向使用者呈現正在發生的事:付款還在進行、已完成或已取消。更可靠的做法是誠實實作狀態機 not_ready_for_paymentready_for_paymentcompleted/canceled,並由後端回傳真實狀態,而不是發明臨時欄位。

錯誤 #4:把 Shared Payment Token 當「可重複使用的卡」。
依設計,SPT 是一次性或嚴格受限的 token:綁定特定操作、金額與商家。試圖快取它「以備不時之需」或重用於其他購買,是糟糕的主意。最好的情況是 PSP 直接拒絕第二次嘗試;最糟的是你把付款與訂單的記錄搞亂。每次 checkout_session.complete 都應該有全新的 token;若付款未成功,需重新取得新的 token。

錯誤 #5:在 /checkout_sessions 與 webhooks 中缺乏冪等性。
在真實網路中請求會被重送:ChatGPT 可能在逾時後重送 POST /checkout_sessions,PSP 也可能在暫時性錯誤後重送 webhook。若你的實作每次都建立新的訂單與資料列,很快就會一團混亂:重複扣款、重複訂單,以及系統間的奇怪差異。使用 Idempotency-Key、檢查重送、保存前一次呼叫的結果——這不是「可選的最佳化」,而是打造穩健 ACP 整合的必要元素。

錯誤 #6:忘了與 Product Feed 對齊。
有時 ACP 層被設計在「真空」裡:SKU 與價格取自某些內部資料表,卻與最終進入 Product Feed 的內容不一致。結果 ChatGPT 給使用者看的(依 feed)與透過 ACP 結帳的內容完全對不上。要避免這類情況,你的 SKU 與價格模型務必一致:feed、ACP 後端與內部資料庫應該指向同一真實來源,即使表層有不同投影與快取。

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