validate($request, [ 'voter_id' => 'required|integer', 'method' => 'nullable|string|in:cash,bank', 'code' => 'required|string|min:4|max:20', ]); $electionId = Util::getCurrentElection(); $method = $request->input('method', 'cash'); $voterId = (int) $request->input('voter_id'); $code = trim((string) $request->input('code')); $row = AllowanceClaimCode::where('election_id', $electionId) ->where('voter_id', $voterId) ->where('method', $method) ->first(); if (!$row) { return response()->json([ 'status' => 'failed', 'message' => 'Kod tuntutan tidak dijumpai.', ], 404); } if ($row->used_at) { return response()->json([ 'status' => 'failed', 'message' => 'Kod tuntutan telah digunakan.', ], 422); } if ($row->expires_at && Carbon::now()->greaterThan($row->expires_at)) { return response()->json([ 'status' => 'failed', 'message' => 'Kod tuntutan telah tamat tempoh.', ], 422); } if (!Hash::check($code, $row->code_hash)) { return response()->json([ 'status' => 'failed', 'message' => 'Kod tuntutan tidak sah.', ], 422); } // Mark used (consumed) here so it cannot be reused $row->used_at = Carbon::now(); $row->used_by_admin_id = $request->user() ? (int) $request->user()->id : null; $row->save(); return response()->json([ 'status' => 'success', 'message' => 'Kod tuntutan disahkan.', 'claim' => [ 'id' => $row->id, 'voter_id' => $row->voter_id, 'method' => $row->method, 'expires_at' => $row->expires_at, 'used_at' => $row->used_at, ], ]); } }