![]() 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
session_start();
require_once 'config/database.php';
require_once 'config/email.php';
require_once 'utils/subscription_helpers.php';
require_once 'includes/translations.php';
// Enable error logging
error_log("process_credit_payment.php called at " . date('Y-m-d H:i:s'));
// Set content type to JSON
header('Content-Type: application/json');
// Check if user is logged in
if (!isset($_SESSION['user_id'])) {
error_log("process_credit_payment.php: User not logged in");
echo json_encode(['success' => false, 'error' => 'User not logged in']);
exit;
}
// Get POST data first to check what type of purchase this is
$input = json_decode(file_get_contents('php://input'), true);
$action = $input['action'] ?? '';
// IMPORTANT: Credit purchases require active subscription (minimum Essential $5/month)
// BUT: Track and ticket purchases do NOT require subscription - only credit card payment
// Check if this is a credit purchase before requiring subscription
$requires_subscription_check = false;
if ($action === 'create_payment_intent' || $action === 'process_paypal_payment') {
// These actions are for credit purchases only
$requires_subscription_check = true;
} elseif ($action === 'process_cart_payment') {
// Check cart contents to see if there are credit items
$cart_data = $input['cart'] ?? [];
if (!empty($cart_data)) {
// Check if cart has credit items
if (isset($cart_data['credits']) && !empty($cart_data['credits'])) {
$requires_subscription_check = true;
} elseif (is_array($cart_data) && !empty($cart_data) && isset($cart_data[0]['package'])) {
// Legacy format - all items are credits
$requires_subscription_check = true;
}
// If cart only has tracks or tickets, no subscription required
}
}
// Only check subscription if this is a credit purchase
if ($requires_subscription_check) {
$has_active_subscription = hasActiveSubscription($_SESSION['user_id']);
if ($has_active_subscription === false) {
error_log("process_credit_payment.php: User " . $_SESSION['user_id'] . " attempted credit purchase without active subscription");
echo json_encode([
'success' => false,
'error' => t('checkout.subscription_required_error'),
'message' => t('checkout.subscription_required_message'),
'requires_subscription' => true,
'subscription_url' => '/account_settings.php?tab=subscription'
]);
exit;
}
}
error_log("process_credit_payment.php: Action = '$action', Input = " . json_encode($input));
// Stripe configuration
$stripe_secret_key = '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';
// Credit package configurations
$credit_packages = [
'starter' => [
'name' => 'Starter',
'credits' => 30,
'price' => 1999, // $19.99 in cents
'stripe_price_id' => 'price_starter_credits'
],
'pro' => [
'name' => 'Pro',
'credits' => 200,
'price' => 5900, // $59.00 in cents
'stripe_price_id' => 'price_pro_credits'
],
'premium' => [
'name' => 'Premium',
'credits' => 500,
'price' => 12900, // $129.00 in cents
'stripe_price_id' => 'price_premium_credits'
]
];
// Add shutdown handler to catch fatal errors and log them
register_shutdown_function(function() {
$error = error_get_last();
if ($error && in_array($error['type'], [E_ERROR, E_PARSE, E_CORE_ERROR, E_COMPILE_ERROR])) {
$log = date('Y-m-d H:i:s') . " FATAL: {$error['message']} in {$error['file']} on line {$error['line']}\n";
file_put_contents(__DIR__ . '/payment_errors.log', $log, FILE_APPEND);
if (!headers_sent()) {
header('Content-Type: application/json');
}
echo json_encode(['success' => false, 'error' => 'Server error: ' . $error['message']]);
exit;
}
});
try {
switch ($action) {
case 'create_payment_intent':
handleCreatePaymentIntent($input, $credit_packages, $stripe_secret_key);
break;
case 'process_cart_payment':
handleCartPayment($input, $credit_packages, $stripe_secret_key);
break;
case 'confirm_payment':
handleConfirmPayment($input, $stripe_secret_key);
break;
case 'process_paypal_payment':
handlePayPalPayment($input, $credit_packages);
break;
default:
echo json_encode(['success' => false, 'error' => 'Invalid action']);
break;
}
} catch (Exception $e) {
// Log error
error_log("Credit payment error: " . $e->getMessage());
echo json_encode(['success' => false, 'error' => $e->getMessage()]);
}
function handleCreatePaymentIntent($input, $credit_packages, $stripe_secret_key) {
// IMPORTANT: Credit purchases require active subscription (minimum Essential $5/month)
$has_active_subscription = hasActiveSubscription($_SESSION['user_id']);
if ($has_active_subscription === false) {
error_log("handleCreatePaymentIntent: User " . $_SESSION['user_id'] . " attempted credit purchase without active subscription");
echo json_encode([
'success' => false,
'error' => t('checkout.subscription_required_error'),
'message' => t('checkout.subscription_required_message'),
'requires_subscription' => true,
'subscription_url' => '/account_settings.php?tab=subscription'
]);
exit;
}
$package_id = $input['package'] ?? '';
$quantity = $input['quantity'] ?? 1;
if (!isset($credit_packages[$package_id])) {
throw new Exception('Invalid package selected');
}
$package = $credit_packages[$package_id];
$total_amount = $package['price'] * $quantity;
$total_credits = $package['credits'] * $quantity;
// Create Stripe payment intent
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.stripe.com/v1/payment_intents');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $stripe_secret_key,
'Content-Type: application/x-www-form-urlencoded'
]);
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query([
'amount' => $total_amount,
'currency' => 'usd',
'metadata' => json_encode([
'user_id' => $_SESSION['user_id'],
'package' => $package_id,
'credits' => $total_credits,
'quantity' => $quantity,
'subscription_period' => '30_days'
])
]));
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);
if ($curl_error) {
throw new Exception('cURL Error: ' . $curl_error);
}
if ($http_code !== 200) {
$error_data = json_decode($response, true);
$error_message = $error_data['error']['message'] ?? 'HTTP Error: ' . $http_code;
throw new Exception($error_message);
}
$payment_intent = json_decode($response, true);
// Log payment intent creation
logPaymentEvent('payment_intent_created', [
'user_id' => $_SESSION['user_id'],
'package' => $package_id,
'credits' => $total_credits,
'amount' => $total_amount,
'payment_intent_id' => $payment_intent['id']
]);
echo json_encode([
'success' => true,
'client_secret' => $payment_intent['client_secret'],
'payment_intent_id' => $payment_intent['id'],
'amount' => $total_amount,
'credits' => $total_credits
]);
}
function processFreeOrder($track_items, $user_id, $ticket_items = []) {
try {
$pdo = getDBConnection();
$pdo->beginTransaction();
$processed_tracks = [];
foreach ($track_items as $item) {
$track_id = $item['track_id'];
$quantity = $item['quantity'] ?? 1;
// Get track details from database
$stmt = $pdo->prepare("
SELECT
mt.id,
mt.title,
mt.price,
mt.user_id as artist_id,
u.name as artist_name,
u.plan as artist_plan
FROM music_tracks mt
JOIN users u ON mt.user_id = u.id
WHERE mt.id = ? AND mt.status = 'complete'
");
$stmt->execute([$track_id]);
$track = $stmt->fetch(PDO::FETCH_ASSOC);
if (!$track) {
throw new Exception("Track not found: $track_id");
}
// Check if user already purchased this track
$stmt = $pdo->prepare("
SELECT id FROM track_purchases
WHERE user_id = ? AND track_id = ?
");
$stmt->execute([$user_id, $track_id]);
if ($stmt->fetch()) {
// Track already purchased, skip
continue;
}
$track_price = floatval($track['price']);
$is_free_user_track = (strtolower($track['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 (?, ?, ?, ?, ?, ?, ?, ?, NOW())
");
$stmt->execute([
$track_id,
$user_id,
$track['artist_id'],
$track_price,
$quantity,
$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)
VALUES (?, ?, ?, 0, 'free')
");
$stmt->execute([$user_id, $track_id, $track_price]);
$purchase_id = $pdo->lastInsertId();
// Notify artist of the purchase
require_once __DIR__ . '/utils/artist_notifications.php';
notifyArtistOfTrackPurchase(
$track['artist_id'],
$track_id,
$user_id,
$track_price,
$purchase_id
);
// Add to user's library
$stmt = $pdo->prepare("
INSERT IGNORE INTO user_library (user_id, track_id, purchase_date)
VALUES (?, ?, NOW())
");
$stmt->execute([$user_id, $track_id]);
$processed_tracks[] = [
'track_id' => $track_id,
'title' => $track['title'],
'artist_name' => $track['artist_name'],
'price_paid' => $track_price
];
}
// Process free tickets
$processed_tickets = [];
if (!empty($ticket_items)) {
foreach ($ticket_items as $ticket_item) {
if (!isset($ticket_item['event_id']) || !$ticket_item['is_free']) {
continue; // Skip paid tickets or invalid items
}
$event_id = $ticket_item['event_id'];
$quantity = $ticket_item['quantity'] ?? 1;
// Create tickets
for ($i = 0; $i < $quantity; $i++) {
$ticket_code = 'EVT-' . strtoupper(substr(md5($event_id . $user_id . time() . $i), 0, 12));
$qr_data = json_encode([
'ticket_code' => $ticket_code,
'event_id' => $event_id,
'user_id' => $user_id,
'timestamp' => time()
]);
$stmt = $pdo->prepare("
INSERT INTO event_tickets (
event_id, user_id, ticket_code, qr_code_data,
price_paid, payment_method, status
) VALUES (?, ?, ?, ?, ?, 'free', 'confirmed')
");
$stmt->execute([
$event_id,
$user_id,
$ticket_code,
$qr_data,
0
]);
$processed_tickets[] = [
'event_id' => $event_id,
'ticket_code' => $ticket_code
];
}
// Update event attendee count
$stmt = $pdo->prepare("
INSERT INTO event_attendees (event_id, user_id, status)
VALUES (?, ?, 'attending')
ON DUPLICATE KEY UPDATE status = 'attending'
");
$stmt->execute([$event_id, $user_id]);
}
}
// Clear cart from session
if (isset($_SESSION['cart'])) {
$_SESSION['cart'] = [];
}
if (isset($_SESSION['credit_cart'])) {
$_SESSION['credit_cart'] = [];
}
if (isset($_SESSION['ticket_cart'])) {
$_SESSION['ticket_cart'] = [];
}
$pdo->commit();
// Log free order processing
logPaymentEvent('free_order_processed', [
'user_id' => $user_id,
'tracks_processed' => count($processed_tracks),
'track_ids' => array_column($processed_tracks, 'track_id'),
'tickets_processed' => count($processed_tickets),
'ticket_event_ids' => array_unique(array_column($processed_tickets, 'event_id'))
]);
// Return success response indicating free order
echo json_encode([
'success' => true,
'is_free_order' => true,
'message' => 'Free order processed successfully!',
'tracks' => $processed_tracks,
'tickets' => $processed_tickets,
'client_secret' => null // No payment needed
]);
} catch (Exception $e) {
if ($pdo->inTransaction()) {
$pdo->rollBack();
}
error_log("processFreeOrder: Exception: " . $e->getMessage());
echo json_encode(['success' => false, 'error' => 'Error processing free order: ' . $e->getMessage()]);
}
}
function handleCartPayment($input, $credit_packages, $stripe_secret_key) {
try {
error_log("handleCartPayment called with input: " . json_encode($input));
$cart_data = $input['cart'] ?? [];
if (empty($cart_data)) {
error_log("handleCartPayment: Cart is empty");
echo json_encode(['success' => false, 'error' => 'Cart is empty']);
return;
}
// Handle different cart formats
$credit_items = [];
$track_items = [];
$ticket_items = [];
if (isset($cart_data['credits']) || isset($cart_data['tracks']) || isset($cart_data['tickets'])) {
// New mixed cart format with credits, tracks, and tickets properties
$credit_items = $cart_data['credits'] ?? [];
$track_items = $cart_data['tracks'] ?? [];
$ticket_items = $cart_data['tickets'] ?? [];
} elseif (is_array($cart_data) && !empty($cart_data) && isset($cart_data[0]['package'])) {
// Frontend is sending credit items array directly
$credit_items = $cart_data;
$track_items = [];
$ticket_items = [];
} else {
// Legacy format - assume all items are credits
$credit_items = $cart_data;
$track_items = [];
$ticket_items = [];
}
error_log("handleCartPayment: Credit items = " . json_encode($credit_items));
error_log("handleCartPayment: Track items = " . json_encode($track_items));
error_log("handleCartPayment: Ticket items = " . json_encode($ticket_items));
// IMPORTANT: If cart contains credit items, subscription is required
// Track and ticket purchases do NOT require subscription - only credit card payment
if (!empty($credit_items)) {
$has_active_subscription = hasActiveSubscription($_SESSION['user_id']);
if ($has_active_subscription === false) {
error_log("handleCartPayment: User " . $_SESSION['user_id'] . " attempted credit purchase without active subscription");
echo json_encode([
'success' => false,
'error' => t('checkout.subscription_required_error'),
'message' => t('checkout.subscription_required_message'),
'requires_subscription' => true,
'subscription_url' => '/account_settings.php?tab=subscription'
]);
return;
}
}
// Calculate total amount and credits
$total_amount = 0;
$total_credits = 0;
$cart_summary = [];
// Process credit items
foreach ($credit_items as $item) {
if (!isset($item['package']) || !isset($item['quantity'])) {
error_log("handleCartPayment: Missing package or quantity in credit item: " . json_encode($item));
echo json_encode(['success' => false, 'error' => 'Malformed credit item in cart']);
return;
}
$package_id = $item['package'];
$quantity = $item['quantity'];
if (!isset($credit_packages[$package_id])) {
error_log("handleCartPayment: Invalid package in cart: $package_id");
echo json_encode(['success' => false, 'error' => 'Invalid package in cart: ' . $package_id]);
return;
}
$package = $credit_packages[$package_id];
$item_total = $package['price'] * $quantity;
$item_credits = $package['credits'] * $quantity;
$total_amount += $item_total;
$total_credits += $item_credits;
$cart_summary[] = [
'type' => 'credit',
'package' => $package_id,
'name' => $package['name'],
'credits' => $item_credits,
'quantity' => $quantity,
'amount' => $item_total
];
}
// Process track items
foreach ($track_items as $item) {
if (!isset($item['track_id']) || !isset($item['title']) || !isset($item['price'])) {
error_log("handleCartPayment: Missing fields in track item: " . json_encode($item));
echo json_encode(['success' => false, 'error' => 'Malformed track item in cart']);
return;
}
$track_price = $item['price'] * 100; // Convert to cents
$quantity = $item['quantity'] ?? 1;
$item_total = $track_price * $quantity;
$total_amount += $item_total;
$cart_summary[] = [
'type' => 'track',
'track_id' => $item['track_id'],
'title' => $item['title'],
'artist' => $item['artist_name'] ?? $item['artist'] ?? 'Unknown Artist',
'quantity' => $quantity,
'amount' => $item_total
];
}
// Process ticket items
foreach ($ticket_items as $item) {
if (!isset($item['event_id']) || !isset($item['ticket_price'])) {
error_log("handleCartPayment: Missing fields in ticket item: " . json_encode($item));
echo json_encode(['success' => false, 'error' => 'Malformed ticket item in cart']);
return;
}
$ticket_price = $item['ticket_price'] * 100; // Convert to cents
$quantity = $item['quantity'] ?? 1;
$item_total = $ticket_price * $quantity;
// Only add to total if not free
if (!$item['is_free']) {
$total_amount += $item_total;
}
$cart_summary[] = [
'type' => 'ticket',
'event_id' => $item['event_id'],
'event_title' => $item['event_title'] ?? 'Event Ticket',
'ticket_price' => $item['ticket_price'],
'is_free' => $item['is_free'] ?? false,
'quantity' => $quantity,
'amount' => $item_total
];
}
// Get billing address data
$billing_address = $input['billing_address'] ?? [];
// Check if this is a free order (total amount is 0 and there are track items or free tickets)
if ($total_amount == 0 && (!empty($track_items) || !empty($ticket_items))) {
// Process free order directly without Stripe
processFreeOrder($track_items, $_SESSION['user_id'], $ticket_items);
return; // Exit after processing free order
}
// If total is 0 but no track/ticket items, something is wrong
if ($total_amount == 0 && empty($track_items) && empty($credit_items) && empty($ticket_items)) {
error_log("handleCartPayment: Cart total is 0 with no items");
echo json_encode(['success' => false, 'error' => 'Invalid cart: total is 0 with no items']);
return;
}
// Create Stripe payment intent for cart
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.stripe.com/v1/payment_intents');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_POST, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $stripe_secret_key,
'Content-Type: application/x-www-form-urlencoded'
]);
// Prepare payment intent data
// Stripe metadata has a 500 character limit per field
// Store minimal cart data (IDs, quantities, amounts only) to stay under limit
// Full cart details are stored in database via storeCartSnapshot()
$minimal_cart = [];
foreach ($cart_summary as $item) {
$minimal_cart[] = [
't' => $item['type'], // 'track', 'credit', or 'ticket'
'i' => $item['type'] === 'track' ? $item['track_id'] : ($item['type'] === 'ticket' ? $item['event_id'] : $item['package']),
'q' => $item['quantity'],
'a' => $item['amount']
];
}
$cart_items_json = json_encode($minimal_cart);
// If still too long, split into multiple fields
$payment_intent_data = [
'amount' => $total_amount,
'currency' => 'usd',
'metadata[user_id]' => $_SESSION['user_id'],
'metadata[total_credits]' => $total_credits,
'metadata[has_tracks]' => !empty($track_items) ? 'true' : 'false',
'metadata[has_tickets]' => !empty($ticket_items) ? 'true' : 'false',
'metadata[subscription_period]' => '30_days',
'metadata[payment_type]' => 'mixed_cart_checkout'
];
// Handle cart_items metadata - split if too long
if (strlen($cart_items_json) <= 500) {
// Fits in one field
$payment_intent_data['metadata[cart_items]'] = $cart_items_json;
} else {
// Still too long - split into chunks
$chunks = str_split($cart_items_json, 450); // Leave room for field name
for ($i = 0; $i < count($chunks); $i++) {
$payment_intent_data["metadata[cart_items_" . ($i + 1) . "]"] = $chunks[$i];
}
$payment_intent_data['metadata[cart_items_count]'] = count($chunks);
}
// Add billing address if provided
if (!empty($billing_address)) {
$payment_intent_data['metadata[billing_name]'] = ($billing_address['billing_first_name'] ?? '') . ' ' . ($billing_address['billing_last_name'] ?? '');
$payment_intent_data['metadata[billing_email]'] = $billing_address['billing_email'] ?? '';
$payment_intent_data['metadata[billing_address]'] = $billing_address['billing_address'] ?? '';
$payment_intent_data['metadata[billing_city]'] = $billing_address['billing_city'] ?? '';
$payment_intent_data['metadata[billing_state]'] = $billing_address['billing_state'] ?? '';
$payment_intent_data['metadata[billing_zip]'] = $billing_address['billing_zip'] ?? '';
$payment_intent_data['metadata[billing_country]'] = $billing_address['billing_country'] ?? '';
}
// Log BEFORE creating payment intent
$before_pi_log = [
'timestamp' => date('Y-m-d H:i:s'),
'action' => 'before_create_payment_intent',
'user_id' => $_SESSION['user_id'],
'total_amount' => $total_amount,
'total_credits' => $total_credits,
'cart_items_count' => count($cart_summary),
'track_items_count' => count($track_items),
'credit_items_count' => count($credit_items),
'ticket_items_count' => count($ticket_items),
'cart_summary' => $cart_summary,
'track_items' => $track_items,
'credit_items' => $credit_items
];
$before_pi_log_file = __DIR__ . '/logs/cart_payment_detailed.log';
if (!is_dir(__DIR__ . '/logs')) {
mkdir(__DIR__ . '/logs', 0755, true);
}
file_put_contents($before_pi_log_file, json_encode($before_pi_log) . "\n", FILE_APPEND | LOCK_EX);
// Store cart snapshot BEFORE creating payment intent (for validation)
// We'll store it after we get the payment_intent_id
curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query($payment_intent_data));
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);
if ($curl_error) {
$error_log = [
'timestamp' => date('Y-m-d H:i:s'),
'action' => 'create_payment_intent_curl_error',
'user_id' => $_SESSION['user_id'],
'error' => $curl_error,
'cart_summary' => $cart_summary
];
file_put_contents($before_pi_log_file, json_encode($error_log) . "\n", FILE_APPEND | LOCK_EX);
error_log("handleCartPayment: cURL error: $curl_error");
echo json_encode(['success' => false, 'error' => 'cURL Error: ' . $curl_error]);
return;
}
if ($http_code !== 200) {
$error_data = json_decode($response, true);
$error_message = $error_data['error']['message'] ?? 'HTTP Error: ' . $http_code;
$error_log = [
'timestamp' => date('Y-m-d H:i:s'),
'action' => 'create_payment_intent_stripe_error',
'user_id' => $_SESSION['user_id'],
'http_code' => $http_code,
'error' => $error_message,
'stripe_response' => $error_data,
'cart_summary' => $cart_summary
];
file_put_contents($before_pi_log_file, json_encode($error_log) . "\n", FILE_APPEND | LOCK_EX);
error_log("handleCartPayment: Stripe API error: $error_message");
echo json_encode(['success' => false, 'error' => $error_message]);
return;
}
$payment_intent = json_decode($response, true);
// Store cart snapshot NOW that we have payment_intent_id
if (isset($payment_intent['id'])) {
try {
require_once __DIR__ . '/webhooks/purchase_validation.php';
storeCartSnapshot($_SESSION['user_id'], $cart_summary, $payment_intent['id']);
} catch (Exception $e) {
error_log("Failed to store cart snapshot: " . $e->getMessage());
}
}
// Log AFTER creating payment intent
$after_pi_log = [
'timestamp' => date('Y-m-d H:i:s'),
'action' => 'after_create_payment_intent',
'user_id' => $_SESSION['user_id'],
'payment_intent_id' => $payment_intent['id'],
'client_secret' => $payment_intent['client_secret'] ?? null,
'amount' => $total_amount,
'status' => $payment_intent['status'] ?? 'unknown',
'cart_summary' => $cart_summary,
'metadata_sent' => [
'user_id' => $_SESSION['user_id'],
'cart_items' => $cart_summary,
'total_credits' => $total_credits,
'has_tracks' => !empty($track_items) ? 'true' : 'false',
'payment_type' => 'mixed_cart_checkout'
]
];
file_put_contents($before_pi_log_file, json_encode($after_pi_log) . "\n", FILE_APPEND | LOCK_EX);
// Log cart payment intent creation
logPaymentEvent('cart_payment_intent_created', [
'user_id' => $_SESSION['user_id'],
'cart_items' => $cart_summary,
'total_credits' => $total_credits,
'amount' => $total_amount,
'payment_intent_id' => $payment_intent['id']
]);
// Store payment data in session for email confirmation
$_SESSION['last_payment_data'] = [
'order_details' => [
'total_amount' => $total_amount,
'cart_summary' => $cart_summary,
'payment_intent_id' => $payment_intent['id']
],
'billing_address' => $billing_address
];
$response_data = [
'success' => true,
'client_secret' => $payment_intent['client_secret'],
'payment_intent_id' => $payment_intent['id'],
'amount' => $total_amount,
'credits' => $total_credits,
'cart_summary' => $cart_summary
];
error_log("handleCartPayment: Returning success response: " . json_encode($response_data));
echo json_encode($response_data);
} catch (Exception $e) {
error_log("handleCartPayment: Exception: " . $e->getMessage());
echo json_encode(['success' => false, 'error' => 'Server error: ' . $e->getMessage()]);
}
}
function handleConfirmPayment($input, $stripe_secret_key) {
$payment_intent_id = $input['payment_intent_id'] ?? '';
if (empty($payment_intent_id)) {
throw new Exception('Payment intent ID is required');
}
// Retrieve payment intent from Stripe
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'https://api.stripe.com/v1/payment_intents/' . $payment_intent_id);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
curl_setopt($ch, CURLOPT_HTTPHEADER, [
'Authorization: Bearer ' . $stripe_secret_key
]);
$response = curl_exec($ch);
$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE);
$curl_error = curl_error($ch);
curl_close($ch);
if ($curl_error) {
throw new Exception('cURL Error: ' . $curl_error);
}
if ($http_code !== 200) {
$error_data = json_decode($response, true);
$error_message = $error_data['error']['message'] ?? 'HTTP Error: ' . $http_code;
throw new Exception($error_message);
}
$payment_intent = json_decode($response, true);
// Check if payment was successful
if ($payment_intent['status'] !== 'succeeded') {
throw new Exception('Payment not completed. Status: ' . $payment_intent['status']);
}
// Extract metadata
$metadata = json_decode($payment_intent['metadata']['metadata'] ?? '{}', true);
$user_id = $metadata['user_id'] ?? $_SESSION['user_id'];
$credits = $metadata['credits'] ?? $metadata['total_credits'] ?? 0;
$package = $metadata['package'] ?? 'unknown';
// Add credits to user account
$current_credits = $_SESSION['credits'] ?? 0;
$new_credits = $current_credits + $credits;
$_SESSION['credits'] = $new_credits;
// Update user credits in database (you'll need to implement this)
updateUserCredits($user_id, $new_credits);
// Log successful payment
logPaymentEvent('payment_succeeded', [
'user_id' => $user_id,
'payment_intent_id' => $payment_intent_id,
'credits_added' => $credits,
'total_credits' => $new_credits,
'amount' => $payment_intent['amount'],
'package' => $package
]);
echo json_encode([
'success' => true,
'credits_added' => $credits,
'total_credits' => $new_credits,
'payment_intent_id' => $payment_intent_id
]);
}
// updateUserCredits function is already defined in config/database.php
function logPaymentEvent($event_type, $data) {
$log_entry = [
'timestamp' => date('Y-m-d H:i:s'),
'event_type' => $event_type,
'data' => $data
];
$log_file = __DIR__ . '/logs/credit_payments.log';
file_put_contents($log_file, json_encode($log_entry) . "\n", FILE_APPEND | LOCK_EX);
}
function handlePayPalPayment($input, $credit_packages) {
// IMPORTANT: Credit purchases require active subscription (minimum Essential $5/month)
$has_active_subscription = hasActiveSubscription($_SESSION['user_id']);
if ($has_active_subscription === false) {
error_log("handlePayPalPayment: User " . $_SESSION['user_id'] . " attempted credit purchase without active subscription");
echo json_encode([
'success' => false,
'error' => t('checkout.subscription_required_error'),
'message' => t('checkout.subscription_required_message'),
'requires_subscription' => true,
'subscription_url' => '/account_settings.php?tab=subscription'
]);
exit;
}
$cart_data = $input['cart'] ?? [];
if (empty($cart_data)) {
throw new Exception('Cart is empty');
}
// Handle different cart formats (same as handleCartPayment)
$credit_items = [];
$track_items = [];
if (isset($cart_data['credits']) && isset($cart_data['tracks'])) {
// New mixed cart format with credits and tracks properties
$credit_items = $cart_data['credits'] ?? [];
$track_items = $cart_data['tracks'] ?? [];
} elseif (is_array($cart_data) && !empty($cart_data) && isset($cart_data[0]['package'])) {
// Frontend is sending credit items array directly
$credit_items = $cart_data;
$track_items = [];
} else {
// Legacy format - assume all items are credits
$credit_items = $cart_data;
$track_items = [];
}
// Double-check subscription for credit items
if (!empty($credit_items) && $has_active_subscription === false) {
error_log("handlePayPalPayment: Credit items in cart but no active subscription");
echo json_encode([
'success' => false,
'error' => t('checkout.subscription_required_error'),
'message' => t('checkout.subscription_required_for_credits'),
'requires_subscription' => true,
'subscription_url' => '/account_settings.php?tab=subscription'
]);
exit;
}
error_log("handlePayPalPayment: Credit items = " . json_encode($credit_items));
error_log("handlePayPalPayment: Track items = " . json_encode($track_items));
// Calculate total amount and credits
$total_amount = 0;
$total_credits = 0;
$cart_summary = [];
// Process credit items
foreach ($credit_items as $item) {
if (!isset($item['package']) || !isset($item['quantity'])) {
error_log("handlePayPalPayment: Missing package or quantity in credit item: " . json_encode($item));
throw new Exception('Malformed credit item in cart');
}
$package_id = $item['package'];
$quantity = $item['quantity'];
if (!isset($credit_packages[$package_id])) {
throw new Exception('Invalid package in cart: ' . $package_id);
}
$package = $credit_packages[$package_id];
$item_total = $package['price'] * $quantity;
$item_credits = $package['credits'] * $quantity;
$total_amount += $item_total;
$total_credits += $item_credits;
$cart_summary[] = [
'type' => 'credit',
'package' => $package_id,
'name' => $package['name'],
'credits' => $item_credits,
'quantity' => $quantity,
'amount' => $item_total
];
}
// Process track items
foreach ($track_items as $item) {
if (!isset($item['track_id']) || !isset($item['title']) || !isset($item['price'])) {
error_log("handlePayPalPayment: Missing fields in track item: " . json_encode($item));
throw new Exception('Malformed track item in cart');
}
$track_price = $item['price'] * 100; // Convert to cents
$quantity = $item['quantity'] ?? 1;
$item_total = $track_price * $quantity;
$total_amount += $item_total;
$cart_summary[] = [
'type' => 'track',
'track_id' => $item['track_id'],
'title' => $item['title'],
'artist' => $item['artist'] ?? $item['artist_name'] ?? 'Unknown Artist',
'quantity' => $quantity,
'amount' => $item_total
];
}
// For now, redirect to a PayPal checkout page
// In a real implementation, you would create a PayPal order here
$paypal_url = "https://www.paypal.com/checkoutnow?token=" . generatePayPalToken($total_amount, $cart_summary);
// Log PayPal payment attempt
logPaymentEvent('paypal_payment_attempt', [
'user_id' => $_SESSION['user_id'],
'cart_items' => $cart_summary,
'total_credits' => $total_credits,
'amount' => $total_amount,
'paypal_url' => $paypal_url
]);
echo json_encode([
'success' => true,
'paypal_url' => $paypal_url,
'amount' => $total_amount,
'credits' => $total_credits,
'cart_summary' => $cart_summary
]);
}
function generatePayPalToken($amount, $cart_summary) {
// This is a placeholder function
// In a real implementation, you would:
// 1. Create a PayPal order via PayPal API
// 2. Return the PayPal order ID/token
// 3. Handle the payment completion via webhook
// For now, return a dummy token
return 'PAYPAL_' . uniqid() . '_' . time();
}
?>