# 支付宝付费开通会员 — 部署指南

本文档涵盖支付宝电脑网站支付功能的完整部署流程。用户注册后通过支付宝付费自动激活会员，有效期 1 年。

---

## 前置条件

- 支付宝企业/个体商户账号
- 已在 [支付宝开放平台](https://open.alipay.com) 创建应用并获取 APPID
- 应用已开通 **「电脑网站支付」** 产品权限（`alipay.trade.page.pay` 接口）
- 服务器域名已配置 HTTPS（支付宝回调要求公网可达）

---

## 第一部分：支付宝开放平台配置

### 1. 创建应用

登录 https://open.alipay.com → 控制台 → 创建应用（或使用已有应用）。

### 2. 开通「电脑网站支付」产品

进入应用详情 → 产品绑定/产品管理 → 添加 **「电脑网站支付」** → 提交审核。

> **注意**：未开通此产品会导致 `ACQ.ACCESS_FORBIDDEN` 错误。

### 3. 生成密钥对

进入应用详情 → 开发设置 → 接口加签方式 → 选择 **RSA2(SHA256)**：

1. 使用支付宝官方密钥工具生成 RSA2048 密钥对
2. 将 **应用公钥** 上传到开放平台
3. 上传后平台会显示 **支付宝公钥**（`alipayPublicKey_RSA2`）

最终你会得到两样东西：
- **应用私钥**：你本地保存，代码用来签名请求
- **支付宝公钥**：平台返回给你，代码用来验证回调签名

> 应用公钥只是上传用的中间产物，代码中不需要。

---

## 第二部分：服务器配置

### 1. 放置密钥文件

将密钥保存为标准 PEM 格式，放到 `backend/keys/` 目录：

```bash
mkdir -p /home/rongye/ProgramFiles/ViGent2/backend/keys
```

**`backend/keys/app_private_key.pem`**（应用私钥）：

```
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASC...（你的私钥内容）
...
-----END PRIVATE KEY-----
```

**`backend/keys/alipay_public_key.pem`**（支付宝公钥）：

```
-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8A...（支付宝公钥内容）
...
-----END PUBLIC KEY-----
```

#### PEM 格式要求

支付宝密钥工具导出的是一行纯文本，需要转换为标准 PEM 格式：

- 必须有头尾标记（`-----BEGIN/END ...-----`）
- 密钥内容每 64 字符换行
- 私钥头标记为 `-----BEGIN PRIVATE KEY-----`（PKCS#8 格式）
- 公钥头标记为 `-----BEGIN PUBLIC KEY-----`

如果你拿到的是一行裸密钥，用以下命令转换：

```bash
# 私钥格式化（假设裸密钥在 raw_private.txt 中）
echo "-----BEGIN PRIVATE KEY-----" > app_private_key.pem
cat raw_private.txt | fold -w 64 >> app_private_key.pem
echo "-----END PRIVATE KEY-----" >> app_private_key.pem

# 公钥格式化
echo "-----BEGIN PUBLIC KEY-----" > alipay_public_key.pem
cat raw_public.txt | fold -w 64 >> alipay_public_key.pem
echo "-----END PUBLIC KEY-----" >> alipay_public_key.pem
```

> `backend/keys/` 目录已加入 `.gitignore`，不会被提交到仓库。

### 2. 配置环境变量

在 `backend/.env` 中添加：

```ini
# =============== 支付宝配置 ===============
ALIPAY_APP_ID=你的应用APPID
ALIPAY_PRIVATE_KEY_PATH=/home/rongye/ProgramFiles/ViGent2/backend/keys/app_private_key.pem
ALIPAY_PUBLIC_KEY_PATH=/home/rongye/ProgramFiles/ViGent2/backend/keys/alipay_public_key.pem
ALIPAY_NOTIFY_URL=https://vigent.hbyrkj.top/api/payment/notify
ALIPAY_RETURN_URL=https://vigent.hbyrkj.top/pay
```

| 变量 | 说明 |
|------|------|
| `ALIPAY_APP_ID` | 支付宝开放平台应用 APPID |
| `ALIPAY_PRIVATE_KEY_PATH` | 应用私钥 PEM 文件绝对路径 |
| `ALIPAY_PUBLIC_KEY_PATH` | 支付宝公钥 PEM 文件绝对路径 |
| `ALIPAY_NOTIFY_URL` | 异步回调地址（服务器间通信），必须公网 HTTPS 可达 |
| `ALIPAY_RETURN_URL` | 同步跳转地址（用户支付完成后浏览器跳转回的页面） |

`config.py` 中还有几个可调参数（已有默认值，一般不需要加到 .env）：

| 变量 | 默认值 | 说明 |
|------|--------|------|
| `ALIPAY_SANDBOX` | `false` | 是否使用沙箱环境 |
| `PAYMENT_AMOUNT` | `999.00` | 会员价格（元） |
| `PAYMENT_EXPIRE_DAYS` | `365` | 会员有效天数 |

### 3. 创建数据库表

通过 Docker 在本地 Supabase 中执行：

```bash
docker exec -i supabase-db psql -U postgres -c "
CREATE TABLE IF NOT EXISTS orders (
    id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
    user_id UUID REFERENCES users(id) ON DELETE CASCADE,
    out_trade_no TEXT UNIQUE NOT NULL,
    amount DECIMAL(10, 2) NOT NULL DEFAULT 999.00,
    status TEXT DEFAULT 'pending' CHECK (status IN ('pending', 'paid', 'failed')),
    trade_no TEXT,
    created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
    paid_at TIMESTAMP WITH TIME ZONE
);

CREATE INDEX IF NOT EXISTS idx_orders_user_id ON orders(user_id);
CREATE INDEX IF NOT EXISTS idx_orders_out_trade_no ON orders(out_trade_no);
"
```

### 4. 安装依赖

```bash
# 后端（在 venv 中）
cd /home/rongye/ProgramFiles/ViGent2/backend
venv/bin/pip install python-alipay-sdk
```

> 前端无额外依赖需要安装。

### 5. Nginx 配置

确保 Nginx 将 `/api/payment/notify` 代理到后端。如果现有配置已覆盖 `/api/` 前缀，则无需额外修改：

```nginx
location /api/ {
    proxy_pass http://localhost:8006;
    # ... 现有配置
}
```

### 6. 重启服务

```bash
# 构建前端
cd /home/rongye/ProgramFiles/ViGent2/frontend
npx next build

# 重启
pm2 restart vigent2-backend
pm2 restart vigent2-frontend
```

---

## 第三部分：正式上线

测试通过后，将 `backend/app/core/config.py` 中的测试金额改为正式价格：

```python
PAYMENT_AMOUNT: float = 999.00      # 正式价格
```

或在 `backend/.env` 中添加覆盖：

```ini
PAYMENT_AMOUNT=999.00
```

然后重启后端：

```bash
pm2 restart vigent2-backend
```

---

## 支付流程说明

```
用户注册 → 登录（密码正确但 is_active=false）
         → 后端返回 403 + payment_token
         → 前端跳转 /pay 页面
         → POST /api/payment/create-order → 返回支付宝收银台 URL
         → 前端重定向到支付宝收银台页面（支持扫码、账号登录、余额等多种支付方式）
         → 用户完成支付
         → 支付宝异步回调 POST /api/payment/notify
         → 后端验签 → 更新订单 → 激活用户（is_active=true, expires_at=+365天）
         → 支付宝同步跳转回 /pay?out_trade_no=xxx
         → 前端轮询 GET /api/payment/status/{out_trade_no}
         → 轮询到 paid → 提示成功 → 跳转登录页
         → 用户重新登录 → 成功进入系统
```

**电脑网站支付 vs 当面付**：电脑网站支付（`alipay.trade.page.pay`）会跳转到支付宝官方收银台页面，用户可以选择扫码、支付宝账号登录、余额等多种方式支付，体验更好。当面付（`alipay.trade.precreate`）仅生成一个二维码，只能扫码支付。

会员到期续费同流程：登录时检测到过期 → 返回 PAYMENT_REQUIRED → 跳转 /pay。

管理员手动激活功能不受影响，两种方式并存。

---

## 涉及文件

| 文件 | 变更类型 | 说明 |
|------|---------|------|
| `backend/requirements.txt` | 修改 | 添加 `python-alipay-sdk` |
| `backend/database/schema.sql` | 修改 | 新增 `orders` 表 |
| `backend/app/core/config.py` | 修改 | 支付宝配置项 |
| `backend/app/core/security.py` | 修改 | payment_token 函数 |
| `backend/app/core/deps.py` | 修改 | is_active 安全兜底 |
| `backend/app/repositories/orders.py` | 新建 | orders 数据层 |
| `backend/app/modules/payment/__init__.py` | 新建 | 模块初始化 |
| `backend/app/modules/payment/schemas.py` | 新建 | 请求/响应模型 |
| `backend/app/modules/payment/service.py` | 新建 | 支付业务逻辑（电脑网站支付） |
| `backend/app/modules/payment/router.py` | 新建 | 3 个 API 端点 |
| `backend/app/modules/auth/router.py` | 修改 | 登录返回 PAYMENT_REQUIRED |
| `backend/app/main.py` | 修改 | 注册 payment_router |
| `backend/.env` | 修改 | 支付宝环境变量 |
| `backend/keys/` | 新建 | PEM 密钥文件 |
| `frontend/src/shared/lib/auth.ts` | 修改 | login() 处理 paymentToken |
| `frontend/src/shared/api/axios.ts` | 修改 | PUBLIC_PATHS 加 /pay |
| `frontend/src/app/login/page.tsx` | 修改 | paymentToken 跳转 |
| `frontend/src/app/register/page.tsx` | 修改 | 注册成功提示文案 |
| `frontend/src/app/pay/page.tsx` | 新建 | 付费页面（重定向到支付宝收银台） |

---

## 常见问题

### RSA key format is not supported

密钥文件缺少 PEM 头尾标记或未按 64 字符换行。参考「PEM 格式要求」重新格式化。

### ACQ.ACCESS_FORBIDDEN

应用未开通「电脑网站支付」产品。在支付宝开放平台 → 应用详情 → 产品管理中添加并开通。

### 支付宝回调不到

1. 检查 `ALIPAY_NOTIFY_URL` 是否公网 HTTPS 可达
2. 检查 Nginx 是否将 `/api/payment/notify` 代理到后端
3. 支付宝回调超时（15s 未响应）会重试，共重试 8 次，持续 24 小时

### 支付完成后页面未跳转回来

检查 `ALIPAY_RETURN_URL` 配置是否正确，必须是前端 `/pay` 页面的完整 URL（如 `https://vigent.hbyrkj.top/pay`）。支付宝会在用户支付完成后将浏览器重定向到此地址，并附带 `out_trade_no` 等参数。

### 前端显示"网络错误"而非具体错误

API 函数缺少 try/catch 捕获 axios 异常。已在 `auth.ts` 的 `register()` 和 `login()` 中修复。
