From c3fd8415a9ef0e82e4311bf0dce13c3b2975f009 Mon Sep 17 00:00:00 2001
From: Bilal-Elhalawaty <b.elhalawaty@idaratech.sa>
Date: Sun, 28 Dec 2025 10:44:18 +0200
Subject: [PATCH 1/3] feat: add circuit breaker with rate and count strategies

  Prevents cascading failures by automatically blocking requests to
  failing services. Supports per-service configuration with customizable
  thresholds and controlled recovery testing via HALF_OPEN state.

  - Rate strategy: percentage-based failure threshold
  - Count strategy: absolute failure count threshold
  - Redis/Cache storage with sliding time windows
  - Comprehensive documentation and examples
---
 config/integrations.php                       |  56 +-
 docs/CIRCUIT_BREAKER.md                       | 628 ++++++++++++++++++
 src/CircuitBreaker/CircuitBreaker.php         | 180 +++++
 src/CircuitBreaker/CircuitBreakerFactory.php  |  88 +++
 .../CircuitBreakerInterceptor.php             |  77 +++
 .../Config/CircuitBreakerConfig.php           | 184 +++++
 .../Config/CountStrategyConfig.php            |  47 ++
 .../Config/RateStrategyConfig.php             |  66 ++
 .../Contracts/CircuitBreakerStorage.php       |  73 ++
 .../Contracts/StrategyInterface.php           |  51 ++
 src/CircuitBreaker/Enums/CircuitState.php     |  20 +
 .../Exceptions/CircuitOpenException.php       |  35 +
 src/CircuitBreaker/Storage/CacheStorage.php   | 136 ++++
 src/CircuitBreaker/Storage/RedisStorage.php   | 114 ++++
 src/CircuitBreaker/Strategy/CountStrategy.php |  82 +++
 src/CircuitBreaker/Strategy/RateStrategy.php  |  83 +++
 src/Client.php                                | 471 +++++++------
 src/Contracts/IClient.php                     |  25 +-
 src/IntegrationsServiceProvider.php           | 131 +++-
 19 files changed, 2290 insertions(+), 257 deletions(-)
 create mode 100644 docs/CIRCUIT_BREAKER.md
 create mode 100644 src/CircuitBreaker/CircuitBreaker.php
 create mode 100644 src/CircuitBreaker/CircuitBreakerFactory.php
 create mode 100644 src/CircuitBreaker/CircuitBreakerInterceptor.php
 create mode 100644 src/CircuitBreaker/Config/CircuitBreakerConfig.php
 create mode 100644 src/CircuitBreaker/Config/CountStrategyConfig.php
 create mode 100644 src/CircuitBreaker/Config/RateStrategyConfig.php
 create mode 100644 src/CircuitBreaker/Contracts/CircuitBreakerStorage.php
 create mode 100644 src/CircuitBreaker/Contracts/StrategyInterface.php
 create mode 100644 src/CircuitBreaker/Enums/CircuitState.php
 create mode 100644 src/CircuitBreaker/Exceptions/CircuitOpenException.php
 create mode 100644 src/CircuitBreaker/Storage/CacheStorage.php
 create mode 100644 src/CircuitBreaker/Storage/RedisStorage.php
 create mode 100644 src/CircuitBreaker/Strategy/CountStrategy.php
 create mode 100644 src/CircuitBreaker/Strategy/RateStrategy.php

diff --git a/config/integrations.php b/config/integrations.php
index 8cb1fa5..da93fd1 100644
--- a/config/integrations.php
+++ b/config/integrations.php
@@ -1,13 +1,43 @@
-<?php
-return [
-    'base_uri' => env('INTEGRATIONS_BASE_URI', null),
-    'timeout'  => env('INTEGRATIONS_TIMEOUT', null),
-    'retry'    => [
-        'times'    => env('INTEGRATIONS_RETRY_TIMES', 0),
-        'sleep_ms' => env('INTEGRATIONS_RETRY_SLEEP_MS', 0),
-    ],
-    'default_headers' => [],
-    'logging' => [
-        'channel' => env('INTEGRATIONS_LOG_CHANNEL', null),
-    ],
-];
+<?php
+return [
+    'base_uri' => env('INTEGRATIONS_BASE_URI', null),
+    'timeout'  => env('INTEGRATIONS_TIMEOUT', null),
+    'retry'    => [
+        'times'    => env('INTEGRATIONS_RETRY_TIMES', 0),
+        'sleep_ms' => env('INTEGRATIONS_RETRY_SLEEP_MS', 0),
+    ],
+    'default_headers' => [],
+    'logging' => [
+        'channel' => env('INTEGRATIONS_LOG_CHANNEL', null),
+    ],
+
+    /*
+    |--------------------------------------------------------------------------
+    | Circuit Breaker Configuration (Default Settings)
+    |--------------------------------------------------------------------------
+    |
+    | Default circuit breaker settings that services can opt-in to use.
+    |
+    | Per-service control:
+    | 1. Custom config: Override circuitBreakerConfig() method for custom settings
+    | 2. Default settings: Set $circuitBreakerEnabled = true to use these defaults
+    | 3. Disabled: Default behavior (no override needed)
+    |
+    | See: docs/CIRCUIT_BREAKER_PER_SERVICE.md
+    |
+    */
+    'circuit_breaker' => [
+        'storage' => env('INTEGRATIONS_CIRCUIT_BREAKER_STORAGE', 'cache'),
+        'cache_store' => env('INTEGRATIONS_CIRCUIT_BREAKER_CACHE_STORE', null),
+        'prefix' => env('INTEGRATIONS_CIRCUIT_BREAKER_PREFIX', 'circuit_breaker'),
+        'strategy' => env('INTEGRATIONS_CIRCUIT_BREAKER_STRATEGY', 'rate'),
+        'time_window' => env('INTEGRATIONS_CIRCUIT_BREAKER_TIME_WINDOW', 60),
+        'failure_rate_threshold' => env('INTEGRATIONS_CIRCUIT_BREAKER_FAILURE_RATE_THRESHOLD', 50),
+        'failure_count_threshold' => env('INTEGRATIONS_CIRCUIT_BREAKER_FAILURE_COUNT_THRESHOLD', 5),
+        'minimum_requests' => env('INTEGRATIONS_CIRCUIT_BREAKER_MINIMUM_REQUESTS', 10),
+        'interval_to_half_open' => env('INTEGRATIONS_CIRCUIT_BREAKER_INTERVAL_TO_HALF_OPEN', 30),
+        'success_threshold' => env('INTEGRATIONS_CIRCUIT_BREAKER_SUCCESS_THRESHOLD', 3),
+        'failure_status_codes' => [500, 502, 503, 504],
+        'ignored_status_codes' => [],
+    ],
+];
diff --git a/docs/CIRCUIT_BREAKER.md b/docs/CIRCUIT_BREAKER.md
new file mode 100644
index 0000000..9e4dff8
--- /dev/null
+++ b/docs/CIRCUIT_BREAKER.md
@@ -0,0 +1,628 @@
+# Circuit Breaker
+
+## What Is It?
+
+Protects your application from cascading failures when external services become unreliable. Automatically stops requests to failing services and gives them time to recover.
+
+**What "Trip" Means:**
+- **Trip** = Circuit transitions from CLOSED (normal) to OPEN (blocking requests)
+- When circuit "trips", it opens and blocks all requests to protect your system
+- After a cooldown period, it tests if the service recovered
+
+---
+
+## Three Circuit States
+
+```
+CLOSED 🟢 (Normal - all requests allowed)
+    ↓
+    Too many failures detected
+    ↓
+OPEN 🔴 (Service down - block ALL requests)
+    ↓
+    Wait for cooldown period (e.g., 30s)
+    ↓
+HALF_OPEN ⚠️ (Testing - allow FEW requests to test recovery)
+    ↓
+    If 3 successes → CLOSED 🟢 (recovered!)
+    If 1 failure   → OPEN 🔴 (still broken, wait longer)
+```
+
+### Why HALF_OPEN is Needed
+
+Without HALF_OPEN, you'd have two bad options:
+- ❌ **Stay OPEN forever** → Never recover, block healthy services
+- ❌ **Auto-CLOSE immediately** → Flood failing service with thousands of requests
+
+**HALF_OPEN = Controlled Testing**
+- After cooldown (e.g., 30s), test with just a FEW requests (e.g., 3)
+- If those succeed → Service is healthy → Fully reopen
+- If any fail → Service still broken → Wait longer
+
+---
+
+## How to Use
+
+### Option 1: Custom Configuration Per Service
+
+```php
+use Idaratech\Integrations\Client;
+use Idaratech\Integrations\CircuitBreaker\Config\RateStrategyConfig;
+
+class PaymentClient extends Client
+{
+    protected function circuitBreakerConfig(): ?CircuitBreakerConfig
+    {
+        return RateStrategyConfig::make()
+            ->failureRateThreshold(40.0)   // Trip at 40% failure rate
+            ->minimumRequests(10)           // Need 10 requests minimum
+            ->timeWindow(60)                // Track failures in last 60s
+            ->intervalToHalfOpen(30)        // Wait 30s before testing recovery
+            ->successThreshold(3)           // Need 3 successes to close
+            ->storage('redis');
+    }
+}
+```
+
+### Option 2: Count Strategy (Absolute Failures)
+
+```php
+use Idaratech\Integrations\CircuitBreaker\Config\CountStrategyConfig;
+
+class EmailClient extends Client
+{
+    protected function circuitBreakerConfig(): ?CircuitBreakerConfig
+    {
+        return CountStrategyConfig::make()
+            ->failureCountThreshold(5)      // Trip after 5 failures
+            ->intervalToHalfOpen(120)       // Wait 2 minutes
+            ->storage('redis');
+    }
+}
+```
+
+---
+
+## Strategies Explained
+
+### Rate Strategy (Percentage-Based)
+
+Trips when **failure percentage** exceeds threshold.
+
+**Formula:**
+```
+failureRate = (failures / totalRequests) × 100
+
+if totalRequests >= minimumRequests AND failureRate >= threshold:
+    TRIP → OPEN
+```
+
+**Example:**
+```php
+RateStrategyConfig::make()
+    ->failureRateThreshold(50.0)    // 50% threshold
+    ->minimumRequests(10);           // Need at least 10 requests
+```
+
+**Scenarios:**
+```
+Scenario 1: Not enough data
+Total: 8 requests, 5 failures (62.5% failure rate)
+Result: DON'T TRIP (8 < 10 minimum requests)
+
+Scenario 2: Below threshold
+Total: 20 requests, 8 failures (40% failure rate)
+Result: DON'T TRIP (40% < 50%)
+
+Scenario 3: Exceeds threshold - TRIP!
+Total: 20 requests, 11 failures (55% failure rate)
+Result: TRIP! (55% >= 50% AND 20 >= 10)
+```
+
+**Best for:** High-traffic services, external APIs
+
+---
+
+### Count Strategy (Absolute Count)
+
+Trips when **absolute failure count** exceeds threshold.
+
+**Formula:**
+```
+if failures >= threshold:
+    TRIP → OPEN
+```
+
+**Example:**
+```php
+CountStrategyConfig::make()
+    ->failureCountThreshold(5);     // Trip after 5 failures
+```
+
+**Scenarios:**
+```
+Scenario 1: Below threshold
+Failures: 3, Successes: 100
+Result: DON'T TRIP (3 < 5)
+
+Scenario 2: Reaches threshold - TRIP!
+Failures: 5, Successes: 2
+Result: TRIP! (5 >= 5)
+```
+
+**Best for:** Critical services (payments), low-traffic services, strict failure policies
+
+---
+
+### Strategy Comparison
+
+| Aspect | Rate Strategy | Count Strategy |
+|--------|--------------|----------------|
+| **Trips based on** | Percentage (failures/total) | Absolute number |
+| **Requires minimum requests** | Yes | No |
+| **Formula** | `(failures/total)×100 >= 50%` | `failures >= 5` |
+| **Best for** | High traffic, variable load | Critical systems, low traffic |
+| **Example** | 11 failures out of 20 = 55% | 5 failures (regardless of total) |
+
+---
+
+## Configuration Parameters Explained
+
+### 1. `timeWindow` - Memory Span (ALWAYS Active)
+
+**What it does:** Defines how long to "remember" failures and successes
+
+**Always running:** Tracks in ALL states (CLOSED, OPEN, HALF_OPEN)
+
+**Example:**
+```php
+->timeWindow(60)  // Track failures in last 60 seconds
+```
+
+**How it works:**
+```
+Current time: 10:01:00
+
+Failures in storage:
+09:59:50 - Failure ❌ EXPIRED (outside 60s window)
+10:00:05 - Failure ✓ COUNTED (within 60s)
+10:00:30 - Failure ✓ COUNTED (within 60s)
+10:00:50 - Failure ✓ COUNTED (within 60s)
+
+Only count: 3 failures (last 60 seconds)
+Old failures automatically expire!
+```
+
+**Purpose:**
+- Prevents old failures from affecting current decisions
+- Keeps circuit breaker responsive to current service health
+- Creates a "sliding window" that moves with time
+
+---
+
+### 2. `intervalToHalfOpen` - Recovery Cooldown (Only When OPEN)
+
+**What it does:** How long to wait after circuit opens before testing recovery
+
+**Only active:** When circuit is OPEN
+
+**Example:**
+```php
+->intervalToHalfOpen(30)  // Wait 30 seconds before testing
+```
+
+**How it works:**
+```
+10:00:00 - Circuit OPENS
+           Start timer from HERE
+
+10:00:10 - Request arrives
+           10s < 30s → BLOCKED ⛔
+
+10:00:30 - Request arrives
+           30s >= 30s → Transition to HALF_OPEN ✓
+           Request ALLOWED (testing recovery)
+```
+
+**Purpose:**
+- Gives failing service time to recover
+- Prevents immediate retry attempts
+
+---
+
+### 3. `successThreshold` - Recovery Test (Only in HALF_OPEN)
+
+**What it does:** How many CONSECUTIVE successes needed to close circuit
+
+**Only active:** When circuit is HALF_OPEN
+
+**NOT tracked in timeWindow!** Separate counter, resets on any failure.
+
+**Example:**
+```php
+->successThreshold(3)  // Need 3 consecutive successes
+```
+
+**How it works:**
+```
+State: HALF_OPEN
+
+Request 1: 200 ✓ → successCount = 1
+Request 2: 200 ✓ → successCount = 2
+Request 3: 200 ✓ → successCount = 3
+→ Circuit CLOSES! 🟢
+
+Alternative (failure during test):
+Request 1: 200 ✓ → successCount = 1
+Request 2: 500 ✗ → FAILURE!
+→ Circuit goes back to OPEN 🔴
+→ Reset counter to 0
+→ Wait another 30s before retrying
+```
+
+**Purpose:**
+- Ensures service is truly recovered (not just one lucky success)
+- Prevents flapping (OPEN → CLOSED → OPEN)
+
+---
+
+### Visual Summary of Parameters
+
+```
+timeWindow(60) - ALWAYS SLIDING
+├─────────────────────────────────────────────────────┤
+09:59:50    10:00:00    10:00:30    10:01:00
+   ❌          ✓           ✓         NOW
+expired    counted     counted
+└──────────── Only count last 60s ────────────┘
+
+
+intervalToHalfOpen(30) - ONLY WHEN OPEN
+Circuit OPENS ├─────────────┤ Test recovery
+         10:00:00      10:00:30
+                          ↓
+                    HALF_OPEN
+
+
+successThreshold(3) - ONLY IN HALF_OPEN
+HALF_OPEN: ✓ ✓ ✓ → CLOSED
+HALF_OPEN: ✓ ✗   → Back to OPEN
+           └─ Reset counter
+```
+
+---
+
+## Complete Lifecycle Example
+
+**Configuration:**
+```php
+RateStrategyConfig::make()
+    ->failureRateThreshold(50.0)   // 50% failures
+    ->minimumRequests(10)
+    ->timeWindow(60)                // Track last 60s
+    ->intervalToHalfOpen(30)        // Wait 30s before testing
+    ->successThreshold(3);          // Need 3 successes
+```
+
+**Timeline:**
+
+```
+State: CLOSED 🟢
+════════════════════════════════════════════════════════
+10:00:00-10:00:40 - 9 requests (4 success, 5 failures)
+                    9 < 10 minimum → Don't trip yet
+
+10:00:45 - Request 10: 500 ✗
+           Total: 10 requests (4 success, 6 failures)
+           Failure rate: (6/10) × 100 = 60%
+           60% >= 50% threshold → TRIP! 🔴
+
+State: OPEN 🔴 (openedAt = 10:00:45)
+════════════════════════════════════════════════════════
+10:00:50 - Request arrives
+           Time since opened: 5s < 30s
+           → BLOCKED ⛔ (throw CircuitOpenException)
+
+10:01:00 - Request arrives
+           15s < 30s → BLOCKED ⛔
+
+10:01:15 - Request arrives (30s passed!)
+           30s >= 30s → Transition to HALF_OPEN ⚠️
+           → Request ALLOWED (testing)
+
+State: HALF_OPEN ⚠️ (testing recovery)
+════════════════════════════════════════════════════════
+10:01:15 - Request 1: 200 ✓ (successCount = 1/3)
+10:01:20 - Request 2: 200 ✓ (successCount = 2/3)
+10:01:25 - Request 3: 200 ✓ (successCount = 3/3)
+           3 >= 3 → Circuit CLOSES! 🟢
+
+State: CLOSED 🟢 (service recovered!)
+════════════════════════════════════════════════════════
+10:01:30 - Back to normal operation
+           Start tracking new failures in timeWindow
+```
+
+---
+
+## Storage
+
+Circuit state is stored per service using Redis or Cache.
+
+**Key Structure:**
+```
+circuit_breaker:{service}:state          → "closed", "open", "half_open"
+circuit_breaker:{service}:failures       → 3
+circuit_breaker:{service}:successes      → 15
+circuit_breaker:{service}:opened_at      → 1703520000
+circuit_breaker:{service}:half_open_successes → 2
+```
+
+**Multiple Services:**
+```
+circuit_breaker:api.stripe.com:state     → "open"       (down)
+circuit_breaker:api.twilio.com:state     → "closed"     (working)
+circuit_breaker:payment.internal:state   → "half_open"  (testing)
+```
+
+---
+
+## FAQ - Common Questions
+
+### Q1: What does "trip" mean?
+
+**Trip** means the circuit breaker transitions from CLOSED to OPEN state.
+- Circuit "trips" when too many failures detected
+- Once tripped, circuit is OPEN and blocks all requests
+- Protects your system from cascading failures
+
+---
+
+### Q2: Why do we need HALF_OPEN state? Can't we just go CLOSED → OPEN → CLOSED?
+
+**Without HALF_OPEN you'd have problems:**
+
+❌ **Option A:** Circuit stays OPEN forever
+- Service recovers but circuit never reopens
+- You're blocking requests to a healthy service
+
+❌ **Option B:** Circuit auto-closes after timeout
+- Thousands of requests flood the still-broken service
+- Circuit trips again immediately
+- Creates flapping: OPEN → CLOSED → OPEN → CLOSED...
+
+✅ **HALF_OPEN solves this:**
+- Test with just a FEW requests (e.g., 3)
+- If they succeed → Service is healthy → Fully reopen
+- If any fail → Service still broken → Wait longer
+- Prevents flooding, enables controlled recovery
+
+---
+
+### Q3: What's the difference between `timeWindow` and `intervalToHalfOpen`?
+
+**Completely different purposes:**
+
+**`timeWindow(60)`** = Memory span (ALWAYS active)
+- How long to track failures/successes
+- Creates a sliding 60-second window
+- Old failures automatically expire
+- Used in ALL states (CLOSED, OPEN, HALF_OPEN)
+- Example: "Only count failures from last 60 seconds"
+
+**`intervalToHalfOpen(30)`** = Recovery cooldown (only when OPEN)
+- How long to wait after circuit opens before testing
+- Only used when circuit is OPEN
+- Measured from when circuit opened (openedAt timestamp)
+- Example: "Wait 30 seconds before trying recovery"
+
+**Think of it:**
+- `timeWindow` = How far back you look when counting
+- `intervalToHalfOpen` = How long you wait before testing recovery
+
+---
+
+### Q4: Is `successThreshold(3)` tracked within the `timeWindow(60)`?
+
+**NO!** There are TWO types of success tracking:
+
+**1. Regular successes (CLOSED state)** - YES, tracked in timeWindow
+```
+Used for calculating failure rate in Rate Strategy
+Example: 10 requests = 6 failures + 4 successes (within 60s)
+These successes ARE in timeWindow
+```
+
+**2. Half-open successes (HALF_OPEN state)** - NO, NOT in timeWindow
+```
+Separate counter: halfOpenSuccessCount
+Used ONLY to decide when to close circuit
+NOT subject to timeWindow expiration
+Resets to 0 on any failure
+These successes are NOT in timeWindow
+```
+
+**Example:**
+```
+State: HALF_OPEN
+10:01:00 - Success ✓ (counter = 1)
+10:01:05 - Success ✓ (counter = 2)
+10:01:10 - Success ✓ (counter = 3) → CLOSE!
+
+Only took 10 seconds (not 60 seconds)
+These are consecutive, immediate successes
+No timeWindow involved here!
+```
+
+---
+
+### Q5: When does the `intervalToHalfOpen` timer start?
+
+**It starts the moment the circuit trips to OPEN.**
+
+When circuit opens, we store `openedAt` timestamp. The timer counts from there.
+
+**Example:**
+```php
+->intervalToHalfOpen(30)  // 30 seconds
+```
+
+```
+10:00:00 - Circuit trips to OPEN
+           openedAt = 10:00:00 ← Timer starts HERE
+
+10:00:10 - Request arrives
+           (10:00:10 - 10:00:00) = 10s < 30s
+           → Still in cooldown, BLOCKED
+
+10:00:30 - Request arrives
+           (10:00:30 - 10:00:00) = 30s >= 30s
+           → Cooldown finished, transition to HALF_OPEN
+```
+
+---
+
+### Q6: Can I use different strategies for different services?
+
+**Yes!** Each client can have its own configuration:
+
+```php
+class PaymentClient extends Client
+{
+    protected function circuitBreakerConfig(): ?CircuitBreakerConfig
+    {
+        // Strict - trip after just 3 failures
+        return CountStrategyConfig::make()
+            ->failureCountThreshold(3)
+            ->intervalToHalfOpen(120);  // 2 min cooldown
+    }
+}
+
+class EmailClient extends Client
+{
+    protected function circuitBreakerConfig(): ?CircuitBreakerConfig
+    {
+        // Flexible - trip at 50% failure rate
+        return RateStrategyConfig::make()
+            ->failureRateThreshold(50.0)
+            ->minimumRequests(10);
+    }
+}
+```
+
+---
+
+### Q7: How do failures expire from the timeWindow?
+
+**Automatically, based on TTL (Time To Live).**
+
+When a failure is recorded, it's stored with expiration = `timeWindow` seconds.
+
+**Example:**
+```php
+->timeWindow(60)  // 60 seconds
+```
+
+```
+10:00:00 - Failure recorded
+           Stored in Redis/Cache with TTL = 60s
+           Will expire at 10:01:00
+
+10:00:30 - Check failure count
+           Still counted (30s < 60s TTL)
+
+10:01:00 - Failure expires automatically
+           No longer counted
+
+10:01:30 - Check failure count
+           This failure is gone (expired)
+```
+
+Redis/Cache automatically removes expired keys. You don't need to manually clean up.
+
+---
+
+### Q8: What happens if service fails during HALF_OPEN testing?
+
+**Circuit immediately trips back to OPEN.**
+
+```
+State: HALF_OPEN
+Request 1: 200 ✓ (successCount = 1)
+Request 2: 200 ✓ (successCount = 2)
+Request 3: 500 ✗ FAILURE!
+
+Action:
+→ Circuit goes back to OPEN 🔴
+→ Reset successCount to 0
+→ Set NEW openedAt timestamp
+→ Must wait another intervalToHalfOpen (e.g., 30s)
+→ Then try recovery again
+```
+
+**This prevents:**
+- Premature recovery (service not fully healthy)
+- Flapping between states
+- Overwhelming fragile services
+
+---
+
+### Q9: How do I monitor circuit breaker state?
+
+**Check storage directly or add logging:**
+
+```php
+// Check state in Redis/Cache
+$state = Cache::get('circuit_breaker:api.stripe.com:state');
+// Returns: "closed", "open", or "half_open"
+
+$failures = Cache::get('circuit_breaker:api.stripe.com:failures');
+```
+
+Or add logging in your client:
+
+```php
+try {
+    $response = $client->do($request);
+} catch (CircuitOpenException $e) {
+    \Log::warning('Circuit open for service', [
+        'service' => $e->getService(),
+        'opened_at' => Cache::get("circuit_breaker:{$e->getService()}:opened_at"),
+    ]);
+
+    return ['error' => 'Service temporarily unavailable'];
+}
+```
+
+---
+
+### Q10: What status codes are considered failures?
+
+**By default: 5xx errors (500, 502, 503, 504)**
+
+You can customize:
+
+```php
+RateStrategyConfig::make()
+    ->failureStatusCodes([500, 502, 503, 504, 429]);  // Add 429 Too Many Requests
+```
+
+**Success = 2xx and 3xx**
+**Failure = 4xx and 5xx (or custom list)**
+
+Note: Network errors and exceptions are always considered failures.
+
+---
+
+## Best Practices
+
+1. **Use Rate Strategy for high-traffic services** (external APIs)
+2. **Use Count Strategy for critical low-traffic services** (payments)
+3. **Set appropriate timeWindow** - Too short = premature trips, too long = slow recovery
+4. **Don't set intervalToHalfOpen too low** - Give service time to truly recover
+5. **Use Redis for production** - Better performance than cache
+6. **Monitor circuit state** - Log when circuits open/close
+7. **Handle CircuitOpenException gracefully** - Return user-friendly errors
+8. **Test your thresholds** - Start conservative, adjust based on metrics
+
+---
diff --git a/src/CircuitBreaker/CircuitBreaker.php b/src/CircuitBreaker/CircuitBreaker.php
new file mode 100644
index 0000000..cef4064
--- /dev/null
+++ b/src/CircuitBreaker/CircuitBreaker.php
@@ -0,0 +1,180 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker;
+
+use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
+use Idaratech\Integrations\CircuitBreaker\Contracts\StrategyInterface;
+use Idaratech\Integrations\CircuitBreaker\Enums\CircuitState;
+use Idaratech\Integrations\CircuitBreaker\Exceptions\CircuitOpenException;
+use Idaratech\Integrations\Logger;
+use Throwable;
+
+class CircuitBreaker
+{
+    /** @var int[] */
+    protected array $failureStatusCodes = [500, 502, 503, 504];
+
+    /** @var int[] */
+    protected array $ignoredStatusCodes = [];
+
+    public function __construct(
+        protected readonly CircuitBreakerStorage $storage,
+        protected readonly StrategyInterface $strategy
+    ) {}
+
+    public function isAvailable(string $service): bool
+    {
+        $state = $this->getState($service);
+
+        if ($state->isClosed() || $state->isHalfOpen()) {
+            return true;
+        }
+
+        if ($this->strategy->shouldAttemptReset($service, $this->storage)) {
+            $this->transitionTo($service, CircuitState::HALF_OPEN);
+            return true;
+        }
+
+        return false;
+    }
+
+    public function getState(string $service): CircuitState
+    {
+        return $this->storage->getState($service);
+    }
+
+    /**
+     * @template T
+     * @param callable(): T $callable
+     * @param callable(): T|null $fallback
+     * @return T
+     * @throws CircuitOpenException|Throwable
+     */
+    public function call(string $service, callable $callable, ?callable $fallback = null): mixed
+    {
+        if (! $this->isAvailable($service)) {
+            return $fallback
+                ? $fallback()
+                : throw new CircuitOpenException($service, CircuitState::OPEN);
+        }
+
+        try {
+            $result = $callable();
+            $this->success($service);
+            return $result;
+        } catch (Throwable $e) {
+            $this->failure($service);
+            throw $e;
+        }
+    }
+
+    public function success(string $service): void
+    {
+        $state = $this->getState($service);
+
+        if ($state->isHalfOpen()) {
+            $this->storage->incrementHalfOpenSuccess($service);
+
+            if ($this->strategy->shouldClose($service, $this->storage)) {
+                $this->transitionTo($service, CircuitState::CLOSED);
+            }
+            return;
+        }
+
+        $this->strategy->recordSuccess($service, $this->storage);
+    }
+
+    public function failure(string $service): void
+    {
+        $state = $this->getState($service);
+
+        if ($state->isHalfOpen()) {
+            $this->transitionTo($service, CircuitState::OPEN);
+            return;
+        }
+
+        $this->strategy->recordFailure($service, $this->storage);
+
+        if ($this->strategy->shouldTrip($service, $this->storage)) {
+            $this->transitionTo($service, CircuitState::OPEN);
+        }
+    }
+
+    public function recordHttpResult(string $service, int $statusCode): void
+    {
+        $this->isFailureStatusCode($statusCode)
+            ? $this->failure($service)
+            : $this->success($service);
+    }
+
+    public function isFailureStatusCode(int $statusCode): bool
+    {
+        if (in_array($statusCode, $this->ignoredStatusCodes, true)) {
+            return false;
+        }
+
+        if (! empty($this->failureStatusCodes)) {
+            return in_array($statusCode, $this->failureStatusCodes, true);
+        }
+
+        return $statusCode >= 500;
+    }
+
+    /** @param int[] $statusCodes */
+    public function setFailureStatusCodes(array $statusCodes): self
+    {
+        $this->failureStatusCodes = $statusCodes;
+        return $this;
+    }
+
+    /** @param int[] $statusCodes */
+    public function setIgnoredStatusCodes(array $statusCodes): self
+    {
+        $this->ignoredStatusCodes = $statusCodes;
+        return $this;
+    }
+
+    public function getStorage(): CircuitBreakerStorage
+    {
+        return $this->storage;
+    }
+
+    public function getStrategy(): StrategyInterface
+    {
+        return $this->strategy;
+    }
+
+    protected function transitionTo(string $service, CircuitState $newState): void
+    {
+        if ($this->getState($service) === $newState) {
+            return;
+        }
+
+        $this->storage->setState($service, $newState);
+
+        match ($newState) {
+            CircuitState::OPEN => $this->onOpen($service),
+            CircuitState::HALF_OPEN => $this->onHalfOpen($service),
+            CircuitState::CLOSED => $this->onClosed($service),
+        };
+    }
+
+    protected function onOpen(string $service): void
+    {
+        $this->storage->setOpenedAt($service, time());
+        $this->storage->resetHalfOpenSuccess($service);
+        Logger::warning('CIRCUIT_BREAKER_TRIPPED', ['service' => $service]);
+    }
+
+    protected function onHalfOpen(string $service): void
+    {
+        $this->storage->resetHalfOpenSuccess($service);
+        Logger::info('CIRCUIT_BREAKER_HALF_OPEN', ['service' => $service]);
+    }
+
+    protected function onClosed(string $service): void
+    {
+        $this->storage->reset($service);
+        Logger::info('CIRCUIT_BREAKER_CLOSED', ['service' => $service]);
+    }
+}
\ No newline at end of file
diff --git a/src/CircuitBreaker/CircuitBreakerFactory.php b/src/CircuitBreaker/CircuitBreakerFactory.php
new file mode 100644
index 0000000..19ca4c5
--- /dev/null
+++ b/src/CircuitBreaker/CircuitBreakerFactory.php
@@ -0,0 +1,88 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker;
+
+use Idaratech\Integrations\CircuitBreaker\Config\CircuitBreakerConfig;
+use Idaratech\Integrations\CircuitBreaker\Config\CountStrategyConfig;
+use Idaratech\Integrations\CircuitBreaker\Config\RateStrategyConfig;
+use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
+use Idaratech\Integrations\CircuitBreaker\Contracts\StrategyInterface;
+use Idaratech\Integrations\CircuitBreaker\Storage\CacheStorage;
+use Idaratech\Integrations\CircuitBreaker\Storage\RedisStorage;
+use Idaratech\Integrations\CircuitBreaker\Strategy\CountStrategy;
+use Idaratech\Integrations\CircuitBreaker\Strategy\RateStrategy;
+
+/**
+ * Factory for creating circuit breaker instances from configuration objects.
+ */
+class CircuitBreakerFactory
+{
+    /**
+     * Create a circuit breaker from a configuration object.
+     *
+     * @param CircuitBreakerConfig $config Configuration object
+     * @return CircuitBreaker
+     */
+    public function createFromConfig(CircuitBreakerConfig $config): CircuitBreaker
+    {
+        $storage = $this->createStorageFromConfig($config);
+        $strategy = $this->createStrategyFromConfig($config);
+
+        $circuitBreaker = new CircuitBreaker($storage, $strategy);
+
+        // Set optional configurations
+        $circuitBreaker->setFailureStatusCodes($config->getFailureStatusCodes());
+        $circuitBreaker->setIgnoredStatusCodes($config->getIgnoredStatusCodes());
+
+        return $circuitBreaker;
+    }
+
+    /**
+     * Create storage adapter from configuration.
+     *
+     * @param CircuitBreakerConfig $config
+     * @return CircuitBreakerStorage
+     */
+    protected function createStorageFromConfig(CircuitBreakerConfig $config): CircuitBreakerStorage
+    {
+        $storageType = $config->getStorage();
+        $prefix = $config->getPrefix() ?? config('integrations.circuit_breaker.prefix', 'circuit_breaker');
+
+        if ($storageType === 'redis') {
+            return new RedisStorage($prefix, $config->getRedisConnection());
+        }
+
+        return new CacheStorage($prefix, $config->getCacheStore());
+    }
+
+    /**
+     * Create strategy from configuration.
+     *
+     * @param CircuitBreakerConfig $config
+     * @return StrategyInterface
+     * @throws \InvalidArgumentException
+     */
+    protected function createStrategyFromConfig(CircuitBreakerConfig $config): StrategyInterface
+    {
+        if ($config instanceof CountStrategyConfig) {
+            return new CountStrategy(
+                timeWindow: $config->getTimeWindow(),
+                failureCountThreshold: $config->getFailureCountThreshold(),
+                intervalToHalfOpen: $config->getIntervalToHalfOpen(),
+                successThreshold: $config->getSuccessThreshold()
+            );
+        }
+
+        if ($config instanceof RateStrategyConfig) {
+            return new RateStrategy(
+                timeWindow: $config->getTimeWindow(),
+                failureRateThreshold: $config->getFailureRateThreshold(),
+                minimumRequests: $config->getMinimumRequests(),
+                intervalToHalfOpen: $config->getIntervalToHalfOpen(),
+                successThreshold: $config->getSuccessThreshold()
+            );
+        }
+
+        throw new \InvalidArgumentException('Unknown circuit breaker config type: ' . get_class($config));
+    }
+}
diff --git a/src/CircuitBreaker/CircuitBreakerInterceptor.php b/src/CircuitBreaker/CircuitBreakerInterceptor.php
new file mode 100644
index 0000000..e730f86
--- /dev/null
+++ b/src/CircuitBreaker/CircuitBreakerInterceptor.php
@@ -0,0 +1,77 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker;
+
+use Idaratech\Integrations\CircuitBreaker\Exceptions\CircuitOpenException;
+use Idaratech\Integrations\Contracts\IRequest;
+use Idaratech\Integrations\Contracts\IResponse;
+
+class CircuitBreakerInterceptor
+{
+    protected CircuitBreaker $circuitBreaker;
+
+    public function __construct(CircuitBreaker $circuitBreaker)
+    {
+        $this->circuitBreaker = $circuitBreaker;
+    }
+
+
+    /**
+     * Check if the circuit allows the request (before middleware).
+     *
+     * @throws CircuitOpenException
+     */
+    public function before(IRequest $request): void
+    {
+        $service = $this->resolveServiceName($request);
+
+        if (!$this->circuitBreaker->isAvailable($service)) {
+            throw new CircuitOpenException(
+                $service,
+                $this->circuitBreaker->getState($service)
+            );
+        }
+    }
+
+    /**
+     * Record the result after the request completes (after middleware).
+     */
+    public function after(IRequest $request, IResponse $response): void
+    {
+        $service = $this->resolveServiceName($request);
+        $statusCode = $response->statusCode();
+
+        $this->circuitBreaker->recordHttpResult($service, $statusCode);
+    }
+
+    /**
+     * Record a failure when an exception occurs.
+     */
+    public function onException(IRequest $request, \Throwable $exception): void
+    {
+        $service = $this->resolveServiceName($request);
+        $this->circuitBreaker->failure($service);
+    }
+
+    /**
+     * Resolve the service name from the request.
+     */
+    protected function resolveServiceName(IRequest $request): string
+    {
+        $uri = $request->fullUri();
+
+        if (empty($uri)) {
+            $uri = $request->baseUrl() . $request->uri();
+        }
+
+        $parsed = parse_url($uri);
+
+        if (isset($parsed['host'])) {
+            return $parsed['host'];
+        }
+
+        // Fallback to base URL or URI
+        return $request->baseUrl() ?: $request->uri();
+    }
+
+}
diff --git a/src/CircuitBreaker/Config/CircuitBreakerConfig.php b/src/CircuitBreaker/Config/CircuitBreakerConfig.php
new file mode 100644
index 0000000..2f7bc04
--- /dev/null
+++ b/src/CircuitBreaker/Config/CircuitBreakerConfig.php
@@ -0,0 +1,184 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Config;
+
+/**
+ * Abstract base class for circuit breaker configuration.
+ *
+ * Provides shared configuration properties and methods that are common
+ * to all circuit breaker strategies.
+ */
+abstract class CircuitBreakerConfig
+{
+    protected int $timeWindow = 60;
+    protected int $intervalToHalfOpen = 30;
+    protected int $successThreshold = 3;
+    protected string $storage = 'cache';
+    protected ?string $prefix = null;
+    protected ?string $redisConnection = null;
+    protected ?string $cacheStore = null;
+    protected array $failureStatusCodes = [500, 502, 503, 504];
+    protected array $ignoredStatusCodes = [];
+
+    /**
+     * Set the time window in seconds to track requests/failures.
+     *
+     * @param int $seconds Time window in seconds
+     * @return static
+     */
+    public function timeWindow(int $seconds): static
+    {
+        $this->timeWindow = $seconds;
+        return $this;
+    }
+
+    /**
+     * Set the interval in seconds before attempting recovery (OPEN → HALF_OPEN).
+     *
+     * @param int $seconds Interval in seconds
+     * @return static
+     */
+    public function intervalToHalfOpen(int $seconds): static
+    {
+        $this->intervalToHalfOpen = $seconds;
+        return $this;
+    }
+
+    /**
+     * Set the number of consecutive successes needed to close the circuit.
+     *
+     * @param int $count Number of successes
+     * @return static
+     */
+    public function successThreshold(int $count): static
+    {
+        $this->successThreshold = $count;
+        return $this;
+    }
+
+    /**
+     * Set the storage adapter type ('redis' or 'cache').
+     *
+     * @param string $type Storage type
+     * @return static
+     */
+    public function storage(string $type): static
+    {
+        $this->storage = $type;
+        return $this;
+    }
+
+    /**
+     * Set the cache key prefix for circuit breaker state.
+     *
+     * @param string $prefix Cache key prefix
+     * @return static
+     */
+    public function prefix(string $prefix): static
+    {
+        $this->prefix = $prefix;
+        return $this;
+    }
+
+    /**
+     * Set the Redis connection name (only for Redis storage).
+     *
+     * @param string|null $connection Redis connection name
+     * @return static
+     */
+    public function redisConnection(?string $connection): static
+    {
+        $this->redisConnection = $connection;
+        return $this;
+    }
+
+    /**
+     * Set the cache store name (only for cache storage).
+     *
+     * @param string|null $store Cache store name
+     * @return static
+     */
+    public function cacheStore(?string $store): static
+    {
+        $this->cacheStore = $store;
+        return $this;
+    }
+
+    /**
+     * Set the HTTP status codes that are considered failures.
+     *
+     * @param array $codes Array of HTTP status codes
+     * @return static
+     */
+    public function failureStatusCodes(array $codes): static
+    {
+        $this->failureStatusCodes = $codes;
+        return $this;
+    }
+
+    /**
+     * Set the HTTP status codes that should be ignored by the circuit breaker.
+     *
+     * @param array $codes Array of HTTP status codes
+     * @return static
+     */
+    public function ignoredStatusCodes(array $codes): static
+    {
+        $this->ignoredStatusCodes = $codes;
+        return $this;
+    }
+
+    // Getters
+
+    public function getTimeWindow(): int
+    {
+        return $this->timeWindow;
+    }
+
+    public function getIntervalToHalfOpen(): int
+    {
+        return $this->intervalToHalfOpen;
+    }
+
+    public function getSuccessThreshold(): int
+    {
+        return $this->successThreshold;
+    }
+
+    public function getStorage(): string
+    {
+        return $this->storage;
+    }
+
+    public function getPrefix(): ?string
+    {
+        return $this->prefix;
+    }
+
+    public function getRedisConnection(): ?string
+    {
+        return $this->redisConnection;
+    }
+
+    public function getCacheStore(): ?string
+    {
+        return $this->cacheStore;
+    }
+
+    public function getFailureStatusCodes(): array
+    {
+        return $this->failureStatusCodes;
+    }
+
+    public function getIgnoredStatusCodes(): array
+    {
+        return $this->ignoredStatusCodes;
+    }
+
+    /**
+     * Get the strategy type ('rate' or 'count').
+     *
+     * @return string
+     */
+    abstract public function getStrategy(): string;
+}
diff --git a/src/CircuitBreaker/Config/CountStrategyConfig.php b/src/CircuitBreaker/Config/CountStrategyConfig.php
new file mode 100644
index 0000000..bfd96f9
--- /dev/null
+++ b/src/CircuitBreaker/Config/CountStrategyConfig.php
@@ -0,0 +1,47 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Config;
+
+/**
+ * Configuration for count-based circuit breaker strategy.
+ *
+ * Trips the circuit when the absolute failure count exceeds a threshold
+ * within a specified time window.
+ */
+class CountStrategyConfig extends CircuitBreakerConfig
+{
+    protected int $failureCountThreshold = 5;
+
+    /**
+     * Create a new count strategy configuration instance.
+     *
+     * @return self
+     */
+    public static function make(): self
+    {
+        return new self();
+    }
+
+    /**
+     * Set the failure count threshold.
+     * Circuit trips when this many failures occur within the time window.
+     *
+     * @param int $count Number of failures to trip circuit
+     * @return self
+     */
+    public function failureCountThreshold(int $count): self
+    {
+        $this->failureCountThreshold = $count;
+        return $this;
+    }
+
+    public function getFailureCountThreshold(): int
+    {
+        return $this->failureCountThreshold;
+    }
+
+    public function getStrategy(): string
+    {
+        return 'count';
+    }
+}
diff --git a/src/CircuitBreaker/Config/RateStrategyConfig.php b/src/CircuitBreaker/Config/RateStrategyConfig.php
new file mode 100644
index 0000000..b43c41f
--- /dev/null
+++ b/src/CircuitBreaker/Config/RateStrategyConfig.php
@@ -0,0 +1,66 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Config;
+
+/**
+ * Configuration for rate-based circuit breaker strategy.
+ *
+ * Trips the circuit when the failure rate (percentage) exceeds a threshold
+ * within a specified time window.
+ */
+class RateStrategyConfig extends CircuitBreakerConfig
+{
+    protected float $failureRateThreshold = 50.0;
+    protected int $minimumRequests = 10;
+
+    /**
+     * Create a new rate strategy configuration instance.
+     *
+     * @return self
+     */
+    public static function make(): self
+    {
+        return new self();
+    }
+
+    /**
+     * Set the failure rate threshold percentage (0-100).
+     * Circuit trips when failure rate exceeds this percentage.
+     *
+     * @param float $percentage Failure rate percentage
+     * @return self
+     */
+    public function failureRateThreshold(float $percentage): self
+    {
+        $this->failureRateThreshold = $percentage;
+        return $this;
+    }
+
+    /**
+     * Set the minimum number of requests before evaluating failure rate.
+     * Circuit won't trip until this many requests have been made.
+     *
+     * @param int $count Minimum requests
+     * @return self
+     */
+    public function minimumRequests(int $count): self
+    {
+        $this->minimumRequests = $count;
+        return $this;
+    }
+
+    public function getFailureRateThreshold(): float
+    {
+        return $this->failureRateThreshold;
+    }
+
+    public function getMinimumRequests(): int
+    {
+        return $this->minimumRequests;
+    }
+
+    public function getStrategy(): string
+    {
+        return 'rate';
+    }
+}
diff --git a/src/CircuitBreaker/Contracts/CircuitBreakerStorage.php b/src/CircuitBreaker/Contracts/CircuitBreakerStorage.php
new file mode 100644
index 0000000..3c7eabc
--- /dev/null
+++ b/src/CircuitBreaker/Contracts/CircuitBreakerStorage.php
@@ -0,0 +1,73 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Contracts;
+
+use Idaratech\Integrations\CircuitBreaker\Enums\CircuitState;
+
+interface CircuitBreakerStorage
+{
+    /**
+     * Get the current state of the circuit.
+     */
+    public function getState(string $service): CircuitState;
+
+    /**
+     * Set the state of the circuit.
+     */
+    public function setState(string $service, CircuitState $state, ?int $ttl = null): void;
+
+    /**
+     * Increment the failure count for a service.
+     */
+    public function incrementFailure(string $service, int $timeWindow): int;
+
+    /**
+     * Increment the success count for a service.
+     */
+    public function incrementSuccess(string $service, int $timeWindow): int;
+
+    /**
+     * Get the failure count for a service.
+     */
+    public function getFailureCount(string $service): int;
+
+    /**
+     * Get the success count for a service.
+     */
+    public function getSuccessCount(string $service): int;
+
+    /**
+     * Get the total request count (failures + successes).
+     */
+    public function getRequestCount(string $service): int;
+
+    /**
+     * Reset all circuit breaker state for a service.
+     */
+    public function reset(string $service): void;
+
+    /**
+     * Get the timestamp when the circuit was opened.
+     */
+    public function getOpenedAt(string $service): ?int;
+
+    /**
+     * Set the timestamp when the circuit was opened.
+     */
+    public function setOpenedAt(string $service, int $timestamp): void;
+
+    /**
+     * Get the success count in half-open state.
+     */
+    public function getHalfOpenSuccessCount(string $service): int;
+
+    /**
+     * Increment the success count in half-open state.
+     */
+    public function incrementHalfOpenSuccess(string $service): int;
+
+    /**
+     * Reset the success count in half-open state.
+     */
+    public function resetHalfOpenSuccess(string $service): void;
+}
diff --git a/src/CircuitBreaker/Contracts/StrategyInterface.php b/src/CircuitBreaker/Contracts/StrategyInterface.php
new file mode 100644
index 0000000..c8f67be
--- /dev/null
+++ b/src/CircuitBreaker/Contracts/StrategyInterface.php
@@ -0,0 +1,51 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Contracts;
+
+interface StrategyInterface
+{
+    /**
+     * Check if the circuit should trip to open state.
+     */
+    public function shouldTrip(string $service, CircuitBreakerStorage $storage): bool;
+
+    /**
+     * Check if the circuit should transition from half-open to closed.
+     */
+    public function shouldClose(string $service, CircuitBreakerStorage $storage): bool;
+
+    /**
+     * Check if enough time has passed to transition from open to half-open.
+     */
+    public function shouldAttemptReset(string $service, CircuitBreakerStorage $storage): bool;
+
+    /**
+     * Record a successful operation.
+     */
+    public function recordSuccess(string $service, CircuitBreakerStorage $storage): void;
+
+    /**
+     * Record a failed operation.
+     */
+    public function recordFailure(string $service, CircuitBreakerStorage $storage): void;
+
+    /**
+     * Get the time window in seconds.
+     */
+    public function getTimeWindow(): int;
+
+    /**
+     * Get the interval to half-open in seconds.
+     */
+    public function getIntervalToHalfOpen(): int;
+
+    /**
+     * Get the minimum requests before evaluation.
+     */
+    public function getMinimumRequests(): int;
+
+    /**
+     * Get the success threshold for half-open state.
+     */
+    public function getSuccessThreshold(): int;
+}
diff --git a/src/CircuitBreaker/Enums/CircuitState.php b/src/CircuitBreaker/Enums/CircuitState.php
new file mode 100644
index 0000000..137c9da
--- /dev/null
+++ b/src/CircuitBreaker/Enums/CircuitState.php
@@ -0,0 +1,20 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Enums;
+
+enum CircuitState: string
+{
+    case CLOSED = 'closed';
+    case OPEN = 'open';
+    case HALF_OPEN = 'half_open';
+
+    public function isClosed(): bool
+    {
+        return $this === self::CLOSED;
+    }
+
+    public function isHalfOpen(): bool
+    {
+        return $this === self::HALF_OPEN;
+    }
+}
diff --git a/src/CircuitBreaker/Exceptions/CircuitOpenException.php b/src/CircuitBreaker/Exceptions/CircuitOpenException.php
new file mode 100644
index 0000000..bfe5621
--- /dev/null
+++ b/src/CircuitBreaker/Exceptions/CircuitOpenException.php
@@ -0,0 +1,35 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Exceptions;
+
+use Exception;
+use Idaratech\Integrations\CircuitBreaker\Enums\CircuitState;
+
+class CircuitOpenException extends Exception
+{
+    protected string $service;
+    protected CircuitState $state;
+
+    public function __construct(
+        string $service,
+        CircuitState $state = CircuitState::OPEN,
+        ?Exception $previous = null
+    ) {
+        $this->service = $service;
+        $this->state = $state;
+
+        $message = "Circuit breaker is open for service '{$service}'.";
+
+        parent::__construct($message, 503, $previous);
+    }
+
+    public function getService(): string
+    {
+        return $this->service;
+    }
+
+    public function getState(): CircuitState
+    {
+        return $this->state;
+    }
+}
diff --git a/src/CircuitBreaker/Storage/CacheStorage.php b/src/CircuitBreaker/Storage/CacheStorage.php
new file mode 100644
index 0000000..357b91f
--- /dev/null
+++ b/src/CircuitBreaker/Storage/CacheStorage.php
@@ -0,0 +1,136 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Storage;
+
+use Illuminate\Contracts\Cache\Repository;
+use Illuminate\Support\Carbon;
+use Illuminate\Support\Facades\Cache;
+use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
+use Idaratech\Integrations\CircuitBreaker\Enums\CircuitState;
+use Psr\SimpleCache\InvalidArgumentException;
+
+class CacheStorage implements CircuitBreakerStorage
+{
+    protected Repository $cache;
+    protected string $prefix;
+
+    public function __construct(string $prefix = 'circuit_breaker', ?string $store = null)
+    {
+        $this->cache = Cache::store($store);
+        $this->prefix = $prefix;
+    }
+
+    protected function key(string $service, string $suffix): string
+    {
+        return "{$this->prefix}:{$service}:{$suffix}";
+    }
+
+    /**
+     * @throws InvalidArgumentException
+     */
+    public function getState(string $service): CircuitState
+    {
+        $state = $this->cache->get($this->key($service, 'state'));
+
+        return CircuitState::tryFrom($state ?: '') ?? CircuitState::CLOSED;
+    }
+
+    public function setState(string $service, CircuitState $state, ?int $ttl = null): void
+    {
+        $key = $this->key($service, 'state');
+
+        if ($ttl !== null) {
+            $this->cache->put($key, $state->value, $ttl);
+        } else {
+            $this->cache->forever($key, $state->value);
+        }
+    }
+
+    public function incrementFailure(string $service, int $timeWindow): int
+    {
+        return $this->incrementWithExpiry($this->key($service, 'failures'), $timeWindow);
+    }
+
+    public function incrementSuccess(string $service, int $timeWindow): int
+    {
+        return $this->incrementWithExpiry($this->key($service, 'successes'), $timeWindow);
+    }
+
+    protected function incrementWithExpiry(string $key, int $ttl): int
+    {
+        // Add key with TTL if it doesn't exist, then increment
+        $this->cache->add($key, 0, $ttl);
+
+        return (int)$this->cache->increment($key);
+    }
+
+    /**
+     * @throws InvalidArgumentException
+     */
+    public function getFailureCount(string $service): int
+    {
+        return (int)$this->cache->get($this->key($service, 'failures'), 0);
+    }
+
+    /**
+     * @throws InvalidArgumentException
+     */
+    public function getSuccessCount(string $service): int
+    {
+        return (int)$this->cache->get($this->key($service, 'successes'), 0);
+    }
+
+    /**
+     * @throws InvalidArgumentException
+     */
+    public function getRequestCount(string $service): int
+    {
+        return $this->getFailureCount($service) + $this->getSuccessCount($service);
+    }
+
+    public function reset(string $service): void
+    {
+        $suffixes = ['state', 'failures', 'successes', 'opened_at', 'half_open_successes'];
+
+        foreach ($suffixes as $suffix) {
+            $this->cache->forget($this->key($service, $suffix));
+        }
+    }
+
+    /**
+     * @throws InvalidArgumentException
+     */
+    public function getOpenedAt(string $service): ?int
+    {
+        $value = $this->cache->get($this->key($service, 'opened_at'));
+
+        return $value ? (int)$value : null;
+    }
+
+    public function setOpenedAt(string $service, int $timestamp): void
+    {
+        $this->cache->forever($this->key($service, 'opened_at'), $timestamp);
+    }
+
+    /**
+     * @throws InvalidArgumentException
+     */
+    public function getHalfOpenSuccessCount(string $service): int
+    {
+        return (int)$this->cache->get($this->key($service, 'half_open_successes'), 0);
+    }
+
+    public function incrementHalfOpenSuccess(string $service): int
+    {
+        $key = $this->key($service, 'half_open_successes');
+
+        $this->cache->add($key, 0, Carbon::now()->addDay());
+
+        return (int)$this->cache->increment($key);
+    }
+
+    public function resetHalfOpenSuccess(string $service): void
+    {
+        $this->cache->forget($this->key($service, 'half_open_successes'));
+    }
+}
diff --git a/src/CircuitBreaker/Storage/RedisStorage.php b/src/CircuitBreaker/Storage/RedisStorage.php
new file mode 100644
index 0000000..8102421
--- /dev/null
+++ b/src/CircuitBreaker/Storage/RedisStorage.php
@@ -0,0 +1,114 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Storage;
+
+use Illuminate\Contracts\Redis\Connection;
+use Illuminate\Support\Facades\Redis;
+use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
+use Idaratech\Integrations\CircuitBreaker\Enums\CircuitState;
+
+class RedisStorage implements CircuitBreakerStorage
+{
+    protected Connection $redis;
+    protected string $prefix;
+
+    public function __construct(string $prefix = 'circuit_breaker', ?string $connection = null)
+    {
+        $this->redis = Redis::connection($connection);
+        $this->prefix = $prefix;
+    }
+
+    protected function key(string $service, string $suffix): string
+    {
+        return "{$this->prefix}:{$service}:{$suffix}";
+    }
+
+    public function getState(string $service): CircuitState
+    {
+        $state = $this->redis->get($this->key($service, 'state'));
+
+        return CircuitState::tryFrom($state ?: '') ?? CircuitState::CLOSED;
+    }
+
+    public function setState(string $service, CircuitState $state, ?int $ttl = null): void
+    {
+        $key = $this->key($service, 'state');
+
+        if ($ttl !== null) {
+            $this->redis->setex($key, $ttl, $state->value);
+        } else {
+            $this->redis->set($key, $state->value);
+        }
+    }
+
+    public function incrementFailure(string $service, int $timeWindow): int
+    {
+        return $this->incrementWithExpiry($this->key($service, 'failures'), $timeWindow);
+    }
+
+    public function incrementSuccess(string $service, int $timeWindow): int
+    {
+        return $this->incrementWithExpiry($this->key($service, 'successes'), $timeWindow);
+    }
+
+    protected function incrementWithExpiry(string $key, int $ttl): int
+    {
+        $count = $this->redis->incr($key);
+        $this->redis->expire($key, $ttl);
+
+        return (int) $count;
+    }
+
+    public function getFailureCount(string $service): int
+    {
+        return (int) ($this->redis->get($this->key($service, 'failures')) ?: 0);
+    }
+
+    public function getSuccessCount(string $service): int
+    {
+        return (int) ($this->redis->get($this->key($service, 'successes')) ?: 0);
+    }
+
+    public function getRequestCount(string $service): int
+    {
+        return $this->getFailureCount($service) + $this->getSuccessCount($service);
+    }
+
+    public function reset(string $service): void
+    {
+        $this->redis->del([
+            $this->key($service, 'state'),
+            $this->key($service, 'failures'),
+            $this->key($service, 'successes'),
+            $this->key($service, 'opened_at'),
+            $this->key($service, 'half_open_successes'),
+        ]);
+    }
+
+    public function getOpenedAt(string $service): ?int
+    {
+        $value = $this->redis->get($this->key($service, 'opened_at'));
+
+        return $value ? (int) $value : null;
+    }
+
+    public function setOpenedAt(string $service, int $timestamp): void
+    {
+        $this->redis->set($this->key($service, 'opened_at'), $timestamp);
+    }
+
+    public function getHalfOpenSuccessCount(string $service): int
+    {
+        return (int) ($this->redis->get($this->key($service, 'half_open_successes')) ?: 0);
+    }
+
+    public function incrementHalfOpenSuccess(string $service): int
+    {
+        return (int) $this->redis->incr($this->key($service, 'half_open_successes'));
+    }
+
+    public function resetHalfOpenSuccess(string $service): void
+    {
+        $this->redis->del([$this->key($service, 'half_open_successes')]);
+    }
+}
diff --git a/src/CircuitBreaker/Strategy/CountStrategy.php b/src/CircuitBreaker/Strategy/CountStrategy.php
new file mode 100644
index 0000000..52e1128
--- /dev/null
+++ b/src/CircuitBreaker/Strategy/CountStrategy.php
@@ -0,0 +1,82 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Strategy;
+
+use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
+use Idaratech\Integrations\CircuitBreaker\Contracts\StrategyInterface;
+
+/**
+ * Count-based circuit breaker strategy.
+ *
+ * Trips the circuit when the absolute failure count exceeds a threshold
+ * within a specified time window.
+ */
+class CountStrategy implements StrategyInterface
+{
+    public function __construct(
+        protected readonly int $timeWindow = 60,
+        protected readonly int $failureCountThreshold = 5,
+        protected readonly int $intervalToHalfOpen = 30,
+        protected readonly int $successThreshold = 3
+    ) {}
+
+    public function shouldTrip(string $service, CircuitBreakerStorage $storage): bool
+    {
+        $failures = $storage->getFailureCount($service);
+
+        return $failures >= $this->failureCountThreshold;
+    }
+
+    public function shouldClose(string $service, CircuitBreakerStorage $storage): bool
+    {
+        $halfOpenSuccesses = $storage->getHalfOpenSuccessCount($service);
+
+        return $halfOpenSuccesses >= $this->successThreshold;
+    }
+
+    public function shouldAttemptReset(string $service, CircuitBreakerStorage $storage): bool
+    {
+        $openedAt = $storage->getOpenedAt($service);
+
+        if ($openedAt === null) {
+            return true;
+        }
+
+        return (time() - $openedAt) >= $this->intervalToHalfOpen;
+    }
+
+    public function recordSuccess(string $service, CircuitBreakerStorage $storage): void
+    {
+        $storage->incrementSuccess($service, $this->timeWindow);
+    }
+
+    public function recordFailure(string $service, CircuitBreakerStorage $storage): void
+    {
+        $storage->incrementFailure($service, $this->timeWindow);
+    }
+
+    public function getTimeWindow(): int
+    {
+        return $this->timeWindow;
+    }
+
+    public function getIntervalToHalfOpen(): int
+    {
+        return $this->intervalToHalfOpen;
+    }
+
+    public function getMinimumRequests(): int
+    {
+        return 0; // Count strategy doesn't require minimum requests
+    }
+
+    public function getSuccessThreshold(): int
+    {
+        return $this->successThreshold;
+    }
+
+    public function getFailureCountThreshold(): int
+    {
+        return $this->failureCountThreshold;
+    }
+}
diff --git a/src/CircuitBreaker/Strategy/RateStrategy.php b/src/CircuitBreaker/Strategy/RateStrategy.php
new file mode 100644
index 0000000..52f1047
--- /dev/null
+++ b/src/CircuitBreaker/Strategy/RateStrategy.php
@@ -0,0 +1,83 @@
+<?php
+
+namespace Idaratech\Integrations\CircuitBreaker\Strategy;
+
+use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
+use Idaratech\Integrations\CircuitBreaker\Contracts\StrategyInterface;
+
+/**
+ * Rate-based circuit breaker strategy.
+ *
+ * Trips the circuit when the failure rate exceeds a threshold percentage
+ * within a specified time window.
+ */
+class RateStrategy implements StrategyInterface
+{
+    public function __construct(
+        protected readonly int $timeWindow = 60,
+        protected readonly float $failureRateThreshold = 50.0,
+        protected readonly int $minimumRequests = 10,
+        protected readonly int $intervalToHalfOpen = 30,
+        protected readonly int $successThreshold = 3
+    ) {}
+
+    public function shouldTrip(string $service, CircuitBreakerStorage $storage): bool
+    {
+        $totalRequests = $storage->getRequestCount($service);
+
+        if ($totalRequests < $this->minimumRequests) {
+            return false;
+        }
+
+        $failureRate = ($storage->getFailureCount($service) / $totalRequests) * 100;
+
+        return $failureRate >= $this->failureRateThreshold;
+    }
+
+    public function shouldClose(string $service, CircuitBreakerStorage $storage): bool
+    {
+        return $storage->getHalfOpenSuccessCount($service) >= $this->successThreshold;
+    }
+
+    public function shouldAttemptReset(string $service, CircuitBreakerStorage $storage): bool
+    {
+        $openedAt = $storage->getOpenedAt($service);
+
+        return $openedAt === null || (time() - $openedAt) >= $this->intervalToHalfOpen;
+    }
+
+    public function recordSuccess(string $service, CircuitBreakerStorage $storage): void
+    {
+        $storage->incrementSuccess($service, $this->timeWindow);
+    }
+
+    public function recordFailure(string $service, CircuitBreakerStorage $storage): void
+    {
+        $storage->incrementFailure($service, $this->timeWindow);
+    }
+
+    public function getTimeWindow(): int
+    {
+        return $this->timeWindow;
+    }
+
+    public function getIntervalToHalfOpen(): int
+    {
+        return $this->intervalToHalfOpen;
+    }
+
+    public function getMinimumRequests(): int
+    {
+        return $this->minimumRequests;
+    }
+
+    public function getSuccessThreshold(): int
+    {
+        return $this->successThreshold;
+    }
+
+    public function getFailureRateThreshold(): float
+    {
+        return $this->failureRateThreshold;
+    }
+}
\ No newline at end of file
diff --git a/src/Client.php b/src/Client.php
index d4461f9..75a14f1 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -1,199 +1,272 @@
-<?php
-namespace Idaratech\Integrations;
-
-use GuzzleHttp\RequestOptions;
-use Idaratech\Integrations\Contracts\ResponseMapperInterface;
-use Idaratech\Integrations\Dto\DefaultResponseMapper;
-use Idaratech\Integrations\Contracts\IClient as ClientInterface;
-use Idaratech\Integrations\Contracts\IRequest as RequestInterface;
-use Idaratech\Integrations\Contracts\IResponse as ResponseInterface;
-use Idaratech\Integrations\Http\Enums\HeaderKey as HK;
-use Idaratech\Integrations\Http\Support\HeaderBag;
-use Idaratech\Integrations\Http\Support\HttpLogger;
-use Idaratech\Integrations\Http\Support\RequestContextBuilder;
-use Idaratech\Integrations\Http\Support\ResponseFactory;
-use Idaratech\Integrations\Http\Transport\LaravelHttpTransport;
-use Idaratech\Integrations\Http\Transport\Transport;
-use Illuminate\Http\Client\Response as HttpResponse;
-
-class Client implements ClientInterface
-{
-    protected array $headers = [];
-    protected array $options = [];
-    protected ?string $baseUri = null;
-    protected ?int $timeout = null;
-    protected int $retryTimes = 0;
-    protected int $retrySleepMs = 0;
-
-    protected Transport $transport;
-    protected RequestContextBuilder $builder;
-    protected HttpLogger $logger;
-    protected ResponseFactory $responseFactory;
-    protected ResponseMapperInterface $mapper;
-
-    public function __construct(?string $baseUri = null, array $headers = [], array $options = [])
-    {
-        $this->baseUri = $baseUri;
-        $this->headers = $headers;
-        $this->options = $options;
-
-        $this->transport = new LaravelHttpTransport($this->headers, $this->timeout, $this->retryTimes, $this->retrySleepMs);
-        $this->builder = new RequestContextBuilder($this->baseUri, $this->headers, $this->options);
-        $this->logger = new HttpLogger();
-        $this->responseFactory = new ResponseFactory();
-        $this->mapper = new DefaultResponseMapper();
-    }
-
-    public function withHeaders(array $headers): ClientInterface
-    {
-        $normalized = [];
-        foreach ($headers as $k => $v) {
-            if ($k instanceof HK) { $normalized[$k->key()] = $v; }
-            else { $normalized[is_string($k) ? $k : (string) $k] = $v; }
-        }
-        $this->headers = HeaderBag::merge($this->headers, $normalized);
-        $this->transport->withHeaders($this->headers);
-        $this->builder->withHeaders($this->headers);
-        return $this;
-    }
-
-    public function setHeader(string|HK $key, mixed $value): ClientInterface
-    {
-        if ($key instanceof HK) { $key = $key->key(); }
-        $this->headers = HeaderBag::merge($this->headers, [$key => $value]);
-        $this->transport->withHeaders($this->headers);
-        $this->builder->withHeaders($this->headers);
-        return $this;
-    }
-
-    public function withBearer(string $token): ClientInterface
-    {
-        return $this->setHeader(HK::AUTHORIZATION, 'Bearer ' . $token);
-    }
-
-    public function withBasicAuth(string $username, string $password): ClientInterface
-    {
-        $this->options[RequestOptions::AUTH] = [$username, $password];
-        $this->builder->withOptions($this->options);
-        return $this;
-    }
-
-    public function withBaseUri(?string $baseUri): ClientInterface
-    {
-        $this->baseUri = $baseUri;
-        $this->builder->withBaseUri($this->baseUri);
-        return $this;
-    }
-
-    public function retry(int $times, int $sleepMs = 0): ClientInterface
-    {
-        $this->retryTimes = $times;
-        $this->retrySleepMs = $sleepMs;
-        $this->transport->retry($times, $sleepMs);
-        return $this;
-    }
-
-    public function timeout(int $seconds): ClientInterface
-    {
-        $this->timeout = $seconds;
-        $this->transport->timeout($seconds);
-        return $this;
-    }
-
-    public function withOption(string $key, mixed $value): ClientInterface
-    {
-        $this->options[$key] = $value;
-        $this->builder->withOptions($this->options);
-        return $this;
-    }
-
-    public function withOptions(array $options): ClientInterface
-    {
-        $this->options = array_replace($this->options, $options);
-        $this->builder->withOptions($this->options);
-        return $this;
-    }
-
-    public function do(RequestInterface $request): ResponseInterface
-    {
-        $request->runBeforeMiddlewares($this);
-
-        $ctx = $this->builder->build($request);
-        $url = $ctx['url'];
-        $options = $ctx['options'];
-        $method = $request->method();
-        $mergedHeaders = HeaderBag::merge($this->headers, $request->headers());
-
-        $this->logger->logRequest([
-            'method'  => $method,
-            'url'     => $url,
-            'headers' => $this->redactHeaders($mergedHeaders),
-            'query'   => $request->query(),
-            'body'    => $this->redactBody($request->body()),
-        ]);
-
-        $res = $this->transport
-            ->withHeaders($mergedHeaders)
-            ->send($method, $url, $options);
-
-        $response = $this->createResponse($request, $res);
-
-        $request->runAfterMiddlewares($this);
-
-        $this->logger->logResponse($response);
-
-        return $response;
-    }
-
-    public function process(RequestInterface $request): Contracts\IDto
-    {
-        $response = $this->do($request);
-        return $this->responseToDto($response);
-    }
-
-    protected function createResponse(RequestInterface $request, HttpResponse $res): ResponseInterface
-    {
-        return $this->responseFactory->create($request, $res);
-    }
-
-    protected function responseToDto(ResponseInterface $response)
-    {
-        return $this->mapper->map($response);
-    }
-
-    protected function resolveMethod($method): string
-    {
-        if (is_string($method)) return strtoupper($method);
-        if (is_object($method)) {
-            if (method_exists($method, 'value')) return strtoupper((string) $method->value);
-            if (method_exists($method, 'name')) return strtoupper((string) $method->name);
-            if (is_callable($method)) return strtoupper((string) $method());
-            if (method_exists($method, '__toString')) return strtoupper((string) $method);
-        }
-        return 'GET';
-    }
-
-    protected function redactHeaders(array $headers): array
-    {
-        $out = [];
-        foreach ($headers as $k => $v) {
-            $key = is_string($k) ? strtolower($k) : (string) $k;
-            $out[$k] = in_array($key, ['authorization','x-api-key']) ? $this->maskToken((string)$v) : $v;
-        }
-        return $out;
-    }
-
-    protected function redactBody(array $body): array
-    {
-        $sensitive = ['password','token','access_token','secret','client_secret','authorization'];
-        foreach ($sensitive as $k) if (array_key_exists($k, $body)) $body[$k] = '******';
-        return $body;
-    }
-
-    protected function maskToken(string $value): string
-    {
-        $len = strlen($value);
-        if ($len <= 8) return '******';
-        return substr($value, 0, 4) . str_repeat('*', max(0, $len - 8)) . substr($value, -4);
-    }
-}
+<?php
+namespace Idaratech\Integrations;
+
+use GuzzleHttp\RequestOptions;
+use Idaratech\Integrations\CircuitBreaker\CircuitBreaker;
+use Idaratech\Integrations\CircuitBreaker\CircuitBreakerFactory;
+use Idaratech\Integrations\CircuitBreaker\CircuitBreakerInterceptor;
+use Idaratech\Integrations\CircuitBreaker\Config\CircuitBreakerConfig;
+use Idaratech\Integrations\CircuitBreaker\Exceptions\CircuitOpenException;
+use Idaratech\Integrations\Contracts\IClient as ClientInterface;
+use Idaratech\Integrations\Contracts\IRequest as RequestInterface;
+use Idaratech\Integrations\Contracts\IResponse as ResponseInterface;
+use Idaratech\Integrations\Contracts\ResponseMapperInterface;
+use Idaratech\Integrations\Dto\DefaultResponseMapper;
+use Idaratech\Integrations\Http\Enums\HeaderKey as HK;
+use Idaratech\Integrations\Http\Support\HeaderBag;
+use Idaratech\Integrations\Http\Support\HttpLogger;
+use Idaratech\Integrations\Http\Support\RequestContextBuilder;
+use Idaratech\Integrations\Http\Support\ResponseFactory;
+use Idaratech\Integrations\Http\Transport\LaravelHttpTransport;
+use Idaratech\Integrations\Http\Transport\Transport;
+use Illuminate\Http\Client\Response as HttpResponse;
+use Throwable;
+
+class Client implements ClientInterface
+{
+    protected array $headers = [];
+    protected array $options = [];
+    protected ?string $baseUri = null;
+    protected ?int $timeout = null;
+    protected int $retryTimes = 0;
+    protected int $retrySleepMs = 0;
+
+    protected Transport $transport;
+    protected RequestContextBuilder $builder;
+    protected HttpLogger $logger;
+    protected ResponseFactory $responseFactory;
+    protected ResponseMapperInterface $mapper;
+    protected ?CircuitBreakerInterceptor $circuitBreakerInterceptor = null;
+    protected bool $circuitBreakerEnabled = false;
+
+    public function __construct(?string $baseUri = null, array $headers = [], array $options = [])
+    {
+        $this->baseUri = $baseUri;
+        $this->headers = $headers;
+        $this->options = $options;
+
+        $this->transport = new LaravelHttpTransport($this->headers, $this->timeout, $this->retryTimes, $this->retrySleepMs);
+        $this->builder = new RequestContextBuilder($this->baseUri, $this->headers, $this->options);
+        $this->logger = new HttpLogger();
+        $this->responseFactory = new ResponseFactory();
+        $this->mapper = new DefaultResponseMapper();
+
+        // Auto-configure circuit breaker if config is provided
+        $this->configureCircuitBreakerFromConfig();
+    }
+
+    public function withHeaders(array $headers): ClientInterface
+    {
+        $normalized = [];
+        foreach ($headers as $k => $v) {
+            if ($k instanceof HK) { $normalized[$k->key()] = $v; }
+            else { $normalized[is_string($k) ? $k : (string) $k] = $v; }
+        }
+        $this->headers = HeaderBag::merge($this->headers, $normalized);
+        $this->transport->withHeaders($this->headers);
+        $this->builder->withHeaders($this->headers);
+        return $this;
+    }
+
+    public function setHeader(string|HK $key, mixed $value): ClientInterface
+    {
+        if ($key instanceof HK) { $key = $key->key(); }
+        $this->headers = HeaderBag::merge($this->headers, [$key => $value]);
+        $this->transport->withHeaders($this->headers);
+        $this->builder->withHeaders($this->headers);
+        return $this;
+    }
+
+    public function withBearer(string $token): ClientInterface
+    {
+        return $this->setHeader(HK::AUTHORIZATION, 'Bearer ' . $token);
+    }
+
+    public function withBasicAuth(string $username, string $password): ClientInterface
+    {
+        $this->options[RequestOptions::AUTH] = [$username, $password];
+        $this->builder->withOptions($this->options);
+        return $this;
+    }
+
+    public function withBaseUri(?string $baseUri): ClientInterface
+    {
+        $this->baseUri = $baseUri;
+        $this->builder->withBaseUri($this->baseUri);
+        return $this;
+    }
+
+    public function retry(int $times, int $sleepMs = 0): ClientInterface
+    {
+        $this->retryTimes = $times;
+        $this->retrySleepMs = $sleepMs;
+        $this->transport->retry($times, $sleepMs);
+        return $this;
+    }
+
+    public function timeout(int $seconds): ClientInterface
+    {
+        $this->timeout = $seconds;
+        $this->transport->timeout($seconds);
+        return $this;
+    }
+
+    public function withOption(string $key, mixed $value): ClientInterface
+    {
+        $this->options[$key] = $value;
+        $this->builder->withOptions($this->options);
+        return $this;
+    }
+
+    public function withOptions(array $options): ClientInterface
+    {
+        $this->options = array_replace($this->options, $options);
+        $this->builder->withOptions($this->options);
+        return $this;
+    }
+
+    /**
+     * @throws Throwable
+     * @throws CircuitOpenException
+     */
+    public function do(RequestInterface $request): ResponseInterface
+    {
+        $this->circuitBreakerInterceptor?->before($request);
+
+
+        $request->runBeforeMiddlewares($this);
+
+        $ctx = $this->builder->build($request);
+        $url = $ctx['url'];
+        $options = $ctx['options'];
+        $method = $this->resolveMethod($request->method());
+        $mergedHeaders = HeaderBag::merge($this->headers, $request->headers());
+
+        $this->logger->logRequest([
+            'method'  => $method,
+            'url'     => $url,
+            'headers' => $this->redactHeaders($mergedHeaders),
+            'query'   => $request->query(),
+            'body'    => $this->redactBody($request->body()),
+        ]);
+
+        try {
+            $res = $this->transport
+                ->withHeaders($mergedHeaders)
+                ->send($method, $url, $options);
+
+            $response = $this->createResponse($request, $res);
+
+            $this->circuitBreakerInterceptor?->after($request, $response);
+
+            $request->runAfterMiddlewares($this);
+
+            $this->logger->logResponse($response);
+
+            return $response;
+        } catch (Throwable $e) {
+            $this->circuitBreakerInterceptor?->onException($request, $e);
+            throw $e;
+        }
+    }
+
+    public function process(RequestInterface $request): Contracts\IDto
+    {
+        $response = $this->do($request);
+        return $this->responseToDto($response);
+    }
+
+    protected function createResponse(RequestInterface $request, HttpResponse $res): ResponseInterface
+    {
+        return $this->responseFactory->create($request, $res);
+    }
+
+    protected function responseToDto(ResponseInterface $response)
+    {
+        return $this->mapper->map($response);
+    }
+
+    protected function resolveMethod($method): string
+    {
+        if (is_string($method)) return strtoupper($method);
+        if (is_object($method)) {
+            if (method_exists($method, 'value')) return strtoupper((string) $method->value);
+            if (method_exists($method, 'name')) return strtoupper((string) $method->name);
+            if (is_callable($method)) return strtoupper((string) $method());
+            if (method_exists($method, '__toString')) return strtoupper((string) $method);
+        }
+        return 'GET';
+    }
+
+    protected function redactHeaders(array $headers): array
+    {
+        $out = [];
+        foreach ($headers as $k => $v) {
+            $key = is_string($k) ? strtolower($k) : (string) $k;
+            $out[$k] = in_array($key, ['authorization','x-api-key']) ? $this->maskToken((string)$v) : $v;
+        }
+        return $out;
+    }
+
+    protected function redactBody(array $body): array
+    {
+        $sensitive = ['password','token','access_token','secret','client_secret','authorization'];
+        foreach ($sensitive as $k) if (array_key_exists($k, $body)) $body[$k] = '******';
+        return $body;
+    }
+
+    protected function maskToken(string $value): string
+    {
+        $len = strlen($value);
+        if ($len <= 8) return '******';
+        return substr($value, 0, 4) . str_repeat('*', max(0, $len - 8)) . substr($value, -4);
+    }
+
+    /**
+     * Configure circuit breaker for this client.
+     * Override this method in subclasses to provide custom configuration.
+     *
+     * @return CircuitBreakerConfig|null Circuit breaker configuration or null to use global default
+     */
+    protected function circuitBreakerConfig(): ?CircuitBreakerConfig
+    {
+        return null; // Default: no custom circuit breaker
+    }
+
+    /**
+     * Auto-configure circuit breaker from config if provided.
+     *
+     * @return void
+     */
+    protected function configureCircuitBreakerFromConfig(): void
+    {
+        $config = $this->circuitBreakerConfig();
+
+        // Priority 1: Custom config (highest priority)
+        if ($config !== null) {
+            $factory = app(CircuitBreakerFactory::class);
+            $circuitBreaker = $factory->createFromConfig($config);
+            $this->withCircuitBreaker($circuitBreaker);
+            return;
+        }
+
+        // Priority 2: Per-service enabled flag
+        if ($this->circuitBreakerEnabled) {
+            try {
+                $globalCircuitBreaker = app(CircuitBreaker::class);
+                $this->withCircuitBreaker($globalCircuitBreaker);
+            } catch (\Throwable $e) {
+                // Silently ignore if circuit breaker dependencies are not available
+            }
+        }
+
+        // No global config check - circuit breaker is always per-service controlled
+    }
+
+    public function withCircuitBreaker(CircuitBreaker $circuitBreaker): ClientInterface
+    {
+        $this->circuitBreakerInterceptor = new CircuitBreakerInterceptor($circuitBreaker);
+        return $this;
+    }
+
+}
diff --git a/src/Contracts/IClient.php b/src/Contracts/IClient.php
index d9a08e9..358495c 100755
--- a/src/Contracts/IClient.php
+++ b/src/Contracts/IClient.php
@@ -1,11 +1,14 @@
-<?php
-
-namespace Idaratech\Integrations\Contracts;
-
-interface IClient
-{
-    public function do(IRequest $request): IResponse;
-
-    public function process(IRequest $request): IDto;
-
-}
+<?php
+
+namespace Idaratech\Integrations\Contracts;
+
+use Idaratech\Integrations\CircuitBreaker\CircuitBreaker;
+
+interface IClient
+{
+    public function do(IRequest $request): IResponse;
+
+    public function process(IRequest $request): IDto;
+
+    public function withCircuitBreaker(CircuitBreaker $circuitBreaker): self;
+}
diff --git a/src/IntegrationsServiceProvider.php b/src/IntegrationsServiceProvider.php
index 87aa162..d1ae2f2 100644
--- a/src/IntegrationsServiceProvider.php
+++ b/src/IntegrationsServiceProvider.php
@@ -1,34 +1,97 @@
-<?php
-namespace Idaratech\Integrations;
-
-use Idaratech\Integrations\Contracts\ResponseMapperInterface;
-use Idaratech\Integrations\Dto\DefaultResponseMapper;
-use Idaratech\Integrations\Http\Transport\LaravelHttpTransport;
-use Idaratech\Integrations\Http\Transport\Transport;
-use Illuminate\Support\ServiceProvider;
-
-class IntegrationsServiceProvider extends ServiceProvider
-{
-    public function register(): void
-    {
-        $this->mergeConfigFrom(__DIR__.'/../config/integrations.php', 'integrations');
-
-        $this->app->bind(Transport::class, function ($app) {
-            $headers = config('integrations.default_headers', []);
-            $timeout = config('integrations.timeout', null);
-            $retry   = config('integrations.retry.times', 0);
-            $sleep   = config('integrations.retry.sleep_ms', 0);
-
-            return (new LaravelHttpTransport($headers, $timeout, $retry, $sleep));
-        });
-
-        $this->app->bind(ResponseMapperInterface::class, DefaultResponseMapper::class);
-    }
-
-    public function boot(): void
-    {
-        $this->publishes([
-            __DIR__.'/../config/integrations.php' => config_path('integrations.php'),
-        ], 'config');
-    }
-}
+<?php
+
+namespace Idaratech\Integrations;
+
+use Idaratech\Integrations\CircuitBreaker\CircuitBreaker;
+use Idaratech\Integrations\CircuitBreaker\CircuitBreakerFactory;
+use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
+use Idaratech\Integrations\CircuitBreaker\Storage\CacheStorage;
+use Idaratech\Integrations\CircuitBreaker\Storage\RedisStorage;
+use Idaratech\Integrations\CircuitBreaker\Strategy\CountStrategy;
+use Idaratech\Integrations\CircuitBreaker\Strategy\RateStrategy;
+use Idaratech\Integrations\Contracts\ResponseMapperInterface;
+use Idaratech\Integrations\Dto\DefaultResponseMapper;
+use Idaratech\Integrations\Http\Transport\LaravelHttpTransport;
+use Idaratech\Integrations\Http\Transport\Transport;
+use Illuminate\Support\Facades\Log;
+use Illuminate\Support\ServiceProvider;
+
+class IntegrationsServiceProvider extends ServiceProvider
+{
+    public function register(): void
+    {
+        $this->mergeConfigFrom(__DIR__.'/../config/integrations.php', 'integrations');
+
+        $this->app->bind(Transport::class, function ($app) {
+            $headers = config('integrations.default_headers', []);
+            $timeout = config('integrations.timeout', null);
+            $retry   = config('integrations.retry.times', 0);
+            $sleep   = config('integrations.retry.sleep_ms', 0);
+
+            return (new LaravelHttpTransport($headers, $timeout, $retry, $sleep));
+        });
+
+        $this->app->bind(ResponseMapperInterface::class, DefaultResponseMapper::class);
+
+        $this->registerCircuitBreaker();
+    }
+
+    protected function registerCircuitBreaker(): void
+    {
+        // Register circuit breaker factory for per-service configuration
+        $this->app->singleton(CircuitBreakerFactory::class, function () {
+            return new CircuitBreakerFactory();
+        });
+
+        // Register global circuit breaker singleton as fallback
+        // Used only when circuitBreakerConfig() is not overridden and enabled in config
+        $this->app->singleton(CircuitBreaker::class, function ($app) {
+            $config = config('integrations.circuit_breaker');
+
+            // Create storage adapter
+            $storage = $this->createStorageFromConfig($config);
+
+            // Create strategy
+            $strategy = match ($config['strategy']) {
+                'count' => new CountStrategy(
+                    $config['time_window'],
+                    $config['failure_count_threshold'],
+                    $config['interval_to_half_open'],
+                    $config['success_threshold']
+                ),
+                default => new RateStrategy(
+                    $config['time_window'],
+                    $config['failure_rate_threshold'],
+                    $config['minimum_requests'],
+                    $config['interval_to_half_open'],
+                    $config['success_threshold']
+                ),
+            };
+
+            return (new CircuitBreaker($storage, $strategy))
+                ->setFailureStatusCodes($config['failure_status_codes'] ?? [])
+                ->setIgnoredStatusCodes($config['ignored_status_codes'] ?? []);
+        });
+    }
+
+    protected function createStorageFromConfig(array $config): CircuitBreakerStorage
+    {
+        if ($config['storage'] === 'redis') {
+            try {
+                return new RedisStorage($config['prefix'], $config['redis_connection'] ?? null);
+            } catch (\Throwable $e) {
+                Log::warning('Circuit breaker: Redis unavailable, falling back to cache', [
+                    'error' => $e->getMessage(),
+                ]);
+            }
+        }
+        return new CacheStorage($config['prefix'], $config['cache_store'] ?? null);
+    }
+
+    public function boot(): void
+    {
+        $this->publishes([
+            __DIR__.'/../config/integrations.php' => config_path('integrations.php'),
+        ], 'config');
+    }
+}

From 3079c71d76dfe1de4cf3ca1ee2401f4bf6bbc9fd Mon Sep 17 00:00:00 2001
From: Bilal-Elhalawaty <b.elhalawaty@idaratech.sa>
Date: Mon, 29 Dec 2025 17:35:42 +0200
Subject: [PATCH 2/3] refactor: update circuit breaker configuration and
 improve validation checks

---
 config/integrations.php                       |  31 +-
 docs/CIRCUIT_BREAKER.md                       | 628 ------------------
 src/CircuitBreaker/CircuitBreaker.php         | 126 ++--
 src/CircuitBreaker/CircuitBreakerFactory.php  |   8 +-
 .../CircuitBreakerInterceptor.php             |   9 +-
 .../Config/CircuitBreakerConfig.php           |  47 +-
 .../Config/CountStrategyConfig.php            |  26 +-
 .../Config/RateStrategyConfig.php             |  34 +-
 .../Contracts/CircuitBreakerStorage.php       |  21 +
 .../Contracts/StrategyInterface.php           |  30 +-
 .../Exceptions/CircuitOpenException.php       |   5 +
 src/CircuitBreaker/Storage/CacheStorage.php   |  68 +-
 src/CircuitBreaker/Storage/RedisStorage.php   |  89 ++-
 src/CircuitBreaker/Strategy/CountStrategy.php |  54 +-
 src/CircuitBreaker/Strategy/RateStrategy.php  |  53 +-
 src/Client.php                                |  18 +-
 src/IntegrationsServiceProvider.php           | 140 ++--
 17 files changed, 513 insertions(+), 874 deletions(-)
 delete mode 100644 docs/CIRCUIT_BREAKER.md

diff --git a/config/integrations.php b/config/integrations.php
index da93fd1..bf1be38 100644
--- a/config/integrations.php
+++ b/config/integrations.php
@@ -13,31 +13,18 @@
 
     /*
     |--------------------------------------------------------------------------
-    | Circuit Breaker Configuration (Default Settings)
+    | Circuit Breaker Storage Prefix
     |--------------------------------------------------------------------------
     |
-    | Default circuit breaker settings that services can opt-in to use.
+    | Storage key prefix for circuit breaker state (app-namespaced).
+    | This ensures multiple applications can share the same Redis/Cache
+    | without key collisions.
     |
-    | Per-service control:
-    | 1. Custom config: Override circuitBreakerConfig() method for custom settings
-    | 2. Default settings: Set $circuitBreakerEnabled = true to use these defaults
-    | 3. Disabled: Default behavior (no override needed)
-    |
-    | See: docs/CIRCUIT_BREAKER_PER_SERVICE.md
+    | Note: APP_NAME is sanitized to replace ':' with '_' to prevent key parsing issues.
     |
     */
-    'circuit_breaker' => [
-        'storage' => env('INTEGRATIONS_CIRCUIT_BREAKER_STORAGE', 'cache'),
-        'cache_store' => env('INTEGRATIONS_CIRCUIT_BREAKER_CACHE_STORE', null),
-        'prefix' => env('INTEGRATIONS_CIRCUIT_BREAKER_PREFIX', 'circuit_breaker'),
-        'strategy' => env('INTEGRATIONS_CIRCUIT_BREAKER_STRATEGY', 'rate'),
-        'time_window' => env('INTEGRATIONS_CIRCUIT_BREAKER_TIME_WINDOW', 60),
-        'failure_rate_threshold' => env('INTEGRATIONS_CIRCUIT_BREAKER_FAILURE_RATE_THRESHOLD', 50),
-        'failure_count_threshold' => env('INTEGRATIONS_CIRCUIT_BREAKER_FAILURE_COUNT_THRESHOLD', 5),
-        'minimum_requests' => env('INTEGRATIONS_CIRCUIT_BREAKER_MINIMUM_REQUESTS', 10),
-        'interval_to_half_open' => env('INTEGRATIONS_CIRCUIT_BREAKER_INTERVAL_TO_HALF_OPEN', 30),
-        'success_threshold' => env('INTEGRATIONS_CIRCUIT_BREAKER_SUCCESS_THRESHOLD', 3),
-        'failure_status_codes' => [500, 502, 503, 504],
-        'ignored_status_codes' => [],
-    ],
+    'circuit_breaker_prefix' => env(
+        'INTEGRATIONS_CIRCUIT_BREAKER_PREFIX',
+        'cb:' . str_replace(':', '_', env('APP_NAME', 'app'))
+    ),
 ];
diff --git a/docs/CIRCUIT_BREAKER.md b/docs/CIRCUIT_BREAKER.md
deleted file mode 100644
index 9e4dff8..0000000
--- a/docs/CIRCUIT_BREAKER.md
+++ /dev/null
@@ -1,628 +0,0 @@
-# Circuit Breaker
-
-## What Is It?
-
-Protects your application from cascading failures when external services become unreliable. Automatically stops requests to failing services and gives them time to recover.
-
-**What "Trip" Means:**
-- **Trip** = Circuit transitions from CLOSED (normal) to OPEN (blocking requests)
-- When circuit "trips", it opens and blocks all requests to protect your system
-- After a cooldown period, it tests if the service recovered
-
----
-
-## Three Circuit States
-
-```
-CLOSED 🟢 (Normal - all requests allowed)
-    ↓
-    Too many failures detected
-    ↓
-OPEN 🔴 (Service down - block ALL requests)
-    ↓
-    Wait for cooldown period (e.g., 30s)
-    ↓
-HALF_OPEN ⚠️ (Testing - allow FEW requests to test recovery)
-    ↓
-    If 3 successes → CLOSED 🟢 (recovered!)
-    If 1 failure   → OPEN 🔴 (still broken, wait longer)
-```
-
-### Why HALF_OPEN is Needed
-
-Without HALF_OPEN, you'd have two bad options:
-- ❌ **Stay OPEN forever** → Never recover, block healthy services
-- ❌ **Auto-CLOSE immediately** → Flood failing service with thousands of requests
-
-**HALF_OPEN = Controlled Testing**
-- After cooldown (e.g., 30s), test with just a FEW requests (e.g., 3)
-- If those succeed → Service is healthy → Fully reopen
-- If any fail → Service still broken → Wait longer
-
----
-
-## How to Use
-
-### Option 1: Custom Configuration Per Service
-
-```php
-use Idaratech\Integrations\Client;
-use Idaratech\Integrations\CircuitBreaker\Config\RateStrategyConfig;
-
-class PaymentClient extends Client
-{
-    protected function circuitBreakerConfig(): ?CircuitBreakerConfig
-    {
-        return RateStrategyConfig::make()
-            ->failureRateThreshold(40.0)   // Trip at 40% failure rate
-            ->minimumRequests(10)           // Need 10 requests minimum
-            ->timeWindow(60)                // Track failures in last 60s
-            ->intervalToHalfOpen(30)        // Wait 30s before testing recovery
-            ->successThreshold(3)           // Need 3 successes to close
-            ->storage('redis');
-    }
-}
-```
-
-### Option 2: Count Strategy (Absolute Failures)
-
-```php
-use Idaratech\Integrations\CircuitBreaker\Config\CountStrategyConfig;
-
-class EmailClient extends Client
-{
-    protected function circuitBreakerConfig(): ?CircuitBreakerConfig
-    {
-        return CountStrategyConfig::make()
-            ->failureCountThreshold(5)      // Trip after 5 failures
-            ->intervalToHalfOpen(120)       // Wait 2 minutes
-            ->storage('redis');
-    }
-}
-```
-
----
-
-## Strategies Explained
-
-### Rate Strategy (Percentage-Based)
-
-Trips when **failure percentage** exceeds threshold.
-
-**Formula:**
-```
-failureRate = (failures / totalRequests) × 100
-
-if totalRequests >= minimumRequests AND failureRate >= threshold:
-    TRIP → OPEN
-```
-
-**Example:**
-```php
-RateStrategyConfig::make()
-    ->failureRateThreshold(50.0)    // 50% threshold
-    ->minimumRequests(10);           // Need at least 10 requests
-```
-
-**Scenarios:**
-```
-Scenario 1: Not enough data
-Total: 8 requests, 5 failures (62.5% failure rate)
-Result: DON'T TRIP (8 < 10 minimum requests)
-
-Scenario 2: Below threshold
-Total: 20 requests, 8 failures (40% failure rate)
-Result: DON'T TRIP (40% < 50%)
-
-Scenario 3: Exceeds threshold - TRIP!
-Total: 20 requests, 11 failures (55% failure rate)
-Result: TRIP! (55% >= 50% AND 20 >= 10)
-```
-
-**Best for:** High-traffic services, external APIs
-
----
-
-### Count Strategy (Absolute Count)
-
-Trips when **absolute failure count** exceeds threshold.
-
-**Formula:**
-```
-if failures >= threshold:
-    TRIP → OPEN
-```
-
-**Example:**
-```php
-CountStrategyConfig::make()
-    ->failureCountThreshold(5);     // Trip after 5 failures
-```
-
-**Scenarios:**
-```
-Scenario 1: Below threshold
-Failures: 3, Successes: 100
-Result: DON'T TRIP (3 < 5)
-
-Scenario 2: Reaches threshold - TRIP!
-Failures: 5, Successes: 2
-Result: TRIP! (5 >= 5)
-```
-
-**Best for:** Critical services (payments), low-traffic services, strict failure policies
-
----
-
-### Strategy Comparison
-
-| Aspect | Rate Strategy | Count Strategy |
-|--------|--------------|----------------|
-| **Trips based on** | Percentage (failures/total) | Absolute number |
-| **Requires minimum requests** | Yes | No |
-| **Formula** | `(failures/total)×100 >= 50%` | `failures >= 5` |
-| **Best for** | High traffic, variable load | Critical systems, low traffic |
-| **Example** | 11 failures out of 20 = 55% | 5 failures (regardless of total) |
-
----
-
-## Configuration Parameters Explained
-
-### 1. `timeWindow` - Memory Span (ALWAYS Active)
-
-**What it does:** Defines how long to "remember" failures and successes
-
-**Always running:** Tracks in ALL states (CLOSED, OPEN, HALF_OPEN)
-
-**Example:**
-```php
-->timeWindow(60)  // Track failures in last 60 seconds
-```
-
-**How it works:**
-```
-Current time: 10:01:00
-
-Failures in storage:
-09:59:50 - Failure ❌ EXPIRED (outside 60s window)
-10:00:05 - Failure ✓ COUNTED (within 60s)
-10:00:30 - Failure ✓ COUNTED (within 60s)
-10:00:50 - Failure ✓ COUNTED (within 60s)
-
-Only count: 3 failures (last 60 seconds)
-Old failures automatically expire!
-```
-
-**Purpose:**
-- Prevents old failures from affecting current decisions
-- Keeps circuit breaker responsive to current service health
-- Creates a "sliding window" that moves with time
-
----
-
-### 2. `intervalToHalfOpen` - Recovery Cooldown (Only When OPEN)
-
-**What it does:** How long to wait after circuit opens before testing recovery
-
-**Only active:** When circuit is OPEN
-
-**Example:**
-```php
-->intervalToHalfOpen(30)  // Wait 30 seconds before testing
-```
-
-**How it works:**
-```
-10:00:00 - Circuit OPENS
-           Start timer from HERE
-
-10:00:10 - Request arrives
-           10s < 30s → BLOCKED ⛔
-
-10:00:30 - Request arrives
-           30s >= 30s → Transition to HALF_OPEN ✓
-           Request ALLOWED (testing recovery)
-```
-
-**Purpose:**
-- Gives failing service time to recover
-- Prevents immediate retry attempts
-
----
-
-### 3. `successThreshold` - Recovery Test (Only in HALF_OPEN)
-
-**What it does:** How many CONSECUTIVE successes needed to close circuit
-
-**Only active:** When circuit is HALF_OPEN
-
-**NOT tracked in timeWindow!** Separate counter, resets on any failure.
-
-**Example:**
-```php
-->successThreshold(3)  // Need 3 consecutive successes
-```
-
-**How it works:**
-```
-State: HALF_OPEN
-
-Request 1: 200 ✓ → successCount = 1
-Request 2: 200 ✓ → successCount = 2
-Request 3: 200 ✓ → successCount = 3
-→ Circuit CLOSES! 🟢
-
-Alternative (failure during test):
-Request 1: 200 ✓ → successCount = 1
-Request 2: 500 ✗ → FAILURE!
-→ Circuit goes back to OPEN 🔴
-→ Reset counter to 0
-→ Wait another 30s before retrying
-```
-
-**Purpose:**
-- Ensures service is truly recovered (not just one lucky success)
-- Prevents flapping (OPEN → CLOSED → OPEN)
-
----
-
-### Visual Summary of Parameters
-
-```
-timeWindow(60) - ALWAYS SLIDING
-├─────────────────────────────────────────────────────┤
-09:59:50    10:00:00    10:00:30    10:01:00
-   ❌          ✓           ✓         NOW
-expired    counted     counted
-└──────────── Only count last 60s ────────────┘
-
-
-intervalToHalfOpen(30) - ONLY WHEN OPEN
-Circuit OPENS ├─────────────┤ Test recovery
-         10:00:00      10:00:30
-                          ↓
-                    HALF_OPEN
-
-
-successThreshold(3) - ONLY IN HALF_OPEN
-HALF_OPEN: ✓ ✓ ✓ → CLOSED
-HALF_OPEN: ✓ ✗   → Back to OPEN
-           └─ Reset counter
-```
-
----
-
-## Complete Lifecycle Example
-
-**Configuration:**
-```php
-RateStrategyConfig::make()
-    ->failureRateThreshold(50.0)   // 50% failures
-    ->minimumRequests(10)
-    ->timeWindow(60)                // Track last 60s
-    ->intervalToHalfOpen(30)        // Wait 30s before testing
-    ->successThreshold(3);          // Need 3 successes
-```
-
-**Timeline:**
-
-```
-State: CLOSED 🟢
-════════════════════════════════════════════════════════
-10:00:00-10:00:40 - 9 requests (4 success, 5 failures)
-                    9 < 10 minimum → Don't trip yet
-
-10:00:45 - Request 10: 500 ✗
-           Total: 10 requests (4 success, 6 failures)
-           Failure rate: (6/10) × 100 = 60%
-           60% >= 50% threshold → TRIP! 🔴
-
-State: OPEN 🔴 (openedAt = 10:00:45)
-════════════════════════════════════════════════════════
-10:00:50 - Request arrives
-           Time since opened: 5s < 30s
-           → BLOCKED ⛔ (throw CircuitOpenException)
-
-10:01:00 - Request arrives
-           15s < 30s → BLOCKED ⛔
-
-10:01:15 - Request arrives (30s passed!)
-           30s >= 30s → Transition to HALF_OPEN ⚠️
-           → Request ALLOWED (testing)
-
-State: HALF_OPEN ⚠️ (testing recovery)
-════════════════════════════════════════════════════════
-10:01:15 - Request 1: 200 ✓ (successCount = 1/3)
-10:01:20 - Request 2: 200 ✓ (successCount = 2/3)
-10:01:25 - Request 3: 200 ✓ (successCount = 3/3)
-           3 >= 3 → Circuit CLOSES! 🟢
-
-State: CLOSED 🟢 (service recovered!)
-════════════════════════════════════════════════════════
-10:01:30 - Back to normal operation
-           Start tracking new failures in timeWindow
-```
-
----
-
-## Storage
-
-Circuit state is stored per service using Redis or Cache.
-
-**Key Structure:**
-```
-circuit_breaker:{service}:state          → "closed", "open", "half_open"
-circuit_breaker:{service}:failures       → 3
-circuit_breaker:{service}:successes      → 15
-circuit_breaker:{service}:opened_at      → 1703520000
-circuit_breaker:{service}:half_open_successes → 2
-```
-
-**Multiple Services:**
-```
-circuit_breaker:api.stripe.com:state     → "open"       (down)
-circuit_breaker:api.twilio.com:state     → "closed"     (working)
-circuit_breaker:payment.internal:state   → "half_open"  (testing)
-```
-
----
-
-## FAQ - Common Questions
-
-### Q1: What does "trip" mean?
-
-**Trip** means the circuit breaker transitions from CLOSED to OPEN state.
-- Circuit "trips" when too many failures detected
-- Once tripped, circuit is OPEN and blocks all requests
-- Protects your system from cascading failures
-
----
-
-### Q2: Why do we need HALF_OPEN state? Can't we just go CLOSED → OPEN → CLOSED?
-
-**Without HALF_OPEN you'd have problems:**
-
-❌ **Option A:** Circuit stays OPEN forever
-- Service recovers but circuit never reopens
-- You're blocking requests to a healthy service
-
-❌ **Option B:** Circuit auto-closes after timeout
-- Thousands of requests flood the still-broken service
-- Circuit trips again immediately
-- Creates flapping: OPEN → CLOSED → OPEN → CLOSED...
-
-✅ **HALF_OPEN solves this:**
-- Test with just a FEW requests (e.g., 3)
-- If they succeed → Service is healthy → Fully reopen
-- If any fail → Service still broken → Wait longer
-- Prevents flooding, enables controlled recovery
-
----
-
-### Q3: What's the difference between `timeWindow` and `intervalToHalfOpen`?
-
-**Completely different purposes:**
-
-**`timeWindow(60)`** = Memory span (ALWAYS active)
-- How long to track failures/successes
-- Creates a sliding 60-second window
-- Old failures automatically expire
-- Used in ALL states (CLOSED, OPEN, HALF_OPEN)
-- Example: "Only count failures from last 60 seconds"
-
-**`intervalToHalfOpen(30)`** = Recovery cooldown (only when OPEN)
-- How long to wait after circuit opens before testing
-- Only used when circuit is OPEN
-- Measured from when circuit opened (openedAt timestamp)
-- Example: "Wait 30 seconds before trying recovery"
-
-**Think of it:**
-- `timeWindow` = How far back you look when counting
-- `intervalToHalfOpen` = How long you wait before testing recovery
-
----
-
-### Q4: Is `successThreshold(3)` tracked within the `timeWindow(60)`?
-
-**NO!** There are TWO types of success tracking:
-
-**1. Regular successes (CLOSED state)** - YES, tracked in timeWindow
-```
-Used for calculating failure rate in Rate Strategy
-Example: 10 requests = 6 failures + 4 successes (within 60s)
-These successes ARE in timeWindow
-```
-
-**2. Half-open successes (HALF_OPEN state)** - NO, NOT in timeWindow
-```
-Separate counter: halfOpenSuccessCount
-Used ONLY to decide when to close circuit
-NOT subject to timeWindow expiration
-Resets to 0 on any failure
-These successes are NOT in timeWindow
-```
-
-**Example:**
-```
-State: HALF_OPEN
-10:01:00 - Success ✓ (counter = 1)
-10:01:05 - Success ✓ (counter = 2)
-10:01:10 - Success ✓ (counter = 3) → CLOSE!
-
-Only took 10 seconds (not 60 seconds)
-These are consecutive, immediate successes
-No timeWindow involved here!
-```
-
----
-
-### Q5: When does the `intervalToHalfOpen` timer start?
-
-**It starts the moment the circuit trips to OPEN.**
-
-When circuit opens, we store `openedAt` timestamp. The timer counts from there.
-
-**Example:**
-```php
-->intervalToHalfOpen(30)  // 30 seconds
-```
-
-```
-10:00:00 - Circuit trips to OPEN
-           openedAt = 10:00:00 ← Timer starts HERE
-
-10:00:10 - Request arrives
-           (10:00:10 - 10:00:00) = 10s < 30s
-           → Still in cooldown, BLOCKED
-
-10:00:30 - Request arrives
-           (10:00:30 - 10:00:00) = 30s >= 30s
-           → Cooldown finished, transition to HALF_OPEN
-```
-
----
-
-### Q6: Can I use different strategies for different services?
-
-**Yes!** Each client can have its own configuration:
-
-```php
-class PaymentClient extends Client
-{
-    protected function circuitBreakerConfig(): ?CircuitBreakerConfig
-    {
-        // Strict - trip after just 3 failures
-        return CountStrategyConfig::make()
-            ->failureCountThreshold(3)
-            ->intervalToHalfOpen(120);  // 2 min cooldown
-    }
-}
-
-class EmailClient extends Client
-{
-    protected function circuitBreakerConfig(): ?CircuitBreakerConfig
-    {
-        // Flexible - trip at 50% failure rate
-        return RateStrategyConfig::make()
-            ->failureRateThreshold(50.0)
-            ->minimumRequests(10);
-    }
-}
-```
-
----
-
-### Q7: How do failures expire from the timeWindow?
-
-**Automatically, based on TTL (Time To Live).**
-
-When a failure is recorded, it's stored with expiration = `timeWindow` seconds.
-
-**Example:**
-```php
-->timeWindow(60)  // 60 seconds
-```
-
-```
-10:00:00 - Failure recorded
-           Stored in Redis/Cache with TTL = 60s
-           Will expire at 10:01:00
-
-10:00:30 - Check failure count
-           Still counted (30s < 60s TTL)
-
-10:01:00 - Failure expires automatically
-           No longer counted
-
-10:01:30 - Check failure count
-           This failure is gone (expired)
-```
-
-Redis/Cache automatically removes expired keys. You don't need to manually clean up.
-
----
-
-### Q8: What happens if service fails during HALF_OPEN testing?
-
-**Circuit immediately trips back to OPEN.**
-
-```
-State: HALF_OPEN
-Request 1: 200 ✓ (successCount = 1)
-Request 2: 200 ✓ (successCount = 2)
-Request 3: 500 ✗ FAILURE!
-
-Action:
-→ Circuit goes back to OPEN 🔴
-→ Reset successCount to 0
-→ Set NEW openedAt timestamp
-→ Must wait another intervalToHalfOpen (e.g., 30s)
-→ Then try recovery again
-```
-
-**This prevents:**
-- Premature recovery (service not fully healthy)
-- Flapping between states
-- Overwhelming fragile services
-
----
-
-### Q9: How do I monitor circuit breaker state?
-
-**Check storage directly or add logging:**
-
-```php
-// Check state in Redis/Cache
-$state = Cache::get('circuit_breaker:api.stripe.com:state');
-// Returns: "closed", "open", or "half_open"
-
-$failures = Cache::get('circuit_breaker:api.stripe.com:failures');
-```
-
-Or add logging in your client:
-
-```php
-try {
-    $response = $client->do($request);
-} catch (CircuitOpenException $e) {
-    \Log::warning('Circuit open for service', [
-        'service' => $e->getService(),
-        'opened_at' => Cache::get("circuit_breaker:{$e->getService()}:opened_at"),
-    ]);
-
-    return ['error' => 'Service temporarily unavailable'];
-}
-```
-
----
-
-### Q10: What status codes are considered failures?
-
-**By default: 5xx errors (500, 502, 503, 504)**
-
-You can customize:
-
-```php
-RateStrategyConfig::make()
-    ->failureStatusCodes([500, 502, 503, 504, 429]);  // Add 429 Too Many Requests
-```
-
-**Success = 2xx and 3xx**
-**Failure = 4xx and 5xx (or custom list)**
-
-Note: Network errors and exceptions are always considered failures.
-
----
-
-## Best Practices
-
-1. **Use Rate Strategy for high-traffic services** (external APIs)
-2. **Use Count Strategy for critical low-traffic services** (payments)
-3. **Set appropriate timeWindow** - Too short = premature trips, too long = slow recovery
-4. **Don't set intervalToHalfOpen too low** - Give service time to truly recover
-5. **Use Redis for production** - Better performance than cache
-6. **Monitor circuit state** - Log when circuits open/close
-7. **Handle CircuitOpenException gracefully** - Return user-friendly errors
-8. **Test your thresholds** - Start conservative, adjust based on metrics
-
----
diff --git a/src/CircuitBreaker/CircuitBreaker.php b/src/CircuitBreaker/CircuitBreaker.php
index cef4064..a70e467 100644
--- a/src/CircuitBreaker/CircuitBreaker.php
+++ b/src/CircuitBreaker/CircuitBreaker.php
@@ -2,12 +2,12 @@
 
 namespace Idaratech\Integrations\CircuitBreaker;
 
+use Carbon\Carbon;
 use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
 use Idaratech\Integrations\CircuitBreaker\Contracts\StrategyInterface;
 use Idaratech\Integrations\CircuitBreaker\Enums\CircuitState;
-use Idaratech\Integrations\CircuitBreaker\Exceptions\CircuitOpenException;
 use Idaratech\Integrations\Logger;
-use Throwable;
+use Psr\SimpleCache\InvalidArgumentException;
 
 class CircuitBreaker
 {
@@ -17,11 +17,20 @@ class CircuitBreaker
     /** @var int[] */
     protected array $ignoredStatusCodes = [];
 
+    /**
+     * @param CircuitBreakerStorage $storage
+     * @param StrategyInterface $strategy
+     */
     public function __construct(
         protected readonly CircuitBreakerStorage $storage,
         protected readonly StrategyInterface $strategy
     ) {}
 
+    /**
+     * @param string $service
+     * @return bool
+     * @throws InvalidArgumentException
+     */
     public function isAvailable(string $service): bool
     {
         $state = $this->getState($service);
@@ -38,36 +47,21 @@ public function isAvailable(string $service): bool
         return false;
     }
 
+    /**
+     * @param string $service
+     * @return CircuitState
+     * @throws InvalidArgumentException
+     */
     public function getState(string $service): CircuitState
     {
         return $this->storage->getState($service);
     }
 
     /**
-     * @template T
-     * @param callable(): T $callable
-     * @param callable(): T|null $fallback
-     * @return T
-     * @throws CircuitOpenException|Throwable
+     * @param string $service
+     * @return void
+     * @throws InvalidArgumentException
      */
-    public function call(string $service, callable $callable, ?callable $fallback = null): mixed
-    {
-        if (! $this->isAvailable($service)) {
-            return $fallback
-                ? $fallback()
-                : throw new CircuitOpenException($service, CircuitState::OPEN);
-        }
-
-        try {
-            $result = $callable();
-            $this->success($service);
-            return $result;
-        } catch (Throwable $e) {
-            $this->failure($service);
-            throw $e;
-        }
-    }
-
     public function success(string $service): void
     {
         $state = $this->getState($service);
@@ -84,6 +78,11 @@ public function success(string $service): void
         $this->strategy->recordSuccess($service, $this->storage);
     }
 
+    /**
+     * @param string $service
+     * @return void
+     * @throws InvalidArgumentException
+     */
     public function failure(string $service): void
     {
         $state = $this->getState($service);
@@ -100,6 +99,12 @@ public function failure(string $service): void
         }
     }
 
+    /**
+     * @param string $service
+     * @param int $statusCode
+     * @return void
+     * @throws InvalidArgumentException
+     */
     public function recordHttpResult(string $service, int $statusCode): void
     {
         $this->isFailureStatusCode($statusCode)
@@ -107,6 +112,10 @@ public function recordHttpResult(string $service, int $statusCode): void
             : $this->success($service);
     }
 
+    /**
+     * @param int $statusCode
+     * @return bool
+     */
     public function isFailureStatusCode(int $statusCode): bool
     {
         if (in_array($statusCode, $this->ignoredStatusCodes, true)) {
@@ -120,37 +129,44 @@ public function isFailureStatusCode(int $statusCode): bool
         return $statusCode >= 500;
     }
 
-    /** @param int[] $statusCodes */
+    /**
+     * @param int[] $statusCodes
+     * @return self
+     */
     public function setFailureStatusCodes(array $statusCodes): self
     {
         $this->failureStatusCodes = $statusCodes;
         return $this;
     }
 
-    /** @param int[] $statusCodes */
+    /**
+     * @param int[] $statusCodes
+     * @return self
+     */
     public function setIgnoredStatusCodes(array $statusCodes): self
     {
         $this->ignoredStatusCodes = $statusCodes;
         return $this;
     }
 
-    public function getStorage(): CircuitBreakerStorage
-    {
-        return $this->storage;
-    }
-
-    public function getStrategy(): StrategyInterface
-    {
-        return $this->strategy;
-    }
-
+    /**
+     * @param string $service
+     * @param CircuitState $newState
+     * @return void
+     * @throws InvalidArgumentException
+     */
     protected function transitionTo(string $service, CircuitState $newState): void
     {
         if ($this->getState($service) === $newState) {
             return;
         }
 
-        $this->storage->setState($service, $newState);
+        // Calculate TTL for OPEN state (auto-cleanup after 1 hour for abandoned circuits)
+        $ttl = $newState === CircuitState::OPEN
+            ? 3600
+            : null;
+
+        $this->storage->setState($service, $newState, $ttl);
 
         match ($newState) {
             CircuitState::OPEN => $this->onOpen($service),
@@ -159,22 +175,50 @@ protected function transitionTo(string $service, CircuitState $newState): void
         };
     }
 
+    /**
+     * @param string $service
+     * @return void
+     */
     protected function onOpen(string $service): void
     {
         $this->storage->setOpenedAt($service, time());
         $this->storage->resetHalfOpenSuccess($service);
-        Logger::warning('CIRCUIT_BREAKER_TRIPPED', ['service' => $service]);
+
+        Logger::warning('CIRCUIT_BREAKER_TRIPPED', [
+            'service' => $service,
+            'failures' => $this->storage->getFailureCount($service),
+            'successes' => $this->storage->getSuccessCount($service),
+            'timestamp' => Carbon::now()->toDateTimeString(),
+        ]);
     }
 
+    /**
+     * @param string $service
+     * @return void
+     */
     protected function onHalfOpen(string $service): void
     {
         $this->storage->resetHalfOpenSuccess($service);
-        Logger::info('CIRCUIT_BREAKER_HALF_OPEN', ['service' => $service]);
+
+        Logger::info('CIRCUIT_BREAKER_HALF_OPEN', [
+            'service' => $service,
+            'message' => 'Testing recovery',
+            'timestamp' => Carbon::now()->toDateTimeString(),
+        ]);
     }
 
+    /**
+     * @param string $service
+     * @return void
+     */
     protected function onClosed(string $service): void
     {
         $this->storage->reset($service);
-        Logger::info('CIRCUIT_BREAKER_CLOSED', ['service' => $service]);
+
+        Logger::info('CIRCUIT_BREAKER_CLOSED', [
+            'service' => $service,
+            'message' => 'Service recovered',
+            'timestamp' => Carbon::now()->toDateTimeString(),
+        ]);
     }
 }
\ No newline at end of file
diff --git a/src/CircuitBreaker/CircuitBreakerFactory.php b/src/CircuitBreaker/CircuitBreakerFactory.php
index 19ca4c5..3a5c50a 100644
--- a/src/CircuitBreaker/CircuitBreakerFactory.php
+++ b/src/CircuitBreaker/CircuitBreakerFactory.php
@@ -12,15 +12,12 @@
 use Idaratech\Integrations\CircuitBreaker\Strategy\CountStrategy;
 use Idaratech\Integrations\CircuitBreaker\Strategy\RateStrategy;
 
-/**
- * Factory for creating circuit breaker instances from configuration objects.
- */
 class CircuitBreakerFactory
 {
     /**
      * Create a circuit breaker from a configuration object.
      *
-     * @param CircuitBreakerConfig $config Configuration object
+     * @param CircuitBreakerConfig $config
      * @return CircuitBreaker
      */
     public function createFromConfig(CircuitBreakerConfig $config): CircuitBreaker
@@ -30,7 +27,6 @@ public function createFromConfig(CircuitBreakerConfig $config): CircuitBreaker
 
         $circuitBreaker = new CircuitBreaker($storage, $strategy);
 
-        // Set optional configurations
         $circuitBreaker->setFailureStatusCodes($config->getFailureStatusCodes());
         $circuitBreaker->setIgnoredStatusCodes($config->getIgnoredStatusCodes());
 
@@ -46,7 +42,7 @@ public function createFromConfig(CircuitBreakerConfig $config): CircuitBreaker
     protected function createStorageFromConfig(CircuitBreakerConfig $config): CircuitBreakerStorage
     {
         $storageType = $config->getStorage();
-        $prefix = $config->getPrefix() ?? config('integrations.circuit_breaker.prefix', 'circuit_breaker');
+        $prefix = $config->getPrefix();
 
         if ($storageType === 'redis') {
             return new RedisStorage($prefix, $config->getRedisConnection());
diff --git a/src/CircuitBreaker/CircuitBreakerInterceptor.php b/src/CircuitBreaker/CircuitBreakerInterceptor.php
index e730f86..8caf304 100644
--- a/src/CircuitBreaker/CircuitBreakerInterceptor.php
+++ b/src/CircuitBreaker/CircuitBreakerInterceptor.php
@@ -5,6 +5,7 @@
 use Idaratech\Integrations\CircuitBreaker\Exceptions\CircuitOpenException;
 use Idaratech\Integrations\Contracts\IRequest;
 use Idaratech\Integrations\Contracts\IResponse;
+use Psr\SimpleCache\InvalidArgumentException;
 
 class CircuitBreakerInterceptor
 {
@@ -20,6 +21,7 @@ public function __construct(CircuitBreaker $circuitBreaker)
      * Check if the circuit allows the request (before middleware).
      *
      * @throws CircuitOpenException
+     * @throws InvalidArgumentException
      */
     public function before(IRequest $request): void
     {
@@ -35,6 +37,7 @@ public function before(IRequest $request): void
 
     /**
      * Record the result after the request completes (after middleware).
+     * @throws InvalidArgumentException
      */
     public function after(IRequest $request, IResponse $response): void
     {
@@ -46,8 +49,10 @@ public function after(IRequest $request, IResponse $response): void
 
     /**
      * Record a failure when an exception occurs.
+     * @param IRequest $request
+     * @throws InvalidArgumentException
      */
-    public function onException(IRequest $request, \Throwable $exception): void
+    public function onException(IRequest $request): void
     {
         $service = $this->resolveServiceName($request);
         $this->circuitBreaker->failure($service);
@@ -55,6 +60,8 @@ public function onException(IRequest $request, \Throwable $exception): void
 
     /**
      * Resolve the service name from the request.
+     * @param IRequest $request
+     * @return string
      */
     protected function resolveServiceName(IRequest $request): string
     {
diff --git a/src/CircuitBreaker/Config/CircuitBreakerConfig.php b/src/CircuitBreaker/Config/CircuitBreakerConfig.php
index 2f7bc04..32d48b9 100644
--- a/src/CircuitBreaker/Config/CircuitBreakerConfig.php
+++ b/src/CircuitBreaker/Config/CircuitBreakerConfig.php
@@ -2,12 +2,6 @@
 
 namespace Idaratech\Integrations\CircuitBreaker\Config;
 
-/**
- * Abstract base class for circuit breaker configuration.
- *
- * Provides shared configuration properties and methods that are common
- * to all circuit breaker strategies.
- */
 abstract class CircuitBreakerConfig
 {
     protected int $timeWindow = 60;
@@ -21,13 +15,16 @@ abstract class CircuitBreakerConfig
     protected array $ignoredStatusCodes = [];
 
     /**
-     * Set the time window in seconds to track requests/failures.
-     *
-     * @param int $seconds Time window in seconds
+     * @param int $seconds
      * @return static
      */
     public function timeWindow(int $seconds): static
     {
+        if ($seconds < 1) {
+            throw new \InvalidArgumentException(
+                "Time window must be at least 1 seconds, got: {$seconds}"
+            );
+        }
         $this->timeWindow = $seconds;
         return $this;
     }
@@ -35,11 +32,16 @@ public function timeWindow(int $seconds): static
     /**
      * Set the interval in seconds before attempting recovery (OPEN → HALF_OPEN).
      *
-     * @param int $seconds Interval in seconds
+     * @param int $seconds
      * @return static
      */
     public function intervalToHalfOpen(int $seconds): static
     {
+        if ($seconds < 1) {
+            throw new \InvalidArgumentException(
+                "Interval to half-open must be at least 1 second, got: {$seconds}"
+            );
+        }
         $this->intervalToHalfOpen = $seconds;
         return $this;
     }
@@ -47,11 +49,16 @@ public function intervalToHalfOpen(int $seconds): static
     /**
      * Set the number of consecutive successes needed to close the circuit.
      *
-     * @param int $count Number of successes
+     * @param int $count
      * @return static
      */
     public function successThreshold(int $count): static
     {
+        if ($count < 1) {
+            throw new \InvalidArgumentException(
+                "Success threshold must be at least 1, got: {$count}"
+            );
+        }
         $this->successThreshold = $count;
         return $this;
     }
@@ -59,7 +66,7 @@ public function successThreshold(int $count): static
     /**
      * Set the storage adapter type ('redis' or 'cache').
      *
-     * @param string $type Storage type
+     * @param string $type
      * @return static
      */
     public function storage(string $type): static
@@ -71,7 +78,7 @@ public function storage(string $type): static
     /**
      * Set the cache key prefix for circuit breaker state.
      *
-     * @param string $prefix Cache key prefix
+     * @param string $prefix
      * @return static
      */
     public function prefix(string $prefix): static
@@ -83,7 +90,7 @@ public function prefix(string $prefix): static
     /**
      * Set the Redis connection name (only for Redis storage).
      *
-     * @param string|null $connection Redis connection name
+     * @param string|null $connection
      * @return static
      */
     public function redisConnection(?string $connection): static
@@ -95,7 +102,7 @@ public function redisConnection(?string $connection): static
     /**
      * Set the cache store name (only for cache storage).
      *
-     * @param string|null $store Cache store name
+     * @param string|null $store
      * @return static
      */
     public function cacheStore(?string $store): static
@@ -107,7 +114,7 @@ public function cacheStore(?string $store): static
     /**
      * Set the HTTP status codes that are considered failures.
      *
-     * @param array $codes Array of HTTP status codes
+     * @param array $codes
      * @return static
      */
     public function failureStatusCodes(array $codes): static
@@ -119,7 +126,7 @@ public function failureStatusCodes(array $codes): static
     /**
      * Set the HTTP status codes that should be ignored by the circuit breaker.
      *
-     * @param array $codes Array of HTTP status codes
+     * @param array $codes
      * @return static
      */
     public function ignoredStatusCodes(array $codes): static
@@ -128,8 +135,6 @@ public function ignoredStatusCodes(array $codes): static
         return $this;
     }
 
-    // Getters
-
     public function getTimeWindow(): int
     {
         return $this->timeWindow;
@@ -150,9 +155,9 @@ public function getStorage(): string
         return $this->storage;
     }
 
-    public function getPrefix(): ?string
+    public function getPrefix(): string
     {
-        return $this->prefix;
+        return $this->prefix ?? config('integrations.circuit_breaker_prefix', 'cb:app');
     }
 
     public function getRedisConnection(): ?string
diff --git a/src/CircuitBreaker/Config/CountStrategyConfig.php b/src/CircuitBreaker/Config/CountStrategyConfig.php
index bfd96f9..9dc293d 100644
--- a/src/CircuitBreaker/Config/CountStrategyConfig.php
+++ b/src/CircuitBreaker/Config/CountStrategyConfig.php
@@ -2,15 +2,9 @@
 
 namespace Idaratech\Integrations\CircuitBreaker\Config;
 
-/**
- * Configuration for count-based circuit breaker strategy.
- *
- * Trips the circuit when the absolute failure count exceeds a threshold
- * within a specified time window.
- */
 class CountStrategyConfig extends CircuitBreakerConfig
 {
-    protected int $failureCountThreshold = 5;
+    protected ?int $failureCountThreshold = null;
 
     /**
      * Create a new count strategy configuration instance.
@@ -26,20 +20,36 @@ public static function make(): self
      * Set the failure count threshold.
      * Circuit trips when this many failures occur within the time window.
      *
-     * @param int $count Number of failures to trip circuit
+     * @param int $count
      * @return self
      */
     public function failureCountThreshold(int $count): self
     {
+        if ($count < 1) {
+            throw new \InvalidArgumentException(
+                "Failure count threshold must be at least 1, got: {$count}"
+            );
+        }
         $this->failureCountThreshold = $count;
         return $this;
     }
 
+    /**
+     * @return int
+     */
     public function getFailureCountThreshold(): int
     {
+        if ($this->failureCountThreshold === null) {
+            throw new \LogicException(
+                'failureCountThreshold not set. Call ->failureCountThreshold() before using this config.'
+            );
+        }
         return $this->failureCountThreshold;
     }
 
+    /**
+     * @return string
+     */
     public function getStrategy(): string
     {
         return 'count';
diff --git a/src/CircuitBreaker/Config/RateStrategyConfig.php b/src/CircuitBreaker/Config/RateStrategyConfig.php
index b43c41f..ca92b88 100644
--- a/src/CircuitBreaker/Config/RateStrategyConfig.php
+++ b/src/CircuitBreaker/Config/RateStrategyConfig.php
@@ -2,16 +2,10 @@
 
 namespace Idaratech\Integrations\CircuitBreaker\Config;
 
-/**
- * Configuration for rate-based circuit breaker strategy.
- *
- * Trips the circuit when the failure rate (percentage) exceeds a threshold
- * within a specified time window.
- */
 class RateStrategyConfig extends CircuitBreakerConfig
 {
-    protected float $failureRateThreshold = 50.0;
-    protected int $minimumRequests = 10;
+    protected ?float $failureRateThreshold = null;
+    protected ?int $minimumRequests = null;
 
     /**
      * Create a new rate strategy configuration instance.
@@ -27,11 +21,16 @@ public static function make(): self
      * Set the failure rate threshold percentage (0-100).
      * Circuit trips when failure rate exceeds this percentage.
      *
-     * @param float $percentage Failure rate percentage
+     * @param float $percentage
      * @return self
      */
     public function failureRateThreshold(float $percentage): self
     {
+        if ($percentage < 0 || $percentage > 100) {
+            throw new \InvalidArgumentException(
+                "Failure rate threshold must be between 0 and 100, got: {$percentage}"
+            );
+        }
         $this->failureRateThreshold = $percentage;
         return $this;
     }
@@ -40,22 +39,37 @@ public function failureRateThreshold(float $percentage): self
      * Set the minimum number of requests before evaluating failure rate.
      * Circuit won't trip until this many requests have been made.
      *
-     * @param int $count Minimum requests
+     * @param int $count
      * @return self
      */
     public function minimumRequests(int $count): self
     {
+        if ($count < 1) {
+            throw new \InvalidArgumentException(
+                "Minimum requests must be at least 1, got: {$count}"
+            );
+        }
         $this->minimumRequests = $count;
         return $this;
     }
 
     public function getFailureRateThreshold(): float
     {
+        if ($this->failureRateThreshold === null) {
+            throw new \LogicException(
+                'failureRateThreshold not set. Call ->failureRateThreshold() before using this config.'
+            );
+        }
         return $this->failureRateThreshold;
     }
 
     public function getMinimumRequests(): int
     {
+        if ($this->minimumRequests === null) {
+            throw new \LogicException(
+                'minimumRequests not set. Call ->minimumRequests() before using this config.'
+            );
+        }
         return $this->minimumRequests;
     }
 
diff --git a/src/CircuitBreaker/Contracts/CircuitBreakerStorage.php b/src/CircuitBreaker/Contracts/CircuitBreakerStorage.php
index 3c7eabc..c1b6cf8 100644
--- a/src/CircuitBreaker/Contracts/CircuitBreakerStorage.php
+++ b/src/CircuitBreaker/Contracts/CircuitBreakerStorage.php
@@ -3,71 +3,92 @@
 namespace Idaratech\Integrations\CircuitBreaker\Contracts;
 
 use Idaratech\Integrations\CircuitBreaker\Enums\CircuitState;
+use Psr\SimpleCache\InvalidArgumentException;
 
 interface CircuitBreakerStorage
 {
     /**
      * Get the current state of the circuit.
+     * @param string $service
+     * @return CircuitState
+     * @throws InvalidArgumentException
      */
     public function getState(string $service): CircuitState;
 
     /**
      * Set the state of the circuit.
+     * @param string $service
+     * @param CircuitState $state
+     * @param int|null $ttl
      */
     public function setState(string $service, CircuitState $state, ?int $ttl = null): void;
 
     /**
      * Increment the failure count for a service.
+     * @param string $service
+     * @param int $timeWindow
      */
     public function incrementFailure(string $service, int $timeWindow): int;
 
     /**
      * Increment the success count for a service.
+     * @param string $service
+     * @param int $timeWindow
      */
     public function incrementSuccess(string $service, int $timeWindow): int;
 
     /**
      * Get the failure count for a service.
+     * @param string $service
      */
     public function getFailureCount(string $service): int;
 
     /**
      * Get the success count for a service.
+     * @param string $service
      */
     public function getSuccessCount(string $service): int;
 
     /**
      * Get the total request count (failures + successes).
+     * @param string $service
      */
     public function getRequestCount(string $service): int;
 
     /**
      * Reset all circuit breaker state for a service.
+     * @param string $service
      */
     public function reset(string $service): void;
 
     /**
      * Get the timestamp when the circuit was opened.
+     * @param string $service
      */
     public function getOpenedAt(string $service): ?int;
 
     /**
      * Set the timestamp when the circuit was opened.
+     * @param string $service
+     * @param int $timestamp
      */
     public function setOpenedAt(string $service, int $timestamp): void;
 
     /**
      * Get the success count in half-open state.
+     * @param string $service
      */
     public function getHalfOpenSuccessCount(string $service): int;
 
     /**
      * Increment the success count in half-open state.
+     * @param string $service
      */
     public function incrementHalfOpenSuccess(string $service): int;
 
     /**
      * Reset the success count in half-open state.
+     * @param string $service
      */
     public function resetHalfOpenSuccess(string $service): void;
 }
diff --git a/src/CircuitBreaker/Contracts/StrategyInterface.php b/src/CircuitBreaker/Contracts/StrategyInterface.php
index c8f67be..0f18793 100644
--- a/src/CircuitBreaker/Contracts/StrategyInterface.php
+++ b/src/CircuitBreaker/Contracts/StrategyInterface.php
@@ -6,46 +6,74 @@ interface StrategyInterface
 {
     /**
      * Check if the circuit should trip to open state.
+     *
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return bool
      */
     public function shouldTrip(string $service, CircuitBreakerStorage $storage): bool;
 
     /**
      * Check if the circuit should transition from half-open to closed.
+     *
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return bool
      */
     public function shouldClose(string $service, CircuitBreakerStorage $storage): bool;
 
     /**
      * Check if enough time has passed to transition from open to half-open.
+     *
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return bool
      */
     public function shouldAttemptReset(string $service, CircuitBreakerStorage $storage): bool;
 
     /**
      * Record a successful operation.
+     *
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return void
      */
     public function recordSuccess(string $service, CircuitBreakerStorage $storage): void;
 
     /**
      * Record a failed operation.
+     *
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return void
      */
     public function recordFailure(string $service, CircuitBreakerStorage $storage): void;
 
     /**
      * Get the time window in seconds.
+     *
+     * @return int
      */
     public function getTimeWindow(): int;
 
     /**
      * Get the interval to half-open in seconds.
+     *
+     * @return int
      */
     public function getIntervalToHalfOpen(): int;
 
     /**
      * Get the minimum requests before evaluation.
+     *
+     * @return int
      */
     public function getMinimumRequests(): int;
 
     /**
      * Get the success threshold for half-open state.
+     *
+     * @return int
      */
     public function getSuccessThreshold(): int;
-}
+}
\ No newline at end of file
diff --git a/src/CircuitBreaker/Exceptions/CircuitOpenException.php b/src/CircuitBreaker/Exceptions/CircuitOpenException.php
index bfe5621..bc28fea 100644
--- a/src/CircuitBreaker/Exceptions/CircuitOpenException.php
+++ b/src/CircuitBreaker/Exceptions/CircuitOpenException.php
@@ -10,6 +10,11 @@ class CircuitOpenException extends Exception
     protected string $service;
     protected CircuitState $state;
 
+    /**
+     * @param string $service
+     * @param CircuitState $state
+     * @param Exception|null $previous
+     */
     public function __construct(
         string $service,
         CircuitState $state = CircuitState::OPEN,
diff --git a/src/CircuitBreaker/Storage/CacheStorage.php b/src/CircuitBreaker/Storage/CacheStorage.php
index 357b91f..58508c8 100644
--- a/src/CircuitBreaker/Storage/CacheStorage.php
+++ b/src/CircuitBreaker/Storage/CacheStorage.php
@@ -3,7 +3,6 @@
 namespace Idaratech\Integrations\CircuitBreaker\Storage;
 
 use Illuminate\Contracts\Cache\Repository;
-use Illuminate\Support\Carbon;
 use Illuminate\Support\Facades\Cache;
 use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
 use Idaratech\Integrations\CircuitBreaker\Enums\CircuitState;
@@ -14,18 +13,29 @@ class CacheStorage implements CircuitBreakerStorage
     protected Repository $cache;
     protected string $prefix;
 
+    /**
+     * @param string $prefix
+     * @param string|null $store
+     */
     public function __construct(string $prefix = 'circuit_breaker', ?string $store = null)
     {
         $this->cache = Cache::store($store);
         $this->prefix = $prefix;
     }
 
+    /**
+     * @param string $service
+     * @param string $suffix
+     * @return string
+     */
     protected function key(string $service, string $suffix): string
     {
         return "{$this->prefix}:{$service}:{$suffix}";
     }
 
     /**
+     * @param string $service
+     * @return CircuitState
      * @throws InvalidArgumentException
      */
     public function getState(string $service): CircuitState
@@ -35,6 +45,12 @@ public function getState(string $service): CircuitState
         return CircuitState::tryFrom($state ?: '') ?? CircuitState::CLOSED;
     }
 
+    /**
+     * @param string $service
+     * @param CircuitState $state
+     * @param int|null $ttl
+     * @return void
+     */
     public function setState(string $service, CircuitState $state, ?int $ttl = null): void
     {
         $key = $this->key($service, 'state');
@@ -46,25 +62,41 @@ public function setState(string $service, CircuitState $state, ?int $ttl = null)
         }
     }
 
+    /**
+     * @param string $service
+     * @param int $timeWindow
+     * @return int
+     */
     public function incrementFailure(string $service, int $timeWindow): int
     {
         return $this->incrementWithExpiry($this->key($service, 'failures'), $timeWindow);
     }
 
+    /**
+     * @param string $service
+     * @param int $timeWindow
+     * @return int
+     */
     public function incrementSuccess(string $service, int $timeWindow): int
     {
         return $this->incrementWithExpiry($this->key($service, 'successes'), $timeWindow);
     }
 
+    /**
+     * @param string $key
+     * @param int $ttl
+     * @return int
+     */
     protected function incrementWithExpiry(string $key, int $ttl): int
     {
-        // Add key with TTL if it doesn't exist, then increment
         $this->cache->add($key, 0, $ttl);
 
         return (int)$this->cache->increment($key);
     }
 
     /**
+     * @param string $service
+     * @return int
      * @throws InvalidArgumentException
      */
     public function getFailureCount(string $service): int
@@ -73,6 +105,8 @@ public function getFailureCount(string $service): int
     }
 
     /**
+     * @param string $service
+     * @return int
      * @throws InvalidArgumentException
      */
     public function getSuccessCount(string $service): int
@@ -81,6 +115,8 @@ public function getSuccessCount(string $service): int
     }
 
     /**
+     * @param string $service
+     * @return int
      * @throws InvalidArgumentException
      */
     public function getRequestCount(string $service): int
@@ -88,6 +124,10 @@ public function getRequestCount(string $service): int
         return $this->getFailureCount($service) + $this->getSuccessCount($service);
     }
 
+    /**
+     * @param string $service
+     * @return void
+     */
     public function reset(string $service): void
     {
         $suffixes = ['state', 'failures', 'successes', 'opened_at', 'half_open_successes'];
@@ -98,6 +138,8 @@ public function reset(string $service): void
     }
 
     /**
+     * @param string $service
+     * @return int|null
      * @throws InvalidArgumentException
      */
     public function getOpenedAt(string $service): ?int
@@ -107,12 +149,20 @@ public function getOpenedAt(string $service): ?int
         return $value ? (int)$value : null;
     }
 
+    /**
+     * Auto-expire after 1 hour to prevent abandoned circuits
+     * @param string $service
+     * @param int $timestamp
+     * @return void
+     */
     public function setOpenedAt(string $service, int $timestamp): void
     {
         $this->cache->forever($this->key($service, 'opened_at'), $timestamp);
     }
 
     /**
+     * @param string $service
+     * @return int
      * @throws InvalidArgumentException
      */
     public function getHalfOpenSuccessCount(string $service): int
@@ -120,15 +170,19 @@ public function getHalfOpenSuccessCount(string $service): int
         return (int)$this->cache->get($this->key($service, 'half_open_successes'), 0);
     }
 
+    /**
+     * @param string $service
+     * @return int
+     */
     public function incrementHalfOpenSuccess(string $service): int
     {
-        $key = $this->key($service, 'half_open_successes');
-
-        $this->cache->add($key, 0, Carbon::now()->addDay());
-
-        return (int)$this->cache->increment($key);
+        return (int) $this->cache->increment($this->key($service, 'half_open_successes'));
     }
 
+    /**
+     * @param string $service
+     * @return void
+     */
     public function resetHalfOpenSuccess(string $service): void
     {
         $this->cache->forget($this->key($service, 'half_open_successes'));
diff --git a/src/CircuitBreaker/Storage/RedisStorage.php b/src/CircuitBreaker/Storage/RedisStorage.php
index 8102421..52f3f84 100644
--- a/src/CircuitBreaker/Storage/RedisStorage.php
+++ b/src/CircuitBreaker/Storage/RedisStorage.php
@@ -12,17 +12,30 @@ class RedisStorage implements CircuitBreakerStorage
     protected Connection $redis;
     protected string $prefix;
 
+    /**
+     * @param string $prefix
+     * @param string|null $connection
+     */
     public function __construct(string $prefix = 'circuit_breaker', ?string $connection = null)
     {
         $this->redis = Redis::connection($connection);
         $this->prefix = $prefix;
     }
 
+    /**
+     * @param string $service
+     * @param string $suffix
+     * @return string
+     */
     protected function key(string $service, string $suffix): string
     {
         return "{$this->prefix}:{$service}:{$suffix}";
     }
 
+    /**
+     * @param string $service
+     * @return CircuitState
+     */
     public function getState(string $service): CircuitState
     {
         $state = $this->redis->get($this->key($service, 'state'));
@@ -30,6 +43,12 @@ public function getState(string $service): CircuitState
         return CircuitState::tryFrom($state ?: '') ?? CircuitState::CLOSED;
     }
 
+    /**
+     * @param string $service
+     * @param CircuitState $state
+     * @param int|null $ttl
+     * @return void
+     */
     public function setState(string $service, CircuitState $state, ?int $ttl = null): void
     {
         $key = $this->key($service, 'state');
@@ -41,39 +60,81 @@ public function setState(string $service, CircuitState $state, ?int $ttl = null)
         }
     }
 
+    /**
+     * Increment failure count with expiry
+     * @param string $service
+     * @param int $timeWindow
+     * @return int
+     */
     public function incrementFailure(string $service, int $timeWindow): int
     {
         return $this->incrementWithExpiry($this->key($service, 'failures'), $timeWindow);
     }
 
+    /**
+     * Increment success count with expiry
+     * @param string $service
+     * @param int $timeWindow
+     * @return int
+     */
     public function incrementSuccess(string $service, int $timeWindow): int
     {
         return $this->incrementWithExpiry($this->key($service, 'successes'), $timeWindow);
     }
 
+    /**
+     * Lua script for atomic increment with conditional TTL
+     * This prevents race conditions in high concurrency scenarios
+     * TTL is only set on first increment (when counter = 1)
+     * Subsequent increments preserve the original TTL for true sliding window behavior
+     * @param string $key
+     * @param int $ttl
+     * @return int
+     */
     protected function incrementWithExpiry(string $key, int $ttl): int
     {
-        $count = $this->redis->incr($key);
-        $this->redis->expire($key, $ttl);
+        $script = <<<'LUA'
+            local current = redis.call('INCR', KEYS[1])
+            if current == 1 then
+                redis.call('EXPIRE', KEYS[1], ARGV[1])
+            end
+            return current
+LUA;
 
-        return (int) $count;
+        return (int) $this->redis->eval($script, 1, $key, $ttl);
     }
 
+    /**
+     * @param string $service
+     * @return int
+     */
     public function getFailureCount(string $service): int
     {
         return (int) ($this->redis->get($this->key($service, 'failures')) ?: 0);
     }
 
+    /**
+     * @param string $service
+     * @return int
+     */
     public function getSuccessCount(string $service): int
     {
         return (int) ($this->redis->get($this->key($service, 'successes')) ?: 0);
     }
 
+    /**
+     * @param string $service
+     * @return int
+     */
     public function getRequestCount(string $service): int
     {
         return $this->getFailureCount($service) + $this->getSuccessCount($service);
     }
 
+    /**
+     * @param string $service
+     * @return void
+     */
     public function reset(string $service): void
     {
         $this->redis->del([
@@ -85,6 +146,10 @@ public function reset(string $service): void
         ]);
     }
 
+    /**
+     * @param string $service
+     * @return int|null
+     */
     public function getOpenedAt(string $service): ?int
     {
         $value = $this->redis->get($this->key($service, 'opened_at'));
@@ -92,21 +157,39 @@ public function getOpenedAt(string $service): ?int
         return $value ? (int) $value : null;
     }
 
+    /**
+     * Auto-expire after 1 hour to prevent abandoned circuits
+     * @param string $service
+     * @param int $timestamp
+     * @return void
+     */
     public function setOpenedAt(string $service, int $timestamp): void
     {
         $this->redis->set($this->key($service, 'opened_at'), $timestamp);
     }
 
+    /**
+     * @param string $service
+     * @return int
+     */
     public function getHalfOpenSuccessCount(string $service): int
     {
         return (int) ($this->redis->get($this->key($service, 'half_open_successes')) ?: 0);
     }
 
+    /**
+     * @param string $service
+     * @return int
+     */
     public function incrementHalfOpenSuccess(string $service): int
     {
         return (int) $this->redis->incr($this->key($service, 'half_open_successes'));
     }
 
+    /**
+     * @param string $service
+     * @return void
+     */
     public function resetHalfOpenSuccess(string $service): void
     {
         $this->redis->del([$this->key($service, 'half_open_successes')]);
diff --git a/src/CircuitBreaker/Strategy/CountStrategy.php b/src/CircuitBreaker/Strategy/CountStrategy.php
index 52e1128..25246f7 100644
--- a/src/CircuitBreaker/Strategy/CountStrategy.php
+++ b/src/CircuitBreaker/Strategy/CountStrategy.php
@@ -5,14 +5,14 @@
 use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
 use Idaratech\Integrations\CircuitBreaker\Contracts\StrategyInterface;
 
-/**
- * Count-based circuit breaker strategy.
- *
- * Trips the circuit when the absolute failure count exceeds a threshold
- * within a specified time window.
- */
 class CountStrategy implements StrategyInterface
 {
+    /**
+     * @param int $timeWindow
+     * @param int $failureCountThreshold
+     * @param int $intervalToHalfOpen
+     * @param int $successThreshold
+     */
     public function __construct(
         protected readonly int $timeWindow = 60,
         protected readonly int $failureCountThreshold = 5,
@@ -20,6 +20,11 @@ public function __construct(
         protected readonly int $successThreshold = 3
     ) {}
 
+    /**
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return bool
+     */
     public function shouldTrip(string $service, CircuitBreakerStorage $storage): bool
     {
         $failures = $storage->getFailureCount($service);
@@ -27,6 +32,11 @@ public function shouldTrip(string $service, CircuitBreakerStorage $storage): boo
         return $failures >= $this->failureCountThreshold;
     }
 
+    /**
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return bool
+     */
     public function shouldClose(string $service, CircuitBreakerStorage $storage): bool
     {
         $halfOpenSuccesses = $storage->getHalfOpenSuccessCount($service);
@@ -34,6 +44,11 @@ public function shouldClose(string $service, CircuitBreakerStorage $storage): bo
         return $halfOpenSuccesses >= $this->successThreshold;
     }
 
+    /**
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return bool
+     */
     public function shouldAttemptReset(string $service, CircuitBreakerStorage $storage): bool
     {
         $openedAt = $storage->getOpenedAt($service);
@@ -45,38 +60,63 @@ public function shouldAttemptReset(string $service, CircuitBreakerStorage $stora
         return (time() - $openedAt) >= $this->intervalToHalfOpen;
     }
 
+    /**
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return void
+     */
     public function recordSuccess(string $service, CircuitBreakerStorage $storage): void
     {
         $storage->incrementSuccess($service, $this->timeWindow);
     }
 
+    /**
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return void
+     */
     public function recordFailure(string $service, CircuitBreakerStorage $storage): void
     {
         $storage->incrementFailure($service, $this->timeWindow);
     }
 
+    /**
+     * @return int
+     */
     public function getTimeWindow(): int
     {
         return $this->timeWindow;
     }
 
+    /**
+     * @return int
+     */
     public function getIntervalToHalfOpen(): int
     {
         return $this->intervalToHalfOpen;
     }
 
+    /**
+     * @return int
+     */
     public function getMinimumRequests(): int
     {
         return 0; // Count strategy doesn't require minimum requests
     }
 
+    /**
+     * @return int
+     */
     public function getSuccessThreshold(): int
     {
         return $this->successThreshold;
     }
 
+    /**
+     * @return int
+     */
     public function getFailureCountThreshold(): int
     {
         return $this->failureCountThreshold;
     }
-}
+}
\ No newline at end of file
diff --git a/src/CircuitBreaker/Strategy/RateStrategy.php b/src/CircuitBreaker/Strategy/RateStrategy.php
index 52f1047..f946282 100644
--- a/src/CircuitBreaker/Strategy/RateStrategy.php
+++ b/src/CircuitBreaker/Strategy/RateStrategy.php
@@ -5,14 +5,15 @@
 use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
 use Idaratech\Integrations\CircuitBreaker\Contracts\StrategyInterface;
 
-/**
- * Rate-based circuit breaker strategy.
- *
- * Trips the circuit when the failure rate exceeds a threshold percentage
- * within a specified time window.
- */
 class RateStrategy implements StrategyInterface
 {
+    /**
+     * @param int $timeWindow
+     * @param float $failureRateThreshold
+     * @param int $minimumRequests
+     * @param int $intervalToHalfOpen
+     * @param int $successThreshold
+     */
     public function __construct(
         protected readonly int $timeWindow = 60,
         protected readonly float $failureRateThreshold = 50.0,
@@ -21,6 +22,11 @@ public function __construct(
         protected readonly int $successThreshold = 3
     ) {}
 
+    /**
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return bool
+     */
     public function shouldTrip(string $service, CircuitBreakerStorage $storage): bool
     {
         $totalRequests = $storage->getRequestCount($service);
@@ -34,11 +40,21 @@ public function shouldTrip(string $service, CircuitBreakerStorage $storage): boo
         return $failureRate >= $this->failureRateThreshold;
     }
 
+    /**
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return bool
+     */
     public function shouldClose(string $service, CircuitBreakerStorage $storage): bool
     {
         return $storage->getHalfOpenSuccessCount($service) >= $this->successThreshold;
     }
 
+    /**
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return bool
+     */
     public function shouldAttemptReset(string $service, CircuitBreakerStorage $storage): bool
     {
         $openedAt = $storage->getOpenedAt($service);
@@ -46,36 +62,61 @@ public function shouldAttemptReset(string $service, CircuitBreakerStorage $stora
         return $openedAt === null || (time() - $openedAt) >= $this->intervalToHalfOpen;
     }
 
+    /**
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return void
+     */
     public function recordSuccess(string $service, CircuitBreakerStorage $storage): void
     {
         $storage->incrementSuccess($service, $this->timeWindow);
     }
 
+    /**
+     * @param string $service
+     * @param CircuitBreakerStorage $storage
+     * @return void
+     */
     public function recordFailure(string $service, CircuitBreakerStorage $storage): void
     {
         $storage->incrementFailure($service, $this->timeWindow);
     }
 
+    /**
+     * @return int
+     */
     public function getTimeWindow(): int
     {
         return $this->timeWindow;
     }
 
+    /**
+     * @return int
+     */
     public function getIntervalToHalfOpen(): int
     {
         return $this->intervalToHalfOpen;
     }
 
+    /**
+     * @return int
+     */
     public function getMinimumRequests(): int
     {
         return $this->minimumRequests;
     }
 
+    /**
+     * @return int
+     */
     public function getSuccessThreshold(): int
     {
         return $this->successThreshold;
     }
 
+    /**
+     * @return float
+     */
     public function getFailureRateThreshold(): float
     {
         return $this->failureRateThreshold;
diff --git a/src/Client.php b/src/Client.php
index 75a14f1..dea1d29 100644
--- a/src/Client.php
+++ b/src/Client.php
@@ -37,7 +37,6 @@ class Client implements ClientInterface
     protected ResponseFactory $responseFactory;
     protected ResponseMapperInterface $mapper;
     protected ?CircuitBreakerInterceptor $circuitBreakerInterceptor = null;
-    protected bool $circuitBreakerEnabled = false;
 
     public function __construct(?string $baseUri = null, array $headers = [], array $options = [])
     {
@@ -165,7 +164,7 @@ public function do(RequestInterface $request): ResponseInterface
 
             return $response;
         } catch (Throwable $e) {
-            $this->circuitBreakerInterceptor?->onException($request, $e);
+            $this->circuitBreakerInterceptor?->onException($request);
             throw $e;
         }
     }
@@ -242,25 +241,12 @@ protected function configureCircuitBreakerFromConfig(): void
     {
         $config = $this->circuitBreakerConfig();
 
-        // Priority 1: Custom config (highest priority)
+        // Only custom config supported - no global fallback
         if ($config !== null) {
             $factory = app(CircuitBreakerFactory::class);
             $circuitBreaker = $factory->createFromConfig($config);
             $this->withCircuitBreaker($circuitBreaker);
-            return;
         }
-
-        // Priority 2: Per-service enabled flag
-        if ($this->circuitBreakerEnabled) {
-            try {
-                $globalCircuitBreaker = app(CircuitBreaker::class);
-                $this->withCircuitBreaker($globalCircuitBreaker);
-            } catch (\Throwable $e) {
-                // Silently ignore if circuit breaker dependencies are not available
-            }
-        }
-
-        // No global config check - circuit breaker is always per-service controlled
     }
 
     public function withCircuitBreaker(CircuitBreaker $circuitBreaker): ClientInterface
diff --git a/src/IntegrationsServiceProvider.php b/src/IntegrationsServiceProvider.php
index d1ae2f2..f91aa3e 100644
--- a/src/IntegrationsServiceProvider.php
+++ b/src/IntegrationsServiceProvider.php
@@ -1,97 +1,43 @@
-<?php
-
-namespace Idaratech\Integrations;
-
-use Idaratech\Integrations\CircuitBreaker\CircuitBreaker;
-use Idaratech\Integrations\CircuitBreaker\CircuitBreakerFactory;
-use Idaratech\Integrations\CircuitBreaker\Contracts\CircuitBreakerStorage;
-use Idaratech\Integrations\CircuitBreaker\Storage\CacheStorage;
-use Idaratech\Integrations\CircuitBreaker\Storage\RedisStorage;
-use Idaratech\Integrations\CircuitBreaker\Strategy\CountStrategy;
-use Idaratech\Integrations\CircuitBreaker\Strategy\RateStrategy;
-use Idaratech\Integrations\Contracts\ResponseMapperInterface;
-use Idaratech\Integrations\Dto\DefaultResponseMapper;
-use Idaratech\Integrations\Http\Transport\LaravelHttpTransport;
-use Idaratech\Integrations\Http\Transport\Transport;
-use Illuminate\Support\Facades\Log;
-use Illuminate\Support\ServiceProvider;
-
-class IntegrationsServiceProvider extends ServiceProvider
-{
-    public function register(): void
-    {
-        $this->mergeConfigFrom(__DIR__.'/../config/integrations.php', 'integrations');
-
-        $this->app->bind(Transport::class, function ($app) {
-            $headers = config('integrations.default_headers', []);
-            $timeout = config('integrations.timeout', null);
-            $retry   = config('integrations.retry.times', 0);
-            $sleep   = config('integrations.retry.sleep_ms', 0);
-
-            return (new LaravelHttpTransport($headers, $timeout, $retry, $sleep));
-        });
-
-        $this->app->bind(ResponseMapperInterface::class, DefaultResponseMapper::class);
-
-        $this->registerCircuitBreaker();
-    }
-
-    protected function registerCircuitBreaker(): void
-    {
-        // Register circuit breaker factory for per-service configuration
-        $this->app->singleton(CircuitBreakerFactory::class, function () {
-            return new CircuitBreakerFactory();
-        });
-
-        // Register global circuit breaker singleton as fallback
-        // Used only when circuitBreakerConfig() is not overridden and enabled in config
-        $this->app->singleton(CircuitBreaker::class, function ($app) {
-            $config = config('integrations.circuit_breaker');
-
-            // Create storage adapter
-            $storage = $this->createStorageFromConfig($config);
-
-            // Create strategy
-            $strategy = match ($config['strategy']) {
-                'count' => new CountStrategy(
-                    $config['time_window'],
-                    $config['failure_count_threshold'],
-                    $config['interval_to_half_open'],
-                    $config['success_threshold']
-                ),
-                default => new RateStrategy(
-                    $config['time_window'],
-                    $config['failure_rate_threshold'],
-                    $config['minimum_requests'],
-                    $config['interval_to_half_open'],
-                    $config['success_threshold']
-                ),
-            };
-
-            return (new CircuitBreaker($storage, $strategy))
-                ->setFailureStatusCodes($config['failure_status_codes'] ?? [])
-                ->setIgnoredStatusCodes($config['ignored_status_codes'] ?? []);
-        });
-    }
-
-    protected function createStorageFromConfig(array $config): CircuitBreakerStorage
-    {
-        if ($config['storage'] === 'redis') {
-            try {
-                return new RedisStorage($config['prefix'], $config['redis_connection'] ?? null);
-            } catch (\Throwable $e) {
-                Log::warning('Circuit breaker: Redis unavailable, falling back to cache', [
-                    'error' => $e->getMessage(),
-                ]);
-            }
-        }
-        return new CacheStorage($config['prefix'], $config['cache_store'] ?? null);
-    }
-
-    public function boot(): void
-    {
-        $this->publishes([
-            __DIR__.'/../config/integrations.php' => config_path('integrations.php'),
-        ], 'config');
-    }
-}
+<?php
+
+namespace Idaratech\Integrations;
+
+use Idaratech\Integrations\CircuitBreaker\CircuitBreakerFactory;
+use Idaratech\Integrations\Console\Commands\CircuitBreakerControlCommand;
+use Idaratech\Integrations\Console\Commands\CircuitBreakerStatusCommand;
+use Idaratech\Integrations\Contracts\ResponseMapperInterface;
+use Idaratech\Integrations\Dto\DefaultResponseMapper;
+use Idaratech\Integrations\Http\Transport\LaravelHttpTransport;
+use Idaratech\Integrations\Http\Transport\Transport;
+use Illuminate\Support\ServiceProvider;
+
+class IntegrationsServiceProvider extends ServiceProvider
+{
+    public function register(): void
+    {
+        $this->mergeConfigFrom(__DIR__.'/../config/integrations.php', 'integrations');
+
+        $this->app->bind(Transport::class, function ($app) {
+            $headers = config('integrations.default_headers', []);
+            $timeout = config('integrations.timeout', null);
+            $retry   = config('integrations.retry.times', 0);
+            $sleep   = config('integrations.retry.sleep_ms', 0);
+
+            return (new LaravelHttpTransport($headers, $timeout, $retry, $sleep));
+        });
+
+        $this->app->bind(ResponseMapperInterface::class, DefaultResponseMapper::class);
+
+        // Register circuit breaker factory for per-service configuration
+        $this->app->singleton(CircuitBreakerFactory::class, function () {
+            return new CircuitBreakerFactory();
+        });
+    }
+
+    public function boot(): void
+    {
+        $this->publishes([
+            __DIR__.'/../config/integrations.php' => config_path('integrations.php'),
+        ], 'config');
+    }
+}

From f00c7220f65b6d68a06b5fdc800aa25cfb2985f5 Mon Sep 17 00:00:00 2001
From: Bilal-Elhalawaty <b.elhalawaty@idaratech.sa>
Date: Mon, 29 Dec 2025 18:49:55 +0200
Subject: [PATCH 3/3] feat: add Circuit Breaker implementation overview and
 file structure documentation

---
 docs/CIRCUIT_BREAKER.md | 231 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 231 insertions(+)
 create mode 100644 docs/CIRCUIT_BREAKER.md

diff --git a/docs/CIRCUIT_BREAKER.md b/docs/CIRCUIT_BREAKER.md
new file mode 100644
index 0000000..eb4b4fd
--- /dev/null
+++ b/docs/CIRCUIT_BREAKER.md
@@ -0,0 +1,231 @@
+# Circuit Breaker - Implementation Overview
+
+> **Brief:** Automatic failure detection and recovery mechanism for HTTP clients. Prevents cascading failures by "opening the circuit" when a service is unhealthy.
+
+---
+
+## What is Circuit Breaker?
+
+A circuit breaker monitors HTTP requests and automatically stops sending requests to failing services. It has three states:
+
+- **CLOSED** → Normal operation, requests flow through
+- **OPEN** → Service failing, requests are blocked (fail fast)
+- **HALF_OPEN** → Testing recovery, limited requests allowed
+
+```
+CLOSED → (failures exceed threshold) → OPEN
+OPEN → (wait interval) → HALF_OPEN
+HALF_OPEN → (success) → CLOSED
+HALF_OPEN → (failure) → OPEN
+```
+---
+
+## File Structure
+
+```
+src/CircuitBreaker/
+├── CircuitBreaker.php              # Core circuit breaker logic
+├── CircuitBreakerFactory.php       # Factory to create instances
+├── CircuitBreakerInterceptor.php   # HTTP request/response hooks
+│
+├── Config/
+│   ├── CircuitBreakerConfig.php    # Base configuration (abstract)
+│   ├── CountStrategyConfig.php     # Config for count-based strategy
+│   └── RateStrategyConfig.php      # Config for rate-based strategy
+│
+├── Contracts/
+│   ├── CircuitBreakerStorage.php   # Storage interface
+│   └── StrategyInterface.php       # Strategy interface
+│
+├── Enums/
+│   └── CircuitState.php            # CLOSED, OPEN, HALF_OPEN
+│
+├── Exceptions/
+│   └── CircuitOpenException.php    # Thrown when circuit is open
+│
+├── Storage/
+│   ├── CacheStorage.php            # Laravel Cache implementation
+│   └── RedisStorage.php            # Redis implementation
+│
+└── Strategy/
+    ├── CountStrategy.php           # Trip after N failures
+    └── RateStrategy.php            # Trip when failure rate > X%
+```
+
+---
+
+## Key Components
+
+### 1. Strategies (Tripping Logic)
+
+Determines **when** to open the circuit.
+
+#### **CountStrategy**
+- Trips after **N consecutive failures** within time window
+- Example: Open circuit after 5 failures in 60 seconds
+- Use case: Simple failure counting
+
+#### **RateStrategy**
+- Trips when **failure rate > X%** (requires minimum requests)
+- Example: Open circuit when 50% of last 10 requests fail
+- Use case: Percentage-based thresholds
+
+### 2. Storage (State Persistence)
+
+Stores circuit state and metrics.
+
+#### **RedisStorage**
+- Uses Redis for distributed state across servers
+- Key format: `{prefix}:{service}:{metric}`
+- Supports TTL for auto-cleanup
+
+#### **CacheStorage**
+- Uses Laravel Cache (file, Redis, Memcached, etc.)
+- Configurable cache store
+- Same interface as RedisStorage
+
+### 3. Configuration
+
+#### **CountStrategyConfig**
+```php
+CountStrategyConfig::make()
+    ->failureCountThreshold(5)      // Trip after 5 failures
+    ->timeWindow(60)                // Within 60 seconds
+    ->intervalToHalfOpen(30)        // Wait 30s before retry
+    ->successThreshold(3)           // 3 successes to close
+    ->storage('redis');             // Use Redis
+```
+
+#### **RateStrategyConfig**
+```php
+RateStrategyConfig::make()
+    ->failureRateThreshold(50.0)    // Trip at 50% failure rate
+    ->minimumRequests(10)           // Need 10 requests minimum
+    ->timeWindow(60)                // Within 60 seconds
+    ->intervalToHalfOpen(30)        // Wait 30s before retry
+    ->successThreshold(3)           // 3 successes to close
+    ->storage('cache');             // Use Laravel Cache
+```
+
+---
+
+## How It Works
+
+### Request Flow
+
+```
+1. HTTP Request
+   ↓
+2. CircuitBreakerInterceptor::before()
+   ↓
+3. Check: CircuitBreaker::isAvailable()
+   ├─ CLOSED/HALF_OPEN → Allow request (proceed to step 4)
+   └─ OPEN → Throw CircuitOpenException (stop here)
+   ↓
+4. Execute HTTP request
+   ↓
+5. Success?
+   ├─ YES → CircuitBreakerInterceptor::after() → record success
+   └─ NO → CircuitBreakerInterceptor::onException() → record failure
+   ↓
+6. Strategy evaluates if circuit should trip
+   ├─ Threshold exceeded? → Transition to OPEN
+   └─ Normal → Stay CLOSED
+```
+
+### State Transitions
+
+**CLOSED → OPEN**
+- Strategy detects threshold exceeded (failures or rate)
+- Circuit blocks all requests
+- Sets `opened_at` timestamp
+
+**OPEN → HALF_OPEN**
+- After `intervalToHalfOpen` seconds (default: 30s)
+- Next request triggers transition
+- Allows limited testing
+
+**HALF_OPEN → CLOSED**
+- After `successThreshold` consecutive successes (default: 3)
+- Circuit fully recovers
+- Resets all metrics
+
+**HALF_OPEN → OPEN**
+- Any failure during testing
+- Circuit immediately reopens
+
+---
+
+## Usage Example
+
+### Basic Setup
+
+```php
+// In your HTTP client class
+class MudadClient extends Client
+{
+    protected function circuitBreakerConfig(): ?CircuitBreakerConfig
+    {
+        return RateStrategyConfig::make()
+            ->failureRateThreshold(40.0)    // 40% failure rate
+            ->minimumRequests(10)            // Need 10 requests
+            ->timeWindow(60)                 // In 60 seconds
+            ->intervalToHalfOpen(30)         // Wait 30s to retry
+            ->successThreshold(3)            // 3 successes to recover
+            ->storage('redis')               // Use Redis
+            ->prefix('cb:mudad');            // Custom prefix
+    }
+}
+```
+
+### Making Requests
+
+```php
+try {
+    $client = new MudadClient();
+    $response = $client->do($request);
+    // Success - circuit records success
+} catch (CircuitOpenException $e) {
+    // Circuit is open - service is down
+    // Use fallback logic
+} catch (Throwable $e) {
+    // Other error - circuit records failure
+}
+```
+---
+
+## Configuration Options
+
+### Common Settings (Both Strategies)
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `timeWindow` | 60 | Time window in seconds to track metrics |
+| `intervalToHalfOpen` | 30 | Seconds to wait before attempting recovery |
+| `successThreshold` | 3 | Consecutive successes needed to close circuit |
+| `storage` | 'cache' | Storage type: 'redis' or 'cache' |
+| `prefix` | 'cb:app' | Cache/Redis key prefix |
+| `failureStatusCodes` | [500,502,503,504] | HTTP codes considered failures |
+| `ignoredStatusCodes` | [] | HTTP codes to ignore |
+
+### CountStrategy Specific
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `failureCountThreshold` | 5 | Number of failures to trip circuit |
+
+### RateStrategy Specific
+
+| Option | Default | Description |
+|--------|---------|-------------|
+| `failureRateThreshold` | 50.0 | Failure rate percentage (0-100) |
+| `minimumRequests` | 10 | Minimum requests before evaluating rate |
+
+### Storage Options
+
+| Option | Description | When to Use |
+|--------|-------------|-------------|
+| `redisConnection()` | Specify Redis connection name | Multiple Redis servers |
+| `cacheStore()` | Specify cache store name | Multiple cache backends |
+
+---
\ No newline at end of file
