| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687 |
- <?php
- namespace App\Services;
- use App\Services\Sharing\EffectiveBet as Money;
- use Illuminate\Contracts\Cache\LockTimeoutException;
- use Illuminate\Support\Facades\Cache;
- use Illuminate\Support\Facades\DB;
- use Illuminate\Support\Facades\Schema;
- class ThirdGameCommissionService
- {
- public const BASIS = 'game_gross_profit';
- public function amount(string $platform, string $grossProfit, array $rates): ?string
- {
- if (bccomp($grossProfit, '0', 10) === 0) return Money::zero();
- $ratio = $rates['ratios'][strtolower(trim($platform))] ?? null;
- return $ratio === null ? null : bcmul($grossProfit, $ratio, 10);
- }
- /** Finance reads original orders, independently of the reporting projection's sync schedule. */
- public function summary(?string $start, ?string $end): array
- {
- $known = Money::zero(); $people = []; $rates = null;
- $quality = ['missing_ratio' => 0, 'missing_time' => 0, 'missing_amount' => 0, 'foreign_currency' => 0];
- if (Schema::hasTable('third_game_orders')) {
- $query = DB::table('third_game_orders as o')->join('users as u', 'u.id', '=', 'o.user_id')
- ->where(function ($q) { $q->whereNull('u.from')->orWhere('u.from', '<>', 2); })
- ->where('o.status', 1)->select('o.*');
- $query->where('o.player_id', config('third_game.player_prefix', 'u') === 'tu' ? 'like' : 'not like', 'tu%');
- if ($start !== null && $end !== null) {
- $query->where(function ($q) use ($start, $end) {
- $q->whereBetween('o.last_update_time', [$start, $end])
- ->orWhere(function ($q) use ($end) { $q->whereNull('o.last_update_time')->where('o.bet_time', '<=', $end); });
- });
- }
- foreach ($query->orderBy('o.id')->cursor() as $row) {
- if ($row->last_update_time === null) { ++$quality['missing_time']; continue; }
- if (strtoupper(trim((string) $row->currency)) !== config('reporting.currency', 'CNY')) { ++$quality['foreign_currency']; continue; }
- if ($row->settled_amount === null) { ++$quality['missing_amount']; continue; }
- $gross = bcsub('0', (string) $row->settled_amount, 10);
- if (bccomp($gross, '0', 10) === 0) continue;
- $rates ??= $this->rates();
- $amount = $this->amount((string) $row->platform, $gross, $rates);
- if ($amount === null) { ++$quality['missing_ratio']; continue; }
- $known = bcadd($known, $amount, 10);
- if (bccomp($amount, '0', 10) !== 0) $people[$row->user_id] = true;
- }
- }
- $complete = array_sum($quality) === 0;
- return ['amount' => $complete ? $known : null, 'known_amount' => $known, 'people' => count($people),
- 'complete' => $complete, 'quality' => $quality, 'basis' => self::BASIS, 'estimated' => true,
- 'rate_date' => $rates['date'] ?? null, 'rates_fetched_at' => $rates['fetched_at'] ?? null];
- }
- /** One quota request per merchant/currency/Beijing day, including failed requests. */
- public function rates(): array
- {
- $now = now('Asia/Shanghai');
- $unavailable = ['date' => $now->toDateString(), 'fetched_at' => null, 'available' => false, 'ratios' => []];
- if (trim((string) config('third_game.sn')) === '' || trim((string) config('third_game.key')) === '') return $unavailable;
- $merchant = hash('sha256', json_encode([
- config('third_game.api_url'), config('third_game.sn'), config('third_game.key'), config('third_game.currency', 'CNY'),
- ]));
- $key = 'third_game:commission_rates:' . $merchant . ':' . $now->toDateString();
- $cached = Cache::get($key);
- if (is_array($cached)) return $cached;
- try {
- return Cache::lock($key . ':lock', max(30, (int) config('third_game.timeout', 10) + 10))->block(2, function () use ($key, $now, $unavailable) {
- $cached = Cache::get($key);
- if (is_array($cached)) return $cached;
- // Reserve the day before HTTP so concurrent requests and failures never retry in a loop.
- Cache::put($key, $unavailable, $now->copy()->endOfDay());
- $result = app(ThirdGameBalanceService::class)->quotaRatios();
- $snapshot = ['date' => $now->toDateString(), 'fetched_at' => now('Asia/Shanghai')->toDateTimeString(),
- 'available' => (bool) $result['ok'], 'ratios' => $result['ratios']];
- Cache::put($key, $snapshot, $now->copy()->endOfDay());
- return $snapshot;
- });
- } catch (LockTimeoutException $e) {
- return Cache::get($key, $unavailable);
- }
- }
- }
|