n1coePayDocs

Client SDKs

Web

n1co.js and @n1co/elements, n1co's card fields on your page.

Browser · n1co.js or npm

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

pay.html
<script src="https://elements.n1co.com/v1/n1co.js"></script>
<script>
  const client = n1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });
</script>

Web

Terminal
npm install @n1co/elements
pnpm add @n1co/elements
yarn add @n1co/elements
bun add @n1co/elements
n1co.ts
import { loadN1co } from '@n1co/elements';

// null during server-side rendering: call it in the browser.
const client = await loadN1co('pk_3x4mpl3pUbl1shabl3k3yAAAAAAAAAAA', { environment: 'sandbox' });

Vue / Nuxt

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

pay.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

card.ts
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

Card.vue
<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.

Type to search the documentation.