From 689e456fe54367e258056dc0d7aae2ff46052752 Mon Sep 17 00:00:00 2001 From: CarlosTorres Date: Tue, 27 Jan 2026 23:48:27 +0000 Subject: [PATCH] =?UTF-8?q?fix:=20Corregir=20autenticaci=C3=B3n=20API,=20b?= =?UTF-8?q?ypass=20de=20pago=20y=20relaciones=20de=20modelos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Corregir middleware Authenticate para retornar JSON 401 en rutas API - Agregar método unauthenticated() en Handler para respuestas JSON - Implementar bypass de pago en ContractController - Corregir relaciones belongsToMany en Postulations y Suppliers - Corregir concatenación de strings en NoHomeController Co-Authored-By: Claude Opus 4.5 --- app/Exceptions/Handler.php | 18 +++ app/Http/Controllers/ContractController.php | 160 ++++++++++++-------- app/Http/Controllers/NoHomeController.php | 2 +- app/Http/Middleware/Authenticate.php | 4 + app/Models/Postulations.php | 2 +- app/Models/Suppliers.php | 2 +- 6 files changed, 122 insertions(+), 66 deletions(-) diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 05aeb49..69d50eb 100755 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -4,6 +4,7 @@ namespace App\Exceptions; use Throwable; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; +use Illuminate\Auth\AuthenticationException; class Handler extends ExceptionHandler { @@ -48,4 +49,21 @@ class Handler extends ExceptionHandler { return parent::render($request, $exception); } + + /** + * Convert an authentication exception into a response. + * + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Auth\AuthenticationException $exception + * @return \Illuminate\Http\Response + */ + protected function unauthenticated($request, AuthenticationException $exception) + { + // Para rutas API, siempre retornar JSON + if ($request->is('api/*') || $request->expectsJson()) { + return response()->json(['message' => 'Unauthenticated.'], 401); + } + + return redirect()->guest(route('login')); + } } diff --git a/app/Http/Controllers/ContractController.php b/app/Http/Controllers/ContractController.php index e23dae2..29e1b45 100755 --- a/app/Http/Controllers/ContractController.php +++ b/app/Http/Controllers/ContractController.php @@ -159,28 +159,50 @@ class ContractController extends Controller public function create(Request $request) { - $rules = [ - 'postulation_id' => 'required|numeric', - 'supplier_id' => 'required|numeric', - 'card_id' => 'required|numeric', - 'code' => 'required|numeric', - 'device_id' => 'required|string|regex:/(^[A-Za-z0-9 ]+$)+/', - 'coupon' => 'nullable|string|regex:/(^[A-Za-z0-9 ]+$)+/', - ]; + // Si el bypass está activo, usar reglas relajadas + $paymentBypass = env('PAYMENT_BYPASS', false); + + if ($paymentBypass) { + $rules = [ + 'postulation_id' => 'required|numeric', + 'supplier_id' => 'required|numeric', + 'card_id' => 'required|string', + 'code' => 'required|string', + 'device_id' => 'required|string', + 'coupon' => 'nullable|string', + ]; + } else { + $rules = [ + 'postulation_id' => 'required|numeric', + 'supplier_id' => 'required|numeric', + 'card_id' => 'required|numeric', + 'code' => 'required|numeric', + 'device_id' => 'required|string|regex:/(^[A-Za-z0-9 ]+$)+/', + 'coupon' => 'nullable|string|regex:/(^[A-Za-z0-9 ]+$)+/', + ]; + } $validator = Validator::make($request->all(), $rules); if ($validator->fails()) { + // Para rutas API, retornar JSON en lugar de redirect + if ($request->is('api/*') || $request->expectsJson()) { + return response()->json(['type' => 'error', 'message' => $validator->errors()->first()], 422); + } return redirect()->back()->withInput($request->all())->withErrors($validator); } else { $user = Auth::user(); $postulation = Postulations::where('id', $request->postulation_id)->first(); $coupon = Coupon::where('name', $request->coupon)->first(); - Openpay::setProductionMode(true); + + if (!$paymentBypass) { + Openpay::setProductionMode(true); + } if ($user->id == $postulation->user_id) { - if ($request->card_id) { + $card = null; + if (!$paymentBypass && $request->card_id) { $card = Cards::where('id', $request->card_id)->first(); } $supplier = Suppliers::where('id', $request->supplier_id)->first(); @@ -191,7 +213,8 @@ class ContractController extends Controller $ichambafee = iChambaParameter::where('parameter', 'ichamba_fee')->first(); $category = Categories::where('id', $postulation->category_id)->first(); - if ($card->user_id == $user->id) { + // En modo bypass, saltar la validación de tarjeta + if ($paymentBypass || ($card && $card->user_id == $user->id)) { $contract = new CurrentContracts(); $contract->user_id = $postulation->user_id; @@ -232,14 +255,17 @@ class ContractController extends Controller $discount = (($fee*(($coupon->percentage = null ? 0 : $coupon->percentage)/100))+($coupon->amount = null ? 0 : $coupon->amount)); $contract->coupon_id = $coupon->id; - $chargeData = array( - 'source_id' => $card->token, - 'method' => 'card', - 'amount' => ((($supplier->minimun_fee < 150 ? 150 : $supplier->minimun_fee)*(1 - (($coupon->percentage = null ? 0 : $coupon->percentage)/100)))-($coupon->amount = null ? 0 : $coupon->amount)), - 'description' => ('Contrato del usuario: ' . $user->name . ' del servicio ' . $category->name . ' realizado por el proveedor: ' . $supplier->company_name), - 'device_session_id' => $request->device_id, - 'cvv2' => $request->code - ); + // Solo crear chargeData si no estamos en bypass mode + if (!$paymentBypass && $card) { + $chargeData = array( + 'source_id' => $card->token, + 'method' => 'card', + 'amount' => ((($supplier->minimun_fee < 150 ? 150 : $supplier->minimun_fee)*(1 - (($coupon->percentage = null ? 0 : $coupon->percentage)/100)))-($coupon->amount = null ? 0 : $coupon->amount)), + 'description' => ('Contrato del usuario: ' . $user->name . ' del servicio ' . $category->name . ' realizado por el proveedor: ' . $supplier->company_name), + 'device_session_id' => $request->device_id, + 'cvv2' => $request->code + ); + } } } } else { @@ -248,61 +274,69 @@ class ContractController extends Controller $discount = 0; $contract->coupon_id = null; - $chargeData = array( - 'source_id' => $card->token, - 'method' => 'card', - 'amount' => ($supplier->minimun_fee < 150 ? 150 : $supplier->minimun_fee), - 'description' => ('Contrato del usuario: ' . $user->name . ' del servicio ' . $category->name . ' realizado por el proveedor: ' . $supplier->company_name), - 'device_session_id' => $request->device_id, - 'cvv2' => $request->code - ); + // Solo crear chargeData si no estamos en bypass mode + if (!$paymentBypass && $card) { + $chargeData = array( + 'source_id' => $card->token, + 'method' => 'card', + 'amount' => ($supplier->minimun_fee < 150 ? 150 : $supplier->minimun_fee), + 'description' => ('Contrato del usuario: ' . $user->name . ' del servicio ' . $category->name . ' realizado por el proveedor: ' . $supplier->company_name), + 'device_session_id' => $request->device_id, + 'cvv2' => $request->code + ); + } } if (!empty($request->card_id) && !empty($request->device_id) && !empty($request->code) && $fee > $discount) { - try { - $openpay = Openpay::getInstance(config('app.openpay_id'), config('app.openpay_apikey')); + // Bypass de pago para pruebas + if (env('PAYMENT_BYPASS', false)) { + $contract->transaction_id = 'BYPASS_' . uniqid(); + } else { + try { + $openpay = Openpay::getInstance(config('app.openpay_id'), config('app.openpay_apikey')); - $customer = $openpay->customers->get($user->openpay_id); - $charge = $customer->charges->create($chargeData); + $customer = $openpay->customers->get($user->openpay_id); + $charge = $customer->charges->create($chargeData); - } catch (OpenpayApiTransactionError $e) { - return response()->json([ - 'type' => 'error', - 'message' => 'No se pudo procesar la transacción' - ]); - } catch (OpenpayApiRequestError $e) { - return response()->json([ - 'type' => 'error', - 'message' => 'No se pudo procesar la operación' - ]); - } catch (OpenpayApiConnectionError $e) { - return response()->json([ - 'type' => 'error', - 'message' => 'Error al conectarse a Openpay:' . $e->getMessage() - ]); + } catch (OpenpayApiTransactionError $e) { + return response()->json([ + 'type' => 'error', + 'message' => 'No se pudo procesar la transacción' + ]); + } catch (OpenpayApiRequestError $e) { + return response()->json([ + 'type' => 'error', + 'message' => 'No se pudo procesar la operación' + ]); + } catch (OpenpayApiConnectionError $e) { + return response()->json([ + 'type' => 'error', + 'message' => 'Error al conectarse a Openpay:' . $e->getMessage() + ]); - } catch (OpenpayApiAuthError $e) { - return response()->json([ - 'type' => 'error', - 'message' => 'Error al conectarse a Openpay' . $e->getMessage() - ]); + } catch (OpenpayApiAuthError $e) { + return response()->json([ + 'type' => 'error', + 'message' => 'Error al conectarse a Openpay' . $e->getMessage() + ]); - } catch (OpenpayApiError $e) { - return response()->json([ - 'type' => 'error', - 'message' => 'Error al conectarse a Openpay' . $e->getMessage() - ]); - } catch (Exception $e) { - return response()->json([ - 'type' => 'error', - 'message' => 'Error: ' . $e->getMessage() - ]); + } catch (OpenpayApiError $e) { + return response()->json([ + 'type' => 'error', + 'message' => 'Error al conectarse a Openpay' . $e->getMessage() + ]); + } catch (Exception $e) { + return response()->json([ + 'type' => 'error', + 'message' => 'Error: ' . $e->getMessage() + ]); + } + + $contract->transaction_id = $charge->id; } - $contract->transaction_id = $charge->id; - } else if ($coupon) { if ($coupon->limit > 0 && $discount >= $fee) { if(!isset($checkccontracts) && !isset($checkccontracts)) { diff --git a/app/Http/Controllers/NoHomeController.php b/app/Http/Controllers/NoHomeController.php index cc765b0..d2fc180 100755 --- a/app/Http/Controllers/NoHomeController.php +++ b/app/Http/Controllers/NoHomeController.php @@ -146,7 +146,7 @@ class NoHomeController extends Controller $data = null, $buttons = null, $schedule = null, - $headings = $client->name + ", tu proveedor del servicio ha llegado" + $headings = $client->name . ", tu proveedor del servicio ha llegado" ); return response()->json([ //'message' => 'Por favor espere a los 10 minutos de tolerancia de la hora acordada' diff --git a/app/Http/Middleware/Authenticate.php b/app/Http/Middleware/Authenticate.php index a4be5c5..f0e77ea 100755 --- a/app/Http/Middleware/Authenticate.php +++ b/app/Http/Middleware/Authenticate.php @@ -14,6 +14,10 @@ class Authenticate extends Middleware */ protected function redirectTo($request) { + // Para rutas API, nunca redirigir - dejar que lance excepción 401 + if ($request->is('api/*')) { + return null; + } if (! $request->expectsJson()) { return route('login'); } diff --git a/app/Models/Postulations.php b/app/Models/Postulations.php index 16c26b7..b182870 100755 --- a/app/Models/Postulations.php +++ b/app/Models/Postulations.php @@ -44,7 +44,7 @@ class Postulations extends Model public function suppliers() { - return $this->hasMany(Suppliers::class); + return $this->belongsToMany(Suppliers::class, 'postulations_suppliers', 'postulations_id', 'suppliers_id')->withTimestamps(); } public function status() diff --git a/app/Models/Suppliers.php b/app/Models/Suppliers.php index 1cd5f26..c4783f3 100755 --- a/app/Models/Suppliers.php +++ b/app/Models/Suppliers.php @@ -55,7 +55,7 @@ class Suppliers extends Model public function postulations() { - return $this->belongsToMany(Postulations::class)->withTimestamps(); + return $this->belongsToMany(Postulations::class, 'postulations_suppliers', 'suppliers_id', 'postulations_id')->withTimestamps(); } public function payments()