changes to the checkout format
This commit is contained in:
@@ -163,7 +163,7 @@
|
|||||||
margin-top: 1.5rem;
|
margin-top: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NEU: Loading State UI */
|
/* Loading State UI */
|
||||||
#setup-loading-state,
|
#setup-loading-state,
|
||||||
#setup-success-state {
|
#setup-success-state {
|
||||||
display: none;
|
display: none;
|
||||||
@@ -190,7 +190,7 @@
|
|||||||
height: 2rem;
|
height: 2rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* NEU: Success State UI */
|
/* Success State UI */
|
||||||
.success-icon {
|
.success-icon {
|
||||||
width: 70px;
|
width: 70px;
|
||||||
height: 70px;
|
height: 70px;
|
||||||
@@ -210,7 +210,7 @@
|
|||||||
border-radius: 12px;
|
border-radius: 12px;
|
||||||
padding: 1.5rem;
|
padding: 1.5rem;
|
||||||
margin: 1.5rem auto;
|
margin: 1.5rem auto;
|
||||||
max-width: 400px;
|
max-width: 450px;
|
||||||
text-align: left;
|
text-align: left;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -233,8 +233,27 @@
|
|||||||
margin-bottom: 1.5rem;
|
margin-bottom: 1.5rem;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.btn.success-btn {
|
||||||
|
background-color: #1b8a3e;
|
||||||
|
border-color: #1b8a3e;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
.btn.success-btn:hover {
|
||||||
|
background-color: #146c2e;
|
||||||
|
border-color: #146c2e;
|
||||||
|
}
|
||||||
|
|
||||||
@media (max-width: 720px) {
|
@media (max-width: 720px) {
|
||||||
.booking-review__grid, .booking-review__form-grid { grid-template-columns: 1fr; }
|
.booking-review__grid, .booking-review__form-grid { grid-template-columns: 1fr; }
|
||||||
|
.booking-review__actions { flex-direction: column; }
|
||||||
|
.booking-review__actions .btn { width: 100%; min-width: 0; }
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Verstecke UI-Elemente beim Drucken */
|
||||||
|
@media print {
|
||||||
|
header, footer, nav, .booking-review__actions { display: none !important; }
|
||||||
|
body { background: white; }
|
||||||
|
.booking-review__panel { box-shadow: none; border: none; padding: 0; }
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
@@ -255,9 +274,11 @@
|
|||||||
<span class="booking-review__label">Paket</span>
|
<span class="booking-review__label">Paket</span>
|
||||||
<div class="booking-review__value">{{ package }}</div>
|
<div class="booking-review__value">{{ package }}</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="booking-review__card">
|
<!-- OPTIMIERT: Eindeutiger Hinweis auf Rechnungsstellung -->
|
||||||
<span class="booking-review__label">Preis</span>
|
<div class="booking-review__card" style="border-left: 4px solid #1b8a3e;">
|
||||||
<div class="booking-review__value">{{ package_price }} € <br><small>Kauf auf Rechnung (30 Tage)</small></div>
|
<span class="booking-review__label">Zahlungsart</span>
|
||||||
|
<div class="booking-review__value" style="color: #1b8a3e;">Kauf auf Rechnung</div>
|
||||||
|
<small style="color: #5e757d; font-weight: 600;">30 Tage Zahlungsziel nach Bereitstellung</small>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -265,7 +286,6 @@
|
|||||||
Die Software-Instanz wird nach dem Absenden <strong>sofort für 30 Tage freigeschaltet</strong>. Die finale Laufzeit aktiviert sich automatisch nach Zahlungseingang durch Ihren Schulträger.
|
Die Software-Instanz wird nach dem Absenden <strong>sofort für 30 Tage freigeschaltet</strong>. Die finale Laufzeit aktiviert sich automatisch nach Zahlungseingang durch Ihren Schulträger.
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Das Formular schickt die Daten an /booking_payment_async -->
|
|
||||||
<form id="checkout-form" method="POST" action="{{ url_for('booking_payment_async') }}">
|
<form id="checkout-form" method="POST" action="{{ url_for('booking_payment_async') }}">
|
||||||
<input type="hidden" name="package" value="{{ package_key }}">
|
<input type="hidden" name="package" value="{{ package_key }}">
|
||||||
|
|
||||||
@@ -288,10 +308,11 @@
|
|||||||
<input id="contact_email" name="contact_email" type="email" required value="{{ form_data.contact_email }}">
|
<input id="contact_email" name="contact_email" type="email" required value="{{ form_data.contact_email }}">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- OPTIMIERT: Rechnungsdaten inkl. Kassenzeichen -->
|
||||||
<div class="booking-review__section-title">2. Rechnungsadresse (Schulträger)</div>
|
<div class="booking-review__section-title">2. Rechnungsadresse (Schulträger)</div>
|
||||||
<div class="booking-review__field booking-review__field--full">
|
<div class="booking-review__field booking-review__field--full">
|
||||||
<label for="billing_organization">Einrichtung / Amt *</label>
|
<label for="billing_organization">Einrichtung / Amt *</label>
|
||||||
<input id="billing_organization" name="billing_organization" type="text" required value="{{ form_data.billing_organization }}">
|
<input id="billing_organization" name="billing_organization" type="text" required placeholder="z.B. Stadt Neustadt - Schulverwaltungsamt" value="{{ form_data.billing_organization }}">
|
||||||
</div>
|
</div>
|
||||||
<div class="booking-review__field booking-review__field--full">
|
<div class="booking-review__field booking-review__field--full">
|
||||||
<label for="billing_street">Straße & Hausnummer *</label>
|
<label for="billing_street">Straße & Hausnummer *</label>
|
||||||
@@ -305,21 +326,30 @@
|
|||||||
<label for="billing_city">Ort *</label>
|
<label for="billing_city">Ort *</label>
|
||||||
<input id="billing_city" name="billing_city" type="text" required value="{{ form_data.billing_city }}">
|
<input id="billing_city" name="billing_city" type="text" required value="{{ form_data.billing_city }}">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="booking-review__field booking-review__field--full">
|
||||||
|
<label for="billing_reference">Kassenzeichen / Haushaltsstelle / Aktenzeichen <span style="font-weight: normal; color: #5e757d;">(Optional)</span></label>
|
||||||
|
<input id="billing_reference" name="billing_reference" type="text" placeholder="Wichtig für die Zahlungsabwicklung der Stadt" value="{{ form_data.billing_reference }}">
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="booking-review__section-title">3. Technische Einrichtung</div>
|
<div class="booking-review__section-title">3. Technische Einrichtung</div>
|
||||||
<div class="booking-review__field booking-review__field--full">
|
<div class="booking-review__field booking-review__field--full">
|
||||||
<label for="tenant_slug">Gewünschte Schul-URL *</label>
|
<label for="tenant_slug">Gewünschte Schul-URL *</label>
|
||||||
<div class="input-group--subdomain">
|
<div class="input-group--subdomain">
|
||||||
<input id="tenant_slug" name="tenant_slug" type="text" required pattern="[a-z0-9-]+" placeholder="gymnasium-neustadt" value="{{ form_data.tenant_slug }}">
|
<input id="tenant_slug" name="tenant_slug" type="text" required pattern="[a-z0-9-]+" title="Nur Kleinbuchstaben, Zahlen und Bindestriche erlaubt" placeholder="gymnasium-neustadt" value="{{ form_data.tenant_slug }}">
|
||||||
<span class="domain-suffix">.euresoftware.de</span>
|
<span class="domain-suffix">.invario-software.de</span>
|
||||||
</div>
|
</div>
|
||||||
<div id="tenant_slug_feedback" style="margin-top:0.4rem; font-size:0.9rem; font-weight:600;"></div>
|
<div id="tenant_slug_feedback" style="margin-top:0.4rem; font-size:0.9rem; font-weight:600;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- OPTIMIERT: Rechtsgültige Links für Verträge -->
|
||||||
<div class="booking-review__field booking-review__field--full booking-review__consent">
|
<div class="booking-review__field booking-review__field--full booking-review__consent">
|
||||||
<label for="agreements_accepted">
|
<label for="agreements_accepted">
|
||||||
<input id="agreements_accepted" name="agreements_accepted" type="checkbox" required>
|
<input id="agreements_accepted" name="agreements_accepted" type="checkbox" required>
|
||||||
<span>Ich habe die Nutzungsbedingungen und den obligatorischen AVV gelesen und stimme diesen verbindlich zu.</span>
|
<span>
|
||||||
|
Ich habe die <a href="{{ url_for('agb') }}" target="_blank" rel="noopener">AGB (PDF)</a>,
|
||||||
|
die <a href="{{ url_for('datenschutz') }}" target="_blank" rel="noopener">Datenschutzerklärung</a> sowie den gesetzlich erforderlichen
|
||||||
|
<a href="{{ url_for('avv') }}" target="_blank" rel="noopener">Auftragsverarbeitungsvertrag (AVV)</a> gelesen und stimme diesen Dokumenten verbindlich zu.
|
||||||
|
</span>
|
||||||
</label>
|
</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@@ -338,7 +368,7 @@
|
|||||||
<!-- ========================================== -->
|
<!-- ========================================== -->
|
||||||
<div id="setup-loading-state">
|
<div id="setup-loading-state">
|
||||||
<h2>Instanz wird bereitgestellt</h2>
|
<h2>Instanz wird bereitgestellt</h2>
|
||||||
<p>Ihre Bestellung war erfolgreich. Die Rechnung wird gerade verschickt.</p>
|
<p>Ihre Bestellung wird verarbeitet. Die Rechnung wird zeitgleich generiert.</p>
|
||||||
<div class="spinner"></div>
|
<div class="spinner"></div>
|
||||||
<div class="status-messages" id="loading-status-text">Validierung der Daten...</div>
|
<div class="status-messages" id="loading-status-text">Validierung der Daten...</div>
|
||||||
<p style="margin-top: 2rem; color: #5e757d; font-size: 0.9rem;">Bitte schließen Sie dieses Fenster nicht. Dies dauert in der Regel 15-30 Sekunden.</p>
|
<p style="margin-top: 2rem; color: #5e757d; font-size: 0.9rem;">Bitte schließen Sie dieses Fenster nicht. Dies dauert in der Regel 15-30 Sekunden.</p>
|
||||||
@@ -350,7 +380,7 @@
|
|||||||
<div id="setup-success-state">
|
<div id="setup-success-state">
|
||||||
<div class="success-icon">✓</div>
|
<div class="success-icon">✓</div>
|
||||||
<h2>Ihre Schule ist online!</h2>
|
<h2>Ihre Schule ist online!</h2>
|
||||||
<p>Das Setup wurde erfolgreich abgeschlossen. Sie können sich nun einloggen.</p>
|
<p>Das Setup wurde erfolgreich abgeschlossen. Ihre Rechnung wurde an die angegebene E-Mail-Adresse versandt.</p>
|
||||||
|
|
||||||
<div class="warning-text">
|
<div class="warning-text">
|
||||||
Aus Datenschutzgründen (AVV) müssen Sie das generierte Passwort nach dem ersten Login zwingend ändern!
|
Aus Datenschutzgründen (AVV) müssen Sie das generierte Passwort nach dem ersten Login zwingend ändern!
|
||||||
@@ -362,7 +392,16 @@
|
|||||||
<p><strong>Initial-Passwort:</strong> <code id="success-pass">-</code></p>
|
<p><strong>Initial-Passwort:</strong> <code id="success-pass">-</code></p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<a id="btn-go-to-tenant" href="#" class="btn" target="_blank" style="width: 100%; max-width: 400px;">Jetzt zur Schulplattform wechseln</a>
|
<!-- OPTIMIERT: Rechnungs-Download, Weiterleitung und Drucken -->
|
||||||
|
<div class="booking-review__actions" style="justify-content: center; margin-top: 2rem;">
|
||||||
|
<a id="btn-download-invoice" href="#" class="btn success-btn" target="_blank" style="display: none;">
|
||||||
|
📄 Rechnung (PDF) herunterladen
|
||||||
|
</a>
|
||||||
|
<a id="btn-go-to-tenant" href="#" class="btn" target="_blank">Zur Schulplattform wechseln</a>
|
||||||
|
<button onclick="window.print();" class="btn secondary" type="button">
|
||||||
|
🖨️ Zugangsdaten drucken
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
@@ -417,7 +456,7 @@
|
|||||||
// AJAX Form Submit & Transition to Loading
|
// AJAX Form Submit & Transition to Loading
|
||||||
// ==========================================
|
// ==========================================
|
||||||
form.addEventListener('submit', async (e) => {
|
form.addEventListener('submit', async (e) => {
|
||||||
e.preventDefault(); // Verhindert das klassische Neuladen der Seite
|
e.preventDefault();
|
||||||
errorAlert.style.display = 'none';
|
errorAlert.style.display = 'none';
|
||||||
submitBtn.disabled = true;
|
submitBtn.disabled = true;
|
||||||
|
|
||||||
@@ -425,7 +464,6 @@
|
|||||||
const tenantSlug = formData.get('tenant_slug');
|
const tenantSlug = formData.get('tenant_slug');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 1. Schicke Daten ans Backend (muss im Backend JSON zurückgeben!)
|
|
||||||
const response = await fetch(form.action, {
|
const response = await fetch(form.action, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
body: formData,
|
body: formData,
|
||||||
@@ -438,11 +476,11 @@
|
|||||||
throw new Error(result.error || 'Es gab ein Problem bei der Buchung.');
|
throw new Error(result.error || 'Es gab ein Problem bei der Buchung.');
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Erfolg: Formular verstecken, Ladebildschirm zeigen
|
// Erfolg: Formular verstecken, Ladebildschirm zeigen
|
||||||
formState.style.display = 'none';
|
formState.style.display = 'none';
|
||||||
loadingState.style.display = 'block';
|
loadingState.style.display = 'block';
|
||||||
|
|
||||||
// 3. Starte Polling
|
// Starte Polling
|
||||||
startPolling(tenantSlug);
|
startPolling(tenantSlug);
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@@ -456,10 +494,10 @@
|
|||||||
// Polling Logik (Warten auf den Server)
|
// Polling Logik (Warten auf den Server)
|
||||||
// ==========================================
|
// ==========================================
|
||||||
async function startPolling(slug) {
|
async function startPolling(slug) {
|
||||||
// Fake-Texte für den Nutzer, damit er weiß, dass das System arbeitet
|
|
||||||
const statusMessages = [
|
const statusMessages = [
|
||||||
"Prüfe Serverkapazitäten...",
|
"Registriere Schuldatenbank...",
|
||||||
"Richte Datenbank ein...",
|
"Erstelle Auftragsverarbeitungsvertrag...",
|
||||||
|
"Generiere Rechnung für Schulträger...",
|
||||||
"Konfiguriere Nginx & Zertifikate...",
|
"Konfiguriere Nginx & Zertifikate...",
|
||||||
"Führe finale Health-Checks durch..."
|
"Führe finale Health-Checks durch..."
|
||||||
];
|
];
|
||||||
@@ -472,17 +510,15 @@
|
|||||||
}
|
}
|
||||||
}, 3500);
|
}, 3500);
|
||||||
|
|
||||||
// Das eigentliche Polling zum Backend
|
|
||||||
const checkStatus = async () => {
|
const checkStatus = async () => {
|
||||||
try {
|
try {
|
||||||
// Backend-Route, die den Status des tenant_slug abfragt
|
|
||||||
const res = await fetch(`/instance_request/status?slug=${slug}`);
|
const res = await fetch(`/instance_request/status?slug=${slug}`);
|
||||||
const data = await res.json();
|
const data = await res.json();
|
||||||
|
|
||||||
if (data.status === 'ready') {
|
if (data.status === 'ready') {
|
||||||
// SETUP FERTIG!
|
// SETUP FERTIG!
|
||||||
clearInterval(msgInterval);
|
clearInterval(msgInterval);
|
||||||
showSuccessState(data.url, data.username, data.password);
|
showSuccessState(data.url, data.username, data.password, data.invoice_url);
|
||||||
} else if (data.status === 'failed') {
|
} else if (data.status === 'failed') {
|
||||||
// SETUP FEHLGESCHLAGEN
|
// SETUP FEHLGESCHLAGEN
|
||||||
clearInterval(msgInterval);
|
clearInterval(msgInterval);
|
||||||
@@ -493,7 +529,6 @@
|
|||||||
setTimeout(checkStatus, 3000);
|
setTimeout(checkStatus, 3000);
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
// Bei Netzwerkfehlern während des Pollings einfach weiter probieren
|
|
||||||
setTimeout(checkStatus, 3000);
|
setTimeout(checkStatus, 3000);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@@ -505,7 +540,7 @@
|
|||||||
// ==========================================
|
// ==========================================
|
||||||
// Erfolgs-Bildschirm anzeigen
|
// Erfolgs-Bildschirm anzeigen
|
||||||
// ==========================================
|
// ==========================================
|
||||||
function showSuccessState(url, user, pass) {
|
function showSuccessState(url, user, pass, invoiceUrl) {
|
||||||
loadingState.style.display = 'none';
|
loadingState.style.display = 'none';
|
||||||
successState.style.display = 'block';
|
successState.style.display = 'block';
|
||||||
|
|
||||||
@@ -514,6 +549,13 @@
|
|||||||
document.getElementById('success-user').textContent = user;
|
document.getElementById('success-user').textContent = user;
|
||||||
document.getElementById('success-pass').textContent = pass;
|
document.getElementById('success-pass').textContent = pass;
|
||||||
document.getElementById('btn-go-to-tenant').href = url;
|
document.getElementById('btn-go-to-tenant').href = url;
|
||||||
|
|
||||||
|
// Rechnungs-Button einblenden, falls URL vorhanden
|
||||||
|
const invoiceBtn = document.getElementById('btn-download-invoice');
|
||||||
|
if (invoiceUrl) {
|
||||||
|
invoiceBtn.href = invoiceUrl;
|
||||||
|
invoiceBtn.style.display = 'inline-flex';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})();
|
})();
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
Reference in New Issue
Block a user