where('player_id', $player->id) ->where('wallet_type', 'lottery') ->where('currency_code', strtoupper($currencyCode)) ->lockForUpdate() ->first(); if ($wallet === null) { $wallet = PlayerWallet::query()->create([ 'player_id' => $player->id, 'wallet_type' => 'lottery', 'currency_code' => strtoupper($currencyCode), 'balance' => 0, 'frozen_balance' => 0, 'status' => 0, 'version' => 0, ]); $wallet = PlayerWallet::query()->whereKey($wallet->id)->lockForUpdate()->firstOrFail(); } $before = (int) $wallet->balance; if ($before < $amountMinor) { throw new TicketOperationException('bet_insufficient_balance', ErrorCode::BetInsufficientBalance->value); } $after = $before - $amountMinor; $wallet->forceFill([ 'balance' => $after, 'version' => (int) $wallet->version + 1, ])->save(); WalletTxn::query()->create([ 'txn_no' => $this->newTxnNo(), 'player_id' => $player->id, 'wallet_id' => $wallet->id, 'biz_type' => 'bet_deduct', 'biz_no' => $order->order_no, 'direction' => self::TXN_DIR_OUT, 'amount' => $amountMinor, 'balance_before' => $before, 'balance_after' => $after, 'status' => self::TXN_POSTED, 'external_ref_no' => null, 'idempotent_key' => $order->client_trace_id, 'remark' => null, ]); } /** * 结算派彩入账(产品文档:派彩写入钱包流水;幂等键按结算批次 + 玩家)。 */ public function creditSettlementPayout(Player $player, string $currencyCode, int $amountMinor, int $settlementBatchId): void { if ($amountMinor <= 0) { return; } $currency = strtoupper($currencyCode); $wallet = PlayerWallet::query() ->where('player_id', $player->id) ->where('wallet_type', 'lottery') ->where('currency_code', $currency) ->lockForUpdate() ->first(); if ($wallet === null) { $wallet = PlayerWallet::query()->create([ 'player_id' => $player->id, 'wallet_type' => 'lottery', 'currency_code' => $currency, 'balance' => 0, 'frozen_balance' => 0, 'status' => 0, 'version' => 0, ]); $wallet = PlayerWallet::query()->whereKey($wallet->id)->lockForUpdate()->firstOrFail(); } $before = (int) $wallet->balance; $after = $before + $amountMinor; $wallet->forceFill([ 'balance' => $after, 'version' => (int) $wallet->version + 1, ])->save(); WalletTxn::query()->create([ 'txn_no' => $this->newTxnNo(), 'player_id' => $player->id, 'wallet_id' => $wallet->id, 'biz_type' => 'settle_payout', 'biz_no' => 'SB'.$settlementBatchId, 'direction' => self::TXN_DIR_IN, 'amount' => $amountMinor, 'balance_before' => $before, 'balance_after' => $after, 'status' => self::TXN_POSTED, 'external_ref_no' => null, 'idempotent_key' => 'settle-payout:'.$settlementBatchId.':'.$player->id, 'remark' => null, ]); } private function newTxnNo(): string { return 'WL'.now()->format('YmdHis').str_pad((string) random_int(0, 999999), 6, '0', STR_PAD_LEFT); } }