# XHOCK · Flujo de compra de tickets

PHP puro, sin Composer, sin `vendor/`, **sin escribir en disco**. Subes la carpeta
y funciona. Zapier lleva el control de las órdenes.

## Flujo

```
index.html (modal)
   │  POST FormData
   ▼
api/checkout.php ──► valida ──► ZAPIER_LEAD_URL   (lead, aún sin pagar)
   │  crea Checkout Session: price y quantity del servidor,
   │  datos del pedido en metadata
   ▼
checkout.stripe.com
   │  paga
   ├──────────────► Stripe ──► trigger nativo de Zapier  (pago confirmado)
   ▼  redirect
gracias.php?session_id=cs_xxx ──► resumen real del pedido
```

Reglas que sostienen todo:

- **El precio nunca viene del navegador.** Sale de `STRIPE_PRICE_ID` en `.env`.
- **La cantidad se recalcula** contando las fichas de jugador, no del input.
- **En la URL sólo viaja `session_id`.** Nombre, monto y jugadores se resuelven
  contra Stripe en el servidor. Nada del resumen es falsificable desde la barra
  de direcciones.
- **Sin estado local.** El pedido vive en la `metadata` de la Checkout Session y
  en tu hoja de Zapier. No hay base de datos ni archivos que respaldar.

## Dónde viven los datos del pedido

`api/checkout.php` escribe esto en la metadata de la sesión:

| Clave | Ejemplo |
|---|---|
| `order_id` | `KOA-20260821-b08e0789` |
| `first_name`, `last_name`, `phone`, `country` | — |
| `ticket_type` | `Player Ticket` |
| `player_1` … `player_N` | `Ana Rivera\|27\|Left Wing\|S` |

Límites de Stripe: 50 claves, 500 caracteres por valor. Con `MAX_TICKETS=10`
usas 16 claves de ~110 caracteres. Si algún día subes el tope por encima de ~40
tickets, hay que repensarlo.

Bonus: esa metadata se ve en el dashboard de Stripe, dentro de cada pago. Tu
equipo puede consultar los jugadores desde ahí sin salir de Stripe.

## Archivos

| Archivo | Qué hace |
|---|---|
| `api/checkout.php` | Valida el form, avisa a Zapier, crea la sesión de Stripe |
| `api/order-status.php` | JSON de estado; lo consulta gracias.php si el pago tarda |
| `lib/bootstrap.php` | Carga `.env`, elige claves test/live, helpers |
| `lib/stripe.php` | Cliente REST de Stripe por cURL |
| `lib/support.php` | POST a Zapier, ids de referencia, pack/unpack de jugadores |
| `lib/summary.php` | Convierte un `session_id` en el resumen que ve el comprador |
| `gracias.php` | Página de gracias (sustituye a `gracias.html`) |

## Puesta en marcha

### 1. `.env`

```bash
cp .env.example .env    # y rellena los valores
chmod 600 .env
```

`APP_ENV=test` usa las claves `*_TEST`; `APP_ENV=live` usa las `*_LIVE`.
`bootstrap.php` verifica que la clave empiece por `sk_test_`/`sk_live_` según el
entorno, así que no se puede cobrar de verdad por accidente.

Pon `APP_URL=https://tudominio.com/ruta-de-la-landing` en producción (sin slash
final). En local puede quedar vacío: se deduce del request.

### 2. Zapier

**Zap 1 — lead (catch hook).** Ya configurado en `ZAPIER_LEAD_URL`. Se dispara
al enviar el formulario, antes de pagar. Campos útiles: `order_id`, contacto,
`quantity`, `players` (array) y `players_summary` (una línea de texto plana, para
no necesitar un paso de código).

**Zap 2 — pago confirmado (trigger nativo de Stripe).** En Zapier, app *Stripe*,
trigger de pago o de checkout completado. Trae `metadata.order_id`.

Usa `order_id` como clave para cruzar los dos Zaps: *Lookup Row* por `order_id`
→ *Update Row* con `status: paid`. Así una sola fila por pedido pasa de
pendiente a pagada.

No dupliques la notificación: si algún día activas también un catch hook para
pagos, tu equipo contactaría dos veces al mismo comprador.

### 3. Proteger `.env` y `lib/`

Apache / LiteSpeed (Hostinger): ya viene resuelto en `.htaccess`.

Nginx: añade al server block —

```nginx
location ~ /\.env      { deny all; }
location ~ ^.*/lib/    { deny all; }
```

Comprueba que funciona: `curl https://tudominio.com/ruta/.env` debe dar 403/404,
nunca el contenido.

## Probar en local

```bash
php -S 127.0.0.1:8788 -t .
```

1. Abre `http://127.0.0.1:8788/index.html`, llena el modal y envía.
2. Paga con la tarjeta de prueba `4242 4242 4242 4242`, cualquier fecha futura,
   cualquier CVC.
3. Deberías aterrizar en `gracias.php?session_id=cs_test_...` con el resumen
   completo: referencia, cantidad, total y los jugadores.

## Pasar a producción

1. Crea el producto y el precio en modo **live** (los ids de test no sirven).
2. Rellena `STRIPE_SECRET_KEY_LIVE` y `STRIPE_PRICE_ID_LIVE`.
3. `APP_ENV=live`, `APP_DEBUG=false`, `APP_URL` con el dominio real.
4. Conecta el Zap 2 a tu cuenta de Stripe en modo live.
5. Compra de prueba real (y reembólsala) para confirmar la cadena completa.

## Cosas que ya están cubiertas

- Doble clic en "Continue to Payment" → misma sesión de Stripe (idempotency key).
- Checkout cancelado → vuelve al modal con los datos puestos, sin cobro.
- Pago aún procesándose → la página muestra "confirmando" y se actualiza sola.
- Bots → honeypot invisible en el formulario.
- URL de gracias compartida → deja de mostrar el pedido tras
  `ORDER_SUMMARY_TTL_HOURS`, y el email va enmascarado.
- Sin JavaScript → el form hace POST nativo y el flujo funciona igual.
