where('id', $orderId)->find(); if (!$order) { throw new RuntimeException('Order does not exist'); } $orderNo = is_string($order['order_no']) ? $order['order_no'] : strval($order['order_no']); if ($orderNo === '') { throw new RuntimeException('Order number is empty'); } $statusRaw = $order['status'] ?? 0; $status = is_numeric($statusRaw) ? intval($statusRaw) : 0; // 如果已结算,直接返回已有结果(幂等) if ($status === 1) { $userId = is_numeric($order['user_id'] ?? null) ? intval($order['user_id']) : 0; $coinAfter = '0.00'; if ($userId > 0) { $coin = Db::name('user')->where('id', $userId)->value('coin'); $coinAfter = is_string($coin) ? $coin : strval($coin); } $amt = self::amountString($order['amount'] ?? '0'); $bns = self::amountString($order['bonus_amount'] ?? '0'); return [ 'order_id' => $orderId, 'order_no' => $orderNo, 'amount' => $amt, 'bonus_amount' => $bns, 'credit' => bcadd($amt, $bns, 2), 'balance_before' => $coinAfter, 'balance_after' => $coinAfter, 'pay_time' => is_numeric($order['pay_time'] ?? null) ? intval($order['pay_time']) : 0, 'already_settled' => true, ]; } if ($status !== 0) { throw new RuntimeException('Order status does not allow settlement'); } $amount = self::amountString($order['amount'] ?? '0'); if (bccomp($amount, '0', 2) <= 0) { throw new RuntimeException('Order amount is invalid'); } $bonus = self::amountString($order['bonus_amount'] ?? '0'); if (bccomp($bonus, '0', 2) < 0) { $bonus = '0.00'; } $credit = bcadd($amount, $bonus, 2); $userId = is_numeric($order['user_id'] ?? null) ? intval($order['user_id']) : 0; if ($userId <= 0) { throw new RuntimeException('Order user is invalid'); } $user = Db::name('user')->where('id', $userId)->find(); if (!$user) { throw new RuntimeException('User does not exist'); } $channelId = is_numeric($order['channel_id'] ?? null) ? intval($order['channel_id']) : null; $balanceBefore = self::amountString($user['coin'] ?? '0'); $balanceAfter = bcadd($balanceBefore, $credit, 2); $now = time(); $baseRemark = is_string($order['remark'] ?? null) ? $order['remark'] : ''; // 备注包含充值与赠送的明细,方便后续稽核 $detail = sprintf('amount=%s,bonus=%s,credit=%s', $amount, $bonus, $credit); $note = sprintf('[%s] %s (%s)', $source, $sourceLabel, $detail); $combined = $baseRemark === '' ? $note : ($baseRemark . ' | ' . $note); if ($extraRemark !== null && $extraRemark !== '') { $combined .= ' | ' . $extraRemark; } $finalRemark = mb_substr($combined, 0, 255); $walletIdem = 'deposit_settle_' . $orderNo; Db::startTrans(); try { $affected = Db::name('deposit_order') ->where('id', $orderId) ->where('status', 0) ->update([ 'status' => 1, 'pay_time' => $now, 'remark' => $finalRemark, 'update_time' => $now, ]); if ($affected <= 0) { throw new RuntimeException('Order state changed, please refresh and retry'); } Db::name('user')->where('id', $userId)->update([ 'coin' => $balanceAfter, 'update_time' => $now, ]); $walletExists = Db::name('user_wallet_record') ->where('idempotency_key', $walletIdem) ->value('id'); if (!$walletExists) { Db::name('user_wallet_record')->insert([ 'user_id' => $userId, 'channel_id' => $channelId, 'biz_type' => 'deposit', 'direction' => 1, 'amount' => $credit, 'balance_before' => $balanceBefore, 'balance_after' => $balanceAfter, 'ref_type' => 'deposit_order', 'ref_id' => $orderId, 'idempotency_key' => $walletIdem, 'operator_admin_id' => $operatorAdminId, 'remark' => mb_substr($note, 0, 500), 'create_time' => $now, ]); } Db::commit(); } catch (Throwable $e) { Db::rollback(); throw new RuntimeException($e->getMessage()); } GameWebSocketEventBus::publish('wallet.changed', [ 'user_id' => $userId, 'balance_after' => $balanceAfter, 'biz_type' => 'deposit', 'order_no' => $orderNo, 'changed_at' => $now, ]); return [ 'order_id' => $orderId, 'order_no' => $orderNo, 'amount' => $amount, 'bonus_amount' => $bonus, 'credit' => $credit, 'balance_before' => $balanceBefore, 'balance_after' => $balanceAfter, 'pay_time' => $now, 'already_settled' => false, ]; } /** * 将任意数值输入格式化为 2 位小数字符串(不做强制类型转换) */ private static function amountString($raw): string { if (is_string($raw)) { $s = trim($raw); } elseif (is_int($raw) || is_float($raw)) { $s = strval($raw); } else { return '0.00'; } if (!is_numeric($s)) { return '0.00'; } return bcadd($s, '0', 2); } }