| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448 |
- <?php
- namespace Tests\Unit;
- use App\Services\WorldCup\Repositories\WorldCupReferralRepositoryInterface;
- use App\Services\WorldCup\WorldCupReferralRewardService;
- use App\Game\GlobalUserInfo;
- use Tests\TestCase;
- class WorldCupReferralRewardServiceTest extends TestCase
- {
- public function test_first_deposit_creates_reviewing_reward_for_both_users()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $repository->bindReferral(1001, 1002, 'manual');
- $service = new WorldCupReferralRewardService($repository);
- $result = $service->handleFirstDeposit(1002, 120.0, 'order-120');
- $this->assertTrue($result['success']);
- $this->assertSame('created', $result['status']);
- $this->assertCount(1, $repository->rewards);
- $this->assertSame(1001, $repository->rewards[0]['referrer_id']);
- $this->assertSame(1002, $repository->rewards[0]['invitee_id']);
- $this->assertSame(12000, $repository->rewards[0]['first_deposit_amt']);
- $this->assertSame(6000, $repository->rewards[0]['reward_each']);
- $this->assertSame(12000, $repository->rewards[0]['total_liability']);
- $this->assertSame('reviewing', $repository->rewards[0]['status']);
- }
- public function test_reward_caps_at_one_hundred_dollars_each_and_two_hundred_total()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $repository->bindReferral(1001, 1002, 'manual');
- $service = new WorldCupReferralRewardService($repository);
- $service->handleFirstDeposit(1002, 300.0, 'order-300');
- $this->assertSame(10000, $repository->rewards[0]['reward_each']);
- $this->assertSame(20000, $repository->rewards[0]['total_liability']);
- }
- public function test_duplicate_order_is_idempotent()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $repository->bindReferral(1001, 1002, 'manual');
- $service = new WorldCupReferralRewardService($repository);
- $first = $service->handleFirstDeposit(1002, 120.0, 'order-120');
- $second = $service->handleFirstDeposit(1002, 120.0, 'order-120');
- $this->assertSame('created', $first['status']);
- $this->assertSame('exists', $second['status']);
- $this->assertCount(1, $repository->rewards);
- }
- public function test_current_order_not_marked_successful_yet_still_creates_first_deposit_reward()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $repository->bindReferral(1001, 1002, 'manual');
- $repository->successfulOrderSnByUser[1002] = [];
- $service = new WorldCupReferralRewardService($repository);
- $result = $service->handleFirstDeposit(1002, 120.0, 'order-pending-status');
- $this->assertTrue($result['success']);
- $this->assertSame('created', $result['status']);
- $this->assertCount(1, $repository->rewards);
- $this->assertSame('order-pending-status', $repository->rewards[0]['first_deposit_order_sn']);
- }
- public function test_non_first_deposit_does_not_create_reward()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $repository->bindReferral(1001, 1002, 'manual');
- $repository->successfulOrderSnByUser[1002] = ['order-old'];
- $service = new WorldCupReferralRewardService($repository);
- $result = $service->handleFirstDeposit(1002, 120.0, 'order-new');
- $this->assertTrue($result['success']);
- $this->assertSame('not_first_deposit', $result['status']);
- $this->assertCount(0, $repository->rewards);
- }
- public function test_unbound_invitee_does_not_create_reward()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $service = new WorldCupReferralRewardService($repository);
- $result = $service->handleFirstDeposit(1002, 120.0, 'order-120');
- $this->assertTrue($result['success']);
- $this->assertSame('not_bound', $result['status']);
- $this->assertCount(0, $repository->rewards);
- }
- public function test_bind_invite_code_prevents_self_invite_and_duplicate_binding()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $repository->ensureUserState(1001);
- $inviteCode = $repository->states[1001]['invite_code'];
- $service = new WorldCupReferralRewardService($repository);
- $self = $service->bindInvite(1001, $inviteCode, 'manual');
- $bound = $service->bindInvite(1002, $inviteCode, 'manual');
- $duplicate = $service->bindInvite(1002, $inviteCode, 'manual');
- $this->assertFalse($self['success']);
- $this->assertSame('Cannot invite yourself', $self['message']);
- $this->assertTrue($bound['success']);
- $this->assertSame(1001, $repository->states[1002]['referred_by_user_id']);
- $this->assertTrue($duplicate['success']);
- $this->assertSame('exists', $duplicate['status']);
- }
- public function test_invite_log_returns_stats_and_invited_users()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $repository->bindReferral(1001, 1002, 'manual');
- $repository->bindReferral(1001, 1003, 'manual');
- $repository->createReward([
- 'referrer_id' => 1001,
- 'invitee_id' => 1002,
- 'first_deposit_order_sn' => 'order-120',
- 'first_deposit_amt' => 12000,
- 'reward_each' => 6000,
- 'total_liability' => 12000,
- 'risk_score' => 0,
- 'risk_level' => 'low',
- 'signals' => '[]',
- 'status' => 'reviewing',
- ]);
- $service = new WorldCupReferralRewardService($repository);
- $result = $service->inviteLog(1001, 'invited', 20);
- $this->assertSame(2, $result['stats']['invited_count']);
- $this->assertSame(1, $result['stats']['deposited_count']);
- $this->assertSame(1, $result['stats']['awaiting_count']);
- $this->assertSame(1002, $result['list'][0]['invitee_id']);
- $this->assertSame('deposited', $result['list'][0]['status']);
- $this->assertSame(12000, $result['list'][0]['first_deposit_amt']);
- $this->assertSame(1003, $result['list'][1]['invitee_id']);
- $this->assertSame('awaiting', $result['list'][1]['status']);
- }
- public function test_invite_log_can_return_first_deposits_only()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $repository->bindReferral(1001, 1002, 'manual');
- $repository->bindReferral(1001, 1003, 'manual');
- $repository->createReward([
- 'referrer_id' => 1001,
- 'invitee_id' => 1002,
- 'first_deposit_order_sn' => 'order-120',
- 'first_deposit_amt' => 12000,
- 'reward_each' => 6000,
- 'total_liability' => 12000,
- 'risk_score' => 0,
- 'risk_level' => 'low',
- 'signals' => '[]',
- 'status' => 'reviewing',
- ]);
- $service = new WorldCupReferralRewardService($repository);
- $result = $service->inviteLog(1001, 'deposits', 20);
- $this->assertCount(1, $result['list']);
- $this->assertSame(1002, $result['list'][0]['invitee_id']);
- }
- public function test_reward_log_returns_reviewing_and_paid_amounts()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $repository->createReward([
- 'referrer_id' => 1001,
- 'invitee_id' => 1002,
- 'first_deposit_order_sn' => 'order-120',
- 'first_deposit_amt' => 12000,
- 'reward_each' => 6000,
- 'total_liability' => 12000,
- 'risk_score' => 0,
- 'risk_level' => 'low',
- 'signals' => '[]',
- 'status' => 'reviewing',
- ]);
- $repository->createReward([
- 'referrer_id' => 1001,
- 'invitee_id' => 1003,
- 'first_deposit_order_sn' => 'order-40',
- 'first_deposit_amt' => 4000,
- 'reward_each' => 2000,
- 'total_liability' => 4000,
- 'risk_score' => 0,
- 'risk_level' => 'low',
- 'signals' => '[]',
- 'status' => 'approved',
- ]);
- $service = new WorldCupReferralRewardService($repository);
- $result = $service->rewardLog(1001, 20);
- $this->assertSame(6000, $result['stats']['reviewing_amount']);
- $this->assertSame(2000, $result['stats']['paid_amount']);
- $this->assertSame('reviewing', $result['list'][0]['status']);
- $this->assertSame('paid', $result['list'][1]['status']);
- $this->assertSame(GlobalUserInfo::faceidToAvatar(2), $result['list'][0]['avatar']);
- }
- public function test_reward_log_only_returns_rewards_invited_by_current_user()
- {
- $repository = new InMemoryWorldCupReferralRepository();
- $repository->createReward([
- 'referrer_id' => 1001,
- 'invitee_id' => 1002,
- 'first_deposit_order_sn' => 'order-own',
- 'first_deposit_amt' => 12000,
- 'reward_each' => 6000,
- 'total_liability' => 12000,
- 'risk_score' => 0,
- 'risk_level' => 'low',
- 'signals' => '[]',
- 'status' => 'reviewing',
- ]);
- $repository->createReward([
- 'referrer_id' => 9999,
- 'invitee_id' => 1001,
- 'first_deposit_order_sn' => 'order-parent',
- 'first_deposit_amt' => 8000,
- 'reward_each' => 4000,
- 'total_liability' => 8000,
- 'risk_score' => 0,
- 'risk_level' => 'low',
- 'signals' => '[]',
- 'status' => 'approved',
- ]);
- $service = new WorldCupReferralRewardService($repository);
- $result = $service->rewardLog(1001, 20);
- $this->assertSame(6000, $result['stats']['reviewing_amount']);
- $this->assertSame(0, $result['stats']['paid_amount']);
- $this->assertCount(1, $result['list']);
- $this->assertSame(1002, $result['list'][0]['invitee_id']);
- }
- }
- class InMemoryWorldCupReferralRepository implements WorldCupReferralRepositoryInterface
- {
- public $states = [];
- public $referrals = [];
- public $rewards = [];
- public $firstOrderSnByUser = [];
- public $successfulOrderSnByUser = [];
- public function ensureUserState(int $userId): array
- {
- if (!isset($this->states[$userId])) {
- $this->states[$userId] = [
- 'user_id' => $userId,
- 'invite_code' => 'WC' . $userId,
- 'referred_by_user_id' => null,
- 'referral_bind_at' => null,
- 'referral_bind_type' => null,
- 'device_fp' => null,
- 'pay_account_hash' => null,
- 'signup_ip' => null,
- ];
- }
- return $this->states[$userId];
- }
- public function findUserState(int $userId): ?array
- {
- return $this->states[$userId] ?? null;
- }
- public function findUserByInviteCode(string $inviteCode): ?array
- {
- foreach ($this->states as $state) {
- if ($state['invite_code'] === $inviteCode) {
- return $state;
- }
- }
- return null;
- }
- public function findReferralByInvitee(int $inviteeId): ?array
- {
- return $this->referrals[$inviteeId] ?? null;
- }
- public function bindReferral(int $referrerId, int $inviteeId, string $bindType): array
- {
- $this->ensureUserState($referrerId);
- $this->ensureUserState($inviteeId);
- $this->referrals[$inviteeId] = [
- 'referrer_id' => $referrerId,
- 'invitee_id' => $inviteeId,
- 'bind_type' => $bindType,
- ];
- $this->states[$inviteeId]['referred_by_user_id'] = $referrerId;
- $this->states[$inviteeId]['referral_bind_type'] = $bindType;
- $this->states[$inviteeId]['referral_bind_at'] = '2026-06-05 12:00:00';
- return $this->referrals[$inviteeId];
- }
- public function isFirstSuccessfulOrder(int $userId, string $orderSn): bool
- {
- if (array_key_exists($userId, $this->successfulOrderSnByUser)) {
- $successfulOrders = $this->successfulOrderSnByUser[$userId];
- if (!in_array($orderSn, $successfulOrders, true)) {
- return count($successfulOrders) === 0;
- }
- return reset($successfulOrders) === $orderSn;
- }
- return ($this->firstOrderSnByUser[$userId] ?? $orderSn) === $orderSn;
- }
- public function findRewardByInvitee(int $inviteeId): ?array
- {
- foreach ($this->rewards as $reward) {
- if ((int)$reward['invitee_id'] === $inviteeId) {
- return $reward;
- }
- }
- return null;
- }
- public function createReward(array $reward): array
- {
- $reward['reward_id'] = count($this->rewards) + 1;
- $this->rewards[] = $reward;
- return $reward;
- }
- public function paidOrdersMissingRewards(int $limit): array
- {
- return [];
- }
- public function inviteLogs(int $referrerId, string $type, int $limit): array
- {
- $rows = [];
- foreach ($this->referrals as $referral) {
- if ((int)$referral['referrer_id'] !== $referrerId) {
- continue;
- }
- $reward = $this->findRewardByInvitee((int)$referral['invitee_id']);
- if ($type === 'deposits' && !$reward) {
- continue;
- }
- $rows[] = [
- 'invitee_id' => (int)$referral['invitee_id'],
- 'game_id' => (int)$referral['invitee_id'],
- 'bind_at' => $referral['bind_at'] ?? '2026-06-05 12:00:00',
- 'first_deposit_amt' => $reward['first_deposit_amt'] ?? 0,
- 'reward_each' => $reward['reward_each'] ?? 0,
- 'reward_status' => $reward['status'] ?? null,
- 'status' => $reward ? 'deposited' : 'awaiting',
- ];
- }
- return array_slice($rows, 0, $limit);
- }
- public function inviteStats(int $referrerId): array
- {
- $invited = 0;
- $deposited = 0;
- foreach ($this->referrals as $referral) {
- if ((int)$referral['referrer_id'] !== $referrerId) {
- continue;
- }
- $invited++;
- if ($this->findRewardByInvitee((int)$referral['invitee_id'])) {
- $deposited++;
- }
- }
- return [
- 'invited_count' => $invited,
- 'deposited_count' => $deposited,
- 'awaiting_count' => $invited - $deposited,
- ];
- }
- public function rewardLogs(int $userId, int $limit): array
- {
- $rows = [];
- foreach ($this->rewards as $reward) {
- if ((int)$reward['referrer_id'] !== $userId) {
- continue;
- }
- $rows[] = [
- 'reward_id' => (int)$reward['reward_id'],
- 'referrer_id' => (int)$reward['referrer_id'],
- 'invitee_id' => (int)$reward['invitee_id'],
- 'friend_user_id' => (int)$reward['invitee_id'],
- 'avatar' => GlobalUserInfo::faceidToAvatar((int)$reward['invitee_id'] - 1000),
- 'first_deposit_amt' => (int)$reward['first_deposit_amt'],
- 'reward_each' => (int)$reward['reward_each'],
- 'status' => $reward['status'] === 'approved' ? 'paid' : $reward['status'],
- 'submitted_at' => $reward['submitted_at'] ?? '2026-06-05 12:00:00',
- ];
- }
- return array_slice($rows, 0, $limit);
- }
- public function rewardStats(int $userId): array
- {
- $reviewing = 0;
- $paid = 0;
- foreach ($this->rewards as $reward) {
- if ((int)$reward['referrer_id'] !== $userId) {
- continue;
- }
- if ($reward['status'] === 'approved') {
- $paid += (int)$reward['reward_each'];
- } elseif (in_array($reward['status'], ['reviewing', 'on_hold'], true)) {
- $reviewing += (int)$reward['reward_each'];
- }
- }
- return [
- 'reviewing_amount' => $reviewing,
- 'paid_amount' => $paid,
- ];
- }
- }
|