# Checkout por compra

Genera una sesión de checkout por cada pedido sobre un link existente.

> [!WARNING]
> El SDK `vexor` de npm (Stripe, MercadoPago, PayPal, Square, Talo) está **deprecado**: ya no se mantiene y no debe usarse. vexorpay es hoy una **API REST de links de pago en USD, sin SDK oficial**. Migración: <https://developers.vexorpay.com/docs/migracion-desde-el-sdk>

Fuente: <https://developers.staging.vexorpay.com/docs/api/checkout>

Genera una **sesión de checkout** sobre un link existente y te devuelve un `checkoutUrl` listo para redirigir al cliente.

Es el caso de uso "link reutilizable que se paga N veces": cada llamada crea una sesión nueva, con el email del comprador precargado y tu referencia del pedido. El cobro se hace en tu cuenta de cobro y el comprador recibe el recibo por email. La venta aparece como [orden](/docs/api/orders) cuando el comprador paga, y se dispara el webhook `payment.completed`.

> En un link **`one_time`** el checkout cobra el monto del link. En un link **`recurring`** lleva directo al plan indicado con `price_id` (o al default), sin pasar por el selector de planes de la página pública `url` (`/p/{slug}`). Ver [Suscripciones](/docs/api/subscriptions).

## Request

`POST /api/v1/payment-links/{id}/checkout`

### Headers

| Header | Valor |
| --- | --- |
| `Authorization` | `Bearer vxp_live_tu_clave` |
| `Content-Type` | `application/json` |
| `Vexor-Account` | opcional — id del usuario conectado, para cobrar un link suyo ([cuentas conectadas](/docs/api/connected-accounts)) |

`Idempotency-Key` no aplica: cada llamada crea una sesión de checkout nueva.

### Body

| Campo | Tipo | Requerido | Descripción |
| --- | --- | --- | --- |
| `reference` | string | no | Tu referencia del pedido (p. ej. `ORD-1001`, hasta 200 caracteres). Si se omite, se genera una única (`VXP-…`). Vuelve en la respuesta, en la venta (`reference` de [`GET /orders`](/docs/api/orders)), en sus webhooks y en `/events`. |
| `customer_email` | string | no | Email del comprador (precarga el checkout). |
| `metadata` | object | no | Pares clave/valor del pedido. Sólo valores planos: texto, número o booleano. Vuelven en la `metadata` de la venta. |
| `price_id` | string | no | Solo para links `recurring`: id del plan (`prices[].id`) al que se suscribe el comprador. Ausente = plan default (`isDefault` o el primero). En un link `one_time` responde `400 validation_error`. |

### Ejemplo

**curl**

```bash
curl https://api.vexorpay.com/api/v1/payment-links/66f1c1e2c8a4b0d1f2e3a4b5/checkout \
  -X POST \
  -H "Authorization: Bearer vxp_live_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{
    "reference": "ORD-1001",
    "customer_email": "cliente@mail.com",
    "metadata": { "userId": "u_42" }
  }'
```

**Node.js**

```js
const res = await fetch(
  'https://api.vexorpay.com/api/v1/payment-links/66f1c1e2c8a4b0d1f2e3a4b5/checkout',
  {
    method: 'POST',
    headers: {
      Authorization: 'Bearer vxp_live_tu_clave',
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      reference: 'ORD-1001',
      customer_email: 'cliente@mail.com',
      metadata: { userId: 'u_42' },
    }),
  },
)

const data = await res.json()
```

**PHP**

```php
<?php

$client = curl_init(
  'https://api.vexorpay.com/api/v1/payment-links/66f1c1e2c8a4b0d1f2e3a4b5/checkout',
);
curl_setopt_array($client, [
  CURLOPT_POST => true,
  CURLOPT_RETURNTRANSFER => true,
  CURLOPT_HTTPHEADER => [
    'Authorization: Bearer vxp_live_tu_clave',
    'Content-Type: application/json',
  ],
  CURLOPT_POSTFIELDS => json_encode([
    'reference' => 'ORD-1001',
    'customer_email' => 'cliente@mail.com',
    'metadata' => ['userId' => 'u_42'],
  ]),
]);

$data = json_decode(curl_exec($client), true);
```

**Python**

```python

response = requests.post(
    'https://api.vexorpay.com/api/v1/payment-links/66f1c1e2c8a4b0d1f2e3a4b5/checkout',
    headers={
        'Authorization': 'Bearer vxp_live_tu_clave',
        'Content-Type': 'application/json',
    },
    json={
        'reference': 'ORD-1001',
        'customer_email': 'cliente@mail.com',
        'metadata': {'userId': 'u_42'},
    },
)
data = response.json()
```

**Go**

```go
body, _ := json.Marshal(map[string]any{
    "reference":      "ORD-1001",
    "customer_email": "cliente@mail.com",
    "metadata":       map[string]any{"userId": "u_42"},
})

req, _ := http.NewRequest("POST", "https://api.vexorpay.com/api/v1/payment-links/66f1c1e2c8a4b0d1f2e3a4b5/checkout", bytes.NewReader(body))
req.Header.Set("Authorization", "Bearer vxp_live_tu_clave")
req.Header.Set("Content-Type", "application/json")

res, _ := http.DefaultClient.Do(req)
defer res.Body.Close()
```

### Respuesta exitosa

`200 OK`

```json
{
  "checkoutId": "ch_4f8a2c9d1e7b",
  "reference": "ORD-1001",
  "price_id": null,
  "checkoutUrl": "https://…",
  "expiresAt": null
}
```

Redirige al cliente a `checkoutUrl` (si `expiresAt` no es `null`, antes de esa fecha). `reference` es la tuya o la generada; `checkoutId` vuelve en la venta (`checkoutId` de [`GET /api/v1/orders`](/docs/api/orders)). `price_id` es `null` en links de pago único. En los pagos únicos el comprador puede pagar en su moneda local o en USD.

### Links recurrentes: checkout directo a un plan

En un link `recurring`, manda el `price_id` del plan (o nada para el default):

```bash
curl https://api.vexorpay.com/api/v1/payment-links/66f1c1e2c8a4b0d1f2e3a4b5/checkout \
  -X POST \
  -H "Authorization: Bearer vxp_live_tu_clave" \
  -H "Content-Type: application/json" \
  -d '{ "price_id": "66f1c1e2c8a4b0d1f2e3a4c1" }'
```

```json
{
  "checkoutId": "ch_9b3e7a1c5d2f",
  "reference": "VXP-7KQ4M9TZ2HXA",
  "price_id": "66f1c1e2c8a4b0d1f2e3a4c1",
  "checkoutUrl": "https://…",
  "expiresAt": null
}
```

Cuando el comprador paga, se crea la suscripción y se cobra cada ciclo en USD (webhooks `subscription.created` y `payment.completed` por cada cobro; ver [Suscripciones](/docs/api/subscriptions)).

## Errores

| Error | HTTP | Causa |
| --- | --- | --- |
| `link_not_found` | 404 | No existe ese link de pago, no está activo o es de otro modo (prueba/producción). |
| `price_not_found` | 404 | `price_id` no existe o no pertenece a ese link recurrente. |
| `validation_error` | 400 | `price_id` en un link de pago único, u otro dato inválido. |
| `invalid_request` | 400 | El body no es JSON válido. |
| `prices_required` | 400 | El link recurrente no tiene planes. |
| `payment_account_not_ready` | 409 | La cuenta de cobro del dueño del link no está activada (o fue rechazada). |
| `upstream_error` | 502 | El procesador de pagos no creó el checkout. Reintenta en unos minutos. |
