![]() Server : Apache/2 System : Linux server-15-235-50-60 5.15.0-164-generic #174-Ubuntu SMP Fri Nov 14 20:25:16 UTC 2025 x86_64 User : gositeme ( 1004) PHP Version : 8.2.29 Disable Function : exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname Directory : /home/gositeme/domains/soundstudiopro.com/private_html/ |
# Subscribe.php Audit Report
## 🔍 Current Flow Analysis
### Question: Does subscription get created with Stripe every time user clicks subscribe?
**Answer: YES** - A Stripe Checkout Session is created every time the user clicks the subscribe button (if they don't have an active subscription).
### Current Flow:
1. **Page Load (GET request)**:
- Line 121: Checks for existing subscription ONCE: `$existing_subscription = hasActiveSubscription($_SESSION['user_id']);`
- Displays subscription info if exists, or shows subscribe button if not
2. **Button Click (POST request)**:
- Line 126: `if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_subscription']))`
- Line 128: Checks `$existing_subscription` (from page load, not fresh check!)
- Line 187-197: Creates NEW Stripe Checkout Session every time
- Redirects to Stripe Checkout
## ⚠️ Issues Found
### 1. **RACE CONDITION - Stale Subscription Check**
**Location:** Lines 121, 128
**Problem:**
- Subscription check happens at page load (line 121)
- When user clicks subscribe, it uses the OLD check result (line 128)
- If user subscribes in another tab/window between page load and button click, the check won't catch it
**Impact:** User could create duplicate subscriptions
**Fix Needed:** Check subscription status INSIDE the POST handler, not before
### 2. **NO RATE LIMITING**
**Location:** Line 187
**Problem:**
- No protection against rapid button clicks
- User could spam-click and create multiple checkout sessions
- Each checkout session creation costs API quota
**Impact:**
- Multiple Stripe API calls
- Potential for duplicate checkout sessions
- Poor user experience
**Fix Needed:** Add rate limiting or disable button after first click
### 3. **NO DUPLICATE CHECKOUT SESSION PREVENTION**
**Location:** Line 187-197
**Problem:**
- No check for existing pending checkout sessions
- User could have multiple active checkout sessions for same plan
**Impact:** Confusion, potential duplicate charges
**Fix Needed:** Check for existing pending sessions before creating new one
### 4. **NO CSRF PROTECTION**
**Location:** Line 349 (form)
**Problem:**
- Form has no CSRF token
- Vulnerable to CSRF attacks
**Impact:** Security vulnerability
**Fix Needed:** Add CSRF token to form
### 5. **HARDCODED STRIPE SECRET KEY**
**Location:** Line 123
**Problem:**
- Stripe secret key is hardcoded in the file
- Should be in environment variable or config file
**Impact:** Security risk if file is exposed
**Fix Needed:** Move to environment variable
## ✅ What Works Correctly
1. ✅ Checks for active subscription before showing subscribe button
2. ✅ Validates price ID configuration
3. ✅ Proper error handling for Stripe API calls
4. ✅ Redirects to Stripe Checkout correctly
5. ✅ Uses metadata to track user_id and plan
## 🔧 Recommended Fixes
### Priority 1: Fix Race Condition
```php
// Inside POST handler, check subscription status FRESH
if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_POST['create_subscription'])) {
// FRESH check for existing subscription
$existing_subscription = hasActiveSubscription($_SESSION['user_id']);
if ($existing_subscription && is_array($existing_subscription)) {
// Block subscription
}
}
```
### Priority 2: Add Rate Limiting
```php
// Check if user recently created a checkout session
$recent_session_stmt = $pdo->prepare("
SELECT id FROM checkout_sessions
WHERE user_id = ?
AND created_at > DATE_SUB(NOW(), INTERVAL 5 MINUTE)
LIMIT 1
");
$recent_session_stmt->execute([$_SESSION['user_id']]);
if ($recent_session_stmt->fetch()) {
$error_message = "Please wait a moment before creating another checkout session.";
}
```
### Priority 3: Disable Button After Click
```javascript
// Add to form button
onclick="this.disabled=true; this.form.submit();"
```
### Priority 4: Add CSRF Protection
```php
// Generate token
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
// In form
<input type="hidden" name="csrf_token" value="<?= $_SESSION['csrf_token'] ?>">
// In POST handler
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die("Invalid request");
}
```
### Priority 5: Move Stripe Secret to Config
```php
$stripe_secret = getenv('STRIPE_SECRET_KEY')
?? require __DIR__ . '/config/stripe.php';
```
## 📊 Summary
**Current Behavior:**
- ✅ Creates Stripe Checkout Session on each click (correct)
- ❌ Uses stale subscription check (race condition)
- ❌ No rate limiting (can spam-click)
- ❌ No CSRF protection (security risk)
- ❌ Hardcoded secret key (security risk)
**Recommendation:**
Fix the race condition first (Priority 1), then add rate limiting (Priority 2), then security improvements (Priorities 3-5).