52 lines
1.3 KiB
PHP
52 lines
1.3 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace App\Http\Middleware;
|
||
|
|
|
||
|
|
use Closure;
|
||
|
|
use Illuminate\Http\Request;
|
||
|
|
use Illuminate\Support\Facades\Cache;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* Idempotency key support for safe POST retries.
|
||
|
|
*
|
||
|
|
* If Idempotency-Key header is present, caches the response for 24h.
|
||
|
|
* Subsequent requests with the same key return the cached response.
|
||
|
|
*/
|
||
|
|
class IdempotencyKey
|
||
|
|
{
|
||
|
|
public function handle(Request $request, Closure $next): mixed
|
||
|
|
{
|
||
|
|
if ($request->method() !== 'POST') {
|
||
|
|
return $next($request);
|
||
|
|
}
|
||
|
|
|
||
|
|
$key = $request->header('Idempotency-Key');
|
||
|
|
if (! $key) {
|
||
|
|
return $next($request);
|
||
|
|
}
|
||
|
|
|
||
|
|
$cacheKey = 'idempotency:' . hash('sha256', $key);
|
||
|
|
|
||
|
|
// Return cached response if exists
|
||
|
|
$cached = Cache::get($cacheKey);
|
||
|
|
if ($cached) {
|
||
|
|
return response()->json($cached['body'], $cached['status'])
|
||
|
|
->header('X-Idempotency-Replayed', 'true');
|
||
|
|
}
|
||
|
|
|
||
|
|
$response = $next($request);
|
||
|
|
|
||
|
|
// Cache successful responses for 24h
|
||
|
|
if ($response->status() < 500) {
|
||
|
|
Cache::put($cacheKey, [
|
||
|
|
'body' => json_decode($response->getContent(), true),
|
||
|
|
'status' => $response->status(),
|
||
|
|
], 86400);
|
||
|
|
}
|
||
|
|
|
||
|
|
return $response;
|
||
|
|
}
|
||
|
|
}
|