- Brand/Theming: Marketing-Farb- und Typographievariablen in `resources/css/app.css` eingeführt, AdminLayout, Dashboardkarten und Onboarding-Komponenten entsprechend angepasst; Dokumentation (`docs/todo/tenant-admin-onboarding-fusion.md`, `docs/changes/...`) aktualisiert. - Checkout & Payments: Checkout-, PayPal-Controller und Tests für integrierte Stripe/PayPal-Flows sowie Paket-Billing-Abläufe überarbeitet; neue PayPal SDK-Factory und Admin-API-Helper (`resources/js/admin/api.ts`) schaffen Grundlage für Billing/Members/Tasks-Seiten. - DX & Tests: Neue Playwright/E2E-Struktur (docs/testing/e2e.md, `tests/e2e/tenant-onboarding-flow.test.ts`, Utilities), E2E-Tenant-Seeder und zusätzliche Übersetzungen/Factories zur Unterstützung der neuen Flows. - Marketing-Kommunikation: Automatische Kontakt-Bestätigungsmail (`ContactConfirmation` + Blade-Template) implementiert; Guest-PWA unter `/event` erreichbar. - Nebensitzung: Blogsystem gefixt und umfassenden BlogPostSeeder für Beispielinhalte angelegt.
251 lines
9.2 KiB
PHP
251 lines
9.2 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Log;
|
|
use App\Models\PackagePurchase;
|
|
use App\Models\Tenant;
|
|
use App\Models\TenantPackage;
|
|
use App\Models\Package;
|
|
|
|
use PaypalServerSdkLib\Models\Builders\OrderRequestBuilder;
|
|
use PaypalServerSdkLib\Models\Builders\PurchaseUnitRequestBuilder;
|
|
use PaypalServerSdkLib\Models\Builders\AmountWithBreakdownBuilder;
|
|
use PaypalServerSdkLib\Models\Builders\OrderApplicationContextBuilder;
|
|
use PaypalServerSdkLib\Models\CheckoutPaymentIntent;
|
|
use App\Services\PayPal\PaypalClientFactory;
|
|
|
|
class PayPalController extends Controller
|
|
{
|
|
private $client;
|
|
private PaypalClientFactory $clientFactory;
|
|
|
|
public function __construct(PaypalClientFactory $clientFactory)
|
|
{
|
|
$this->clientFactory = $clientFactory;
|
|
$this->client = $clientFactory->make();
|
|
}
|
|
|
|
public function createOrder(Request $request)
|
|
{
|
|
$request->validate([
|
|
'tenant_id' => 'required|exists:tenants,id',
|
|
'package_id' => 'required|exists:packages,id',
|
|
]);
|
|
|
|
$tenant = Tenant::findOrFail($request->tenant_id);
|
|
$package = Package::findOrFail($request->package_id);
|
|
|
|
$ordersController = $this->client->getOrdersController();
|
|
|
|
$body = OrderRequestBuilder::init(
|
|
CheckoutPaymentIntent::CAPTURE,
|
|
[
|
|
PurchaseUnitRequestBuilder::init(
|
|
AmountWithBreakdownBuilder::init('EUR', number_format($package->price, 2, '.', ''))
|
|
->build()
|
|
)
|
|
->description('Package: ' . $package->name)
|
|
->customId(json_encode([
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'type' => 'endcustomer_event'
|
|
]))
|
|
->build()
|
|
]
|
|
)
|
|
->applicationContext(
|
|
OrderApplicationContextBuilder::init()
|
|
->brandName('Fotospiel')
|
|
->landingPage('BILLING')
|
|
->build()
|
|
)
|
|
->build();
|
|
|
|
$collect = [
|
|
'body' => $body,
|
|
'prefer' => 'return=representation'
|
|
];
|
|
|
|
try {
|
|
$response = $ordersController->createOrder($collect);
|
|
|
|
if ($response->getStatusCode() === 201) {
|
|
$result = $response->getResult();
|
|
$approveLink = collect($result->links)->first(fn($link) => $link->rel === 'approve')?->href;
|
|
|
|
return response()->json([
|
|
'id' => $result->id,
|
|
'approve_url' => $approveLink,
|
|
]);
|
|
}
|
|
|
|
Log::error('PayPal order creation failed', ['response' => $response]);
|
|
return response()->json(['error' => 'Order creation failed'], 400);
|
|
} catch (\Exception $e) {
|
|
Log::error('PayPal order creation exception', ['error' => $e->getMessage()]);
|
|
return response()->json(['error' => 'Order creation failed'], 500);
|
|
}
|
|
}
|
|
|
|
public function captureOrder(Request $request)
|
|
{
|
|
$request->validate(['order_id' => 'required']);
|
|
|
|
$ordersController = $this->client->getOrdersController();
|
|
|
|
$collect = [
|
|
'id' => $request->order_id,
|
|
'prefer' => 'return=representation'
|
|
];
|
|
|
|
try {
|
|
$response = $ordersController->captureOrder($collect);
|
|
|
|
if ($response->getStatusCode() === 201) {
|
|
$result = $response->getResult();
|
|
$customId = $result->purchaseUnits[0]->customId ?? null;
|
|
|
|
if ($customId) {
|
|
$metadata = json_decode($customId, true);
|
|
$tenantId = $metadata['tenant_id'] ?? null;
|
|
$packageId = $metadata['package_id'] ?? null;
|
|
$type = $metadata['type'] ?? 'endcustomer_event';
|
|
|
|
if ($tenantId && $packageId) {
|
|
$tenant = Tenant::findOrFail($tenantId);
|
|
$package = Package::findOrFail($packageId);
|
|
|
|
PackagePurchase::create([
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'provider_id' => $result->id,
|
|
'price' => $result->purchaseUnits[0]->amount->value,
|
|
'type' => $type,
|
|
'purchased_at' => now(),
|
|
'refunded' => false,
|
|
]);
|
|
TenantPackage::create([
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'price' => $package->price,
|
|
'purchased_at' => now(),
|
|
'active' => true,
|
|
]);
|
|
|
|
$tenant->update(['subscription_status' => 'active']);
|
|
} else {
|
|
Log::error('Invalid metadata in PayPal custom_id', ['custom_id' => $customId]);
|
|
}
|
|
|
|
Log::info('PayPal order captured and purchase created: ' . $result->id);
|
|
}
|
|
|
|
return response()->json(['status' => 'captured', 'order' => $result]);
|
|
}
|
|
|
|
Log::error('PayPal order capture failed', ['response' => $response]);
|
|
return response()->json(['error' => 'Capture failed'], 400);
|
|
} catch (\Exception $e) {
|
|
Log::error('PayPal order capture exception', ['error' => $e->getMessage()]);
|
|
return response()->json(['error' => 'Capture failed'], 500);
|
|
}
|
|
}
|
|
|
|
public function createSubscription(Request $request)
|
|
{
|
|
$request->validate([
|
|
'tenant_id' => 'required|exists:tenants,id',
|
|
'package_id' => 'required|exists:packages,id',
|
|
'plan_id' => 'required', // PayPal plan ID for the package
|
|
]);
|
|
|
|
$tenant = Tenant::findOrFail($request->tenant_id);
|
|
$package = Package::findOrFail($request->package_id);
|
|
|
|
$ordersController = $this->client->getOrdersController();
|
|
|
|
$storedPaymentSource = new \PaypalServerSdkLib\Models\StoredPaymentSource(
|
|
'CUSTOMER',
|
|
'RECURRING'
|
|
);
|
|
$storedPaymentSource->setUsage('FIRST');
|
|
|
|
$paymentSource = new \PaypalServerSdkLib\Models\PaymentSource();
|
|
$paymentSource->storedPaymentSource = $storedPaymentSource;
|
|
|
|
$body = OrderRequestBuilder::init(
|
|
CheckoutPaymentIntent::CAPTURE,
|
|
[
|
|
PurchaseUnitRequestBuilder::init(
|
|
AmountWithBreakdownBuilder::init('EUR', number_format($package->price, 2, '.', ''))
|
|
->build()
|
|
)
|
|
->description('Subscription Package: ' . $package->name)
|
|
->customId(json_encode([
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'type' => 'reseller_subscription',
|
|
'plan_id' => $request->plan_id
|
|
]))
|
|
->build()
|
|
]
|
|
)
|
|
->paymentSource($paymentSource)
|
|
->applicationContext(
|
|
OrderApplicationContextBuilder::init()
|
|
->brandName('Fotospiel')
|
|
->landingPage('BILLING')
|
|
->build()
|
|
)
|
|
->build();
|
|
|
|
$collect = [
|
|
'body' => $body,
|
|
'prefer' => 'return=representation'
|
|
];
|
|
|
|
try {
|
|
$response = $ordersController->createOrder($collect);
|
|
|
|
if ($response->getStatusCode() === 201) {
|
|
$result = $response->getResult();
|
|
$orderId = $result->id;
|
|
|
|
// Initial purchase record for subscription setup
|
|
TenantPackage::create([
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'price' => $package->price,
|
|
'purchased_at' => now(),
|
|
'expires_at' => now()->addYear(), // Assuming annual subscription
|
|
'active' => true,
|
|
]);
|
|
|
|
PackagePurchase::create([
|
|
'tenant_id' => $tenant->id,
|
|
'package_id' => $package->id,
|
|
'provider_id' => $orderId . '_sub_' . $request->plan_id, // Combine for uniqueness
|
|
'price' => $package->price,
|
|
'type' => 'reseller_subscription',
|
|
'purchased_at' => now(),
|
|
]);
|
|
|
|
$approveLink = collect($result->links)->first(fn($link) => $link->rel === 'approve')?->href;
|
|
|
|
return response()->json([
|
|
'order_id' => $orderId,
|
|
'approve_url' => $approveLink,
|
|
]);
|
|
}
|
|
|
|
Log::error('PayPal subscription order creation failed', ['response' => $response]);
|
|
return response()->json(['error' => 'Subscription order creation failed'], 400);
|
|
} catch (\Exception $e) {
|
|
Log::error('PayPal subscription order creation exception', ['error' => $e->getMessage()]);
|
|
return response()->json(['error' => 'Subscription order creation failed'], 500);
|
|
}
|
|
}
|
|
}
|