Updated checkout to wait for backend confirmation before advancing, added a “Processing payment…” state with retry/ refresh fallback, and now use Paddle totals/currency for purchase records + confirmation emails (with new email translations).
This commit is contained in:
@@ -2,21 +2,27 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Http\Requests\Checkout\CheckoutFreeActivationRequest;
|
||||
use App\Http\Requests\Checkout\CheckoutLoginRequest;
|
||||
use App\Http\Requests\Checkout\CheckoutRegisterRequest;
|
||||
use App\Http\Requests\Checkout\CheckoutSessionStatusRequest;
|
||||
use App\Mail\Welcome;
|
||||
use App\Models\AbandonedCheckout;
|
||||
use App\Models\CheckoutSession;
|
||||
use App\Models\Package;
|
||||
use App\Models\Tenant;
|
||||
use App\Models\User;
|
||||
use App\Services\Checkout\CheckoutAssignmentService;
|
||||
use App\Services\Checkout\CheckoutSessionService;
|
||||
use App\Support\CheckoutRoutes;
|
||||
use App\Support\Concerns\PresentsPackages;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Support\Facades\Auth;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Hash;
|
||||
use Illuminate\Support\Facades\Mail;
|
||||
use Illuminate\Support\Facades\Validator;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\Rules\Password;
|
||||
use Inertia\Inertia;
|
||||
|
||||
class CheckoutController extends Controller
|
||||
@@ -61,35 +67,15 @@ class CheckoutController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function register(Request $request): \Illuminate\Http\JsonResponse
|
||||
public function register(CheckoutRegisterRequest $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'email' => 'required|email|max:255|unique:users,email',
|
||||
'username' => 'required|string|max:255|unique:users,username',
|
||||
'password' => ['required', 'confirmed', Password::defaults()],
|
||||
'first_name' => 'required|string|max:255',
|
||||
'last_name' => 'required|string|max:255',
|
||||
'address' => 'required|string|max:500',
|
||||
'phone' => 'required|string|max:255',
|
||||
'package_id' => 'required|exists:packages,id',
|
||||
'terms' => 'required|accepted',
|
||||
'privacy_consent' => 'required|accepted',
|
||||
'locale' => 'nullable|string|max:10',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json([
|
||||
'errors' => $validator->errors(),
|
||||
], 422);
|
||||
}
|
||||
|
||||
$package = Package::findOrFail($request->package_id);
|
||||
$validated = $validator->validated();
|
||||
$user = DB::transaction(function () use ($request, $package, $validated) {
|
||||
$validated = $request->validated();
|
||||
$package = Package::findOrFail($validated['package_id']);
|
||||
$user = DB::transaction(function () use ($package, $validated) {
|
||||
|
||||
// User erstellen
|
||||
$user = User::create([
|
||||
'email' => $request->email,
|
||||
'email' => $validated['email'],
|
||||
'username' => $validated['username'],
|
||||
'first_name' => $validated['first_name'],
|
||||
'last_name' => $validated['last_name'],
|
||||
@@ -97,7 +83,7 @@ class CheckoutController extends Controller
|
||||
'address' => $validated['address'],
|
||||
'phone' => $validated['phone'],
|
||||
'preferred_locale' => $validated['locale'] ?? null,
|
||||
'password' => Hash::make($request->password),
|
||||
'password' => Hash::make($validated['password']),
|
||||
'pending_purchase' => true,
|
||||
]);
|
||||
|
||||
@@ -171,28 +157,21 @@ class CheckoutController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function login(Request $request)
|
||||
public function login(CheckoutLoginRequest $request): JsonResponse
|
||||
{
|
||||
$validator = Validator::make($request->all(), [
|
||||
'identifier' => 'required|string',
|
||||
'password' => 'required|string',
|
||||
'remember' => 'boolean',
|
||||
'locale' => 'nullable|string',
|
||||
]);
|
||||
|
||||
if ($validator->fails()) {
|
||||
return response()->json(['errors' => $validator->errors()], 422);
|
||||
$validated = $request->validated();
|
||||
$packageId = $validated['package_id'] ?? $request->session()->get('selected_package_id');
|
||||
if ($packageId) {
|
||||
$request->session()->put('selected_package_id', $packageId);
|
||||
}
|
||||
|
||||
$packageId = $request->session()->get('selected_package_id');
|
||||
|
||||
// Custom Auth für Identifier (E-Mail oder Username)
|
||||
$identifier = $request->identifier;
|
||||
$identifier = $validated['identifier'];
|
||||
$user = User::where('email', $identifier)
|
||||
->orWhere('username', $identifier)
|
||||
->first();
|
||||
|
||||
if (! $user || ! Hash::check($request->password, $user->password)) {
|
||||
if (! $user || ! Hash::check($validated['password'], $user->password)) {
|
||||
return response()->json([
|
||||
'errors' => ['identifier' => ['Ungültige Anmeldedaten.']],
|
||||
], 422);
|
||||
@@ -220,6 +199,74 @@ class CheckoutController extends Controller
|
||||
]);
|
||||
}
|
||||
|
||||
public function activateFree(
|
||||
CheckoutFreeActivationRequest $request,
|
||||
CheckoutSessionService $sessions,
|
||||
CheckoutAssignmentService $assignment,
|
||||
): JsonResponse {
|
||||
$validated = $request->validated();
|
||||
|
||||
$user = $request->user();
|
||||
if (! $user) {
|
||||
return response()->json(['message' => 'Unauthenticated.'], 401);
|
||||
}
|
||||
|
||||
$package = Package::findOrFail($validated['package_id']);
|
||||
|
||||
if ($package->price > 0) {
|
||||
return response()->json([
|
||||
'message' => 'Package is not eligible for free activation.',
|
||||
], 422);
|
||||
}
|
||||
|
||||
$requiresWaiver = (bool) ($package->activates_immediately ?? true);
|
||||
|
||||
if ($requiresWaiver && ! $request->boolean('accepted_waiver')) {
|
||||
return response()->json([
|
||||
'errors' => [
|
||||
'accepted_waiver' => ['Ein sofortiger Beginn der digitalen Dienstleistung erfordert Ihre ausdrückliche Zustimmung.'],
|
||||
],
|
||||
], 422);
|
||||
}
|
||||
|
||||
$session = $sessions->createOrResume($user, $package, [
|
||||
'tenant' => $user->tenant,
|
||||
'locale' => $validated['locale'] ?? null,
|
||||
]);
|
||||
|
||||
$sessions->selectProvider($session, CheckoutSession::PROVIDER_FREE);
|
||||
|
||||
$now = now();
|
||||
$session->forceFill([
|
||||
'accepted_terms_at' => $now,
|
||||
'accepted_privacy_at' => $now,
|
||||
'accepted_withdrawal_notice_at' => $now,
|
||||
'digital_content_waiver_at' => $requiresWaiver && $request->boolean('accepted_waiver') ? $now : null,
|
||||
'legal_version' => config('app.legal_version', $now->toDateString()),
|
||||
])->save();
|
||||
|
||||
$assignment->finalise($session, [
|
||||
'provider' => CheckoutSession::PROVIDER_FREE,
|
||||
]);
|
||||
|
||||
$sessions->markCompleted($session, $now);
|
||||
|
||||
return response()->json([
|
||||
'status' => 'completed',
|
||||
'checkout_session_id' => $session->id,
|
||||
]);
|
||||
}
|
||||
|
||||
public function sessionStatus(
|
||||
CheckoutSessionStatusRequest $request,
|
||||
CheckoutSession $session,
|
||||
): JsonResponse {
|
||||
return response()->json([
|
||||
'status' => $session->status,
|
||||
'completed_at' => optional($session->completed_at)->toIso8601String(),
|
||||
]);
|
||||
}
|
||||
|
||||
public function trackAbandonedCheckout(Request $request)
|
||||
{
|
||||
$validated = $request->validate([
|
||||
|
||||
Reference in New Issue
Block a user