id(); $table->string('site_code'); $table->string('site_player_id'); $table->string('username')->nullable(); $table->string('nickname')->nullable(); $table->string('default_currency')->default('NPR'); $table->string('funding_mode')->default('wallet'); $table->smallInteger('status')->default(0); $table->timestamps(); }); Schema::create('player_wallets', function (Blueprint $table): void { $table->id(); $table->foreignId('player_id'); $table->string('wallet_type'); $table->string('currency_code'); $table->bigInteger('balance')->default(0); $table->bigInteger('frozen_balance')->default(0); $table->smallInteger('status')->default(0); $table->integer('version')->default(0); $table->timestamps(); }); Schema::create('wallet_txns', function (Blueprint $table): void { $table->id(); $table->string('txn_no'); $table->foreignId('player_id'); $table->foreignId('wallet_id'); $table->string('biz_type'); $table->string('biz_no')->nullable(); $table->smallInteger('direction'); $table->bigInteger('amount'); $table->bigInteger('balance_before'); $table->bigInteger('balance_after'); $table->string('status'); $table->string('external_ref_no')->nullable(); $table->string('idempotent_key')->nullable(); $table->string('remark')->nullable(); $table->timestamps(); }); Schema::create('transfer_orders', function (Blueprint $table): void { $table->id(); $table->string('transfer_no'); $table->foreignId('player_id'); $table->string('direction'); $table->string('currency_code'); $table->bigInteger('amount'); $table->string('idempotent_key'); $table->string('status'); $table->timestamps(); }); Schema::create('player_credit_accounts', function (Blueprint $table): void { $table->foreignId('player_id')->primary(); $table->bigInteger('credit_limit')->default(0); $table->bigInteger('used_credit')->default(0); $table->bigInteger('frozen_credit')->default(0); $table->timestamps(); }); Schema::create('credit_ledger', function (Blueprint $table): void { $table->id(); $table->string('owner_type'); $table->unsignedBigInteger('owner_id'); $table->bigInteger('amount'); $table->string('reason'); $table->string('ref_type')->nullable(); $table->unsignedBigInteger('ref_id')->nullable(); $table->timestamps(); }); Schema::create('ticket_items', function (Blueprint $table): void { $table->id(); }); Schema::create('settlement_bills', function (Blueprint $table): void { $table->id(); $table->string('bill_type')->default('player'); $table->bigInteger('net_amount')->default(0); $table->bigInteger('paid_amount')->default(0); $table->bigInteger('unpaid_amount')->default(0); $table->json('meta_json')->nullable(); }); Schema::create('payment_records', function (Blueprint $table): void { $table->id(); $table->foreignId('settlement_bill_id'); $table->bigInteger('amount'); $table->string('status'); $table->timestamp('confirmed_at')->nullable(); }); }); test('financial chain audit passes for consistent wallet ledger', function (): void { $player = Player::query()->create([ 'site_code' => 'main', 'site_player_id' => 'financial-audit-ok', 'username' => 'financial_audit_ok', 'nickname' => null, 'default_currency' => 'NPR', 'status' => 0, ]); $wallet = PlayerWallet::query()->create([ 'player_id' => $player->id, 'wallet_type' => 'lottery', 'currency_code' => 'NPR', 'balance' => 1000, 'frozen_balance' => 0, 'status' => 0, 'version' => 1, ]); WalletTxn::query()->create([ 'txn_no' => 'WX_financial_audit_ok', 'player_id' => $player->id, 'wallet_id' => $wallet->id, 'biz_type' => 'manual_seed', 'biz_no' => 'manual_seed_ok', 'direction' => 1, 'amount' => 1000, 'balance_before' => 0, 'balance_after' => 1000, 'status' => 'posted', 'idempotent_key' => 'financial-audit-ok', ]); $this->artisan('lottery:audit-financial-chain') ->expectsOutputToContain('Financial chain audit passed.') ->assertExitCode(0); }); test('financial chain audit reports wallet balance drift', function (): void { $player = Player::query()->create([ 'site_code' => 'main', 'site_player_id' => 'financial-audit-bad', 'username' => 'financial_audit_bad', 'nickname' => null, 'default_currency' => 'NPR', 'status' => 0, ]); $wallet = PlayerWallet::query()->create([ 'player_id' => $player->id, 'wallet_type' => 'lottery', 'currency_code' => 'NPR', 'balance' => 900, 'frozen_balance' => 0, 'status' => 0, 'version' => 1, ]); WalletTxn::query()->create([ 'txn_no' => 'WX_financial_audit_bad', 'player_id' => $player->id, 'wallet_id' => $wallet->id, 'biz_type' => 'manual_seed', 'biz_no' => 'manual_seed_bad', 'direction' => 1, 'amount' => 1000, 'balance_before' => 0, 'balance_after' => 1000, 'status' => 'posted', 'idempotent_key' => 'financial-audit-bad', ]); $this->artisan('lottery:audit-financial-chain') ->expectsOutputToContain('Financial chain audit found') ->expectsOutputToContain('[wallet_latest_mismatch]') ->assertExitCode(1); });