n1coePayDocs

Empezar

Payment intents

Cobra con payment intents en tres pasos.

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

Paso 1Crea un payment intent en tu servidor

Tu servidor crea el intent con el monto de tu pedido, nunca con uno que llegue del navegador. A la página le entrega solo el intent_token.

Tu secret_key va en el encabezado Authorization y nunca sale de tu servidor.

El id de una tarjeta guardada es el que devuelve la captura de tarjeta.

curl

POST /v1/payment_intents
curl -s -X POST https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/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"],
    "save_card": true
    "payment_method": "65f1c0ffee0123456789abcd"
  }'

Node.js

server.mjs
app.post('/checkout/payment-intent', async (req, res) => {
  // El monto sale de tu pedido, nunca del navegador.
  const response = await fetch('https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/payment_intents', {
    method: 'POST',
    headers: {
      Authorization: `Bearer ${process.env.N1CO_SECRET_KEY}`,
      'Content-Type': 'application/json',
    },
    body: JSON.stringify({
      amount: 1250,
      currency: 'USD',
      customer: { id: 'cust-1001', name: 'Ana López', email: 'ana.lopez@example.com', phone: '+503 7000-1234' },
      reference: 'order-8812',
      page_origins: ['https://shop.example.com'],
      save_card: true,
      payment_method: '65f1c0ffee0123456789abcd',
    }),
  });
  if (!response.ok) throw new Error(`n1co ${response.status}: ${await response.text()}`);
  const intent = await response.json();
  res.json({ intent_token: intent.intent_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/payment-intent", async (IHttpClientFactory factory) =>
{
    // El monto sale de tu pedido, nunca del navegador.
    var response = await factory.CreateClient("n1co").PostAsJsonAsync("/v1/payment_intents", new
    {
        amount = 1250,
        currency = "USD",
        customer = new { id = "cust-1001", name = "Ana López", email = "ana.lopez@example.com", phone = "+503 7000-1234" },
        reference = "order-8812",
        page_origins = new[] { "https://shop.example.com" },
        save_card = true,
        payment_method = "65f1c0ffee0123456789abcd",
    });
    response.EnsureSuccessStatusCode();
    var intent = await response.Content.ReadFromJsonAsync<System.Text.Json.JsonElement>();
    return Results.Ok(new { intent_token = intent.GetProperty("intent_token").GetString() });
});

PHP

payment-intent.php
<?php

// El monto sale de tu pedido, nunca del navegador.
$ch = curl_init('https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/payment_intents');
curl_setopt_array($ch, [
    CURLOPT_POST => true,
    CURLOPT_POSTFIELDS => json_encode([
        'amount' => 1250,
        'currency' => 'USD',
        'customer' => ['id' => 'cust-1001', 'name' => 'Ana López', 'email' => 'ana.lopez@example.com', 'phone' => '+503 7000-1234'],
        'reference' => 'order-8812',
        'page_origins' => ['https://shop.example.com'],
        'save_card' => true,
        'payment_method' => '65f1c0ffee0123456789abcd',
    ]),
    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");
}
$intent = json_decode($response, true);
header('Content-Type: application/json');
echo json_encode(['intent_token' => $intent['intent_token']]);

PHP (SDK)

payment-intent.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 monto sale de tu pedido, nunca del navegador.
$intent = $n1co->paymentIntents->create([
    'amount' => 1250,
    'currency' => 'USD',
    'customer' => ['id' => 'cust-1001', 'name' => 'Ana López', 'email' => 'ana.lopez@example.com', 'phone' => '+503 7000-1234'],
    'reference' => 'order-8812',
    'page_origins' => ['https://shop.example.com'],
    'save_card' => true,
    'payment_method' => '65f1c0ffee0123456789abcd',
]);
header('Content-Type: application/json');
echo json_encode(['intent_token' => $intent->intent_token]);

Java

CheckoutController.java
// Java 21+, Spring Boot y Jackson.
@RestController
class CheckoutController {
  private final HttpClient http = HttpClient.newHttpClient();
  private final ObjectMapper json = new ObjectMapper();

  @PostMapping("/checkout/payment-intent")
  Map<String, Object> paymentIntent() throws Exception {
    // El monto sale de tu pedido, nunca del navegador.
    var body = Map.of(
        "amount", 1250,
        "currency", "USD",
        "customer", Map.of("id", "cust-1001", "name", "Ana López", "email", "ana.lopez@example.com", "phone", "+503 7000-1234"),
        "reference", "order-8812",
        "page_origins", List.of("https://shop.example.com"),
        "save_card", true);
        "payment_method", "65f1c0ffee0123456789abcd");
    var request = HttpRequest.newBuilder(URI.create("https://api-epay-sandbox.n1co.shophttps://api-epay.n1co.com/v1/payment_intents"))
        .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 intent = json.readValue(response.body(), Map.class);
    return Map.of("intent_token", intent.get("intent_token"));
  }
}
201Created
{
  "id": "pi_UGF5bWVudEludGVudEV4YW1w",
  "object": "payment_intent",
  "intent_token": "pi_UGF5bWVudEludGVudEV4YW1w_token_SW50ZW50VG9rZW5FeGFtcGxlMDAwMDAw",
  "status": "requires_payment_method",
  "amount": 1250,
  "currency": "USD",
  "reference": "order-8812",
  "expires_at": "2026-10-17T16:04:05Z"
}
Campo Valores Por defecto Para qué
currency Código ISO 4217 con dos decimales, por ejemplo USD Requerido La moneda de amount.
save_card true: la tarjeta queda guardada para el comprador; false: un solo uso true Qué pasa con la tarjeta nueva.
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.
Campo Valores Por defecto Para qué
currency Código ISO 4217 con dos decimales, por ejemplo USD Requerido La moneda de amount.
payment_method Id de una tarjeta guardada del mismo customer.id, activa Opcional Cobra esa tarjeta.
cvc_required true, false false (opcional) Pide el código de seguridad; solo con payment_method.
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 2Cobra en tu página

Monta el card element y confirma el pago con el intent_token. Los datos de la tarjeta se escriben en un iframe de n1co, y 3D Secure aparece cuando el banco lo pide.

Confirma el pago con el intent_token; la tarjeta guardada no necesita formulario. 3D Secure aparece cuando el banco lo pide.

HTML

pagar.html
<!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>
      <button id="pay" type="submit">Pagar $12.50</button>
    </form>

    <script src="https://elements.n1co.com/v1/n1co.js"></script>
    <script>
      const n1 = n1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
      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 secret_key y devuelve solo su intent_token.
      const intentToken = fetch('/checkout/payment-intent', { method: 'POST' })
        .then((r) => r.json())
        .then((body) => body.intent_token);

      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({ intent_token: await intentToken, card });
          const intent = await n1.confirmPayment({ intent_token: await intentToken });
          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 {
            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>

Web

Terminal
npm install @n1co/elements
pnpm add @n1co/elements
yarn add @n1co/elements
bun add @n1co/elements
pagar.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('#pay')!.addEventListener('click', async () => {
    const { intent_token } = await fetch('/checkout/payment-intent', { method: 'POST' }).then((r) => r.json());
    const intent = await client.confirmPayment({ intent_token, card });
    const intent = await client.confirmPayment({ intent_token });
    // intent.status: 'succeeded', 'processing' u otro, como en la pestaña HTML.
  });
}

Vue / Nuxt

Terminal
npm install @n1co/elements
pnpm add @n1co/elements
yarn add @n1co/elements
bun add @n1co/elements
Pagar.vue
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { onMounted, ref } from 'vue';
import { loadN1co, type N1coClient, type Element as CardElement } from '@n1co/elements';
import { loadN1co, type N1coClient } from '@n1co/elements';

const cardBox = ref<HTMLElement | null>(null);
const client = ref<N1coClient | null>(null);
const card = ref<CardElement | null>(null);
const message = ref('');
const complete = ref(false);
const paying = ref(false);

onMounted(async () => {
  // null durante el renderizado en servidor; en Nuxt también puedes envolver este componente en <ClientOnly>.
  client.value = await loadN1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
  if (!client.value || !cardBox.value) return;
  card.value = client.value.elements().create('card', { locale: 'es' });
  card.value.on('change', (e) => {
    complete.value = e.complete;
    message.value = e.error ? e.error.message : '';
  });
  card.value.mount(cardBox.value);
});

onBeforeUnmount(() => card.value?.unmount());

async function pay() {
  if (!client.value || !card.value) return;
  if (!client.value) return;
  paying.value = true;
  message.value = '';
  try {
    // Tu endpoint crea el intent con tu secret_key y devuelve solo su intent_token.
    const { intent_token } = await fetch('/checkout/payment-intent', { method: 'POST' }).then((r) => r.json());
    const intent = await client.value.confirmPayment({ intent_token, card: card.value });
    const intent = await client.value.confirmPayment({ intent_token });
    if (intent.status === 'succeeded') {
      // Tu servidor confirma con el webhook antes de entregar el pedido.
      window.location.assign('/checkout/thanks');
    } else if (intent.status === 'processing') {
      message.value = 'Estamos confirmando tu pago. No vuelvas a pagar; te avisaremos.';
    } else {
      message.value = intent.last_error?.message ?? 'No se pudo completar el pago.';
      paying.value = false;
    }
  } catch (error) {
    message.value = error instanceof Error ? error.message : String(error);
    paying.value = false;
  }
}
</script>

<template>
  <form @submit.prevent="pay">
    <div ref="cardBox"></div>
    <p role="alert" v-text="message"></p>
    <button type="submit" :disabled="!complete || paying">Pagar $12.50</button>
    <button type="submit" :disabled="paying">Pagar $12.50</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
Checkout.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, useN1co, type CardFieldHandle } from '@n1co/react-native';
import { N1coProvider, useN1co } from '@n1co/react-native';

// Desde una app, tu servidor crea el intent sin page_origins: la página que paga es la de n1co.
export function App() {
  return (
    <N1coProvider publishableKey="pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA" environment="sandbox" locale="es">
      <Checkout />
    </N1coProvider>
  );
}

function Checkout() {
  const card = useRef<CardFieldHandle>(null);
  const [complete, setComplete] = useState(false);
  const { confirmPayment } = useN1co();

  async function pay() {
    const { intent_token } = await fetch('https://shop.example.com/checkout/payment-intent', { method: 'POST' }).then(
      (r) => r.json(),
    );
    const intent = await confirmPayment({ intent_token, card: card.current });
    const intent = await confirmPayment({ intent_token });
    // intent.status: 'succeeded', 'processing' u otro, como en la pestaña HTML.
  }

  return (
    <>
      <CardField ref={card} onChange={(event) => setComplete(event.complete)} />
      <Button title="Pagar" disabled={!complete} onPress={pay} />
    </>
  );
  return <Button title="Pagar" onPress={pay} />;
}
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 entrega el pedido cuando n1co le avisa del pago, con una firma válida. La verificación está en Configura tus webhooks.

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 === 'payment_intent.succeeded') {
    // Entrega el pedido de event.data.object.reference; descarta los event.id repetidos.
  }
  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() == "payment_intent.succeeded")
    {
        // Entrega el pedido de data.object.reference; descarta los id repetidos.
    }
    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'] === 'payment_intent.succeeded') {
    // Entrega el pedido de $event['data']['object']['reference']; descarta los id repetidos.
}
http_response_code(204);

PHP (SDK)

webhook-endpoint.php
<?php

require __DIR__ . '/webhooks.php'; // $event, verificado por el SDK

if ($event->type === 'payment_intent.succeeded') {
    // Entrega el pedido de $event->data->object->reference; descarta los $event->id repetidos.
}
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 ("payment_intent.succeeded".equals(event.get("type"))) {
      // Entrega el pedido de data.object.reference; descarta los id repetidos.
    }
    return ResponseEntity.noContent().build();
  }
}

Ya cobraste tu primer pago.

Tu servidor creó el intent, tu página lo cobró y el webhook lo confirmó.

Escribe para buscar en la documentación.