捐赠订单处理应采用状态机+队列+事务保护协同机制:用独立状态机管理生命周期,异步任务解耦副作用,支付回调通过事务后置触发,查询使用AR链式方法保障性能与语义。

捐赠业务后端的订单处理,核心是把“用户发起捐赠”这个动作快速响应、可靠落地,同时解耦通知、记账、库存(如有实物回馈)、第三方回调等副作用。Yii2 中推荐用「状态机 + 队列 + 事务保护」三者协同,而不是在控制器里堆逻辑。
用独立状态机管理捐赠单生命周期
别把状态流转写在 Donation 模型里或控制器中。新建 common/services/DonationStateMachine.php,统一处理「待支付 → 已支付 → 已退款 → 已发放回馈」等关键跳转:
- 所有变更必须走
$stateMachine->apply($donation, 'paid', $context)入口,确保前置校验(如金额非负、重复支付拦截)一致 - 状态字段用 PHP 枚举定义(
enum DonationStatus: string),避免魔法字符串和拼写错误 - 行为(Behavior)只用于快捷方法,比如
$donation->canRefund()或自动绑定数据库 scope,不执行流转
支付成功后触发异步任务,不阻塞响应
用户点击支付后,前端调用 /donation/pay 接口,后端只需生成订单、返回支付参数,其他全部丢进队列:
- 控制器中:创建 Donation 记录 → 调用
Yii::$app->queue->push(new \common\jobs\SendReceiptJob(['donation_id' => $donation->id])) - Job 类里不要操作 ActiveRecord 实例(如
$donation->save()),只传原始 ID 或数组;发邮件、写日志、调用短信 SDK 等都放execute()里 - 启动消费者:
php yii queue/listen --max=5,防止内存累积
支付回调必须走事务后置触发
支付宝/微信 notify 回调是捐赠流程中最关键的一环,必须防重、验签、更新状态、触发后续动作——但这些不能在事务内直接执行:
- 回调验证通过后,开启事务更新
donation.status = paid和pay_at字段 - 副作用(如发电子证书、增加用户积分、记录财务流水)全部注册到
Transaction::onCommit() - 如果用了 DB 驱动的队列,
push()可放在onCommit里,保证和状态更新强一致;否则改用延迟双删策略清理缓存
捐赠单查询与统计要兼顾性能和语义
后台常需查「今日捐赠人数」「累计金额」「各渠道占比」,别手写 SQL,用 ActiveRecord 链式查询更安全:
- 查某天所有已支付捐赠单:
Donation::find()->where(['status' => DonationStatus::Paid->value])->andWhere(['>=', 'created_at', '2026-08-17 00:00:00'])->all() - 统计总金额:
Donation::find()->where(['status' => DonationStatus::Paid->value])->sum('amount')(返回数字,不是对象) - 按支付方式分组计数:
Donation::find()->select(['pay_channel', 'count(*) as cnt'])->groupBy('pay_channel')->asArray()->all()


















