PHP code example of devaction-labs / idempotency

1. Go to this page and download the library: Download devaction-labs/idempotency library. Choose the download type require.

2. Extract the ZIP file and open the index.php.

3. Add this code to the index.php.
    
        
<?php
require_once('vendor/autoload.php');

/* Start to develop here. Best regards https://php-download.com/ */

    

devaction-labs / idempotency example snippets


// routes/api.php
Route::middleware(['auth:api', 'idempotent'])->group(function () {
    Route::post('/payments',  [PaymentController::class, 'store']);
    Route::post('/refunds',   [RefundController::class, 'store']);
    Route::delete('/orders/{order}', [OrderController::class, 'destroy']);
});

// Allow the route to be called without a key (e.g. public webhook probe)
Route::post('/webhooks/stripe', $handler)
    ->middleware('idempotent:optional');

// Short TTL for ephemeral actions
Route::post('/votes', $handler)
    ->middleware('idempotent:ttl=60');

// Stricter scope for tenant-partitioned operations
Route::post('/charges', $handler)
    ->middleware('idempotent:ttl=900,scope=user');

return [
    'enabled'     => env('IDEMPOTENCY_ENABLED', true),
    'header_name' => env('IDEMPOTENCY_HEADER_NAME', 'Idempotency-Key'),
    'methods'     => ['POST', 'PUT', 'PATCH', 'DELETE'],

    'cache_store' => env('IDEMPOTENCY_CACHE_STORE', null),  // null = default
    'ttl'         => (int) env('IDEMPOTENCY_TTL', 86_400),   // seconds

    'scope' => env('IDEMPOTENCY_SCOPE', 'user_route'),
    //       global | user | route | ip | user_route | FQCN<ScopeResolver>

    'validation' => [
        'pattern'        => env('IDEMPOTENCY_KEY_PATTERN', 'uuid'),
        'max_key_length' => (int) env('IDEMPOTENCY_KEY_MAX_LENGTH', 255),
    ],

    'payload' => [
        'algo'          => env('IDEMPOTENCY_HASH_ALGO', 'sha256'),
        'sort_keys'     => true,
        'ignore'        => ['timestamp', 'client_request_id'],
        '

'payload' => [
    'algo'               => 'sha256',
    'sort_keys'          => true,
    'ignore'             => ['timestamp', 'captured_at'],
    '

'response' => [
    'strip_headers' => ['X-Internal-Token'],
],

use DevactionLabs\Idempotency\Events\IdempotencyAlertFired;
use DevactionLabs\Idempotency\Logging\EventType;

Event::listen(IdempotencyAlertFired::class, function (IdempotencyAlertFired $event): void {
    match ($event->eventType) {
        EventType::PAYLOAD_MISMATCH    => logger()->warning('idempotency.mismatch', $event->context),
        EventType::CONCURRENT_CONFLICT => logger()->info('idempotency.concurrent',   $event->context),
        EventType::SIZE_WARNING        => logger()->notice('idempotency.large',      $event->context),
        default                        => logger()->debug('idempotency.'.$event->eventType->value, $event->context),
    };
});

use DevactionLabs\Idempotency\Contracts\{
    KeyValidator,
    PayloadHasher,
    ScopeResolver,
    ResponseSerializer,
    TelemetryDriver,
};

// In your AppServiceProvider
public function register(): void
{
    $this->app->bind(ScopeResolver::class, TenantScopeResolver::class);
    $this->app->bind(PayloadHasher::class, WebhookAwareHasher::class);
}

final class TenantScopeResolver implements ScopeResolver
{
    public function resolve(Illuminate\Http\Request $request): string
    {
        $tenant = $request->header('X-Tenant-ID') ?? 'public';
        $user   = $request->user()?->getAuthIdentifier() ?? 'guest';

        return "t:{$tenant}:u:{$user}";
    }
}

use DevactionLabs\Idempotency\Facades\Idempotency;

Idempotency::flush('123e4567-e89b-12d3-a456-426614174000', scope: 'u42');
Idempotency::has('123e4567-e89b-12d3-a456-426614174000');

// config/cache.php
'stores' => [
    'idempotency' => [
        'driver'     => 'redis',
        'connection' => 'idempotency',  // a distinct Redis DB or instance
    ],
],

// B) Shared store but prefixed — good enough
'cache' => [
    'prefix' => env('CACHE_PREFIX', Str::slug(env('APP_NAME'), '_').'_cache'),
],

// bootstrap/app.php — Laravel 11+
->withMiddleware(function (Middleware $middleware) {
    $middleware->trustProxies(at: '*'); // or specific subnets
})

// routes/api.php
Route::post('/payments', [PaymentController::class, 'store'])
    ->middleware(['auth:api', 'throttle:payments', 'idempotent']);

// app/Providers/AppServiceProvider.php
use Illuminate\Cache\RateLimiting\Limit;
use Illuminate\Support\Facades\RateLimiter;

public function boot(): void
{
    RateLimiter::for('payments', function (Request $request) {
        return Limit::perMinute(60)
            ->by($request->user()?->id ?: $request->ip())
            ->response(fn () => response()->json(['error' => 'Too many requests'], 429));
    });
}