![]() 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/ |
<?php
/**
* Admin Purchase Tracker
*
* This tool helps you:
* 1. View recent payment attempts from logs
* 2. Identify missing purchases (paid but not in database)
* 3. Fix missing purchases with one click
*
* Created: November 29, 2025
*/
session_start();
require_once __DIR__ . '/config/database.php';
// Check if admin
if (!isset($_SESSION['is_admin']) || !$_SESSION['is_admin']) {
die("Admin access required. Please log in as admin first.");
}
$pdo = getDBConnection();
// Handle actions
$action = $_GET['action'] ?? '';
$message = '';
$message_type = '';
if ($action === 'fix' && isset($_GET['user_id']) && isset($_GET['track_id']) && isset($_GET['payment_intent'])) {
$result = fixMissingPurchase(
$pdo,
(int)$_GET['user_id'],
(int)$_GET['track_id'],
$_GET['payment_intent']
);
$message = $result['message'];
$message_type = $result['success'] ? 'success' : 'error';
}
?>
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Purchase Tracker - Admin</title>
<style>
* { box-sizing: border-box; }
body {
font-family: 'Segoe UI', Arial, sans-serif;
padding: 20px;
background: #0f0f0f;
color: #e0e0e0;
line-height: 1.6;
margin: 0;
}
.container { max-width: 1400px; margin: 0 auto; }
h1 { color: #667eea; margin-bottom: 10px; }
h2 { color: #a0aec0; font-size: 18px; margin-top: 30px; border-bottom: 1px solid #333; padding-bottom: 10px; }
h3 { color: #667eea; margin: 0 0 15px 0; }
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 20px;
border-bottom: 1px solid #333;
}
.stats-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-bottom: 30px;
}
.stat-card {
background: #1a1a2e;
padding: 20px;
border-radius: 10px;
text-align: center;
border: 1px solid #333;
}
.stat-card .number {
font-size: 36px;
font-weight: bold;
color: #667eea;
}
.stat-card.danger .number { color: #e53e3e; }
.stat-card.success .number { color: #48bb78; }
.stat-card.warning .number { color: #ffc107; }
.stat-card .label {
font-size: 14px;
color: #718096;
margin-top: 5px;
}
.section {
background: #1a1a2e;
border-radius: 10px;
padding: 20px;
margin-bottom: 20px;
border: 1px solid #333;
}
.section.alert {
border-color: #e53e3e;
background: rgba(229, 62, 62, 0.1);
}
table {
width: 100%;
border-collapse: collapse;
margin-top: 15px;
font-size: 14px;
}
th, td {
padding: 12px 10px;
text-align: left;
border-bottom: 1px solid #333;
}
th {
background: #252542;
color: #a0aec0;
font-weight: 600;
text-transform: uppercase;
font-size: 12px;
letter-spacing: 0.5px;
}
tr:hover { background: rgba(102, 126, 234, 0.1); }
.badge {
display: inline-block;
padding: 4px 10px;
border-radius: 20px;
font-size: 12px;
font-weight: 600;
}
.badge-success { background: rgba(72, 187, 120, 0.2); color: #48bb78; }
.badge-error { background: rgba(229, 62, 62, 0.2); color: #e53e3e; }
.badge-warning { background: rgba(255, 193, 7, 0.2); color: #ffc107; }
.badge-info { background: rgba(102, 126, 234, 0.2); color: #667eea; }
.btn {
display: inline-block;
padding: 8px 16px;
background: #667eea;
color: white;
text-decoration: none;
border-radius: 5px;
font-size: 13px;
border: none;
cursor: pointer;
transition: background 0.2s;
}
.btn:hover { background: #5a67d8; }
.btn-danger { background: #e53e3e; }
.btn-danger:hover { background: #c53030; }
.btn-sm { padding: 5px 10px; font-size: 12px; }
.alert-box {
padding: 15px 20px;
border-radius: 8px;
margin-bottom: 20px;
}
.alert-box.success {
background: rgba(72, 187, 120, 0.2);
border: 1px solid #48bb78;
color: #48bb78;
}
.alert-box.error {
background: rgba(229, 62, 62, 0.2);
border: 1px solid #e53e3e;
color: #e53e3e;
}
.user-link {
color: #667eea;
text-decoration: none;
}
.user-link:hover { text-decoration: underline; }
.track-list {
display: flex;
flex-wrap: wrap;
gap: 5px;
}
.track-chip {
background: #252542;
padding: 3px 8px;
border-radius: 4px;
font-size: 12px;
}
.missing-row {
background: rgba(229, 62, 62, 0.1) !important;
}
.log-preview {
background: #0a0a0a;
padding: 10px;
border-radius: 5px;
font-family: monospace;
font-size: 12px;
max-height: 200px;
overflow-y: auto;
white-space: pre-wrap;
word-break: break-all;
}
.tabs {
display: flex;
gap: 5px;
margin-bottom: 20px;
}
.tab {
padding: 10px 20px;
background: #252542;
color: #a0aec0;
text-decoration: none;
border-radius: 5px 5px 0 0;
border: 1px solid #333;
border-bottom: none;
}
.tab.active {
background: #1a1a2e;
color: #667eea;
border-color: #667eea;
}
.empty-state {
text-align: center;
padding: 40px;
color: #718096;
}
.empty-state .icon {
font-size: 48px;
margin-bottom: 15px;
}
code {
background: #252542;
padding: 2px 6px;
border-radius: 3px;
font-family: monospace;
font-size: 12px;
}
.refresh-note {
font-size: 12px;
color: #718096;
}
</style>
</head>
<body>
<div class="container">
<div class="header-bar">
<div>
<h1>🔍 Purchase Tracker</h1>
<p class="refresh-note">Scans payment logs and compares with database records</p>
</div>
<div>
<a href="?refresh=1" class="btn">🔄 Refresh</a>
<a href="admin.php" class="btn">← Back to Admin</a>
</div>
</div>
<?php if ($message): ?>
<div class="alert-box <?= $message_type ?>">
<?= htmlspecialchars($message) ?>
</div>
<?php endif; ?>
<?php
// Parse the cart payment logs
$log_file = __DIR__ . '/logs/cart_payment_detailed.log';
$recent_payments = [];
$missing_purchases = [];
if (file_exists($log_file)) {
$lines = file($log_file, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES);
$lines = array_reverse($lines); // Most recent first
$seen_intents = [];
foreach ($lines as $line) {
$entry = json_decode($line, true);
if (!$entry) continue;
// Only look at "after_create_payment_intent" entries
if (($entry['action'] ?? '') !== 'after_create_payment_intent') continue;
$payment_intent_id = $entry['payment_intent_id'] ?? null;
if (!$payment_intent_id || isset($seen_intents[$payment_intent_id])) continue;
$seen_intents[$payment_intent_id] = true;
$user_id = $entry['user_id'] ?? null;
$cart_summary = $entry['cart_summary'] ?? [];
$timestamp = $entry['timestamp'] ?? 'Unknown';
$amount = ($entry['amount'] ?? 0) / 100;
// Get user info
$user_name = 'Unknown';
$user_email = '';
if ($user_id) {
$stmt = $pdo->prepare("SELECT name, email FROM users WHERE id = ?");
$stmt->execute([$user_id]);
$user = $stmt->fetch(PDO::FETCH_ASSOC);
if ($user) {
$user_name = $user['name'];
$user_email = $user['email'];
}
}
// Extract track items
$track_items = [];
foreach ($cart_summary as $item) {
if (($item['type'] ?? '') === 'track') {
$track_id = $item['track_id'] ?? null;
if ($track_id) {
// Check if purchased
$stmt = $pdo->prepare("SELECT id FROM track_purchases WHERE user_id = ? AND track_id = ?");
$stmt->execute([$user_id, $track_id]);
$is_purchased = (bool)$stmt->fetch();
$track_items[] = [
'track_id' => $track_id,
'title' => $item['title'] ?? 'Unknown',
'artist' => $item['artist'] ?? 'Unknown',
'is_purchased' => $is_purchased
];
if (!$is_purchased) {
$missing_purchases[] = [
'payment_intent_id' => $payment_intent_id,
'user_id' => $user_id,
'user_name' => $user_name,
'user_email' => $user_email,
'track_id' => $track_id,
'title' => $item['title'] ?? 'Unknown',
'artist' => $item['artist'] ?? 'Unknown',
'timestamp' => $timestamp,
'amount' => ($item['amount'] ?? 0) / 100
];
}
}
}
}
$recent_payments[] = [
'payment_intent_id' => $payment_intent_id,
'user_id' => $user_id,
'user_name' => $user_name,
'user_email' => $user_email,
'timestamp' => $timestamp,
'amount' => $amount,
'track_items' => $track_items,
'has_missing' => count(array_filter($track_items, fn($t) => !$t['is_purchased'])) > 0
];
// Limit to last 50 payment attempts
if (count($recent_payments) >= 50) break;
}
}
// Count stats
$total_payments = count($recent_payments);
$payments_with_missing = count(array_filter($recent_payments, fn($p) => $p['has_missing']));
$total_missing_tracks = count($missing_purchases);
?>
<!-- Stats Row -->
<div class="stats-row">
<div class="stat-card">
<div class="number"><?= $total_payments ?></div>
<div class="label">Recent Payment Attempts</div>
</div>
<div class="stat-card <?= $payments_with_missing > 0 ? 'danger' : 'success' ?>">
<div class="number"><?= $payments_with_missing ?></div>
<div class="label">Payments With Missing Tracks</div>
</div>
<div class="stat-card <?= $total_missing_tracks > 0 ? 'danger' : 'success' ?>">
<div class="number"><?= $total_missing_tracks ?></div>
<div class="label">Total Missing Track Purchases</div>
</div>
</div>
<?php if (!empty($missing_purchases)): ?>
<!-- Missing Purchases Alert -->
<div class="section alert">
<h3>⚠️ Missing Purchases Found!</h3>
<p>These tracks were in payment attempts but are NOT in the database. The customer may have paid but not received their tracks.</p>
<table>
<thead>
<tr>
<th>Date</th>
<th>Customer</th>
<th>Track</th>
<th>Artist</th>
<th>Price</th>
<th>Payment Intent</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<?php foreach ($missing_purchases as $mp): ?>
<tr class="missing-row">
<td><?= htmlspecialchars($mp['timestamp']) ?></td>
<td>
<a href="artist_profile.php?id=<?= $mp['user_id'] ?>" class="user-link">
<?= htmlspecialchars($mp['user_name']) ?>
</a>
<br><small style="color: #718096;"><?= htmlspecialchars($mp['user_email']) ?></small>
</td>
<td>
<a href="track.php?id=<?= $mp['track_id'] ?>" class="user-link">
<?= htmlspecialchars($mp['title']) ?>
</a>
<br><small style="color: #718096;">ID: <?= $mp['track_id'] ?></small>
</td>
<td><?= htmlspecialchars($mp['artist']) ?></td>
<td>$<?= number_format($mp['amount'], 2) ?></td>
<td><code><?= substr($mp['payment_intent_id'], 0, 20) ?>...</code></td>
<td>
<a href="?action=fix&user_id=<?= $mp['user_id'] ?>&track_id=<?= $mp['track_id'] ?>&payment_intent=<?= urlencode($mp['payment_intent_id']) ?>"
class="btn btn-danger btn-sm"
onclick="return confirm('Add this purchase to the database?');">
🔧 Fix
</a>
</td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<p style="margin-top: 20px;">
<strong>Note:</strong> Before fixing, verify in Stripe Dashboard that the payment actually succeeded.
</p>
</div>
<?php endif; ?>
<!-- Recent Payment Attempts -->
<div class="section">
<h3>📋 Recent Payment Attempts (from logs)</h3>
<p style="color: #718096; font-size: 14px;">
These are payment intents created in checkout. Green = all tracks purchased. Red = some tracks missing.
</p>
<?php if (empty($recent_payments)): ?>
<div class="empty-state">
<div class="icon">📭</div>
<p>No payment attempts found in logs.</p>
<p style="font-size: 13px;">Log file: <code><?= $log_file ?></code></p>
</div>
<?php else: ?>
<table>
<thead>
<tr>
<th>Date</th>
<th>Customer</th>
<th>Amount</th>
<th>Tracks</th>
<th>Status</th>
<th>Payment Intent</th>
</tr>
</thead>
<tbody>
<?php foreach ($recent_payments as $payment): ?>
<tr class="<?= $payment['has_missing'] ? 'missing-row' : '' ?>">
<td><?= htmlspecialchars($payment['timestamp']) ?></td>
<td>
<a href="artist_profile.php?id=<?= $payment['user_id'] ?>" class="user-link">
<?= htmlspecialchars($payment['user_name']) ?>
</a>
</td>
<td>$<?= number_format($payment['amount'], 2) ?></td>
<td>
<div class="track-list">
<?php foreach ($payment['track_items'] as $track): ?>
<span class="track-chip" style="<?= $track['is_purchased'] ? '' : 'background: rgba(229, 62, 62, 0.3);' ?>">
<?= $track['is_purchased'] ? '✓' : '✗' ?>
<?= htmlspecialchars($track['title']) ?>
</span>
<?php endforeach; ?>
<?php if (empty($payment['track_items'])): ?>
<span style="color: #718096;">No tracks</span>
<?php endif; ?>
</div>
</td>
<td>
<?php if ($payment['has_missing']): ?>
<span class="badge badge-error">Missing</span>
<?php else: ?>
<span class="badge badge-success">Complete</span>
<?php endif; ?>
</td>
<td><code><?= substr($payment['payment_intent_id'], 0, 20) ?>...</code></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<?php endif; ?>
</div>
<!-- Log Files Info -->
<div class="section">
<h3>📁 Log Files Reference</h3>
<table>
<thead>
<tr>
<th>Log File</th>
<th>Purpose</th>
<th>Size</th>
<th>Last Modified</th>
</tr>
</thead>
<tbody>
<?php
$log_files = [
'logs/cart_payment_detailed.log' => 'Payment intent creation (cart checkout)',
'logs/credit_payments.log' => 'Credit package payments',
'logs/stripe_webhooks.log' => 'Stripe webhook events',
'logs/track_purchase_errors.log' => 'Track purchase errors',
'logs/manual_purchase_fixes.log' => 'Manual fixes applied'
];
foreach ($log_files as $file => $purpose):
$full_path = __DIR__ . '/' . $file;
$exists = file_exists($full_path);
?>
<tr>
<td><code><?= $file ?></code></td>
<td><?= $purpose ?></td>
<td><?= $exists ? number_format(filesize($full_path)) . ' bytes' : '<span style="color: #718096;">Not found</span>' ?></td>
<td><?= $exists ? date('Y-m-d H:i:s', filemtime($full_path)) : '-' ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<!-- Quick Links -->
<div class="section">
<h3>🔗 Related Tools</h3>
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
<a href="investigate_stephane_10_tracks.php" class="btn">🔍 Full Stripe Investigation</a>
<a href="fix_stephane_3_tracks.php" class="btn">🔧 Auto-Find Missing (Stephane)</a>
<a href="admin_check_user_5_purchases.php" class="btn">👤 Check User #5</a>
<a href="reconcile_stripe_purchases.php" class="btn">⚖️ Reconcile All</a>
</div>
</div>
</div>
</body>
</html>
<?php
/**
* Fix a missing purchase
*/
function fixMissingPurchase($pdo, $user_id, $track_id, $payment_intent_id) {
try {
// Check if already purchased
$stmt = $pdo->prepare("SELECT id FROM track_purchases WHERE user_id = ? AND track_id = ?");
$stmt->execute([$user_id, $track_id]);
if ($stmt->fetch()) {
return ['success' => false, 'message' => 'This track is already purchased by this user.'];
}
// Get track info
$stmt = $pdo->prepare("SELECT id, title, price, user_id as artist_id FROM music_tracks WHERE id = ?");
$stmt->execute([$track_id]);
$track = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$track) {
return ['success' => false, 'message' => 'Track not found in database.'];
}
$pdo->beginTransaction();
// Determine revenue recipient
$artist_stmt = $pdo->prepare("SELECT plan FROM users WHERE id = ?");
$artist_stmt->execute([$track['artist_id']]);
$artist = $artist_stmt->fetch(PDO::FETCH_ASSOC);
$is_free_user_track = ($artist && strtolower($artist['plan']) === 'free');
$revenue_recipient = $is_free_user_track ? 'platform' : 'artist';
$recipient_id = $is_free_user_track ? 1 : $track['artist_id'];
// Record sale
$stmt = $pdo->prepare("
INSERT INTO sales (track_id, buyer_id, artist_id, amount, quantity, revenue_recipient, recipient_id, is_free_user_track, created_at)
VALUES (?, ?, ?, ?, 1, ?, ?, ?, NOW())
");
$stmt->execute([
$track_id,
$user_id,
$track['artist_id'],
$track['price'],
$revenue_recipient,
$recipient_id,
$is_free_user_track ? 1 : 0
]);
// Record purchase
$stmt = $pdo->prepare("
INSERT INTO track_purchases (user_id, track_id, price_paid, credits_used, payment_method, stripe_payment_intent_id, purchase_date)
VALUES (?, ?, ?, 0, 'stripe', ?, NOW())
");
$stmt->execute([$user_id, $track_id, $track['price'], $payment_intent_id]);
$purchase_id = $pdo->lastInsertId();
// Add to library
$stmt = $pdo->prepare("INSERT IGNORE INTO user_library (user_id, track_id, purchase_date) VALUES (?, ?, NOW())");
$stmt->execute([$user_id, $track_id]);
$pdo->commit();
// Log the fix
$log_entry = [
'timestamp' => date('Y-m-d H:i:s'),
'action' => 'admin_purchase_tracker_fix',
'user_id' => $user_id,
'track_id' => $track_id,
'track_title' => $track['title'],
'payment_intent_id' => $payment_intent_id,
'purchase_id' => $purchase_id,
'fixed_by' => $_SESSION['user_id'] ?? 'unknown'
];
$log_file = __DIR__ . '/logs/manual_purchase_fixes.log';
file_put_contents($log_file, json_encode($log_entry) . "\n", FILE_APPEND | LOCK_EX);
return [
'success' => true,
'message' => "✓ Fixed! Purchase #{$purchase_id} created for \"{$track['title']}\" (Track #{$track_id})"
];
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
return ['success' => false, 'message' => 'Error: ' . $e->getMessage()];
}
}
?>