instance('config', new Repository(['third_game' => [ 'api_url' => 'https://supplier.test', 'sn' => 'test-merchant', 'key' => 'test-secret', 'currency' => 'CNY', ]])); $app->instance('cache', new CacheRepository(new ArrayStore())); $app->instance(Factory::class, new Factory()); $app->instance('log', new \Psr\Log\NullLogger()); Facade::setFacadeApplication($app); Carbon::setTestNow(Carbon::parse('2026-11-04 12:00:00', 'Asia/Shanghai')); Http::preventStrayRequests(); } protected function tearDown(): void { Carbon::setTestNow(); Facade::clearResolvedInstances(); parent::tearDown(); } public function test_documented_platform_spelling_zero_rates_and_daily_refresh(): void { Http::fake(['https://supplier.test/api/server/quota' => Http::sequence() ->push(['code' => 10000, 'data' => ['ratios' => [ ['platfrom' => ' AG ', 'ratio' => 0.09], ['platfrom' => 'pg', 'ratio' => '0'], ]]]) ->push(['code' => 10000, 'data' => ['ratios' => [['platfrom' => 'ag', 'ratio' => '0.10']]]])]); $service = new ThirdGameCommissionService(); $first = $service->rates(); $this->assertSame(['ag' => '0.0900000000', 'pg' => '0.0000000000'], $first['ratios']); $this->assertSame($first, (new ThirdGameCommissionService())->rates()); Http::assertSentCount(1); Http::assertSent(fn ($request) => $request->method() === 'POST' && $request['currency'] === 'CNY' && $request->hasHeader('sn', 'test-merchant') && $request->hasHeader('sign')); Carbon::setTestNow(Carbon::parse('2026-11-05 00:00:01', 'Asia/Shanghai')); $this->assertSame('0.1000000000', $service->rates()['ratios']['ag']); Http::assertSentCount(2); } public function test_provider_failure_is_cached_for_the_day_and_does_not_turn_into_a_zero_rate(): void { Http::fake(['*' => Http::response(['code' => 10404, 'data' => null])]); $service = new ThirdGameCommissionService(); $this->assertFalse($service->rates()['available']); $this->assertSame([], $service->rates()['ratios']); Http::assertSentCount(1); } public function test_merchant_balance_shares_the_quota_request_and_preserves_the_last_success_on_failure(): void { Http::fake(['https://supplier.test/api/server/quota' => Http::sequence() ->push(['code' => 10000, 'data' => ['CNY' => '100.125', 'ratios' => []]]) ->push(['code' => 10009, 'data' => null]) ->push(['code' => 10000, 'data' => ['CNY' => '0', 'ratios' => []]])]); $service = new ThirdGameBalanceService(); $start = now(); $this->assertNull($service->merchantBalance()['balance']); Http::assertNothingSent(); $service->refreshMerchantBalance(); $this->assertTrue($service->quotaRatios()['ok']); $this->assertSame(['balance' => '100.1250000000', 'currency' => 'CNY', 'stale' => false], $service->merchantBalance()); Carbon::setTestNow($start->copy()->addSeconds(59)); $service->refreshMerchantBalance(); Http::assertSentCount(1); Carbon::setTestNow($start->copy()->addSeconds(60)); $service->refreshMerchantBalance(); $service->refreshMerchantBalance(); $this->assertSame(['balance' => '100.1250000000', 'currency' => 'CNY', 'stale' => true], $service->merchantBalance()); Http::assertSentCount(2); Carbon::setTestNow($start->copy()->addSeconds(120)); $service->refreshMerchantBalance(); $this->assertSame(['balance' => '0.0000000000', 'currency' => 'CNY', 'stale' => false], $service->merchantBalance()); Http::assertSentCount(3); } public function test_the_shared_quota_cooldown_does_not_reuse_yesterdays_rates(): void { Http::fake(['*' => Http::sequence() ->push(['code' => 10000, 'data' => ['CNY' => '10', 'ratios' => [['platfrom' => 'ag', 'ratio' => '0.1']]]]) ->push(['code' => 10000, 'data' => ['CNY' => '10', 'ratios' => [['platfrom' => 'ag', 'ratio' => '0.2']]]])]); Carbon::setTestNow(Carbon::parse('2026-11-04 23:59:50', 'Asia/Shanghai')); (new ThirdGameBalanceService())->refreshMerchantBalance(); Carbon::setTestNow(Carbon::parse('2026-11-05 00:00:00', 'Asia/Shanghai')); $service = new ThirdGameCommissionService(); $this->assertFalse($service->rates()['available']); Http::assertSentCount(1); Carbon::setTestNow(Carbon::parse('2026-11-05 00:01:00', 'Asia/Shanghai')); $this->assertSame(['ag' => '0.2000000000'], $service->rates()['ratios']); Http::assertSentCount(2); } public function test_malformed_and_conflicting_rates_are_not_accepted_and_merchant_caches_are_isolated(): void { Http::fake(['*' => Http::response(['code' => 10000, 'data' => ['ratios' => [ ['platfrom' => 'negative', 'ratio' => '-0.1'], ['platfrom' => 'large', 'ratio' => '1.01'], ['platfrom' => 'bad', 'ratio' => 'NaN'], ['platfrom' => 'ag', 'ratio' => '.09'], ['platfrom' => 'pg', 'ratio' => '0.1'], ['platfrom' => 'PG', 'ratio' => '0.2'], ['platform' => 'pp', 'ratio' => '0.1234567891'], ]]])]); $service = new ThirdGameCommissionService(); $this->assertSame(['pp' => '0.1234567891'], $service->rates()['ratios']); config(['third_game.sn' => 'another-merchant']); $service->rates(); Http::assertSentCount(2); config(['third_game.currency' => 'USD']); $service->rates(); Http::assertSentCount(3); } }