# Pagos

Habilita los pagos con Stripe, Shopify o WhatsApp.

### Sintaxis

```
$ vela enable payments [--provider <stripe|shopify|whatsapp>]
```

Sin `--provider` se te pide que elijas uno. Después se te piden las claves del proveedor, y las que ya están en tu `.env` se conservan sin preguntar. Fuera de una terminal (CI, scripts) la opción es obligatoria, y también las claves: ponlas antes en `.env` o en el entorno.

Un proyecto usa un solo proveedor de pagos. Los patrones que se apoyan en los pagos, como [ecommerce](/es/docs/enable/ecommerce), lo siguen.

### Stripe

```
$ vela enable payments --provider stripe
```

> [Depende de: backend](/es/docs/enable/backend) · [Depende de: workflows](/es/docs/workflows)

| Variable | Descripción |
| --- | --- |
| `STRIPE_SECRET_KEY` | La clave secreta (`sk_...`) con la que el servidor llama a la API de Stripe. |
| `PUBLIC_STRIPE_PUBLISHABLE_KEY` | La clave publicable (`pk_...`) que Stripe.js carga en el navegador. |
| `STRIPE_WEBHOOK_SECRET` | El secreto de firma (`whsec_...`) con el que `/webhooks/stripe` verifica los eventos. `stripe listen --forward-to localhost:5173/webhooks/stripe` imprime uno para el desarrollo local. |

También se te pide un `price_id` para la página de demostración en `/payment`; si lo dejas vacío se crea un producto de prueba.

El módulo de pagos crea las tablas de base de datos y el código necesarios para el procesamiento de pagos, los eventos de webhook, los recibos, los barridos y más.

#### Tablas de la base de datos

El módulo de pagos crea las siguientes tablas de base de datos:

```
stripe_products
stripe_prices
```

#### Webhooks

Los webhooks de Stripe se generan en `src/routes/webhooks/stripe`, con el evento de intención de pago completada incluido por defecto.

#### Clientes

> [Depende de: auth](/es/docs/enable/auth)

Si la autenticación está habilitada con el comando `vela enable auth`, se crea un cliente de Stripe para cada usuario en el registro y los métodos de pago del usuario se pueden guardar y gestionar en la página de configuración de facturación del usuario. El cliente lo vincula el [workflow](/es/docs/workflows) `link-stripe-customer`, iniciado por la acción de registro con el id del usuario como clave de idempotencia, así que registrarse nunca espera a Stripe y un reintento nunca crea un segundo cliente. La página de facturación muestra un estado de preparación durante el instante que tarda.

#### Tablas de la base de datos

Estas tablas adicionales se crean si la autenticación está habilitada:

```
stripe_customers
stripe_payment_methods
transactions
```

#### Webhooks

Además del evento de intención de pago completada, el evento de método de pago adjuntado se incluye por defecto para el usuario autenticado.

#### Workflows

El trabajo de Stripe que no pertenece al camino de la petición se ejecuta como [workflows](/es/docs/workflows) en `src/lib/workflows`:

- `stripe-event` - La ruta del webhook verifica la firma de cada evento y encola una ejecución por id de evento. La ejecución llama al manejador de `src/routes/webhooks/stripe/handlers/dispatch.ts`; un manejador que lanza un error se reintenta con espera creciente, y un reenvío del mismo evento cae en la ejecución que ya existe.
- `sync-stripe-products` y `sync-stripe-prices` - Recurrentes cada diez minutos en `sync-stripe-catalog.ts`, añadiendo los productos y precios nuevos en Stripe y actualizando el resto.
- `sync-stripe-customers` - Recurrente cada diez minutos cuando la autenticación está habilitada, vinculando a cualquier usuario que no haya pasado por el registro.

Todas las ejecuciones, con sus pasos y cualquier error, aparecen en **Workflows**, en el panel de PocketBase.

### Shopify

```
$ vela enable payments --provider shopify
```

| Variable | Descripción |
| --- | --- |
| `PUBLIC_SHOPIFY_STORE_DOMAIN` | El dominio `*.myshopify.com` de la tienda. |
| `PUBLIC_SHOPIFY_STOREFRONT_TOKEN` | Un token de acceso público de la Storefront API. En el panel de Shopify, añade el canal de ventas Headless, crea una tienda y copia su token de acceso público. |

Ambas son públicas: la Storefront API se llama desde el navegador con un token hecho para ello. Shopify guarda los productos, los clientes y los pedidos, así que no necesita backend y funciona en un sitio estático.

- `src/lib/shopify.ts` - El cliente de la Storefront API, de `@shopify/storefront-api-client` de Shopify, fijado a la versión `2026-10` de la API. Shopify mantiene cada versión durante un año; adelanta `SHOPIFY_API_VERSION` antes de que termine.
- `src/lib/components/payments/buy-button.svelte` - Un botón de compra que crea un carrito de una línea y lleva al comprador al checkout de Shopify.

Se te pide una variante de producto (`gid://shopify/ProductVariant/...`) para una página de demostración en `/payment`; déjala vacía para omitir la página.

### WhatsApp

```
$ vela enable payments --provider whatsapp
```

En el sitio no se paga nada. El cliente elige lo que quiere, y un pedido abre un chat de WhatsApp con el negocio, con el pedido ya escrito. El negocio lo confirma, cobra y organiza la entrega en el chat. El mismo pedido se registra en segundo plano en un formulario alojado en velastack.dev, así que se conserva aunque el cliente nunca envíe el mensaje.

| Variable | Descripción |
| --- | --- |
| `PUBLIC_WHATSAPP_NUMBER` | El número de WhatsApp del negocio, con el código de país, con el que los pedidos abren un chat. |
| `PUBLIC_WHATSAPP_ORDER_FORM` | El formulario alojado en el que se registra cada pedido. Déjalo vacío para no registrar nada. |

WhatsApp necesita la moneda del negocio (consulta [Monedas](#monedas)): todos los precios de WhatsApp están en ella, el botón, el carrito, el mensaje y el pedido registrado.

Cuando el proyecto está vinculado a velastack.dev y has iniciado sesión, el comando crea el formulario `order` del proyecto y rellena `PUBLIC_WHATSAPP_ORDER_FORM` por sí mismo, así que solo se te pide el número. Los pedidos registrados están en los formularios del proyecto en velastack.dev, cada uno con su referencia, el cliente, las líneas y el mensaje tal como se habría enviado. Como Shopify, no necesita backend y funciona en un sitio estático.

- `src/lib/order.ts` - `orderMessage(order)`, el mensaje que recibe el negocio; cámbialo como quieras. `placeOrder` registra el pedido y abre el chat.
- `src/lib/components/payments/order-button.svelte` - Un botón para pedir por WhatsApp: `<OrderButton name="Concha" price={12} />`. Pide un nombre, un teléfono y una nota opcional, y después abre el chat. El precio es opcional; omítelo para dar el precio en el chat.

Cada pedido tiene una referencia corta, como `A7K-3Q2`, que aparece tanto en el mensaje como en el pedido registrado, para poder relacionar un chat con su registro. Los enlaces, las referencias y el registro vienen de [`@velastack/whatsapp`](https://github.com/velastack/whatsapp).

### Monedas

Todos los proveedores preguntan tres cosas sobre el negocio y las guardan en `src/lib/site.ts`, junto al nombre del sitio (o pasa `--currency`, `--country` y `--currencies`; un valor que el archivo ya tiene se conserva):

- `currency` - La moneda en la que el negocio pone sus precios y cobra, ISO 4217, como `MXN`. WhatsApp no puede recibir pedidos sin ella.
- `country` - El país del negocio, ISO 3166-1, como `MX`. Los precios se formatean en el idioma de la página para ese país: un `es` a secas imprimiría `360,00 MXN`, `es-MX` imprime `$360.00`, y una página en inglés del mismo sitio imprime `MX$360.00`.
- `currencies` - Otras monedas en las que el sitio muestra los precios, separadas por comas. En blanco significa solo la moneda del negocio, y nada de lo que sigue aparece.

Con una segunda moneda en la lista, un selector de moneda aparece en la barra de navegación junto al selector de idioma, y la elección se guarda en una cookie durante un año. En un sitio con servidor, un visitante que no ha elegido empieza en la moneda de su región cuando el sitio la ofrece, leída de la cabecera de país del CDN (Cloudflare, Vercel, CloudFront) o del idioma del navegador; un sitio estático empieza en la moneda del negocio. Un precio mostrado en otra moneda es una estimación al tipo de cambio del día, marcada con `≈`; lo que el cliente paga es el precio en la moneda del negocio.

Cuando el propio proveedor tiene un precio en la moneda del visitante, la página muestra ese, exacto y sin marca. El importe fijo de un precio de Stripe en otra moneda (`currency_options`, definido en el panel y sincronizado con el catálogo) se muestra y, cuando todos los precios del carrito lo tienen, se cobra; si no, Stripe cobra en la moneda del precio salvo que Adaptive Pricing esté activo en tu panel. Una tienda Shopify que vende en esa moneda a través de Shopify Markets pone precio al catálogo, al carrito y al checkout en ella. En WhatsApp el mensaje añade la estimación del cliente en una línea, y el negocio cotiza en su moneda.

Los tipos de cambio son tipos de referencia de bancos centrales, combinados por [Frankfurter](https://frankfurter.dev) y publicados una vez al día en `velastack.dev/v1/rates`, que todos los sitios leen: la respuesta nombra las instituciones detrás de cada tipo. Un sitio los descarga en el navegador una vez al día; sin ellos, los precios se quedan en la moneda del negocio.

- `src/lib/currency.svelte.ts` - La moneda en la que se muestran los precios, los tipos de cambio, `convert` y `format`. `formatPrice(amount)` es lo que usan las páginas generadas.
- `src/lib/components/currency-select.svelte` - El selector. No muestra nada hasta que `currencies` lista una segunda moneda.
- `src/lib/currency.server.ts` - Con servidor: `handleCurrency` decide la moneda de cada visitante (su elección, si no la de su región, si no la del negocio) en `locals.currency`, y el layout raíz se la pasa al almacén junto con los tipos del día, así que el primer render ya la muestra. Un sitio estático no lo tiene y aplica la elección del visitante tras cargar.

Un proyecto que habilitó los pagos antes de esto no tiene `country` ni `currencies`: añádelos a mano en `src/lib/site.ts`, porque volver a ejecutar `vela enable payments` rehace la configuración del proveedor.

### Cambiar de proveedor

Ejecuta [`vela disable payments`](/es/docs/disable/payments) y vuelve a habilitar los pagos con el otro `--provider`. Habilitar un segundo proveedor encima del primero se rechaza, porque los patrones que se apoyan en los pagos ya no sabrían cuál usa el proyecto.