throttleKey($workspaceId, $userId, $request); return $this->limiter->tooManyAttempts($key, self::MAX_ATTEMPTS); } /** * Increment the checkout attempt counter. */ public function increment(?int $workspaceId, ?int $userId, Request $request): void { $key = $this->throttleKey($workspaceId, $userId, $request); $this->limiter->hit($key, self::DECAY_SECONDS); } /** * Get the number of attempts made. */ public function attempts(?int $workspaceId, ?int $userId, Request $request): int { return $this->limiter->attempts($this->throttleKey($workspaceId, $userId, $request)); } /** * Get seconds until rate limit resets. */ public function availableIn(?int $workspaceId, ?int $userId, Request $request): int { return $this->limiter->availableIn($this->throttleKey($workspaceId, $userId, $request)); } /** * Clear rate limit (e.g., after successful checkout). */ public function clear(?int $workspaceId, ?int $userId, Request $request): void { $this->limiter->clear($this->throttleKey($workspaceId, $userId, $request)); } /** * Check if customer/IP has exceeded coupon validation rate limits. */ public function tooManyCouponAttempts(?int $workspaceId, ?int $userId, Request $request): bool { $key = $this->couponThrottleKey($workspaceId, $userId, $request); return $this->limiter->tooManyAttempts($key, self::MAX_COUPON_ATTEMPTS); } /** * Increment the coupon validation attempt counter. */ public function incrementCoupon(?int $workspaceId, ?int $userId, Request $request): void { $key = $this->couponThrottleKey($workspaceId, $userId, $request); $this->limiter->hit($key, self::COUPON_DECAY_SECONDS); } /** * Get seconds until coupon rate limit resets. */ public function couponAvailableIn(?int $workspaceId, ?int $userId, Request $request): int { return $this->limiter->availableIn($this->couponThrottleKey($workspaceId, $userId, $request)); } /** * Generate throttle key for coupon validation. */ protected function couponThrottleKey(?int $workspaceId, ?int $userId, Request $request): string { if ($workspaceId) { return "coupon:workspace:{$workspaceId}"; } if ($userId) { return "coupon:user:{$userId}"; } $ip = $request->ip() ?? 'unknown'; return "coupon:ip:{$ip}"; } /** * Generate throttle key from workspace/user/IP. * * Rate limiting hierarchy: * - Authenticated user with workspace: workspace_id * - Authenticated user without workspace: user_id * - Guest: IP address */ protected function throttleKey(?int $workspaceId, ?int $userId, Request $request): string { if ($workspaceId) { return "checkout:workspace:{$workspaceId}"; } if ($userId) { return "checkout:user:{$userId}"; } $ip = $request->ip() ?? 'unknown'; return "checkout:ip:{$ip}"; } }