Cobrar
Cobra con payment intents. Tu servidor crea el intent, tu página lo confirma con n1co.js y el webhook te dice el resultado.
- Tu servidor crea un payment intent con
POST /v1/payment_intentsy entrega suclient_secreta la página. - Tu página confirma el pago con
confirmPayment()de n1co.js, que muestra 3D Secure si el banco lo pide. - Tu servidor entrega el pedido cuando el webhook
payment_intent.succeededlo confirma.
Primero, tu servidor crea el intent:
curl -s -X POST https://api-epay.dev.n1co.dev/v1/payment_intents \
-H "Authorization: Bearer $N1CO_SECRET_KEY" \
-H 'Content-Type: application/json' \
-d '{
"amount": 1250,
"currency": "USD",
"customer": {
"id": "cust-1001",
"name": "Ana López",
"email": "ana.lopez@example.com",
"phone": "+503 7000-1234"
},
"description": "Order 8812",
"reference": "order-8812",
"metadata": { "cart": "c-1029" },
"page_origins": ["https://shop.example.com"]
}'{
"id": "pi_UGF5bWVudEludGVudEV4YW1w",
"object": "payment_intent",
"client_secret": "pi_UGF5bWVudEludGVudEV4YW1w_secret_U2VjcmV0RXhhbXBsZTAwMDAwMDAwMDAw",
"status": "requires_payment_method",
"amount": 1250,
"currency": "USD",
"description": "Order 8812",
"reference": "order-8812",
"metadata": { "cart": "c-1029" },
"billing": null,
"page_origins": ["https://shop.example.com"],
"card": null,
"payment_method": null,
"next_action": null,
"last_error": null,
"attempts_remaining": 3,
"authorization_code": null,
"order_id": null,
"cancellation_reason": null,
"refund": null,
"created_at": "2026-10-17T15:04:05Z",
"updated_at": "2026-10-17T15:04:05Z",
"succeeded_at": null,
"canceled_at": null,
"expires_at": "2026-10-17T16:04:05Z"
}Luego, la página. Pide el client_secret una vez y lo usa en cada intento:
<!doctype html>
<html lang="es">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>Pagar</title>
</head>
<body>
<form id="payment-form">
<div id="card"></div>
<p id="payment-message" role="alert"></p>
<button id="pay" type="submit" disabled>Pagar $12.50</button>
</form>
<script src="https://elements.n1co.dev/v1/n1co.js"></script>
<script>
const n1 = n1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA');
const card = n1.elements().create('card', { locale: 'es' });
const pay = document.querySelector('#pay');
const message = document.querySelector('#payment-message');
// Tu endpoint crea el intent con tu llave secreta y devuelve solo su client_secret.
const clientSecret = fetch('/checkout/payment-intent', { method: 'POST' })
.then((r) => r.json())
.then((body) => body.client_secret);
card
.on('change', ({ complete, error }) => {
pay.disabled = !complete;
message.textContent = error ? error.message : '';
})
.mount('#card');
document.querySelector('#payment-form').addEventListener('submit', async (event) => {
event.preventDefault();
pay.disabled = true;
message.textContent = '';
try {
const intent = await n1.confirmPayment({ client_secret: await clientSecret, card });
if (intent.status === 'succeeded') {
// Tu servidor confirma con el webhook antes de entregar el pedido.
window.location.assign('/checkout/gracias');
} else if (intent.status === 'processing') {
message.textContent = 'Estamos confirmando tu pago. No vuelvas a pagar; te avisaremos.';
} else if (intent.status === 'canceled') {
message.textContent = 'Este pago ya no se puede completar. Vuelve a empezar tu compra.';
} else {
message.textContent = intent.last_error?.message ?? 'No se pudo completar el pago.';
pay.disabled = false;
}
} catch (error) {
message.textContent = error.message;
pay.disabled = false;
}
});
</script>
</body>
</html>Crear el payment intent#
amountva en unidades menores:1250son 12.50.currencyes un código ISO 4217 con dos decimales. Tu tienda cobra en una sola moneda, la de su primer pago exitoso.customerlleva tuiddel comprador, su nombre, su correo y su teléfono, como en Guardar una tarjeta.billinges la dirección de facturación. n1co la pide para tarjetas de Estados Unidos y Canadá.page_originslimita el intent a tus páginas, igual que en las sesiones de tarjeta.
El client_secret se muestra solo aquí. Un intent vence en una hora y tiene 3 intentos. Las reglas de cada campo están en la referencia del API.
Cobrar una tarjeta guardada#
Tu servidor adjunta la tarjeta con su id en payment_method. La tarjeta debe ser del cliente con ese customer.id. El navegador nunca elige ni ve las tarjetas guardadas.
curl -s -X POST https://api-epay.dev.n1co.dev/v1/payment_intents \
-H "Authorization: Bearer $N1CO_SECRET_KEY" \
-H 'Content-Type: application/json' \
-d '{
"amount": 1250,
"currency": "USD",
"customer": { "id": "cust-1001", "name": "Ana López", "email": "ana.lopez@example.com", "phone": "+503 7000-1234" },
"payment_method": "card_Q2FyZEV4YW1wbGUwMDAwMDAx"
}'La página confirma solo con el client_secret, sin card element:
const intent = await n1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA').confirmPayment({ client_secret });| Error | Qué pasó | Qué hacer |
|---|---|---|
404 card_not_found |
La tarjeta no es de ese cliente. | Revisa los dos id. |
409 card_unavailable |
La tarjeta ya no se puede usar. | Cobra con el card element. |
Una tarjeta nueva nunca se guarda al cobrar. Para guardarla, usa una sesión de tarjeta.
Lo que devuelve confirmPayment()#
n1co.js resuelve 3D Secure en su propia ventana y devuelve el intent. Revisa intent.status:
status |
Qué hacer en tu página |
|---|---|
succeeded |
Muestra la confirmación. |
processing |
Muestra que se está confirmando. No crees otro intent. |
requires_payment_method |
Muestra last_error.message y deja reintentar. |
canceled |
Crea un intent nuevo si el comprador quiere pagar. |
Estos casos piden atención:
billing_required: pide país, estado y código postal, y confirma otra vez conbilling.card_unavailable: la tarjeta guardada dejó de servir; el comprador paga con el card element.outcome_unknown: el intent queda enprocessingy n1co nunca cobra dos veces. Espera el webhook.
Los códigos de last_error están en Errores y los rechazos de la promesa en la referencia de n1co.js.
Confirmar en tu servidor#
Entrega el pedido solo cuando tu servidor recibe payment_intent.succeeded con firma válida, o lee el intent con GET /v1/payment_intents/{id} y está succeeded.
curl -s https://api-epay.dev.n1co.dev/v1/payment_intents/pi_UGF5bWVudEludGVudEV4YW1w \
-H "Authorization: Bearer $N1CO_SECRET_KEY"| Evento | Cuándo |
|---|---|
payment_intent.succeeded |
Una vez, al aceptarse el cargo. |
payment_intent.payment_failed |
Por cada intento fallido. |
payment_intent.canceled |
Al cancelarse o vencer. |
Estados, cancelación y reembolsos#
succeeded y canceled son finales. Sin intentos restantes, el intent también termina en canceled.Cancela un intent que ya no necesitas con POST /v1/payment_intents/{id}/cancel. Solo se cancela en requires_payment_method o requires_action.
Para devolver un pago succeeded, crea un reembolso.