这篇补一份 2026 年 8 月前后跑通的 ChatGPT 优惠 Checkout 直卡记录。当时采用“先绑卡,再创建 Checkout”,用来处理特定卡头在结账页直接提交时容易被拒的问题。
声明 :本文参考了开源社区的已有项目。我做的部分主要是把订单创建和自动支付流程串起来。相关接口与风控规则随时可能变化,这里只作协议研究和排错记录,请勿用于非法用途。
现在直卡风控越来越看卡头,单纯先绑卡也不一定好使。这里保留当时的协议链路和实际踩过的坑。
1. 为什么要“先绑卡”
常规的人机结账流程是:用户打开网页,进入 Checkout 界面,在内嵌的 Stripe Elements 里填卡号并提交。但在特定卡头(如测试用的 4513 卡头)下,直接在结账页面付优惠单基本必挂。
社区在摸索中发现了一条解耦路径:同样的卡,如果不在结账页直接填,而是先走独立的绑卡流程将它附加(Attach)到账号里,然后再建优惠 Checkout 去消费这张已存的 pm_(PaymentMethod),扣款就能走通。
flowchart LR
subgraph Direct ["直接结账路径 (易被拒)"]
D1["优惠 Checkout 页面"] --> D2["Stripe Elements 输入卡号"] --> D3["直接扣款风控拦截 ❌"]
end
subgraph Split ["解耦路径 (先绑后结)"]
S1["SetupIntent 独立绑卡"] --> S2["Stripe 校验卡片生成 pm_"] --> S3["Attach 到账号作为默认卡"]
S3 --> S4["创建优惠 Checkout"] --> S5["消费已有 pm_ 完成确认 ✔️"]
end
两条入口走的校验流程不同:
- 直接结账 :在 Checkout 页面输入新卡时,Stripe Radar 和平台会结合即时扣款策略与输入行为做实时反欺诈评估;
- 预先绑定 :通过
/backend-api/payments/payment_method发起的绑定走 StripeSetupIntent,用于验证卡片的基础可用性与后续扣款授权(Off-Session Setup)。两条流程的校验规则不同,对应的商户分片密钥(如 OpenAI 常见的KslHRdbaPg与C6h1nxGoI3)也可能不同。
实现时需要把原本连在一起的动作拆成“独立绑卡”和“引用旧卡付账”两个阶段。
2. 账号会话与上下文隔离
跑自动化时,输入通常是一枚有效的 Access Token(简称 AT)。程序拿 AT 解析出 Account ID 等信息,并为每个执行单元建立独立的会话上下文。
Account Context
├── Access Token (用于调用业务 API)
├── Account ID (标识计费归属)
├── Device ID (模拟客户端设备)
└── Session Cookies (维护身份与防爬会话)
上下文隔离没做好是最容易翻车的地方。Stripe 和平台接口都在核验 IP、Cookie 和 Session 归属,一旦账号 A 的设备 ID 或 Cookie 串到了账号 B 的请求里,轻则报错找不到状态,重则直接连坐风控。
多账号批量跑时可以并行,但针对单个账号,内部的绑卡和结账步骤必须串行加锁执行。
3. 绑卡流程和实际问题
预绑卡的目标是在不产生任何订阅费的前提下,让 Stripe 吐出一枚合法的 pm_... 并把它挂在当前账号名下。
3.1 协议流转链路
- 客户端调平台接口申请
SetupIntent,拿到client_secret和对应的 Stripe Publishable Key(pk_live_...); - 浏览器或无头环境加载 Stripe Elements,把卡号、到期时间和 CVC 直接提交给 Stripe;
- 调
stripe.confirmCardSetup,由 Stripe 完成验证并生成 PaymentMethod ID; - 客户端通知平台将该
pm_设为默认付款方式。
sequenceDiagram
participant Client as 自动化客户端
participant OAI as 平台 API (ChatGPT)
participant Stripe as Stripe 网关
Client->>OAI: POST /payments/payment_method (申请 SetupIntent)
OAI-->>Client: 返回 client_secret 与 pk_live
Client->>Stripe: confirmCardSetup (卡号/CVC 提交)
Stripe-->>Client: 返回成功并生成 pm_xxxx
Client->>OAI: 设置默认付款方式 (Attach)
OAI-->>Client: 确认绑定指令已接收
3.2 三个实际问题
在把这套逻辑串成自动化时,有三个很具体的工程坑:
Publishable Key 缺失
部分全新账号第一次申请 SetupIntent 时,接口只返回了 Intent 本身,没有给渲染组件需要的 Publishable Key。 可以在同一个账号会话里先跑一次轻量级 Checkout 初始化,再回来读取上下文。不能借用其他账号的公钥。
UI 组件复用时的并发冲突
批量处理多个账号时,虽然服务端的 SetupIntent 可以并行准备,但前端页面上通常只有一套 Card Element。如果多个任务同时调 confirmCardSetup,会直接报 in-flight 冲突。
服务端可以并行准备各自的 SetupIntent;到了前端填卡环节,则用单队列逐个确认。卡片确认成功后,再并行发送后续的 Attach 请求。
Stripe 确认与平台 Attach 之间的延迟
stripe.confirmCardSetup 返回成功,只说明 Stripe 已经建好了 pm_,平台自己的账单系统可能暂时还查不到它。
调完“设置默认付款方式”后,需要轮询平台的 saved_payment_methods 接口。目标 pm_ 出现在列表里之后,再创建 Checkout,否则可能出现绑卡成功但结账单暂时看不到卡片的情况。
4. 衔接优惠 Checkout 与金额门禁
卡片 Attach 成功后,才开始创建优惠 Checkout。这里所谓的“提炼”,其实就是把账号本来就有的活动资格变成一张状态明确、能调接口确认的临时订单。
flowchart TD
A["已完成 Attach 的账号"] --> B["创建优惠 Checkout (NL/US等)"]
B --> C["应用 Promotion / 折扣代码"]
C --> D["重新读取 Checkout Snapshot"]
D --> E{"金额与币种门禁校验"}
E -- 异常/非预期扣费 --> F["硬终止并告警 🛑"]
E -- 符合预期 (如 0 元试用) --> G["读取当前 Customer Context"]
G --> H["校验 pm_ 归属并生成 ConfirmationToken"]
H --> I["确认支付 (Confirm)"]
金额检查
为防止活动失效后按原价扣款(比如 20 美元),确认前要检查金额:
- Promotion 接口返回 200 不等于优惠真的生效了;
- 必须重新读一次 Checkout Snapshot 里的
amount_total和currency; - 如果目标是零元试用,应付金额必须为 0;金额不对或显示为正常订阅价时,立即终止任务。
5. 付款前的 PaymentMethod 核对
在最后确认扣款前,必须做双向核对:
- 账号维度的
saved_payment_methods列表; - 当前 Checkout 实例内挂载的
Customer Context付款方式。
两边必须同时包含刚才绑好的那枚 pm_,才能继续调 Stripe 创建 ConfirmationToken(ctoken_...),最后调平台的 /confirm 接口提交。
Saved PaymentMethod (账号侧)
↓
Customer Context (订单侧一致性核验)
↓
Stripe ConfirmationToken (生成临时确认凭证)
↓
ChatGPT Checkout Confirm (平台发起终审)
↓
Stripe PaymentIntent / SetupIntent (网关完成状态结算)
如果是零元试用单,底层确认的基本是 SetupIntent;如果遇到 requires_action(如需要进一步解决挑战验证),程序需要捕获并把挑战转给对应的验证模块,不能看到接口返回 200 就盲目当作成功。
6. 如何判断成功,什么时候重试
整条链路要根据每一步的实际状态判断进度,不能只看请求有没有报错:
| 阶段 | 检查内容 | 失败处理 |
|---|---|---|
| 卡片验证 | 拿到 Stripe 返回的有效 pm_ | 拒卡说明卡头失效,丢弃任务 |
| Attach 挂载 | 账号的 saved_payment_methods 查到目标 ID | 短周期退避轮询,超时则重置会话 |
| 优惠应用 | Checkout Snapshot 里的 amount_total 达标 | 金额不对立刻抛错退出,严禁扣款 |
| 订单关联 | Checkout 看到同一个 pm_ | 重新初始化 Checkout Context |
| 终态确认 | 订单状态变为 Active,或 Intent 为 succeeded | 处理 3DS / hCaptcha 或标记失败 |
网络波动或临时超时可以重试当前步骤。金额不对、Session 过期或 ID 对不上时,应清掉本轮上下文重新开始,不要把上轮留下的 pm_ 或 Cookie 拼到下一轮里。
当时跑通的顺序是先存卡,等 Attach 确认完成,再创建并确认 Checkout。每一步都需要检查对应状态,出错时也比较容易定位。
iDEAL 不需要预先绑卡,后半段会换成 Provider Redirect,单独记在下一篇。

