1.新增获取充值/提现配置接口/api/finance/depositWithdrawConfig

2.优化充值和提现方式
This commit is contained in:
2026-04-23 10:15:01 +08:00
parent 0f28c0fd2a
commit aa1299c018
55 changed files with 901 additions and 750 deletions

View File

@@ -0,0 +1,71 @@
<?php
declare(strict_types=1);
namespace app\common\library\finance;
/**
* 模拟第三方支付HMAC 签名的收银台地址 + 回调验签,便于未来替换为真实网关仅改 URL/验签实现。
*/
final class DepositMockGateway
{
/**
* 优先读取环境变量 DEPOSIT_MOCK_HMAC_KEY其次 config('app.deposit_mock_hmac_key'),再使用开发默认值(生产环境务必设置 env
*/
public static function hmacKey(): string
{
$raw = getenv('DEPOSIT_MOCK_HMAC_KEY');
if (is_string($raw) && trim($raw) !== '') {
return trim($raw);
}
$cfg = config('app.deposit_mock_hmac_key', '');
if (is_string($cfg) && $cfg !== '') {
return $cfg;
}
return 'webman-dfw-deposit-mock-dev-key-set-DEPOSIT_MOCK_HMAC_KEY-in-prod';
}
public static function signOrderNo(string $orderNo): string
{
if ($orderNo === '') {
return '';
}
return hash_hmac('sha256', $orderNo, self::hmacKey());
}
public static function verifyOrderNo(string $orderNo, string $sign): bool
{
if ($orderNo === '' || $sign === '') {
return false;
}
$e = self::signOrderNo($orderNo);
if ($e === '') {
return false;
}
return hash_equals($e, $sign);
}
/**
* 玩家浏览器打开的「第三方收银台」地址(本项目中为简单 HTML 模拟页,点击后向 notify 发起 POST 完成入账)。
*
* @param string|null $publicOrigin 如 https://api.example.com为 null 时只返回以 / 开头的 path+query由客户端与 API 域名拼接
*/
public static function payPageUrl(string $orderNo, ?string $publicOrigin = null): string
{
$sign = self::signOrderNo($orderNo);
$q = http_build_query([
'order_no' => $orderNo,
'sign' => $sign,
]);
$path = '/api/finance/depositMockPayPage?' . $q;
if ($publicOrigin === null) {
return $path;
}
$base = rtrim($publicOrigin, '/');
return $base . $path;
}
}

View File

@@ -75,7 +75,7 @@ final class DepositSettlement
// 如果已结算,直接返回已有结果(幂等)
if ($status === 1) {
$userId = is_numeric($order['user_id'] ?? null) ? intval($order['user_id']) : 0;
$coinAfter = '0.0000';
$coinAfter = '0.00';
if ($userId > 0) {
$coin = Db::name('user')->where('id', $userId)->value('coin');
$coinAfter = is_string($coin) ? $coin : strval($coin);
@@ -87,7 +87,7 @@ final class DepositSettlement
'order_no' => $orderNo,
'amount' => $amt,
'bonus_amount' => $bns,
'credit' => bcadd($amt, $bns, 4),
'credit' => bcadd($amt, $bns, 2),
'balance_before' => $coinAfter,
'balance_after' => $coinAfter,
'pay_time' => is_numeric($order['pay_time'] ?? null) ? intval($order['pay_time']) : 0,
@@ -100,14 +100,14 @@ final class DepositSettlement
}
$amount = self::amountString($order['amount'] ?? '0');
if (bccomp($amount, '0', 4) <= 0) {
if (bccomp($amount, '0', 2) <= 0) {
throw new RuntimeException('订单金额异常');
}
$bonus = self::amountString($order['bonus_amount'] ?? '0');
if (bccomp($bonus, '0', 4) < 0) {
$bonus = '0.0000';
if (bccomp($bonus, '0', 2) < 0) {
$bonus = '0.00';
}
$credit = bcadd($amount, $bonus, 4);
$credit = bcadd($amount, $bonus, 2);
$userId = is_numeric($order['user_id'] ?? null) ? intval($order['user_id']) : 0;
if ($userId <= 0) {
@@ -121,7 +121,7 @@ final class DepositSettlement
$channelId = is_numeric($order['channel_id'] ?? null) ? intval($order['channel_id']) : null;
$balanceBefore = self::amountString($user['coin'] ?? '0');
$balanceAfter = bcadd($balanceBefore, $credit, 4);
$balanceAfter = bcadd($balanceBefore, $credit, 2);
$now = time();
$baseRemark = is_string($order['remark'] ?? null) ? $order['remark'] : '';
@@ -142,12 +142,10 @@ final class DepositSettlement
->where('id', $orderId)
->where('status', 0)
->update([
'status' => 1,
'pay_time' => $now,
'review_admin_id' => $operatorAdminId,
'review_time' => $operatorAdminId !== null ? $now : null,
'remark' => $finalRemark,
'update_time' => $now,
'status' => 1,
'pay_time' => $now,
'remark' => $finalRemark,
'update_time' => $now,
]);
if ($affected <= 0) {
throw new RuntimeException('订单状态已变更,请刷新后重试');
@@ -199,7 +197,7 @@ final class DepositSettlement
}
/**
* 将任意数值输入格式化为 4 位小数字符串(不做强制类型转换)
* 将任意数值输入格式化为 2 位小数字符串(不做强制类型转换)
*/
private static function amountString($raw): string
{
@@ -208,11 +206,11 @@ final class DepositSettlement
} elseif (is_int($raw) || is_float($raw)) {
$s = strval($raw);
} else {
return '0.0000';
return '0.00';
}
if (!is_numeric($s)) {
return '0.0000';
return '0.00';
}
return bcadd($s, '0', 4);
return bcadd($s, '0', 2);
}
}

View File

@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace app\common\library\finance;
use app\common\service\GameHotDataRedis;
use support\think\Db;
/**
* 提现打码量(流水)门槛工具库
@@ -25,16 +26,16 @@ final class WithdrawFlow
{
public const CONFIG_KEY = 'withdraw_bet_flow_ratio';
public const DEFAULT_RATIO = '1.0000';
public const DEFAULT_RATIO = '1.00';
/** 当 ratio = 0不限打码max_withdraw_by_flow 用此哨兵表示"无限"。14 位整数位足够覆盖任何业务金额。 */
public const UNLIMITED_FLOW = '99999999999999.9999';
public const UNLIMITED_FLOW = '99999999999999.99';
/** 单用户最多允许同时存在的「待审核」(withdraw_order.status=0) 提现订单数。 */
public const MAX_PENDING_WITHDRAW = 3;
/**
* 读取当前打码倍数(字符串 4 位小数,至少 0
* 读取当前打码倍数(字符串 2 位小数,至少 0
*/
public static function ratio(): string
{
@@ -46,32 +47,32 @@ final class WithdrawFlow
if (!is_string($val) || trim($val) === '' || !is_numeric(trim($val))) {
return self::DEFAULT_RATIO;
}
$normalized = bcadd(trim($val), '0', 4);
if (bccomp($normalized, '0', 4) < 0) {
return '0.0000';
$normalized = bcadd(trim($val), '0', 2);
if (bccomp($normalized, '0', 2) < 0) {
return '0.00';
}
return $normalized;
}
/**
* 归一化金额字段到 4 位小数字符串,非法输入返回 '0.0000'
* 归一化金额字段到 2 位小数字符串,非法输入返回 '0.00'
*/
public static function amountString($raw): string
{
if ($raw === null || $raw === '') {
return '0.0000';
return '0.00';
}
if (is_string($raw)) {
$s = trim($raw);
} elseif (is_int($raw) || is_float($raw)) {
$s = strval($raw);
} else {
return '0.0000';
return '0.00';
}
if (!is_numeric($s)) {
return '0.0000';
return '0.00';
}
return bcadd($s, '0', 4);
return bcadd($s, '0', 2);
}
/**
@@ -108,28 +109,28 @@ final class WithdrawFlow
$withdraw = self::amountString($userSnapshot['total_withdraw_coin'] ?? '0');
$flow = self::amountString($userSnapshot['bet_flow_coin'] ?? '0');
$net = bcsub($deposit, $withdraw, 4);
if (bccomp($net, '0', 4) < 0) {
$net = '0.0000';
$net = bcsub($deposit, $withdraw, 2);
if (bccomp($net, '0', 2) < 0) {
$net = '0.00';
}
$ratio = self::ratio();
$required = bcmul($net, $ratio, 4);
$remaining = bcsub($required, $flow, 4);
if (bccomp($remaining, '0', 4) < 0) {
$remaining = '0.0000';
$required = bcmul($net, $ratio, 2);
$remaining = bcsub($required, $flow, 2);
if (bccomp($remaining, '0', 2) < 0) {
$remaining = '0.00';
}
$eligible = bccomp($flow, $required, 4) >= 0;
$eligible = bccomp($flow, $required, 2) >= 0;
// max_withdraw_by_flow = max(0, bet_flow_coin / ratio - total_withdraw_coin)
$unlimited = bccomp($ratio, '0', 4) === 0;
$unlimited = bccomp($ratio, '0', 2) === 0;
if ($unlimited) {
$maxByFlow = self::UNLIMITED_FLOW;
} else {
$lifetime = bcdiv($flow, $ratio, 4);
$maxByFlow = bcsub($lifetime, $withdraw, 4);
if (bccomp($maxByFlow, '0', 4) < 0) {
$maxByFlow = '0.0000';
$lifetime = bcdiv($flow, $ratio, 2);
$maxByFlow = bcsub($lifetime, $withdraw, 2);
if (bccomp($maxByFlow, '0', 2) < 0) {
$maxByFlow = '0.00';
}
}
@@ -147,18 +148,18 @@ final class WithdrawFlow
/**
* 取单笔最大可提现额 = min(coin_balance, max_withdraw_by_flow)。
* 返回值为 4 位小数字符串,已与 ratio=0不限逻辑兼容。
* 返回值为 2 位小数字符串,已与 ratio=0不限逻辑兼容。
*/
public static function maxWithdrawable(string $coinBalance, array $flowStatus): string
{
$coin = self::amountString($coinBalance);
if (bccomp($coin, '0', 4) < 0) {
$coin = '0.0000';
if (bccomp($coin, '0', 2) < 0) {
$coin = '0.00';
}
if (!empty($flowStatus['flow_unlimited'])) {
return $coin;
}
$byFlow = self::amountString($flowStatus['max_withdraw_by_flow'] ?? '0');
return bccomp($coin, $byFlow, 4) <= 0 ? $coin : $byFlow;
return bccomp($coin, $byFlow, 2) <= 0 ? $coin : $byFlow;
}
}

View File

@@ -10,9 +10,9 @@ use support\think\Db;
/**
* 充值支付渠道:优先读取 game_config.finance_cashier.channels无此键时回退 game_config.deposit_channel迁移期镜像
*
* 每项code须在代码/环境注册表内、sort、status(0/1)、tier_ids空=全部启用档位)
* 每项code须在代码/环境注册表内、sort、status(0/1)
*
* 渠道展示名以代码注册表为准;运营只配置开关排序、可用档位。
* 渠道展示名以代码注册表为准;运营只配置开关排序,默认兼容全部充值档位。
*/
final class DepositChannel
{
@@ -150,23 +150,11 @@ final class DepositChannel
$sort = isset($row['sort']) && is_numeric($row['sort']) ? intval($row['sort']) : 0;
$status = isset($row['status']) && is_numeric($row['status']) ? intval($row['status']) : 1;
$status = $status === 1 ? 1 : 0;
$tierIds = [];
if (isset($row['tier_ids']) && is_array($row['tier_ids'])) {
foreach ($row['tier_ids'] as $tid) {
if (is_string($tid)) {
$t = trim($tid);
if ($t !== '' && preg_match('/^[a-zA-Z0-9_\-]{1,32}$/', $t)) {
$tierIds[] = $t;
}
}
}
$tierIds = array_values(array_unique($tierIds));
}
$out[] = [
'code' => $code,
'sort' => $sort,
'status' => $status,
'tier_ids' => $tierIds,
'tier_ids' => [],
];
}
@@ -219,17 +207,8 @@ final class DepositChannel
*/
public static function isTierAllowed(array $overrideRow, string $tierId): bool
{
$ids = $overrideRow['tier_ids'] ?? [];
if (!is_array($ids) || $ids === []) {
return true;
}
foreach ($ids as $id) {
if (is_string($id) && $id === $tierId) {
return true;
}
}
return false;
// 渠道不再配置档位白名单:默认兼容全部充值档位。
return true;
}
/**
@@ -268,9 +247,6 @@ final class DepositChannel
if (!isset($registry[$code])) {
continue;
}
if (!self::isTierAllowed($row, $tierId)) {
continue;
}
$meta = $registry[$code];
$name = self::pickLangName($meta, $lang);
$sortRaw = $row['sort'] ?? 0;

View File

@@ -14,9 +14,9 @@ use InvalidArgumentException;
* - title : string档位中文名称必填前端中文环境展示
* - title_en : string档位英文名称可选前端英文环境展示为空时回退到 title
* - currency : string支付货币代码38 位大写字母,如 MYR、CNY
* - pay_amount : string玩家支付的法币/支付货币额度(4 位小数)
* - amount : string到账基础平台币4 位小数)
* - bonus_amount : string赠送平台币4 位小数,可为 0
* - pay_amount : string玩家支付的法币/支付货币额度(2 位小数)
* - amount : string到账基础平台币2 位小数)
* - bonus_amount : string赠送平台币2 位小数,可为 0
* - desc : string档位中文描述可空<=255
* - desc_en : string档位英文描述可空<=255为空时回退到 desc
* - sort : int排序权重小值在前
@@ -95,7 +95,7 @@ final class DepositTier
$payAmount = self::normalizeAmount($row['pay_amount'] ?? '');
$amount = self::normalizeAmount($row['amount'] ?? '');
$bonus = self::normalizeAmount($row['bonus_amount'] ?? '0');
if (bccomp($payAmount, '0', 4) <= 0 && bccomp($amount, '0', 4) > 0) {
if (bccomp($payAmount, '0', 2) <= 0 && bccomp($amount, '0', 2) > 0) {
// 历史数据仅有 amount平台币用占位同步 pay_amount运营应在后台改为真实支付额度
$payAmount = $amount;
}
@@ -187,17 +187,17 @@ final class DepositTier
}
$payAmount = self::normalizeAmount($row['pay_amount'] ?? '');
if (bccomp($payAmount, '0', 4) <= 0) {
if (bccomp($payAmount, '0', 2) <= 0) {
throw new InvalidArgumentException('第 ' . $no . ' 行支付货币额度必须大于 0');
}
$amount = self::normalizeAmount($row['amount'] ?? '');
if (bccomp($amount, '0', 4) <= 0) {
if (bccomp($amount, '0', 2) <= 0) {
throw new InvalidArgumentException('第 ' . $no . ' 行基础平台币到账必须大于 0');
}
$bonus = self::normalizeAmount($row['bonus_amount'] ?? '0');
if (bccomp($bonus, '0', 4) < 0) {
if (bccomp($bonus, '0', 2) < 0) {
throw new InvalidArgumentException('第 ' . $no . ' 行赠送金额不能为负数');
}
@@ -333,25 +333,25 @@ final class DepositTier
}
/**
* 将金额归一化为 4 位小数字符串;非法输入返回 '0.0000'
* 将金额归一化为 2 位小数字符串;非法输入返回 '0.00'
*/
public static function normalizeAmount($raw): string
{
if ($raw === null || $raw === '') {
return '0.0000';
return '0.00';
}
if (is_string($raw)) {
$s = trim($raw);
} elseif (is_int($raw) || is_float($raw)) {
$s = strval($raw);
} else {
return '0.0000';
return '0.00';
}
$s = str_replace(',', '.', $s);
if (!is_numeric($s)) {
return '0.0000';
return '0.00';
}
return bcadd($s, '0', 4);
return bcadd($s, '0', 2);
}
/**

View File

@@ -353,10 +353,10 @@ final class FinanceCashierConfig
$seenCodes[$code] = true;
$dep = $row['deposit_coins_per_fiat'] ?? '';
$wdr = $row['withdraw_coins_per_fiat'] ?? '';
if (!is_string($dep) || $dep === '' || !is_numeric($dep) || bccomp($dep, '0', 8) <= 0) {
if (!is_string($dep) || $dep === '' || !is_numeric($dep) || bccomp($dep, '0', 2) <= 0) {
throw new InvalidArgumentException('第 ' . ($idx + 1) . ' 行:充值汇率须为大于 0 的数字');
}
if (!is_string($wdr) || $wdr === '' || !is_numeric($wdr) || bccomp($wdr, '0', 8) <= 0) {
if (!is_string($wdr) || $wdr === '' || !is_numeric($wdr) || bccomp($wdr, '0', 2) <= 0) {
throw new InvalidArgumentException('第 ' . ($idx + 1) . ' 行:提现汇率须为大于 0 的数字');
}
}
@@ -379,7 +379,7 @@ final class FinanceCashierConfig
if (isset($p['withdraw_limits']) && is_array($p['withdraw_limits'])) {
foreach (['min_ewallet', 'min_bank'] as $k) {
$v = $p['withdraw_limits'][$k] ?? '0';
if (!is_string($v) || !is_numeric($v) || bccomp($v, '0', 4) < 0) {
if (!is_string($v) || !is_numeric($v) || bccomp($v, '0', 2) < 0) {
throw new InvalidArgumentException('提现最低限额须为不小于 0 的数字');
}
}

View File

@@ -147,7 +147,7 @@ final class StreakWinReward
}
/**
* 返回该注单适用的「赔率乘数」字符串(= 配置档位的 odds_factor供 bcmul(total_amount, ..., 4)。
* 返回该注单适用的「赔率乘数」字符串(= 配置档位的 odds_factor供 bcmul(total_amount, ..., 2)。
*/
public static function totalOddsMultiplierForStreakAtBet(int $streakAtBet): string
{
@@ -156,7 +156,7 @@ final class StreakWinReward
$factor = 1;
}
return bcadd((string) $factor, '0', 4);
return bcadd((string) $factor, '0', 2);
}
/**

View File

@@ -0,0 +1,81 @@
<?php
declare(strict_types=1);
namespace app\common\service;
use support\think\Db;
/**
* 充值待支付订单超时处理:
* - 同用户最多允许 3 笔待支付订单
* - 待支付订单创建后 60 秒未支付,自动标记为失败并写失败原因
*/
final class DepositOrderExpireService
{
public const MAX_PENDING_DEPOSIT = 3;
public const EXPIRE_SECONDS = 60;
/**
* 超时失效处理。
*
* @param int|null $userId 仅处理某用户null 表示不过滤用户
* @param string|null $orderNo 仅处理某订单null 表示不过滤订单
*
* @return int 本次转失败的订单数
*/
public static function expirePendingOrders(?int $userId = null, ?string $orderNo = null): int
{
$expireBefore = time() - self::EXPIRE_SECONDS;
$query = Db::name('deposit_order')
->where('status', 0)
->where('create_time', '<=', $expireBefore);
if ($userId !== null && $userId > 0) {
$query->where('user_id', $userId);
}
if ($orderNo !== null && $orderNo !== '') {
$query->where('order_no', $orderNo);
}
$rows = $query->field(['id', 'remark'])->select()->toArray();
if ($rows === []) {
return 0;
}
$now = time();
$affectedCount = 0;
foreach ($rows as $row) {
$id = isset($row['id']) && is_numeric($row['id']) ? intval($row['id']) : 0;
if ($id <= 0) {
continue;
}
$oldRemark = isset($row['remark']) && is_string($row['remark']) ? trim($row['remark']) : '';
$reason = '[timeout] unpaid over ' . self::EXPIRE_SECONDS . 's';
$remark = $oldRemark === '' ? $reason : mb_substr($oldRemark . ' | ' . $reason, 0, 255);
$affected = Db::name('deposit_order')
->where('id', $id)
->where('status', 0)
->update([
'status' => 2,
'remark' => $remark,
'update_time' => $now,
]);
if (is_numeric($affected) && intval($affected) > 0) {
$affectedCount++;
}
}
return $affectedCount;
}
public static function pendingCountByUserId(int $userId): int
{
if ($userId <= 0) {
return 0;
}
return Db::name('deposit_order')
->where('user_id', $userId)
->where('status', 0)
->count();
}
}

View File

@@ -59,7 +59,7 @@ final class GameBetSettleService
}
$win = self::computeWinAmount($bet, $resultNumber);
$jackpot = '0.0000';
$jackpot = '0.00';
$affected = Db::name('bet_order')
->where('id', $betId)
@@ -78,10 +78,10 @@ final class GameBetSettleService
self::creditUserBetFlow($bet, $now);
if ($userId > 0) {
if (bccomp($win, '0', 4) > 0) {
if (bccomp($win, '0', 2) > 0) {
$userOutcome[$userId]['had_win'] = true;
}
if (bccomp($win, '0', 4) > 0 && StreakWinReward::isJackpotForStreakAtBet((int) ($bet['streak_at_bet'] ?? 0))) {
if (bccomp($win, '0', 2) > 0 && StreakWinReward::isJackpotForStreakAtBet((int) ($bet['streak_at_bet'] ?? 0))) {
$jackpotNotify[$userId] = true;
}
}
@@ -91,7 +91,7 @@ final class GameBetSettleService
}
$balanceAfter = (string) (Db::name('user')->where('id', $userId)->value('coin') ?? '0');
if (bccomp($win, '0', 4) > 0) {
if (bccomp($win, '0', 2) > 0) {
$paid = self::creditUserPayout($bet, $betId, $win, $now);
if ($paid !== null) {
$balanceAfter = $paid;
@@ -102,17 +102,17 @@ final class GameBetSettleService
if (!isset($aggregateByUser[$userId])) {
$aggregateByUser[$userId] = [
'period_no' => $periodNo,
'total_win' => '0.0000',
'total_win' => '0.00',
'balance_after' => $balanceAfter,
'orders' => [],
];
}
$aggregateByUser[$userId]['total_win'] = bcadd($aggregateByUser[$userId]['total_win'], $win, 4);
$aggregateByUser[$userId]['total_win'] = bcadd($aggregateByUser[$userId]['total_win'], $win, 2);
$aggregateByUser[$userId]['balance_after'] = $balanceAfter;
$aggregateByUser[$userId]['orders'][] = [
'order_no' => (string) $betId,
'win_amount' => $win,
'hit' => bccomp($win, '0', 4) > 0,
'hit' => bccomp($win, '0', 2) > 0,
];
}
@@ -150,7 +150,7 @@ final class GameBetSettleService
'balance_after' => $agg['balance_after'],
]);
if (bccomp($agg['total_win'], '0', 4) > 0) {
if (bccomp($agg['total_win'], '0', 2) > 0) {
UserPushService::publish((int) $userId, UserPushService::EVT_WALLET_CHANGED, [
'reason' => 'payout',
'ref_type' => 'game_period',
@@ -167,7 +167,7 @@ final class GameBetSettleService
continue;
}
$agg = $aggregateByUser[$uid];
if (bccomp($agg['total_win'], '0', 4) <= 0) {
if (bccomp($agg['total_win'], '0', 2) <= 0) {
continue;
}
$jackpotHits[] = [
@@ -187,7 +187,7 @@ final class GameBetSettleService
public static function settlePendingForEndedRecords(): int
{
$rows = Db::name('game_record')
->where('status', 4)
->where('status', 2)
->whereNotNull('result_number')
->field(['id', 'result_number'])
->order('id', 'asc')
@@ -237,13 +237,13 @@ final class GameBetSettleService
$pickNumbers = [];
}
if (!in_array($resultNumber, array_map('intval', $pickNumbers), true)) {
return '0.0000';
return '0.00';
}
$total = (string) ($bet['total_amount'] ?? '0');
$streak = (int) ($bet['streak_at_bet'] ?? 0);
$odds = StreakWinReward::totalOddsMultiplierForStreakAtBet($streak);
return bcmul($total, $odds, 4);
return bcmul($total, $odds, 2);
}
/**
@@ -263,8 +263,8 @@ final class GameBetSettleService
if ($total === '' || !is_numeric($total)) {
return;
}
$flow = bcadd($total, '0', 4);
if (bccomp($flow, '0', 4) <= 0) {
$flow = bcadd($total, '0', 2);
if (bccomp($flow, '0', 2) <= 0) {
return;
}
Db::name('user')
@@ -299,7 +299,7 @@ final class GameBetSettleService
}
$before = (string) ($user['coin'] ?? '0');
$after = bcadd($before, $winAmount, 4);
$after = bcadd($before, $winAmount, 2);
Db::name('user_wallet_record')->insert([
'user_id' => $userId,

View File

@@ -207,12 +207,12 @@ final class GameLiveService
for ($n = 1; $n <= self::DRAW_NUMBER_MAX; $n++) {
$loss = self::estimateLossForNumber($bets, $n);
$candidates[] = ['number' => $n, 'estimated_loss' => $loss];
if ($bestLoss === null || bccomp((string) $loss, (string) $bestLoss, 4) < 0) {
if ($bestLoss === null || bccomp((string) $loss, (string) $bestLoss, 2) < 0) {
$bestLoss = $loss;
$bestNumbers = [$n];
continue;
}
if (bccomp((string) $loss, (string) $bestLoss, 4) === 0) {
if (bccomp((string) $loss, (string) $bestLoss, 2) === 0) {
$bestNumbers[] = $n;
}
}
@@ -225,7 +225,7 @@ final class GameLiveService
}
$finalNumber = $manualNumber ?? $bestNumber;
$finalLoss = '0.0000';
$finalLoss = '0.00';
if ($finalNumber !== null) {
$finalLoss = self::estimateLossForNumber($bets, $finalNumber);
}
@@ -558,7 +558,7 @@ final class GameLiveService
if ($betId <= 0) {
continue;
}
if ($userId <= 0 || bccomp($total, '0', 4) <= 0) {
if ($userId <= 0 || bccomp($total, '0', 2) <= 0) {
Db::name('bet_order')->where('id', $betId)->where('status', 1)->update([
'status' => 3,
'update_time' => $now,
@@ -566,7 +566,7 @@ final class GameLiveService
continue;
}
$before = (string) (Db::name('user')->where('id', $userId)->value('coin') ?? '0');
$after = bcadd($before, $total, 4);
$after = bcadd($before, $total, 2);
$u = Db::name('user')->where('id', $userId)->where('coin', $before)->update([
'coin' => $after,
'update_time' => $now,
@@ -836,12 +836,12 @@ final class GameLiveService
$bestNumbers = [];
for ($n = 1; $n <= self::DRAW_NUMBER_MAX; $n++) {
$loss = self::estimateLossForNumber($bets, $n);
if ($bestLoss === null || bccomp((string) $loss, (string) $bestLoss, 4) < 0) {
if ($bestLoss === null || bccomp((string) $loss, (string) $bestLoss, 2) < 0) {
$bestLoss = $loss;
$bestNumbers = [$n];
continue;
}
if (bccomp((string) $loss, (string) $bestLoss, 4) === 0) {
if (bccomp((string) $loss, (string) $bestLoss, 2) === 0) {
$bestNumbers[] = $n;
}
}
@@ -878,7 +878,7 @@ final class GameLiveService
private static function estimateLossForNumber(array $bets, int $number): string
{
$payout = '0.0000';
$payout = '0.00';
foreach ($bets as $bet) {
$pickNumbers = $bet['pick_numbers'];
if (is_string($pickNumbers)) {
@@ -894,8 +894,8 @@ final class GameLiveService
$total = (string) ($bet['total_amount'] ?? '0');
$streak = (int) ($bet['streak_at_bet'] ?? 0);
$odds = StreakWinReward::totalOddsMultiplierForStreakAtBet($streak);
$orderPayout = bcmul($total, $odds, 4);
$payout = bcadd($payout, $orderPayout, 4);
$orderPayout = bcmul($total, $odds, 2);
$payout = bcadd($payout, $orderPayout, 2);
}
return $payout;
}

View File

@@ -29,7 +29,7 @@ final class GameRecordStatService
if ($status !== 4) {
Db::name('game_record')->where('id', $recordId)->update([
'platform_profit_amount' => '0.0000',
'platform_profit_amount' => '0.00',
'winner_user_count' => 0,
'update_time' => $now,
]);
@@ -45,8 +45,8 @@ final class GameRecordStatService
$resultNum = (int) $resultRaw;
$bets = Db::name('bet_order')->where('period_id', $recordId)->select()->toArray();
$totalBet = '0.0000';
$totalPayout = '0.0000';
$totalBet = '0.00';
$totalPayout = '0.00';
$winnerUserIds = [];
foreach ($bets as $bet) {
@@ -55,16 +55,16 @@ final class GameRecordStatService
continue;
}
$tb = (string) ($bet['total_amount'] ?? '0');
$totalBet = bcadd($totalBet, $tb, 4);
$totalBet = bcadd($totalBet, $tb, 2);
if ($st === 2) {
$payout = bcadd((string) ($bet['win_amount'] ?? '0'), (string) ($bet['jackpot_extra_amount'] ?? '0'), 4);
$payout = bcadd((string) ($bet['win_amount'] ?? '0'), (string) ($bet['jackpot_extra_amount'] ?? '0'), 2);
} else {
$payout = self::estimatePayoutForBet($bet, $resultNum);
}
$totalPayout = bcadd($totalPayout, $payout, 4);
if (bccomp($payout, '0', 4) > 0) {
$totalPayout = bcadd($totalPayout, $payout, 2);
if (bccomp($payout, '0', 2) > 0) {
$uid = (int) ($bet['user_id'] ?? 0);
if ($uid > 0) {
$winnerUserIds[$uid] = true;
@@ -72,7 +72,7 @@ final class GameRecordStatService
}
}
$profit = bcsub($totalBet, $totalPayout, 4);
$profit = bcsub($totalBet, $totalPayout, 2);
Db::name('game_record')->where('id', $recordId)->update([
'platform_profit_amount' => $profit,
@@ -96,12 +96,12 @@ final class GameRecordStatService
$pickNumbers = [];
}
if (!in_array($resultNumber, array_map('intval', $pickNumbers), true)) {
return '0.0000';
return '0.00';
}
$total = (string) ($bet['total_amount'] ?? '0');
$streak = (int) ($bet['streak_at_bet'] ?? 0);
$odds = StreakWinReward::totalOddsMultiplierForStreakAtBet($streak);
return bcmul($total, $odds, 4);
return bcmul($total, $odds, 2);
}
}