Migrar del SDK legacy

De tonder-web-sdk v2 (InlineCheckout / LiteInlineCheckout) a @tonder.io/web-sdk, incluyendo el cambio de webhooks.

Para comercios en tonder-web-sdk v2 — InlineCheckout o LiteInlineCheckout — que migran a @tonder.io/web-sdk.

Lee esto antes de planear el trabajo

Esta migración no es solo un cambio de front-end. Tu handler de webhooks también tiene que cambiar, y esa es la parte que los comercios subestiman al planear.

El SDK nuevo cobra a través de un API de pagos de Tonder distinto al del SDK legacy, así que los eventos que recibe tu servidor traen otro payload. No es un renombre de campos: algunos desaparecen, otros son nuevos, y si aceptas APMs tus dos handlers actuales se vuelven uno. Presupuesta tiempo de backend — ver Webhooks.

Todo lo demás es un movimiento función por función. Tarjetas guardadas, Card on File, enrolamiento, re-captura de CVV, APMs, SafetyPay, 3DS y campos seguros existen en el SDK nuevo.

¿Qué clase importabas?

UsabasTu migración esVe a
LiteInlineCheckoutCasi todo renombres de métodos — la UI ya es tuyaRuta A
InlineCheckoutConstruyes la UI de checkout que el SDK viejo dibujabaRuta B

Ambas rutas comparten Webhooks, que es el mismo trabajo en cualquiera de las dos.

Preparación — ambas rutas

Carga el SDK (cliente)

El SDK legacy se distribuía como paquete de npm y como script tag. Este también, y no estás atado a la opción que usabas antes — ambas funcionan en cualquier framework, React y Next.js incluidos.

Las actualizaciones te llegan
CDN <script> — el SDK llega como window.TonderAutomáticamente; la URL sigue un canal de versión mayor (/web-sdk/v1/)
npmnpm install @tonder.io/web-sdkCuando subes la versión y despliegas

Si estabas en npm, elimina el paquete viejo para que los dos no puedan cargarse a la vez:

npm uninstall tonder-web-sdk

Una app en TypeScript puede instalar el paquete de npm como devDependency solo por los tipos y mantener el runtime por CDN.

Snippets de instalación y CDN: Instalación.

Crea la instancia (cliente)

Antes — una clase que construyes, luego configuras, luego usas para cobrar

const checkout = new LiteInlineCheckout({
  mode: 'stage',
  apiKey: 'YOUR_KEY',
  returnUrl: 'https://merchant.example.com/return',
  callBack: (result) => handleResult(result),
});
await checkout.injectCheckout();

checkout.configureCheckout({
  customer: { firstName: 'Ada', lastName: 'Lovelace', email: 'ada@example.com' },
  order_reference: 'ORD-001',
});

Después — un factory, y el cliente pertenece a la sesión

const tonder = createTonder({
  api_key: tonderPublicConfig.api_key,
  environment: 'stage', // cambia a 'production' al salir a producción
  session: {
    customer: { email: 'ada@example.com', first_name: 'Ada', last_name: 'Lovelace' },
  },
});
await tonder.init();
LegacyNuevoNota
mode: 'stage' | 'production'environment: 'stage' | 'production'Mismos dos valores, llave renombrada
apiKeyapi_keyLa misma llave pública
injectCheckout()init()
configureCheckout({ customer })session.customer al crearYa no es una llamada aparte
returnUrl en el constructorreturn_url por llamada a pay()Permite URLs de retorno por transacción
callBackevents.payment — o la promesa que devuelve pay()Ver abajo
order_referenceclient_referenceRenombrado; tu correlación se conserva. Ver Correlación

Referencia completa: createTonder(config).

Resultados: callback o promesa (cliente)

El callBack legacy se disparaba para todo. El SDK nuevo te da ambos canales, y no son alternativas:

  • pay() devuelve una promesa con la transacción — úsala para actualizar la pantalla.
  • events.payment se dispara para todos los métodos, incluyendo flujos sin promesa. Un solo conjunto de handlers cubre el checkout completo.

Un rechazo no es un error en ninguno de los dos canales: llega como una transacción con estado declinado, en on_completed. on_error significa que no existe transacción alguna. Detalle de eventos en el README del SDK.

Ruta A: desde LiteInlineCheckout

La UI ya es tuya, así que esto es un pase de renombres más un cambio de comportamiento.

1. Mapea los métodos (cliente)

LegacyNuevo
injectCheckout()init()
mountCardFields(request)tonder.create('card_fields', options).mount()
unmountCardFields(context)card_fields.unmount()
revealCardFields(request)card_fields.reveal(input)
getCustomerCards()getCustomerCards()
saveCustomerCard()enrollCard()
removeCustomerCard(skyflowId)removeCustomerCard(card_id)
getCustomerPaymentMethods()getPaymentMethods()
payment(data)pay(input)
verify3dsTransaction()— el SDK resuelve 3DS por sí mismo

card_id y unmount_context conservan el significado que tenían en mountCardFields, incluyendo los valores all / none / current.

2. Monta los campos de tarjeta (cliente)

Antes

await checkout.mountCardFields({ /* configuración de campos */ });

Después — los ids de contenedor son los mismos defaults que usaba el SDK legacy

<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>
const card_fields = tonder.create('card_fields');
await card_fields.mount();

Cada campo configurado necesita su contenedor en el DOM antes de mount(), o la llamada se rechaza con MOUNT_COLLECT_ERROR.

Los estilos, labels y placeholders se mueven a customization.card_fields en createTonder() — ya no se pasan a la llamada de montaje. Ver customization.card_fields.

3. Cobra (cliente)

Antes

checkout.configureCheckout({ customer, order_reference: 'ORD-001' });
const result = await checkout.payment({ /* carrito */ });

Después

const transaction = await tonder.pay({
  amount: 150,
  currency: 'MXN',
  return_url: 'https://merchant.example.com/return',
  client_reference: 'ORD-001',
  payment_method: { type: 'card' },
});

Las tarjetas guardadas usan { type: 'saved_card', card_id }; los APMs usan su código de método. Lista completa de campos: tonder.pay(input).

4. Elimina verify3dsTransaction() (cliente)

El flujo legacy exigía llamarlo en la página de retorno. El SDK nuevo resuelve 3DS por sí mismo y te da a elegir la presentación:

  • presentation_mode: 'redirect' — el navegador navega, como hoy
  • presentation_mode: 'embedded' — un modal, y el comprador nunca sale de tu página

Ver Modo de presentación.

5. Opcional: agrega Apple Pay (cliente)

No existía en el SDK legacy, así que esto es capacidad nueva más que migración.

Hay algo que vale la pena iniciar ahora y no al final: pide a Tonder habilitar Apple Pay y registrar tus dominios. Tonder trata con Apple — tú nunca los contactas y no necesitas cuenta de desarrollador de Apple. Tu parte es enviar la lista de dominios y alojar un archivo que Tonder te da. No es un paso de código, pero condiciona todo lo demás, y es la razón más común por la que una integración terminada no funciona en producción.

Después, sigue la guía de Apple Pay.

Ruta B: desde InlineCheckout

InlineCheckout dibujaba el checkout completo: el formulario de tarjeta, la lista de tarjetas guardadas, el selector de APMs y el estilo alrededor. El SDK nuevo no dibuja un checkout. Tú construyes la UI; el SDK te da inputs seguros para los datos de tarjeta y la información para renderizar todo lo demás.

Eso es toda esta migración. Cada capacidad que tenías sigue ahí — lo que cambia es quién la renderiza.

1. Inventaría lo que la UI vieja le daba a tus compradores (planeación)

Antes de escribir código, lista cuáles de estas cosas mostraba realmente tu checkout. Solo reconstruyes esas.

La UI vieja dibujabaAhora renderizasEl SDK te da
Formulario de tarjetaTu propio layoutInputs seguros vía create('card_fields') — nunca tocas el PAN
Lista de tarjetas guardadasTu propia lista y selectorgetCustomerCards() — número enmascarado, marca, expiración, subscription_id
Checkbox de guardar tarjetaTu propio checkboxenrollCard()
Selector de APMsTu propia listagetPaymentMethods() — incluye el label y la URL del logo de cada método
Selector de banco SafetyPayTu propio selectorgetPaymentMethodBanks() — agrupados en cash y transfer
Botón de pagar, textos, coloresTuyos
Estados de carga y errorTuyosCódigos de error para ramificar

No tienes que diseñar iconos de métodos de pago. getPaymentMethods() devuelve una URL de logo por método — es lo que la UI vieja renderizaba.

2. Reemplaza los tres métodos de ciclo de vida (cliente)

Antes — el SDK era dueño de la pantalla

const checkout = new InlineCheckout({ mode, apiKey, returnUrl, callBack });
await checkout.injectCheckout();   // dibujaba todo dentro de tu contenedor
checkout.setCallback(handleResult);
checkout.removeCheckout();

Después — tú eres dueño de la pantalla; el SDK, de la seguridad de la tarjeta y del cobro

Tenías un contenedor y el SDK lo llenaba con un checkout entero. Ahora tú armas tu formulario, y el SDK monta un iframe seguro dentro de cada input de tarjeta. Todo lo demás — labels, el botón de pagar, la lista de tarjetas guardadas, el selector de APMs — es tu markup.

<!-- tu formulario, tu layout; solo estos cinco son del SDK -->
<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 id="pay">Pagar</button>
const tonder = createTonder({ /* …ver Preparación… */ });
await tonder.init();

const card_fields = tonder.create('card_fields');
await card_fields.mount();          // dentro de los contenedores de arriba

const transaction = await tonder.pay({ /* …ver Ruta A, paso 3… */ });

card_fields.unmount();              // tu teardown, en lugar de removeCheckout()

Esos cinco ids son los defaults; cada campo configurado necesita su contenedor en el DOM antes de mount(), o la llamada se rechaza con MOUNT_COLLECT_ERROR. Dales un max-height en tu CSS para que los iframes no crezcan antes de asentarse.

3. Construye los flujos que inventariaste (cliente)

Son los mismos flujos por los que la UI vieja llevaba a tu comprador:

FlujoGuía
Tarjeta nuevaInicio rápido: pago con tarjeta
Tarjeta guardadaPagar con una tarjeta guardada
Guardar una tarjetaGuardar una tarjeta nueva
APMs y SafetyPayMétodos de pago alternativos
Apple Pay — nuevo, no existía en el SDK legacyApple Pay

Una regla que vale la pena llevar a tu propia UI: subscription_id en una tarjeta guardada decide si necesitas CVV. Presente significa cobrar directo; null significa montar primero el campo de CVV de tarjeta guardada.

4. Estilos (cliente)

El customization viejo tenía dos mitades. Solo una tiene contraparte:

Customization legacyAhora
Estilos, labels y placeholders de los campos seguroscustomization.card_fields en createTonder()
UI del checkout: secciones visibles, colores, texto del botónTu propio CSS — no hay equivalente en el SDK

Webhooks (servidor)

Ambas rutas necesitan esto, y es la parte que no es un renombre.

Hoy recibes dos formas de payload distintas — una para pagos con tarjeta, otra para APMs. Ahora recibirás una sola, la misma para todos los métodos: el formato Short, el mismo que envía API Direct. El detalle de ambos formatos está en el Modelo de webhooks y el catálogo de eventos.

Webhooks de tarjeta

Campo legacyCampo nuevoNota
transaction_referenceDesaparece. Nada del payload nuevo lo reemplaza
payment_idDesaparece
checkout_idDesaparece. No hay objeto checkout en la ruta nueva
idNuevo, y el que importa. El identificador de Tonder para la transacción — lo que pasas a getTransaction() y citas a soporte
transaction_idNuevo. Un id interno de Tonder del registro de procesamiento. No es tu llave de correlación
client_referenceNuevo. Tu referencia de orden, dentro del webhook. Con esto correlacionas tu pedido
status y transaction_statusstatusEl par legacy estaba duplicado; ahora hay uno
amount, currency, metadatamismos nombresSe conservan
providerproviderSe conserva
transaction_typeoperation_type
operation_datecreatedAhora en ISO 8601
number_of_payment_attemptsDesaparece
response (anidado, del procesador)Desaparece. Lee status y los campos de rechazo
event_typep. ej. payment_Success
payment_method_typep. ej. CARD, SPEI, OXXO
actionp. ej. MODIFY

Webhooks de APM

Antes — envuelto, y con forma distinta a los de tarjeta

{
  "action": "payment",
  "type": "apm",
  "data": { "transaction_status": "Success", "payment_id": 41714, "checkout_id": "…" }
}

Después — idéntico en forma a un webhook de tarjeta

{
  "id": "fc38522e-…",
  "operation_type": "payment",
  "status": "Success",
  "payment_method_type": "SPEI",
  "client_reference": "ORD-001",
  "transaction_id": "e9340a04-…",
  "event_type": "payment_Success"
}

Si tu código ramifica en type === 'apm' o desenvuelve data, elimínalo. Un solo handler cubre ahora todos los métodos.

Qué hacer

  1. Lee la especificación del payload actual: Cómo funcionan los webhooks y el Modelo de webhooks.
  2. Correlaciona con client_reference en lugar de checkout_id o payment_id.
  3. Mantén el handler idempotente guardando los ids de eventos procesados — la política de reintentos y la Dead Letter Queue están en Entrega y reintentos.
  4. Si migras gradualmente, mantén ambos endpoints durante el corte; los payloads se distinguen por la presencia de event_type.

Correlación (servidor)

Tu referencia de orden sigue funcionando. Se renombra, y sigue identificando lo mismo del lado de Tonder — la conciliación que ya tienes no necesita repensarse, solo cambia el nombre del campo.

Campo
LegacyconfigureCheckout({ order_reference })
Nuevopay({ client_reference })

metadata existe en ambos, sin cambios.

Revisa esto antes de migrar. Ningún SDK fusiona estos campos — la referencia de orden y metadata viajan por separado, y siempre lo han hecho. Pero en los reportes exportados de transacciones, la columna Business Transaction ID prefiere metadata.order_id y solo cae a la referencia de orden cuando está ausente. Si has enviado ambos con valores distintos, tus webhooks correlacionan con un identificador y tus reportes con otro. Decide cuál lee realmente tu conciliación antes de mapearlo, y envía el mismo valor en ambos de aquí en adelante. La tabla completa de metadata está en tonder.pay(input).

Prueba la migración

Corre esto en stage antes de cambiar el tráfico de producción.

PruebaQué demuestra que funcionó
Pago con tarjeta nuevaLa transacción se crea vía /process/, no por el router
Tarjeta rechazadaLlega como transacción con estado declinado, no como error lanzado
3DSSe resuelve sin que llames verify3dsTransaction()
Tarjeta guardada con subscription_idCobra sin pedir CVV
Tarjeta guardada sin subscription_idPide CVV y luego cobra
EnrolamientoLa tarjeta aparece después en getCustomerCards()
Un APMDevuelve Pending y se liquida por webhook
Handler de webhooksProcesa el payload plano nuevo para una tarjeta Y un APM
CorrelaciónEl client_reference de tu orden aparece en el webhook

Las últimas dos son las que valen una prueba explícita. Todo lo de arriba falla ruidosamente; un descuadre de webhooks falla en silencio, y te enteras cuando un pedido no se surte.

Siguientes pasos

¿Te resultó útil esta página?

En esta página