n1coePayDocs

Empezar

Captura de tarjeta

Guarda la tarjeta de un comprador para cobrarla después.

≈ 10 minutos · curl, Node.js, .NET, PHP o Java · n1co.js, Web o React Native

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

POST /v1/card_sessions
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

server.mjs
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

Program.cs
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

card-session.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)

card-session.php
<?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

CardSessionController.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"));
  }
}
201Created
{
  "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

guardar-tarjeta.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

Terminal
npm install @n1co/elements
pnpm add @n1co/elements
yarn add @n1co/elements
bun add @n1co/elements
guardar-tarjeta.ts
import { 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

Terminal
npm install @n1co/elements
pnpm add @n1co/elements
yarn add @n1co/elements
bun add @n1co/elements
GuardarTarjeta.vue
<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

Terminal
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-values
pnpm 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-values
yarn 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-values
bun 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-values
SaveCard.tsx
import '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

server.mjs
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

Program.cs
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

webhook-endpoint.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)

webhook-endpoint.php
<?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

WebhookController.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();
  }
}
GET /v1/card_sessions/{id}
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.

Escribe para buscar en la documentación.