手续费率, 'fee_amount' => 手续费金额, 'actual_amount' => 实际到账金额] */ public static function calculateInFee(TransferApp $app, string $amount, array $context = []): FeeCalculatedEvent { return self::calculateFeeWithEvents($app, $amount, 'in', $context); } /** * 计算转出手续费 * * @param TransferApp $app 划转应用 * @param string $amount 转出金额 * @param array $context 额外的上下文数据 * @return array ['fee_rate' => 手续费率, 'fee_amount' => 手续费金额, 'actual_amount' => 实际到账金额] */ public static function calculateOutFee(TransferApp $app, string $amount, array $context = []): FeeCalculatedEvent { return self::calculateFeeWithEvents($app, $amount, 'out', $context); } /** * 带事件机制的手续费计算方法 * * @param TransferApp $app 划转应用 * @param string $amount 金额 * @param string $type 类型:'in' 或 'out' * @param array $context 额外的上下文数据 * @return FeeCalculatedEvent */ private static function calculateFeeWithEvents(TransferApp $app, string $amount, string $type, array $context = []): FeeCalculatedEvent { // 获取应用配置的手续费参数 if ($type === 'in') { $feeRate = $app->fee_in_rate; $minFee = $app->fee_in_min; $maxFee = $app->fee_in_max; } else { $feeRate = $app->fee_out_rate; $minFee = $app->fee_out_min; $maxFee = $app->fee_out_max; } // 使用原有逻辑计算基础手续费 $baseResult = self::calculateFee($amount, $feeRate, $minFee, $maxFee); // 创建手续费计算事件 $event = new FeeCalculatingEvent( app: $app, amount: $amount, type: $type, feeAmount: $baseResult['fee_amount'], context: $context ); // 触发事件,允许其他模块修改手续费 Event::dispatch($event); // 触发计算完成事件 $calculatedEvent = FeeCalculatedEvent::fromCalculatingEvent($event); return $calculatedEvent; } /** * 计算手续费的通用方法 * * @param string $amount 金额 * @param float $feeRate 手续费率 * @param float $minFee 最低手续费 * @param float $maxFee 最高手续费 * @return array */ private static function calculateFee(string $amount, float $feeRate, float $minFee, float $maxFee): array { $amountDecimal = bcmul($amount, '1', 4); // 转换为4位小数 // 按比例计算手续费 $feeAmount = bcmul($amountDecimal, (string)$feeRate, 4); // 应用最低手续费限制 if (bccomp($feeAmount, (string)$minFee, 4) < 0) { $feeAmount = bcmul((string)$minFee, '1', 4); } // 应用最高手续费限制(如果设置了) if ($maxFee > 0 && bccomp($feeAmount, (string)$maxFee, 4) > 0) { $feeAmount = bcmul((string)$maxFee, '1', 4); } // 计算实际到账金额 $actualAmount = bcsub($amountDecimal, $feeAmount, 4); return [ 'fee_rate' => $feeRate, 'fee_amount' => $feeAmount, 'actual_amount' => $actualAmount, ]; } /** * 检查应用是否启用了手续费 * * @param TransferApp $app 划转应用 * @param string $type 类型:'in' 或 'out' * @return bool */ public static function isFeeEnabled(TransferApp $app, string $type): bool { if ($type === 'in') { return $app->fee_in_rate > 0 || $app->fee_in_min > 0; } elseif ($type === 'out') { return $app->fee_out_rate > 0 || $app->fee_out_min > 0; } return false; } /** * 获取应用的手续费配置信息 * * @param TransferApp $app 划转应用 * @return array */ public static function getFeeConfig(TransferApp $app): array { return [ 'in' => [ 'rate' => $app->fee_in_rate, 'min' => $app->fee_in_min, 'max' => $app->fee_in_max, 'enabled' => self::isFeeEnabled($app, 'in'), ], 'out' => [ 'rate' => $app->fee_out_rate, 'min' => $app->fee_out_min, 'max' => $app->fee_out_max, 'enabled' => self::isFeeEnabled($app, 'out'), ], 'account_uid' => $app->fee_account_uid, ]; } /** * 获取订单的手续费统计信息 * * @param int $appId 应用ID(0表示所有应用) * @param string $startDate 开始日期 * @param string $endDate 结束日期 * @return array */ public static function getFeeStatistics(int $appId = 0, string $startDate = '', string $endDate = ''): array { $query = TransferOrder::query() ->where('status', 100) // 只统计已完成的订单 ->where('fee_amount', '>', 0); // 只统计有手续费的订单 if ($appId > 0) { $query->where('transfer_app_id', $appId); } if ($startDate) { $query->whereDate('created_at', '>=', $startDate); } if ($endDate) { $query->whereDate('created_at', '<=', $endDate); } $orders = $query->get(); $stats = [ 'total_orders' => $orders->count(), 'total_fee' => $orders->sum('fee_amount'), 'avg_fee_rate' => $orders->avg('fee_rate'), 'in_orders' => $orders->where('type', TransferType::IN)->count(), 'in_fee' => $orders->where('type', TransferType::IN)->sum('fee_amount'), 'out_orders' => $orders->where('type', TransferType::OUT)->count(), 'out_fee' => $orders->where('type', TransferType::OUT)->sum('fee_amount'), 'date_range' => [ 'start' => $startDate, 'end' => $endDate, ], ]; return $stats; } /** * 获取应用的手续费收入统计 * * @param int $appId 应用ID * @param int $days 统计天数(默认30天) * @return array */ public static function getAppFeeIncome(int $appId, int $days = 30): array { $startDate = now()->subDays($days)->startOfDay(); $orders = TransferOrder::where('transfer_app_id', $appId) ->where('status', 100) ->where('fee_amount', '>', 0) ->where('created_at', '>=', $startDate) ->get(); $dailyStats = []; for ($i = 0; $i < $days; $i++) { $date = now()->subDays($i)->format('Y-m-d'); $dayOrders = $orders->filter(function ($order) use ($date) { return $order->created_at->format('Y-m-d') === $date; }); $dailyStats[$date] = [ 'date' => $date, 'orders' => $dayOrders->count(), 'fee_amount' => $dayOrders->sum('fee_amount'), 'in_orders' => $dayOrders->where('type', TransferType::IN)->count(), 'in_fee' => $dayOrders->where('type', TransferType::IN)->sum('fee_amount'), 'out_orders' => $dayOrders->where('type', TransferType::OUT)->count(), 'out_fee' => $dayOrders->where('type', TransferType::OUT)->sum('fee_amount'), ]; } return [ 'app_id' => $appId, 'days' => $days, 'total_fee' => $orders->sum('fee_amount'), 'total_orders' => $orders->count(), 'avg_daily_fee' => $orders->sum('fee_amount') / $days, 'daily_stats' => array_reverse($dailyStats, true), ]; } /** * 验证手续费配置的合法性 * * @param array $config 手续费配置 * @return array ['valid' => bool, 'errors' => array] */ public static function validateFeeConfig(array $config): array { $errors = []; // 验证费率范围 if (isset($config['fee_in_rate']) && ($config['fee_in_rate'] < 0 || $config['fee_in_rate'] > 1)) { $errors[] = '转入手续费率必须在0-1之间'; } if (isset($config['fee_out_rate']) && ($config['fee_out_rate'] < 0 || $config['fee_out_rate'] > 1)) { $errors[] = '转出手续费率必须在0-1之间'; } // 验证最低手续费 if (isset($config['fee_in_min']) && $config['fee_in_min'] < 0) { $errors[] = '转入最低手续费不能为负数'; } if (isset($config['fee_out_min']) && $config['fee_out_min'] < 0) { $errors[] = '转出最低手续费不能为负数'; } // 验证最高手续费 if (isset($config['fee_in_max']) && $config['fee_in_max'] < 0) { $errors[] = '转入最高手续费不能为负数'; } if (isset($config['fee_out_max']) && $config['fee_out_max'] < 0) { $errors[] = '转出最高手续费不能为负数'; } // 验证最低和最高手续费的关系 if (isset($config['fee_in_min'], $config['fee_in_max']) && $config['fee_in_max'] > 0 && $config['fee_in_min'] > $config['fee_in_max']) { $errors[] = '转入最低手续费不能大于最高手续费'; } if (isset($config['fee_out_min'], $config['fee_out_max']) && $config['fee_out_max'] > 0 && $config['fee_out_min'] > $config['fee_out_max']) { $errors[] = '转出最低手续费不能大于最高手续费'; } return [ 'valid' => empty($errors), 'errors' => $errors, ]; } /** * 格式化手续费金额显示 * * @param string|float $amount 手续费金额 * @param int $decimals 小数位数 * @return string */ public static function formatFeeAmount($amount, int $decimals = 4): string { return number_format((float)$amount, $decimals); } /** * 格式化手续费率显示 * * @param float $rate 手续费率 * @param int $decimals 小数位数 * @return string */ public static function formatFeeRate(float $rate, int $decimals = 2): string { return number_format($rate * 100, $decimals) . '%'; } /** * 转移手续费到指定用户 * * 将手续费从指定应用的手续费账户转移到目标用户账户 * * @param int $appId 出钱的应用ID(从该应用的手续费账户扣除) * @param int $targetUserId 目标用户ID(收钱的) * @param float $amount 金额(钱数) * @param int $orderId 订单ID(划转依据) * @param string $transferType 转移类型(默认为'fee_transfer') * @return bool|string 成功返回true,失败返回错误信息 */ public static function transfer(int $appId, int $targetUserId, float $amount, int $orderId, string $transferType = 'fee_transfer') { // 检查金额是否有效 if ($amount <= 0) { return '转移金额必须大于0'; } // 获取应用配置 $app = TransferApp::find($appId); if (!$app) { return '应用不存在'; } // 获取手续费收取账户UID $feeAccountUid = $app->getFeeAccountUid(); // 检查目标用户是否存在 $targetUser = \App\Module\User\Models\User::find($targetUserId); if (!$targetUser) { return '目标用户不存在'; } // 创建资金服务实例(从手续费账户转出) $fundService = new \App\Module\Fund\Services\FundService($feeAccountUid, $app->fund_id); // 构建备注信息 $remark = "手续费转移-{$app->title}-订单{$orderId}"; try { // 使用trade方法进行资金转移 $result = $fundService->trade( $targetUserId, $amount, $transferType, $orderId, $remark ); if (is_string($result)) { // 转移失败 \UCore\Helper\Logger::error("手续费转移失败", [ 'app_id' => $appId, 'fee_account_uid' => $feeAccountUid, 'target_user_id' => $targetUserId, 'amount' => $amount, 'order_id' => $orderId, 'transfer_type' => $transferType, 'error' => $result ]); return $result; } // 转移成功,记录日志 \UCore\Helper\Logger::info("手续费转移成功", [ 'app_id' => $appId, 'app_title' => $app->title, 'fee_account_uid' => $feeAccountUid, 'target_user_id' => $targetUserId, 'target_username' => $targetUser->username, 'amount' => $amount, 'order_id' => $orderId, 'transfer_type' => $transferType, 'fund_id' => $app->fund_id ]); return true; } catch (\Exception $e) { // 异常处理 \UCore\Helper\Logger::error("手续费转移异常", [ 'app_id' => $appId, 'target_user_id' => $targetUserId, 'amount' => $amount, 'order_id' => $orderId, 'transfer_type' => $transferType, 'exception' => $e->getMessage(), 'trace' => $e->getTraceAsString() ]); return '手续费转移异常: ' . $e->getMessage(); } } }