Implement multi-tenancy support with OAuth2 authentication for tenant admins, Stripe integration for event purchases and credits ledger, new Filament resources for event purchases, updated API routes and middleware for tenant isolation and token guarding, added factories/seeders/migrations for new models (Tenant, EventPurchase, OAuth entities, etc.), enhanced tests, and documentation updates. Removed outdated DemoAchievementsSeeder.
This commit is contained in:
99
app/Jobs/ValidateStripeWebhookJob.php
Normal file
99
app/Jobs/ValidateStripeWebhookJob.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Jobs;
|
||||
|
||||
use App\Models\EventPurchase;
|
||||
use App\Models\Tenant;
|
||||
use Illuminate\Bus\Queueable;
|
||||
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||
use Illuminate\Foundation\Bus\Dispatchable;
|
||||
use Illuminate\Queue\InteractsWithQueue;
|
||||
use Illuminate\Queue\SerializesModels;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Facades\Log;
|
||||
|
||||
class ValidateStripeWebhookJob implements ShouldQueue
|
||||
{
|
||||
use Dispatchable, InteractsWithQueue, Queueable, SerializesModels;
|
||||
|
||||
public $payload;
|
||||
public $sig;
|
||||
public $tries = 3;
|
||||
public $backoff = [60, 300, 600]; // Retry delays
|
||||
|
||||
public function __construct($payload, $sig)
|
||||
{
|
||||
$this->payload = $payload;
|
||||
$this->sig = $sig;
|
||||
}
|
||||
|
||||
public function handle()
|
||||
{
|
||||
$secret = config('services.stripe.webhook');
|
||||
|
||||
if (!$secret) {
|
||||
Log::error('No Stripe webhook secret configured');
|
||||
return;
|
||||
}
|
||||
|
||||
$expectedSig = 'v1=' . hash_hmac('sha256', $this->payload, $secret);
|
||||
|
||||
if (!hash_equals($expectedSig, $this->sig)) {
|
||||
Log::error('Invalid signature in Stripe webhook job');
|
||||
return;
|
||||
}
|
||||
|
||||
$event = json_decode($this->payload, true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||
Log::error('Invalid JSON in Stripe webhook job: ' . json_last_error_msg());
|
||||
return;
|
||||
}
|
||||
|
||||
if ($event['type'] === 'checkout.session.completed') {
|
||||
$session = $event['data']['object'];
|
||||
$receiptId = $session['id'];
|
||||
|
||||
if (EventPurchase::where('external_receipt_id', $receiptId)->exists()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$tenantId = $session['metadata']['tenant_id'] ?? null;
|
||||
|
||||
if (!$tenantId) {
|
||||
Log::warning('No tenant_id in Stripe metadata job', ['receipt_id' => $receiptId]);
|
||||
return;
|
||||
}
|
||||
|
||||
$tenant = Tenant::find($tenantId);
|
||||
|
||||
if (!$tenant) {
|
||||
Log::error('Tenant not found in Stripe webhook job', ['tenant_id' => $tenantId]);
|
||||
return;
|
||||
}
|
||||
|
||||
$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 via job', ['receipt_id' => $receiptId, 'tenant_id' => $tenantId]);
|
||||
} else {
|
||||
Log::info('Unhandled Stripe event in job', ['type' => $event['type']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user