feat: integrate login/registration into PurchaseWizard
This commit is contained in:
@@ -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
|
||||
}
|
||||
@@ -33,12 +33,14 @@ class AuthenticatedSessionController extends Controller
|
||||
try {
|
||||
$request->authenticate();
|
||||
} catch (\Illuminate\Validation\ValidationException $e) {
|
||||
$request->session()->flash('error', __('auth.login_failed'));
|
||||
return redirect()->route('login')->withErrors($e->errors());
|
||||
}
|
||||
|
||||
Log::info('Login attempt', ['login' => $request->login, 'authenticated' => Auth::check()]);
|
||||
|
||||
$request->session()->regenerate();
|
||||
$request->session()->flash('success', __('auth.login_success'));
|
||||
|
||||
$user = Auth::user();
|
||||
if ($user && $user->email_verified_at === null) {
|
||||
|
||||
@@ -119,8 +119,9 @@ class MarketingRegisterController extends Controller
|
||||
if (!empty($validated['package_id'])) {
|
||||
$package = Package::find($validated['package_id']);
|
||||
if (!$package) {
|
||||
$request->session()->flash('error', __('auth.registration_failed'));
|
||||
// No action if package not found
|
||||
return redirect()->route('dashboard')->with('success', 'Registrierung erfolgreich! Bitte verifizieren Sie Ihre E-Mail.');
|
||||
return redirect()->route('dashboard')->with('success', __('auth.registration_success'));
|
||||
} else if ((float) $package->price <= 0.0) {
|
||||
// Assign free package
|
||||
TenantPackage::create([
|
||||
@@ -129,7 +130,7 @@ class MarketingRegisterController extends Controller
|
||||
'active' => true,
|
||||
'price' => 0,
|
||||
]);
|
||||
|
||||
|
||||
PackagePurchase::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'package_id' => $package->id,
|
||||
@@ -138,26 +139,31 @@ class MarketingRegisterController extends Controller
|
||||
'purchased_at' => now(),
|
||||
'provider_id' => 'free',
|
||||
]);
|
||||
|
||||
|
||||
$tenant->update(['subscription_status' => 'active']);
|
||||
|
||||
|
||||
$user->update(['role' => 'tenant_admin', 'pending_purchase' => false]);
|
||||
Auth::login($user); // Re-login to refresh session
|
||||
|
||||
|
||||
$request->session()->flash('success', __('auth.registration_success'));
|
||||
|
||||
if ($shouldAutoVerify) {
|
||||
return redirect()->route('dashboard')->with('success', 'Registrierung und Package-Zuweisung erfolgreich!');
|
||||
return redirect()->route('dashboard')->with('success', __('auth.registration_success'));
|
||||
} else {
|
||||
return redirect()->route('verification.notice')->with('status', 'registration-success');
|
||||
}
|
||||
} else {
|
||||
// For paid package, keep pending_purchase true, redirect to buy
|
||||
return redirect()->route('buy.packages', $package->id)->with('success', 'Registrierung erfolgreich! Fahren Sie mit dem Kauf fort.');
|
||||
$request->session()->flash('success', __('auth.registration_success'));
|
||||
return redirect()->route('buy.packages', $package->id)->with('success', __('auth.registration_success'));
|
||||
}
|
||||
}
|
||||
|
||||
// No package
|
||||
$request->session()->flash('success', __('auth.registration_success'));
|
||||
|
||||
if ($shouldAutoVerify) {
|
||||
return redirect()->route('dashboard')->with('success', 'Registrierung erfolgreich!');
|
||||
return redirect()->route('dashboard')->with('success', __('auth.registration_success'));
|
||||
}
|
||||
|
||||
session()->flash('status', 'registration-success');
|
||||
|
||||
@@ -2,137 +2,263 @@
|
||||
|
||||
namespace App\Http\Controllers;
|
||||
|
||||
use App\Models\User;
|
||||
|
||||
use Illuminate\Http\Request;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
use PayPal\PayPalHttp\Client;
|
||||
use PayPal\Checkout\Orders\OrdersGetRequest;
|
||||
use App\Models\TenantPackage;
|
||||
use PayPal\Environment\SandboxEnvironment;
|
||||
use PayPal\Environment\LiveEnvironment;
|
||||
use PayPal\PayPalClient;
|
||||
use PayPal\Webhook\Webhook;
|
||||
use PayPal\Webhook\VerifyWebhookSignature;
|
||||
use App\Models\PackagePurchase;
|
||||
use App\Models\Package;
|
||||
use App\Models\TenantPackage;
|
||||
use App\Models\Tenant;
|
||||
use Exception;
|
||||
use App\Models\Package;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class PayPalWebhookController extends Controller
|
||||
{
|
||||
public function handle(Request $request)
|
||||
public function verify(Request $request): JsonResponse
|
||||
{
|
||||
$input = $request->all();
|
||||
$ipnMessage = $input['ipn_track_id'] ?? null;
|
||||
$verification = $this->verifyIPN($request);
|
||||
$request->validate([
|
||||
'webhook_id' => 'required|string',
|
||||
'webhook_event' => 'required|array',
|
||||
]);
|
||||
|
||||
if (!$verification) {
|
||||
Log::warning('PayPal IPN verification failed', ['ipn_track_id' => $ipnMessage]);
|
||||
return response('Invalid IPN', 400);
|
||||
}
|
||||
$webhookId = $request->webhook_id;
|
||||
$event = $request->webhook_event;
|
||||
|
||||
$eventType = $input['payment_status'] ?? null;
|
||||
$customId = $input['custom'] ?? null;
|
||||
$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'));
|
||||
|
||||
if (!$eventType || !$customId) {
|
||||
Log::warning('Missing event type or custom ID in PayPal IPN', ['input' => $input]);
|
||||
return response('Invalid data', 400);
|
||||
}
|
||||
$client = PayPalClient::client($environment);
|
||||
|
||||
if ($eventType !== 'Completed') {
|
||||
Log::info('Non-completed PayPal event ignored', ['event' => $eventType, 'ipn_track_id' => $ipnMessage]);
|
||||
return response('OK', 200);
|
||||
}
|
||||
$signatureVerification = new VerifyWebhookSignature();
|
||||
$signatureVerification->setClient($client);
|
||||
$signatureVerification->setWebhookId($webhookId);
|
||||
$signatureVerification->setAuthAlgo($request->header('PayPal-Auth-Algo'));
|
||||
$signatureVerification->setTransmissionId($request->header('PayPal-Transmission-Id'));
|
||||
$signatureVerification->setTransmissionSig($request->header('PayPal-Transmission-Sig'));
|
||||
$signatureVerification->setTransmissionTime($request->header('PayPal-Transmission-Time'));
|
||||
$signatureVerification->setWebhookBody($request->getContent());
|
||||
$signatureVerification->setWebhookCertUrl($request->header('PayPal-Cert-Url'));
|
||||
|
||||
try {
|
||||
$verificationResult = $signatureVerification->verify();
|
||||
|
||||
if ($verificationResult->getVerificationStatus() === 'SUCCESS') {
|
||||
// Process the webhook event
|
||||
$this->handleEvent($event);
|
||||
|
||||
return response()->json(['status' => 'SUCCESS'], 200);
|
||||
} else {
|
||||
Log::warning('PayPal webhook verification failed', ['status' => $verificationResult->getVerificationStatus()]);
|
||||
return response()->json(['status' => 'FAILURE'], 400);
|
||||
}
|
||||
} catch (\Exception $e) {
|
||||
Log::error('PayPal webhook verification error: ' . $e->getMessage());
|
||||
return response()->json(['status' => 'FAILURE'], 500);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleEvent(array $event): void
|
||||
{
|
||||
$eventType = $event['event_type'] ?? '';
|
||||
$resource = $event['resource'] ?? [];
|
||||
|
||||
Log::info('PayPal webhook received', ['event_type' => $eventType, 'resource_id' => $resource['id'] ?? 'unknown']);
|
||||
|
||||
switch ($eventType) {
|
||||
case 'CHECKOUT.ORDER.APPROVED':
|
||||
// Handle order approval if needed
|
||||
break;
|
||||
|
||||
case 'PAYMENT.CAPTURE.COMPLETED':
|
||||
$this->handleCaptureCompleted($resource);
|
||||
break;
|
||||
|
||||
case 'PAYMENT.CAPTURE.DENIED':
|
||||
$this->handleCaptureDenied($resource);
|
||||
break;
|
||||
|
||||
case 'BILLING.SUBSCRIPTION.ACTIVATED':
|
||||
// Handle subscription activation for SaaS
|
||||
$this->handleSubscriptionActivated($resource);
|
||||
break;
|
||||
|
||||
case 'BILLING.SUBSCRIPTION.CANCELLED':
|
||||
$this->handleSubscriptionCancelled($resource);
|
||||
break;
|
||||
|
||||
default:
|
||||
Log::info('Unhandled PayPal webhook event', ['event_type' => $eventType]);
|
||||
}
|
||||
}
|
||||
|
||||
private function handleCaptureCompleted(array $capture): void
|
||||
{
|
||||
$orderId = $capture['id'] ?? null;
|
||||
if (!$orderId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Idempotent check
|
||||
$purchase = PackagePurchase::where('provider_id', $orderId)->first();
|
||||
if ($purchase) {
|
||||
Log::info('PayPal capture already processed', ['order_id' => $orderId]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Extract metadata from custom_id if available, but for webhook, use order ID
|
||||
// Fetch order to get custom_id
|
||||
$this->processPurchaseFromOrder($orderId, 'completed');
|
||||
}
|
||||
|
||||
private function handleCaptureDenied(array $capture): void
|
||||
{
|
||||
$orderId = $capture['id'] ?? null;
|
||||
Log::warning('PayPal capture denied', ['order_id' => $orderId]);
|
||||
|
||||
// Handle denial, e.g., notify tenant or refund logic if needed
|
||||
// For now, log
|
||||
}
|
||||
|
||||
private function handleSubscriptionActivated(array $subscription): void
|
||||
{
|
||||
$subscriptionId = $subscription['id'] ?? null;
|
||||
if (!$subscriptionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update tenant subscription status
|
||||
// Assume metadata has tenant_id
|
||||
$customId = $subscription['custom_id'] ?? null;
|
||||
if ($customId) {
|
||||
$metadata = json_decode($customId, true);
|
||||
if (!$metadata || !isset($metadata['tenant_id'], $metadata['package_id'])) {
|
||||
throw new Exception('Invalid metadata');
|
||||
$tenantId = $metadata['tenant_id'] ?? null;
|
||||
|
||||
if ($tenantId) {
|
||||
$tenant = Tenant::find($tenantId);
|
||||
if ($tenant) {
|
||||
$tenant->update(['subscription_status' => 'active']);
|
||||
Log::info('PayPal subscription activated', ['subscription_id' => $subscriptionId, 'tenant_id' => $tenantId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function handleSubscriptionCancelled(array $subscription): void
|
||||
{
|
||||
$subscriptionId = $subscription['id'] ?? null;
|
||||
if (!$subscriptionId) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Update tenant to cancelled
|
||||
$customId = $subscription['custom_id'] ?? null;
|
||||
if ($customId) {
|
||||
$metadata = json_decode($customId, true);
|
||||
$tenantId = $metadata['tenant_id'] ?? null;
|
||||
|
||||
if ($tenantId) {
|
||||
$tenant = Tenant::find($tenantId);
|
||||
if ($tenant) {
|
||||
$tenant->update(['subscription_status' => 'cancelled']);
|
||||
// Deactivate TenantPackage
|
||||
TenantPackage::where('tenant_id', $tenantId)->update(['active' => false]);
|
||||
Log::info('PayPal subscription cancelled', ['subscription_id' => $subscriptionId, 'tenant_id' => $tenantId]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private function processPurchaseFromOrder(string $orderId, string $status): void
|
||||
{
|
||||
// Fetch order details
|
||||
$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);
|
||||
|
||||
$showOrder = new OrdersGetRequest($orderId);
|
||||
$showOrder->prefer('return=representation');
|
||||
|
||||
try {
|
||||
$response = $client->execute($showOrder);
|
||||
$order = $response->result;
|
||||
|
||||
$customId = $order->purchaseUnits[0]->customId ?? null;
|
||||
if (!$customId) {
|
||||
Log::error('No custom_id in PayPal order', ['order_id' => $orderId]);
|
||||
return;
|
||||
}
|
||||
|
||||
$tenant = Tenant::find($metadata['tenant_id']);
|
||||
$package = Package::find($metadata['package_id']);
|
||||
$metadata = json_decode($customId, true);
|
||||
$tenantId = $metadata['tenant_id'] ?? null;
|
||||
$packageId = $metadata['package_id'] ?? null;
|
||||
|
||||
if (!$tenantId || !$packageId) {
|
||||
Log::error('Missing metadata in PayPal order', ['order_id' => $orderId, 'metadata' => $metadata]);
|
||||
return;
|
||||
}
|
||||
|
||||
$tenant = Tenant::find($tenantId);
|
||||
$package = Package::find($packageId);
|
||||
|
||||
if (!$tenant || !$package) {
|
||||
throw new Exception('Tenant or package not found');
|
||||
Log::error('Tenant or package not found for PayPal order', ['order_id' => $orderId]);
|
||||
return;
|
||||
}
|
||||
|
||||
// Idempotent: Check if already processed
|
||||
$existingPurchase = PackagePurchase::where('tenant_id', $tenant->id)
|
||||
->where('package_id', $package->id)
|
||||
->where('provider_id', 'paypal')
|
||||
->where('purchased_at', '>=', now()->subDay()) // Recent to avoid duplicates
|
||||
->first();
|
||||
DB::transaction(function () use ($tenant, $package, $orderId, $status) {
|
||||
// Idempotent check
|
||||
$existing = PackagePurchase::where('provider_id', $orderId)->first();
|
||||
if ($existing) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($existingPurchase) {
|
||||
Log::info('PayPal purchase already processed', ['purchase_id' => $existingPurchase->id]);
|
||||
return response('OK', 200);
|
||||
}
|
||||
|
||||
// Activate package
|
||||
TenantPackage::updateOrCreate(
|
||||
[
|
||||
PackagePurchase::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'package_id' => $package->id,
|
||||
],
|
||||
[
|
||||
'active' => true,
|
||||
'provider_id' => $orderId,
|
||||
'price' => $package->price,
|
||||
'type' => $package->type === 'endcustomer' ? 'endcustomer_event' : 'reseller_subscription',
|
||||
'purchased_at' => now(),
|
||||
'expires_at' => now()->addYear(),
|
||||
]
|
||||
);
|
||||
'status' => $status,
|
||||
'metadata' => json_encode(['paypal_order' => $orderId, 'webhook' => true]),
|
||||
]);
|
||||
|
||||
$user = User::find($metadata['user_id'] ?? null);
|
||||
if ($user) {
|
||||
$user->update(['role' => 'tenant_admin']);
|
||||
}
|
||||
// For trial: if first purchase and reseller, set trial
|
||||
$activePackages = TenantPackage::where('tenant_id', $tenant->id)
|
||||
->where('active', true)
|
||||
->count();
|
||||
|
||||
// Log purchase
|
||||
PackagePurchase::create([
|
||||
'tenant_id' => $tenant->id,
|
||||
'package_id' => $package->id,
|
||||
'provider_id' => 'paypal',
|
||||
'price' => $package->price,
|
||||
'type' => $package->type,
|
||||
'purchased_at' => now(),
|
||||
'refunded' => false,
|
||||
]);
|
||||
$expiresAt = now()->addYear();
|
||||
if ($activePackages === 0 && $package->type === 'reseller_subscription') {
|
||||
$expiresAt = now()->addDays(14); // Trial
|
||||
}
|
||||
|
||||
$tenant->update(['subscription_status' => 'active']);
|
||||
TenantPackage::updateOrCreate(
|
||||
['tenant_id' => $tenant->id, 'package_id' => $package->id],
|
||||
[
|
||||
'price' => $package->price,
|
||||
'purchased_at' => now(),
|
||||
'active' => true,
|
||||
'expires_at' => $expiresAt,
|
||||
]
|
||||
);
|
||||
|
||||
Log::info('PayPal purchase processed successfully', [
|
||||
'tenant_id' => $tenant->id,
|
||||
'package_id' => $package->id,
|
||||
'ipn_track_id' => $ipnMessage,
|
||||
]);
|
||||
$tenant->update(['subscription_status' => 'active']);
|
||||
});
|
||||
|
||||
return response('OK', 200);
|
||||
} catch (Exception $e) {
|
||||
Log::error('PayPal webhook processing error: ' . $e->getMessage(), [
|
||||
'input' => $input,
|
||||
'ipn_track_id' => $ipnMessage,
|
||||
]);
|
||||
return response('Error', 500);
|
||||
Log::info('PayPal purchase processed via webhook', ['order_id' => $orderId, 'tenant_id' => $tenantId, 'status' => $status]);
|
||||
|
||||
} catch (\Exception $e) {
|
||||
Log::error('Error processing PayPal order in webhook: ' . $e->getMessage(), ['order_id' => $orderId]);
|
||||
}
|
||||
}
|
||||
|
||||
private function verifyIPN(Request $request)
|
||||
{
|
||||
$rawBody = $request->getContent();
|
||||
$params = $request->all();
|
||||
|
||||
// For sandbox, post to PayPal verify endpoint
|
||||
$verifyParams = array_merge($params, ['cmd' => '_notify-validate']);
|
||||
|
||||
$response = file_get_contents('https://ipnpb.paypal.com/cgi-bin/webscr', false, stream_context_create([
|
||||
'http' => [
|
||||
'method' => 'POST',
|
||||
'header' => 'Content-type: application/x-www-form-urlencoded',
|
||||
'content' => http_build_query($verifyParams),
|
||||
],
|
||||
]));
|
||||
|
||||
if ($response === false) {
|
||||
Log::error('PayPal IPN verification request failed');
|
||||
return false;
|
||||
}
|
||||
|
||||
return trim($response) === 'VERIFIED';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ class LoginRequest extends FormRequest
|
||||
RateLimiter::hit($this->throttleKey());
|
||||
|
||||
throw ValidationException::withMessages([
|
||||
'login' => __('auth.failed'),
|
||||
'login' => __('auth.failed_credentials'),
|
||||
]);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user