Client SDKs
Web
n1co.js and @n1co/elements, n1co's card fields on your page.
Runs in the buyer's browser, on an https page; for development http://localhost and http://127.0.0.1 are accepted too (add them to page_origins if you use it).
Install
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 during server-side rendering: call it in the browser.
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 during server-side rendering; in Nuxt you can also wrap this component in <ClientOnly>.
client.value = await loadN1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
if (!client.value || !cardBox.value) return;
card.value = client.value.elements().create('card', { locale: 'en' });
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 {
// Your endpoint creates the intent with your secret_key and returns only its 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') {
// Your server confirms with the webhook before fulfilling the order.
window.location.assign('/checkout/thanks');
} else if (intent.status === 'processing') {
message.value = 'We are confirming your payment. Do not pay again; we will let you know.';
} else {
message.value = intent.last_error?.message ?? 'The payment could not be completed.';
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">Pay $12.50</button>
</form>
</template>| Option | Values | Default | Where |
|---|---|---|---|
environment |
'production', 'sandbox' |
'production' |
n1co(), loadN1co() |
locale |
'es', 'en' |
'es' |
create('card', …), create('cvc', …) |
Methods
Client
| Method | What it does | Returns |
|---|---|---|
n1co(publishableKey, options?) |
Creates the client with your publishable_key. |
The client |
loadN1co(publishableKey, options?) |
Loads n1co.js from npm and creates the client. | Promise of the client, null during SSR |
elements().create('card', options?) |
Creates the card form. | Card element |
elements().create('cvc', options?) |
Creates the security code of a saved card. | Cvc element |
confirmPayment({ intent_token, card?, cvc?, billing? }) |
Charges the intent with a new card, the attached one or its code; handles 3D Secure. | Promise of the intent in a final state |
retrievePaymentIntent(intent_token) |
Reads the intent as it is now. | Promise of the intent |
Element
| Method | What it does | Returns |
|---|---|---|
mount(selectorOrElement) |
Inserts the iframe. | The element |
unmount() |
Removes the iframe and discards what was typed. | The element |
on(event, handler) |
Adds a handler for ready, change, focus, blur, error or capture. |
The element |
off(event, handler) |
Removes a handler. | The element |
focus(field?) |
Focuses a field. | The element |
clear() |
Empties the fields. | The element |
update({ appearance?, locale? }) |
Changes the appearance or the language. | The element |
capture({ session_token }) |
Saves the card under the session; card element only. | Promise of { ok: true, card } |
Appearance
Pass appearance when you create the field. update() merges variables without removing earlier ones: when switching themes, send the full appearance. What the buyer typed is kept.
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',
},
},
};
// Follows the OS theme; update() always gets the full appearance.
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;
// Follows the OS theme; update() always gets the full appearance.
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>| Option | Values | Default | What it controls |
|---|---|---|---|
theme |
'light', 'dark' |
'light' |
Base colors and fixed secondary text. |
colorBackground |
#rgb or #rrggbb |
by theme: #ffffff, #1c2128 |
Field background. |
colorText |
#rgb or #rrggbb |
#1f2328, #e6e8eb |
Typed text. |
colorTextPlaceholder |
#rgb or #rrggbb |
#8a8f98, #7d8590 |
Placeholder text. |
colorBorder |
#rgb or #rrggbb |
#c9ced6, #444c56 |
Border. |
colorBorderFocus |
#rgb or #rrggbb |
#2f6feb, #539bf5 |
Border when focused. |
colorDanger |
#rgb or #rrggbb |
#c62828, #ff7b72 |
Error messages. |
fontFamily |
'system', 'sans-serif', 'serif', 'monospace' |
'system' |
Typeface. |
fontSizeBase |
12 to 20 px (16 or '16px') |
15 px | Base text size. |
borderRadius |
0 to 16 px | 6 px | Corners. |
spacingUnit |
2 to 12 px | 4 px | Spacing. |
Contrast is enforced: an invalid appearance throws invalid_options synchronously from create() or update().
Set colorBackground to the background of the container the field sits on so it blends in.