订单这玩意儿从生成到完结,得经历好几个状态。新手最容易栽在状态流转的逻辑上。比如用户下单后库存扣了,结果支付超时,你得把库存还回去对吧?这儿用事务锁住关键操作是基本操作。我习惯用MySQL事务配合PHP的PDO,在订单创建时就把库存锁定、优惠券标记、订单表插入这几个动作包成原子操作。万一某个环节失败,整个事务回滚,避免产生脏数据。具体代码大概长这样:
再说支付回调处理。第三方支付平台回调时最怕两件事:重复处理和数据不一致。我的做法是在订单表加个唯一流水号字段,接到回调先查这个流水号是否存在,存在就直接返回成功,避免重复扣款。然后验证签名和金额,都通过后才更新订单状态为已支付,同时触发库存实际扣减。这里注意要用悲观锁,在查询订单时加FOR UPDATE,防止并发修改。
订单取消和退款这块更得小心。用户取消订单时要判断是否已支付,已支付的走退款流程,未支付的直接释放库存。退款最好做成异步任务,用消息队列处理。我们项目用Redis做队列,PHP worker进程从队列取退款任务,调用支付平台API,失败就重试三次。关键是要记录完整的操作日志,方便对账。
超时未支付订单的自动关闭也是个常见需求。传统做法是用crontab定时跑脚本,查询超过30分钟的待支付订单然后取消。但现在更推荐用延迟队列,比如RabbitMQ的死信交换机或者Redis的键空间通知,订单创建时扔个延迟消息,时间到了自动处理,比定时任务更精准。
数据量大时的分表策略也得提前规划。我们按用户ID哈希分表,订单表和订单商品表都拆成16个,查询时带用户ID的路由到具体表。历史订单归档另外做,不影响线上活跃订单查询。
最后说说实践中容易忽略的细节。订单号生成别用自增ID,用时间戳+随机数或者雪花算法,避免被猜出订单量。重要操作都要留审计日志,谁在什么时候修改了什么字段。与物流平台对接时做好超时控制和异常重试。还有,千万别在订单流程里写死循环发送邮件短信,我们吃过亏,用户下个单收到几十条通知,差点被投诉到破产。
总之PHP处理电商订单,关键是理解业务场景,把状态流转想清楚,数据库操作加好锁,异步处理耗时任务。把这些基础打牢,再复杂的订单流程也能hold住。