393 lines
14 KiB
PHP
393 lines
14 KiB
PHP
<?php
|
|
|
|
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
|
|
{
|
|
public function index(Request $request): JsonResponse
|
|
{
|
|
$type = $request->query('type', 'endcustomer');
|
|
$packages = Package::where('type', $type)
|
|
->orderBy('price')
|
|
->get();
|
|
|
|
$packages->each(function ($package) {
|
|
$package->features = json_decode($package->features ?? '[]', true);
|
|
});
|
|
|
|
return response()->json([
|
|
'data' => $packages,
|
|
'message' => "Packages for type '{$type}' loaded successfully.",
|
|
]);
|
|
}
|
|
|
|
public function purchase(Request $request): JsonResponse
|
|
{
|
|
$request->validate([
|
|
'package_id' => 'required|exists:packages,id',
|
|
'type' => 'required|in:endcustomer_event,reseller_subscription',
|
|
'payment_method' => 'required|in:stripe,paypal',
|
|
'event_id' => 'nullable|exists:events,id', // For endcustomer
|
|
]);
|
|
|
|
$package = Package::findOrFail($request->package_id);
|
|
$tenant = $request->attributes->get('tenant');
|
|
|
|
if (!$tenant) {
|
|
throw ValidationException::withMessages(['tenant' => 'Tenant not found.']);
|
|
}
|
|
|
|
if ($package->price == 0) {
|
|
// Free package - direct assignment
|
|
return $this->handleFreePurchase($request, $package, $tenant);
|
|
}
|
|
|
|
// Paid purchase
|
|
return $this->handlePaidPurchase($request, $package, $tenant);
|
|
}
|
|
|
|
public function createPaymentIntent(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.']);
|
|
}
|
|
|
|
\Stripe\Stripe::setApiKey(config('services.stripe.secret'));
|
|
|
|
$paymentIntent = \Stripe\PaymentIntent::create([
|
|
'amount' => $package->price * 100,
|
|
'currency' => 'eur',
|
|
'metadata' => [
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'type' => 'endcustomer_event',
|
|
],
|
|
]);
|
|
|
|
return response()->json([
|
|
'client_secret' => $paymentIntent->client_secret,
|
|
]);
|
|
}
|
|
|
|
public function completePurchase(Request $request): JsonResponse
|
|
{
|
|
$request->validate([
|
|
'package_id' => 'required|exists:packages,id',
|
|
'payment_method_id' => 'required_without:paypal_order_id|string',
|
|
'paypal_order_id' => 'required_without:payment_method_id|string',
|
|
]);
|
|
|
|
$package = Package::findOrFail($request->package_id);
|
|
$tenant = $request->attributes->get('tenant');
|
|
|
|
if (!$tenant) {
|
|
throw ValidationException::withMessages(['tenant' => 'Tenant not found.']);
|
|
}
|
|
|
|
$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->input($provider === 'paypal' ? 'paypal_order_id' : 'payment_method_id'),
|
|
'price' => $package->price,
|
|
'type' => 'endcustomer_event',
|
|
'purchased_at' => now(),
|
|
'metadata' => json_encode(['note' => 'Wizard purchase', 'provider' => $provider]),
|
|
]);
|
|
|
|
TenantPackage::create([
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'price' => $package->price,
|
|
'purchased_at' => now(),
|
|
'active' => true,
|
|
]);
|
|
});
|
|
|
|
return response()->json([
|
|
'message' => 'Purchase completed successfully.',
|
|
'provider' => $provider,
|
|
], 201);
|
|
}
|
|
|
|
public function assignFree(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.']);
|
|
}
|
|
|
|
if ($package->price != 0) {
|
|
throw ValidationException::withMessages(['package' => 'Not a free package.']);
|
|
}
|
|
|
|
DB::transaction(function () use ($request, $package, $tenant) {
|
|
PackagePurchase::create([
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'provider_id' => 'free_wizard',
|
|
'price' => $package->price,
|
|
'type' => 'endcustomer_event',
|
|
'purchased_at' => now(),
|
|
'metadata' => json_encode(['note' => 'Free via wizard']),
|
|
]);
|
|
|
|
TenantPackage::create([
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'price' => $package->price,
|
|
'purchased_at' => now(),
|
|
'active' => true,
|
|
]);
|
|
});
|
|
|
|
return response()->json([
|
|
'message' => 'Free package assigned successfully.',
|
|
], 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) {
|
|
$purchaseData = [
|
|
'tenant_id' => $tenant->id,
|
|
'event_id' => $request->event_id,
|
|
'package_id' => $package->id,
|
|
'provider_id' => 'free',
|
|
'price' => $package->price,
|
|
'type' => $request->type,
|
|
'metadata' => json_encode([
|
|
'note' => 'Free package assigned',
|
|
'ip' => $request->ip(),
|
|
]),
|
|
];
|
|
|
|
PackagePurchase::create($purchaseData);
|
|
|
|
if ($request->event_id) {
|
|
// Assign to event
|
|
\App\Models\EventPackage::create([
|
|
'event_id' => $request->event_id,
|
|
'package_id' => $package->id,
|
|
'price' => $package->price,
|
|
'purchased_at' => now(),
|
|
]);
|
|
} else {
|
|
// Reseller subscription
|
|
\App\Models\TenantPackage::create([
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'price' => $package->price,
|
|
'purchased_at' => now(),
|
|
'expires_at' => now()->addYear(),
|
|
'active' => true,
|
|
]);
|
|
}
|
|
});
|
|
|
|
return response()->json([
|
|
'message' => 'Free package assigned successfully.',
|
|
'purchase' => ['package' => $package->name, 'type' => $request->type],
|
|
], 201);
|
|
}
|
|
|
|
private function handlePaidPurchase(Request $request, Package $package, $tenant): JsonResponse
|
|
{
|
|
$type = $request->type;
|
|
|
|
if ($type === 'reseller_subscription') {
|
|
$response = (new StripeController())->createSubscription($request);
|
|
return $response;
|
|
} else {
|
|
$response = (new StripeController())->createPaymentIntent($request);
|
|
return $response;
|
|
}
|
|
}
|
|
|
|
// Helper for PayPal client - add this if not exists, or use global
|
|
// But since SDK has PayPalClient, assume it's used
|
|
} |