| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611 |
- <?php
- namespace Longyi\RewardPoints\Http\Controllers;
- use Illuminate\Support\Facades\Log;
- use Longyi\RewardPoints\Models\RewardActiveRule;
- use Longyi\RewardPoints\Repositories\RewardPointRepository;
- use Longyi\RewardPoints\Repositories\RewardPointSettingRepository;
- use Longyi\RewardPoints\Models\RewardPointCustomerSign;
- use Longyi\RewardPoints\Helpers\ApiResponse;
- use Carbon\Carbon;
- use Illuminate\Http\Request;
- use Illuminate\Routing\Controller;
- use Longyi\RewardPoints\Models\RewardPointHistory;
- use Illuminate\Support\Facades\Redis;
- class RewardPointsController extends Controller
- {
- protected $rewardPointRepository;
- protected $settingRepository;
- protected $_config;
- public function __construct(
- RewardPointRepository $rewardPointRepository,
- RewardPointSettingRepository $settingRepository
- ) {
- $this->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';
- }
- }
|