---
id: FEAT-012
title: envelope-buy-mock
domain: envelope
status: done
since_version: 0.4.0
---

# FEAT-012 — 信封买（mock 付款）

## User Story

As a **已用完每日 3 封免费信封的用户**, I want to **花钱买更多信封**, so that **能继续给新目标发信不被每日额度卡住**。

**当前限制**：0.4.x live 期用 `--mock` 标志（paid=1 直接加余量，不真扣钱）。真付走 `FEAT-013`（v0.5.0 接入支付宝 / 微信）。

## Acceptance Criteria

- [x] `xq envelope buy --count N --mock` 命令加 N 个信封到付费余量
- [x] `--mock` 必带（防止用户以为真付费了）
- [x] `--count` 上限 100（防一次充太多 + 真付后对账复杂）
- [x] 每次买单写 `envelope_orders` 表：`order_id / user_id / count / paid / mock / created_at`
- [x] 事务里扣库存 / 加余量，幂等
- [x] 返回 `order_id` + 新余量

## Tests

- `test/test_mail.py::test_buy_envelope_mock_happy_path`
- `test/test_mail.py::test_buy_envelope_count_limit`（N=0/1/100/101）
- `test/test_mail.py::test_buy_envelope_balance_increment`
- `test/test_mail.py::test_buy_envelope_requires_mock_flag`
- `test/test_cli.py::test_cli_envelope_buy_mock`

## CLI / API

- CLI: `xq envelope buy --count N --mock`（实现：`src/xiangqin/cli.py::cmd_envelope_buy`）
- API: `POST /envelope/buy`（body: `{count, mock: true}`，实现：`src/xiangqin/app.py::envelope_buy` → `src/xiangqin/mail.py::buy_envelope`）

## 依赖 / 被依赖

- **依赖**：`FEAT-001/002` 登录、`FEAT-011 envelope.state` 查余量
- **被依赖**：`FEAT-014 dm.send`（第 4 封起扣付费余量）、`FEAT-019 inbox.reply`（同上）、`FEAT-013 buy-real`（真付接入时沿用 db schema + API 合约）

## 风险 / 边界

- **不做**：`--mock` 不可去掉前调用真付 API（防意外扣钱）
- **商业假设**：本 feature + `FEAT-013` 验证 R-1（有人愿意为"敲门"付 ¥1）
- **订单号 ULID**：方便排序 + 不可猜

## 相关

- objective `O3-envelope-business-loop`
- issues `#013 / #014`（真付接入）
- ADR `2026-04-23-m4-mail-model-touchreach`（信封商业模式）
