# AI Code Check Report 样例

项目：Demo SaaS Checkout

检查范围：支付 checkout 创建订单流程

## 结论

我会优先修这个问题：支付请求缺少稳定的幂等键，用户重复点击或网络重试时可能创建重复订单。

## 位置

- 文件：`app/api/checkout/route.ts`
- 关键逻辑：创建 payment intent 后再写入订单状态
- 触发条件：用户连续点击付款按钮、浏览器自动重试、移动网络中断后恢复

## 为什么重要

这是收入路径问题。它可能导致同一用户被重复收费，或者后台出现多个待支付订单。即使支付平台最终拦住了重复扣款，客服和退款成本也会很高。

## 证据

当前逻辑通常长这样：

```ts
const intent = await stripe.paymentIntents.create({
  amount,
  currency: "usd",
});

await db.orders.create({
  userId,
  planId,
  paymentIntentId: intent.id,
  status: "pending",
});
```

问题是：每次请求都会创建新的 payment intent。代码没有用 `userId + planId + requestId` 或订单草稿 ID 生成稳定幂等键。

## 最小修法

1. 前端创建 checkout 前先生成一个 `checkoutRequestId`，同一次点击保持不变。
2. 后端先创建或读取 `pending` 订单，再创建 payment intent。
3. 创建 payment intent 时传入稳定幂等键。
4. 如果同一个用户、套餐、requestId 重试，返回已有订单和已有 payment intent。

示例方向：

```ts
const idempotencyKey = `checkout:${user.id}:${plan.id}:${checkoutRequestId}`;

const existingOrder = await db.orders.findFirst({
  where: { userId: user.id, planId: plan.id, checkoutRequestId },
});

if (existingOrder?.paymentIntentId) {
  return Response.json({ paymentIntentId: existingOrder.paymentIntentId });
}

const intent = await stripe.paymentIntents.create(
  { amount, currency: "usd" },
  { idempotencyKey },
);
```

## 验证方式

手动验证：

1. 打开 checkout 页面。
2. 连续快速点击付款按钮 3 次。
3. 在网络面板里重放 checkout 请求。
4. 数据库里应该只出现一个 pending 订单。
5. 支付平台里应该只出现一个 payment intent。

自动测试建议：

```text
given same user, plan, checkoutRequestId
when checkout endpoint is called twice
then payment provider create call happens once
and response returns the same paymentIntentId
```

## 不在本次范围内

- 没有检查 webhook 签名验证。
- 没有检查退款流程。
- 没有检查生产支付平台配置。
- 没有重构数据库 schema。

## 下一步

先补幂等键和重复请求测试。这个修完后，再检查 webhook 是否会把订单状态错误地从 paid 改回 pending。

