repository = $repository ?: new SqlWorldCupReferralRepository(); $this->referralService = $referralService ?: new WorldCupReferralService(); } public function getInviteState(int $userId): array { $state = $this->repository->ensureUserState($userId); return [ 'invite_code' => $state['invite_code'], 'referral_bound' => !empty($state['referred_by_user_id']), 'referred_by_user_id' => $state['referred_by_user_id'] ?? null, ]; } public function bindInvite(int $inviteeId, string $inviteCode, string $bindType = 'manual'): array { $inviteCode = trim($inviteCode); if ($inviteCode === '') { return $this->fail('Invite code is required'); } $this->repository->ensureUserState($inviteeId); $referrer = $this->repository->findUserByInviteCode($inviteCode); if (!$referrer) { return $this->fail('Invite code not found'); } $referrerId = (int)$referrer['user_id']; if ($referrerId === $inviteeId) { return $this->fail('Cannot invite yourself'); } $exists = $this->repository->findReferralByInvitee($inviteeId); if ($exists) { return [ 'success' => true, 'status' => 'exists', 'data' => $exists, ]; } $referral = $this->repository->bindReferral($referrerId, $inviteeId, $bindType); return [ 'success' => true, 'status' => 'created', 'data' => $referral, ]; } public function handleFirstDeposit(int $userId, float $payAmt, string $orderSn): array { if (!$this->repository->isFirstSuccessfulOrder($userId, $orderSn)) { Log::info('Order is not the first successful deposit', [ 'user_id' => $userId, 'order_sn' => $orderSn, ]); return $this->ok('not_first_deposit'); } $referral = $this->repository->findReferralByInvitee($userId); if (!$referral) { Log::info('No referral found for user', [ 'user_id' => $userId, 'order_sn' => $orderSn, ]); return $this->ok('not_bound'); } $exists = $this->repository->findRewardByInvitee($userId); if ($exists) { Log::info('Reward already exists for user', [ 'user_id' => $userId, 'order_sn' => $orderSn, 'reward_id' => $exists['reward_id'], ]); return [ 'success' => true, 'status' => 'exists', 'data' => $exists, ]; } $firstDepositAmount = (int)round($payAmt * 100); $calculation = $this->referralService->calculateReward($firstDepositAmount); if (!$calculation['qualifies']) { Log::info('User is not qualified for reward', [ 'user_id' => $userId, 'order_sn' => $orderSn, ]); return $this->ok('not_qualified'); } $risk = $this->scoreRisk((int)$referral['referrer_id'], $userId); $reward = $this->repository->createReward([ 'referrer_id' => (int)$referral['referrer_id'], 'invitee_id' => $userId, 'first_deposit_order_sn' => $orderSn, 'first_deposit_amt' => $firstDepositAmount, 'reward_each' => $calculation['reward_each'], 'total_liability' => $calculation['total_liability'], 'risk_score' => $risk['risk_score'], 'risk_level' => $risk['risk_level'], 'signals' => json_encode($risk['signals']), 'status' => 'reviewing', ]); return [ 'success' => true, 'status' => 'created', 'data' => $reward, ]; } public function generateMissingRewards(int $limit = 200): array { $orders = $this->repository->paidOrdersMissingRewards($limit); $result = [ 'checked' => count($orders), 'created' => 0, 'skipped' => 0, ]; foreach ($orders as $order) { $handled = $this->handleFirstDeposit( (int)$order['user_id'], ((float)$order['amount']) / 100, (string)$order['order_sn'] ); if (($handled['status'] ?? '') === 'created') { $result['created']++; } else { $result['skipped']++; } } return $result; } public function inviteLog(int $userId, string $type = 'invited', int $limit = 20): array { $type = $type === 'deposits' ? 'deposits' : 'invited'; $limit = $this->normalizeLimit($limit); return [ 'server_time' => time(), 'stats' => $this->repository->inviteStats($userId), 'type' => $type, 'list' => $this->repository->inviteLogs($userId, $type, $limit), ]; } public function rewardLog(int $userId, int $limit = 20): array { $limit = $this->normalizeLimit($limit); return [ 'stats' => $this->repository->rewardStats($userId), 'list' => $this->repository->rewardLogs($userId, $limit), ]; } private function scoreRisk(int $referrerId, int $inviteeId): array { $referrer = $this->repository->findUserState($referrerId) ?: []; $invitee = $this->repository->findUserState($inviteeId) ?: []; $signals = []; $score = 0; if (!empty($referrer['device_fp']) && !empty($invitee['device_fp']) && $referrer['device_fp'] === $invitee['device_fp']) { $signals[] = 'same_device'; $score += 40; } if (!empty($referrer['pay_account_hash']) && !empty($invitee['pay_account_hash']) && $referrer['pay_account_hash'] === $invitee['pay_account_hash']) { $signals[] = 'same_payment'; $score += 40; } if (!empty($referrer['signup_ip']) && !empty($invitee['signup_ip']) && $referrer['signup_ip'] === $invitee['signup_ip']) { $signals[] = 'same_ip'; $score += 20; } return [ 'risk_score' => $score, 'risk_level' => $score >= 40 ? 'high' : ($score >= 20 ? 'medium' : 'low'), 'signals' => $signals, ]; } private function normalizeLimit(int $limit): int { if ($limit <= 0) { return 20; } return min($limit, 100); } private function ok(string $status): array { return [ 'success' => true, 'status' => $status, 'data' => [], ]; } private function fail(string $message): array { return [ 'success' => false, 'message' => $message, 'data' => [], ]; } }