Widerruf: Gesetzliche Online-Widerrufsfunktion (Formular, Bestätigungsmail, Admin-Verwaltung)
- Öffentliches Widerrufsformular /widerruf mit Bestell-Lookup (throttled) - Bestätigungsmail an Kunde + Weiterleitung an bestellungen@ (config app.withdrawal_mail) - Admin-Übersicht/-Detail unter /admin/withdrawals inkl. Sidenav-Eintrag - Widerruf-Button in Portal-Bestellansicht und Footer-Links - Model OrderWithdrawal + Migration + Factory + Tests Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
parent
9dc138e45b
commit
83ef5f1e52
24 changed files with 1607 additions and 92 deletions
192
tests/Feature/OrderWithdrawalTest.php
Normal file
192
tests/Feature/OrderWithdrawalTest.php
Normal file
|
|
@ -0,0 +1,192 @@
|
|||
<?php
|
||||
|
||||
use App\Mail\MailWithdrawalConfirmation;
|
||||
use App\Models\Country;
|
||||
use App\Models\OrderWithdrawal;
|
||||
use App\Models\ShoppingOrder;
|
||||
use App\Models\ShoppingOrderItem;
|
||||
use App\Models\ShoppingUser;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Tests\TestCase;
|
||||
|
||||
uses(TestCase::class, RefreshDatabase::class);
|
||||
|
||||
/**
|
||||
* @return array{order: ShoppingOrder, item: ShoppingOrderItem}
|
||||
*/
|
||||
function makeShopOrder(string $email = 'kunde@example.com'): array
|
||||
{
|
||||
makeShopEnv();
|
||||
$country = Country::first();
|
||||
$product = makeProduct(['1']);
|
||||
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => 0,
|
||||
'billing_firstname' => 'Max',
|
||||
'billing_lastname' => 'Mustermann',
|
||||
'billing_email' => $email,
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'is_from' => 'shopping',
|
||||
]);
|
||||
|
||||
$order = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => 0,
|
||||
'user_shop_id' => 0,
|
||||
'payment_for' => 6,
|
||||
'country_id' => $country->id,
|
||||
'language' => 'de',
|
||||
'total' => 119,
|
||||
'txaction' => 'paid',
|
||||
]);
|
||||
|
||||
$item = ShoppingOrderItem::create([
|
||||
'shopping_order_id' => $order->id,
|
||||
'product_id' => $product->id,
|
||||
'qty' => 2,
|
||||
'price' => 59.5,
|
||||
]);
|
||||
|
||||
return ['order' => $order, 'item' => $item];
|
||||
}
|
||||
|
||||
it('zeigt die Widerrufsseite an', function () {
|
||||
$this->get(route('withdrawal.create'))->assertOk();
|
||||
});
|
||||
|
||||
it('zeigt nach erfolgreicher Identifikation die Bestellung mit Absende-Button', function () {
|
||||
['order' => $order] = makeShopOrder('kunde@example.com');
|
||||
|
||||
$response = $this->post(route('withdrawal.lookup'), [
|
||||
'order_number' => (string) $order->id,
|
||||
'email' => 'kunde@example.com',
|
||||
]);
|
||||
|
||||
$response->assertOk()
|
||||
->assertSee('#'.$order->id)
|
||||
->assertSee(__('withdrawal.submit'));
|
||||
});
|
||||
|
||||
it('legt bei vollständigem Widerruf einen Datensatz an und versendet die Eingangsbestätigung', function () {
|
||||
Mail::fake();
|
||||
['order' => $order] = makeShopOrder('kunde@example.com');
|
||||
|
||||
$response = $this->post(route('withdrawal.store'), [
|
||||
'order_number' => (string) $order->id,
|
||||
'email' => 'kunde@example.com',
|
||||
'scope' => 'full',
|
||||
]);
|
||||
|
||||
$response->assertOk();
|
||||
|
||||
$withdrawal = OrderWithdrawal::first();
|
||||
expect($withdrawal)->not->toBeNull()
|
||||
->and($withdrawal->shopping_order_id)->toBe($order->id)
|
||||
->and($withdrawal->type)->toBe(OrderWithdrawal::TYPE_FULL)
|
||||
->and($withdrawal->status)->toBe(OrderWithdrawal::STATUS_RECEIVED)
|
||||
->and($withdrawal->customer_email)->toBe('kunde@example.com')
|
||||
->and($withdrawal->declared_at)->not->toBeNull();
|
||||
|
||||
Mail::assertSent(MailWithdrawalConfirmation::class, function (MailWithdrawalConfirmation $mail) {
|
||||
return $mail->hasTo('kunde@example.com')
|
||||
&& $mail->hasBcc(config('app.withdrawal_mail'));
|
||||
});
|
||||
});
|
||||
|
||||
it('findet die Bestellung auch über Groß-/Kleinschreibung der E-Mail', function () {
|
||||
Mail::fake();
|
||||
['order' => $order] = makeShopOrder('kunde@example.com');
|
||||
|
||||
$this->post(route('withdrawal.store'), [
|
||||
'order_number' => (string) $order->id,
|
||||
'email' => 'KUNDE@EXAMPLE.COM',
|
||||
'scope' => 'full',
|
||||
])->assertOk();
|
||||
|
||||
expect(OrderWithdrawal::count())->toBe(1);
|
||||
});
|
||||
|
||||
it('speichert nur die ausgewählten Positionen beim teilweisen Widerruf', function () {
|
||||
Mail::fake();
|
||||
['order' => $order, 'item' => $item] = makeShopOrder();
|
||||
|
||||
$this->post(route('withdrawal.store'), [
|
||||
'order_number' => (string) $order->id,
|
||||
'email' => 'kunde@example.com',
|
||||
'scope' => 'partial',
|
||||
'items' => [$item->id => 1],
|
||||
])->assertOk();
|
||||
|
||||
$withdrawal = OrderWithdrawal::first();
|
||||
expect($withdrawal->type)->toBe(OrderWithdrawal::TYPE_PARTIAL)
|
||||
->and($withdrawal->items)->toHaveCount(1)
|
||||
->and($withdrawal->items[0]['qty'])->toBe(1)
|
||||
->and($withdrawal->items[0]['shopping_order_item_id'])->toBe($item->id);
|
||||
});
|
||||
|
||||
it('lehnt einen teilweisen Widerruf ohne ausgewählte Artikel ab', function () {
|
||||
Mail::fake();
|
||||
['order' => $order] = makeShopOrder();
|
||||
|
||||
$this->post(route('withdrawal.store'), [
|
||||
'order_number' => (string) $order->id,
|
||||
'email' => 'kunde@example.com',
|
||||
'scope' => 'partial',
|
||||
'items' => [],
|
||||
])->assertRedirect();
|
||||
|
||||
expect(OrderWithdrawal::count())->toBe(0);
|
||||
Mail::assertNothingSent();
|
||||
});
|
||||
|
||||
it('findet keine Bestellung bei falscher E-Mail', function () {
|
||||
['order' => $order] = makeShopOrder('kunde@example.com');
|
||||
|
||||
$this->post(route('withdrawal.lookup'), [
|
||||
'order_number' => (string) $order->id,
|
||||
'email' => 'falsch@example.com',
|
||||
])->assertRedirect();
|
||||
|
||||
expect(OrderWithdrawal::count())->toBe(0);
|
||||
});
|
||||
|
||||
it('widerruft keine Nicht-Shop-Bestellung (payment_for != 6)', function () {
|
||||
makeShopEnv();
|
||||
$country = Country::first();
|
||||
$shoppingUser = ShoppingUser::create([
|
||||
'auth_user_id' => 0,
|
||||
'billing_firstname' => 'Max',
|
||||
'billing_lastname' => 'Mustermann',
|
||||
'billing_email' => 'kunde@example.com',
|
||||
'billing_country_id' => $country->id,
|
||||
'shipping_country_id' => $country->id,
|
||||
'is_from' => 'user_order',
|
||||
]);
|
||||
$order = ShoppingOrder::create([
|
||||
'shopping_user_id' => $shoppingUser->id,
|
||||
'auth_user_id' => 0,
|
||||
'user_shop_id' => 0,
|
||||
'payment_for' => 3,
|
||||
'country_id' => $country->id,
|
||||
'language' => 'de',
|
||||
'total' => 119,
|
||||
]);
|
||||
|
||||
$this->post(route('withdrawal.store'), [
|
||||
'order_number' => (string) $order->id,
|
||||
'email' => 'kunde@example.com',
|
||||
'scope' => 'full',
|
||||
])->assertRedirect();
|
||||
|
||||
expect(OrderWithdrawal::count())->toBe(0);
|
||||
});
|
||||
|
||||
it('validiert Pflichtfelder', function () {
|
||||
$this->post(route('withdrawal.store'), [
|
||||
'order_number' => '',
|
||||
'email' => 'keine-mail',
|
||||
'scope' => 'full',
|
||||
])->assertSessionHasErrors(['order_number', 'email']);
|
||||
});
|
||||
Loading…
Add table
Add a link
Reference in a new issue