fix(core): harden settlement and wallet integration

This commit is contained in:
wchino
2026-07-22 01:40:37 +08:00
parent 150c3e7ebd
commit 35f6e46958
54 changed files with 2564 additions and 359 deletions

View File

@@ -1446,3 +1446,126 @@ test('ticket pending confirmation reconcile refunds when draw no longer accepts
->and(WalletTxn::query()->where('biz_type', 'bet_reverse')->where('biz_no', 'TO-PENDING-CLOSED')->count())->toBe(1)
->and((int) RiskPool::query()->where('draw_id', $draw->id)->where('normalized_number', '1234')->value('locked_amount'))->toBe(0);
});
test('ticket pending confirmation reconcile releases credit hold when draw no longer accepts bets', function (): void {
$draw = ticketOpenDraw('20260511-credit-stale');
$draw->forceFill([
'status' => DrawStatus::Closed->value,
'close_time' => now()->subMinute(),
'draw_time' => now()->subMinute(),
])->save();
$site = DB::table('admin_sites')->where('is_default', true)->first();
$player = Player::query()->create([
'site_code' => (string) $site->code,
'agent_node_id' => (int) DB::table('agent_nodes')->where('depth', 0)->value('id'),
'site_player_id' => 'native:stale-credit-hold',
'auth_source' => 'lottery_native',
'funding_mode' => 'credit',
'username' => 'stale_credit_hold',
'nickname' => null,
'default_currency' => 'NPR',
'status' => 0,
]);
DB::table('player_credit_accounts')->insert([
'player_id' => $player->id,
'credit_limit' => 1000,
'used_credit' => 1,
'frozen_credit' => 0,
'created_at' => now(),
'updated_at' => now(),
]);
$order = TicketOrder::query()->create([
'order_no' => 'TO-PENDING-CREDIT-CLOSED',
'player_id' => $player->id,
'draw_id' => $draw->id,
'currency_code' => 'NPR',
'total_bet_amount' => 100,
'total_rebate_amount' => 0,
'total_actual_deduct' => 100,
'total_estimated_payout' => 3000,
'status' => 'pending_confirm',
'submit_source' => 'h5',
'client_trace_id' => 'pending-credit-on-closed-draw',
'created_at' => now()->subMinutes(20),
'updated_at' => now()->subMinutes(20),
]);
TicketOrder::query()->whereKey($order->id)->update(['updated_at' => now()->subMinutes(20)]);
$item = TicketItem::query()->create([
'ticket_no' => 'TK-PENDING-CREDIT-CLOSED',
'order_id' => $order->id,
'player_id' => $player->id,
'draw_id' => $draw->id,
'original_number' => '1234',
'normalized_number' => '1234',
'play_code' => 'big',
'dimension' => 4,
'digit_slot' => null,
'bet_mode' => 'straight',
'unit_bet_amount' => 100,
'total_bet_amount' => 100,
'rebate_rate_snapshot' => 0,
'commission_rate_snapshot' => 0,
'actual_deduct_amount' => 100,
'odds_snapshot_json' => [],
'rule_snapshot_json' => [],
'combination_count' => 1,
'estimated_max_payout' => 3000,
'risk_locked_amount' => 3000,
'status' => 'pending_confirm',
'fail_reason_code' => null,
'fail_reason_text' => null,
'win_amount' => 0,
'jackpot_win_amount' => 0,
'settled_at' => null,
'created_at' => now()->subMinutes(20),
'updated_at' => now()->subMinutes(20),
]);
TicketCombination::query()->create([
'ticket_item_id' => $item->id,
'combination_no' => 1,
'number_4d' => '1234',
'bet_amount' => 100,
'estimated_payout' => 3000,
'created_at' => now()->subMinutes(20),
]);
RiskPool::query()->create([
'draw_id' => $draw->id,
'normalized_number' => '1234',
'total_cap_amount' => 5000,
'locked_amount' => 3000,
'remaining_amount' => 2000,
'sold_out_status' => 0,
'version' => 1,
]);
$this->artisan('lottery:ticket-pending-confirm-reconcile --stale-minutes=15 --limit=100')
->expectsOutputToContain('refunded: 1')
->assertExitCode(0);
expect($order->fresh()->status)->toBe('refunded')
->and($item->fresh()->status)->toBe('refunded')
->and((int) DB::table('player_credit_accounts')->where('player_id', $player->id)->value('used_credit'))->toBe(0)
->and(DB::table('credit_ledger')
->where('owner_id', $player->id)
->where('reason', 'bet_hold_release')
->where('ref_type', 'ticket_order')
->where('ref_id', $order->id)
->count())->toBe(1)
->and((int) RiskPool::query()->where('draw_id', $draw->id)->where('normalized_number', '1234')->value('locked_amount'))->toBe(0);
$this->artisan('lottery:ticket-pending-confirm-reconcile --stale-minutes=15 --limit=100')
->assertExitCode(0);
expect(DB::table('credit_ledger')
->where('owner_id', $player->id)
->where('reason', 'bet_hold_release')
->where('ref_type', 'ticket_order')
->where('ref_id', $order->id)
->count())->toBe(1);
});