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

# SDK de medios de pago

> Integra el widget de pago de TapiPay en cualquier sitio o aplicación, con eventos y alto automático.

El **SDK de embed** monta el widget de pago de TapiPay dentro de tu sitio. A diferencia de un [iframe manual](/es/portal-de-pagos/embeber-portal), ajusta su alto solo, muestra un skeleton mientras carga y te avisa por eventos qué está haciendo el usuario.

Es el mismo SDK que usa la [gestión de adhesiones](/es/portal-de-pagos/sdk-adhesiones): cambia una sola opción (`view`) para montar una vista u otra.

## Instalación

<CodeGroup>
  ```bash npm theme={null}
  npm install @npmtapi/embed
  ```

  ```bash pnpm theme={null}
  pnpm add @npmtapi/embed
  ```

  ```bash yarn theme={null}
  yarn add @npmtapi/embed
  ```
</CodeGroup>

<Note>
  Los tipos de TypeScript vienen incluidos en el paquete. No necesitas instalar nada aparte.
</Note>

### Entrypoints

| Import                 | Qué expone                           |
| ---------------------- | ------------------------------------ |
| `@npmtapi/embed`       | `loadTapipay()` y todos los tipos    |
| `@npmtapi/embed/react` | `<TapipayWidget />` y `useTapipay()` |

`<TapipayWidget />` es el camino recomendado en React: se encarga de cargar el SDK, montar el widget y limpiarlo al desmontar. `useTapipay()` es el hook de bajo nivel, para cuando necesitas controlar `initialize()` y `destroy()` a mano.

Si no usas bundler, no hace falta instalar nada: carga el script y trabaja con `window.tapipay`.

## Identifica tu empresa

Pasa **una** de estas dos opciones, no las dos:

<ResponseField name="organization" type="string">
  El `slug` o alias de tu empresa (por ejemplo, `acme-corp`). Corresponde al campo **Alias** en **Configuración → Organización** del dashboard. En data-attrs: `data-organization`.
</ResponseField>

<ResponseField name="companyCode" type="string">
  El código de empresa configurado para tu organización en TapiPay (por ejemplo, `MX-S-12345`). El formato varía entre integraciones: si no sabes cuál es el tuyo, confírmalo con tu contacto de TapiPay. En data-attrs: `data-company-code`.
</ResponseField>

<Tip>
  Si manejas **muchas empresas o sub-billers**, usa `companyCode`: no necesitas conocer ni administrar el `slug` de cada una. Si pasas las dos opciones, `companyCode` gana.
</Tip>

Todos los ejemplos de esta página funcionan igual si reemplazas `organization: "acme-corp"` por `companyCode: "MX-S-12345"`.

## Uso rápido

<Tabs>
  <Tab title="Script tag">
    Sin bundler. El script deja `window.tapipay` disponible al cargar.

    ```html theme={null}
    <div id="widget"></div>
    <script src="https://app.tapipay.la/embed.js"></script>
    <script>
      tapipay.initialize({
        container: "#widget",
        organization: "acme-corp",
        identifier: "CLI-00042"
      });

      tapipay.on("paymentSubmitted", function (data) {
        console.log("Pago enviado:", data);
      });
    </script>
    ```
  </Tab>

  <Tab title="Data-attrs">
    Cero JavaScript. El SDK busca los contenedores con `data-tapipay-widget` y los monta al cargar el DOM.

    ```html theme={null}
    <div
      data-tapipay-widget
      data-organization="acme-corp"
      data-identifier="CLI-00042"
    ></div>
    <script src="https://app.tapipay.la/embed.js"></script>
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={null}
    import { TapipayWidget } from "@npmtapi/embed/react";

    export function PagoPage({ clienteId }: { clienteId: string }) {
      return (
        <TapipayWidget
          organization="acme-corp"
          identifier={clienteId}
          onPaymentSubmitted={(data) => console.log("Pago enviado:", data)}
          onCancelled={() => console.log("Pago cancelado por el usuario")}
        />
      );
    }
    ```
  </Tab>
</Tabs>

## Opciones

### `initialize(options)`

| Opción              | Tipo                      | Requerido | Default         | Descripción                                                                                           |
| ------------------- | ------------------------- | --------- | --------------- | ----------------------------------------------------------------------------------------------------- |
| `container`         | `string \| HTMLElement`   | Sí        |                 | Selector CSS o referencia al nodo del DOM donde se monta el widget.                                   |
| `organization`      | `string`                  | Sí\*      |                 | `slug` de tu empresa. \*Requerido salvo que pases `companyCode`.                                      |
| `companyCode`       | `string`                  | Sí\*      |                 | Código de empresa. \*Alternativa a `organization`; si pasas las dos, esta gana.                       |
| `identifier`        | `string`                  | Sí        |                 | Identificador del usuario final (el mismo que envías al crear la deuda).                              |
| `externalRequestId` | `string`                  | No        |                 | Deuda a preseleccionar. Tiene precedencia sobre `selectionStrategy`.                                  |
| `selectionStrategy` | `"oldestCreated"`         | No        | la más reciente | Cómo elegir la deuda pendiente cuando hay varias. Omitido: la más reciente.                           |
| `view`              | `"payments" \| "autopay"` | No        | `"payments"`    | Vista a montar. Con `"autopay"` monta la [gestión de adhesiones](/es/portal-de-pagos/sdk-adhesiones). |

Si faltan `container`, `identifier` o los dos identificadores de empresa, el SDK no monta nada y avisa por consola.

### `destroy(container)`

Desmonta el widget: quita el iframe, el skeleton y los listeners internos.

```javascript theme={null}
tapipay.destroy("#widget");
```

Llamarlo es importante en una SPA, al desmontar el componente que contiene el widget. `<TapipayWidget />` ya lo hace por ti. Si vuelves a llamar `initialize()` sobre un contenedor que ya tiene un widget, el SDK lo destruye y lo vuelve a montar.

### `loadTapipay(options)`

| Opción        | Tipo                     | Requerido | Default        | Descripción               |
| ------------- | ------------------------ | --------- | -------------- | ------------------------- |
| `environment` | `"production" \| "homo"` | No        | `"production"` | Entorno del SDK a cargar. |

Es idempotente por entorno: llamarlo varias veces devuelve la misma promesa y no inyecta el script dos veces. Es compatible con React Strict Mode.

## Eventos

```javascript theme={null}
// El popup de pago se abrió
tapipay.on("paymentWindowOpened", function () {
  console.log("Ventana de pago abierta");
});

// El usuario completó el formulario y volvió al callback
tapipay.on("paymentSubmitted", function (data) {
  console.log("Pago enviado:", data);
});

// El usuario cerró el popup sin pagar
tapipay.on("paymentCancelled", function () {
  console.log("Pago cancelado por el usuario");
});

// Quitar un listener
tapipay.off("paymentSubmitted", handler);
```

<Warning>
  `paymentSubmitted` significa **enviado, no confirmado**. Su `status` es siempre `"pending"`: el usuario completó el formulario, pero el pago todavía no está acreditado. Nunca marques una deuda como pagada con este evento. El estado real llega por la [notificación de pago](/es/webhooks/notificacion-de-pago) a tu backend.
</Warning>

### Payloads

```typescript theme={null}
interface TapipayPaymentWindowOpenedEvent {
  type: "paymentWindowOpened";
}

interface TapipayPaymentSubmittedEvent {
  type: string;                    // "TAPIPAY_CALLBACK"
  status: "pending";               // siempre pending
  data: Record<string, string>;    // params del callback (pueden venir vacíos)
}

type TapipayPaymentCancelledEvent = Record<string, never>;  // sin campos
```

### En React

Los eventos se pasan como props. Ojo con el nombre del tercero: el evento se llama `paymentCancelled`, pero la prop es `onCancelled`.

| Prop                    | Evento equivalente    |
| ----------------------- | --------------------- |
| `onPaymentWindowOpened` | `paymentWindowOpened` |
| `onPaymentSubmitted`    | `paymentSubmitted`    |
| `onCancelled`           | `paymentCancelled`    |

`<TapipayWidget />` acepta además todas las opciones de `initialize()` (menos `container`), más `environment`, `className` y `style` para el contenedor.

## Otros frameworks

<Tabs>
  <Tab title="Next.js">
    ```tsx theme={null}
    "use client";
    import { TapipayWidget } from "@npmtapi/embed/react";
    import { useRouter } from "next/navigation";

    export default function PagarPage() {
      const router = useRouter();
      return (
        <TapipayWidget
          organization="acme-corp"
          identifier="CLI-00042"
          onPaymentSubmitted={() => router.push("/procesando")}
        />
      );
    }
    ```

    El widget es un componente de cliente: necesita `"use client"`.
  </Tab>

  <Tab title="Vue 3">
    ```vue theme={null}
    <template>
      <div ref="widgetRef"></div>
    </template>

    <script setup>
    import { loadTapipay } from "@npmtapi/embed";
    import { onMounted, onUnmounted, ref } from "vue";

    const props = defineProps({ organization: String, identifier: String });
    const widgetRef = ref(null);
    let tapipay = null;

    onMounted(async () => {
      tapipay = await loadTapipay();
      tapipay.initialize({
        container: widgetRef.value,
        organization: props.organization,
        identifier: props.identifier
      });
      tapipay.on("paymentSubmitted", (data) => console.log("Pago enviado:", data));
    });

    onUnmounted(() => tapipay?.destroy(widgetRef.value));
    </script>
    ```
  </Tab>

  <Tab title="Angular">
    ```typescript theme={null}
    import { loadTapipay, TapipayInstance } from "@npmtapi/embed";
    import { Component, ElementRef, Input, OnDestroy, OnInit, ViewChild } from "@angular/core";

    @Component({
      selector: "app-tapipay-widget",
      template: `<div #container></div>`
    })
    export class TapipayWidgetComponent implements OnInit, OnDestroy {
      @Input() organization!: string;
      @Input() identifier!: string;
      @ViewChild("container", { static: true }) containerRef!: ElementRef;

      private tapipay: TapipayInstance | null = null;

      async ngOnInit() {
        this.tapipay = await loadTapipay();
        this.tapipay.initialize({
          container: this.containerRef.nativeElement,
          organization: this.organization,
          identifier: this.identifier
        });
      }

      ngOnDestroy() {
        this.tapipay?.destroy(this.containerRef.nativeElement);
      }
    }
    ```
  </Tab>
</Tabs>

## Entorno de homologación

<Tabs>
  <Tab title="npm">
    ```typescript theme={null}
    import { loadTapipay } from "@npmtapi/embed";

    const tapipay = await loadTapipay({ environment: "homo" });
    tapipay.initialize({
      container: "#widget",
      organization: "acme-corp",
      identifier: "CLI-00042"
    });
    ```
  </Tab>

  <Tab title="React">
    ```tsx theme={null}
    <TapipayWidget
      organization="acme-corp"
      identifier="CLI-00042"
      environment="homo"
    />
    ```
  </Tab>

  <Tab title="Script tag">
    Apunta el `src` al host de homologación.

    ```html theme={null}
    <script src="https://homo.tapipay.la/embed.js"></script>
    ```
  </Tab>
</Tabs>

| Entorno      | Script                             | Origen del widget         |
| ------------ | ---------------------------------- | ------------------------- |
| `production` | `https://app.tapipay.la/embed.js`  | `https://app.tapipay.la`  |
| `homo`       | `https://homo.tapipay.la/embed.js` | `https://homo.tapipay.la` |

## Qué monta el SDK

Saber qué URL construye el SDK ayuda a depurar: si el widget queda en blanco, abre esa URL directo en el navegador y vas a ver el error real.

| Vista                     | URL del iframe                                 |
| ------------------------- | ---------------------------------------------- |
| Pagos, por `organization` | `{origen}/s/{organization}/embed/{identifier}` |
| Pagos, por `companyCode`  | `{origen}/c/{companyCode}/embed/{identifier}`  |
| Adhesiones                | la misma, con `/autopay` al final              |

`externalRequestId` y `selectionStrategy` se agregan como query params. Por ejemplo, `initialize()` con `organization: "acme-corp"`, `identifier: "CLI-00042"` y `externalRequestId: "INV-2026-001"` monta:

```text theme={null}
https://app.tapipay.la/s/acme-corp/embed/CLI-00042?externalRequestId=INV-2026-001
```
