CodeGym /课程 /ChatGPT Apps /ACP / Instant Checkout:标准及其在 ChatGPT 中的实现

ACP / Instant Checkout:标准及其在 ChatGPT 中的实现

ChatGPT Apps
第 14 级 , 课程 3
可用

1. 为什么需要 ACP,它为何不只是“又一个 REST API”

如果从挑剔的角度看,ACP 像是一组普通的 HTTP 端点与 JSON 结构:某个 /checkout_sessions、一些 webhook、一些 token。很容易觉得:“好吧,这又是某个平台的定制 API。”但 ACP 的思想更深。

ACP 被设计为三方之间的开放交互协议:AI 平台(例如 ChatGPT)、你的电商后端以及支付服务提供商(PSP)。它的目标是标准化:如何描述商品与价格、AI 如何声明用户的购买意图、如何创建结账会话、如何执行支付,以及所有参与者如何获知最终状态。

关键点在于:同一个实现了 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 端点以及 webhook。
Delegated Payment 如何以委托 token 的形式把支付数据传递给商户。 在完成支付时与 Stripe 的 Shared Payment Token 协作。

我们在前面的讲座已讨论过 Product Feed。现在关注后两个模块:Agentic CheckoutDelegated Payment

务必区分三个层级:

  1. 标准(SPEC)。官方文档规定了必须有哪些字段与端点、哪些状态合法、你需要提供哪些保证。
  2. 架构模式(ARCH)。例如,把 SKU 和订单分表存储、为 ACP 做一层服务封装、对 webhook 使用消息队列。这些是最佳实践,但不属于标准本身。
  3. 具体实现(GiftGenius 示例)。这是我们的教学项目:表结构、TypeScript 中的具体类型名、如何记录日志等。这些是示例,而非规范性文件。

我们会反复强调 SPEC 与架构/实现之间的边界——以免出现“我在讲座里看到一个 persona_tags 字段,就以为它属于官方规范”的误解。

3. 从内部看 checkout_session:结构与状态

Agentic Checkout Spec 的核心对象是你后端的 checkout_session。从逻辑上看,它代表一次购买的状态:买了哪些商品、金额多少、有哪些配送选项、当前支付尝试到了哪一步。

规范对 checkout_session 的必填字段大致如下(措辞为便于教学而简化,和原文略有差异):

  • id —— 你生成并返回的字符串会话标识。ChatGPT 将在后续调用中使用它。
  • buyer —— 买家信息:姓名、邮箱、电话,有时还有地址。在正式规范中,该对象有明确结构,以便 PSP 与你的系统可靠使用。
  • status —— 反映购买当前状态的字符串枚举。基础状态:
    • 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 —— 给用户展示的消息列表(如提醒或错误),ChatGPT 会使用它们与买家沟通。
  • links —— 链接列表,例如退货政策、隐私政策与服务条款(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)。该 token:

  • 与商户及该会话唯一关联;
  • 对金额与有效期受限;
  • 不会向你暴露真实卡号。

结果是这样的分工:

参与方 能看到卡的支付信息 能看到 Shared Payment Token 能看到订单明细(SKU、金额)
用户 是(在 UI 中输入) 否(不需要) 部分(买了什么、多少钱)
ChatGPT/OpenAI 是(支付流程中)
PSP(Stripe) 在支付上下文内
商户

这种设计让商户无需保存支付卡信息,专注于订单业务逻辑,把合规问题交给 PSP 与平台。

洞见

Shared Payment Token 的意义在于:对你的后端隐藏卡信息,但由你来发起支付。也可以换个角度理解它。

你或许遇到过这样的场景:商店或酒店先在你的卡上做预授权(hold),随后再扣款。可以把 Shared Payment Token 当作预授权 token。ChatGPT 已在用户账户上做了预授权,但未扣款。它把这个预授权 token 交给你,你再把它发给 Stripe 完成扣款。

这里有两个重要细节:

  • 预授权金额与实际扣款金额不应相差太大,最好完全一致。
  • 你可以通过 ChatGPT 以 $1 卖出订阅的首月,随后每月按 $49.99 扣费。

请求 POST /checkout_sessions/{id}/complete

当用户在 Instant Checkout 中点击确认支付时,ChatGPT 会:

  1. 向 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. 使用 spt_123 在 PSP 侧创建支付(具体取决于 PSP;以 Stripe 为例,会有相应的端点与 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);
});

在真实世界里,这几行之间会包含错误处理、重试、日志,以及与你的订单模型的集成。

若出现问题(例如支付被拒),你应返回状态为 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 并调用你的 ACP 后端 POST /checkout_sessions,得到包含金额与状态的 checkout_session
  5. 在 Instant Checkout 的 UI 中,用户看到最终金额、商品名称、退货政策与确认按钮。
  6. 确认时,ChatGPT 向 PSP 获取 Shared Payment Token,并按前述调用 POST /checkout_sessions/{id}/complete
  7. 你的后端完成扣款、创建订单,返回状态为 completedcheckout_session
  8. ChatGPT 展示购买确认;你的后端(依据 Agentic Checkout Spec 的 webhook)可把事件回传给 OpenAI,让平台得知订单的后续。

用 sequence 图表示如下:

sequenceDiagram
    actor U as 用户
    participant GPT as ChatGPT
    participant GG as GiftGenius ACP 后端
    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: 向 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、金额与买家信息。
    • 在演示版中可以不真正对接 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 发送 webhook,以及实现更复杂的退款场景。但在本讲的实践范围内,学会稳定走完“创建会话 → 更新 → 完成”的闭环,并确保不丢钱、逻辑清晰,就足够了。

10. 设计 ACP / Instant Checkout 时的常见错误

错误 1:角色混淆(“ChatGPT 就是我的商店”)。
一些开发者会把 ChatGPT 想象成“中心台账系统”,尝试把订单的业务状态保存在平台侧:“既然有 checkout_session,那我就从 OpenAI 读取订单历史吧。”这条路行不通。checkout_session 是协议对象,不是订单的单一事实来源。单一事实来源应是你的电商后端:订单、状态、退款与报表都应在此。ChatGPT 在这个架构中只是一个“聊天中的可信前端”。

错误 2:信任来自 ChatGPT 的输入价格。
很容易想:“代理已经挑好了 SKU 甚至算出了金额,那就照这个金额扣款吧。”不能这么做。来自 ChatGPT 的输入(line items、预估价格)只能视作建议,而非命令。你的后端必须自行校验 SKU、价格、库存、折扣适用性等,并与 Product Feed 与自有数据库对照。否则你会遇到“模型四舍五入出 bug,用户 $0.01 买走商品”这类问题。

错误 3:忽视状态与状态机。
早期原型里常见“漏水”式实现:会话状态永远是 completed,或干脆自创 ok,把与真实支付状态不一致的地方藏起来。结果是 ChatGPT 无法正确向用户展示进度:支付在路上、已完成还是被取消。更可靠的做法是老老实实实现状态机 not_ready_for_paymentready_for_paymentcompleted/canceled,并从后端返回真实状态,而不是杜撰字段。

错误 4:把 Shared Payment Token 当作“可复用的卡”。
SPT 的设计是一次性或严格受限的 token:绑定了特定操作、金额与商户。尝试“以防万一”缓存它或在另一笔购买中复用,都是坏主意。好的情况,PSP 会拒绝二次使用;坏的情况,你会把支付台账与订单搞乱。每个 checkout_session.complete 都应使用全新的 token;若支付失败,就需要重新申请。

错误 5:在 /checkout_sessions 与 webhook 中缺少幂等性。
真实网络里请求可能被重复: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