I have a cancel order method that refunds the user.
However, using the API, if a user calls the endpoint twice (in a loop) for the same record, it refunds the user twice. If I try 3x Api calls at once, first 2 requests gets the refund, 3rd request doesn't.
public function cancelOrder($orderId) {
// First I tried to solve with cache,
// but it is not fast enough to catch the loop
if (Cache::has("api-canceling-$orderId")) {
return response()->json(['message' => "Already cancelling"], 403);
}
Cache::put("api-voiding-$labelId", true, 60);
// Here I get the transaction, and check if 'transaction->is_cancelled'.
// I thought cache will be faster but apparently not enough.
$transaction = Transaction::where('order_id', $orderId)
->where('user_id', auth()->user()->id)
->where('type', "Order Charge")
->firstOrFail();
if ($transaction->is_cancelled) {
return response()->json(['message' => "Order already cancelled"], 403);
}
// Here I do the api call to 3rd party service and wait for response
try {
$result = (new OrderCanceller)->cancel($orderId);
} catch (Exception $e) {
return response()->json(['message' => $e->getMessage()], 403);
}
$transaction->is_cancelled = true;
$transaction->save();
// This is the operation getting called twice.
Transaction::createCancelRefund($transaction);
return response()->json(['message' => 'Refund is made to your account']);
}
The createCancelRefund() method looks like this:
public static function createCancelRefund($transaction) {
Transaction::create([
'user_id' => $transaction->user_id,
'credit_movement' => $transaction->credit_movement * -1,
'type' => "Order Refund"
]);
}
Things I have tried:
Wrapping everything inside
cancelOrder()method intoDB::transaction({}, 5)closure. (Also triedDB::beginTransaction()approach)Using
->lockForUpdate()on$transaction = Transaction::where('order_id', $orderId)... query.Wrapping
createCancelRefund()content insideDB::transaction({}, 5), but I think as it'screate()it didn't help.Tried using cache but it was not as fast.
Looked up to throttling but doesn't seem like it can prevent this (if I say 2 request/min, duplicate creation still occurs)
What is the proper way of preventing duplicate refund creations inside createCancelRefund()?