instance('config',new \Illuminate\Config\Repository(['admin_realtime'=>['enabled'=>false]])); $app->instance('log',new \Psr\Log\NullLogger()); \Illuminate\Support\Facades\Facade::setFacadeApplication($app); $db=new \Illuminate\Database\Capsule\Manager($app); $db->addConnection(['driver'=>'sqlite','database'=>':memory:','prefix'=>'bot_']); $db->setAsGlobal();$db->bootEloquent(); $app->instance('db',$db->getDatabaseManager());$app->bind('db.schema',fn()=>$db->schema()); $sql=[ 'CREATE TABLE bot_wallets (id INTEGER PRIMARY KEY,member_id TEXT,available_balance TEXT,created_at TEXT,updated_at TEXT)', 'CREATE TABLE bot_lhc_order (id INTEGER PRIMARY KEY,member_id TEXT,issue TEXT,type INTEGER,lottery_status INTEGER,amount TEXT,total_amount TEXT,coupon_discount TEXT,coupon_id INTEGER,ordernum TEXT,number TEXT,created_at INTEGER,updated_at INTEGER)', 'CREATE TABLE bot_lhc_order_item (id INTEGER PRIMARY KEY,ordernum TEXT,lottery_status INTEGER,updated_at INTEGER)', 'CREATE TABLE bot_promotion_coupons (id INTEGER PRIMARY KEY,status TEXT,used_source TEXT,used_stake TEXT,used_at TEXT,updated_at TEXT)', 'CREATE TABLE bot_promotion_coupon_uses (id INTEGER PRIMARY KEY,coupon_id INTEGER,source TEXT,order_id INTEGER,discount TEXT,refunded_at TEXT,updated_at TEXT)', ]; foreach($sql as $q) \Illuminate\Support\Facades\DB::statement($q); $DB=\Illuminate\Support\Facades\DB::class; $DB::table('wallets')->insert(['id'=>1,'member_id'=>'1001','available_balance'=>'0']); $DB::table('promotion_coupons')->insert(['id'=>1,'status'=>'used']); foreach([1=>['1','0'],2=>['10000','1']] as $id=>[$amount,$discount]) { $DB::table('lhc_order')->insert(['id'=>$id,'member_id'=>'1001','issue'=>'issue','type'=>5,'lottery_status'=>0,'amount'=>$amount,'total_amount'=>$id===1?'2':$amount,'coupon_discount'=>$discount,'coupon_id'=>1,'ordernum'=>'order'.$id,'created_at'=>time(),'updated_at'=>time()]); $DB::table('lhc_order_item')->insert(['id'=>$id,'ordernum'=>'order'.$id,'lottery_status'=>0]); $DB::table('promotion_coupon_uses')->insert(['id'=>$id,'coupon_id'=>1,'source'=>'lhc_order','order_id'=>$id,'discount'=>$discount]); } $DB::table('lhc_order_item')->insert(['id'=>3,'ordernum'=>'order1','lottery_status'=>0]); \App\Services\BetService::betFail('issue'); $checks = 0; function same($actual, $expected, string $message): void { global $checks; ++$checks; if ($actual !== $expected) throw new \RuntimeException($message . ': ' . json_encode($actual)); } same(bcadd((string)$DB::table('wallets')->value('available_balance'), '0', 2), '10001.00', 'All paid stakes, including zero-discount groups, are refunded'); same($DB::table('promotion_coupons')->value('status'), 'unused', 'Coupon returns after all orders are refunded'); same($DB::table('promotion_coupon_uses')->whereNull('refunded_at')->count(), 0, 'Zero-discount use is also refunded'); same($DB::table('lhc_order')->orderBy('id')->pluck('lottery_status')->all(), [1, 1], 'Existing failed-draw terminal states are preserved'); same(count(\App\Services\BalanceLogService::$logs), 2, 'One ledger entry per order'); same(bcadd((string)\App\Services\BalanceLogService::$logs[0][1], '0', 2), '2.00', 'First ledger uses the complete paid group stake'); same(\App\Jobs\SendTelegramMessageJob::$sent, 2, 'Notifications are stubbed, never sent externally'); $DB::table('promotion_coupons')->update(['status' => 'used', 'used_source' => 'sport_order', 'used_stake' => '100']); $DB::table('promotion_coupon_uses')->insert(['id' => 3, 'coupon_id' => 1, 'source' => 'sport_order', 'order_id' => 9, 'discount' => '1']); \App\Services\PromotionCouponService::release('lhc_order', 1); same($DB::table('promotion_coupons')->value('status'), 'used', 'Retrying an old refund never restores a reused coupon'); $DB::beginTransaction(); \App\Services\PromotionCouponService::release('sport_order', 9); same($DB::table('promotion_coupons')->value('status'), 'unused', 'Nested release completes inside refund transaction'); $DB::rollBack(); same($DB::table('promotion_coupons')->value('status'), 'used', 'Outer rollback restores coupon state'); same($DB::table('promotion_coupon_uses')->where('id', 3)->value('refunded_at'), null, 'Outer rollback restores use state'); echo "PASS {$checks} coupon failed-draw refund checks\n"; }