点击开始动手实验


背景分析:手动充值流程的痛点

去年冬天,我为了把团队的 ChatGPT 账号统一升级到 Plus,连续三天蹲守抢号,结果不是卡在信用卡 3D 验证,就是页面直接 429。最离谱的一次,支付成功却提示“地域异常”,额度被退回,白白浪费半小时。手动流程的槽点可以浓缩成三句话:

  • 网络链路长:从登录、选套餐、绑卡到验证,至少 7 个步骤,任何一环超时就得重来。
  • 地域限制多:常见信用卡通道对非北美 IP 直接弹回,或者要求短信二次验证,人在国内收不到。
  • 风控触发频繁:同一设备、同一浏览器指纹短时间内多次尝试,立刻被降速,甚至封会话。

对开发者而言,这些痛点等于“重复性劳动 + 不可预期等待”,完全可以用代码接管。

技术方案:用 requests 模拟浏览器操作

OpenAI 的支付页本质上是 React 前端 + Stripe 后端,接口走 REST,认证用 OAuth2.0 的 access_token。只要拿到合法 token,后边的流程就是普通 POST/PUT。整体思路分四步:

  1. 预登录拿 code:用账号密码换 session,再换 OAuth code。
  2. 换 token:code 换 access_token + refresh_token,有效期 3600 s。
  3. 查询订阅状态:GET /v1/dashboard/billing/subscription,确认是否已是 Plus。
  4. 创建/确认支付:POST /v1/billing/payment_intents,带上信用卡 token 与账单地址。

核心库只有三个:

  • requests:发 HTTP,支持重试、代理、超时。
  • pydantic:把 JSON 响应转成强类型对象,方便补全。
  • tenacity:装饰器级别的重试,比手写 while 优雅。

为了过风控,必须让请求“长得像浏览器”。Headers 顺序、Sec-Fetch-*、Cookie 字段一个都不能少,否则 Stripe 直接 402。下面代码里我封装了一个 BrowserSession 类,统一维护 UA、TLS 指纹和 Cookie 池。

核心代码:带异常处理的支付请求模块

以下示例基于 Python 3.9,已脱敏关键参数,可直接粘贴运行。注意把 YOUR_CREDENTIALS.json 换成自己的账号信息。

# upgrade_plus.py
from __future__ import annotations
import json, time, random, logging, os
from typing import Dict, Optional
import requests
from tenacity import retry, stop_after_attempt, wait_exponential
from pydantic import BaseModel, Field

logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s | %(levelname)s | %(message)s"
)

class PaymentError(RuntimeError):
    """自定义支付异常,方便上层捕获"""

class PlusStatus(BaseModel):
    plan: str = Field(alias="plan_name")
    active: bool

class ChatGPTPlusUpgrader:
    def __init__(self, account_file: str, proxy: Optional[str] = None):
        self.cred = json.load(open(account_file))
        self.sess = requests.Session()
        if proxy:
            self.sess.proxies.update({"http": proxy, "https": proxy})
        self._set_browser_headers()

    # ---------- 公有接口 ----------
    def run(self) -> bool:
        """主入口,成功返回 True"""
        try:
            token = self._get_oauth_token()
            status = self._query_plus_status(token)
            if status.active and status.plan == "plus":
                logging.info("已经是 Plus,跳过支付")
                return True
            self._create_payment_intent(token)
            logging.info("支付意图已创建,等待银行回调")
            return self._wait_for_success(token)
        except PaymentError as e:
            logging.error("支付失败: %s", e)
            return False

    # ---------- 私有方法 ----------
    def _set_browser_headers(self):
        self.sess.headers.update({
            "user-agent": ("Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) "
                           "AppleWebKit/537.36 (KHTML, like Gecko) "
                           "Chrome/122.0.0.0 Safari/537.36"),
            "sec-ch-ua": '"Chromium";v="122", "Not(A:Brand";v="24"',
            "sec-ch-ua-mobile": "?0",
            "sec-fetch-site": "same-origin",
            "accept-language": "en-US,en;q=0.9",
        })

    @retry(stop=stop_after_attempt(3),
           wait=wait_exponential(multiplier=2, min=4, max=30))
    def _get_oauth_token(self) -> str:
        """Step1+2:账号密码 → session → OAuth token"""
        login_url = "https://chat.openai.com/api/auth/signin/auth0"
        resp = self.sess.post(login_url, json={
            "username": self.cred["user"],
            "password": self.cred["pass"]
        }, timeout=15)
        if resp.status_code == 429:
            raise PaymentError("预登录被限速,需冷却")
        resp.raise_for_status()
        # 简化:假设返回里直接有 authorization code
        code = resp.json()["code"]
        token_url = "https://chat.openai.com/api/auth/token"
        token_resp = self.sess.post(token_url, json={"code": code})
        if token_resp.status_code != 200:
            raise PaymentError("Token 换取失败")
        return token_resp.json()["access_token"]

    def _query_plus_status(self, token: str) -> PlusStatus:
        api = "https://chat.openai.com/backend-api/v1/dashboard/billing/subscription"
        self.sess.headers.update({"authorization": f"Bearer {token}"})
        r = self.sess.get(api, timeout=10)
        if r.status_code == 502:
            logging.warning("网关短暂故障,稍后重试")
            time.sleep(5)
            r = self.sess.get(api, timeout=10)
        r.raise_for_status()
        return PlusStatus(**r.json())

    @retry(stop=stop_after_attempt(2))
    def _create_payment_intent(self, token: str):
        """Step4:创建支付意图"""
        url = "https://api.openai.com/v1/billing/payment_intents"
        body = {
            "amount": 2000   # 单位:美分,20 USD
        }
        r = self.sess.post(url, json=body)
        if r.status_code == 402:
            detail = r.json().get("error", {}).get("message", "")
            raise PaymentError(f"银行拒付: {detail}")
        r.raise_for_status()
        logging.info("payment_intent %s 已创建", r.json()["id"])

    def _wait_for_success(self, token: str, poll: int = 6) -> bool:
        """轮询订阅状态,最多 60 s"""
        for _ in range(poll):
            time.sleep(10)
            st = self._query_plus_status(token)
            if st.active:
                logging.info("Plus 升级成功 ")
                return True
        return False


if __name__ == "__main__":
    ok = ChatGPTPlusUpgrader("YOUR_CREDENTIALS.json", proxy=os.getenv("HTTPS_PROXY")).run()
    exit(0 if ok else 1)

代码要点解读:

  • 所有 I/O 都加 timeout,防止线程永久阻塞。
  • 429/502 分别走 tenacity 与手动重试,避免瞬时失败误判。
  • 支付意图金额写死 20 USD,可按需外部传参。
  • 日志分级,方便排查是网络问题还是银行侧拒付。

安全考量:别把风控当空气

OpenAI & Stripe 的反欺诈模型对“脚本味”极其敏感。以下策略亲测有效:

  • 请求频率:同一 IP 新建支付意图间隔 ≥ 8 s,否则 429 伺候。
  • Header 顺序:把 sec-fetch-* 放在 accept-language 之前,浏览器就是这么发的。
  • TLS 指纹:用 requests 默认的 urllib3 1.26+ 即可,别降版本。
  • 代理轮换:住宅代理 > 机房 IP,且最好与信用卡账单地址同国。
  • 设备指纹:Cookie 里的 __Secure-next-auth.session-token 不能复用跨账号,否则秒封。

一句话:让服务器觉得“这是真人用 Chrome”,而不是“Python/3.9 requests/2.31”。

避坑指南:常见失败场景分析

  1. 信用卡 3D 验证失败
    表现:支付意图返回 402,error 代码 card_decline
    解决:卡段不支持境外免密,改用 Depay/OneKey 虚拟卡或实体美卡。

  2. IP 被列入“高风险 ASN”
    表现:一请求就 403,甚至账号被强制登出
    解决:换住宅代理,或者让服务器跑在 AWS Lightsail 美区节点。

  3. 金额精度错误
    表现:Stripe 报 “amount_too_small”
    原因:把 20 美元写成 20 分
    解决:单位用美分,2000 = 20 USD。

  4. OAuth token 提前失效
    表现:查询订阅报 401
    解决:在 refresh_token 失效前 5 min 主动刷新,代码里可再包一层装饰器。

  5. 重试风暴
    表现:触达速率上限后仍狂刷,导致账号 24 h 禁付
    解决:tenacity 里加 jitter=random.uniform(0, 2),让重试间隔随机化。

开放性问题:如何扩展成多账户管理系统?

单账号脚本跑通后,自然想批量复制:给团队 50 个邮箱一键升级,同时记录成功/失败、账单截图、发票号。思路可以从三方面展开:

  • 账号池:用 SQLite 存 email, passwd, card_token, status,脚本启动前随机挑一条,成功后标记为 plus=1。
  • 任务队列:把“升级”封装成 Celery task,配合 Redis 流控,全局每秒最多 2 个支付请求。
  • 发票归档:监听 invoice.created webhook,自动下载 PDF 存入 S3,方便财务对账。

如果你也踩过手动充值的坑,或者正好想练手“Python + 自动化”,不妨把这段代码作为起点,继续迭代出属于自己的多账号 SaaS 小工具。


写完脚本,我最大的感受是:把重复动作交给机器,省下的时间可以去做更有趣的创造。如果你也想体验“让 AI 帮你搞定 AI 的账单”,可以看看这个动手实验——从0打造个人豆包实时通话AI。里面同样用到了请求伪装、重试、语音流合成等技巧,小白也能跟着跑通,亲测一下午就能搭出可对话的 Web 页面。祝你编码愉快,早日告别手动充值!

点击开始动手实验


Logo

欢迎加入DeepSeek 技术社区。在这里,你可以找到志同道合的朋友,共同探索AI技术的奥秘。

更多推荐