Guardar una tarjeta
Guarda la tarjeta de un comprador con una sesión de tarjeta y el card element de n1co.js, sin que los datos pasen por tu página.
- Tu servidor crea una sesión de tarjeta con
POST /v1/card_sessionsy entrega suclient_secreta la página. - Tu página muestra el card element y llama a
capture({ client_secret }). - Tu servidor confirma la tarjeta con la sesión o con el webhook
card.created.
Primero, tu servidor crea la sesión:
curl -s -X POST https://api-epay.dev.n1co.dev/v1/card_sessions \
-H "Authorization: Bearer $N1CO_SECRET_KEY" \
-H 'Content-Type: application/json' \
-d '{
"customer": {
"id": "cust-1001",
"name": "Ana López",
"email": "ana.lopez@example.com",
"phone": "+503 7000-1234"
},
"page_origins": ["https://shop.example.com"]
}'{
"id": "cs_U2Vzc2lvbkV4YW1wbGUwMDAx",
"client_secret": "cs_U2Vzc2lvbkV4YW1wbGUwMDAx_secret_U2VjcmV0RXhhbXBsZTAwMDAwMDAwMDAw",
"status": "requires_card",
"attempts_remaining": 5,
"card": null,
"page_origins": ["https://shop.example.com"],
"expires_at": "2026-10-16T15:19:05Z",
"created_at": "2026-10-16T15:04:05Z"
}Luego, la página. Cambia la llave pública por la tuya y apunta los dos fetch a tus endpoints:
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Guardar tarjeta</title>
</head>
<body>
<form id="card-form">
<div id="card"></div>
<p id="card-message" role="alert"></p>
<button id="save" type="submit" disabled>Guardar tarjeta</button>
</form>
<script src="https://elements.n1co.dev/v1/n1co.js"></script>
<script>
const card = n1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA').elements().create('card', { locale: 'es' });
const save = document.querySelector('#save');
const message = document.querySelector('#card-message');
card
.on('change', ({ complete, error }) => {
save.disabled = !complete;
message.textContent = error ? error.message : '';
})
.on('error', (error) => {
message.textContent = error.message;
})
.mount('#card');
document.querySelector('#card-form').addEventListener('submit', async (event) => {
event.preventDefault();
save.disabled = true;
try {
// Tu endpoint crea la sesión con tu llave secreta y devuelve solo su client_secret.
const { client_secret } = await fetch('/checkout/card-session', { method: 'POST' }).then((r) => r.json());
const result = await card.capture({ client_secret });
// Tu servidor confirma leyendo la sesión con la llave secreta.
await fetch('/checkout/card', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ card: result.card.id }),
});
message.textContent = `Guardamos tu tarjeta terminada en ${result.card.last4}.`;
} catch (error) {
message.textContent = error.message;
save.disabled = false;
}
});
</script>
</body>
</html>La página se sirve por https. Para desarrollar también se aceptan http://localhost y http://127.0.0.1.
El cliente viaja en la solicitud#
customer es un objeto con tu propio id del comprador, su nombre, su correo y su teléfono. No hay que crear el cliente antes: el API lo crea la primera vez que ve ese id y, en los llamados siguientes, actualiza los demás datos.
| Campo | Reglas |
|---|---|
id |
Tu identificador: de 1 a 64 caracteres A-Z a-z 0-9 . _ -. |
name |
De 2 a 100 caracteres. |
email |
Un correo válido. |
phone |
Formato E.164. Se aceptan espacios, guiones y paréntesis. |
Los cuatro son requeridos. Ninguna respuesta ni webhook devuelve el cliente.
La sesión#
El client_secret se muestra solo al crear la sesión. Sirve para una tarjeta, durante 15 minutos y 5 intentos.
Con page_origins, la sesión solo funciona en esas páginas: de 1 a 5 orígenes como https://shop.example.com. Úsalo siempre en producción.
status |
Significado |
|---|---|
requires_card |
Esperando una tarjeta. |
completed |
Se guardó una tarjeta; card tiene su id. |
expired |
Venció sin una tarjeta. |
canceled |
Sin intentos, o la captura no se pudo confirmar. |
Lo que tu página debe manejar#
capture() se resuelve con { ok: true, card }: el id de la tarjeta, la marca, los últimos cuatro dígitos y el vencimiento. Si falla, se rechaza con un code estable y un message listo para mostrar.
- Un campo inválido: muestra
message; el comprador lo corrige. card_rejected: pide otra tarjeta.- La sesión ya no sirve (
card_session_expired,card_session_canceledy similares): crea una sesión nueva. capture_timeoutonetwork_error: la tarjeta pudo guardarse. Lee la sesión en tu servidor antes de reintentar.
Todos los códigos, opciones y eventos del card element están en la referencia de n1co.js.
Confirmar en tu servidor#
El resultado del navegador es una pista. Tu servidor confirma con GET /v1/card_sessions/{id}: la sesión está completed y card es el id de la tarjeta.
curl -s https://api-epay.dev.n1co.dev/v1/card_sessions/cs_U2Vzc2lvbkV4YW1wbGUwMDAx \
-H "Authorization: Bearer $N1CO_SECRET_KEY"También recibes el webhook card.created con la tarjeta. Su card_session es el id de la sesión (cs_…), que la une al comprador para el que creaste la sesión:
{
"id": "evt_RXZlbnRFeGFtcGxlMDAwMDAx",
"type": "card.created",
"created_at": "2026-10-16T15:06:12Z",
"data": {
"object": {
"id": "card_Q2FyZEV4YW1wbGUwMDAwMDAx",
"card_session": "cs_U2Vzc2lvbkV4YW1wbGUwMDAx",
"brand": "visa",
"last4": "4242",
"exp_month": 12,
"exp_year": 2030,
"issuer": { "name": "Banco Ejemplo", "country": "SV" },
"fingerprint": "5f0c3b1e9a7d4c2b8e6f0a1d3c5b7e9f1a3c5e7b9d1f3a5c7e9b1d3f5a7c9e1b",
"status": "active",
"created_at": "2026-10-16T15:06:12Z"
}
}
}Guarda el id de la tarjeta junto a tu usuario: con él y el mismo customer.id cobras la tarjeta después. Si el comprador guarda una tarjeta que ya tenía, recibes el mismo id y no llega otro card.created.
Tarjetas que dejan de servir#
Una tarjeta active se puede cobrar. Pasa a unavailable cuando n1co deja de conocerla, y vuelve a active si el comprador la guarda de nuevo.
Al cobrar, el API rechaza una tarjeta unavailable con 409 card_unavailable: pide al comprador que la escriba otra vez. Los campos de la tarjeta están en la referencia del API.