---
name: gptpay-api-integration
description: 接入 AI Pay 的能力目录、账户积分、异步支付与取消续费任务，使用 API Key 和幂等编号处理重试。
metadata:
  short-description: AI Pay API 接入规范
---

# AI Pay API 接入

本文定义服务当前唯一 HTTP 契约。支付、升级和检查点恢复由私有 [Go 协议 SDK](https://github.com/DouDOU-start/gptpay-protocol) 执行，调用方只依赖业务 API。

## 通用契约

程序调用使用 `Authorization: Bearer API_KEY`，浏览器控制台使用 HttpOnly 会话 Cookie。管理员接口仅接受管理员网页会话。JSON 请求使用 `Content-Type: application/json`。

- 成功：`{"data": ...}`。
- 失败：非 2xx HTTP 状态，`{"error":{"code":"invalid_request","message":"请求字段或字段类型无效"}}`；可选 `details` 位于 `error` 内。
- 列表：`data.items`、`data.total`、`data.page`、`data.pageSize`。分页从 1 开始，每页最多 100 条；筛选选项可附加在列表数据中。
- 任务编号只用 `id`。详情直接是 `data`，没有额外任务包装。
- 不支持旧版本入口、字段别名、隐式旧格式转换；未知支付字段或错误字段类型直接拒绝。
- 不记录或转发原始 Session、API Key、卡号、CVV、轮询凭证及检查点。

## 能力与余额

```http
GET /api/pricing
GET /api/me/profile
```

定价可公开读取，携带 API Key 时返回账户折扣后的积分价。支持能力以 `data.platforms[].plans` 和 `checkoutRegions` 为准；当前平台为 `gpt`，套餐使用 SDK 的 `go`、`plus`、`pro5x`、`pro20x`、`team`、`team5x` 代码。套餐显示名为 `label`，可用性为 `available`，地区代码为 `code`。

账户余额位于 `data.user.credits`。积分账本使用 `GET /api/me/ledger`，条目字段为 `id`、`amount`、`balance`、`kind`、`reference`、`note`、`createdAt`。

## 创建结算链接

独立创建结算链接使用 `POST /api/checkout-links`，参数和结果见 [提链接口](../../../docs/checkout-links.md#接口与依赖)。提链不扣积分、不自动付款，创建请求不自动重试。

## 查询套餐与有效期

`POST /api/subscription/query` 需要本站登录 Cookie 或 API Key，每用户每分钟最多 20 次。仅查询个人账号，不创建任务、不扣积分、不修改续费。

```json
{
  "credentials": {
    "session": {
      "user": {"email": "customer@example.test"},
      "account": {"id": "ACCOUNT_ID"},
      "accessToken": "ACCESS_TOKEN",
      "sessionToken": "SESSION_TOKEN"
    }
  },
  "country": "PH"
}
```

`country` 保留现有地区校验，不再用于选择代理。账号套餐查询不读取平台地区代理，使用后端服务器自身的网络。返回 `data.planType`、`subscriptionPlan`、`hasActiveSubscription`（布尔值或 null）、`activeUntil`（UTC RFC3339 或空字符串）、`renewal`（on/off/unknown）、`fetchedAt`。未提供时间不表示永久有效；上游失败返回 502 `subscription_unavailable`，不伪装为免费套餐。请求和响应不持久化，响应禁止缓存。

## 创建支付任务

```http
POST /api/pay/protocol
```

```json
{
  "platform": "gpt",
  "credentials": {
    "session": {
      "user": {"email": "customer@example.test"},
      "account": {"id": "ACCOUNT_ID"},
      "accessToken": "ACCESS_TOKEN",
      "sessionToken": "SESSION_TOKEN"
    }
  },
  "cardId": "SAVED_CARD_ID",
  "planCode": "plus",
  "billingInterval": "month",
  "checkoutCountry": "PH",
  "clientRequestId": "order-00000001"
}
```

Session 使用账户所有者提供的完整对象。卡片来源三选一：本人保存的 `cardId`；手工 `cardNumber`、`expMonth`、`expYear`、`cvv`（均为字符串）；不传卡字段，由本人可用卡池选择。开启保存地址功能时可附带 `billingAddressId`。

周期为目录允许的 `month` 或 `year`。Team 传整数 `seats`、`seats5x`，合计 2–1000；`team5x` 至少有 1 个 5X 席位。个人套餐省略席位。席位影响上游金额，但不乘算每任务固定积分。

同一操作始终保留原 `clientRequestId`。响应丢失后使用相同请求内容与编号重试，服务返回原任务；同编号不同内容会冲突。受理即预扣积分并排队，不存在独立预报价或金额确认接口。代理、SDK 选项、任务来源由服务决定，客户端不能覆盖。

响应 `data.id` 为任务编号，`data.creditsCost` 为固定积分，`operation=pay`，`source` 为 `console`、`api` 或 `cdk`。

## 查询与结果展示

```http
GET /api/tasks/{id}
GET /api/tasks?page=1&pageSize=20
POST /api/tasks/export
```

列表与导出默认包含本人所有来源的任务。管理员使用 `/api/admin/tasks` 下对应接口，敏感导出须填写密码，原因选填且最多 200 字；查看与导出仍记录审计日志。

任务 `status` 为 `queued`、`processing`、`success`、`failed`。排队、处理中以及 `followUpPending=true` 时查询原任务；不要再建付款。`warningCode=verification_retry_exhausted` 表示后台已停止自动核验，应停止快速轮询并保留记录处理。

- `payment` 是资金证据：`not_attempted`、`unknown`、`pending`、`action_required`、`authorized`、`charged`、`not_charged`。
- `activation` 和 `renewal` 独立于付款；`renewal=off` 才表示已关闭续费。付款成功不因后续激活或取消失败而退款。
- `chargedPrice={amountMinor,currency,source}` 是实际扣款证据，按币种小数位格式化。已付款但缺金额时显示“金额待核实”。
- `estimatedPrice` 只展示为估价，不能冒充实付。
- `flow=subscription_update` 才表示实际升级，不依据所选套餐猜测。
- `providerCode`、`declineCode` 是明确拒付时的可选诊断代码；`failureMessage` 为固定说明，可用于失败展示。`errorCode`、`failureReason` 保持错误代码含义，未知或已扣款结果不附带拒付诊断。
- `errorCode=managed_pending_transactions` 且 `payment=not_attempted` 表示旧卡核验阻止了注资。满足零余额、无本站资金操作及付款执行证据时任务直接失败、释放 CDK 预占；`failureMessage` 展示固定的准备失败说明。
- `steps[].stage` 是阶段标识；`label`、`percent`、`at` 用于进度展示。
- 卡池容量使用 `usageLimit`、`usedCount`、`usageReserved`、`remainingUses`；卡并发使用 `inFlight`、`maxInFlight`。

明确未付款只退款一次并释放资源；未知付款保留积分、卡槽和 CDK 预占，只核验原交易。`payment_task_retry_attempts` 控制 1–5 次核验，不代表重新支付。

## 免费取消续费

```http
POST /api/pay/cancel-renewal
```

请求字段仅 `credentials.session`、`checkoutCountry`、`clientRequestId`。返回同一任务模型，`operation=cancel_renewal`。不需要卡片、不扣积分、不执行退款，查询仍走 `/api/tasks/{id}`。

## 公开 CDK

1. `POST /api/cdk/check`：提交 `{"code":"CDK_CODE"}`，校验并显示固定套餐。
2. `POST /api/cdk/recharge`：提交 `code`、`credentials.session`、`clientRequestId`。套餐、席位、地区和成本在发行时固定。
3. 返回 `data.id`、`data.pollToken`、`data.status` 后，清除页面中的 CDK 与 Session；保留私密轮询凭证。
4. `POST /api/cdk/progress`：提交 `{"id":"TASK_ID","pollToken":"POLL_TOKEN"}`，读取脱敏摘要。

公开查询不依赖控制台 Cookie。发行成本只扣一次；确认付款核销 CDK，明确未付款释放供重试，未知付款保留占用。删除可删除且未使用的 CDK 按发行成本退款。管理列表仅有 `maskedCode`，发行和受保护的揭示接口使用 `code`。

## 错误与重试

- `400`：按能力目录修正字段与类型。
- `401/403`：修复身份或权限，不重复支付。
- `429/503`：退避，保留原操作编号。
- 连接中断、响应丢失或轮询超时：保留原请求编号及任务编号，重试原操作或继续查询。

完整卡号仅在明确授权的保护接口和导出中读取；Session、CVV、密文及检查点不进入公共任务模型。本次部署初始化全新 PostgreSQL，不导入旧业务记录。

## 站点首次初始化

`GET /api/setup` 返回 `{data:{initialized:boolean}}`。未初始化时提交 `POST /api/setup`，请求为 `{email,username,password,confirmPassword}`，成功返回 `{data:{initialized:true}}`，随后通过登录接口创建会话。密码至少 8 个字符且最多 128 字节，必须包含大写字母、小写字母及英文符号，两次密码一致。站点名称默认 `aipay`，在管理设置中修改。重复初始化返回 `409 setup_already_completed`；初始化前注册和验证码接口返回 `503 setup_required`。未知字段拒绝，不支持通过用户管理接口指定角色。

联系二维码上传接口保持 `POST /api/admin/settings/wechat-qr`（multipart file 或 remove=1），文件上限 3 MiB，支持 PNG/JPEG/WebP。图片内容存 PostgreSQL，公开访问 `GET /api/site/wechat-qr`；设置不接受内部文件名。

登录仅接受邮箱（管理员同样适用），邮箱不区分大小写且保持唯一；用户名是可重复的展示名称，不参与登录身份解析。
