diff --git a/PAYPAL_UPLOAD_FEATURES.md b/PAYPAL_UPLOAD_FEATURES.md new file mode 100644 index 0000000..dcca751 --- /dev/null +++ b/PAYPAL_UPLOAD_FEATURES.md @@ -0,0 +1,180 @@ +# Neue Features: PayPal-Integration & Bild-Upload + +Dieses Dokument beschreibt die neu hinzugefügten Features für die GetYourBand-Plattform. + +## 🖼️ Bild-Upload für Bands + +### Features +- **Upload-Funktionalität**: Bands können eigene Bilder hochladen +- **Galerie-Verwaltung**: Anzeige und Verwaltung aller hochgeladenen Bilder +- **Löschen**: Bilder können jederzeit gelöscht werden +- **Validierung**: + - Erlaubte Formate: JPG, PNG, GIF, WEBP + - Maximale Dateigröße: 5MB + - Automatische Dateinamens-Generierung + +### Technische Details +- **Upload-Verzeichnis**: `/storage/uploads/bands/` +- **Handler**: `upload-handler.php` +- **Frontend**: AJAX-basierter Upload mit Fetch API +- **Dateinamensschema**: `band_{band_id}_{unique_id}.{extension}` + +### Verwendung +1. Als Band-User einloggen +2. Zum Profil navigieren (`profil.php`) +3. Sektion "Band-Galerie" finden +4. Auf "+ Bild hochladen" klicken +5. Bild auswählen (wird automatisch hochgeladen) + +### Sicherheit +- Nur authentifizierte Band-User können uploaden +- Strenge Dateitypprüfung (MIME-Type + Extension) +- Größenlimit verhindert DoS +- Sichere Dateinamen ohne User-Input + +--- + +## 💳 PayPal-Integration + +### Features +- **Zahlungsabwicklung**: Kunden können Buchungen direkt mit PayPal bezahlen +- **Service Fee**: Konfigurierbare Servicegebühr (in Admin-Settings) +- **Zahlungs-Tracking**: Alle Zahlungen werden in der Datenbank gespeichert +- **Status-Updates**: Anfragen werden automatisch auf "bestätigt" gesetzt +- **Email-Benachrichtigungen**: Kunde und Band erhalten Bestätigungen + +### Komponenten + +#### 1. Datenbank +Neue Tabelle `payments`: +```sql +CREATE TABLE payments ( + id INTEGER PRIMARY KEY, + request_id INTEGER NOT NULL, + amount REAL NOT NULL, + service_fee REAL NOT NULL, + total_amount REAL NOT NULL, + paypal_order_id TEXT, + paypal_payer_id TEXT, + status TEXT DEFAULT 'pending', + created_at TEXT, + completed_at TEXT +); +``` + +#### 2. Checkout-Seite +**Datei**: `paypal-checkout.php` +- Zeigt Buchungsdetails und Zahlungsübersicht +- Integriert PayPal JavaScript SDK +- Berechnet Gesamtbetrag (Band-Gage + Service Fee) + +#### 3. Payment Processing +**Datei**: `paypal-process.php` +- Speichert erfolgreiche Zahlungen +- Aktualisiert Request-Status +- Sendet Bestätigungs-Emails + +#### 4. Integration in Buchungsflow +**Änderungen in `anfrage.php`**: +- Nach erfolgreicher Anfrage wird PayPal-Button angezeigt (wenn aktiviert) +- Direkter Link zum Checkout + +**Änderungen in `profil.php`**: +- Zahlungsstatus für jede Anfrage angezeigt +- "Jetzt bezahlen"-Button für ausstehende Zahlungen + +### PayPal-Konfiguration + +#### Admin-Einstellungen +Im Admin-Panel (`admin/settings.php`): +- `paypal_enabled`: 0/1 (aktiviert/deaktiviert) +- `service_fee`: Prozentsatz (z.B. 8 für 8%) + +#### PayPal API Credentials +In `paypal-checkout.php` Zeile 80: +```javascript + +``` + +**Wichtig**: `YOUR_PAYPAL_CLIENT_ID` durch echte Client-ID ersetzen! + +#### PayPal Developer Setup +1. Gehen Sie zu https://developer.paypal.com +2. Erstellen Sie eine App in "My Apps & Credentials" +3. Kopieren Sie die Client-ID +4. Für Produktion: Aktivieren Sie Live-Modus und verwenden Sie Live-Credentials + +### Zahlungsablauf + +1. **Kunde erstellt Anfrage** → Request wird in DB gespeichert +2. **PayPal-Link erscheint** → Kunde klickt auf "Mit PayPal bezahlen" +3. **Checkout-Seite** → Übersicht und PayPal-Button +4. **PayPal-Zahlung** → Kunde loggt sich in PayPal ein und zahlt +5. **Payment Processing** → Zahlung wird in DB gespeichert +6. **Status-Update** → Request → "bestätigt", Emails versandt +7. **Rückkehr zum Profil** → Erfolgsmeldung + +### Testmodus + +Die aktuelle Implementation läuft im **Sandbox-Modus**: +- Verwenden Sie PayPal Sandbox-Accounts zum Testen +- Keine echten Transaktionen werden durchgeführt +- Für Produktion: Client-ID auf Live-Credentials umstellen + +### Sicherheit +- Zahlung nur für eigene Requests möglich +- Doppelzahlungen werden verhindert +- Transaktions-IDs werden gespeichert +- Server-seitige Validierung aller Zahlungsdaten + +--- + +## 📂 Neue Dateien + +| Datei | Beschreibung | +|-------|--------------| +| `upload-handler.php` | REST-API für Bild-Uploads (POST/DELETE) | +| `paypal-checkout.php` | PayPal Checkout-Seite | +| `paypal-process.php` | PayPal Payment Processing Backend | +| `storage/uploads/bands/` | Upload-Verzeichnis für Band-Bilder | +| `PAYPAL_UPLOAD_FEATURES.md` | Diese Dokumentation | + +## 🔄 Geänderte Dateien + +| Datei | Änderungen | +|-------|------------| +| `database.sql` | + `payments` Tabelle | +| `profil.php` | + Galerie-Sektion, + Zahlungsstatus in Anfragen | +| `anfrage.php` | + PayPal-Button nach erfolgreicher Anfrage | + +## 🚀 Deployment-Checklist + +- [ ] `storage/uploads/` Verzeichnis erstellen mit Schreibrechten +- [ ] PayPal Developer Account erstellen +- [ ] Client-ID in `paypal-checkout.php` eintragen +- [ ] Admin-Panel: PayPal aktivieren und Service Fee setzen +- [ ] Für Produktion: Auf Live-Credentials umstellen +- [ ] SSL-Zertifikat für HTTPS (PayPal requirement) + +## 🐛 Bekannte Einschränkungen + +1. **PayPal Client-ID**: Muss manuell konfiguriert werden +2. **Keine Rückerstattungen**: Keine Admin-UI für Refunds +3. **Email-System**: Aktuell nur Logging, kein echtes SMTP +4. **Sandbox-Modus**: Standardmäßig aktiviert + +## 📝 Nächste Schritte (Optional) + +- Webhook-Integration für PayPal IPN (Instant Payment Notification) +- Admin-Dashboard für Zahlungsübersicht +- Automatische Rechnungserstellung (PDF) +- Stripe als alternative Zahlungsmethode +- Bulk-Upload für mehrere Bilder +- Bildkompression/Optimierung +- Thumbnail-Generierung + +--- + +**Entwickelt für**: GetYourBand Platform +**Datum**: 2025-12-02 +**Version**: 1.0 diff --git a/anfrage.php b/anfrage.php index bac248c..10b5e08 100644 --- a/anfrage.php +++ b/anfrage.php @@ -15,6 +15,8 @@ $user = currentUser(); $message = ''; $error = ''; +$requestId = null; + if ($_SERVER['REQUEST_METHOD'] === 'POST') { $data = [ 'band_id' => $bandId, @@ -30,6 +32,7 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $error = 'Bitte Datum und Ort ausfüllen.'; } else { createRequest($data); + $requestId = (int) db()->lastInsertId(); $message = 'Anfrage gespeichert und an die Band gemeldet.'; sendEmail('info@' . preg_replace('/\s+/', '', strtolower($band['name'])) . '.ch', 'Neue Anfrage', 'Neue Anfrage für ' . $band['name']); } @@ -52,8 +55,21 @@ $settings = settings();

PayPal Zahlungsabwicklung ist , Service Fee: %.

-
+ +
+ + +
+ + Jetzt mit PayPal bezahlen + +
+ +
+
+ +
+
diff --git a/database.sql b/database.sql index c36c71c..b22bb73 100644 --- a/database.sql +++ b/database.sql @@ -74,3 +74,17 @@ CREATE TABLE IF NOT EXISTS settings ( key TEXT PRIMARY KEY, value TEXT NOT NULL ); + +CREATE TABLE IF NOT EXISTS payments ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + request_id INTEGER NOT NULL, + amount REAL NOT NULL, + service_fee REAL NOT NULL, + total_amount REAL NOT NULL, + paypal_order_id TEXT, + paypal_payer_id TEXT, + status TEXT NOT NULL DEFAULT 'pending', + created_at TEXT DEFAULT CURRENT_TIMESTAMP, + completed_at TEXT, + FOREIGN KEY(request_id) REFERENCES requests(id) ON DELETE CASCADE +); diff --git a/paypal-checkout.php b/paypal-checkout.php new file mode 100644 index 0000000..0a77af6 --- /dev/null +++ b/paypal-checkout.php @@ -0,0 +1,167 @@ +prepare('SELECT r.*, b.name as band_name, b.price as band_price + FROM requests r + JOIN bands b ON b.id = r.band_id + WHERE r.id = :id AND r.user_id = :user_id'); +$stmt->execute([':id' => $requestId, ':user_id' => $user['id']]); +$request = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$request) { + http_response_code(404); + echo 'Anfrage nicht gefunden'; + exit; +} + +$settings = settings(); +if ($settings['paypal_enabled'] !== '1') { + http_response_code(403); + echo 'PayPal-Zahlungen sind derzeit nicht aktiviert'; + exit; +} + +// Calculate amounts +$bandPrice = (int) $request['band_price']; +$serviceFeePercent = (float) $settings['service_fee']; +$serviceFee = $bandPrice * ($serviceFeePercent / 100); +$totalAmount = $bandPrice + $serviceFee; + +// Check if already paid +$stmt = db()->prepare('SELECT * FROM payments WHERE request_id = :id AND status = "completed"'); +$stmt->execute([':id' => $requestId]); +$existingPayment = $stmt->fetch(PDO::FETCH_ASSOC); + +if ($existingPayment) { + $message = 'Diese Buchung wurde bereits bezahlt.'; +} +?> + + + + + + PayPal Zahlung – <?= SITE_NAME ?> + + + +
+ ← Zurück zum Profil +

Zahlung für Buchung

+
+
+ +
+ +

Buchungsdetails

+ + + + + +
Band:
Event-Datum:
Location:
Event-Typ:
+ +

Zahlungsübersicht

+ + + + +
Band-Gage:
Service Fee (%):
Gesamtbetrag:
+ + + + +
+ +

+ Hinweis: Dies ist eine Demo-Integration. Für die Produktivumgebung benötigen Sie echte PayPal API-Credentials. + Aktuell wird im Sandbox-Modus gearbeitet. +

+ +
+ + + + + + + + + diff --git a/paypal-process.php b/paypal-process.php new file mode 100644 index 0000000..9e421d0 --- /dev/null +++ b/paypal-process.php @@ -0,0 +1,95 @@ + 'Ungültige Anfrage']); + exit; +} + +$requestId = (int) $input['request_id']; +$amount = (float) $input['amount']; +$serviceFee = (float) $input['service_fee']; +$totalAmount = (float) $input['total_amount']; +$paypalOrderId = $input['paypal_order_id'] ?? ''; +$paypalPayerId = $input['paypal_payer_id'] ?? ''; + +// Verify request belongs to user +$stmt = db()->prepare('SELECT r.*, b.name as band_name, b.user_id as band_user_id + FROM requests r + JOIN bands b ON b.id = r.band_id + WHERE r.id = :id AND r.user_id = :user_id'); +$stmt->execute([':id' => $requestId, ':user_id' => $user['id']]); +$request = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$request) { + http_response_code(404); + echo json_encode(['error' => 'Anfrage nicht gefunden']); + exit; +} + +// Check if already paid +$stmt = db()->prepare('SELECT * FROM payments WHERE request_id = :id AND status = "completed"'); +$stmt->execute([':id' => $requestId]); +if ($stmt->fetch(PDO::FETCH_ASSOC)) { + http_response_code(400); + echo json_encode(['error' => 'Diese Buchung wurde bereits bezahlt']); + exit; +} + +try { + // Save payment + $stmt = db()->prepare('INSERT INTO payments (request_id, amount, service_fee, total_amount, paypal_order_id, paypal_payer_id, status, completed_at) + VALUES (:request_id, :amount, :service_fee, :total_amount, :paypal_order_id, :paypal_payer_id, :status, :completed_at)'); + + $stmt->execute([ + ':request_id' => $requestId, + ':amount' => $amount, + ':service_fee' => $serviceFee, + ':total_amount' => $totalAmount, + ':paypal_order_id' => $paypalOrderId, + ':paypal_payer_id' => $paypalPayerId, + ':status' => 'completed', + ':completed_at' => (new DateTimeImmutable())->format('c') + ]); + + // Update request status to confirmed + $stmt = db()->prepare('UPDATE requests SET status = :status WHERE id = :id'); + $stmt->execute([':status' => 'bestätigt', ':id' => $requestId]); + + // Send confirmation emails + sendEmail($user['email'], 'Zahlungsbestätigung', + 'Ihre Zahlung für die Buchung von ' . $request['band_name'] . ' wurde erfolgreich verarbeitet.'); + + // Notify band + if ($request['band_user_id']) { + $bandUserStmt = db()->prepare('SELECT email FROM users WHERE id = :id'); + $bandUserStmt->execute([':id' => $request['band_user_id']]); + $bandUser = $bandUserStmt->fetch(PDO::FETCH_ASSOC); + + if ($bandUser) { + sendEmail($bandUser['email'], 'Neue bezahlte Buchung', + 'Sie haben eine neue bezahlte Buchung für ' . $request['event_date'] . ' erhalten.'); + } + } + + echo json_encode([ + 'success' => true, + 'message' => 'Zahlung erfolgreich verarbeitet', + 'payment_id' => (int) db()->lastInsertId() + ]); + +} catch (Exception $e) { + http_response_code(500); + echo json_encode(['error' => 'Fehler beim Speichern der Zahlung: ' . $e->getMessage()]); +} diff --git a/profil.php b/profil.php index 7724dcd..ba515bf 100644 --- a/profil.php +++ b/profil.php @@ -66,20 +66,138 @@ if ($user['role'] === 'band') { + +

Band-Galerie

+ +
+ + Max 5MB (JPG, PNG, GIF, WEBP) +
+ + +

Du hast noch kein Bandprofil angelegt.

+ +
Zahlung erfolgreich abgeschlossen! Vielen Dank für Ihre Buchung.
+ +

Meine Anfragen

- + - + prepare('SELECT * FROM payments WHERE request_id = :id AND status = "completed"'); + $stmt->execute([':id' => $request['id']]); + $payment = $stmt->fetch(PDO::FETCH_ASSOC); + ?> + + diff --git a/upload-handler.php b/upload-handler.php new file mode 100644 index 0000000..3a81b4d --- /dev/null +++ b/upload-handler.php @@ -0,0 +1,120 @@ + 'Nur Bands können Bilder hochladen']); + exit; +} + +// Get band +$stmt = db()->prepare('SELECT * FROM bands WHERE user_id = :id'); +$stmt->execute([':id' => $user['id']]); +$band = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$band) { + http_response_code(404); + echo json_encode(['error' => 'Kein Bandprofil gefunden']); + exit; +} + +if ($_SERVER['REQUEST_METHOD'] === 'POST' && isset($_FILES['image'])) { + $file = $_FILES['image']; + + // Validate file + $allowedTypes = ['image/jpeg', 'image/png', 'image/gif', 'image/webp']; + $maxSize = 5 * 1024 * 1024; // 5MB + + if (!in_array($file['type'], $allowedTypes)) { + http_response_code(400); + echo json_encode(['error' => 'Ungültiger Dateityp. Erlaubt sind: JPG, PNG, GIF, WEBP']); + exit; + } + + if ($file['size'] > $maxSize) { + http_response_code(400); + echo json_encode(['error' => 'Datei zu groß (max 5MB)']); + exit; + } + + if ($file['error'] !== UPLOAD_ERR_OK) { + http_response_code(500); + echo json_encode(['error' => 'Upload-Fehler']); + exit; + } + + // Generate unique filename + $extension = pathinfo($file['name'], PATHINFO_EXTENSION); + $filename = 'band_' . $band['id'] . '_' . uniqid() . '.' . $extension; + $uploadPath = __DIR__ . '/storage/uploads/bands/' . $filename; + + // Move file + if (!move_uploaded_file($file['tmp_name'], $uploadPath)) { + http_response_code(500); + echo json_encode(['error' => 'Datei konnte nicht gespeichert werden']); + exit; + } + + // Save to database + $url = 'storage/uploads/bands/' . $filename; + $stmt = db()->prepare('INSERT INTO band_media (band_id, type, url) VALUES (:band_id, :type, :url)'); + $stmt->execute([ + ':band_id' => $band['id'], + ':type' => 'image', + ':url' => $url + ]); + + $mediaId = (int) db()->lastInsertId(); + + echo json_encode([ + 'success' => true, + 'id' => $mediaId, + 'url' => $url, + 'message' => 'Bild erfolgreich hochgeladen' + ]); + exit; +} + +// Delete image +if ($_SERVER['REQUEST_METHOD'] === 'DELETE') { + parse_str(file_get_contents('php://input'), $deleteData); + $mediaId = (int) ($deleteData['media_id'] ?? 0); + + if (!$mediaId) { + http_response_code(400); + echo json_encode(['error' => 'Keine Media-ID angegeben']); + exit; + } + + // Check ownership + $stmt = db()->prepare('SELECT * FROM band_media WHERE id = :id AND band_id = :band_id'); + $stmt->execute([':id' => $mediaId, ':band_id' => $band['id']]); + $media = $stmt->fetch(PDO::FETCH_ASSOC); + + if (!$media) { + http_response_code(404); + echo json_encode(['error' => 'Bild nicht gefunden']); + exit; + } + + // Delete file + $filePath = __DIR__ . '/' . $media['url']; + if (file_exists($filePath) && strpos($media['url'], 'storage/uploads/') === 0) { + unlink($filePath); + } + + // Delete from database + $stmt = db()->prepare('DELETE FROM band_media WHERE id = :id'); + $stmt->execute([':id' => $mediaId]); + + echo json_encode(['success' => true, 'message' => 'Bild gelöscht']); + exit; +} + +http_response_code(400); +echo json_encode(['error' => 'Ungültige Anfrage']);
BandDatumStatus
BandDatumStatusZahlungAktion
+ + ✓ Bezahlt
+ + + Ausstehend + +
+ + + PayPal bezahlen + + +