# vr-shopxo-plugin 需求对照分析

> ⚠️ **本文档已过时**。本文档描述的早期设计（独立 vr_events/vr_sessions 表、支付回调自建等）已被废弃。
> 当前实现使用 ShopXO 原生 BuyService + vr_tickets 单表方案，详见 ARCHITECTURE.md 和 PHASE_4_PLAN.md。
> 
> ShopXO 插件机制 vs VR 演唱会票务需求
> 基于 shopxo-plugin-dev.md 整理

---

## 一、票务核心功能 vs ShopXO 机制

### 1. 商品模型（票 = item_type='ticket'）

ShopXO 原生商品表 `item` 有 `item_type` 字段扩展能力，但票务的特殊性在于：
- 票有时间场次（session），普通商品无此概念
- 票需要观演人信息，普通商品无此字段
- 票需要 QR 电子票，普通商品无此交付物

**结论**：票务插件需创建独立表 `vr_events` + `vr_sessions` + `vr_tickets`，与 ShopXO `items` 表平行存在，通过 `item_id` 关联。

### 2. 库存扣减（防超卖）

ShopXO 原生库存扣减：SELECT → UPDATE 两步，非原子操作，有超卖风险。

**VR 票务方案**：在支付回调 `api/notify` 中，使用 Postgres 风格 SQL（ShopXO 是 MySQL，需换为 MySQL 原子写法）：

```sql
-- MySQL 原子扣库存（FOR UPDATE SKIP LOCKED 等效写法）
START TRANSACTION;
SELECT stock FROM vr_sessions WHERE id=? AND stock>=? FOR UPDATE;
-- 如果库存足够
UPDATE vr_sessions SET stock=stock-? WHERE id=?;
INSERT INTO vr_tickets (...) VALUES (...);
COMMIT;
```

**关键**：必须在支付回调的 API 控制器中完成，不能依赖 ShopXO 原有订单流程。

### 3. 微信支付回调

ShopXO 支持支付插件机制（`extend/payment/`），但票务插件需要自己的支付回调路由：

```
POST /plugins/api?pluginsname=vr_ticket&pluginscontrol=api&pluginsaction=notify
```

ShopXO 原生支付回调由支付插件处理，票务插件的回调是：**用户付款成功 → 票务插件生成电子票**。

**注意**：ShopXO 的微信支付插件本身已处理微信支付回调，我们需要在 `Event.php` 中监听支付成功事件，或在回调路由中自己调用微信API验证。

### 4. 观演人信息

ShopXO 订单扩展字段能力有限，建议：
- 创建 `vr_attendees` 表（姓名、手机、证件号）
- 在订单 `paid` 状态的 webhook 或手动触发时写入
- 通过订单扩展字段 `extension`（JSON）关联 `vr_attendees`

### 5. QR 电子票

ShopXO 附件上传/管理能力完善，但 QR 生成需自己实现：
- PHP QR Code：`phpqrcode/phpqrcode` Composer 包
- 存储：`vr_tickets.qr_data` 存 QR 内容（AES_Encrypt），QR 图片可 CDN 化

### 6. B 端扫码核销

ShopXO 后台有门店核销机制（多门店插件），但票务核销是独立场景：
- 插件后台：`admin/verify` 控制器
- 扫码枪：调用微信扫一扫或直接输入 QR code
- 核销状态更新：`vr_tickets.status = 'used'`

---

## 二、ShopXO 原有功能借用分析

| 票务需求 | ShopXO 原生能力 | 插件扩展方式 | 优先级 |
|---------|---------------|------------|--------|
| 商品展示 | ✅ items 表 | 创建 vr_events 场次表 | P0 |
| 会员体系 | ✅ 完善 | 不需开发 | P0 |
| 钱包/余额 | ✅ 完善 | 不需开发 | P0 |
| 优惠券 | ✅ 完善 | 不需开发 | P0 |
| 微信支付 | ✅ 支付插件 | 直接用（不需改） | P0 |
| 订单管理 | ✅ orders 表 | 扩展 extension 字段 | P0 |
| 库存扣减 | ⚠️ 非原子 | 支付回调自己写 | P0 |
| 场次/SKU | ❌ 无 | 独立表 vr_sessions | P0 |
| 观演人信息 | ❌ 无 | 独立表 vr_attendees | P0 |
| QR 电子票 | ❌ 无 | phpqrcode + 插件表 | P0 |
| B端核销 | ❌ 无 | 插件后台 admin 页面 | P1 |
| 多场馆 | ❌ 无 | vr_events 已有场馆字段 | P2 |
| 周边商品 | ✅ items 原生 | 不需插件 | P0 |

---

## 三、插件架构设计

### 数据库 E-R 图

```
vr_events (场次)
  └── vr_sessions (场次时间/库存)  1:N
        └── vr_tickets (电子票)  1:N
              └── vr_attendees (观演人)  N:1

items (ShopXO原生) ←── item_id ──→ vr_events (通过 item_id 关联)
orders (ShopXO原生) ←─ extension ──→ vr_attendees (JSON关联)
```

### 插件目录

```
app/plugins/vr_ticket/
├── config.json              # 插件配置（钩子：支付回调）
├── admin/
│   ├── controller/
│   │   ├── Event.php        # 场次管理 CRUD
│   │   ├── Ticket.php        # 电子票管理
│   │   └── Verify.php       # 核销管理
│   └── view/
│       ├── event/            # 场次管理视图
│       ├── ticket/
│       └── verify/           # 核销页面
├── index/
│   ├── controller/
│   │   ├── Index.php         # 前台插件首页
│   │   └── Ticket.php        # 我的票/QR展示
│   └── view/
│       ├── ticket/
│       └── my_tickets.blade.php
├── api/
│   └── controller/
│       └── Notify.php        # ★ 支付回调（最关键）
├── service/
│   └── BaseService.php
├── Event.php                 # 生命周期事件
├── common.php
└── hook/
    └── OrderPaid.php         # 订单支付成功钩子（可选）
```

### 支付回调流程

```
微信支付回调 → /plugins/api?pluginsname=vr_ticket&pluginscontrol=api&pluginsaction=notify
    │
    ├─ 1. 验证签名（微信API）
    ├─ 2. 查询 ShopXO orders 表确认支付状态
    ├─ 3. 读取订单 extension 中的 vr_session_id + vr_attendees
    ├─ 4. 原子扣库存（vr_sessions）
    ├─ 5. 生成 vr_tickets（AES_Encrypt QR content）
    ├─ 6. 写入 vr_attendees 记录
    └─ 7. 更新 orders.extension（票ID列表）
```

---

## 四、已知限制与绕过方案

| 限制 | 影响 | 绕过方案 |
|------|------|---------|
| ShopXO 订单流程不可扩展 | 无法在原生下单流程中插入观演人表单 | 独立购票页，支付后写入观演人 |
| 支付回调由支付插件处理 | 票务插件无法直接拦截微信回调 | 支付插件 → Event.PaySuccess → 票务钩子 |
| 插件不能 Hook 订单状态变更 | 无法监听 paid→fulfilled | 支付回调中直接处理，或定时轮询 |
| DIY 拖拽装修不可代码化 | 票务展示页无法 AI 生成 | 票务页面全部走插件视图，不走 DIY |
| MySQL 非 Postgres | 无 FOR UPDATE SKIP LOCKED | START TRANSACTION + SELECT ... FOR UPDATE |

---

## 五、下一步行动

1. **先确认支付插件的 PaySuccess 事件** — 从 `extend/payment/` 源码确认是否触发 Event.PaySuccess
2. **创建插件骨架** — config.json + BaseService + Event.php
3. **设计数据库迁移脚本** — vr_events, vr_sessions, vr_tickets, vr_attendees
4. **实现支付回调** — api/Notify.php（核心链路）

---
