Web
Integra el Web SDK: campos de tarjeta seguros, tarjetas guardadas y métodos alternativos.
El SDK Web de Tonder (@tonder.io/web-sdk) es
un SDK de navegador en TypeScript para aceptar pagos: campos de tarjeta seguros, pagos con tarjeta
nueva y guardada, presentación hosted/3DS, descubrimiento de métodos de pago, consulta de
transacciones y respuestas compatibles con webhooks.
¿Prefieres que un agente de IA haga la integración por ti? Instala el plugin Tonder Web SDK en Claude Code, Claude Desktop o Codex.
Antes de empezar
Necesitas:
- Tu llave API pública de Tonder. Nunca pongas llaves secretas en código de navegador.
- Un navegador moderno: Chrome, Safari, Firefox o Edge.
- Un endpoint en tu servidor que genere un
secure_tokende corta vida, si usarás tarjetas guardadas / Card-on-File. - Un endpoint de webhooks para confirmar pagos de forma confiable.
Instalación
npm install @tonder.io/web-sdkimport { createTonder, AppError, ErrorKeyEnum } from '@tonder.io/web-sdk';Sin bundler, carga el build global del navegador desde el CDN del entorno:
| Entorno | URL del CDN |
|---|---|
| Stage | https://zplit-stage.s3.us-east-1.amazonaws.com/web-sdk/v1/tonder-web-sdk.min.js |
| Producción | https://zplit-prod.s3.us-east-1.amazonaws.com/web-sdk/v1/tonder-web-sdk.min.js |
<script src="https://zplit-stage.s3.us-east-1.amazonaws.com/web-sdk/v1/tonder-web-sdk.min.js"></script>
<script>
const { createTonder } = window.Tonder;
</script>En un proyecto TypeScript que usa el CDN, puedes instalar @tonder.io/web-sdk como
devDependency solo para los tipos (npm install -D @tonder.io/web-sdk + import type),
manteniendo el runtime en el CDN. No importes código de runtime del paquete en ese caso.
Inicio rápido: pago con tarjeta
Agrega los contenedores para los campos de tarjeta
<form id="checkout-form">
<div id="collect-cardholder-name" class="card-field"></div>
<div id="collect-card-number" class="card-field"></div>
<div id="collect-expiration-month" class="card-field"></div>
<div id="collect-expiration-year" class="card-field"></div>
<div id="collect-cvv" class="card-field"></div>
<button type="submit">Pagar</button>
</form>Limita la altura de cada contenedor para que el iframe seguro no crezca visualmente mientras se acomoda al layout:
.card-field {
width: 100%;
max-height: 90px;
}Inicializa, monta y paga
import { createTonder } from '@tonder.io/web-sdk';
const tonder = createTonder({
api_key: 'pk_test_...',
environment: 'sandbox',
session: {
customer: {
email: 'ada@example.com',
first_name: 'Ada',
last_name: 'Lovelace',
},
},
});
await tonder.init();
const card_fields = tonder.create('card_fields');
await card_fields.mount();
const transaction = await tonder.pay({
amount: 150,
currency: 'MXN',
return_url: 'https://yourstore.example/checkout/return',
client_reference: 'order_1001',
metadata: { cart_id: 'cart_789' },
payment_method: { type: 'card' },
});Maneja el resultado
if (transaction.status === 'Success' || transaction.status === 'Authorized') {
// Muestra la confirmación.
} else if (transaction.status === 'Pending') {
// El cliente puede necesitar completar 3DS o un método asíncrono.
// Confirma el estado final con webhooks o getTransaction().
} else {
// Muestra un mensaje de pago recuperable.
console.warn(transaction.decline_code, transaction.decline_reason);
}Los rechazos no se lanzan como error — se devuelven como transacciones: lee
transaction.status. Los fallos del SDK sí se lanzan como AppError (consulta
Errores).
Los montos del SDK Web son unidades decimales (150 = MXN 150.00). Consulta
Dinero, monedas y montos.
Configuración
createTonder(config) crea una instancia para un comprador/sesión. Recrea el SDK si cambia el
cliente, el secure_token o el entorno.
| Campo | Requerido | Descripción |
|---|---|---|
api_key | Sí | Llave pública de Tonder para integraciones de navegador. |
environment | Sí | 'sandbox', 'stage' o 'production'. |
session.customer | Para pay() y tarjetas guardadas | Identidad del cliente. Omítelo en páginas de retorno de solo lectura que solo llaman getTransaction(). |
session.secure_token | Para tarjetas guardadas | Token de corta vida generado por tu backend. |
presentation_mode | No | 'redirect' por defecto, o 'embedded' para el modal del SDK. |
events.presentation | No | Callbacks on_open / on_close de la vista hosted embebida. |
customization.card_fields | No | Etiquetas, placeholders, estilos y mensajes de validación de los campos seguros. |
La tabla completa de personalización (estilos por campo, mensajes de error, icono de la tarjeta) está en la Referencia SDK · Web.
Modo de presentación
Cuando un pago requiere un paso hosted (3DS, instrucciones de APM), el SDK usa
presentation_mode:
| Modo | Comportamiento |
|---|---|
redirect | El navegador navega a la página hosted. Usa return_url, getTransaction() y webhooks para confirmar el estado final. |
embedded | El SDK abre un modal a pantalla completa. El 3DS de tarjeta espera la transacción final; las instrucciones hosted de APM/SPEI pueden devolver Pending de inmediato. |
Tarjetas guardadas (secure_token)
Las operaciones con tarjetas guardadas (getCustomerCards(), enrollCard(),
removeCustomerCard(), pagar con saved_card) requieren session.customer y
session.secure_token. Genera el token llamando a /api/secure-token/ desde tu backend con
tu llave secreta:
fetch("https://stage.tonder.io/api/secure-token/", {
method: 'POST',
headers: {
'Authorization': 'Token YOUR_SECRET_KEY',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(result => {
const secureToken = result.access;
// Pásalo al frontend como session.secure_token
});fetch("https://app.tonder.io/api/secure-token/", {
method: 'POST',
headers: {
'Authorization': 'Token YOUR_SECRET_KEY',
'Content-Type': 'application/json'
}
})
.then(response => response.json())
.then(result => {
const secureToken = result.access;
});El secure_token generado es válido por 1 hora. Úsalo dentro de ese plazo; si lo cacheas o
reutilizas después, genera uno nuevo.
Pagar con una tarjeta guardada
const tonder = createTonder({
api_key: 'pk_test_...',
environment: 'sandbox',
session: {
customer: { email: 'ada@example.com' },
secure_token: await getSecureTokenFromYourBackend(),
},
});
await tonder.init();
const cards = await tonder.getCustomerCards();
const selected_card = cards[0];
// Monta el CVV de la tarjeta guardada solo cuando la tarjeta no puede cobrarse
// por una suscripción Card-on-File existente.
if (!selected_card.subscription_id) {
const cvv = tonder.create('card_fields', {
card_id: selected_card.card_id,
fields: ['cvv'],
});
await cvv.mount();
}
const transaction = await tonder.pay({
amount: 150,
currency: 'MXN',
return_url: 'https://yourstore.example/checkout/return',
client_reference: 'order_1001',
payment_method: { type: 'saved_card', card_id: selected_card.card_id },
});Guardar una tarjeta nueva
const card_fields = tonder.create('card_fields');
await card_fields.mount();
const enrollment = await tonder.enrollCard();
// { card_id: 'card_123', subscription_id: 'sub_123' }Referencia de la API
Autorización
SecretKeyAuth Tu SECRET key con prefijo Token , p. ej. Token <SECRET_KEY> — distinta de la API key
In: header
Cuerpo de la respuesta
application/json
curl -X POST "https://example.com/secure-token/"{
"access": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzI3NzI3MTM3LCJpYXQiOjE3Mjc3MjM1MzcsImp0aSI6IjFjZTBkZmExODgwNzQzNGI4MDk2MzdlNTliNmM1NWMzIiwidXNlcl9pZCI6NDYxfQ.DFGNJr7JT6z3cp976PDBT57uX7LaYJLYBsdK8kaSAOI"
}Métodos de pago alternativos
Si tu checkout ya sabe qué método ofrecer, pasa el código directamente a pay():
const transaction = await tonder.pay({
amount: 150,
currency: 'MXN',
return_url: 'https://yourstore.example/checkout/return',
client_reference: 'order_1001',
payment_method: { type: 'oxxopay' },
});Usa getPaymentMethods() (opcional) para renderizar los métodos habilitados de tu negocio, y
getPaymentMethodBanks() para los métodos SafetyPay respaldados por banco:
const banks = await tonder.getPaymentMethodBanks();
const bank = banks.cash[0];
const transaction = await tonder.pay({
amount: 150,
currency: 'MXN',
return_url: 'https://yourstore.example/checkout/return',
client_reference: 'order_1001',
payment_method: {
type: 'safetypayCash',
config: {
country: bank.country, // p. ej. 'Mexico'
channel: bank.channel, // 'WP' efectivo, 'OL' transferencia
bank_ids: [{ id: bank.code }], // p. ej. [{ id: '8186' }]
},
},
});Los métodos APM/SPEI suelen liquidar de forma asíncrona. Usa webhooks para el cumplimiento de pedidos.
Demos
Prueba cada flujo en el portal de demos del SDK:
| Flujo | Demo |
|---|---|
| Pago con tarjeta | web/card-payment |
| Enrolamiento de tarjeta | web/enroll-card |
| Tarjetas guardadas | web/saved-cards |
| Métodos de pago | web/payment-methods |
| Bancos SafetyPay | web/safetypay-banks |
Los demos legacy (Web SDK Lite e Inline) usan versiones anteriores del SDK y se conservan solo como referencia — para integraciones nuevas usa los demos de arriba. Si todavía estás en el SDK anterior, sigue la guía de migración.
Conciliación
client_referencees requerido: es tu referencia de orden y aparece en dashboards, exportes, webhooks y reportes de transacciones.- Usa un
idempotency_keyestable por intento de checkout para que los reintentos no dupliquen cargos. No reutilicesclient_referencecomo llave de idempotencia. - Los webhooks del SDK Web usan el payload plano (campos de nivel superior) con
event_type: payment_Success/payment_Pending. Consulta el catálogo de eventos.
