fix: Corregir autenticación API, bypass de pago y relaciones de modelos

- 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 <noreply@anthropic.com>
This commit is contained in:
2026-01-27 23:48:27 +00:00
parent f571caa204
commit 689e456fe5
6 changed files with 122 additions and 66 deletions

View File

@@ -4,6 +4,7 @@ namespace App\Exceptions;
use Throwable; use Throwable;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Auth\AuthenticationException;
class Handler extends ExceptionHandler class Handler extends ExceptionHandler
{ {
@@ -48,4 +49,21 @@ class Handler extends ExceptionHandler
{ {
return parent::render($request, $exception); 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'));
}
} }

View File

@@ -159,6 +159,19 @@ class ContractController extends Controller
public function create(Request $request) { public function create(Request $request) {
// 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 = [ $rules = [
'postulation_id' => 'required|numeric', 'postulation_id' => 'required|numeric',
'supplier_id' => 'required|numeric', 'supplier_id' => 'required|numeric',
@@ -167,20 +180,29 @@ class ContractController extends Controller
'device_id' => 'required|string|regex:/(^[A-Za-z0-9 ]+$)+/', 'device_id' => 'required|string|regex:/(^[A-Za-z0-9 ]+$)+/',
'coupon' => 'nullable|string|regex:/(^[A-Za-z0-9 ]+$)+/', 'coupon' => 'nullable|string|regex:/(^[A-Za-z0-9 ]+$)+/',
]; ];
}
$validator = Validator::make($request->all(), $rules); $validator = Validator::make($request->all(), $rules);
if ($validator->fails()) { 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); return redirect()->back()->withInput($request->all())->withErrors($validator);
} else { } else {
$user = Auth::user(); $user = Auth::user();
$postulation = Postulations::where('id', $request->postulation_id)->first(); $postulation = Postulations::where('id', $request->postulation_id)->first();
$coupon = Coupon::where('name', $request->coupon)->first(); $coupon = Coupon::where('name', $request->coupon)->first();
if (!$paymentBypass) {
Openpay::setProductionMode(true); Openpay::setProductionMode(true);
}
if ($user->id == $postulation->user_id) { 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(); $card = Cards::where('id', $request->card_id)->first();
} }
$supplier = Suppliers::where('id', $request->supplier_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(); $ichambafee = iChambaParameter::where('parameter', 'ichamba_fee')->first();
$category = Categories::where('id', $postulation->category_id)->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 = new CurrentContracts();
$contract->user_id = $postulation->user_id; $contract->user_id = $postulation->user_id;
@@ -232,6 +255,8 @@ class ContractController extends Controller
$discount = (($fee*(($coupon->percentage = null ? 0 : $coupon->percentage)/100))+($coupon->amount = null ? 0 : $coupon->amount)); $discount = (($fee*(($coupon->percentage = null ? 0 : $coupon->percentage)/100))+($coupon->amount = null ? 0 : $coupon->amount));
$contract->coupon_id = $coupon->id; $contract->coupon_id = $coupon->id;
// Solo crear chargeData si no estamos en bypass mode
if (!$paymentBypass && $card) {
$chargeData = array( $chargeData = array(
'source_id' => $card->token, 'source_id' => $card->token,
'method' => 'card', 'method' => 'card',
@@ -242,12 +267,15 @@ class ContractController extends Controller
); );
} }
} }
}
} else { } else {
$fee = ($supplier->minimun_fee < 150 ? 150 : $supplier->minimun_fee); $fee = ($supplier->minimun_fee < 150 ? 150 : $supplier->minimun_fee);
$discount = 0; $discount = 0;
$contract->coupon_id = null; $contract->coupon_id = null;
// Solo crear chargeData si no estamos en bypass mode
if (!$paymentBypass && $card) {
$chargeData = array( $chargeData = array(
'source_id' => $card->token, 'source_id' => $card->token,
'method' => 'card', 'method' => 'card',
@@ -257,9 +285,14 @@ class ContractController extends Controller
'cvv2' => $request->code 'cvv2' => $request->code
); );
} }
}
if (!empty($request->card_id) && !empty($request->device_id) && !empty($request->code) && $fee > $discount) { if (!empty($request->card_id) && !empty($request->device_id) && !empty($request->code) && $fee > $discount) {
// Bypass de pago para pruebas
if (env('PAYMENT_BYPASS', false)) {
$contract->transaction_id = 'BYPASS_' . uniqid();
} else {
try { try {
$openpay = Openpay::getInstance(config('app.openpay_id'), config('app.openpay_apikey')); $openpay = Openpay::getInstance(config('app.openpay_id'), config('app.openpay_apikey'));
@@ -302,6 +335,7 @@ class ContractController extends Controller
} }
$contract->transaction_id = $charge->id; $contract->transaction_id = $charge->id;
}
} else if ($coupon) { } else if ($coupon) {
if ($coupon->limit > 0 && $discount >= $fee) { if ($coupon->limit > 0 && $discount >= $fee) {

View File

@@ -146,7 +146,7 @@ class NoHomeController extends Controller
$data = null, $data = null,
$buttons = null, $buttons = null,
$schedule = null, $schedule = null,
$headings = $client->name + ", tu proveedor del servicio ha llegado" $headings = $client->name . ", tu proveedor del servicio ha llegado"
); );
return response()->json([ return response()->json([
//'message' => 'Por favor espere a los 10 minutos de tolerancia de la hora acordada' //'message' => 'Por favor espere a los 10 minutos de tolerancia de la hora acordada'

View File

@@ -14,6 +14,10 @@ class Authenticate extends Middleware
*/ */
protected function redirectTo($request) protected function redirectTo($request)
{ {
// Para rutas API, nunca redirigir - dejar que lance excepción 401
if ($request->is('api/*')) {
return null;
}
if (! $request->expectsJson()) { if (! $request->expectsJson()) {
return route('login'); return route('login');
} }

View File

@@ -44,7 +44,7 @@ class Postulations extends Model
public function suppliers() public function suppliers()
{ {
return $this->hasMany(Suppliers::class); return $this->belongsToMany(Suppliers::class, 'postulations_suppliers', 'postulations_id', 'suppliers_id')->withTimestamps();
} }
public function status() public function status()

View File

@@ -55,7 +55,7 @@ class Suppliers extends Model
public function postulations() public function postulations()
{ {
return $this->belongsToMany(Postulations::class)->withTimestamps(); return $this->belongsToMany(Postulations::class, 'postulations_suppliers', 'suppliers_id', 'postulations_id')->withTimestamps();
} }
public function payments() public function payments()