seed(CurrencySeeder::class); $this->seed(LotterySettingsSeeder::class); }); test('credit player wallet logs reads credit_ledger not wallet_txns', function (): void { $player = Player::query()->create([ 'site_code' => 'default_site', 'site_player_id' => 'native:logs-1', 'auth_source' => PlayerAuthSource::LOTTERY_NATIVE, 'funding_mode' => PlayerFundingMode::CREDIT, 'username' => 'credit_logs', 'default_currency' => 'NPR', 'status' => 0, ]); DB::table('player_credit_accounts')->insert([ 'player_id' => $player->id, 'credit_limit' => 200, 'used_credit' => 10, 'frozen_credit' => 0, 'created_at' => now(), 'updated_at' => now(), ]); DB::table('credit_ledger')->insert([ 'owner_type' => 'player', 'owner_id' => $player->id, 'amount' => -1000, 'reason' => 'bet_hold', 'ref_type' => 'bet', 'ref_id' => null, 'created_at' => now(), 'updated_at' => now(), ]); $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?page=1&size=10') ->assertOk() ->assertJsonPath('data.ledger_source', 'credit_ledger') ->assertJsonPath('data.funding_mode', PlayerFundingMode::CREDIT) ->assertJsonPath('data.auth_source', PlayerAuthSource::LOTTERY_NATIVE) ->assertJsonPath('data.total', 1) ->assertJsonPath('data.items.0.type', 'bet') ->assertJsonPath('data.items.0.biz_type', 'bet_hold') ->assertJsonPath('data.items.0.ledger_source', 'credit_ledger'); }); test('credit player wallet logs distinguish win credit from bill settlement', function (): void { $player = Player::query()->create([ 'site_code' => 'default_site', 'site_player_id' => 'native:logs-2', 'auth_source' => PlayerAuthSource::LOTTERY_NATIVE, 'funding_mode' => PlayerFundingMode::CREDIT, 'username' => 'credit_logs_2', 'default_currency' => 'NPR', 'status' => 0, ]); DB::table('player_credit_accounts')->insert([ 'player_id' => $player->id, 'credit_limit' => 500, 'used_credit' => 0, 'frozen_credit' => 0, 'created_at' => now(), 'updated_at' => now(), ]); DB::table('credit_ledger')->insert([ [ 'owner_type' => 'player', 'owner_id' => $player->id, 'amount' => 3600, 'reason' => 'settlement_payout', 'ref_type' => 'settlement_bill', 'ref_id' => 25, 'created_at' => now()->subMinute(), 'updated_at' => now()->subMinute(), ], [ 'owner_type' => 'player', 'owner_id' => $player->id, 'amount' => 1200, 'reason' => 'game_settlement_win', 'ref_type' => 'ticket_item', 'ref_id' => 99, 'created_at' => now(), 'updated_at' => now(), ], ]); $response = $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?page=1&size=10'); $response->assertOk() ->assertJsonPath('data.total', 2) ->assertJsonPath('data.items.0.type', 'game_settlement') ->assertJsonPath('data.items.0.biz_type', 'game_settlement_win') ->assertJsonPath('data.items.0.affects_available_credit', true) ->assertJsonPath('data.items.1.type', 'bill_settlement') ->assertJsonPath('data.items.1.biz_type', 'settlement_payout') ->assertJsonPath('data.items.1.affects_available_credit', false) ->assertJsonPath('data.items.1.balance_after', null); expect($response->json('data.items.0.balance_after'))->not->toBeNull(); $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?type=bill_settlement') ->assertOk() ->assertJsonPath('data.total', 1) ->assertJsonPath('data.items.0.type', 'bill_settlement'); $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?type=win_credit') ->assertOk() ->assertJsonPath('data.total', 1) ->assertJsonPath('data.items.0.type', 'game_settlement'); $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?type=credit_release') ->assertOk() ->assertJsonPath('data.total', 1) ->assertJsonPath('data.items.0.type', 'game_settlement'); $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?type=game_settlement') ->assertOk() ->assertJsonPath('data.total', 1) ->assertJsonPath('data.items.0.type', 'game_settlement'); }); test('credit player wallet logs include period rebate records without affecting available credit', function (): void { $player = Player::query()->create([ 'site_code' => 'default_site', 'site_player_id' => 'native:logs-rebate', 'auth_source' => PlayerAuthSource::LOTTERY_NATIVE, 'funding_mode' => PlayerFundingMode::CREDIT, 'username' => 'credit_logs_rebate', 'default_currency' => 'NPR', 'status' => 0, ]); DB::table('player_credit_accounts')->insert([ 'player_id' => $player->id, 'credit_limit' => 500, 'used_credit' => 0, 'frozen_credit' => 0, 'created_at' => now(), 'updated_at' => now(), ]); $drawId = (int) DB::table('draws')->insertGetId([ 'draw_no' => 'RB-LOG-001', 'business_date' => now()->toDateString(), 'sequence_no' => 1, 'status' => 'settled', 'created_at' => now(), 'updated_at' => now(), ]); $orderId = (int) DB::table('ticket_orders')->insertGetId([ 'order_no' => 'RB-LOG-ORDER-001', 'player_id' => $player->id, 'draw_id' => $drawId, 'currency_code' => 'NPR', 'status' => 'settled', 'created_at' => now(), 'updated_at' => now(), ]); $ticketItemId = (int) DB::table('ticket_items')->insertGetId([ 'ticket_no' => 'RB-LOG-TICKET-001', 'order_id' => $orderId, 'player_id' => $player->id, 'draw_id' => $drawId, 'normalized_number' => '1234', 'play_code' => '4d', 'total_bet_amount' => 10000, 'status' => 'settled', 'created_at' => now(), 'updated_at' => now(), ]); DB::table('rebate_records')->insert([ 'player_id' => $player->id, 'ticket_item_id' => $ticketItemId, 'game_type' => '*', 'valid_bet_amount' => 10000, 'rebate_rate' => 0.01, 'rebate_amount' => 100, 'rebate_type' => 'basic', 'owner_agent_id' => null, 'status' => 'accrued', 'created_at' => now(), 'updated_at' => now(), ]); $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?page=1&size=10') ->assertOk() ->assertJsonPath('data.total', 1) ->assertJsonPath('data.items.0.type', 'rebate') ->assertJsonPath('data.items.0.biz_type', 'rebate_accrued') ->assertJsonPath('data.items.0.ledger_source', 'rebate_record') ->assertJsonPath('data.items.0.affects_available_credit', false) ->assertJsonPath('data.items.0.balance_after', null); $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?type=rebate') ->assertOk() ->assertJsonPath('data.total', 1) ->assertJsonPath('data.items.0.type', 'rebate'); $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?type=bet') ->assertOk() ->assertJsonPath('data.total', 0); }); test('credit wallet logs page 2 balance_after continues from page 1 baseline', function (): void { $player = Player::query()->create([ 'site_code' => 'default_site', 'site_player_id' => 'native:logs-page2', 'auth_source' => PlayerAuthSource::LOTTERY_NATIVE, 'funding_mode' => PlayerFundingMode::CREDIT, 'username' => 'credit_logs_page2', 'default_currency' => 'NPR', 'status' => 0, ]); DB::table('player_credit_accounts')->insert([ 'player_id' => $player->id, 'credit_limit' => 500, 'used_credit' => 12, 'frozen_credit' => 0, 'created_at' => now(), 'updated_at' => now(), ]); for ($i = 1; $i <= 12; $i++) { DB::table('credit_ledger')->insert([ 'owner_type' => 'player', 'owner_id' => $player->id, 'amount' => -1000, 'reason' => 'bet_hold', 'ref_type' => 'bet', 'ref_id' => $i, 'created_at' => now()->subMinutes(12 - $i), 'updated_at' => now()->subMinutes(12 - $i), ]); } $page1 = $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?page=1&size=10') ->assertOk() ->json('data'); $page2 = $this->withHeader('Authorization', 'Bearer dev:'.$player->id) ->getJson('/api/v1/wallet/logs?page=2&size=10') ->assertOk() ->json('data'); expect($page1['total'])->toBe(12); expect($page2['items'])->toHaveCount(2); $page1LastBalanceAfter = $page1['items'][9]['balance_after']; $page2FirstBalanceAfter = $page2['items'][0]['balance_after']; // Page 2 must continue the running balance from page 1, not restart at current available. expect($page2FirstBalanceAfter)->toBe($page1LastBalanceAfter + 1000); });