npm package: @npmtapi/embed
Description: TapiPay Embed SDK — carga y controla el widget de pago embebido
Author: tapipay
Latest version: 1.1.0

# @npmtapi/embed

SDK para integrar el widget de pago de TapiPay en cualquier sitio web o aplicación.

<!-- Docs: próximamente en https://docs.tapipay.com/embed -->

---

## Instalación

```bash
# npm
npm install @npmtapi/embed

# pnpm
pnpm add @npmtapi/embed

# yarn
yarn add @npmtapi/embed
```

### Entrypoints

| Import | Contenido |
|--------|-----------|
| `@npmtapi/embed` | `loadTapipay()` + tipos TypeScript |
| `@npmtapi/embed/react` | `<TapipayWidget>` + `useTapipay()` |

Los tipos están incluidos — no necesitas `@types/tapipay`.

---

## Uso rápido

### Script tag (sin bundler)

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

  tapipay.on("paymentWindowOpened", function() {
    console.log("Ventana de pago abierta");
  });

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

### Data-attrs (cero JavaScript)

```html
<script src="https://app.tapipay.la/embed.js"></script>

<div
  data-tapipay-widget
  data-organization="acme"
  data-identifier="CLI-001"
></div>
```

---

## React

```tsx
import { TapipayWidget } from "@npmtapi/embed/react";

export function PagoPage({ clienteId }: { clienteId: string }) {
  return (
    <TapipayWidget
      organization="acme"
      identifier={clienteId}
      onPaymentWindowOpened={() => console.log("Ventana de pago abierta")}
      onPaymentSubmitted={(data) => console.log("Pago enviado:", data)}
      onCancelled={() => console.log("Pago cancelado por el usuario")}
    />
  );
}
```

### Props

| Prop | Tipo | Requerido | Por defecto | Descripción |
|------|------|-----------|-------------|-------------|
| `organization` | `string` | Sí | — | Alias de la organización en TapiPay |
| `identifier` | `string` | Sí | — | Identificador del cliente |
| `externalRequestId` | `string` | No | — | ID de deuda a preseleccionar. Tiene precedencia sobre `selectionStrategy` |
| `selectionStrategy` | `"oldestCreated"` | No | más reciente | Estrategia de selección automática de deuda pendiente. `"oldestCreated"`: la más antigua. Por defecto (omitido): la más reciente |
| `environment` | `'production' \| 'homo'` | No | `'production'` | Entorno del SDK |
| `onPaymentWindowOpened` | `() => void` | No | — | Callback cuando el popup de Yuno se abre exitosamente |
| `onPaymentSubmitted` | `(data) => void` | No | — | Callback cuando el usuario completó el formulario de Yuno y fue redirigido al callback |
| `onCancelled` | `() => void` | No | — | Callback cuando el usuario cierra el popup sin completar el pago |
| `className` | `string` | No | — | Clases CSS del contenedor |
| `style` | `CSSProperties` | No | — | Estilos inline del contenedor |

### Next.js App Router

```tsx
"use client";

import { TapipayWidget } from "@npmtapi/embed/react";
import { useRouter } from "next/navigation";

export default function PagarPage() {
  const router = useRouter();

  return (
    <TapipayWidget
      organization={process.env.NEXT_PUBLIC_TAPIPAY_ORGANIZATION!}
      identifier="CLI-001"
      onPaymentSubmitted={() => router.push("/procesando")}
    />
  );
}
```

---

## Vue 3

```vue
<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>
```

---

## Angular

```typescript
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<HTMLDivElement>;

  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);
  }
}
```

---

## Opciones

### `loadTapipay(options?)`

| Parámetro | Tipo | Requerido | Por defecto | Descripción |
|-----------|------|-----------|-------------|-------------|
| `environment` | `'production' \| 'homo'` | No | `'production'` | Entorno del SDK a cargar |

### `initialize(options)`

| Parámetro | Tipo | Requerido | Por defecto | Descripción |
|-----------|------|-----------|-------------|-------------|
| `container` | `string \| HTMLElement` | Sí | — | Selector CSS o referencia DOM |
| `organization` | `string` | Sí | — | Alias de la organización en TapiPay |
| `identifier` | `string` | Sí | — | Identificador del cliente (DNI, número de cuenta, etc.) |
| `externalRequestId` | `string` | No | — | ID externo de una deuda específica a preseleccionar. Tiene precedencia sobre `selectionStrategy` |
| `selectionStrategy` | `"oldestCreated"` | No | más reciente | Estrategia de selección automática de deuda pendiente. `"oldestCreated"`: la más antigua. Por defecto (omitido): la más reciente |

---

## Eventos

```js
// El popup de Yuno se abrió exitosamente (útil para mostrar un overlay mientras el usuario paga)
tapipay.on("paymentWindowOpened", function() {
  console.log("Ventana de pago abierta");
  // Mostrar overlay/spinner mientras el usuario completa el formulario
});

// El usuario completó el formulario de Yuno y fue redirigido al callback
tapipay.on("paymentSubmitted", function(data) {
  console.log("Pago enviado:", data);
  // Mostrar pantalla de "procesando" y esperar webhook
});

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

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

### `TapipayPaymentWindowOpenedEvent`

```ts
interface TapipayPaymentWindowOpenedEvent {
  type: "paymentWindowOpened";
}
```

### `TapipayPaymentSubmittedEvent`

```ts
interface TapipayPaymentSubmittedEvent {
  type: string;         // "TAPIPAY_CALLBACK"
  status: "pending";
  data: Record<string, string>; // Parámetros que Yuno haya enviado (pueden estar vacíos)
}
```

### `TapipayPaymentCancelledEvent`

```ts
interface TapipayPaymentCancelledEvent {
  // sin campos adicionales
}
```

---

## Entorno de homologación

Para apuntar al entorno de homologación en lugar de producción:

### Vía npm (`loadTapipay`)

```ts
import { loadTapipay } from "@npmtapi/embed";

const tapipay = await loadTapipay({ environment: "homo" });
tapipay.initialize({ container: "#widget", organization: "acme", identifier: "CLI-001" });
```

### Vía React

```tsx
<TapipayWidget
  environment="homo"
  organization="acme"
  identifier="CLI-001"
/>
```

### Vía script tag

Apunta directamente al script del entorno deseado:

```html
<!-- Homologación -->
<script src="https://homo.tapipay.la/embed.js"></script>

<!-- Producción -->
<script src="https://app.tapipay.la/embed.js"></script>
```

> **Nota:** No se soporta cargar dos entornos distintos en la misma página, ya que `window.tapipay` es un objeto global compartido.