Recht: Online-Widerruf ohne Login (gesetzlicher Widerrufsbutton) - Backend
POST /widerruf (host-agnostisch, throttled, Honeypot, eigener withdrawal-Error-Bag): persistiert nur Zuordnungsdaten (Name, E-Mail, optionale Bestellnummer - KEIN Grund, darf nicht verlangt werden) und versendet sofort die gesetzlich vorgeschriebene automatische Eingangsbestaetigung mit Zeitstempel an den Verbraucher plus eine interne Benachrichtigung an die Brand-Kontaktadresse des Hosts. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
47d03ec777
commit
8c0bf00604
9 changed files with 429 additions and 0 deletions
100
tests/Feature/Web/WithdrawalRequestTest.php
Normal file
100
tests/Feature/Web/WithdrawalRequestTest.php
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
<?php
|
||||
|
||||
use App\Mail\WithdrawalRequestReceived;
|
||||
use App\Mail\WithdrawalRequestSubmitted;
|
||||
use App\Models\WithdrawalRequest;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Tests\TestCase;
|
||||
|
||||
test('the hub shows the legally required withdrawal button and two-step modal', function () {
|
||||
/** @var TestCase $this */
|
||||
$response = $this->get('https://pressekonto.test/')->assertSuccessful();
|
||||
$content = $response->getContent();
|
||||
|
||||
expect($content)
|
||||
->toContain('Vertrag widerrufen')
|
||||
->toContain('open-withdrawal-modal')
|
||||
->toContain('action="'.route('widerruf.store').'"')
|
||||
->toContain('Jetzt widerrufen')
|
||||
// Kein Widerrufsgrund-Feld — darf gesetzlich nicht verlangt werden.
|
||||
->not->toContain('name="reason"');
|
||||
});
|
||||
|
||||
test('a withdrawal is persisted and both mails are sent immediately', function () {
|
||||
/** @var TestCase $this */
|
||||
Mail::fake();
|
||||
config()->set('domains.domains.pressekonto.brand.contact_email', 'kontakt@pressekonto.de');
|
||||
|
||||
$this->from('https://pressekonto.test/')
|
||||
->post('https://pressekonto.test/widerruf', [
|
||||
'name' => 'Paula Presse',
|
||||
'email' => 'paula@example.test',
|
||||
'order_reference' => 'STR-2026-0042',
|
||||
])
|
||||
->assertRedirect('https://pressekonto.test/')
|
||||
->assertSessionHas('withdrawal-status');
|
||||
|
||||
$request = WithdrawalRequest::query()->firstOrFail();
|
||||
|
||||
expect($request->name)->toBe('Paula Presse');
|
||||
expect($request->email)->toBe('paula@example.test');
|
||||
expect($request->order_reference)->toBe('STR-2026-0042');
|
||||
expect($request->host)->toBe('pressekonto.test');
|
||||
expect($request->status)->toBe('open');
|
||||
|
||||
// Gesetzlich vorgeschrieben: sofortige automatische Eingangsbestätigung.
|
||||
Mail::assertSent(WithdrawalRequestReceived::class, fn (WithdrawalRequestReceived $mail) => $mail->hasTo('paula@example.test'));
|
||||
Mail::assertSent(WithdrawalRequestSubmitted::class, fn (WithdrawalRequestSubmitted $mail) => $mail->hasTo('kontakt@pressekonto.de'));
|
||||
});
|
||||
|
||||
test('the order reference is optional', function () {
|
||||
/** @var TestCase $this */
|
||||
Mail::fake();
|
||||
|
||||
$this->post('https://pressekonto.test/widerruf', [
|
||||
'name' => 'Paula Presse',
|
||||
'email' => 'paula@example.test',
|
||||
])->assertSessionHas('withdrawal-status');
|
||||
|
||||
expect(WithdrawalRequest::query()->firstOrFail()->order_reference)->toBeNull();
|
||||
});
|
||||
|
||||
test('withdrawal validation errors land in the withdrawal error bag', function () {
|
||||
/** @var TestCase $this */
|
||||
Mail::fake();
|
||||
|
||||
$this->post('https://pressekonto.test/widerruf', [
|
||||
'name' => '',
|
||||
'email' => 'keine-mail',
|
||||
])->assertSessionHasErrorsIn('withdrawal', ['name', 'email']);
|
||||
|
||||
expect(WithdrawalRequest::query()->count())->toBe(0);
|
||||
Mail::assertNothingSent();
|
||||
});
|
||||
|
||||
test('honeypot submissions are silently accepted without persisting or mailing', function () {
|
||||
/** @var TestCase $this */
|
||||
Mail::fake();
|
||||
|
||||
$this->post('https://pressekonto.test/widerruf', [
|
||||
'name' => 'Bot',
|
||||
'email' => 'bot@example.test',
|
||||
'website' => 'https://spam.example',
|
||||
])->assertSessionHas('withdrawal-status');
|
||||
|
||||
expect(WithdrawalRequest::query()->count())->toBe(0);
|
||||
Mail::assertNothingSent();
|
||||
});
|
||||
|
||||
test('a withdrawal from a spoke domain goes to that brand address', function () {
|
||||
/** @var TestCase $this */
|
||||
Mail::fake();
|
||||
config()->set('domains.domains.presseecho.brand.contact_email', 'redaktion@presseecho.de');
|
||||
|
||||
$this->post('https://presseecho.test/widerruf', [
|
||||
'name' => 'Paula Presse',
|
||||
'email' => 'paula@example.test',
|
||||
])->assertSessionHas('withdrawal-status');
|
||||
|
||||
Mail::assertSent(WithdrawalRequestSubmitted::class, fn (WithdrawalRequestSubmitted $mail) => $mail->hasTo('redaktion@presseecho.de'));
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue