# Transfer模块手续费事件系统 ## 概述 Transfer模块的手续费事件系统允许其他模块监听手续费计算过程,并根据业务需求修改手续费数额。这提供了一个灵活的扩展机制,支持各种复杂的手续费策略。 ## 事件类型 ### 1. FeeCalculatingEvent(手续费计算中事件) 在手续费计算过程中触发,允许监听器修改手续费数额。 **事件属性:** - `app`: TransferApp - 划转应用 - `amount`: string - 原始金额 - `type`: string - 手续费类型('in' 或 'out') - `feeRate`: float - 手续费率 - `feeAmount`: string - 手续费金额 - `actualAmount`: string - 实际到账金额 - `isModified`: bool - 是否被修改过 - `modificationReason`: string - 修改原因 - `modifiedBy`: string - 修改者标识 - `context`: array - 额外的上下文数据 **可用方法:** - `modifyFeeAmount(newFeeAmount, reason, modifiedBy)` - 修改手续费金额 - `modifyFeeRate(newFeeRate, reason, modifiedBy)` - 修改手续费率 - `setFree(reason, modifiedBy)` - 设置免手续费 - `addFee(additionalFee, reason, modifiedBy)` - 增加手续费 - `reduceFee(discountFee, reason, modifiedBy)` - 减少手续费 ### 2. FeeCalculatedEvent(手续费计算完成事件) 在手续费计算完成后触发,用于记录日志、统计等。 **事件属性:** - 包含FeeCalculatingEvent的所有最终属性 - 只读,不能修改 ## 使用示例 ### 1. 创建监听器 ```php shouldApplyDiscount($event)) { // 应用折扣 $event->reduceFee( discountFee: '1.0000', reason: '特殊用户折扣', modifiedBy: 'YourModule' ); } } private function shouldApplyDiscount(FeeCalculatingEvent $event): bool { // 你的业务逻辑 return true; } } ``` ### 2. 注册监听器 在你的模块的ServiceProvider中注册监听器: ```php use Illuminate\Support\Facades\Event; use App\Module\Transfer\Events\FeeCalculatingEvent; use App\Module\YourModule\Listeners\YourFeeListener; public function boot() { Event::listen( FeeCalculatingEvent::class, YourFeeListener::class ); } ``` ### 3. 使用上下文数据 在调用手续费计算时传递上下文数据: ```php // 在创建订单时传递用户信息 $feeResult = TransferService::calculateOutFee( transferAppId: 1, amount: '100.00', context: [ 'user_id' => 12345, 'order_type' => 'premium', 'source' => 'mobile_app' ] ); ``` 在监听器中使用上下文数据: ```php public function handle(FeeCalculatingEvent $event): void { $userId = $event->getContext('user_id'); $orderType = $event->getContext('order_type'); if ($orderType === 'premium') { // 高级订单享受优惠 $event->reduceFee('0.5000', '高级订单优惠', 'PremiumService'); } } ``` ## 常见使用场景 ### 1. VIP用户优惠 ```php public function handle(FeeCalculatingEvent $event): void { $userId = $event->getContext('user_id'); if ($this->userService->isVip($userId)) { // VIP用户50%折扣 $discount = bcmul($event->feeAmount, '0.5', 4); $event->reduceFee($discount, 'VIP用户50%折扣', 'VipService'); } } ``` ### 2. 大额交易优惠 ```php public function handle(FeeCalculatingEvent $event): void { if (bccomp($event->amount, '1000.0000', 4) >= 0) { // 大额交易手续费上限 if (bccomp($event->feeAmount, '10.0000', 4) > 0) { $event->modifyFeeAmount('10.0000', '大额交易上限', 'LargeAmountService'); } } } ``` ### 3. 合作伙伴免费 ```php public function handle(FeeCalculatingEvent $event): void { $partnerAppIds = [1, 2, 3]; if (in_array($event->app->id, $partnerAppIds)) { $event->setFree('合作伙伴免费', 'PartnerService'); } } ``` ### 4. 动态手续费率 ```php public function handle(FeeCalculatingEvent $event): void { // 根据市场情况动态调整手续费率 $marketRate = $this->marketService->getCurrentFeeRate(); if ($marketRate !== $event->feeRate) { $event->modifyFeeRate($marketRate, '市场动态调整', 'MarketService'); } } ``` ### 5. 时间段优惠 ```php public function handle(FeeCalculatingEvent $event): void { $hour = now()->hour; // 夜间时段优惠 if ($hour >= 22 || $hour <= 6) { $discount = bcmul($event->feeAmount, '0.3', 4); $event->reduceFee($discount, '夜间时段70%折扣', 'TimeDiscountService'); } } ``` ## 最佳实践 ### 1. 监听器优先级 如果有多个监听器,注意处理顺序: ```php // 在监听器中检查是否已被修改 public function handle(FeeCalculatingEvent $event): void { if ($event->isModified) { // 已经被其他监听器修改过,可以选择跳过或继续处理 return; } // 你的处理逻辑 } ``` ### 2. 日志记录 记录手续费修改的详细信息: ```php public function handle(FeeCalculatingEvent $event): void { $originalFee = $event->feeAmount; // 应用你的逻辑 $event->reduceFee('1.0000', '特殊优惠', 'YourService'); Log::info('手续费优惠应用', [ 'app_id' => $event->app->id, 'amount' => $event->amount, 'original_fee' => $originalFee, 'final_fee' => $event->feeAmount, 'reason' => '特殊优惠', ]); } ``` ### 3. 错误处理 在监听器中添加错误处理: ```php public function handle(FeeCalculatingEvent $event): void { try { // 你的业务逻辑 if ($this->shouldApplyDiscount($event)) { $event->reduceFee('1.0000', '优惠', 'YourService'); } } catch (\Exception $e) { Log::error('手续费监听器错误', [ 'error' => $e->getMessage(), 'app_id' => $event->app->id, 'amount' => $event->amount, ]); // 不要重新抛出异常,避免影响主流程 } } ``` ### 4. 性能考虑 避免在监听器中执行耗时操作: ```php public function handle(FeeCalculatingEvent $event): void { // 使用缓存避免重复查询 $userLevel = Cache::remember( "user_level_{$event->getContext('user_id')}", 300, // 5分钟缓存 fn() => $this->userService->getUserLevel($event->getContext('user_id')) ); if ($userLevel === 'premium') { $event->reduceFee('0.5000', '高级用户优惠', 'UserLevelService'); } } ``` ## 事件流程 1. **计算基础手续费**: 根据应用配置计算基础手续费 2. **创建事件**: 创建FeeCalculatingEvent事件 3. **触发事件**: 分发事件给所有监听器 4. **监听器处理**: 各监听器按注册顺序处理事件 5. **获取结果**: 获取最终的手续费计算结果 6. **触发完成事件**: 分发FeeCalculatedEvent事件 7. **返回结果**: 返回最终的手续费信息 ## 注意事项 1. **监听器顺序**: 多个监听器的执行顺序可能影响最终结果 2. **数据一致性**: 确保手续费修改不会导致负数或异常值 3. **性能影响**: 避免在监听器中执行耗时操作 4. **错误处理**: 监听器中的异常不应影响主流程 5. **日志记录**: 记录手续费修改的详细信息便于审计 6. **测试覆盖**: 为监听器编写充分的测试用例 ## 扩展性 这个事件系统提供了极大的扩展性: - **模块化**: 每个模块可以独立实现自己的手续费策略 - **可配置**: 可以通过配置启用/禁用特定的手续费策略 - **可测试**: 每个监听器都可以独立测试 - **可维护**: 手续费逻辑与核心业务逻辑分离 - **可扩展**: 可以轻松添加新的手续费策略而不影响现有代码