Empezar
Captura de tarjeta
Guarda la tarjeta de un comprador para cobrarla después.
Paso 1Crea una sesión de tarjeta en tu servidor
Tu servidor crea la sesión para el comprador y le entrega a la página solo el session_token. Sirve para una tarjeta, durante 15 minutos.
Tu secret_key va en el encabezado Authorization y nunca sale de tu servidor.
curl
curl -s -X POST https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/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"]
}'Node.js
app.post('/checkout/card-session', async (req, res) => {
// El comprador sale de tu sesión de usuario, nunca del navegador.
const response = await fetch('https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/card_sessions', {
method: 'POST',
headers: {
Authorization: `Bearer ${process.env.N1CO_SECRET_KEY}`,
'Content-Type': 'application/json',
},
body: JSON.stringify({
customer: { id: 'cust-1001', name: 'Ana López', email: 'ana.lopez@example.com', phone: '+503 7000-1234' },
page_origins: ['https://shop.example.com'],
}),
});
if (!response.ok) throw new Error(`n1co ${response.status}: ${await response.text()}`);
const session = await response.json();
res.json({ session_token: session.session_token });
});.NET
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient("n1co", http =>
{
http.BaseAddress = new Uri("https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com");
http.DefaultRequestHeaders.Authorization =
new("Bearer", Environment.GetEnvironmentVariable("N1CO_SECRET_KEY"));
});
var app = builder.Build();
app.MapPost("/checkout/card-session", async (IHttpClientFactory factory) =>
{
// El comprador sale de tu sesión de usuario, nunca del navegador.
var response = await factory.CreateClient("n1co").PostAsJsonAsync("/v1/card_sessions", new
{
customer = new { id = "cust-1001", name = "Ana López", email = "ana.lopez@example.com", phone = "+503 7000-1234" },
page_origins = new[] { "https://shop.example.com" },
});
response.EnsureSuccessStatusCode();
var session = await response.Content.ReadFromJsonAsync<System.Text.Json.JsonElement>();
return Results.Ok(new { session_token = session.GetProperty("session_token").GetString() });
});PHP
<?php
// El comprador sale de tu sesión de usuario, nunca del navegador.
$ch = curl_init('https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/card_sessions');
curl_setopt_array($ch, [
CURLOPT_POST => true,
CURLOPT_POSTFIELDS => json_encode([
'customer' => ['id' => 'cust-1001', 'name' => 'Ana López', 'email' => 'ana.lopez@example.com', 'phone' => '+503 7000-1234'],
'page_origins' => ['https://shop.example.com'],
]),
CURLOPT_HTTPHEADER => [
'Authorization: Bearer ' . getenv('N1CO_SECRET_KEY'),
'Content-Type: application/json',
],
CURLOPT_RETURNTRANSFER => true,
]);
$response = curl_exec($ch);
$status = curl_getinfo($ch, CURLINFO_RESPONSE_CODE);
if ($response === false || $status >= 300) {
throw new RuntimeException("n1co $status: $response");
}
$session = json_decode($response, true);
header('Content-Type: application/json');
echo json_encode(['session_token' => $session['session_token']]);PHP (SDK)
<?php
use N1co\EPay\N1co;
$n1co = new N1co((string) getenv('N1CO_SECRET_KEY'), ['base_url' => 'https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com']);
// El comprador sale de tu sesión de usuario, nunca del navegador.
$session = $n1co->cardSessions->create([
'customer' => ['id' => 'cust-1001', 'name' => 'Ana López', 'email' => 'ana.lopez@example.com', 'phone' => '+503 7000-1234'],
'page_origins' => ['https://shop.example.com'],
]);
header('Content-Type: application/json');
echo json_encode(['session_token' => $session->session_token]);Java
// Java 21+, Spring Boot y Jackson.
@RestController
class CardSessionController {
private final HttpClient http = HttpClient.newHttpClient();
private final ObjectMapper json = new ObjectMapper();
@PostMapping("/checkout/card-session")
Map<String, Object> cardSession() throws Exception {
// El comprador sale de tu sesión de usuario, nunca del navegador.
var body = Map.of(
"customer", Map.of("id", "cust-1001", "name", "Ana López", "email", "ana.lopez@example.com", "phone", "+503 7000-1234"),
"page_origins", List.of("https://shop.example.com"));
var request = HttpRequest.newBuilder(URI.create("https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/card_sessions"))
.header("Authorization", "Bearer " + System.getenv("N1CO_SECRET_KEY"))
.header("Content-Type", "application/json")
.POST(HttpRequest.BodyPublishers.ofString(json.writeValueAsString(body)))
.build();
var response = http.send(request, HttpResponse.BodyHandlers.ofString());
if (response.statusCode() >= 300) {
throw new IllegalStateException("n1co " + response.statusCode() + ": " + response.body());
}
var session = json.readValue(response.body(), Map.class);
return Map.of("session_token", session.get("session_token"));
}
}{
"id": "cs_U2Vzc2lvbkV4YW1wbGUwMDAx",
"session_token": "cs_U2Vzc2lvbkV4YW1wbGUwMDAx_token_U2Vzc2lvblRva2VuRXhhbXBsZTAwMDAw",
"mode": "capture",
"card": null,
"status": "requires_card",
"attempts_remaining": 5,
"page_origins": ["https://shop.example.com"],
"expires_at": "2026-10-16T15:19:05Z"
}| Campo | Valores | Por defecto | Para qué |
|---|---|---|---|
page_origins |
1 a 5 orígenes https://…; http://localhost y http://127.0.0.1 para desarrollar |
Opcional | Solo esas páginas pueden usar el token. |
Paso 2Captura la tarjeta en tu página
Monta el card element y llama a capture() con el session_token. Los datos de la tarjeta se escriben en un iframe de n1co y nunca pasan por tu página.
HTML
<!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.com/v1/n1co.js"></script>
<script>
const card = n1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' }).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 secret_key y devuelve solo su session_token.
const { session_token } = await fetch('/checkout/card-session', { method: 'POST' }).then((r) => r.json());
const result = await card.capture({ session_token });
// result.card: { id, brand, last4, exp_month, exp_year }
message.textContent = `Guardamos tu tarjeta terminada en ${result.card.last4}.`;
} catch (error) {
message.textContent = error.message;
save.disabled = false;
}
});
</script>
</body>
</html>Web
npm install @n1co/elementspnpm add @n1co/elementsyarn add @n1co/elementsbun add @n1co/elementsimport { loadN1co } from '@n1co/elements';
// null durante el renderizado en servidor: llámalo en el navegador.
const client = await loadN1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
if (client) {
const card = client.elements().create('card', { locale: 'es' });
card.mount('#card');
document.querySelector('#save')!.addEventListener('click', async () => {
const { session_token } = await fetch('/checkout/card-session', { method: 'POST' }).then((r) => r.json());
try {
const result = await card.capture({ session_token });
// result.card: { id, brand, last4, exp_month, exp_year }
document.querySelector('#card-error')!.textContent = '';
} catch (error) {
// error.code es estable; error.message viene traducido y se puede mostrar.
document.querySelector('#card-error')!.textContent = (error as Error).message;
}
});
}Vue / Nuxt
npm install @n1co/elementspnpm add @n1co/elementsyarn add @n1co/elementsbun add @n1co/elements<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { loadN1co, type Element as CardElement } from '@n1co/elements';
const cardBox = ref<HTMLElement | null>(null);
const card = ref<CardElement | null>(null);
const message = ref('');
const complete = ref(false);
const saving = ref(false);
onMounted(async () => {
// null durante el renderizado en servidor; en Nuxt también puedes envolver este componente en <ClientOnly>.
const client = await loadN1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
if (!client || !cardBox.value) return;
card.value = client.elements().create('card', { locale: 'es' });
card.value.on('change', (e) => {
complete.value = e.complete;
message.value = e.error ? e.error.message : '';
});
card.value.on('error', (e) => {
message.value = e.message;
});
card.value.mount(cardBox.value);
});
onBeforeUnmount(() => card.value?.unmount());
async function save() {
if (!card.value) return;
saving.value = true;
try {
// Tu endpoint crea la sesión con tu secret_key y devuelve solo su session_token.
const { session_token } = await fetch('/checkout/card-session', { method: 'POST' }).then((r) => r.json());
const result = await card.value.capture({ session_token });
// result.card: { id, brand, last4, exp_month, exp_year }
message.value = `Guardamos tu tarjeta terminada en ${result.card.last4}.`;
} catch (error) {
message.value = error instanceof Error ? error.message : String(error);
saving.value = false;
}
}
</script>
<template>
<form @submit.prevent="save">
<div ref="cardBox"></div>
<p role="alert" v-text="message"></p>
<button type="submit" :disabled="!complete || saving">Guardar tarjeta</button>
</form>
</template>React Native
npm install @n1co/react-native react-native-webview react-native-get-random-values
# Expo: npx expo install react-native-webview expo-crypto react-native-get-random-valuespnpm add @n1co/react-native react-native-webview react-native-get-random-values
# Expo: pnpm expo install react-native-webview expo-crypto react-native-get-random-valuesyarn add @n1co/react-native react-native-webview react-native-get-random-values
# Expo: yarn expo install react-native-webview expo-crypto react-native-get-random-valuesbun add @n1co/react-native react-native-webview react-native-get-random-values
# Expo: bun expo install react-native-webview expo-crypto react-native-get-random-valuesimport 'react-native-get-random-values'; // primera línea de tu app
import { useRef, useState } from 'react';
import { Button } from 'react-native';
import { CardField, N1coProvider, type CardFieldHandle } from '@n1co/react-native';
// Desde una app, tu servidor crea la sesión sin page_origins: la página que captura es la de n1co.
export function App() {
return (
<N1coProvider publishableKey="pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA" environment="sandbox" locale="es">
<SaveCard />
</N1coProvider>
);
}
function SaveCard() {
const card = useRef<CardFieldHandle>(null);
const [complete, setComplete] = useState(false);
async function save() {
const { session_token } = await fetch('https://shop.example.com/checkout/card-session', { method: 'POST' }).then(
(r) => r.json(),
);
const { card: saved } = await card.current!.capture({ session_token });
// saved: { id, brand, last4, exp_month, exp_year }
}
return (
<>
<CardField ref={card} onChange={(event) => setComplete(event.complete)} />
<Button title="Guardar tarjeta" disabled={!complete} onPress={save} />
</>
);
}| Opción | Valores | Por defecto | Dónde |
|---|---|---|---|
environment |
'production', 'sandbox' |
'production' |
n1co(), loadN1co(), N1coProvider |
locale |
'es', 'en' |
'es' |
create('card', …), N1coProvider |
Métodos y opciones de cada SDK: Web y React Native.
Paso 3Confirma con el webhook
Tu servidor guarda la tarjeta cuando n1co le avisa de la captura, con una firma válida; la verificación está en Configura tus webhooks. También puedes leer la sesión: está completed y card es el id de la tarjeta.
Ese id es el payment_method con el que cobras la tarjeta en Payment intents.
Node.js
import express from 'express';
import { verifyWebhook } from './webhooks.mjs';
// express.raw: la firma se calcula sobre el cuerpo crudo, antes de express.json().
app.post('/webhooks/n1co', express.raw({ type: 'application/json', limit: '1mb' }), (req, res) => {
if (!verifyWebhook(req.headers, req.body)) return res.sendStatus(400);
const event = JSON.parse(req.body);
if (event.type === 'card.created') {
// Guarda event.data.object.id con el comprador de la sesión event.data.object.card_session.
}
res.sendStatus(204);
});.NET
app.MapPost("/webhooks/n1co", async (HttpRequest request) =>
{
using var buffer = new MemoryStream();
await request.Body.CopyToAsync(buffer);
var body = buffer.ToArray();
var h = request.Headers;
if (!N1coWebhook.Verify(h["webhook-id"], h["webhook-timestamp"], h["webhook-signature"], body))
return Results.BadRequest();
using var evt = System.Text.Json.JsonDocument.Parse(body);
if (evt.RootElement.GetProperty("type").GetString() == "card.created")
{
// Guarda data.object.id con el comprador de la sesión data.object.card_session.
}
return Results.NoContent();
});PHP
<?php
require __DIR__ . '/webhooks.php';
$body = file_get_contents('php://input');
if (!verify_webhook($body)) {
http_response_code(400);
exit;
}
$event = json_decode($body, true);
if ($event['type'] === 'card.created') {
// Guarda $event['data']['object']['id'] con el comprador de la sesión $event['data']['object']['card_session'].
}
http_response_code(204);PHP (SDK)
<?php
require __DIR__ . '/webhooks.php'; // $event, verificado por el SDK
if ($event->type === 'card.created') {
// Guarda $event->data->object->id con el comprador de la sesión $event->data->object->card_session.
}
http_response_code(204);Java
@RestController
class WebhookController {
private final ObjectMapper json = new ObjectMapper();
@PostMapping("/webhooks/n1co")
ResponseEntity<Void> receive(@RequestHeader HttpHeaders headers, @RequestBody byte[] body) throws Exception {
if (!N1coWebhook.verify(headers.getFirst("webhook-id"), headers.getFirst("webhook-timestamp"),
headers.getFirst("webhook-signature"), body)) {
return ResponseEntity.badRequest().build();
}
var event = json.readValue(body, Map.class);
if ("card.created".equals(event.get("type"))) {
// Guarda data.object.id con el comprador de la sesión data.object.card_session.
}
return ResponseEntity.noContent().build();
}
}curl -s https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/card_sessions/cs_U2Vzc2lvbkV4YW1wbGUwMDAx \
-H "Authorization: Bearer $N1CO_SECRET_KEY"Guardaste la tarjeta de tu comprador.
Cóbrala cuando quieras con su id.