@baaskit/checkout-js
v0.1.6
Published
BaaskitCheckout SDK for embedding Baaskit card payments in merchant checkouts.
Maintainers
Readme
BaaskitCheckout JS
@baaskit/checkout-js permite embeber Baaskit Tarjetas dentro del checkout existente de un comercio mediante un iframe controlado por SDK.
La V1 no procesa pagos reales en este repositorio. Incluye SDK, protocolo postMessage, mock local, ejemplos y tests para validar la arquitectura.
🤖 Para agentes de IA: este SDK trae un Skill de integración
Este paquete incluye un Skill de integración para agentes de IA (Claude, Cursor, Kiro, OpenCode, Antigravity, Copilot, Gemini, …). Es un conjunto de instrucciones autocontenidas que le permite a un agente integrar el checkout embebido de Baaskit end-to-end y sin soporte humano, en cualquier stack de e-commerce (Next.js, Remix/React Router, Shopify, Vue, Angular, PHP, vanilla JS, …). Si descargas o instalas este SDK, el agente puede leer el skill y dejar la integración funcionando solo.
Cómo lo descubre cada agente
| Archivo | Para qué sirve |
|---|---|
| AGENTS.md | Punto de entrada estándar cross-agent (lo leen Cursor, OpenCode y otros). Redirige al skill. |
| skills/baaskit-checkout-integration/SKILL.md | Skill canónico: mapa, reglas de oro, decisión de ui_mode, checklist y verificación. |
| .cursor/rules/baaskit-checkout.mdc | Puntero nativo para Cursor (descubrimiento automático). |
| .claude/skills/baaskit-checkout-integration/SKILL.md | Puntero nativo para Claude Code. |
Instalado como dependencia, el skill queda en
node_modules/@baaskit/checkout-js/skills/baaskit-checkout-integration/ (se incluye en el
paquete npm).
Qué cubre el skill
El skill (SKILL.md) más sus referencias en skills/baaskit-checkout-integration/references/:
| Referencia | Contenido |
|---|---|
| backend.md | OAuth2 client credentials, crear el OnlineCheckout (con ui_mode, cardholder, tokenization_enable), webhook y confirmación server-side, variables de entorno y ambientes. |
| frontend.md | Montar el SDK, botón "Pagar" del comercio, eventos, estados de UI y auto-resize. |
| security.md | PCI/SAQ A, CSP frame-ancestors, manejo de secretos, idempotencia. |
| troubleshooting.md | Fallos comunes y su solución (iframe que no carga, X-Frame-Options, pantalla en blanco, el pago no se confirma, error 502, …). |
El skill instruye al agente a preguntar primero el ui_mode (embedded_page embebido, por
defecto, o hosted_page con redirección) y a implementar la creación del OnlineCheckout en
consecuencia.
Cómo usarlo (agente)
- Lee
AGENTS.md. - Sigue
skills/baaskit-checkout-integration/SKILL.mdy abre las referencias según cada paso. - Pregunta el
ui_mode, detecta el stack del proyecto y traduce los patrones a ese stack.
Flujo correcto
La sesion Baaskit debe crearse cuando el cliente selecciona Baaskit Tarjetas como metodo de pago.
No debe crearse al cargar el carrito. Tampoco debe esperarse hasta que el cliente presione Pagar, porque el iframe necesita una sesion valida para inicializarse.
- Cliente selecciona
Baaskit Tarjetas. - Frontend del comercio llama a su backend:
POST /api/baaskit/session. - Backend valida carrito, stock, descuentos, shipping, impuestos, moneda y orden.
- Backend crea una sesion en Baaskit usando clave secreta.
- Frontend recibe
payment_link_url. - Frontend monta
BaaskitCheckout(el SDK inyecta el iframe con el formulario de tarjeta). - El comercio renderiza su propio boton "Pagar" — el iframe no tiene boton de pago.
- Al presionar "Pagar", el comercio llama
BaaskitCheckout.submit()→ el iframe ejecuta el pago. - El iframe emite estado visual por
postMessage. - La orden se marca como pagada solo por webhook firmado o verificacion server-side.
Instalacion
npm install @baaskit/checkout-jsUso con bundler
import { BaaskitCheckout } from "@baaskit/checkout-js";
const checkout = await fetch("/api/baaskit/session", {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
cartId: currentCart.id
})
}).then((response) => response.json());
BaaskitCheckout.mount("#baaskit-payment-container", {
paymentLinkUrl: checkout.payment_link_url,
environment: "production",
locale: "es-CL",
appearance: {
theme: "light",
borderRadius: "8px",
primaryColor: "#1155cc"
},
onReady: () => {
setUiState("ready");
},
onProcessing: () => {
setUiState("processing");
},
onSuccess: (payload) => {
setUiState("succeeded_pending_confirmation");
console.log("Pago aprobado visualmente", payload);
},
onError: (error) => {
setUiState(error.errorCode === "session_expired" ? "expired" : "failed");
},
onCancel: () => {
setUiState("failed");
}
});Uso con script
<script src="https://cdn.baaskit.com/checkout-js/v1/baaskit-checkout.umd.cjs"></script>
<script>
window.BaaskitCheckout.BaaskitCheckout.mount("#baaskit-payment-container", {
paymentLinkUrl: checkout.payment_link_url,
environment: "production"
});
</script>API
BaaskitCheckout.mount(selector, options);
BaaskitCheckout.submit();
BaaskitCheckout.unmount();
BaaskitCheckout.destroy();
BaaskitCheckout.openRedirectFallback();mount acepta un selector CSS o un HTMLElement. El SDK crea internamente el iframe; el comercio no debe insertarlo manualmente.
submit dispara el pago dentro del iframe. El iframe en modo embebido no tiene boton de pago propio — el comercio debe renderizar su propio boton "Pagar" y llamar a submit() al presionarlo. Esto le da al comercio control total sobre la UX del boton (posicion, estilo, texto, validaciones previas) mientras el iframe se encarga solo de la captura segura de datos y el procesamiento.
unmount remueve iframe, listeners y estado interno. Usalo en el cleanup de componentes, antes de montar una sesion nueva, o si necesitas que el iframe vuelva a leer el monto (ver el warning de abajo).
⚠️ Si tu carrito puede cambiar despues de crear la sesion, actualizar el monto es OBLIGATORIO.
El
amountqueda congelado al crear elOnlineCheckout, y Baaskit cobra siempre el monto persistido, nunca el que muestra tu frontend. Ante un cambio (cantidad, cupon, shipping, total, moneda) tu backend debe llamarPUT /onlinecheckout/{id}con el nuevo total antes de que el comprador pague — si no, se cobra el total viejo.El SDK no expone ningun metodo para cambiar el monto, y es deliberado: es una llamada server-to-server con tus credenciales OAuth2. Nunca la expongas a tu propio frontend — si el navegador pudiera fijar el monto, un comprador podria pagar el total que quisiera.
El iframe puede seguir montado: mostrara el total anterior, pero el cobro sera el nuevo. Muestra tu propio total fuera del iframe, o
unmount()+ montar de nuevo si necesitas que la cifra coincida.
destroy hace lo mismo que unmount e invalida la instancia.
openRedirectFallback queda preparado para casos donde el flujo embebido no sea viable, por ejemplo 3DS bloqueado en iframe, WebView restrictiva o navegador incompatible.
Options
interface BaaskitCheckoutOptions {
paymentLinkUrl: string;
environment?: "development" | "production";
locale?: "es-CL" | "en-US";
appearance?: {
theme?: "light" | "dark" | "auto";
borderRadius?: string;
primaryColor?: string;
};
allowedOrigins?: string[];
embedBaseUrl?: string;
onReady?: () => void;
onProcessing?: (payload?: unknown) => void;
onSuccess?: (payload: {
paymentId: string;
sessionId: string;
orderId?: string;
jwt?: string;
}) => void;
onError?: (error: {
errorCode: string;
message: string;
recoverable: boolean;
}) => void;
onCancel?: () => void;
onResize?: (height: number) => void;
}embedBaseUrl existe para mocks locales y ambientes controlados. En integraciones reales se debe preferir environment.
Flujo POC /embebed
Para esta POC, el iframe se carga desde:
GET https://payment.dev.baaskit.com/embebed?id=JWTEl HTML del checkout se entrega y luego el JavaScript del iframe resuelve el OnlineCheckout llamando:
POST /onlinecheckout/token
Content-Type: application/json
{
"token": "JWT"
}Si /onlinecheckout/token responde correctamente, el iframe inicializa el formulario de tarjeta. Si falla, el iframe debe mostrar estado de checkout invalido, expirado o no disponible.
Esta decision es exclusiva de la POC. Para produccion, no recomiendo entregar el HTML completo antes de validar el JWT. El flujo productivo recomendado es:
GET /embebed?id=JWT
Backend valida el JWT con /onlinecheckout/token o validacion criptografica local
Si es valido, devuelve HTML del checkout
Si no es valido, devuelve error minimo sin cargar la superficie completa de pagoEstados sugeridos del comercio
type BaaskitUiState =
| "not_selected"
| "creating_session"
| "ready"
| "processing"
| "succeeded_pending_confirmation"
| "failed"
| "expired";succeeded_pending_confirmation significa exito preliminar en frontend. La orden real aun no debe considerarse pagada.
La pagina embebida debe ocultar los inputs de tarjeta al comenzar el procesamiento. Durante processing muestra un estado de espera y, tras baaskit.payment.succeeded, reemplaza el formulario por una confirmacion visual sin volver a exponer los datos ingresados. Si el pago falla y es recuperable, puede ofrecer volver al formulario para reintentar.
Backend de referencia del comercio
Crear sesion
POST /api/baaskit/session
Content-Type: application/json{
"cartId": "cart_123"
}Responsabilidades del backend:
- Buscar carrito server-side.
- Validar stock, precios, descuentos, shipping, impuestos, total y moneda.
- Crear o recuperar una orden pendiente.
- Crear
OnlineCheckouten Baaskit con clave secreta. - Retornar
payment_link_urlal frontend.
Respuesta:
{
"session_id": "bsk_chk_123",
"client_secret": "bsk_client_secret_xxx",
"expires_at": "2026-06-18T20:00:00Z"
}Webhook
POST /api/webhooks/baaskitResponsabilidades:
- Validar firma.
- Validar
event_ididempotente. - Validar
order_id. - Validar monto y moneda contra la orden local.
- Marcar la orden como pagada.
- Responder 2xx rapido.
- Ejecutar tareas largas en background.
API Baaskit conceptual
POST /v1/checkout/sessions
Authorization: Bearer sk_test_xxx
Idempotency-Key: UUID{
"order_id": "ORDER-10001",
"amount": 16295,
"currency": "CLP",
"description": "Compra ecommerce",
"customer": {
"email": "[email protected]"
},
"metadata": {
"cart_id": "cart_123",
"merchant_reference": "commerce_abc"
},
"return_url": "https://commerce.cl/checkout/result",
"webhook_url": "https://commerce.cl/api/webhooks/baaskit"
}Desarrollo local
npm install
npm test
npm run build
npm run devAbre http://localhost:5173/examples/vanilla/ para probar el mock.
TODO V1 hacia produccion
- Implementar
/embebedreal enpayment.baaskit.com. - Reemplazar mock por integracion real con Payment API y 3DS.
- Configurar CSP con
frame-ancestorspor comercio autorizado. - Implementar allowlist server-side de dominios merchant.
- Implementar firma de webhooks y validacion de idempotencia.
- Definir contrato final de errores recuperables y expiracion de sesion.
- ~~Definir fallback redirect/modal para emisores o ACS que bloqueen iframe.~~ (resuelto en v0.1.5 con
allow-popupsen sandbox yopenRedirectFallback()).
Changelog
v0.1.6
Solo documentacion — sin cambios de codigo ni de API del SDK.
- Se documenta que actualizar el monto via
PUT /onlinecheckout/{id}es OBLIGATORIO si el carrito puede cambiar despues de crear la sesion. Baaskit cobra el monto persistido, no el que muestra el frontend del comercio; sin esePUTse cobra el total viejo. Hasta esta version el skill y los docs del paquete solo documentabanunmount()+ crear sesion nueva, y no mencionaban elPUTen ninguna parte. skills/.../references/backend.md: nueva seccion 4b conupdateOnlineCheckoutAmount()y la tabla de errores del endpoint (LINE_ITEMS_AMOUNT_MISMATCH,CHECKOUT_NOT_UPDATABLE,EXPIRED_CHECKOUT, 403, 404).SECURITY.mdyskills/.../references/security.md: advertencia de no exponer esePUTal frontend del comercio ni proxearlo reenviando el monto que llegue del browser.skills/.../references/troubleshooting.md: se separa "se cobro el total viejo" (grave, falta elPUT) de "el iframe muestra otro total" (cosmetico, el cobro es correcto). La version anterior afirmaba que una sesion vieja "podria cobrar un monto incorrecto", lo que era enganoso.- Aclaracion de diseño: el SDK no expone ningun metodo para cambiar el monto, y es deliberado — es una llamada server-to-server del backend del comercio.
v0.1.5
- Se agregó
publickey-credentials-create *a la Permissions Policy del iframe (requerido por Mastercard CorePasskeys/FIDO). - Se agregó atributo
sandboxexplícito conallow-popupsyallow-popups-to-escape-sandboxpara permitir que el SDK de Mastercard abra ventanas de autenticación (window.open). - Flags de sandbox incluidos:
allow-scripts,allow-same-origin,allow-forms,allow-popups,allow-popups-to-escape-sandbox,allow-top-navigation-by-user-activation,allow-modals.
