7. Pagos
payment en la base de datos, por lo que la pantalla /cta muestra el mensaje "No hay componentes de pago creados". Esta sección documenta la funcionalidad a partir del código fuente y de cómo se ven las pantallas cuando sí hay datos: la captura incluida (01-listado.png) refleja precisamente ese estado vacío.
Introducción
Los pagos se gestionan a través de componentes de tipo payment, integrados con Stripe. Aquí verás cómo se listan, se reembolsan y cómo funciona la integración con el proveedor de pagos.
Un componente de pago combina un formulario de checkout con la lógica de cobro: el visitante introduce sus datos de tarjeta (o usa métodos express como Apple Pay / Google Pay), Stripe procesa el cargo, y el panel registra el resultado mediante un webhook que actualiza el estado del pago en la base de datos interna.
Acceso rápido
- URL principal:
/cta— listado de componentes de pago y sus transacciones. - Menú lateral: módulos Comunicación o Sitio Web → entrada Pagos / CTA.
- Crear:
/components/createcontype = payment(ver sección 4).
/payments devuelve 404 en este panel. La URL canónica para ver los pagos es /cta (misma ruta que comparten los formularios tipo form, ver sección 6). Si tu bookmark apunta a /payments, actualízalo.
Pantalla principal (listado)
/cta en estado vacío. El panel muestra el mensaje "No hay componentes de pago creados" porque en este despliegue no hay componentes de tipo payment en la base de datos.Cuando sí hay componentes de pago definidos, esta misma pantalla muestra una tabla con, para cada componente payment:
- Nombre del componente: etiqueta del componente que actúa como checkout.
- Total cobrado: suma de los importes confirmados.
- Total reembolsado: suma de los importes devueltos.
- Último pago: fecha del pago confirmado más reciente.
- Acciones: ver pagos, reembolsar, editar el componente.
Tareas habituales
7.1 Ver el listado de pagos
Una vez creado un componente de pago y recibida la primera transacción confirmada, el panel empieza a mostrar pagos en /cta:
- Entra en
/cta. - Verás una fila por cada componente de tipo
paymentcon los totales agregados (cobrado, reembolsado, último pago). - Pulsa sobre un componente para abrir el detalle con la lista completa de transacciones.
El listado detallado de transacciones (ruta /components/{id}/payments) muestra, para cada pago:
- ID interno y ID de Stripe (prefijo
pi_och_). - Importe y moneda.
- Estado:
pending,succeeded,failed,refunded,partially_refunded. - Email del pagador (si se solicitó).
- Fecha de confirmación.
- Acciones: ver detalle, reembolsar total o parcial, descargar recibo.
7.2 Ver el detalle de un pago
Para inspeccionar toda la información de un pago individual:
- Desde el listado (
/components/{id}/payments), pulsa sobre el ID o el botón Ver del pago que te interese. - Se abre la ficha de detalle con:
- Datos de la transacción (importe, moneda, descripción).
- Estado y metadatos que Stripe devuelve (
payment_method,receipt_url,customer, etc.). - Historial de eventos:
created→processing→succeeded, ofailed. - Log del webhook recibido para ese pago (timestamp, payload parcial).
- Desde aquí puedes lanzar un reembolso (ver 7.3) o abrir la ficha de Stripe en una pestaña externa.
7.3 Reembolsar un pago
El panel permite lanzar reembolsos contra Stripe sin salir de la administración:
- Abre el detalle del pago (ver 7.2).
- Pulsa el botón Reembolsar.
- Indica el importe (por defecto se rellena con el total del pago):
- Reembolso total: el importe completo, el estado del pago pasa a
refunded. - Reembolso parcial: un importe menor, el estado pasa a
partially_refundedy se puede lanzar más de un reembolso parcial hasta agotar el total.
- Reembolso total: el importe completo, el estado del pago pasa a
- Opcional: añade un motivo interno (visible solo para administradores) para auditoría.
- Confirma. El panel llama a la API de Stripe (
POST /v1/refunds) y, si Stripe acepta la operación, el estado del pago se actualiza al recibir el webhookcharge.refunded.
Referencia técnica (webmasters)
Integración con Stripe
La integración con Stripe se apoya en tres claves que deben estar configuradas en el .env del servidor:
STRIPE_PUBLIC_KEY=pk_live_xxxxxxxxxxxxxxxxxxxx
STRIPE_SECRET_KEY=sk_live_xxxxxxxxxxxxxxxxxxxx
STRIPE_WEBHOOK_SECRET=whsec_xxxxxxxxxxxxxxxxxxxx
- STRIPE_PUBLIC_KEY — clave pública (live o test según entorno). Se envía al frontend para inicializar Stripe.js o el Payment Element. Es seguro exponerla en HTML/JS.
- STRIPE_SECRET_KEY — clave privada. Solo se usa en el backend (controladores PHP) para crear PaymentIntents, consultar cargos y lanzar reembolsos. Nunca debe aparecer en el frontend.
- STRIPE_WEBHOOK_SECRET — secreto compartido con Stripe al registrar el endpoint del webhook. Permite verificar la firma de cada evento entrante (
Stripe-Signature) para asegurar que el evento viene realmente de Stripe.
Flujo del webhook
Cuando Stripe confirma (o rechaza) un cargo, envía un evento HTTP POST a un endpoint del backend. El panel expone una ruta interna que:
- Verifica la firma del evento con
STRIPE_WEBHOOK_SECRET. Si la firma no encaja, el panel responde400y descarta el evento (esto evita que un atacante pueda inyectar pagos falsos). - Identifica el pago interno a partir del
iddel PaymentIntent o Charge. - Actualiza el estado del pago en la base de datos interna (
pending→succeeded/failed/refunded). - Registra un evento en el log para auditoría (timestamp, tipo de evento, payload parcial).
- Responde
200 OKa Stripe para que no reintente.
La ruta típica del endpoint de webhook es:
POST /api/payments/webhook
Content-Type: application/json
Stripe-Signature: t=...,v1=...
{ "type": "charge.succeeded", "data": { "object": { "id": "ch_..." } } }
Dónde se guardan los pagos
Las transacciones se almacenan en una tabla dedicada (nombre habitual payments o payment_transactions). Cada fila contiene, como mínimo:
- id: identificador interno del pago.
- component_id: FK al componente
paymentque originó el cobro. - stripe_payment_intent_id: ID del PaymentIntent (
pi_...). - stripe_charge_id: ID del cargo (
ch_...). - amount: importe en céntimos.
- currency: código ISO 4217 (
eur,usd, etc.). - status: estado interno mapeado desde el estado de Stripe.
- customer_email / customer_name: datos del pagador si se pidieron.
- refunded_amount: suma de los importes reembolsados hasta el momento.
- created_at / updated_at: marcas temporales.
- metadata: JSON con datos extra devueltos por Stripe (recibos, método de pago, etc.).
Configuración en el panel
El modo de operación (test vs live) se controla habitualmente con:
- Las propias claves del
.env(las clavespk_test_/sk_test_activan el modo test de Stripe). - Una variable adicional
STRIPE_MODE=test|liveque algunas versiones del panel exponen para condicionar comportamientos (por ejemplo, el endpoint del webhook o el logo del checkout).
.env no migra los pagos ya registrados. Los pagos confirmados con claves live siguen siendo reales aunque luego cambies a claves test, y viceversa. Para distinguir pagos de test en producción, fíjate siempre en el prefijo del PaymentIntent (pi_ + identificador alfanumérico) en el dashboard de Stripe.
Rutas internas relevantes
GET /cta— listado general de componentespaymenty agregados.GET /components/{id}/payments— transacciones detalladas de un componente de pago.GET /components/{id}/payments/{pid}— detalle de un pago concreto.POST /components/{id}/payments/{pid}/refund— lanzar un reembolso (total o parcial).POST /api/payments/webhook— endpoint público al que Stripe envía los eventos.POST /api/payments/create-intent— endpoint interno que crea un PaymentIntent en Stripe desde el frontend.
Errores frecuentes
- La pantalla
/ctaaparece vacía: comportamiento esperado en este despliegue, ya que no hay componentespaymentcreados. Para empezar a usar la funcionalidad, crea primero un componente de tipopaymentdesde/components/create(ver 7.1 y sección 4). - La URL
/paymentsda 404: este panel no expone esa ruta. La URL correcta es/cta. Actualiza el bookmark o el enlace guardado. - El pago se confirma en Stripe pero no aparece en el panel: el webhook no llegó o falló la verificación de firma. Comprueba: (1) que la URL del webhook está bien configurada en el dashboard de Stripe, (2) que
STRIPE_WEBHOOK_SECRETen el.envcoincide con el del endpoint, y (3) que el endpoint/api/payments/webhookresponde200 OK(revisa el log del servidor). Si el endpoint está caído, Stripe reintenta durante ~3 días, así que el estado acabará actualizándose cuando se recupere. - El reembolso falla con error "Charge already refunded": ya se había lanzado un reembolso total sobre ese cargo (quizá desde el dashboard de Stripe directamente). Revisa el historial en Stripe antes de reintentar.
- El reembolso queda colgado en estado
pending: el reembolso se creó en Stripe pero el webhookcharge.refundedaún no ha llegado. Espera unos segundos y refresca; si persiste, revisa el endpoint de webhook como en el caso del pago confirmado. - El checkout del frontend no carga:
STRIPE_PUBLIC_KEYno está definida o es incorrecta. Revisa el.envy purga la caché del panel. - Pagos de test cobrados en tarjeta real: estás usando claves
sk_live_por error. Verifica el.envantes de salir a producción.