# vr-shopxo-plugin 需求对照分析 > 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(核心链路) ---