-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathWebhookController.php
More file actions
45 lines (36 loc) · 1.41 KB
/
Copy pathWebhookController.php
File metadata and controls
45 lines (36 loc) · 1.41 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
<?php
declare(strict_types=1);
namespace App\Http\Controllers\Billing;
use App\Domain\Billing\Models\WebhookEvent;
use Illuminate\Http\Request;
use Laravel\Cashier\Http\Controllers\WebhookController as CashierWebhookController;
use Symfony\Component\HttpFoundation\Response;
final class WebhookController extends CashierWebhookController
{
/**
* Handle a Stripe webhook with idempotency dedup.
*
* Stripe retries on 5xx and network failures, so the same event_id can
* arrive multiple times. We persist a row per processed event_id and
* short-circuit on replay.
*/
public function handleWebhook(Request $request)
{
/** @var array{id?: string, type?: string} $payload */
$payload = json_decode($request->getContent(), true) ?? [];
$eventId = $payload['id'] ?? null;
if (is_string($eventId) && WebhookEvent::query()->where('stripe_event_id', $eventId)->exists()) {
// Replay — already processed, ack with 200 so Stripe stops retrying.
return new Response('Webhook already processed.', 200);
}
$response = parent::handleWebhook($request);
if (is_string($eventId)) {
WebhookEvent::create([
'stripe_event_id' => $eventId,
'type' => (string) ($payload['type'] ?? 'unknown'),
'processed_at' => now(),
]);
}
return $response;
}
}