# Refactorización del Flujo de Pago - CardNET

**Fecha**: 8 de Abril de 2026  
**Versión**: 1.0  
**Descripción**: Optimización completa del flujo de pago con CardNET según la documentación oficial

---

## 📋 Resumen de Cambios

### Archivos Modificados
1. **app/Http/Controllers/Web/HomeController.php**
   - `payment()` - Añadida lógica de validación para monto cero
   - `payment_pasarela()` - Refactorizado completamente con nuevos parámetros de CardNET
   - `payment_success()` - Optimizado para verificación correcta de transacciones
   - `payment_error()` - Mejorado con mapeo completo de códigos de respuesta CardNET

2. **resources/views/web/payment_cardnet.blade.php**
   - Actualizada la vista con diseño mejorado
   - Parámetros dinámicos para webservice
   - Better UX con loading spinner

---

## 🔄 Flujo de Pago Optimizado

### Diagrama del Flujo

```
eventConfirm() 
    ↓
payment(Request)
    ├─ ¿Monto = 0? → Ir a payment_ok() (sin CardNET)
    │
    └─ ¿Monto > 0 y payment_method_id = 1? 
        ↓
        payment_pasarela()
            ├─ Crear Person (si no existe)
            ├─ Crear User (si no existe)
            ├─ Guardar Cart con items
            └─ Crear Sesión en CardNET
                ↓
                Vista: payment_cardnet.blade.php (POST a CardNET)
                    ↓
                    CardNET Gateway (captura datos de tarjeta)
                        ↓
                        Retorna a payment_success()
                            ├─ Verificar transacción GET /sessions/{SESSION}
                            ├─ Validar ResponseCode
                            ├─ Si ResponseCode = "00" → payment_ok()
                            └─ Si ResponseCode ≠ "00" → payment_error()
                                ↓
                                Redirect con mensaje de error
```

---

## 💡 Cambios Principales

### 1. **payment() - Nueva Lógica de Monto Cero**

**Antes:**
```php
if ($request->payment_method_id == 1) {
    // Siempre ir a CardNET
    session()->put('request', $request->all());
    return response()->json(['url' => route('web.payment_pasarela', $event_id)]);
}
```

**Después:**
```php
// Calcular total del carrito
$total = 0;
foreach ($cart_data['tickets'] as $ticket) {
    $event_ticket = EventTicket::find($ticket['id']);
    $total += $event_ticket->price * $ticket['qty'];
}

// Si monto > 0 con CardNET, ir a pasarela
if ($request->payment_method_id == 1 && $total > 0) {
    session()->put('request', $request->all());
    return response()->json(['url' => route('web.payment_pasarela', $event_id)]);
}

// Si monto = 0, saltar CardNET y completar directo
return $this->payment_ok($request->all(), $event_id);
```

**Beneficio**: Evita transacciones innecesarias cuando el total es cero.

---

### 2. **payment_pasarela() - Parámetros Optimizados CardNET**

**Cambios Principales:**

#### a) **Formato de Monto (12 dígitos, en centavos)**
```php
// Antes: No estandarizado
'Amount' => $this->maskMoneyZero($cart->total, 11)

// Después: Estándar CardNET
$amount_formatted = str_pad(intval($total * 100), 12, '0', STR_PAD_LEFT);
```

#### b) **Parámetros Requeridos Según CardNET**
```php
$transactionData = [
    'TransactionType'          => '0200',  // Normal Sale
    'CurrencyCode'             => '214',   // RD Peso
    'AcquiringInstitutionCode' => '349',
    'MerchantType'             => $merchant['type'],
    'MerchantNumber'           => $merchant['number'],
    'MerchantTerminal'         => $merchant['terminal'],
    'MerchantTerminal_amex'    => '00000001',
    'ReturnUrl'                => $urls['return'],
    'CancelUrl'                => $urls['cancel'],
    'PageLanguage'             => 'ESP',
    'OrderId'                  => $code,
    'TransactionId'            => $code,
    'Tax'                      => '000000000000',
    'Amount'                   => $amount_formatted,
    'Ipclient'                 => request()->ip(),
    '3DS_email'                => Auth::user()->person->email,
    '3DS_mobilePhone'          => Auth::user()->person->phone,
];
```

#### c) **Manejo de Errores Mejorado**
```php
// Validación de respuesta
if (empty($responseBody['SESSION']) || empty($responseBody['session-key'])) {
    throw new \Exception('Respuesta inválida de CardNET');
}

// Logging de errores
\Log::error('Payment Gateway Error', [
    'exception' => $th,
    'request' => $request,
]);
```

**Beneficio**: Cumple totalmente con especificaciones de CardNET y reduce errores.

---

### 3. **payment_success() - Verificación Optimizada**

**Antes:**
```php
// Verificar directamente sin validar respuesta
$response = $client->get($url . '/sessions/' . $SESSION . '?sk=' . $session_key);
if ($responseBody['ResponseCode'] !== '00') {
    return $this->payment_error(...);
}
dd($form, $cart->event_id, $responseBody);  // ❌ DAD() sin procesar
return $this->payment_ok(...);
```

**Después:**
```php
// Validar respuesta
if ($verifyResponse->getStatusCode() !== 200) {
    \Log::error('CardNET verification failed');
    return $this->payment_error(...);
}

$responseCode = $responseBody['ResponseCode'] ?? '99';

// Manejar códigos según CardNET
if ($responseCode === '00') {
    return $this->payment_ok($form, $cart->event_id, $cardnetResponse);
} else {
    return $this->payment_error($responseBody);
}
```

**Beneficio**: Verificación correcta y procesamiento de transacciones aprobadas.

---

### 4. **payment_error() - Mapeo de Códigos de Respuesta**

**Antes:**
```php
// Mensaje genérico
return redirect()->route('web.home')
    ->with('error', 'Error en la transacción. Código: ' . $request->ResponseCode);
```

**Después:**
```php
$errorMessages = [
    '00' => 'Transacción aprobada',
    '04' => 'La transacción fue rechazada. Contacte a su banco.',
    '33' => 'La tarjeta ha expirado.',
    '51' => 'Fondos insuficientes.',
    '99' => 'Código de seguridad inválido.',
    'TF' => 'La autenticación 3DSecure no fue completada.',
    // +20 más...
];

$errorMessage = $errorMessages[$responseCode] ?? 'Error general';
\Log::error('Payment Error', ['response_code' => $responseCode, ...]);

return redirect()->route('web.home')
    ->with('error', '<b>Error en la transacción.</b><br>' . $errorMessage);
```

**Beneficio**: Mensajes claros para usuarios sobre qué salió mal.

---

### 5. **Vista: payment_cardnet.blade.php**

**Cambios:**

- **Antes**: Variables nó-dinámicas, hardcoded URL de CardNET
- **Después**: 
  - URL de webservice dinámica (soporta QA y Producción)
  - SESSION como formulario oculto
  - Formulario auto-submit después de 1.5s
  - UX mejorado con spinner y mejor styling
  - Soporte para ambos endpoints

```blade
<!-- Dinámico -->
<form action="{{ $webservice }}/authorize" method="post" id="paymentForm">
    <input name="SESSION" value="{{ $session }}" type="hidden" />
</form>

<script>
setTimeout(() => document.getElementById('paymentForm').submit(), 1500);
</script>
```

---

## ✅ Validaciones Incluidas

### Monto Cero
- ✅ No se crea sesión en CardNET
- ✅ Se completa la orden directamente sin pago
- ✅ Se generan boletas normalmente

### Validación de Sesión CardNET
- ✅ Verifica que SESSION y session-key existan
- ✅ Valida respuesta HTTP 200
- ✅ Maneja timeout de 30 minutos

### Códigos de Respuesta
- ✅ Aprobados: 00, 11
- ✅ Retry: 01, 02, 08, 09, 19, 90-92, 97
- ✅ Rechazados: 04-07, 33, 51, 54, 62, 65, 99
- ✅ 3DSecure: TF (3D Secure failure)

### Seguridad
- ✅ TLS 1.2+ requerido
- ✅ Session-key validado en GET
- ✅ IP de cliente registrado
- ✅ Tarjeta mostrada enmascarada (CardNET)

---

## 🔧 Configuración Necesaria

### En `.env`
```
CARDNET_TYPE=5440          # Merchant Type
CARDNET_NUMBER=349011300   # Merchant Number
CARDNET_TERMINAL=00567856  # Terminal Number
CARDNET_WEBSERVICE=https://labservicios.cardnet.com.do  # QA
# CARDNET_WEBSERVICE=https://ecommerce.cardnet.com.do  # Producción
APP_NAME=PROYES BOLETERIA
```

### En `config/cons.php`
```php
'cardnet' => [
    'type'       => env('CARDNET_TYPE'),
    'number'     => env('CARDNET_NUMBER'),
    'terminal'   => env('CARDNET_TERMINAL'),
    'webservice' => env('CARDNET_WEBSERVICE'),
]
```

---

## 📝 Datos de Prueba

### Transacción Aprobada
- Tarjeta: 4111 1111 1111 1111
- Expiración: 12/25
- CVV: 123

### Transacción Rechazada
- Tarjeta: 5555 5555 5555 5557
- Expiración: 12/25
- CVV: 000

---

## 🚀 Testing

### Flujo Completo
1. Seleccionar evento y boletas
2. Confirmar compra
3. Si monto > 0:
   - Seleccionar "Pago con Tarjeta"
   - Completar datos
   - Ser redirigido a CardNET
   - Aceptar o rechazar transacción
   - Retornar a la app
4. Si monto = 0:
   - Seleccionar "Pago con Tarjeta"
   - Completar datos
   - Completar orden directamente (sin CardNET)

### Códigos de Respuesta
- ResponseCode: 00 → ✅ Aprobada
- ResponseCode: 04 → ❌ Rechazada
- ResponseCode: 33 → ❌ Tarjeta expirada

---

## 📚 Referencias

- **CardNET Docs**: /docs/CardNET.pdf
- **Endpoints**: 
  - QA: `https://labservicios.cardnet.com.do`
  - Producción: `https://ecommerce.cardnet.com.do`
- **Métodos**: POST (sesión), POST (pago), GET (verificación)
- **Moneda**: 214 = RD Peso
- **Tipo de Transacción**: 0200 = Normal Sale

---

## 🔍 Notas Importantes

1. **Session Expiration**: Las sesiones expiran a los 30 minutos
2. **Amount Format**: Siempre en centavos (x100) y 12 dígitos
3. **IP Address**: Se registra automáticamente para seguridad
4. **3DSecure**: Campos opcionales pero recomendados para mayor seguridad
5. **Merchand Name**: Máximo 40 caracteres (se trunca si es necesario)

---

## ❓ FAQ

**P: ¿Qué pasa si monto es cero?**  
R: Se salta CardNET y se completa la orden directamente.

**P: ¿Qué significa ResponseCode TF?**  
R: 3DSecure Authentication Failed - El usuario no completó la verificación 3D Secure.

**P: ¿Cómo cambiar de QA a Producción?**  
R: Cambiar CARDNET_WEBSERVICE en .env de `labservicios` a `ecommerce`

**P: ¿Se puede reintentar un pago fallido?**  
R: Sí, el usuario puede volver a intentar desde la misma mesa de compra.

---

**Estado**: ✅ Completado  
**Próximos Pasos**: Pruebas en QA, validar con CardNET, deploy a producción
