Modelos de integración de canal
Este documento describe los cuatro modelos contractuales de integración que beTickets soporta para canales de venta de terceros. Cada canal tiene exactamente un modelo configurado por el equipo de beTickets en el dashboard de administración.
Resumen
| Modelo | Nombre técnico | Frontend comprador | Quién cobra | Widget | Endpoint de pago |
|---|---|---|---|---|---|
| A | portal_only | Portal beTickets (branding del canal) | beTickets (Redsys) | ❌ | /portal/{slug}/{lang}/checkout |
| B | portal_external_payment | Portal beTickets (branding del canal) | Canal (TPV propio) | ❌ | POST /confirm (canal→beTickets) |
| C | widget_betickets_payment | Site del canal (widget) → redirect a portal | beTickets (Redsys) | ✅ | /portal/{slug}/{lang}/checkout?order_token=… |
| D | widget_external_payment | Site del canal (widget embebido) | Canal (TPV propio) | ✅ | POST /confirm (canal→beTickets) |
Los modelos se organizan en dos dimensiones:
- Frontend: portal beTickets vs widget embebido en el site del canal
- Cobro: beTickets gestiona el pago (Redsys) vs el canal cobra con su TPV
Modelo A — Portal white-label, cobro beTickets (portal_only)
El canal utiliza el portal de beTickets con su propio branding (logo, colores, dominio via CNAME). beTickets procesa el cobro a través de Redsys.
Flujo de compra
Comprador → portal.canal.com/{slug}/{lang}/events
→ Selecciona sesión y asientos
→ Checkout nativo del portal: nombre, email, datos de pago
→ Pasarela Redsys (gestionada por beTickets)
→ Confirmación: asientos vendidos, tickets enviadosRequisitos técnicos
- CNAME del dominio del canal apuntando a beTickets
- Logo, colores, favicon configurados en el dashboard
- No requiere
widget_keyni integración técnica adicional
Modelo B — Portal white-label, cobro del canal (portal_external_payment)
El canal utiliza el portal de beTickets con su propio branding. El cobro lo gestiona el canal con su TPV. Cuando el pago se confirma, el canal notifica a beTickets.
Flujo de compra
Comprador → portal.canal.com/{slug}/{lang}/events
→ Selecciona sesión y asientos en el portal beTickets
→ Checkout del portal: nombre, email
→ Canal procesa el pago con su pasarela (server-side)
→ Canal notifica a beTickets:
POST https://api.betickets.com/api/sales-channel/v1/{slug}/{lang}/orders/{orderToken}/confirm
Authorization: Bearer {access_token}
{ "transaction_id": "ext_123", "amount": 125.50, "payment_method": "card" }
→ beTickets: asientos vendidos, tickets enviados al compradorRequisitos técnicos
- CNAME del dominio del canal apuntando a beTickets
widget_keygenerada en el dashboard (necesaria para autenticación B2B)- Implementación server-side del endpoint
confirm - No se permite: redirigir al portal de beTickets para pago (lo maneja el canal)
Autenticación B2B
POST /api/sales-channel/v1/auth/tokenconsales_channel_slug+widget_key→ Bearer token- Usar Bearer token en la llamada de confirmación
⚠️ La llamada a
confirmsiempre debe hacerse desde el servidor del canal, nunca desde el navegador del comprador.
Modelo C — Widget + cobro beTickets (widget_betickets_payment)
El canal embebe el widget en su site para la selección de asientos. El pago lo procesa beTickets a través del portal, al que el widget redirige automáticamente.
Flujo de compra
Comprador → site del canal (con widget embebido)
→ Widget: selecciona asientos (crea orden B2B)
→ Widget: botón "Ir al pago" → redirect a:
{portal_checkout_base_url}/{slug}/{lang}/checkout?order_token={token}
→ Portal beTickets con branding del canal
→ Comprador introduce datos y paga (Redsys)
→ Confirmación: asientos vendidos, tickets enviadosRequisitos técnicos
widget_keygenerada en el dashboard- Integración del widget en el site del canal (ver widget quickstart)
- El widget se autoconfigura para redirect; no pasar
checkoutModenicheckoutBaseUrl - No se permite: llamar a
confirm(el pago lo cierra beTickets internamente)
📘 Walkthrough completo: para una guía paso a paso de integración del Modelo C (configuración del canal, embed, aislamiento de CSS, tracking, checklist de producción y troubleshooting), ver Tutorial · Modelo C.
Modelo D — Widget + cobro del canal (widget_external_payment)
El canal embebe el widget en su site. El canal procesa el cobro con su propia pasarela y notifica a beTickets del resultado. beTickets no envía email de entradas — el canal es responsable del envío (sus propios bonos, billetes de tren, entradas PDF, etc.).
Flujo de compra
Comprador → site del canal (con widget embebido)
→ Widget: selecciona asientos y arma carrito
→ Widget emite onOrderUpdate(order) con order_token y total
→ Canal habilita su botón de pago nativo
→ Canal recoge datos del comprador (nombre, email, teléfono)
→ Canal procesa pago con su pasarela (server-side)
→ Canal notifica a beTickets (server-to-server):
POST /api/sales-channel/v1/{slug}/{lang}/orders/{orderToken}/confirm
Authorization: Bearer {access_token}
{
"transaction_id": "tpv_ext_123",
"amount": 125.50,
"payment_method": "card",
"name": "Ana",
"surname": "García",
"email": "ana@example.com",
"phone": "600000000"
}
→ beTickets: asientos → sold, genera tickets PDF, responde con URLs firmadas (7 días):
{
"order_id": 123,
"status": "completed",
"ticket_pdf_urls": [
{ "order_item_id": 1, "ticket_code": "TK-XXXX", "url": "https://s3.../ticket.pdf" }
]
}
→ Canal descarga los PDFs e incluye las entradas en su propio email al comprador
→ Canal llama instance.notifyPaymentComplete() para limpiar el widgetCuerpo de la petición confirm (modelo D)
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
transaction_id | string | ✅ | Identificador de la transacción en la pasarela del canal |
amount | number | ✅ | Importe cobrado; debe coincidir con order.total_amount (±0.01 €) |
payment_method | string | No | Método de pago (ej. "card", "bizum") |
name | string | ✅ | Nombre del comprador |
surname | string | ✅ | Apellidos del comprador |
email | string | ✅ | Email del comprador (beTickets crea o actualiza el Customer) |
phone | string | No | Teléfono del comprador |
accept_marketing | boolean | No | Consentimiento de marketing |
Respuesta confirm (modelo D)
{
"success": true,
"message": "Pago confirmado correctamente",
"data": {
"order_id": 123,
"status": "completed",
"ticket_pdf_urls": [
{
"order_item_id": 45,
"ticket_code": "TK-ABCD-1234",
"url": "https://tickets-bucket.s3.eu-west-1.amazonaws.com/...?X-Amz-Expires=604800"
}
]
}
}Las URLs de ticket_pdf_urls son presignadas con validez de 7 días. El canal debe descargar los PDFs e incluirlos en su propio email al comprador. beTickets no envía ningún email en este modelo.
Requisitos técnicos
widget_keygenerada en el dashboard- Integración del widget en el site del canal (ver widget quickstart)
- Implementación server-side del endpoint
confirmcon los datos del comprador - No se permite: redirigir al portal de beTickets para el pago
Autenticación B2B
POST /api/sales-channel/v1/auth/tokenconsales_channel_slug+widget_key→ Bearer token- Usar Bearer token en la llamada de confirmación
⚠️ La llamada a
confirmsiempre debe hacerse desde el servidor del canal, nunca desde el navegador del comprador.
Capacidades por modelo
| Endpoint / Capacidad | Modelo A | Modelo B | Modelo C | Modelo D |
|---|---|---|---|---|
POST /portal/{slug}/{lang}/checkout | ✅ | ✅ | ✅ | ❌ 403 |
POST /sales-channel/v1/.../orders (crear orden B2B) | ❌ 403 | ❌ 403 | ✅ | ✅ |
POST /sales-channel/v1/.../orders/{token}/confirm | ❌ 403 | ✅ | ❌ 403 | ✅ |
GET /sales-channel/v1/{slug}/config | ❌ | ✅ | ✅ | ✅ |
| Widget embebido | ❌ | ❌ | ✅ | ✅ |
| Botón checkout en widget | — | — | ✅ | ❌ (oculto) |
Shadow mode: con
enforcement_enabled=false, los endpoints inválidos para el modelo generan un log de warning sin rechazar la petición. Permite detectar canales mal configurados antes de activar el enforcement estricto.
Configurar el modelo en el dashboard
- Ir a Administración → Canales de venta → [canal] → Configuración
- En la sección "Modelo de integración", seleccionar el modelo correspondiente al acuerdo contractual
- Guardar los cambios
El modelo se aplica a todos los eventos del canal. No es posible override por evento en v1.
Leer el modelo desde la API
El endpoint GET /api/sales-channel/v1/{slug}/config devuelve los campos de integración:
{
"data": {
"channel_slug": "mi-canal",
"integration_mode": "widget_external_payment",
"payment_handler": "channel",
"allows_widget": true,
"requires_external_payment_callback": true,
"redirects_to_betickets_checkout": false,
"portal_checkout_base_url": "https://tickets.bticketing.com"
}
}El widget consume este endpoint al arrancar y se autoconfigura según integration_mode. El integrador no debe sobreescribir checkoutMode ni checkoutBaseUrl.
Migraciones de datos
Todos los canales existentes sin configuración de integration_mode tienen asignado el modelo A por defecto. Para reclasificar canales, usar el comando de auditoría:
php artisan sales-channels:audit-integration-modes
php artisan sales-channels:audit-integration-modes --export # genera CSV