getContent(); $sig = $request->header('Stripe-Signature'); $secret = config('services.stripe.webhook'); if (!$secret || !$sig) { abort(400, 'Missing signature'); } $expectedSig = 'v1=' . hash_hmac('sha256', $payload, $secret); if (!hash_equals($expectedSig, $sig)) { abort(400, 'Invalid signature'); } $event = json_decode($payload, true); if (json_last_error() !== JSON_ERROR_NONE) { Log::error('Invalid JSON in Stripe webhook: ' . json_last_error_msg()); return response('', 200); } if ($event['type'] === 'checkout.session.completed') { $session = $event['data']['object']; $receiptId = $session['id']; // Idempotency check if (EventPurchase::where('external_receipt_id', $receiptId)->exists()) { return response('', 200); } $tenantId = $session['metadata']['tenant_id'] ?? null; if (!$tenantId) { Log::warning('No tenant_id in Stripe metadata', ['receipt_id' => $receiptId]); // Dispatch job for retry or manual resolution \App\Jobs\ValidateStripeWebhookJob::dispatch($payload, $sig); return response('', 200); } $tenant = Tenant::find($tenantId); if (!$tenant) { Log::error('Tenant not found for Stripe webhook', ['tenant_id' => $tenantId]); \App\Jobs\ValidateStripeWebhookJob::dispatch($payload, $sig); return response('', 200); } $amount = $session['amount_total'] / 100; $currency = $session['currency']; $eventsPurchased = (int) ($session['metadata']['events_purchased'] ?? 1); DB::transaction(function () use ($tenant, $amount, $currency, $eventsPurchased, $receiptId) { $purchase = EventPurchase::create([ 'tenant_id' => $tenant->id, 'events_purchased' => $eventsPurchased, 'amount' => $amount, 'currency' => $currency, 'provider' => 'stripe', 'external_receipt_id' => $receiptId, 'status' => 'completed', 'purchased_at' => now(), ]); $tenant->incrementCredits($eventsPurchased, 'purchase', null, $purchase->id); }); Log::info('Processed Stripe purchase', ['receipt_id' => $receiptId, 'tenant_id' => $tenantId]); } else { // For other event types, log or dispatch job if needed Log::info('Unhandled Stripe event', ['type' => $event['type']]); // Optionally dispatch job for processing other events \App\Jobs\ValidateStripeWebhookJob::dispatch($payload, $sig); } return response('', 200); } }