FEE_EVENT_SYSTEM.md 7.9 KB

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

namespace App\Module\YourModule\Listeners;

use App\Module\Transfer\Events\FeeCalculatingEvent;

class YourFeeListener
{
    public function handle(FeeCalculatingEvent $event): void
    {
        // 检查业务条件
        if ($this->shouldApplyDiscount($event)) {
            // 应用折扣
            $event->reduceFee(
                discountFee: '1.0000',
                reason: '特殊用户折扣',
                modifiedBy: 'YourModule'
            );
        }
    }
    
    private function shouldApplyDiscount(FeeCalculatingEvent $event): bool
    {
        // 你的业务逻辑
        return true;
    }
}

2. 注册监听器

在你的模块的ServiceProvider中注册监听器:

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. 使用上下文数据

在调用手续费计算时传递上下文数据:

// 在创建订单时传递用户信息
$feeResult = TransferService::calculateOutFee(
    transferAppId: 1,
    amount: '100.00',
    context: [
        'user_id' => 12345,
        'order_type' => 'premium',
        'source' => 'mobile_app'
    ]
);

在监听器中使用上下文数据:

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用户优惠

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. 大额交易优惠

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. 合作伙伴免费

public function handle(FeeCalculatingEvent $event): void
{
    $partnerAppIds = [1, 2, 3];
    
    if (in_array($event->app->id, $partnerAppIds)) {
        $event->setFree('合作伙伴免费', 'PartnerService');
    }
}

4. 动态手续费率

public function handle(FeeCalculatingEvent $event): void
{
    // 根据市场情况动态调整手续费率
    $marketRate = $this->marketService->getCurrentFeeRate();
    
    if ($marketRate !== $event->feeRate) {
        $event->modifyFeeRate($marketRate, '市场动态调整', 'MarketService');
    }
}

5. 时间段优惠

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. 监听器优先级

如果有多个监听器,注意处理顺序:

// 在监听器中检查是否已被修改
public function handle(FeeCalculatingEvent $event): void
{
    if ($event->isModified) {
        // 已经被其他监听器修改过,可以选择跳过或继续处理
        return;
    }
    
    // 你的处理逻辑
}

2. 日志记录

记录手续费修改的详细信息:

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. 错误处理

在监听器中添加错误处理:

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. 性能考虑

避免在监听器中执行耗时操作:

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. 测试覆盖: 为监听器编写充分的测试用例

扩展性

这个事件系统提供了极大的扩展性:

  • 模块化: 每个模块可以独立实现自己的手续费策略
  • 可配置: 可以通过配置启用/禁用特定的手续费策略
  • 可测试: 每个监听器都可以独立测试
  • 可维护: 手续费逻辑与核心业务逻辑分离
  • 可扩展: 可以轻松添加新的手续费策略而不影响现有代码