PHP 多用户商城系统怎么做?拆单、数据隔离与分账结算
“PHP 商城系统”从几百字的单店演示,到支持数百商家独立后台的多用户商城(B2B2C),中间的技术差异非常大。多数人卡在数据隔离、资金分账、以及商家权限边界这三处。本文给出可落地的模型设计与实现要点。

一、先定义清楚“多用户”是哪一种
- 多买家:只有客户注册,一个卖家。这是普通商城,不需要分账;
- 多商家(B2B2C):商家可自主入驻、上架、发货、看自己订单,平台抽佣。这才是“多用户商城”的难点;
- 多租户(SaaS 型):每个客户一套独立品牌店铺(域名/数据隔离),面向代理商卖软件。
三者的架构成本差一个数量级,选型前先确认需求属于哪一种。
二、核心数据模型
shop(id, name, domain, status, settle_rate) -- 店铺 goods(id, shop_id, cat_id, title, price, status) -- 商品,必带 shop_id sku(id, goods_id, spec_json, price, stock) -- 规格维度库存 order(id, order_no, uid, shop_id, total_amount, status, ...) order_item(id, order_id, goods_id, sku_id, shop_id, price, qty, amount) settlement(id, shop_id, order_id, amount, rate, fee, status) -- 分账/结算 wallet(shop_id, balance, frozen) -- 商家钱包
三条关键原则:
- 订单按店铺拆分:用户一次买 A 店 + B 店的商品,要拆成两个父单两个子单(或一个 parent_order + 多个 order)。混在一张单里,发货、退款、分账全都会乱;
- order_item 冗余 shop_id:查询商家订单时避免 JOIN goods(商品可能改名、下架、删除);
- 价格与状态快照:订单项里必须存成交时的单价与商品标题,不能实时关联商品表。
三、数据隔离的实现(最容易出安全事故)
所有商家后台查询都必须强制带 shop_id,而不是“前端没传就查全部”。工程做法:
class GoodsRepo {
public function __construct(private int $shopId) {}
public function paginate(array $w, int $page = 1) {
// 关键:shop_id 由会话注入,不接受请求参数
$cond = array_merge($w, ['shop_id' => $this->shopId]);
return DB::table('goods')->where($cond)->paginate(20, $page);
}
}
// 服务层统一从登录态取
$repo = new GoodsRepo(session('shop_id'));
补充三条防线:① 更新/删除用双条件:WHERE id=? AND shop_id=?,只按 id 更新是越权读写的主要来源;② 全局中间件校验角色(平台管理员与商家是两套权限点,不要混用同一个“管理员”概念);③ 文件上传路径按店铺隔离 /uploads/{shop_id}/...,避免 A 店直接访问 B 店图片。
测试方法:用两个不同店铺账号交叉访问对方的 /goods/edit?id=对方ID,若成功就是越权漏洞。
四、资金:分账、退款、结算
- 抽佣计算:用整数分,
fee = intdiv(amount * rate, 10000)(rate 为万分比),避免浮点误差; - 支付通道选择:微信/支付宝官方都提供服务商模式的分账接口(微信“电商收付通”、支付宝“分账/间连”)。用分账接口才合规,自行“平台先收款、再私下转账给商家”会形成二清问题(无证从事资金结算),这是政策红线;
- 结算周期:常见做法是订单完成后 T+N(如 7/15 天)进入可提现,留出售后窗口;
- 退款分摊:退款要同时退“商家货款 + 平台佣金 + 优惠券承担部分”,三部分按当初的分账比例反向冲销,并在
settlement表记红冲记录(不要直接 UPDATE 原记录); - 对账:每日跑批比对“支付通道流水 ↔ 本地订单 ↔ 分账记录”三方,差异入异常表人工处理。
CREATE TABLE settlement ( id BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY, order_id BIGINT UNSIGNED NOT NULL, shop_id BIGINT UNSIGNED NOT NULL, amount INT UNSIGNED NOT NULL, -- 分(订单实付) fee INT UNSIGNED NOT NULL, -- 平台佣金 net INT UNSIGNED NOT NULL, -- 商家应得 = amount - fee type TINYINT NOT NULL, -- 1 正常 2 退款红冲 status TINYINT NOT NULL DEFAULT 0, UNIQUE KEY uk_order_type (order_id, type), KEY idx_shop_status (shop_id, status) ) ENGINE=InnoDB;
五、并发与库存(沿用单店方案,但要注意跨店券)
- 库存扣减仍是原子 SQL:
UPDATE sku SET stock=stock-? WHERE id=? AND stock>=?,判rowCount(); - 优惠券要区分平台券(全店通用,平台承担)与店铺券(单店,商家承担),成本归属不同,直接决定结算金额;
- 满减/折扣叠加时统一在“下单快照”阶段算好并存进 order_item,后续不再重算。
六、要不要用现成开源系统
先看这些再决定自研:
- Crmeb(PHP/Java,商用需授权)、Hive ERP、萤火小程序商城等国内常见;Magento(Adobe Commerce)、Sylius / Saleor 偏国际;
- 成熟方案(有赞、微盟等 SaaS)适合快速上线,但数据在自己库里这一点需要评估;
- 自研的成本常被低估:多用户商城涉及资金,安全(越权、SQL 注入、支付回调伪造)、发票、税务、《电子商务法》对平台的责任(商家资质审核、消费争议处理、违法商品信息处置)都要覆盖。
支付回调务必:验签 + 校验金额与订单号一致 + 幂等处理(用唯一索引 uk_order_no 防止重复入账)。
七、常见问题 FAQ
问:能不能用一个订单表存多店商品,简单点?
答:短期可以,长期一定改。发货单、物流、售后都按店铺维度,跨店混单会让这些流程无解。
问:商家能不能改自己商品的“已成交订单”?
答:绝对不能。订单金额与状态只能由系统流程变更,商家端仅“发货/备注”。
问:如何防止商家上传违规商品?
答:接入内容审核(图像识别 + 关键词)+ 先发后审或先审后发策略 + 举报入口,平台需留存商家资质,否则出事时责任在平台。
核心:多用户商城的技术难点不在商品与订单,而在“按店铺拆单 + 会话强制注入 shop_id + 走官方分账接口避开二清”这三条。
生产内容,同时打通搜索引擎(SEO)与AI搜索引擎(GEO)流量入口的软件! 点击进入365建站器 »
