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 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'));
}
}

View File

@@ -159,6 +159,19 @@ class ContractController extends Controller
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 = [
'postulation_id' => 'required|numeric',
'supplier_id' => 'required|numeric',
@@ -167,20 +180,29 @@ class ContractController extends Controller
'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();
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,6 +255,8 @@ class ContractController extends Controller
$discount = (($fee*(($coupon->percentage = null ? 0 : $coupon->percentage)/100))+($coupon->amount = null ? 0 : $coupon->amount));
$contract->coupon_id = $coupon->id;
// Solo crear chargeData si no estamos en bypass mode
if (!$paymentBypass && $card) {
$chargeData = array(
'source_id' => $card->token,
'method' => 'card',
@@ -242,12 +267,15 @@ class ContractController extends Controller
);
}
}
}
} else {
$fee = ($supplier->minimun_fee < 150 ? 150 : $supplier->minimun_fee);
$discount = 0;
$contract->coupon_id = null;
// Solo crear chargeData si no estamos en bypass mode
if (!$paymentBypass && $card) {
$chargeData = array(
'source_id' => $card->token,
'method' => 'card',
@@ -257,9 +285,14 @@ class ContractController extends Controller
'cvv2' => $request->code
);
}
}
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 {
$openpay = Openpay::getInstance(config('app.openpay_id'), config('app.openpay_apikey'));
@@ -302,6 +335,7 @@ class ContractController extends Controller
}
$contract->transaction_id = $charge->id;
}
} else if ($coupon) {
if ($coupon->limit > 0 && $discount >= $fee) {

View File

@@ -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'

View File

@@ -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');
}

View File

@@ -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()

View File

@@ -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()