setData($code, $email); } } public function setData(string $code, string $email): self { $this->code = $code; $this->email = $email; $this->user = $this->getUserByEmail($email); return $this; } public function setCategory(array $productCategoryIds): self { $this->productCategoryId = $productCategoryIds; return $this; } public function setProducts(array $productIds = []) { $this->productIds = $productIds; return $this; } public function apply(): array { return $this->isCouponCode() ? $this->applyCouponCode() : $this->applyReferralCode(); } protected function isCouponCode(): bool { return CouponModel::where('coupon_code', $this->code)->exists(); } protected function applyCouponCode(): array { $coupon = CouponModel::where('coupon_code', $this->code) ->where('status', 'true') ->first(); if (! $coupon) { return $this->error('The coupon code entered is invalid. Please try again.'); } if (checkCouponExpiry($coupon->end_date) === 'Coupon Expired') { return $this->error('Coupon expired. Try another coupon.'); } if (CouponUsageModel::where('coupon_id', $coupon->id)->count() >= $coupon->quantity) { return $this->error('Coupon quantity exhausted. Try another coupon.'); } if (! $this->isCouponApplicableToCategory($coupon)) { return $this->error('This coupon is not applicable to selected category products.'); } // ✅ PRODUCT CHECK if (! $this->isCouponApplicableToProduct($coupon)) { return $this->error('This coupon is not applicable to selected product.'); } if (! $this->isCouponApplicableToUser($coupon)) { return $this->error('You are not eligible for this coupon.'); } // Track usage // CouponUsageModel::create([ // 'coupon_id' => $coupon->id, // 'used_by_user_id' => $this->user->id ?? null, // 'used_at' => now(), // ]); $discountInfo = $this->buildCouponResponse($coupon); return $this->success('Coupon successfully applied.', $discountInfo); } protected function isCouponApplicableToCategory($coupon): bool { // ✅ Coupon without categories = valid for all if (! $coupon->categories || $coupon->categories->isEmpty()) { return true; } // ✅ No product categories = coupon not applicable if (empty($this->productCategoryId)) { return false; } // ✅ Coupon category IDs $allowedCategoryIds = $coupon->categories->pluck('id')->toArray(); // ✅ Product category IDs $productCategoryIds = $this->productCategoryId; // ✅ Apply coupon if ANY ONE matches return count(array_intersect($productCategoryIds, $allowedCategoryIds)) > 0; } private function isCouponApplicableToProduct(CouponModel $coupon): bool { // ✅ Coupon has no product restrictions → allow all if ($coupon->products()->count() === 0) { return true; } // ✅ No products sent in request → reject if (empty($this->productIds)) { return true; } // ✅ Get allowed product IDs from coupon $allowedProductIds = $coupon->products()->pluck('product_id')->toArray(); // ✅ Check if ANY requested product is NOT in allowed list foreach ($this->productIds as $productId) { if (! in_array($productId, $allowedProductIds)) { return false; // ❌ even one invalid → reject } } return true; // ✅ all matched } protected function isCouponApplicableToUser($coupon): bool { // Customer-specific coupon if ($coupon->customers !== 'all_customers') { if (! $this->user) { return false; } $eligible = explode(',', $coupon->customers); if (! in_array($this->user->id, $eligible)) { return false; } } /* * GENERAL USER * * General users have one account per email. * Check only that user's ID. */ if ($this->user && $this->user->type !== 'guest') { $usageCount = CouponUsageModel::where('coupon_id', $coupon->id) ->where('used_by_user_id', $this->user->id) ->count(); return $usageCount < $coupon->per_customer_limit; } /* * GUEST USER * * Multiple guest accounts can exist for the same email. * Check all guest user IDs for this email. */ $userIds = User::where('email', $this->email) ->where('type', 'guest') ->pluck('id'); if ($userIds->isNotEmpty()) { $usageCount = CouponUsageModel::where('coupon_id', $coupon->id) ->whereIn('used_by_user_id', $userIds) ->count(); return $usageCount < $coupon->per_customer_limit; } // Brand-new guest email return true; } // referral user protected function applyReferralCode(): array { $referral = ReferralModel::where('code', $this->code)->first(); if (! $referral) { return $this->error('Promo code is invalid.'); } // If type is reward, user must exist if ($referral->type === 'reward' && ! $this->user) { return $this->error('This reward coupon is only usable by the user it belongs to.'); } // Type: reward (self-use) if ($referral->type === 'reward') { if ($referral->user_id !== $this->user->id) { return $this->error('This reward code is only usable by the owner.'); } if ($this->hasReferralBeenUsed($referral->id, $this->user->id)) { return $this->error('You have already used this reward coupon.'); } // get the order id and check their referral code is used before that // $referralCodeCheck = ReferralModel::where('order_id', $referral->order_id)->where('type', 'referral')->first(); // $checkFirstReferralUsed = OrderModel::where('coupon_code', $referralCodeCheck->code)->exists(); // if (!$checkFirstReferralUsed) { // return $this->error('This reward coupon is not yet active. At least one other user must use your referral code before you can apply it.'); // } // ReferralUsageModel::create([ // 'coupon_id' => $referral->id, // 'used_by_user_id' => $this->user->id, // 'used_at' => now(), // ]); $discount = $this->parseDiscountValue($referral->discount_value); $discount['coupon_code'] = $this->code; $discount['coupon_id'] = $referral->id; $discount['type'] = 'reward'; return $this->success('Reward coupon applied.', $discount); } // Type: referral (used by others) if ($referral->type === 'referral') { // Allow guest user (new user not yet created) if ($this->user && $referral->user_id === $this->user->id) { return $this->error('You cannot use your own referral code.'); } if ($this->user && $this->hasReferralBeenUsed($referral->id, $this->user->id)) { return $this->error('You have already used this referral code.'); } // At this point, guest or registered user can use the code // Don't track usage yet if user is not created if ($this->user) { // ReferralUsageModel::create([ // 'coupon_id' => $referral->id, // 'used_by_user_id' => $this->user->id, // 'used_at' => now(), // ]); } $discount = $this->parseDiscountValue($referral->discount_value); $discount['coupon_code'] = $this->code; $discount['coupon_id'] = $referral->id; $discount['type'] = 'referral'; return $this->success('Referral coupon applied successfully.', $discount); } return $this->error('Unsupported referral type.'); } // protected function hasReferralBeenUsed($couponId, $userId): bool // { // return ReferralUsageModel::where('coupon_id', $couponId) // ->where('used_by_user_id', $userId) // ->exists(); // } protected function hasReferralBeenUsed($couponId, $userId): bool { $user = User::find($userId); if (! $user) { return false; } // Get all user IDs (guest + registered) having the same email $userIds = User::where('email', $user->email)->pluck('id'); return ReferralUsageModel::where('coupon_id', $couponId) ->whereIn('used_by_user_id', $userIds) ->exists(); } protected function parseDiscountValue($value): array { if (str_contains($value, '%')) { return [ 'discount_type' => 'percent', 'discount_value' => str_replace('%', '', $value), ]; } return [ 'discount_type' => 'fixed', 'discount_value' => $value, ]; } protected function buildCouponResponse($coupon): array { return [ 'type' => 'coupon', 'coupon_id' => $coupon->id, 'discount_type' => $coupon->discount_type, 'discount_value' => $coupon->discount_value, 'coupon_title' => $coupon->coupon_title, 'coupon_code' => $coupon->coupon_code, 'minimum_cart_value' => $coupon->minimum_cart_value, ]; } protected function getUserByEmail(string $email): ?User { return User::where('email', $email) ->where('type', '!=', 'guest') ->first(); } protected function success(string $msg, array $data = []): array { return ['res' => 'success', 'msg' => $msg, 'data' => $data]; } protected function error(string $msg): array { return ['res' => 'error', 'msg' => $msg]; } }