Student Registration API — Vital4Female

Documentación técnica para integrar el endpoint de registro de estudiantes desde cualquier cliente externo (Vue.js, React, mobile, etc.).


Endpoint

POST /api/v1/students
Content-Type: application/json
Accept: application/json

Request Body

Campo Tipo Requerido Descripción
firstname string Nombre del estudiante (max 100 chars)
lastname string Apellido del estudiante (max 100 chars)
email string Email único — se usa como login
birthday date Fecha de nacimiento (YYYY-MM-DD)
product_id integer ID del producto/membresía seleccionado
membership string Nombre de la membresía
payment_method string Método de pago (ideal, ideal-qr, etc.)
discount_code string Código de descuento (opcional)

Ejemplo de request

{
  "firstname": "Anna",
  "lastname": "Jansen",
  "email": "anna.jansen@example.com",
  "birthday": "1990-05-14",
  "product_id": 3,
  "membership": "Founder Membership",
  "payment_method": "ideal",
  "discount_code": "Founder40"
}

Responses

✅ 201 — Registro exitoso

{
  "success": true,
  "data": {
    "payment_url": "https://app.paypro.nl/betalen/abc123",
    "payment_hash": "sub_AbCdEfGh12345",
    "order_id": 42,
    "user_id": 88
  },
  "message": "Student registered successfully."
}

Acción esperada del cliente: redirigir al usuario a data.payment_url para completar el pago en PayPro. Si payment_url es la URL base de la app, significa que el producto era gratuito y no requiere pago.


❌ 422 — Error de validación o negocio

{
  "success": false,
  "message": "The email has already been taken.",
  "data": {
    "email": ["The email has already been taken."]
  }
}

Posibles causas: - Email ya registrado - Código de descuento inválido, expirado o sin usos disponibles - Campo requerido faltante o con formato incorrecto


❌ 429 — Rate limit excedido

{
  "success": false,
  "message": "Too Many Attempts."
}

❌ 500 — Error interno

{
  "success": false,
  "message": "Registration failed. Please try again."
}

Los detalles del error se loggean internamente. No se exponen al cliente por seguridad.


Flujo completo

Cliente                    API Laravel              PayPro
  │                            │                      │
  │── POST /api/v1/students ──>│                      │
  │                            │── Valida datos        │
  │                            │── Valida descuento    │
  │                            │── Crea User           │
  │                            │── Crea Student        │
  │                            │── Crea Order          │
  │                            │── createCustomer() ──>│
  │                            │── createSubscription()│
  │                            │<── payment_url ───────│
  │                            │── Despacha emails (queue)
  │<── 201 { payment_url } ───│                      │
  │                            │                      │
  │── redirect(payment_url) ──────────────────────────>│
  │                            │                      │
  │<──────────── PayPro procesa y llama webhook ───────│

Ejemplo de integración Vue.js (Composition API)

Ver archivo CheckoutForm.vue incluido en esta documentación.

Composable reutilizable

// composables/useStudentRegistration.js
import { ref } from 'vue'

export function useStudentRegistration() {
  const loading = ref(false)
  const errors  = ref({})

  async function register(payload) {
    loading.value = true
    errors.value  = {}

    try {
      const response = await fetch('/api/v1/students', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json', 'Accept': 'application/json' },
        body: JSON.stringify(payload)
      })

      const json = await response.json()

      if (!response.ok) {
        if (response.status === 422 && json.data) {
          errors.value = json.data  // errores de validación por campo
        }
        throw new Error(json.message || 'Registration failed')
      }

      return json.data  // { payment_url, payment_hash, order_id, user_id }

    } finally {
      loading.value = false
    }
  }

  return { loading, errors, register }
}

Validación de código de descuento

El blade actual llama a un endpoint separado para validar el descuento antes del submit. Este endpoint debe seguir respondiendo:

GET /api/checkdiscounts/{code}
{
  "valid": true,
  "message": "Código válido",
  "amount": 40
}

Configuración de Rate Limiting

En app/Http/Kernel.php o bootstrap/app.php (Laravel 11+), el endpoint ya tiene rate limiting por email + IP con máximo 5 intentos por minuto. Para ajustarlo, modificar las constantes en LoginController:

private const MAX_ATTEMPTS  = 5;
private const DECAY_SECONDS = 60;

Variables de entorno requeridas

PAYPRO_API_KEY=your_key_here
PAYPRO_MODE=TEST                     # o LIVE
APP_URL=https://admin.vital4female.nl
QUEUE_CONNECTION=redis               # recomendado para producción

Cola de trabajos (Queue Workers)

Los emails se despachan de forma asíncrona. En producción levantar al menos un worker:

php artisan queue:work redis --queue=default --tries=3 --timeout=60

Para múltiples workers con Supervisor:

[program:v4f-worker]
command=php /var/www/artisan queue:work redis --tries=3 --timeout=60
numprocs=4
autostart=true
autorestart=true

Webhook de PayPro

Cuando PayPro confirma el pago, llama al webhook configurado. Este endpoint debe actualizar el student_status y order_status a Actief.

POST /webhook/payments

Este flujo es independiente del registro — el registro siempre devuelve pending.