false, 'error' => 'Methode niet toegestaan.']); exit; } $input = json_decode(file_get_contents('php://input'), true); if (!$input) { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Ongeldige input.']); exit; } /* ── PROMO DRY-RUN (check=true) ──────────────────────────────── */ if (!empty($input['check'])) { $PROMO_CODES = unserialize(PROMO_CODES); $PRICES = unserialize(PRICES); $code = strtoupper(trim($input['promo'] ?? '')); $combi_qty = 0; foreach ($input['tickets'] ?? [] as $t) { if (($t['type'] ?? '') === 'combi') $combi_qty += intval($t['qty'] ?? 0); } if (!isset($PROMO_CODES[$code])) { echo json_encode(['promo_ok' => false, 'error' => 'Ongeldige promotiecode.']); exit; } $pc = $PROMO_CODES[$code]; if (date('Y-m-d') > $pc['geldig_tot']) { echo json_encode(['promo_ok' => false, 'error' => 'Deze promotiecode is verlopen.']); exit; } if ($combi_qty < 1) { echo json_encode(['promo_ok' => false, 'error' => 'Deze code is enkel geldig op combitickets.']); exit; } // Max tickets check try { $pdo2 = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8", DB_USER, DB_PASS); $pdo2->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); $stmt2 = $pdo2->prepare("SELECT COUNT(*) FROM waves_tickets WHERE ticket_type = 'combi' AND betaald = 1 AND promo_code = ?"); $stmt2->execute([$code]); $verkocht = (int)$stmt2->fetchColumn(); if (isset($pc['max_tickets']) && ($verkocht + $combi_qty) > $pc['max_tickets']) { $resterend = max(0, $pc['max_tickets'] - $verkocht); echo json_encode(['promo_ok' => false, 'error' => 'Maximum bereikt. Nog ' . $resterend . ' ticket' . ($resterend === 1 ? '' : 's') . ' beschikbaar met korting.']); exit; } } catch (Exception $e) { /* DB fout — laat door */ } echo json_encode(['promo_ok' => true, 'korting' => $pc['korting']]); exit; } $fname = trim($input['fname'] ?? ''); $lname = trim($input['lname'] ?? ''); $email = trim($input['email'] ?? ''); $tickets = $input['tickets'] ?? []; $promo = strtoupper(trim($input['promo'] ?? '')); $postcode = trim($input['postcode'] ?? ''); if (!$fname || !$lname || !filter_var($email, FILTER_VALIDATE_EMAIL) || empty($tickets) || !$postcode) { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Ontbrekende gegevens.']); exit; } // Saniteer input $fname = htmlspecialchars(strip_tags($fname), ENT_QUOTES, 'UTF-8'); $lname = htmlspecialchars(strip_tags($lname), ENT_QUOTES, 'UTF-8'); $naam = $fname . ' ' . $lname; /* ── DATABASE VERBINDING ──────────────────────────────────────── */ try { $pdo = new PDO("mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=utf8", DB_USER, DB_PASS); $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION); } catch (Exception $e) { http_response_code(500); echo json_encode(['success' => false, 'error' => 'Databasefout.']); exit; } /* ── RATE LIMITING ────────────────────────────────────────────── */ $stmt = $pdo->prepare("SELECT COUNT(*) FROM waves_tickets WHERE aangemaakt > DATE_SUB(NOW(), INTERVAL 1 HOUR) AND email = ?"); $stmt->execute([$email]); if ((int)$stmt->fetchColumn() >= MAX_ORDERS_PER_EMAIL) { http_response_code(429); echo json_encode(['success' => false, 'error' => 'Te veel bestellingen. Probeer later opnieuw of neem contact op.']); exit; } /* ── TICKETLIJST OPBOUWEN ─────────────────────────────────────── */ $total_cents = 0; $beschrijving = []; $ticket_list = []; foreach ($tickets as $ticket) { $type = $ticket['type'] ?? ''; $qty = intval($ticket['qty'] ?? 0); if (!isset($PRODUCT_IDS[$type]) || $qty <= 0 || $qty > 20) continue; $total_cents += $qty * $PRICES[$type] * 100; $beschrijving[] = $qty . 'x ' . $LABELS[$type]; for ($i = 0; $i < $qty; $i++) { $ticket_list[] = ['type' => $type, 'label' => $LABELS[$type], 'prijs' => $PRICES[$type]]; } } if (empty($ticket_list) || $total_cents <= 0) { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Geen geldige tickets.']); exit; } /* ── PROMOTIECODE VALIDEREN ───────────────────────────────────── */ $korting_cents = 0; $korting_label = ''; $PROMO_CODES = unserialize(PROMO_CODES); if ($promo !== '') { if (!isset($PROMO_CODES[$promo])) { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Ongeldige promotiecode.']); exit; } $pc = $PROMO_CODES[$promo]; // Geldigheidsperiode controleren if (date('Y-m-d') > $pc['geldig_tot']) { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Deze promotiecode is verlopen.']); exit; } // Minstens 1 combiticket vereist $combi_qty = 0; foreach ($tickets as $t) { if (($t['type'] ?? '') === 'combi') $combi_qty += intval($t['qty'] ?? 0); } if ($combi_qty < 1) { http_response_code(400); echo json_encode(['success' => false, 'error' => 'Deze code is enkel geldig op combitickets.']); exit; } // Maximum verkochte tickets controleren if (isset($pc['max_tickets'])) { $stmt_max = $pdo->prepare("SELECT COUNT(*) FROM waves_tickets WHERE ticket_type = 'combi' AND betaald = 1 AND promo_code = ?"); $stmt_max->execute([$promo]); $verkocht = (int)$stmt_max->fetchColumn(); if (($verkocht + $combi_qty) > $pc['max_tickets']) { $resterend = max(0, $pc['max_tickets'] - $verkocht); http_response_code(400); echo json_encode(['success' => false, 'error' => 'Maximum bereikt. Nog ' . $resterend . ' ticket' . ($resterend === 1 ? '' : 's') . ' beschikbaar met korting.']); exit; } } // Korting berekenen — enkel op combitickets $combi_totaal = $combi_qty * $PRICES['combi'] * 100; $korting_cents = intval($combi_totaal * $pc['korting']); $total_cents = $total_cents - $korting_cents; $korting_label = '- ' . ($pc['korting'] * 100) . '% op combitickets (' . $promo . ')'; $beschrijving[] = $korting_label; // Pas de prijs per combiticket aan in de ticket_list $korting_per_ticket = round($PRICES['combi'] * $pc['korting'], 2); foreach ($ticket_list as &$t) { if ($t['type'] === 'combi') { $t['prijs'] = round($PRICES['combi'] - $korting_per_ticket, 2); } } unset($t); } /* ── MOLLIE BETAALLINK AANMAKEN ───────────────────────────────── */ // We gebruiken een tijdelijke interne order_id (auto-increment via DB) // Eerst een placeholder-rij aanmaken om een ID te krijgen $pdo->prepare("INSERT INTO waves_tickets (ticket_code, order_id, order_ref, naam, email, ticket_type, ticket_label, prijs, betaald) VALUES ('PENDING', 0, 'PENDING', ?, ?, ?, ?, ?, 0)") ->execute([$naam, $email, $ticket_list[0]['type'], $ticket_list[0]['label'], $ticket_list[0]['prijs']]); $first_id = $pdo->lastInsertId(); $mollie_ref = 'WF26-' . str_pad($first_id, 6, '0', STR_PAD_LEFT); // Verwijder de placeholder weer $pdo->prepare("DELETE FROM waves_tickets WHERE id = ?")->execute([$first_id]); $ch = curl_init('https://api.mollie.com/v2/payments'); curl_setopt_array($ch, [ CURLOPT_RETURNTRANSFER => true, CURLOPT_POST => true, CURLOPT_POSTFIELDS => json_encode([ 'amount' => ['currency' => 'EUR', 'value' => number_format($total_cents / 100, 2, '.', '')], 'description' => 'Waves Festival 2026 — ' . implode(', ', $beschrijving), 'redirectUrl' => REDIRECT_URL . '?ref=' . $mollie_ref, 'webhookUrl' => WEBHOOK_URL, 'metadata' => [ 'ref' => $mollie_ref, 'naam' => $naam, 'email' => $email, 'postcode' => $postcode, 'tickets' => $ticket_list, 'promo' => $promo, 'korting' => $korting_cents, ], ]), CURLOPT_HTTPHEADER => ['Content-Type: application/json', 'Authorization: Bearer ' . MOLLIE_KEY], CURLOPT_TIMEOUT => 15, CURLOPT_SSL_VERIFYPEER => true, ]); $mollie = json_decode(curl_exec($ch), true); curl_close($ch); if (empty($mollie['_links']['checkout']['href'])) { http_response_code(500); echo json_encode(['success' => false, 'error' => 'Betaallink mislukt.']); exit; } echo json_encode([ 'success' => true, 'payment_url' => $mollie['_links']['checkout']['href'], 'ref' => $mollie_ref, ]);