Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 615866d215 |
@@ -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
|
||||
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_PAYPAL_CLIENT_ID¤cy=CHF"></script>
|
||||
```
|
||||
|
||||
**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
|
||||
+18
-1
@@ -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();
|
||||
<p>PayPal Zahlungsabwicklung ist <?= $settings['paypal_enabled'] === '1' ? 'aktiviert' : 'optional' ?>, Service Fee: <?= htmlspecialchars($settings['service_fee']) ?>%.</p>
|
||||
</header>
|
||||
<main>
|
||||
<?php if ($message): ?><div class="alert alert-success"><?= htmlspecialchars($message) ?></div><?php endif; ?>
|
||||
<?php if ($message): ?>
|
||||
<div class="alert alert-success">
|
||||
<?= htmlspecialchars($message) ?>
|
||||
<?php if ($requestId && $settings['paypal_enabled'] === '1'): ?>
|
||||
<div style="margin-top: 1rem;">
|
||||
<a href="paypal-checkout.php?request_id=<?= $requestId ?>" class="btn-primary" style="display: inline-block; padding: 0.75rem 1.5rem; text-decoration: none;">
|
||||
Jetzt mit PayPal bezahlen
|
||||
</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
<?php if ($error): ?><div class="alert alert-error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
|
||||
|
||||
<?php if (!$message): ?>
|
||||
<form method="post">
|
||||
<label>Event-Datum
|
||||
<input type="date" class="form-control" name="event_date" required>
|
||||
@@ -72,6 +88,7 @@ $settings = settings();
|
||||
</label>
|
||||
<button class="btn-primary">Anfrage senden</button>
|
||||
</form>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -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
|
||||
);
|
||||
|
||||
@@ -0,0 +1,167 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/auth.php';
|
||||
|
||||
$requestId = isset($_GET['request_id']) ? (int) $_GET['request_id'] : 0;
|
||||
if (!$requestId) {
|
||||
http_response_code(400);
|
||||
echo 'Keine Anfrage-ID angegeben';
|
||||
exit;
|
||||
}
|
||||
|
||||
$user = currentUser();
|
||||
|
||||
// Get request details
|
||||
$stmt = db()->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.';
|
||||
}
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html lang="de">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>PayPal Zahlung – <?= SITE_NAME ?></title>
|
||||
<link rel="stylesheet" href="assets/css/style.css">
|
||||
</head>
|
||||
<body>
|
||||
<header>
|
||||
<a class="badge" href="profil.php">← Zurück zum Profil</a>
|
||||
<h1>Zahlung für Buchung</h1>
|
||||
</header>
|
||||
<main style="max-width: 600px; margin: 0 auto;">
|
||||
<?php if (isset($message)): ?>
|
||||
<div class="alert alert-success"><?= htmlspecialchars($message) ?></div>
|
||||
<?php else: ?>
|
||||
<h2>Buchungsdetails</h2>
|
||||
<table class="table" style="margin-bottom: 2rem;">
|
||||
<tr><td><strong>Band:</strong></td><td><?= htmlspecialchars($request['band_name']) ?></td></tr>
|
||||
<tr><td><strong>Event-Datum:</strong></td><td><?= htmlspecialchars($request['event_date']) ?></td></tr>
|
||||
<tr><td><strong>Location:</strong></td><td><?= htmlspecialchars($request['location']) ?></td></tr>
|
||||
<tr><td><strong>Event-Typ:</strong></td><td><?= htmlspecialchars($request['event_type']) ?></td></tr>
|
||||
</table>
|
||||
|
||||
<h2>Zahlungsübersicht</h2>
|
||||
<table class="table" style="margin-bottom: 2rem;">
|
||||
<tr><td><strong>Band-Gage:</strong></td><td><?= formatPrice($bandPrice) ?></td></tr>
|
||||
<tr><td><strong>Service Fee (<?= htmlspecialchars($serviceFeePercent) ?>%):</strong></td><td><?= formatPrice((int) $serviceFee) ?></td></tr>
|
||||
<tr style="border-top: 2px solid #ffb703;"><td><strong>Gesamtbetrag:</strong></td><td><strong><?= formatPrice((int) $totalAmount) ?></strong></td></tr>
|
||||
</table>
|
||||
|
||||
<div id="payment-status" style="display:none; padding: 1rem; margin-bottom: 1rem; border-radius: 4px;"></div>
|
||||
|
||||
<!-- PayPal Button Container -->
|
||||
<div id="paypal-button-container" style="margin: 2rem 0;"></div>
|
||||
|
||||
<p style="color: #666; font-size: 0.875rem; margin-top: 2rem;">
|
||||
<strong>Hinweis:</strong> Dies ist eine Demo-Integration. Für die Produktivumgebung benötigen Sie echte PayPal API-Credentials.
|
||||
Aktuell wird im Sandbox-Modus gearbeitet.
|
||||
</p>
|
||||
<?php endif; ?>
|
||||
</main>
|
||||
|
||||
<?php if (!isset($message)): ?>
|
||||
<!-- PayPal SDK -->
|
||||
<script src="https://www.paypal.com/sdk/js?client-id=YOUR_PAYPAL_CLIENT_ID¤cy=CHF"></script>
|
||||
|
||||
<script>
|
||||
paypal.Buttons({
|
||||
createOrder: function(data, actions) {
|
||||
return actions.order.create({
|
||||
purchase_units: [{
|
||||
amount: {
|
||||
value: '<?= number_format($totalAmount, 2, '.', '') ?>',
|
||||
currency_code: 'CHF',
|
||||
breakdown: {
|
||||
item_total: {
|
||||
value: '<?= number_format($bandPrice, 2, '.', '') ?>',
|
||||
currency_code: 'CHF'
|
||||
},
|
||||
tax_total: {
|
||||
value: '<?= number_format($serviceFee, 2, '.', '') ?>',
|
||||
currency_code: 'CHF'
|
||||
}
|
||||
}
|
||||
},
|
||||
description: 'Buchung: <?= htmlspecialchars($request['band_name']) ?> - <?= htmlspecialchars($request['event_date']) ?>'
|
||||
}]
|
||||
});
|
||||
},
|
||||
onApprove: function(data, actions) {
|
||||
return actions.order.capture().then(function(details) {
|
||||
// Save payment to database
|
||||
const statusDiv = document.getElementById('payment-status');
|
||||
statusDiv.style.display = 'block';
|
||||
statusDiv.style.background = '#28a745';
|
||||
statusDiv.style.color = 'white';
|
||||
statusDiv.textContent = 'Zahlung erfolgreich! Verarbeite Transaktion...';
|
||||
|
||||
fetch('paypal-process.php', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
request_id: <?= $requestId ?>,
|
||||
amount: <?= $bandPrice ?>,
|
||||
service_fee: <?= number_format($serviceFee, 2, '.', '') ?>,
|
||||
total_amount: <?= number_format($totalAmount, 2, '.', '') ?>,
|
||||
paypal_order_id: data.orderID,
|
||||
paypal_payer_id: details.payer.payer_id
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(result => {
|
||||
if (result.success) {
|
||||
statusDiv.textContent = 'Zahlung erfolgreich abgeschlossen! Sie werden weitergeleitet...';
|
||||
setTimeout(() => {
|
||||
window.location.href = 'profil.php?payment_success=1';
|
||||
}, 2000);
|
||||
} else {
|
||||
statusDiv.style.background = '#dc3545';
|
||||
statusDiv.textContent = 'Fehler beim Speichern der Zahlung: ' + result.error;
|
||||
}
|
||||
});
|
||||
});
|
||||
},
|
||||
onError: function(err) {
|
||||
const statusDiv = document.getElementById('payment-status');
|
||||
statusDiv.style.display = 'block';
|
||||
statusDiv.style.background = '#dc3545';
|
||||
statusDiv.style.color = 'white';
|
||||
statusDiv.textContent = 'Fehler bei der Zahlung: ' + err;
|
||||
}
|
||||
}).render('#paypal-button-container');
|
||||
</script>
|
||||
<?php endif; ?>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,95 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/auth.php';
|
||||
require_once __DIR__ . '/includes/email.php';
|
||||
requireLogin();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$user = currentUser();
|
||||
|
||||
// Get JSON input
|
||||
$input = json_decode(file_get_contents('php://input'), true);
|
||||
|
||||
if (!$input || !isset($input['request_id'])) {
|
||||
http_response_code(400);
|
||||
echo json_encode(['error' => '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()]);
|
||||
}
|
||||
+120
-2
@@ -66,20 +66,138 @@ if ($user['role'] === 'band') {
|
||||
</label>
|
||||
<button class="btn-primary">Speichern</button>
|
||||
</form>
|
||||
|
||||
<h2 style="margin-top: 2rem;">Band-Galerie</h2>
|
||||
<div id="upload-status" style="display:none; padding: 1rem; margin-bottom: 1rem; background: #28a745; color: white; border-radius: 4px;"></div>
|
||||
<div style="margin-bottom: 1rem;">
|
||||
<label class="btn-primary" style="display: inline-block; cursor: pointer;">
|
||||
<input type="file" id="image-upload" accept="image/*" style="display: none;">
|
||||
+ Bild hochladen
|
||||
</label>
|
||||
<small style="display: block; margin-top: 0.5rem; color: #666;">Max 5MB (JPG, PNG, GIF, WEBP)</small>
|
||||
</div>
|
||||
<div id="gallery" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(200px, 1fr)); gap: 1rem;">
|
||||
<?php foreach (bandMedia((int) $band['id']) as $media): ?>
|
||||
<div class="gallery-item" data-media-id="<?= $media['id'] ?>">
|
||||
<img src="<?= htmlspecialchars($media['url']) ?>" alt="Band Foto" style="width: 100%; height: 200px; object-fit: cover; border-radius: 4px;">
|
||||
<button class="delete-image" data-id="<?= $media['id'] ?>" style="margin-top: 0.5rem; background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; width: 100%;">Löschen</button>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('image-upload').addEventListener('change', function(e) {
|
||||
const file = e.target.files[0];
|
||||
if (!file) return;
|
||||
|
||||
const formData = new FormData();
|
||||
formData.append('image', file);
|
||||
|
||||
const statusDiv = document.getElementById('upload-status');
|
||||
statusDiv.style.display = 'block';
|
||||
statusDiv.style.background = '#ffc107';
|
||||
statusDiv.textContent = 'Uploading...';
|
||||
|
||||
fetch('upload-handler.php', {
|
||||
method: 'POST',
|
||||
body: formData
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
statusDiv.style.background = '#28a745';
|
||||
statusDiv.textContent = data.message;
|
||||
|
||||
// Add to gallery
|
||||
const gallery = document.getElementById('gallery');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'gallery-item';
|
||||
div.setAttribute('data-media-id', data.id);
|
||||
div.innerHTML = `
|
||||
<img src="${data.url}" alt="Band Foto" style="width: 100%; height: 200px; object-fit: cover; border-radius: 4px;">
|
||||
<button class="delete-image" data-id="${data.id}" style="margin-top: 0.5rem; background: #dc3545; color: white; border: none; padding: 0.5rem 1rem; border-radius: 4px; cursor: pointer; width: 100%;">Löschen</button>
|
||||
`;
|
||||
gallery.appendChild(div);
|
||||
|
||||
setTimeout(() => { statusDiv.style.display = 'none'; }, 3000);
|
||||
} else {
|
||||
statusDiv.style.background = '#dc3545';
|
||||
statusDiv.textContent = data.error;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
statusDiv.style.background = '#dc3545';
|
||||
statusDiv.textContent = 'Upload fehlgeschlagen: ' + error.message;
|
||||
});
|
||||
|
||||
e.target.value = '';
|
||||
});
|
||||
|
||||
document.addEventListener('click', function(e) {
|
||||
if (e.target.classList.contains('delete-image')) {
|
||||
if (!confirm('Bild wirklich löschen?')) return;
|
||||
|
||||
const mediaId = e.target.getAttribute('data-id');
|
||||
const galleryItem = e.target.closest('.gallery-item');
|
||||
|
||||
fetch('upload-handler.php', {
|
||||
method: 'DELETE',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: 'media_id=' + mediaId
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
galleryItem.remove();
|
||||
} else {
|
||||
alert(data.error);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
<?php else: ?>
|
||||
<p>Du hast noch kein Bandprofil angelegt.</p>
|
||||
<?php endif; ?>
|
||||
|
||||
<?php if ($user['role'] === 'kunde'): ?>
|
||||
<?php if (isset($_GET['payment_success'])): ?>
|
||||
<div class="alert alert-success">Zahlung erfolgreich abgeschlossen! Vielen Dank für Ihre Buchung.</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<h2>Meine Anfragen</h2>
|
||||
<table class="table">
|
||||
<thead><tr><th>Band</th><th>Datum</th><th>Status</th></tr></thead>
|
||||
<thead><tr><th>Band</th><th>Datum</th><th>Status</th><th>Zahlung</th><th>Aktion</th></tr></thead>
|
||||
<tbody>
|
||||
<?php foreach (userRequests((int) $user['id']) as $request): $bandName = findBand((int) $request['band_id']); ?>
|
||||
<?php
|
||||
$settings = settings();
|
||||
foreach (userRequests((int) $user['id']) as $request):
|
||||
$bandName = findBand((int) $request['band_id']);
|
||||
|
||||
// Check payment status
|
||||
$stmt = db()->prepare('SELECT * FROM payments WHERE request_id = :id AND status = "completed"');
|
||||
$stmt->execute([':id' => $request['id']]);
|
||||
$payment = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||
?>
|
||||
<tr>
|
||||
<td><?= htmlspecialchars($bandName['name'] ?? 'Band #' . $request['band_id']) ?></td>
|
||||
<td><?= htmlspecialchars($request['event_date']) ?></td>
|
||||
<td><?= htmlspecialchars($request['status']) ?></td>
|
||||
<td>
|
||||
<?php if ($payment): ?>
|
||||
<span style="color: #28a745;">✓ Bezahlt</span><br>
|
||||
<small style="color: #666;"><?= formatPrice((int) $payment['total_amount']) ?></small>
|
||||
<?php else: ?>
|
||||
<span style="color: #dc3545;">Ausstehend</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
<td>
|
||||
<?php if (!$payment && $settings['paypal_enabled'] === '1'): ?>
|
||||
<a href="paypal-checkout.php?request_id=<?= $request['id'] ?>" class="badge" style="background: #0070ba; color: white; text-decoration: none;">
|
||||
PayPal bezahlen
|
||||
</a>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
|
||||
@@ -0,0 +1,120 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
require_once __DIR__ . '/includes/auth.php';
|
||||
requireLogin();
|
||||
|
||||
header('Content-Type: application/json');
|
||||
|
||||
$user = currentUser();
|
||||
if ($user['role'] !== 'band') {
|
||||
http_response_code(403);
|
||||
echo json_encode(['error' => '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']);
|
||||
Reference in New Issue
Block a user