Idempotency keys, and where the response has to be stored

A client that times out and retries has sent the same request twice, and the server cannot tell whether the first one succeeded — which is what the key is for.

$key = $request->header('Idempotency-Key');

try {
    $record = IdempotentRequest::create(['key' => $key, 'route' => 'orders.store']);
} catch (QueryException $e) {
    $prior = IdempotentRequest::where('key', $key)->firstOrFail();

    return response($prior->response_body, $prior->response_status);
}

// ... do the work, then store the response on $record

Storing the response rather than only the key is what makes the second call return the same answer instead of a conflict, which is what a retrying client needs. The record has to be written in the same transaction as the work, or a crash between them leaves a key claimed and nothing done. Expiring the keys after a day keeps the table small and is safe, since no reasonable client retries later than that.