> ## Documentation Index
> Fetch the complete documentation index at: https://docs.quentli.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Sesiones de inscripción

> Aprende a recolectar métodos de pago sin realizar un cobro usando sesiones de inscripción

<Warning>
  **Característica experimental**: Las sesiones de inscripción están en fase beta y pueden estar sujetas a cambios. Si planeas usarlas en producción, te recomendamos ponerte en contacto con nosotros en [soporte@quentli.com](mailto:soporte@quentli.com).
</Warning>

Las sesiones de inscripción te permiten recolectar métodos de pago de tus clientes sin realizar un cobro inmediato. Esto es útil cuando necesitas guardar un método de pago para cargos futuros, suscripciones, o simplemente para tener una forma de pago registrada en tu sistema.

A diferencia de las *sesiones de pago* que requieren un monto y generan un pago, las sesiones de inscripción están diseñadas específicamente para el registro de métodos de pago.

<Tip>
  Las sesiones de inscripción son ideales para configurar suscripciones, registrar clientes nuevos, o actualizar métodos de pago sin realizar un cargo inmediato.
</Tip>

## ¿Cuándo usar sesiones de inscripción?

Las sesiones de inscripción son útiles en los siguientes casos:

* **Suscripciones nuevas**: Cuando un cliente se inscribe a un servicio recurrente y necesitas su método de pago para cargos futuros.
* **Periodos de prueba**: Para recolectar un método de pago antes de que inicie la facturación.
* **Actualización de métodos de pago**: Cuando un cliente necesita agregar o cambiar su método de pago sin realizar un cargo.
* **Registro de clientes**: Para tener un método de pago guardado desde el momento de registro.

## 1. Crea una sesión de inscripción

El primer paso es crear una sesión de inscripción usando el endpoint de la API. Esta sesión generará una URL única donde tu cliente podrá ingresar y guardar su método de pago de forma segura.

<Warning>
  Este endpoint debe llamarse **desde tu servidor**, no desde el navegador del cliente. Necesitas una API key de Quentli para autenticar la solicitud. Nunca expongas tu API key en el código del cliente.
</Warning>

<Tip>
  Puedes obtener tu API key desde el [Dashboard de Quentli](https://dashboard.quentli.com/desarrollo/llaves-api) en la sección de configuración.
</Tip>

```json POST /v1/setup-sessions theme={null}
{
  "input": {
    "customer": {
      "name": "Ana Rodríguez",
      "externalId": "cliente-123",
      "email": "ana@ejemplo.com",
      "phoneNumber": "+525555123456"
    },
    "displayMode": "CUSTOMER_PORTAL",
    "returnUrl": "https://miapp.com/exito",
    "cancelUrl": "https://miapp.com/cancelado"
  }
}
```

#### Ejemplos desde el servidor

La llamada al endpoint debe realizarse desde tu backend usando tu lenguaje de programación preferido. Aquí hay algunos ejemplos:

<CodeGroup>
  ```typescript Node.js / Next.js theme={null}
  // Server action o API route
  const response = await fetch('https://api.quentli.com/v1/setup-sessions', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${process.env.QUENTLI_API_KEY}`,
    },
    body: JSON.stringify({
      input: {
        customer: {
          name: 'Ana Rodríguez',
          externalId: 'cliente-123',
          email: 'ana@ejemplo.com',
        },
        displayMode: 'CUSTOMER_PORTAL',
        returnUrl: 'https://miapp.com/exito',
        cancelUrl: 'https://miapp.com/cancelado',
      },
    }),
  });

  const data = await response.json();
  // Retorna { url: '...', session: { accessToken: '...', ... } }
  ```

  ```python Python theme={null}
  import requests
  import os

  response = requests.post(
      'https://api.quentli.com/v1/setup-sessions',
      headers={
          'Content-Type': 'application/json',
          'Authorization': f'Bearer {os.getenv("QUENTLI_API_KEY")}',
      },
      json={
          'input': {
              'customer': {
                  'name': 'Ana Rodríguez',
                  'externalId': 'cliente-123',
                  'email': 'ana@ejemplo.com',
              },
              'displayMode': 'CUSTOMER_PORTAL',
              'returnUrl': 'https://miapp.com/exito',
              'cancelUrl': 'https://miapp.com/cancelado',
          }
      }
  )

  data = response.json()
  # Retorna { url: '...', session: { accessToken: '...', ... } }
  ```

  ```csharp C# theme={null}
  using System.Net.Http;
  using System.Text;
  using System.Text.Json;

  var client = new HttpClient();
  client.DefaultRequestHeaders.Add("Authorization", 
      $"Bearer {Environment.GetEnvironmentVariable("QUENTLI_API_KEY")}");

  var payload = new {
      input = new {
          customer = new {
              name = "Ana Rodríguez",
              externalId = "cliente-123",
              email = "ana@ejemplo.com"
          },
          displayMode = "CUSTOMER_PORTAL",
          returnUrl = "https://miapp.com/exito",
          cancelUrl = "https://miapp.com/cancelado"
      }
  };

  var response = await client.PostAsync(
      "https://api.quentli.com/v1/setup-sessions",
      new StringContent(JsonSerializer.Serialize(payload), 
          Encoding.UTF8, "application/json")
  );

  var data = await response.Content.ReadAsStringAsync();
  // Retorna { url: '...', session: { accessToken: '...', ... } }
  ```

  ```php PHP theme={null}
  <?php
  $apiKey = getenv('QUENTLI_API_KEY');

  $data = [
      'input' => [
          'customer' => [
              'name' => 'Ana Rodríguez',
              'externalId' => 'cliente-123',
              'email' => 'ana@ejemplo.com'
          ],
          'displayMode' => 'CUSTOMER_PORTAL',
          'returnUrl' => 'https://miapp.com/exito',
          'cancelUrl' => 'https://miapp.com/cancelado'
      ]
  ];

  $ch = curl_init('https://api.quentli.com/v1/setup-sessions');
  curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
  curl_setopt($ch, CURLOPT_POST, true);
  curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));
  curl_setopt($ch, CURLOPT_HTTPHEADER, [
      'Content-Type: application/json',
      "Authorization: Bearer $apiKey"
  ]);

  $response = curl_exec($ch);
  curl_close($ch);

  $result = json_decode($response, true);
  // Retorna [ 'url' => '...', 'session' => [ 'accessToken' => '...', ... ] ]
  ?>
  ```

  ```ruby Ruby theme={null}
  require 'net/http'
  require 'json'
  require 'uri'

  uri = URI('https://api.quentli.com/v1/setup-sessions')
  http = Net::HTTP.new(uri.host, uri.port)
  http.use_ssl = true

  request = Net::HTTP::Post.new(uri)
  request['Content-Type'] = 'application/json'
  request['Authorization'] = "Bearer #{ENV['QUENTLI_API_KEY']}"
  request.body = {
    input: {
      customer: {
        name: 'Ana Rodríguez',
        externalId: 'cliente-123',
        email: 'ana@ejemplo.com'
      },
      displayMode: 'CUSTOMER_PORTAL',
      returnUrl: 'https://miapp.com/exito',
      cancelUrl: 'https://miapp.com/cancelado'
    }
  }.to_json

  response = http.request(request)
  data = JSON.parse(response.body)
  # Retorna { url: '...', session: { accessToken: '...', ... } }
  ```
</CodeGroup>

### Campos de la solicitud

* **`customer`** (requerido): Información del cliente que guardará el método de pago.
  * `name`: Nombre completo del cliente.
  * `externalId`: Identificador único del cliente en tu sistema.
  * `email`: Correo electrónico del cliente (opcional).
  * `phoneNumber`: Número de teléfono del cliente (opcional).
  * `forceUpdate`: Si es `true`, los datos enviados se tratan como la fuente de verdad. Si otro cliente tiene el `email` o `phoneNumber`, Quentli elimina ese dato del otro cliente y lo asigna al cliente resuelto (opcional).
  * `metadata`: Arreglo de pares clave-valor para información adicional (opcional).

* **`displayMode`** (opcional, por defecto `CUSTOMER_PORTAL`): Define cómo se mostrará la sesión al cliente.
  * `CUSTOMER_PORTAL`: Redirige a una página hospedada por Quentli (recomendado).
  * `EMBEDDED`: Para abrir en una ventana popup usando @quentli/js.
  * `CUSTOM`: Para incrustar en un iframe usando @quentli/js.

* **`returnUrl`** (opcional): URL a donde se redirige al cliente después de guardar exitosamente su método de pago. Solo aplica para modo `CUSTOMER_PORTAL`.

* **`cancelUrl`** (opcional): URL a donde se redirige al cliente si cancela el proceso. Solo aplica para modo `CUSTOMER_PORTAL`.

### Ejemplo de respuesta

```json Respuesta theme={null}
{
  "url": "https://portal.quentli.com/acme/inscripcion?cs=eyJhY2Nlc3NUb2tlbiI...",
  "session": {
    "accessToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "refreshToken": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
    "csrfToken": "abc123def456",
    "expiresAt": "2025-01-15T22:00:00.000Z"
  }
}
```

### Campos de la respuesta

* **`url`**: URL única donde el cliente puede completar el registro de su método de pago.
* **`session`**: Credenciales de autenticación necesarias para integrar con @quentli/js en modos `EMBEDDED` o `CUSTOM`.
  * `accessToken`: Token de acceso para autenticar al cliente.
  * `refreshToken`: Token para renovar la sesión cuando expire.
  * `csrfToken`: Token CSRF para proteger contra ataques.
  * `expiresAt`: Fecha y hora de expiración de la sesión.

<Note>
  Si usas el modo `CUSTOMER_PORTAL`, solo necesitas redirigir al cliente a la `url` proporcionada. Los tokens de sesión solo son necesarios para los modos `EMBEDDED` y `CUSTOM`.
</Note>

## 2. Muestra la sesión al cliente

Una vez que tienes la URL de la sesión, necesitas mostrarla al cliente. Quentli ofrece tres formas de hacerlo, dependiendo de tu caso de uso y nivel de integración deseado.

### Opción 1: Customer Portal (Redirección)

La forma más sencilla es redirigir al cliente directamente a la página hospedada por Quentli. Esta opción ofrece la mejor experiencia de usuario con diseño optimizado y responsive.

```javascript theme={null}
// Opción A: Redirección directa
window.location.href = result.url;

// Opción B: Usando @quentli/js (opcional)
import { Quentli } from '@quentli/js';

const quentli = new Quentli();
quentli.setupSessions.displayPage({ url: result.url });
```

**Ventajas:**

* ✅ Implementación más sencilla
* ✅ Diseño optimizado y mantenido por Quentli
* ✅ Funciona en todos los navegadores sin JavaScript adicional
* ✅ No requiere integración con SDK

**Ideal para:** Aplicaciones que prefieren delegar la UI a Quentli.

### Opción 2: Popup con @quentli/js

Usa el SDK oficial de JavaScript para abrir la sesión en una ventana popup. Esta opción mantiene al cliente en tu aplicación mientras completa el registro.

#### Instalación del SDK

```bash theme={null}
npm install @quentli/js
# o
pnpm add @quentli/js
```

#### Ejemplo de integración

```typescript theme={null}
import { Quentli } from '@quentli/js';

// Inicializa el cliente de Quentli
const quentli = new Quentli({ debug: true });

// Muestra la sesión en un popup
await quentli.setupSessions.displayPopup({
  url: result.url,
  session: {
    accessToken: result.session.accessToken,
    csrfToken: result.session.csrfToken,
  },
  onPaymentMethodAdded: (data) => {
    console.log('Método de pago agregado:', data.paymentMethod);
    // Actualiza tu UI o redirige al cliente
  },
  onCancel: () => {
    console.log('El cliente canceló el proceso');
  },
  onError: (error) => {
    console.error('Error:', error);
  },
  width: 500,  // Ancho de la ventana (opcional)
  height: 700, // Alto de la ventana (opcional)
});
```

**Ventajas:**

* ✅ El cliente permanece en tu aplicación
* ✅ Callbacks en tiempo real del resultado
* ✅ Control sobre el flujo después de guardar el método de pago

**Ideal para:** SPAs y aplicaciones que necesitan control sobre la navegación.

### Opción 3: Iframe con @quentli/js

Incrusta la sesión directamente en tu página usando un iframe. Esta es la opción con mayor nivel de integración visual.

```typescript theme={null}
import { Quentli } from '@quentli/js';

const quentli = new Quentli({ debug: true });

// Obtén el elemento contenedor
const container = document.getElementById('setup-container');

if (!container) {
  throw new Error('Container element not found');
}

// Muestra la sesión en un iframe
const iframe = await quentli.setupSessions.displayEmbedded({
  url: result.url,
  session: {
    accessToken: result.session.accessToken,
    csrfToken: result.session.csrfToken,
  },
  target: container,
  width: '100%',
  height: '600px',
  onPaymentMethodAdded: (data) => {
    console.log('Método de pago agregado:', data.paymentMethod);
  },
  onCancel: () => {
    console.log('El cliente canceló el proceso');
  },
  onError: (error) => {
    console.error('Error:', error);
  },
});
```

```html theme={null}
<!-- Contenedor donde se mostrará el iframe -->
<div id="setup-container"></div>
```

**Ventajas:**

* ✅ Experiencia completamente integrada en tu sitio
* ✅ No hay cambio de página o ventanas emergentes
* ✅ Control total sobre el diseño circundante

**Ideal para:** Dashboards y aplicaciones que requieren una experiencia sin interrupciones.

## 3. Obtén los métodos de pago guardados

Una vez que el cliente ha guardado su método de pago, puedes consultarlos usando el endpoint de métodos de pago.

```
GET /v1/customers/{customerId}/payment-methods
```

### Ejemplo de respuesta

```json Respuesta theme={null}
[
  {
    "id": "pm_clvqmr51j000408jyhcth2rjy",
    "createdAt": "2025-01-15T20:30:00.000Z",
    "updatedAt": "2025-01-15T20:30:00.000Z",
    "customerId": "cus_1234567890",
    "type": "CARD",
    "confirmed": false,
    "default": true,
    "expired": false,
    "cardDetail": {
      "id": "crd_1234567890",
      "createdAt": "2025-01-15T20:30:00.000Z",
      "updatedAt": "2025-01-15T20:30:00.000Z",
      "expiryMonth": 12,
      "expiryYear": 2028,
      "cardholder": "ANA RODRIGUEZ",
      "bin": "424242",
      "last4": "4242",
      "brand": "visa",
      "issuer": "BANCOMER",
      "country": "MX"
    }
  }
]
```

<Note>
  El campo `confirmed` será `false` hasta que se realice un cargo exitoso con este método de pago. Una vez confirmado, podrás usarlo para [cargos automáticos](/api-guide/guias/cargos-automaticos).
</Note>

## 4. Realiza cargos futuros

Con el método de pago guardado, ahora puedes realizar cargos automáticos sin intervención del cliente. Usa el `id` del método de pago obtenido en el paso anterior.

```json POST /v1/payments theme={null}
{
  "input": {
    "amount": 299900,
    "currency": "MXN",
    "description": "Primera mensualidad",
    "paymentMethodId": "pm_clvqmr51j000408jyhcth2rjy",
    "customerId": "cus_1234567890",
    "makeAttempt": true
  }
}
```

<Tip>
  Consulta la guía completa de [cargos automáticos](/api-guide/guias/cargos-automaticos) para aprender más sobre cómo realizar cargos a métodos de pago guardados.
</Tip>

## Seguridad y comunicación

### Transferencia segura de credenciales

Cuando usas los modos `EMBEDDED` o `CUSTOM` con @quentli/js, el SDK maneja automáticamente la transferencia segura de credenciales usando la API **MessageChannel**. Esto significa que:

* ✅ Los tokens nunca se exponen en la URL
* ✅ La comunicación entre tu página y el iframe/popup está cifrada
* ✅ Se validan los orígenes para prevenir ataques XSS

### Validación de origen

Por seguridad, Quentli valida que los mensajes provengan del origen correcto. Esto protege contra intentos de suplantación o interceptación de datos.

## Monitoreo con webhooks

Para mantener tu sistema sincronizado cuando un cliente guarda un método de pago, puedes suscribirte al siguiente webhook:

### `PAYMENT_METHOD_CREATED`

Este evento se dispara cuando un cliente guarda exitosamente un método de pago durante una sesión de inscripción.

```json Ejemplo de payload theme={null}
{
  "eventId": "<id_del_evento>",
  "eventType": "PAYMENT_METHOD_CREATED",
  "data": {
    "paymentMethod": {
      "id": "<id_del_método_de_pago>",
      "type": "CARD",
      "cardDetail": {
        "id": "<id_de_tarjeta>",
        "cardholder": "Ana Rodriguez",
        "last4": "4242",
        "brand": "VISA",
        "expiryMonth": 12,
        "expiryYear": 2028,
        "funding": "DEBIT",
        "issuer": "BBVA",
        "country": "MEX"
      }
    },
    "customer": {
      "id": "cus_1234567890",
      "name": "Ana Rodríguez",
      "email": "ana@ejemplo.com",
      "phoneNumber": "+525555123456",
      "username": "cliente-123",
      "metadata": []
    }
  }
}
```

<Tip>
  Consulta la [documentación completa de webhooks](/api-guide/webhooks) para configurar correctamente los endpoints y ver todos los eventos disponibles.
</Tip>

## Comparación de modos de visualización

| Modo                 | Complejidad | UX                  | Callbacks en tiempo real | Requiere SDK |
| -------------------- | ----------- | ------------------- | ------------------------ | ------------ |
| **CUSTOMER\_PORTAL** | Baja        | Redirige a Quentli  | ❌                        | ❌            |
| **EMBEDDED (Popup)** | Media       | Abre popup          | ✅                        | ✅            |
| **CUSTOM (Iframe)**  | Media       | Integrado en página | ✅                        | ✅            |

## Mejores prácticas

1. **Usa `CUSTOMER_PORTAL` para la mayoría de casos**: Es la opción más sencilla y confiable, especialmente si no necesitas control fino sobre el flujo después de guardar el método de pago.

2. **Valida los webhooks**: Siempre verifica la firma de los webhooks para asegurar que provienen de Quentli.

3. **Maneja los errores apropiadamente**: Usa los callbacks `onError` para capturar y mostrar errores al usuario de forma amigable.

4. **Guarda el `paymentMethodId`**: Una vez que recibas el webhook `PAYMENT_METHOD_CREATED`, guarda el ID del método de pago en tu base de datos para usarlo en cargos futuros.

5. **Implementa reintentos**: Si un cliente cierra el popup o cancela, ofrécele la opción de reintentar fácilmente.

## Recursos adicionales

* [Demo en vivo](https://quentli-example-setup-sessions-next.vercel.app/) - Prueba los tres modos de visualización
* [Ejemplo completo con Next.js](https://github.com/quentli/quentli-examples/tree/main/examples/setup-sessions-nextjs)
* [Documentación de @quentli/js](https://www.npmjs.com/package/@quentli/js)
* [Guía de cargos automáticos](/api-guide/guias/cargos-automaticos)
* [Configuración de webhooks](/api-guide/webhooks)
