Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bcc19577eb |
@@ -1,180 +0,0 @@
|
|||||||
# 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
|
|
||||||
Binary file not shown.
+1
-18
@@ -15,8 +15,6 @@ $user = currentUser();
|
|||||||
$message = '';
|
$message = '';
|
||||||
$error = '';
|
$error = '';
|
||||||
|
|
||||||
$requestId = null;
|
|
||||||
|
|
||||||
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
||||||
$data = [
|
$data = [
|
||||||
'band_id' => $bandId,
|
'band_id' => $bandId,
|
||||||
@@ -32,7 +30,6 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') {
|
|||||||
$error = 'Bitte Datum und Ort ausfüllen.';
|
$error = 'Bitte Datum und Ort ausfüllen.';
|
||||||
} else {
|
} else {
|
||||||
createRequest($data);
|
createRequest($data);
|
||||||
$requestId = (int) db()->lastInsertId();
|
|
||||||
$message = 'Anfrage gespeichert und an die Band gemeldet.';
|
$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']);
|
sendEmail('info@' . preg_replace('/\s+/', '', strtolower($band['name'])) . '.ch', 'Neue Anfrage', 'Neue Anfrage für ' . $band['name']);
|
||||||
}
|
}
|
||||||
@@ -55,21 +52,8 @@ $settings = settings();
|
|||||||
<p>PayPal Zahlungsabwicklung ist <?= $settings['paypal_enabled'] === '1' ? 'aktiviert' : 'optional' ?>, Service Fee: <?= htmlspecialchars($settings['service_fee']) ?>%.</p>
|
<p>PayPal Zahlungsabwicklung ist <?= $settings['paypal_enabled'] === '1' ? 'aktiviert' : 'optional' ?>, Service Fee: <?= htmlspecialchars($settings['service_fee']) ?>%.</p>
|
||||||
</header>
|
</header>
|
||||||
<main>
|
<main>
|
||||||
<?php if ($message): ?>
|
<?php if ($message): ?><div class="alert alert-success"><?= htmlspecialchars($message) ?></div><?php endif; ?>
|
||||||
<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 ($error): ?><div class="alert alert-error"><?= htmlspecialchars($error) ?></div><?php endif; ?>
|
||||||
|
|
||||||
<?php if (!$message): ?>
|
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<label>Event-Datum
|
<label>Event-Datum
|
||||||
<input type="date" class="form-control" name="event_date" required>
|
<input type="date" class="form-control" name="event_date" required>
|
||||||
@@ -88,7 +72,6 @@ $settings = settings();
|
|||||||
</label>
|
</label>
|
||||||
<button class="btn-primary">Anfrage senden</button>
|
<button class="btn-primary">Anfrage senden</button>
|
||||||
</form>
|
</form>
|
||||||
<?php endif; ?>
|
|
||||||
</main>
|
</main>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -74,17 +74,3 @@ CREATE TABLE IF NOT EXISTS settings (
|
|||||||
key TEXT PRIMARY KEY,
|
key TEXT PRIMARY KEY,
|
||||||
value TEXT NOT NULL
|
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
|
|
||||||
);
|
|
||||||
|
|||||||
@@ -1,167 +0,0 @@
|
|||||||
<?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>
|
|
||||||
@@ -1,95 +0,0 @@
|
|||||||
<?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()]);
|
|
||||||
}
|
|
||||||
+2
-120
@@ -66,138 +66,20 @@ if ($user['role'] === 'band') {
|
|||||||
</label>
|
</label>
|
||||||
<button class="btn-primary">Speichern</button>
|
<button class="btn-primary">Speichern</button>
|
||||||
</form>
|
</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: ?>
|
<?php else: ?>
|
||||||
<p>Du hast noch kein Bandprofil angelegt.</p>
|
<p>Du hast noch kein Bandprofil angelegt.</p>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<?php if ($user['role'] === 'kunde'): ?>
|
<?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>
|
<h2>Meine Anfragen</h2>
|
||||||
<table class="table">
|
<table class="table">
|
||||||
<thead><tr><th>Band</th><th>Datum</th><th>Status</th><th>Zahlung</th><th>Aktion</th></tr></thead>
|
<thead><tr><th>Band</th><th>Datum</th><th>Status</th></tr></thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<?php
|
<?php foreach (userRequests((int) $user['id']) as $request): $bandName = findBand((int) $request['band_id']); ?>
|
||||||
$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>
|
<tr>
|
||||||
<td><?= htmlspecialchars($bandName['name'] ?? 'Band #' . $request['band_id']) ?></td>
|
<td><?= htmlspecialchars($bandName['name'] ?? 'Band #' . $request['band_id']) ?></td>
|
||||||
<td><?= htmlspecialchars($request['event_date']) ?></td>
|
<td><?= htmlspecialchars($request['event_date']) ?></td>
|
||||||
<td><?= htmlspecialchars($request['status']) ?></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>
|
</tr>
|
||||||
<?php endforeach; ?>
|
<?php endforeach; ?>
|
||||||
</tbody>
|
</tbody>
|
||||||
|
|||||||
@@ -1,120 +0,0 @@
|
|||||||
<?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