# 支付宝付费开通会员 — 部署指南 本文档涵盖支付宝电脑网站支付功能的完整部署流程。用户注册后通过支付宝付费自动激活会员,有效期 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()` 中修复。