Abo-Recovery: fehlgeschlagene Folgezahlung im Checkout nachholen
- Pay-now-Flow: bei fehlgeschlagenem Abo (Status 3) begleicht der Inhaber die offene Rechnung im Standard-Checkout mit frei waehlbarer, abo-faehiger Zahlart (nur eigenes Abo: Berater me-View + Kundenportal), payNow-Controller + Routen - AboRecoveryCheckoutService baut die ShoppingInstance aus den Abo-Artikeln; neue Spalte shopping_orders.recovery_user_abo_id verknuepft Bestellung<->Abo - Bei Erfolg: AboHelper::advanceAboAfterRecoveryPayment schreibt das Abo fort (Status->2, next_date) und uebernimmt die gewaehlte Zahlart als neues Token fuer kuenftige Abbuchungen - Neue Pay-now-Fehlermail (AboPaymentFailedMail) ersetzt die generische Fehlermail im Abo-Cron; Link fuehrt auf die Abo-Detailseite (CRM bzw. Portal) - Retry-/"extern erledigt"-Buttons nur noch fuer Admins; tote user/portal- Retry-Routen entfernt - Checkout: Recovery-Hinweis statt "Abschluss des Abonnements", Lieferadresse vorbelegt, korrekter Zurueck-Link (Abo statt Shop) - UserAbo::selfServiceDetailUrl zentralisiert die Abo-URL (Mail, Back-Links) - Test-Command abo:test-recovery; Tests fuer payNow, Advance-Logik, Fehlermail Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
parent
78db952130
commit
0660f4e0a0
28 changed files with 1535 additions and 181 deletions
|
|
@ -4,6 +4,7 @@ namespace App\Http\Controllers\Portal;
|
|||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Http\Requests\Abo\AboOneTimeItemRequest;
|
||||
use App\Http\Requests\Abo\UpdateAboCreditCardRequest;
|
||||
use App\Models\Product;
|
||||
use App\Models\ShoppingInstance;
|
||||
use App\Models\ShoppingUser;
|
||||
|
|
@ -15,7 +16,8 @@ use App\Services\AboHelper;
|
|||
use App\Services\AboItemHistoryService;
|
||||
use App\Services\AboOneTimeService;
|
||||
use App\Services\AboOrderCart;
|
||||
use App\Services\AboRetryPaymentService;
|
||||
use App\Services\AboPaymentMethodService;
|
||||
use App\Services\AboRecoveryCheckoutService;
|
||||
use App\Services\Shop;
|
||||
use App\Services\UserService;
|
||||
use App\Services\Util;
|
||||
|
|
@ -508,16 +510,29 @@ class AboController extends Controller
|
|||
return false;
|
||||
}
|
||||
|
||||
public function retryPayment($id, AboRetryPaymentService $retryPaymentService)
|
||||
/**
|
||||
* Startet den Recovery-Checkout für ein fehlgeschlagenes Abo: der Kunde begleicht die offene
|
||||
* Folgerechnung seines eigenen Abos über den Checkout mit frei wählbarer (abo-fähiger) Zahlart.
|
||||
*/
|
||||
public function payNow($id, AboRecoveryCheckoutService $recoveryCheckout)
|
||||
{
|
||||
$user_abo = UserAbo::findOrFail($id);
|
||||
$this->checkPortalPermission($user_abo);
|
||||
|
||||
$result = $retryPaymentService->retry($user_abo);
|
||||
if ($user_abo->status !== 3 || ! $user_abo->active) {
|
||||
\Session()->flash('alert-error', __('abo.pay_now_not_allowed'));
|
||||
|
||||
\Session()->flash($result['success'] ? 'alert-success' : 'alert-error', $result['message']);
|
||||
return redirect(route('portal.my_subscriptions'));
|
||||
}
|
||||
|
||||
return redirect(route('portal.my_subscriptions'));
|
||||
$url = $recoveryCheckout->buildCheckoutUrl($user_abo);
|
||||
if (! $url) {
|
||||
\Session()->flash('alert-error', __('abo.pay_now_failed'));
|
||||
|
||||
return redirect(route('portal.my_subscriptions'));
|
||||
}
|
||||
|
||||
return redirect()->away($url);
|
||||
}
|
||||
|
||||
private function checkPortalPermission($user_abo)
|
||||
|
|
@ -532,6 +547,75 @@ class AboController extends Controller
|
|||
}
|
||||
}
|
||||
|
||||
public function editPaymentMethod($id)
|
||||
{
|
||||
$user_abo = UserAbo::findOrFail($id);
|
||||
$this->checkPortalPermission($user_abo);
|
||||
|
||||
if (! $user_abo->canUpdatePaymentMethod()) {
|
||||
\Session()->flash('alert-error', __('abo.card_update_not_allowed'));
|
||||
|
||||
return redirect(route('portal.my_subscriptions'));
|
||||
}
|
||||
|
||||
return view('portal.abo.payment_method', ['user_abo' => $user_abo, 'view' => 'portal']);
|
||||
}
|
||||
|
||||
public function updatePaymentMethod(UpdateAboCreditCardRequest $request, $id, AboPaymentMethodService $service)
|
||||
{
|
||||
$user_abo = UserAbo::findOrFail($id);
|
||||
$this->checkPortalPermission($user_abo);
|
||||
|
||||
$returnUrls = [
|
||||
'successurl' => route('portal_abos_payment_method_return', [$user_abo->id, 'success']),
|
||||
'errorurl' => route('portal_abos_payment_method_return', [$user_abo->id, 'error']),
|
||||
'backurl' => route('portal_abos_payment_method_return', [$user_abo->id, 'cancel']),
|
||||
];
|
||||
|
||||
$result = $service->updateCreditCard($user_abo, $request->toDto(), $returnUrls, 'portal');
|
||||
|
||||
if ($result->isApproved()) {
|
||||
\Session()->flash('alert-success', __('abo.card_update_success'));
|
||||
|
||||
return redirect(route('portal.my_subscriptions'));
|
||||
}
|
||||
|
||||
if ($result->isRedirect()) {
|
||||
// Karten-Token bis zur 3DS-Rückkehr in der Session zwischenspeichern.
|
||||
session(['abo_card_update.'.$user_abo->id => [
|
||||
'carddata' => $result->carddata,
|
||||
'userid' => $result->userid,
|
||||
]]);
|
||||
|
||||
return redirect()->away($result->redirectUrl);
|
||||
}
|
||||
|
||||
\Session()->flash('alert-error', $result->errorMessage ?? __('abo.card_update_failed'));
|
||||
|
||||
return redirect(route('portal_abos_payment_method_edit', [$user_abo->id]))->withInput();
|
||||
}
|
||||
|
||||
public function paymentMethodReturn($id, $status, AboPaymentMethodService $service)
|
||||
{
|
||||
$user_abo = UserAbo::findOrFail($id);
|
||||
$this->checkPortalPermission($user_abo);
|
||||
|
||||
$stash = session()->pull('abo_card_update.'.$user_abo->id);
|
||||
|
||||
if ($status === 'success' && is_array($stash)) {
|
||||
$service->applyNewCard($user_abo, $stash['carddata'], (int) ($stash['userid'] ?? 0), 'portal');
|
||||
\Session()->flash('alert-success', __('abo.card_update_success'));
|
||||
|
||||
return redirect(route('portal.my_subscriptions'));
|
||||
}
|
||||
|
||||
// Abbruch oder Fehler an der 3DS-Rückkehr: Pending-Spur zurücknehmen, Karte bleibt unverändert.
|
||||
$user_abo->forceFill(['card_update_pending_at' => null])->save();
|
||||
\Session()->flash('alert-error', __('abo.card_update_failed'));
|
||||
|
||||
return redirect(route('portal.my_subscriptions'));
|
||||
}
|
||||
|
||||
public function myAboCreate($step)
|
||||
{
|
||||
$user = Auth::guard('customers')->user();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue