rewardPointRepository = $rewardPointRepository; $this->settingRepository = $settingRepository; $this->_config = request('_config'); } public function index() { $customer = auth()->guard('customer')->user(); if (!$customer) { return redirect()->route('customer.session.index'); } $points = $this->rewardPointRepository->getCustomerPoints($customer->id); $history = $this->rewardPointRepository->getHistory($customer->id); return view($this->_config['view'], compact('points', 'history')); } public function signIn() { $customer = auth()->guard('customer')->user(); if (!$customer) { return ApiResponse::unauthorized(); } // Check if sign in feature is enabled $signinEnabled = $this->settingRepository->getConfigValue('signin_enabled', true); if (!$signinEnabled) { return ApiResponse::forbidden('Sign in feature is disabled'); } $today = Carbon::now()->format('Y-m-d'); $existingSign = RewardPointCustomerSign::where('customer_id', $customer->id) ->where('sign_date', $today) ->first(); if ($existingSign) { return ApiResponse::error('Already signed in today'); } $lastSign = RewardPointCustomerSign::where('customer_id', $customer->id) ->orderBy('sign_date', 'desc') ->first(); $countDate = 1; if ($lastSign) { $lastSignDate = Carbon::parse($lastSign->sign_date); $yesterday = Carbon::yesterday(); if ($lastSignDate->toDateString() === $yesterday->toDateString()) { $countDate = $lastSign->count_date + 1; } elseif ($lastSignDate->toDateString() !== $today) { $countDate = 1; } } // Get sign in points from ly_mw_reward_points_settings table $points = $this->getSignInPointsByDay($countDate); // Create sign in record (Laravel auto-maintains created_at and updated_at) $sign = RewardPointCustomerSign::create([ 'customer_id' => $customer->id, 'sign_date' => $today, 'count_date' => $countDate, 'point' => $points, 'code' => uniqid('SIGN_') ]); // Add points to customer account $this->rewardPointRepository->addPoints( $customer->id, RewardActiveRule::TYPE_SIGN_IN, $points, null, "Daily sign-in reward (Day {$countDate})" ); $totalPoints = $this->rewardPointRepository->getCustomerPoints($customer->id); return ApiResponse::success([ 'points' => $points, 'streak' => $countDate, 'total_points' => $totalPoints ], "Sign in successful! Earned {$points} points"); } /** * Get sign in points by day from ly_mw_reward_points_settings table */ protected function getSignInPointsByDay($day) { // Map sign in days to configuration codes $dayPointsMap = [ 1 => 'signin_day1_points', 2 => 'signin_day2_points', 3 => 'signin_day3_points', 4 => 'signin_day4_points', 5 => 'signin_day5_points', 6 => 'signin_day6_points', 7 => 'signin_day7_points', ]; // If <= 7 days, use corresponding configuration if ($day <= 7 && isset($dayPointsMap[$day])) { return (int) $this->settingRepository->getConfigValue($dayPointsMap[$day], 10); } // 8 days and beyond, use signin_day8_plus_points configuration return (int) $this->settingRepository->getConfigValue('signin_day8_plus_points', 70); } public function getSignStatus() { $customer = auth()->guard('customer')->user(); if (!$customer) { return ApiResponse::unauthorized(); } $today = Carbon::now()->format('Y-m-d'); $signedToday = RewardPointCustomerSign::where('customer_id', $customer->id) ->where('sign_date', $today) ->exists(); $lastSign = RewardPointCustomerSign::where('customer_id', $customer->id) ->orderBy('sign_date', 'desc') ->first(); $currentGroup = 'sign_in'; $settings = $this->settingRepository->getSettingsByGroup($currentGroup); $dayNamesMap = [ 'signin_day1_points' => 'Day 1 Sign-in Points', 'signin_day2_points' => 'Day 2 Sign-in Points', 'signin_day3_points' => 'Day 3 Sign-in Points', 'signin_day4_points' => 'Day 4 Sign-in Points', 'signin_day5_points' => 'Day 5 Sign-in Points', 'signin_day6_points' => 'Day 6 Sign-in Points', 'signin_day7_points' => 'Day 7 Sign-in Points', 'signin_day8_plus_points' => 'Day 7+ Sign-in Points', ]; $new = []; foreach ($settings as $setting) { $code = $setting['code']; if (array_key_exists($code, $dayNamesMap)) { $new[] = [ 'day' => $dayNamesMap[$code], 'points' => $setting['value'] ]; } } return ApiResponse::success([ 'signed_today' => $signedToday, 'current_streak' => $lastSign ? $lastSign->count_date : 0, 'signed_detail' => $new, 'total_points' => $this->rewardPointRepository->getCustomerPoints($customer->id) ]); } /** * Get customer points history/records * * @param Request $request * @return \Illuminate\Http\JsonResponse */ public function getPointsHistory(Request $request) { $customer = auth()->guard('customer')->user(); if (!$customer) { return ApiResponse::unauthorized(); } // Get pagination parameters $page = $request->input('page', 1); $limit = $request->input('per_page', 20); $amountType= $request->input('amountType'); // Validate limit $limit = min(max($limit, 1), 100); // Limit between 1 and 100 // Get paginated history $history = $this->rewardPointRepository->getHistory($customer->id, $limit,$page,$amountType); // Transform history data $transformedHistory = collect($history->items())->map(function ($item) { return [ 'id' => $item->history_id, 'type' => $item->type_of_transaction, 'type_text' => $this->getTransactionTypeText($item->type_of_transaction), 'amount' => $item->amount, 'balance' => $item->balance, 'detail' => $item->transaction_detail, 'order_id' => $item->history_order_id > 0 ? $item->history_order_id : null, 'expired_day' => $item->expired_day, 'expired_time' => $item->expired_time ? Carbon::parse($item->expired_time)->format('Y-m-d H:i:s') : null, 'remaining_points' => $item->point_remaining, 'status' => $item->status, 'status_text' => $this->getStatusText($item->status), 'created_at' => Carbon::parse($item->transaction_time)->format('Y-m-d H:i:s'), ]; }); return ApiResponse::paginated( [ 'current_points' => $this->rewardPointRepository->getCustomerPoints($customer->id), 'history' => $transformedHistory ], $history ); } /** * Get customer points summary * * @return \Illuminate\Http\JsonResponse */ public function getPointsSummary() { $customer = auth()->guard('customer')->user(); if (!$customer) { return ApiResponse::unauthorized(); } $totalPoints = $this->rewardPointRepository->getCustomerPoints($customer->id); // Get points expired in the last month $oneMonthAgo = Carbon::now()->subMonth(); $recentlyExpired = RewardPointHistory::where('customer_id', $customer->id) ->where('status', RewardPointHistory::STATUS_EXPIRED) ->whereNotNull('expired_time') ->where('expired_time', '>=', $oneMonthAgo) ->sum('amount') * -1; // Get expiring points in the next month (still valid but will expire soon) $nextMonth = Carbon::now()->addMonth(); $expiringSoon = RewardPointHistory::where('customer_id', $customer->id) ->where('status', RewardPointHistory::STATUS_COMPLETED) ->whereNotNull('expired_time') ->where('expired_time', '>', Carbon::now()) ->where('expired_time', '<=', $nextMonth) ->sum('point_remaining'); return ApiResponse::success([ 'recently_expired' => $recentlyExpired, 'expiring_soon' => $expiringSoon, 'current_points' => $totalPoints ]); } /** * Apply reward points to cart */ public function applyPoints(Request $request) { $points = $request->input('points', 0); $cartRewardPoints = app('cartrewardpoints'); $validation = $cartRewardPoints->validatePoints($points); if ($validation !== true) { return ApiResponse::error($validation); } $result = $cartRewardPoints->applyPoints($points); if ($result) { $discountDetails = $cartRewardPoints->getDiscountDetails(); return ApiResponse::success([ 'discount' => $discountDetails ], 'Reward points applied successfully'); } return ApiResponse::error('Failed to apply reward points'); } /** * Remove reward points from cart */ public function removePoints() { $cartRewardPoints = app('cartrewardpoints'); $cartRewardPoints->removePoints(); return ApiResponse::success([], 'Reward points removed successfully'); } /** * 浏览产品赠送积分(每个产品每天仅赠送一次) * * @param Request $request * @return \Illuminate\Http\JsonResponse */ public function browseProduct(Request $request) { $customer = auth()->guard('customer')->user(); if (!$customer) { return ApiResponse::unauthorized(); } $productId = $request->input('categorys'); if (!$productId) { return ApiResponse::validationError('categorys is required'); } // 查询有效的浏览产品规则 $rule = RewardActiveRule::active() ->ofType(RewardActiveRule::TYPE_BROWSE_PRODUCT) ->first(); if (!$rule) { return ApiResponse::forbidden('Browse product reward is not available'); } // 检查规则是否适用于当前客户 if (!$rule->isApplicableToCustomer($customer)) { return ApiResponse::forbidden('This reward is not applicable to your account'); } // Redis Key: browse_product:{customer_id}:{date}:{product_id} $today = Carbon::now()->format('Y-m-d'); $redisKey = "browse_product:{$customer->id}:{$today}:{$productId}"; // Redis 判断该产品今天是否已经浏览过 if (Redis::exists($redisKey)) { return ApiResponse::error('You have already earned points for browsing this product today'); } // 获取该客户应得的积分 $points = $rule->getPointsForCustomer($customer); if ($points <= 0) { $points = (int) $rule->reward_point; } if ($points <= 0) { return ApiResponse::error('No points configured for browse product'); } // 添加积分 $this->rewardPointRepository->addPoints( $customer->id, RewardActiveRule::TYPE_BROWSE_PRODUCT, $points, null, "Browsed categorys #{$productId}", $rule ); // 写入 Redis,TTL = 到当天结束的剩余秒数 $endOfDay = Carbon::now()->endOfDay(); $ttl = Carbon::now()->diffInSeconds($endOfDay); Redis::setex($redisKey, $ttl, 1); // 同时记录到当天已浏览产品集合(方便 getBrowsedProducts 读取) $setKey = "browse_product:{$customer->id}:{$today}:set"; Redis::sadd($setKey, $productId); Redis::expire($setKey, $ttl); $totalPoints = $this->rewardPointRepository->getCustomerPoints($customer->id); return ApiResponse::success([ 'categorys' => $productId, 'points' => $points, 'total_points' => $totalPoints ], "You earned {$points} points for browsing this product!"); } /** * 获取今天已浏览并获得积分的产品列表 * * @return \Illuminate\Http\JsonResponse */ public function getBrowsedProducts() { $customer = auth()->guard('customer')->user(); if (!$customer) { return ApiResponse::unauthorized(); } $today = Carbon::now()->format('Y-m-d'); $setKey = "browse_product:{$customer->id}:{$today}:set"; // 从 Redis Set 中获取今天已浏览的产品ID列表 $browsedProductIds = Redis::smembers($setKey); $browsedProductIds = array_map('intval', $browsedProductIds); return ApiResponse::success([ 'browsed_product_ids' => $browsedProductIds, 'browsed_count' => count($browsedProductIds) ]); } /** * 支持的关注平台 */ const FOLLOW_PLATFORMS = ['ig' => 'Instagram', 'fb' => 'Facebook', 'ytb' => 'YouTube', 'tt' => 'TikTok']; /** * 关注社交平台赠送积分(每位用户每个平台仅一次,永久有效) * * @param Request $request * @return \Illuminate\Http\JsonResponse */ public function followPlatform(Request $request) { $customer = auth()->guard('customer')->user(); if (!$customer) { return ApiResponse::unauthorized(); } $platform = $request->input('platform'); if (!$platform || !isset(self::FOLLOW_PLATFORMS[$platform])) { return ApiResponse::validationError('Invalid platform. Supported: ' . implode(', ', array_keys(self::FOLLOW_PLATFORMS))); } // Redis Key: follow_platform:{customer_id}:{platform}(永久有效,不设 TTL) $redisKey = "follow_platform:{$customer->id}:{$platform}"; // 1. Redis 快速判断(主缓存) if (Redis::exists($redisKey)) { return ApiResponse::error('You have already earned points for following ' . self::FOLLOW_PLATFORMS[$platform]); } // 2. DB 兜底检查:防止 Redis 被清理后重复发积分 $dbExists = RewardPointHistory::where('customer_id', $customer->id) ->where('type_of_transaction', RewardActiveRule::TYPE_FOLLOW) ->where('transaction_detail', 'Followed ' . self::FOLLOW_PLATFORMS[$platform]) ->exists(); if ($dbExists) { // DB 有记录但 Redis 丢失,回补 Redis Redis::set($redisKey, 1); return ApiResponse::error('You have already earned points for following ' . self::FOLLOW_PLATFORMS[$platform]); } // 查询有效的关注规则 $rule = RewardActiveRule::active() ->ofType(RewardActiveRule::TYPE_FOLLOW) ->first(); if (!$rule) { return ApiResponse::forbidden('Follow reward is not available'); } // 检查规则是否适用于当前客户 if (!$rule->isApplicableToCustomer($customer)) { return ApiResponse::forbidden('This reward is not applicable to your account'); } // 获取该客户应得的积分 $points = $rule->getPointsForCustomer($customer); if ($points <= 0) { $points = (int) $rule->reward_point; } if ($points <= 0) { return ApiResponse::error('No points configured for follow reward'); } // 添加积分 $this->rewardPointRepository->addPoints( $customer->id, RewardActiveRule::TYPE_FOLLOW, $points, null, 'Followed ' . self::FOLLOW_PLATFORMS[$platform], $rule ); // 写入 Redis(永久有效,无需 TTL) Redis::set($redisKey, 1); $totalPoints = $this->rewardPointRepository->getCustomerPoints($customer->id); return ApiResponse::success([ 'platform' => $platform, 'platform_name' => self::FOLLOW_PLATFORMS[$platform], 'points' => $points, 'total_points' => $totalPoints ], 'You earned ' . $points . ' points for following ' . self::FOLLOW_PLATFORMS[$platform] . '!'); } /** * 获取用户已关注的平台列表及积分状态 * * @return \Illuminate\Http\JsonResponse */ public function getFollowStatus() { $customer = auth()->guard('customer')->user(); if (!$customer) { return ApiResponse::unauthorized(); } $platforms = []; foreach (self::FOLLOW_PLATFORMS as $code => $name) { $redisKey = "follow_platform:{$customer->id}:{$code}"; $followed = (bool) Redis::exists($redisKey); // Redis 未命中时,从 DB 回补并修复 Redis if (!$followed) { $dbExists = RewardPointHistory::where('customer_id', $customer->id) ->where('type_of_transaction', RewardActiveRule::TYPE_FOLLOW) ->where('transaction_detail', 'Followed ' . $name) ->exists(); if ($dbExists) { Redis::set($redisKey, 1); $followed = true; } } $platforms[] = [ 'code' => $code, 'name' => $name, 'followed' => $followed ]; } return ApiResponse::success([ 'platforms' => $platforms, 'followed_count' => count(array_filter($platforms, fn($p) => $p['followed'])) ]); } /** * Get points information for cart */ public function getPointsInfo() { $cartRewardPoints = app('cartrewardpoints'); return ApiResponse::success([ 'available_points' => $cartRewardPoints->getAvailablePoints(), 'points_used' => $cartRewardPoints->getPointsUsed(), 'discount_amount' => $cartRewardPoints->getDiscountAmount(), 'points_value' => $cartRewardPoints->getPointsValue(1), 'max_points_allowed' => $cartRewardPoints->getMaxPointsByCartTotal() ]); } /** * Get transaction type text */ protected function getTransactionTypeText($type) { $types = [ 1 => 'Daily Sign In', 2 => 'Registration', 3 => 'Order Purchase', 4 => 'Product Review', 5 => 'Referral', 6 => 'Birthday', 7 => 'Share', 8 => 'Subscription', 9 => 'Login', 13 => 'Browse Product', 14 => 'Follow', 99 => 'admin', ]; return $types[$type] ?? 'Unknown'; } /** * Get status text */ protected function getStatusText($status) { $statuses = [ 1 => 'Completed', 0 => 'Pending', 3 => 'Expired', 2 => 'Cancelled' ]; return $statuses[$status] ?? 'Unknown'; } }