SDKs de cliente
Web
n1co.js y @n1co/elements, los campos de tarjeta de n1co en tu página.
Corre en el navegador del comprador, en una página https; para desarrollar también se aceptan http://localhost y http://127.0.0.1 (inclúyelos en page_origins si lo usas).
Instalar
HTML
<script src="https://elements.n1co.com/v1/n1co.js"></script>
<script>
const client = n1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
</script>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' });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 N1coClient, type Element as CardElement } 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;
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 });
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>
</form>
</template>| Opción | Valores | Por defecto | Dónde |
|---|---|---|---|
environment |
'production', 'sandbox' |
'production' |
n1co(), loadN1co() |
locale |
'es', 'en' |
'es' |
create('card', …), create('cvc', …) |
Métodos
Cliente
| Método | Qué hace | Devuelve |
|---|---|---|
n1co(publishableKey, options?) |
Crea el cliente con tu publishable_key. |
El cliente |
loadN1co(publishableKey, options?) |
Carga n1co.js desde npm y crea el cliente. | Promesa del cliente, null en SSR |
elements().create('card', options?) |
Crea el formulario de tarjeta. | Elemento de tarjeta |
elements().create('cvc', options?) |
Crea el código de seguridad de una tarjeta guardada. | Elemento cvc |
confirmPayment({ intent_token, card?, cvc?, billing? }) |
Cobra el intent con una tarjeta nueva, la adjunta o su código; resuelve 3D Secure. | Promesa del intent en estado final |
retrievePaymentIntent(intent_token) |
Lee el intent como está ahora. | Promesa del intent |
Elemento
| Método | Qué hace | Devuelve |
|---|---|---|
mount(selectorOrElement) |
Inserta el iframe. | El elemento |
unmount() |
Quita el iframe y descarta lo escrito. | El elemento |
on(event, handler) |
Agrega un manejador de ready, change, focus, blur, error o capture. |
El elemento |
off(event, handler) |
Quita un manejador. | El elemento |
focus(field?) |
Enfoca un campo. | El elemento |
clear() |
Vacía los campos. | El elemento |
update({ appearance?, locale? }) |
Cambia la apariencia o el idioma. | El elemento |
capture({ session_token }) |
Guarda la tarjeta bajo la sesión; solo el elemento de tarjeta. | Promesa de { ok: true, card } |
Apariencia
Pasa appearance al crear el campo. update() mezcla variables sin quitar las anteriores: al cambiar de tema envía la apariencia completa. Lo que el comprador escribió se conserva.
HTML
<div id="card"></div>
<script>
const themes = {
light: {
theme: 'light',
variables: {
colorBackground: '#ffffff',
colorText: '#1a1a1a',
colorTextPlaceholder: '#767676',
colorBorder: '#cccccc',
colorBorderFocus: '#0b7a75',
colorDanger: '#c62828',
},
},
dark: {
theme: 'dark',
variables: {
colorBackground: '#1a1a1a',
colorText: '#f2f2f2',
colorTextPlaceholder: '#8b8b8b',
colorBorder: '#444444',
colorBorderFocus: '#4da3ff',
colorDanger: '#ff7b72',
},
},
};
// Sigue el tema del sistema; update() recibe siempre la apariencia completa.
const media = matchMedia('(prefers-color-scheme: dark)');
const pick = (dark) => themes[dark ? 'dark' : 'light'];
const card = n1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' }).elements().create('card', { appearance: pick(media.matches) });
card.mount('#card');
media.addEventListener('change', (e) => card.update({ appearance: pick(e.matches) }));
</script>Web
import { loadN1co } from '@n1co/elements';
const themes = {
light: {
theme: 'light',
variables: {
colorBackground: '#ffffff',
colorText: '#1a1a1a',
colorTextPlaceholder: '#767676',
colorBorder: '#cccccc',
colorBorderFocus: '#0b7a75',
colorDanger: '#c62828',
},
},
dark: {
theme: 'dark',
variables: {
colorBackground: '#1a1a1a',
colorText: '#f2f2f2',
colorTextPlaceholder: '#8b8b8b',
colorBorder: '#444444',
colorBorderFocus: '#4da3ff',
colorDanger: '#ff7b72',
},
},
} as const;
// Sigue el tema del sistema; update() recibe siempre la apariencia completa.
const media = matchMedia('(prefers-color-scheme: dark)');
const pick = (dark: boolean) => themes[dark ? 'dark' : 'light'];
const client = await loadN1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
const card = client?.elements().create('card', { appearance: pick(media.matches) });
card?.mount('#card');
media.addEventListener('change', (e) => card?.update({ appearance: pick(e.matches) }));Vue / Nuxt
<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref } from 'vue';
import { loadN1co, type Element as CardElement } from '@n1co/elements';
const themes = {
light: {
theme: 'light',
variables: {
colorBackground: '#ffffff',
colorText: '#1a1a1a',
colorTextPlaceholder: '#767676',
colorBorder: '#cccccc',
colorBorderFocus: '#0b7a75',
colorDanger: '#c62828',
},
},
dark: {
theme: 'dark',
variables: {
colorBackground: '#1a1a1a',
colorText: '#f2f2f2',
colorTextPlaceholder: '#8b8b8b',
colorBorder: '#444444',
colorBorderFocus: '#4da3ff',
colorDanger: '#ff7b72',
},
},
} as const;
const cardBox = ref<HTMLElement | null>(null);
const card = ref<CardElement | null>(null);
const media = ref<MediaQueryList | null>(null);
const pick = (dark: boolean) => themes[dark ? 'dark' : 'light'];
const follow = (e: MediaQueryListEvent) => card.value?.update({ appearance: pick(e.matches) });
onMounted(async () => {
const client = await loadN1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
if (!client || !cardBox.value) return;
media.value = window.matchMedia('(prefers-color-scheme: dark)');
card.value = client.elements().create('card', { appearance: pick(media.value.matches) });
card.value.mount(cardBox.value);
media.value.addEventListener('change', follow);
});
onBeforeUnmount(() => {
media.value?.removeEventListener('change', follow);
card.value?.unmount();
});
</script>
<template>
<div ref="cardBox"></div>
</template>| Opción | Valores | Por defecto | Qué controla |
|---|---|---|---|
theme |
'light', 'dark' |
'light' |
Colores base y texto secundario fijo. |
colorBackground |
#rgb o #rrggbb |
según theme: #ffffff, #1c2128 |
Fondo del campo. |
colorText |
#rgb o #rrggbb |
#1f2328, #e6e8eb |
Texto escrito. |
colorTextPlaceholder |
#rgb o #rrggbb |
#8a8f98, #7d8590 |
Texto de ejemplo. |
colorBorder |
#rgb o #rrggbb |
#c9ced6, #444c56 |
Borde. |
colorBorderFocus |
#rgb o #rrggbb |
#2f6feb, #539bf5 |
Borde con foco. |
colorDanger |
#rgb o #rrggbb |
#c62828, #ff7b72 |
Mensajes de error. |
fontFamily |
'system', 'sans-serif', 'serif', 'monospace' |
'system' |
Tipografía. |
fontSizeBase |
12 a 20 px (16 o '16px') |
15 px | Tamaño base del texto. |
borderRadius |
0 a 16 px | 6 px | Esquinas. |
spacingUnit |
2 a 12 px | 4 px | Espaciado. |
El contraste se exige: una apariencia inválida lanza invalid_options de forma síncrona en create() o update().
Ajusta colorBackground al fondo del contenedor donde va el campo para que se integre.