lockForUpdate()->first(); if (!$reward) throw new RuntimeException('数据不存在', HttpStatus::CUSTOM_ERROR); // 回水 status 2 = 部分发放:当月提前发放过,仍可继续发放差额 $openStatuses = $type === 'backflow' ? [Backflow::STATUS_PENDING, Backflow::STATUS_PARTIAL] : [0]; if (!in_array((int) $reward->status, $openStatuses, true)) { if ($skipPaid) return false; throw new RuntimeException('奖励已发放,无需再次发放', HttpStatus::CUSTOM_ERROR); } $cutoff = $type === 'rebate' ? Carbon::now('America/New_York')->format('Y-m-d') : Carbon::now('Asia/Shanghai')->format('Y-m'); // 回水手动发放不限月份(当月按发放时的亏损结算);笔笔返与自动发放仍须等周期结束。 $manualBackflow = $type === 'backflow' && $scheduledCutoff === null; if (!$manualBackflow && (string) $reward->date >= $cutoff) { throw new RuntimeException('未到发放时间', HttpStatus::CUSTOM_ERROR); } if ($scheduledCutoff !== null && (string) $reward->date >= $scheduledCutoff) return false; if ($type === 'rebate') { $result = RewardBasisService::rebate($reward); $basis = $result['effective_amount']; $reward->betting_amount = $result['betting_amount']; $reward->effective_betting_amount = $basis; $reward->rebate_ratio = bccomp($basis, '0', 2) > 0 ? bcdiv($result['amount'], $basis, 6) : '0'; $reward->audited_by = $operator; $changeType = '笔笔返'; $remark = "日期:{$reward->date};有效投注:{$basis}"; foreach ($result['details'] as $detail) { if ($detail['rule_id'] === null) continue; $remark .= ";{$detail['label']}:{$detail['effective_amount']}×{$detail['percentage']}%"; } } else { $result = RewardBasisService::backflow($reward); $reward->loss_amount = $result['loss_amount']; $reward->backflow_ratio = '0'; $changeType = '回水'; $remark = "日期:{$reward->date};亏损:{$result['loss_amount']}"; foreach ($result['details'] as $detail) { if ($detail['rule_id'] === null) continue; $remark .= ";{$detail['label']}亏损{$detail['loss_amount']}奖励{$detail['amount']}"; } } $amount = $result['amount']; $needFlow = $result['need_flow']; $reward->details = $result['details']; $reward->need_flow = $needFlow; // 本次实际发放:笔笔返一次发完;回水只发尚未发放的差额(金额回落时不追回) $pay = $amount; $payFlow = $needFlow; $closed = true; if ($type === 'backflow') { $pay = self::positive(bcsub($amount, (string) $reward->granted_amount, 2)); $payFlow = self::positive(bcsub($needFlow, (string) $reward->granted_need_flow, 2)); $closed = (string) $reward->date < $cutoff; if (!$closed && bccomp($pay, '0', 2) <= 0) { throw new RuntimeException("暂无可发放金额(按当前规则应发 {$amount},已发放 " . bcadd((string) $reward->granted_amount, '0', 2) . ')', HttpStatus::CUSTOM_ERROR); } $remark .= ";本次发放:{$pay};累计发放:" . bcadd((string) $reward->granted_amount, $pay, 2); } if (bccomp($pay, '0', 2) > 0) { $wallet = Wallet::where('member_id', $reward->member_id)->lockForUpdate()->first(); if (!$wallet) throw new RuntimeException('会员钱包不存在', HttpStatus::CUSTOM_ERROR); $before = (string) $wallet->available_balance; $wallet->available_balance = bcadd($before, $pay, 10); if (!$wallet->save()) throw new RuntimeException('余额更新失败', HttpStatus::CUSTOM_ERROR); BalanceLogService::addLog($reward->member_id, $pay, $before, $wallet->available_balance, $changeType, $reward->id, $remark); if (bccomp($payFlow, '0', 2) > 0) { WalletBonus::addData($reward->member_id, $type === 'rebate' ? 4 : 5, $pay, 0, $reward->id, $payFlow); // 规则配置了打码倍数时计入提现打码量 WageringService::addRequirement($wallet, $payFlow, 'activity:' . $changeType, $reward->id, $remark); } } $reward->amount = $amount; if ($type === 'backflow') { $reward->granted_amount = bcadd((string) $reward->granted_amount, $pay, 2); $reward->granted_need_flow = bcadd((string) $reward->granted_need_flow, $payFlow, 2); $reward->status = $closed ? Backflow::STATUS_GRANTED : Backflow::STATUS_PARTIAL; } else { $reward->status = 1; } $reward->grant_time = Carbon::now()->timestamp; if (!$reward->save()) throw new RuntimeException('发放失败', HttpStatus::CUSTOM_ERROR); return true; }, 3); } private static function positive(string $value): string { return bccomp($value, '0', 2) > 0 ? $value : '0.00'; } }