| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298 |
- <?php
- namespace App\Module\Transfer\Services;
- use App\Module\Transfer\Models\TransferApp;
- use App\Module\Transfer\Models\TransferOrder;
- use App\Module\Transfer\Enums\TransferType;
- /**
- * 手续费服务类
- *
- * 负责处理Transfer模块的手续费计算、收取和统计功能
- */
- class FeeService
- {
- /**
- * 计算转入手续费
- *
- * @param TransferApp $app 划转应用
- * @param string $amount 转入金额
- * @return array ['fee_rate' => 手续费率, 'fee_amount' => 手续费金额, 'actual_amount' => 实际到账金额]
- */
- public static function calculateInFee(TransferApp $app, string $amount): array
- {
- return self::calculateFee(
- $amount,
- $app->fee_in_rate,
- $app->fee_in_min,
- $app->fee_in_max
- );
- }
- /**
- * 计算转出手续费
- *
- * @param TransferApp $app 划转应用
- * @param string $amount 转出金额
- * @return array ['fee_rate' => 手续费率, 'fee_amount' => 手续费金额, 'actual_amount' => 实际到账金额]
- */
- public static function calculateOutFee(TransferApp $app, string $amount): array
- {
- return self::calculateFee(
- $amount,
- $app->fee_out_rate,
- $app->fee_out_min,
- $app->fee_out_max
- );
- }
- /**
- * 计算手续费的通用方法
- *
- * @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) . '%';
- }
- }
|