T1neu: Unternehmer-Pflicht-Checkbox im Checkout, protokolliert mit AGB-Version
B2B-Gate (AGB v0.6 par. 1 Abs. 4): Das Buchungs-Modal verlangt vor jedem Stripe-Checkout die Bestaetigung 'Ich bestelle in Ausuebung meiner gewerblichen oder selbstaendigen beruflichen Taetigkeit (par. 14 BGB)'. - booking_confirmations: user, Buchungstyp/-ref, AGB-Version, IP, confirmed_at (Nachweis laut Datenschutz Abschnitt 7) - Checkout-Link erscheint erst nach der Checkbox; CheckoutController verlangt zusaetzlich serverseitig eine zeitnahe Bestaetigung (30 Min.) fuer Abo/Einzel-PM/Credit-Topup — Direktaufrufe laufen ins Leere - config/legal.php als zentrale AGB-Versionsquelle (Seite + Protokoll, Uebergangsloesung bis zum legal_documents-System E3) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
99ab47ba1f
commit
8916bcce7c
9 changed files with 311 additions and 7 deletions
|
|
@ -6,6 +6,7 @@ use App\Enums\Tier;
|
|||
use App\Enums\UserPaymentOptionStatus;
|
||||
use App\Enums\VatTreatment;
|
||||
use App\Exceptions\InsufficientCreditsException;
|
||||
use App\Models\BookingConfirmation;
|
||||
use App\Models\Plan;
|
||||
use App\Models\UserPaymentOption;
|
||||
use App\Services\Billing\CreditPricingService;
|
||||
|
|
@ -33,11 +34,41 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte
|
|||
/** Im Bestätigungs-Modal angezeigte Buchung (inkl. USt.-Aufschlüsselung). */
|
||||
public ?array $booking = null;
|
||||
|
||||
/**
|
||||
* Unternehmer-Pflicht-Checkbox (T1neu, AGB v0.6 § 1 Abs. 4): erst nach
|
||||
* der Bestätigung wird der Checkout-Link angeboten; die Bestätigung
|
||||
* selbst wird mit Timestamp + AGB-Version protokolliert.
|
||||
*/
|
||||
public bool $entrepreneurConfirmed = false;
|
||||
|
||||
public function formatEuro(int $cents): string
|
||||
{
|
||||
return number_format($cents / 100, $cents % 100 === 0 ? 0 : 2, ',', '.') . ' €';
|
||||
}
|
||||
|
||||
/**
|
||||
* Protokolliert die Unternehmer-Bestätigung (T1neu) in dem Moment, in dem
|
||||
* die Pflicht-Checkbox gesetzt wird — mit Zeitpunkt, IP und der aktuell
|
||||
* veröffentlichten AGB-Version. Der Checkout-Controller verlangt eine
|
||||
* zeitnahe Bestätigung, Direktaufrufe ohne Checkbox laufen ins Leere.
|
||||
*/
|
||||
public function updatedEntrepreneurConfirmed(bool $value): void
|
||||
{
|
||||
if (! $value || ! $this->booking) {
|
||||
return;
|
||||
}
|
||||
|
||||
BookingConfirmation::query()->create([
|
||||
'user_id' => auth()->id(),
|
||||
'booking_type' => $this->booking['type'],
|
||||
'booking_ref' => $this->booking['ref'],
|
||||
'interval' => $this->booking['interval'],
|
||||
'agb_version' => (string) config('legal.agb_version'),
|
||||
'ip' => request()->ip(),
|
||||
'confirmed_at' => now(),
|
||||
]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Öffnet das Bestätigungs-Modal für eine Buchung (Tarif-Abo, Einzel-PM
|
||||
* oder Credit-Paket) mit USt.-Aufschlüsselung anhand der Rechnungsadresse.
|
||||
|
|
@ -68,6 +99,8 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte
|
|||
$vatCents = $resolver->taxCentsFor($netCents, $treatment);
|
||||
|
||||
$this->booking = [
|
||||
'type' => $type,
|
||||
'ref' => $ref,
|
||||
'title' => $title,
|
||||
'subtitle' => $subtitle,
|
||||
'interval' => $interval,
|
||||
|
|
@ -81,6 +114,9 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte
|
|||
'needs_address' => ! $user->hasCompleteBillingAddress(),
|
||||
];
|
||||
|
||||
// Jede Buchung verlangt eine frische Unternehmer-Bestätigung.
|
||||
$this->entrepreneurConfirmed = false;
|
||||
|
||||
Flux::modal('confirm-booking')->show();
|
||||
}
|
||||
|
||||
|
|
@ -942,13 +978,37 @@ new #[Layout('components.layouts.app'), Title('Buchungen & Add-ons')] class exte
|
|||
</flux:button>
|
||||
</div>
|
||||
@else
|
||||
{{-- Unternehmer-Pflicht-Checkbox (T1neu, AGB § 1 Abs. 4):
|
||||
Bestätigung wird serverseitig protokolliert (Timestamp,
|
||||
IP, AGB-Version); ohne sie kein Checkout-Link — und der
|
||||
Checkout-Controller verlangt sie zusätzlich. --}}
|
||||
<div class="space-y-2">
|
||||
<flux:switch
|
||||
align="left"
|
||||
wire:model.live="entrepreneurConfirmed"
|
||||
:label="__('Ich bestelle in Ausübung meiner gewerblichen oder selbständigen beruflichen Tätigkeit (§ 14 BGB) und akzeptiere die AGB.')"
|
||||
/>
|
||||
<p class="text-[11.5px] text-[color:var(--color-ink-3)] m-0 ps-0">
|
||||
{{ __('Das Angebot richtet sich ausschließlich an Unternehmen, Selbstständige und Agenturen.') }}
|
||||
<a href="{{ route('agb', ['edition' => 'de']) }}" target="_blank" rel="noopener" class="underline underline-offset-2 hover:text-[color:var(--color-ink)]">
|
||||
{{ __('AGB (Version :version)', ['version' => config('legal.agb_version')]) }}
|
||||
</a>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div class="flex justify-end gap-2">
|
||||
<flux:modal.close>
|
||||
<flux:button variant="filled">{{ __('Abbrechen') }}</flux:button>
|
||||
</flux:modal.close>
|
||||
<flux:button variant="primary" icon="lock-closed" href="{{ $booking['url'] }}">
|
||||
{{ __('Kostenpflichtig buchen') }}
|
||||
</flux:button>
|
||||
@if ($entrepreneurConfirmed)
|
||||
<flux:button variant="primary" icon="lock-closed" href="{{ $booking['url'] }}">
|
||||
{{ __('Kostenpflichtig buchen') }}
|
||||
</flux:button>
|
||||
@else
|
||||
<flux:button variant="primary" icon="lock-closed" disabled>
|
||||
{{ __('Kostenpflichtig buchen') }}
|
||||
</flux:button>
|
||||
@endif
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue