14 von 16

Admin · Kapitel 14 von 16

PHP · MySQL · HTML/CSS – Schritt für Schritt

Kapitel 13

checkout.php – Bestellung & Rechnung

Minimal-Lösung: eine Seite, die den Warenkorb als druckbare Rechnung anzeigt und in die DB einträgt. Statt eines echten Zahlungsmoduls wird die Rechnung mit Strg+P ausgedruckt (oder als PDF gespeichert).

Der Code

checkout.php
<?php
require_once('db.php');
session_start();

// login pflicht
if (!isset($_SESSION['user_id'])) {
    header('Location: login.php');
    exit;
}

$warenkorb = $_SESSION['warenkorb'] ?? [];
if (empty($warenkorb)) {
    header('Location: warenkorb.php');
    exit;
}

// produktdaten holen (gleiche technik wie warenkorb.php)
$ids = array_keys($warenkorb);
$placeholders = implode(',', array_fill(0, count($ids), '?'));
$stmt = $conn->prepare("SELECT id, name, price FROM products WHERE id IN ($placeholders)");
$stmt->execute($ids);
$produkte = $stmt->fetchAll(PDO::FETCH_ASSOC);

$gesamt = 0;
foreach ($produkte as $p) {
    $gesamt += $p['price'] * $warenkorb[$p['id']];
}

// bestellung in db speichern (nur beim ersten aufruf!)
if (!isset($_SESSION['letzte_bestellung_id'])) {
    $conn->beginTransaction();
    try {
        // bestellung anlegen
        $stmt = $conn->prepare("INSERT INTO orders (user_id) VALUES (?)");
        $stmt->execute([$_SESSION['user_id']]);
        $bestell_id = $conn->lastInsertId();

        // positionen anlegen
        $pos = $conn->prepare("INSERT INTO order_items (order_id, product_id, menge, preis) VALUES (?, ?, ?, ?)");
        foreach ($produkte as $p) {
            $pos->execute([$bestell_id, $p['id'], $warenkorb[$p['id']], $p['price']]);
        }

        $conn->commit();
        $_SESSION['letzte_bestellung_id'] = $bestell_id;
        unset($_SESSION['warenkorb']);
    } catch (Exception $e) {
        $conn->rollBack();
        die('Bestellung fehlgeschlagen.');
    }
}

$bestell_id = $_SESSION['letzte_bestellung_id'];
?>
<!DOCTYPE html>
<html lang="de">
<head>
    <meta charset="UTF-8">
    <title>Rechnung Nr. <?= (int)$bestell_id ?></title>
    <link rel="stylesheet" href="styles.css">
    <style>
        /* nur beim drucken: navbar und buttons ausblenden */
        @media print {
            ul, .no-print { display: none; }
        }
    </style>
</head>
<body>
<?php include('navbar.php'); ?>

<h1>Rechnung Nr. <?= (int)$bestell_id ?></h1>
<p>Kunde: <?= htmlspecialchars($_SESSION['username'], ENT_QUOTES, 'UTF-8') ?></p>
<p>Datum: <?= date('d.m.Y H:i') ?></p>

<table border="1" cellpadding="8">
    <tr><th>Produkt</th><th>Menge</th><th>Einzelpreis</th><th>Summe</th></tr>
    <?php foreach ($produkte as $p): $m = $warenkorb[$p['id']] ?? 1; ?>
        <tr>
            <td><?= htmlspecialchars($p['name'], ENT_QUOTES, 'UTF-8') ?></td>
            <td><?= (int)$m ?></td>
            <td><?= number_format($p['price'], 2, ',', '.') ?> €</td>
            <td><?= number_format($p['price'] * $m, 2, ',', '.') ?> €</td>
        </tr>
    <?php endforeach; ?>
    <tr>
        <td colspan="3"><strong>Gesamt:</strong></td>
        <td><strong><?= number_format($gesamt, 2, ',', '.') ?> €</strong></td>
    </tr>
</table>

<br>
<div class="no-print">
    <button onclick="window.print()">Rechnung drucken (Strg+P)</button>
    <a href="index.php">Zurück zum Shop</a>
</div>

</body>
</html>

Was hier passiert

  • Login-Pflicht – bestellen ohne Account geht nicht.
  • Warenkorb leer? → Redirect – keine Rechnung über nichts.
  • Transaction (beginTransaction / commit / rollBack) – die Bestellung und ihre Positionen sind eine logische Einheit. Wenn beim Einfügen einer Position was schiefgeht, wird die halbfertige Bestellung wieder zurückgerollt. Atomicity.
  • Session-Merker letzte_bestellung_id – verhindert doppelte Bestellungen bei F5 auf der Rechnungsseite. Ist die Bestellung einmal eingetragen, wird beim erneuten Aufruf nur noch angezeigt.
  • Warenkorb leerenunset($_SESSION['warenkorb']) nach erfolgreicher Bestellung.
  • @media print – CSS-Regel, die beim Drucken die navbar und die Buttons ausblendet. So sieht die gedruckte Seite nach echter Rechnung aus.
Prüfer-Frage garantiert: "Was ist eine Transaktion und warum brauchst du sie hier?" → Weil eine Bestellung ohne Positionen sinnlos ist. Entweder werden beide Inserts durchgeführt oder keiner. Rollback bei Fehlern.
Für Voll-DSGVO-konform: zusätzliche Felder wie Rechnungs- und Lieferadresse in einer Tabelle rechnungen speichern und beim Bestellen abfragen. Für die Minimal-Version reicht der Username. Wenn der Prüfer nach Rechnungsadresse fragt: sag, dass sie beim Registrieren erfasst wird – dann fehlt in users noch eine Adress-Spalte.

16 Kapitel · ein Projekt

Der Codeinander Lernpfad

DEEN in Vorbereitung

Grundlagen

01GrundlagenIntro & Setup 02GrundlagenDatenbank-Schema 03Grundlagendb.php – Verbindung

Auth

04Authregister.php 05Authlogin.php 06Authlogout.php

Shop

07Shopnavbar.php 08Shopindex.php – Liste 09Shopproductdetail.php

Warenkorb

10Warenkorbcart_add.php 11Warenkorbwarenkorb.php 12Warenkorbcart_remove.php

Admin

13Adminstatistiken.php + Admin 14Admincheckout.php + Rechnung

Referenz

15ReferenzSQL-Cheatsheet 16Referenzzertifizierung-Checkliste