Abo: Doppelbestellung verhindern, Payment-Link loeschen & Pending-Status

Verhindert das Anlegen eines zweiten Abos (eigenes oder Kundenabo) wenn
bereits eines initiiert ist bzw. ein offener Abo-Payment-Link existiert
(redirectIfAboAlreadyInitiated). Payment-Links koennen jetzt geloescht
werden, abgesichert ueber canDeleteInstance + Shop-Zugehoerigkeit, mit
Erfolgs-/Fehler-Flash. Kundenabo-Uebersicht zeigt den Pending-Initial-
Payment-Status (wartend / in Bearbeitung / fehlgeschlagen).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
Kevin 2026-06-25 13:37:30 +00:00
parent 42ef4dd1c6
commit 2b2847add1
7 changed files with 386 additions and 23 deletions

View file

@ -151,12 +151,17 @@ class OrderController extends Controller
}
if (Request::get('action') === 'next') {
$selectedFor = Request::get('switchers-radio-is-for');
if ($this->redirectIfAboAlreadyInitiated($selectedFor, $user, $delivery_id)) {
return redirect(route('user_order_my_delivery', [$for, $delivery_id]));
}
Yard::instance('shopping')->destroy();
if (strpos(Request::get('switchers-radio-is-for'), 'ot') !== false) {
if (strpos($selectedFor, 'ot') !== false) {
$delivery_id = $id;
}
return redirect(route('user_order_my_list', [Request::get('switchers-radio-is-for'), $delivery_id]));
return redirect(route('user_order_my_list', [$selectedFor, $delivery_id]));
}
return view('user.order.delivery', [
@ -172,9 +177,8 @@ class OrderController extends Controller
{
$user = User::find(Auth::user()->id);
if ($for === 'abo-me' && AboHelper::userHasAbo($user)) {
Log::channel(self::LOG_CHANNEL)->error("User #{$user->id} attempted to create abo but already has one");
abort(403, 'User has an Abo. Cannot order.');
if ($this->redirectIfAboAlreadyInitiated($for, $user, $id)) {
return redirect(route('user_order_my_delivery', [$for, $id]));
}
$shopping_user = null;
@ -227,6 +231,10 @@ class OrderController extends Controller
$data = Request::all();
$user = User::find(Auth::user()->id);
if ($this->redirectIfAboAlreadyInitiated($for, $user, $id)) {
return redirect(route('user_order_my_delivery', [$for, $id]));
}
$rules = [
'shipping_salutation' => 'required',
'shipping_firstname' => 'required',
@ -336,6 +344,48 @@ class OrderController extends Controller
return redirect(route('user_order_my_custom_payment', ['identifier' => $identifier]));
}
private function redirectIfAboAlreadyInitiated(?string $for, User $user, $deliveryId = null): bool
{
if ($for === 'abo-me' && AboHelper::userHasAbo($user)) {
Log::channel(self::LOG_CHANNEL)->warning("User #{$user->id} attempted to create abo but already has one initiated");
\Session()->flash('custom-error', __('abo.error_user_has_initiated_abo'));
return true;
}
if ($for === 'abo-ot-customer' && $deliveryId) {
$shoppingUser = Shop::checkShoppingUser($deliveryId, $user);
if (AboHelper::hasAboByEmail($shoppingUser->billing_email) || $this->hasOpenAboPaymentLink($user, $shoppingUser->billing_email)) {
Log::channel(self::LOG_CHANNEL)->warning("User #{$user->id} attempted to create customer abo but one is already initiated", [
'shopping_user_id' => $shoppingUser->id,
'email' => $shoppingUser->billing_email,
]);
\Session()->flash('custom-error', __('abo.error_email_has_abo', ['email' => $shoppingUser->billing_email]));
return true;
}
}
return false;
}
private function hasOpenAboPaymentLink(User $user, string $email): bool
{
$userShopId = $user->shop ? $user->shop->id : null;
if (! $userShopId) {
return false;
}
return ShoppingInstance::where('user_shop_id', $userShopId)
->where('payment', 6)
->where('status', '<', 10)
->get()
->contains(function (ShoppingInstance $shoppingInstance) use ($email) {
return OrderPaymentService::isAboCustomerPaymentLink($shoppingInstance)
&& strcasecmp((string) ($shoppingInstance->shopping_data['billing_email'] ?? ''), $email) === 0;
});
}
/**
* Process payment for user orders
*/

View file

@ -1,18 +1,14 @@
<?php
namespace App\Http\Controllers\User;
use Request;
use App\User;
use App\Models\ShoppingInstance;
use App\Http\Controllers\Controller;
use App\Models\ShoppingInstance;
use App\Services\OrderPaymentService;
use App\User;
class OrderPaymentController extends Controller
{
public function __construct()
{
$this->middleware('active.account');
@ -22,6 +18,7 @@ class OrderPaymentController extends Controller
{
$data = [
];
return view('user.order.payment.index', $data);
}
@ -29,15 +26,34 @@ class OrderPaymentController extends Controller
{
$data = OrderPaymentService::getCustomPayment($identifier);
$data['backlink'] = route('user_order_payment_links');
return view('user.order.payment.custom_payment', $data);
}
public function delete($identifier){
public function delete($identifier)
{
$shoppingInstance = ShoppingInstance::where('identifier', $identifier)->firstOrFail();
$user = User::find(\Auth::user()->id);
$userShopId = $user->shop ? $user->shop->id : null;
if ((int) $shoppingInstance->user_shop_id !== (int) $userShopId) {
abort(403);
}
if (! OrderPaymentService::canDeleteInstance($shoppingInstance)) {
\Session()->flash('alert-error', __('abo.payment_link_delete_not_allowed'));
return redirect(route('user_order_payment_links'));
}
OrderPaymentService::deleteInstance($identifier);
\Session()->flash('alert-success', __('abo.payment_link_delete_success'));
return redirect(route('user_order_payment_links'));
}
public function datatable(){
public function datatable()
{
$user = User::find(\Auth::user()->id);
$user_shop_id = $user->shop ? $user->shop->id : null;
@ -50,7 +66,7 @@ class OrderPaymentController extends Controller
return '<a href="'.route('user_order_payment_links_detail', [$shoppingInstance->identifier]).'" class="btn icon-btn btn-sm btn-primary"><span class="fa fa-edit"></span></a>';
})
->addColumn('created_at', function (ShoppingInstance $shoppingInstance) {
return $shoppingInstance->created_at->format("d.m.Y");
return $shoppingInstance->created_at->format('d.m.Y');
})
->addColumn('status', function (ShoppingInstance $shoppingInstance) {
return OrderPaymentService::getStatusBadge($shoppingInstance);
@ -59,9 +75,9 @@ class OrderPaymentController extends Controller
return $shoppingInstance->payment_method ? $shoppingInstance->payment_method->name : '-';
})
->addColumn('total', function (ShoppingInstance $shoppingInstance) {
if($shoppingInstance->amount > 0){
return '<span class="no-line-break">'.$shoppingInstance->getAmountFormatted()." €</span>";
}else{
if ($shoppingInstance->amount > 0) {
return '<span class="no-line-break">'.$shoppingInstance->getAmountFormatted().' €</span>';
} else {
return '-';
}
})
@ -78,12 +94,16 @@ class OrderPaymentController extends Controller
return $shoppingInstance->shopping_data['billing_email'] ?? '-';
})
->addColumn('delete', function (ShoppingInstance $shoppingInstance) {
if (! OrderPaymentService::canDeleteInstance($shoppingInstance)) {
return '';
}
return '<a onclick="return confirm(\''.__('confirm_delete').'\');" href="'.route('user_order_payment_links_delete', [$shoppingInstance->identifier]).'" class="btn icon-btn btn-sm btn-danger"><span class="fa fa-trash"></span></a>';
})
->orderColumn('id', 'identifier $1')
->orderColumn('created_at', 'created_at $1')
->orderColumn('status', 'status $1')
->orderColumn('status', 'status $1')
->orderColumn('total', 'total $1')
->orderColumn('type', 'type $1')
->orderColumn('billing_firstname', 'billing_firstname $1')
@ -92,6 +112,4 @@ class OrderPaymentController extends Controller
->rawColumns(['id', 'status', 'type', 'total', 'invoice', 'delete'])
->make(true);
}
}
}