RewardCodeService.php 4.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  1. <?php
  2. namespace App\Services;
  3. use App\Game\GlobalUserInfo;
  4. use App\Game\Services\OuroGameService;
  5. use App\Http\helper\NumConfig;
  6. use App\Models\RewardCode;
  7. use App\Models\RewardCodeClaim;
  8. use Carbon\Carbon;
  9. use Illuminate\Support\Facades\DB;
  10. use Illuminate\Support\Str;
  11. class RewardCodeService
  12. {
  13. const STATUS_ACTIVE = 1;
  14. const STATUS_INACTIVE = 0;
  15. /**
  16. * Generate a unique 4-char alphanumeric reward code.
  17. */
  18. public static function generateUniqueCode(int $maxRetry = 5): string
  19. {
  20. for ($i = 0; $i < $maxRetry; $i++) {
  21. $code = strtoupper(Str::random(4));
  22. if (!RewardCode::where('code', $code)->exists()) {
  23. return $code;
  24. }
  25. }
  26. throw new \RuntimeException('Failed to generate unique reward code');
  27. }
  28. /**
  29. * Redeem reward by code.
  30. */
  31. public static function redeem(string $code, string $GlobalUID, string $clientIp = '', int $UserID = 0): array
  32. {
  33. $code = strtoupper($code);
  34. $now = Carbon::now();
  35. if (!$UserID) {
  36. $user = GlobalUserInfo::getGameUserInfo('GlobalUID', $GlobalUID);
  37. if (!$user) {
  38. throw new \InvalidArgumentException('user_not_exist');
  39. }
  40. $UserID = $user->UserID;
  41. }
  42. return DB::connection('write')->transaction(function () use ($code, $UserID, $GlobalUID, $clientIp, $now) {
  43. $rewardCode = RewardCode::where('code', $code)->lockForUpdate()->first();
  44. if (!$rewardCode || $rewardCode->status != self::STATUS_ACTIVE) {
  45. throw new \InvalidArgumentException('code_not_found');
  46. }
  47. if ($rewardCode->expire_at && $now->gt(Carbon::parse($rewardCode->expire_at))) {
  48. throw new \InvalidArgumentException('code_expired');
  49. }
  50. if ($rewardCode->claimed_count >= $rewardCode->total_count) {
  51. throw new \InvalidArgumentException('code_limit_reached');
  52. }
  53. if ($rewardCode->claimed_amount >= $rewardCode->total_amount) {
  54. throw new \InvalidArgumentException('code_amount_exhausted');
  55. }
  56. if (RewardCodeClaim::where('reward_code_id', $rewardCode->id)->where('UserID', $UserID)->exists()) {
  57. throw new \InvalidArgumentException('already_claimed');
  58. }
  59. $remainingCount = $rewardCode->total_count - $rewardCode->claimed_count;
  60. $remainingAmount = $rewardCode->total_amount - $rewardCode->claimed_amount;
  61. if ($remainingAmount < $rewardCode->min_amount) {
  62. throw new \InvalidArgumentException('code_amount_exhausted');
  63. }
  64. // fixed-point to support decimals (<1)
  65. $scale = 100;
  66. $min = (int)round($rewardCode->min_amount * $scale);
  67. $max = (int)round($rewardCode->max_amount * $scale);
  68. $remainingScaled = (int)floor($remainingAmount * $scale);
  69. $minReserve = ($remainingCount - 1) * $min;
  70. $maxPossible = $remainingScaled - $minReserve;
  71. $maxPossible = max($min, min($max, $maxPossible));
  72. $reward = mt_rand($min, $maxPossible) / $scale;
  73. $reward = round($reward, 2);
  74. $rewardCode->claimed_count += 1;
  75. $rewardCode->claimed_amount = round($rewardCode->claimed_amount + $reward, 2);
  76. $rewardCode->save();
  77. RewardCodeClaim::create([
  78. 'reward_code_id' => $rewardCode->id,
  79. 'code' => $rewardCode->code,
  80. 'UserID' => $UserID,
  81. 'GlobalUID' => $GlobalUID,
  82. 'amount' => $reward,
  83. 'client_ip' => $clientIp,
  84. ]);
  85. $scoreAmount = (int)round($reward * NumConfig::NUM_VALUE);
  86. OuroGameService::AddScore($UserID, $scoreAmount, OuroGameService::REASON_RewardCode,false);
  87. return [
  88. 'code' => $rewardCode->code,
  89. 'amount' => $reward,
  90. 'claimed_count' => $rewardCode->claimed_count,
  91. 'claimed_amount' => $rewardCode->claimed_amount,
  92. 'total_count' => $rewardCode->total_count,
  93. 'total_amount' => $rewardCode->total_amount,
  94. 'expire_at' => $rewardCode->expire_at,
  95. ];
  96. });
  97. }
  98. }