feat: integrate login/registration into PurchaseWizard

This commit is contained in:
Codex Agent
2025-10-04 21:38:03 +02:00
parent 3c0bbb688b
commit fdaa2bec62
52 changed files with 1477 additions and 732 deletions

View File

@@ -5,12 +5,20 @@ namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Package;
use App\Models\PackagePurchase;
use App\Models\Tenant;
use App\Models\TenantPackage;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use Stripe\Stripe;
use Stripe\PaymentIntent;
use PayPal\PayPalClient;
use PayPal\Environment\SandboxEnvironment;
use PayPal\Environment\LiveEnvironment;
use PayPal\Checkout\Orders\OrdersCreateRequest;
use PayPal\Checkout\Orders\OrdersCaptureRequest;
class PackageController extends Controller
{
@@ -90,7 +98,8 @@ class PackageController extends Controller
{
$request->validate([
'package_id' => 'required|exists:packages,id',
'payment_method_id' => 'required|string',
'payment_method_id' => 'required_without:paypal_order_id|string',
'paypal_order_id' => 'required_without:payment_method_id|string',
]);
$package = Package::findOrFail($request->package_id);
@@ -100,15 +109,17 @@ class PackageController extends Controller
throw ValidationException::withMessages(['tenant' => 'Tenant not found.']);
}
DB::transaction(function () use ($request, $package, $tenant) {
$provider = $request->has('paypal_order_id') ? 'paypal' : 'stripe';
DB::transaction(function () use ($request, $package, $tenant, $provider) {
PackagePurchase::create([
'tenant_id' => $tenant->id,
'package_id' => $package->id,
'provider_id' => $request->payment_method_id,
'provider_id' => $request->input($provider === 'paypal' ? 'paypal_order_id' : 'payment_method_id'),
'price' => $package->price,
'type' => 'endcustomer_event',
'purchased_at' => now(),
'metadata' => json_encode(['note' => 'Wizard purchase']),
'metadata' => json_encode(['note' => 'Wizard purchase', 'provider' => $provider]),
]);
TenantPackage::create([
@@ -122,6 +133,7 @@ class PackageController extends Controller
return response()->json([
'message' => 'Purchase completed successfully.',
'provider' => $provider,
], 201);
}
@@ -167,6 +179,157 @@ class PackageController extends Controller
], 201);
}
public function createPayPalOrder(Request $request): JsonResponse
{
$request->validate([
'package_id' => 'required|exists:packages,id',
]);
$package = Package::findOrFail($request->package_id);
$tenant = $request->attributes->get('tenant');
if (!$tenant) {
throw ValidationException::withMessages(['tenant' => 'Tenant not found.']);
}
$environment = config('services.paypal.sandbox', true) ? new SandboxEnvironment(
config('services.paypal.client_id'),
config('services.paypal.secret')
) : new LiveEnvironment(
config('services.paypal.client_id'),
config('services.paypal.secret')
);
$client = PayPalClient::client($environment);
$request = new OrdersCreateRequest();
$request->prefer('return=representation');
$request->body = [
"intent" => "CAPTURE",
"purchase_units" => [[
"amount" => [
"currency_code" => "EUR",
"value" => number_format($package->price, 2, '.', ''),
],
"description" => 'Fotospiel Package: ' . $package->name,
"custom_id" => json_encode([
'tenant_id' => $tenant->id,
'package_id' => $package->id,
'user_id' => $tenant->user_id ?? null,
]),
]],
"application_context" => [
"shipping_preference" => "NO_SHIPPING",
"user_action" => "PAY_NOW",
],
];
try {
$response = $client->execute($request);
$order = $response->result;
return response()->json([
'orderID' => $order->id,
]);
} catch (\Exception $e) {
Log::error('PayPal order creation error: ' . $e->getMessage());
throw ValidationException::withMessages(['payment' => 'PayPal-Bestellung fehlgeschlagen.']);
}
}
public function capturePayPalOrder(Request $request): JsonResponse
{
$request->validate([
'order_id' => 'required|string',
]);
$orderId = $request->order_id;
$environment = config('services.paypal.sandbox', true) ? new SandboxEnvironment(
config('services.paypal.client_id'),
config('services.paypal.secret')
) : new LiveEnvironment(
config('services.paypal.client_id'),
config('services.paypal.secret')
);
$client = PayPalClient::client($environment);
$request = new OrdersCaptureRequest($orderId);
$request->prefer('return=representation');
try {
$response = $client->execute($request);
$capture = $response->result;
if ($capture->status !== 'COMPLETED') {
throw new \Exception('PayPal capture not completed: ' . $capture->status);
}
$customId = $capture->purchaseUnits[0]->customId ?? null;
if (!$customId) {
throw new \Exception('No metadata in PayPal order');
}
$metadata = json_decode($customId, true);
$tenant = Tenant::find($metadata['tenant_id']);
$package = Package::find($metadata['package_id']);
if (!$tenant || !$package) {
throw new \Exception('Tenant or package not found');
}
// Idempotent check
$existing = PackagePurchase::where('provider_id', $orderId)->first();
if ($existing) {
return response()->json(['success' => true, 'message' => 'Already processed']);
}
DB::transaction(function () use ($tenant, $package, $orderId) {
PackagePurchase::create([
'tenant_id' => $tenant->id,
'package_id' => $package->id,
'provider_id' => $orderId,
'price' => $package->price,
'type' => $package->type === 'endcustomer' ? 'endcustomer_event' : 'reseller_subscription',
'purchased_at' => now(),
'metadata' => json_encode(['paypal_order' => $orderId]),
]);
// Trial logic for first reseller subscription
$activePackages = TenantPackage::where('tenant_id', $tenant->id)
->where('active', true)
->where('package_id', '!=', $package->id) // Exclude current if renewing
->count();
$expiresAt = now()->addYear();
if ($activePackages === 0 && $package->type === 'reseller_subscription') {
$expiresAt = now()->addDays(14); // 14-day trial
Log::info('PayPal trial activated for tenant', ['tenant_id' => $tenant->id]);
}
TenantPackage::updateOrCreate(
['tenant_id' => $tenant->id, 'package_id' => $package->id],
[
'price' => $package->price,
'purchased_at' => now(),
'active' => true,
'expires_at' => $expiresAt,
]
);
$tenant->update(['subscription_status' => 'active']);
});
Log::info('PayPal order captured successfully', ['order_id' => $orderId, 'tenant_id' => $tenant->id]);
return response()->json(['success' => true, 'message' => 'Payment successful']);
} catch (\Exception $e) {
Log::error('PayPal capture error: ' . $e->getMessage(), ['order_id' => $orderId]);
return response()->json(['success' => false, 'message' => 'Capture failed: ' . $e->getMessage()], 422);
}
}
private function handleFreePurchase(Request $request, Package $package, $tenant): JsonResponse
{
DB::transaction(function () use ($request, $package, $tenant) {
@@ -224,4 +387,7 @@ class PackageController extends Controller
return $response;
}
}
// Helper for PayPal client - add this if not exists, or use global
// But since SDK has PayPalClient, assume it's used
}