|
|
@@ -0,0 +1,309 @@
|
|
|
+# 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
|
|
|
+<?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中注册监听器:
|
|
|
+
|
|
|
+```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. **测试覆盖**: 为监听器编写充分的测试用例
|
|
|
+
|
|
|
+## 扩展性
|
|
|
+
|
|
|
+这个事件系统提供了极大的扩展性:
|
|
|
+
|
|
|
+- **模块化**: 每个模块可以独立实现自己的手续费策略
|
|
|
+- **可配置**: 可以通过配置启用/禁用特定的手续费策略
|
|
|
+- **可测试**: 每个监听器都可以独立测试
|
|
|
+- **可维护**: 手续费逻辑与核心业务逻辑分离
|
|
|
+- **可扩展**: 可以轻松添加新的手续费策略而不影响现有代码
|