Introducing NanoPay.js - Web Payments Made Easy

Edit on CodePen

https://codepen.io/nano2dev/pen/VwRQypE

Introduction

Inspired by Apple Pay ™, NanoPay is a open source, non-custodial javascript library for the Nano blockchain.

Easily accept crypto payments on your website(s) with this package.

Installation

First, add NanoPay to your project's index.html.

CDN:

<script src="https://cdn.nano.to/pay.js"></script>

Local:

<script src="/NanoPay.js"></script>

Download latest version.

Simple Usage

Simple payments can be achieved with basic HTML. No javascript required.

<a 
data-title="Tip @Nano2Dev" 
data-amount="0.133" 
data-address="@development" 
data-button="Open Natrium" 
data-position="bottom">
Tip 0.133 NANO</a>

Javascript API

Configure and open popup programmatically with NanoPay.open() method.

NanoPay.open({ 
  title: "Demo",
  address: '@development', // Username or Nano Address
  notify: '[email protected]', // get an email receipt
  contact: true, // user's email
  shipping: 10, // user's shipping address, use 'true' for free shipping
  currency: 'EUR', // converts prices EUR, default USD
  // amount: 30, // not used if using line_items
  line_items: [
    { name: "Shirt (X-Small)", price: 50 }, 
    { name: "Mens Shoes (9.5)", price: 20 }
  ],
  success: (block) => {
      console.log(block)
      // {
      //     "hash": "D16FF348B634CDB3DF8...9D6F5B180CCD3B93F99A4D15203",
      //     "amount": "NANO_AMOUNT_PAID",
      //     "address": "PAYEE_NANO_ADDRESS",
      //     "username": "PAYEE_USERNAME",
      //     "height": "PAYEE_BLOCK_HEIGHT",
      //     "shipping": "PAYEE_SHIPPING_ADDRESS",
      //     "email": "PAYEE_EMAIL_ADDRESS",
      //     "nanolooker": "https://nanolooker.com/block/D16FF348B634CDB3DF8...9D6F5B180CCD3B93F99A4D15203"
      //     "checkout": "https://api.nano.to/checkout/93be1ab9",
      // }
  },
  cancel: () => {
      console.log("User cancelled")
  },
})

All Options

  • title: (string) Show custom title shown in popup.
  • address: (string) Nano address or Nano.to @Username.
  • contact: (string) User's email address.
  • shipping: (number or bool) User's mailing address. 'true' for free shipping.
  • position: (string) Control popup position (top, center, bottom).
  • wallet: (string) Custom wallet icon: natrium, nault, nautilus, cake
  • button: (string) Custom button text, default "Open Natrium".
  • symbol: (string) Custom NANO symbol, default "NANO".
  • random: (bool) Use unique payment amounts, default true.
  • notify: (string) Send email notification to admin, default false.
  • amount: (string) Custom popup amount, used for simple payments.
  • line_items: (array) List of products user is buying, replaces amount.
  • currency: (string) Fiat currency for Nano price conversion, default "USD".
  • note: (string) Custom note shown in admin email, default false.
  • qrcode: (string) Always show QR Code, default desktop only.
  • success: (function) Called when payment is successful.
  • cancel: (function) Called when popup is cancelled.
  • checkout: (string) Use custom Nano.to Checkout. Read More.
  • email: (string) Pre-configure user's email
  • mailing_address: (string) Pre-configure user's shipping address

Notifications

NanoPay offers email notifications for your convenience.

NanoPay.open({ 
  address: '@bank', // Username or Nano Address
  notify: '[email protected]',
  contact: true,
  shipping: true,
  amount: 0.133
})

Email Notification

Login with Nano

NanoPay makes Login with Nano easy.

NanoPay.open({ 
  title: "Login",
  address: '@bank', // Your App's Address
  amount: 0.0001, // Small Amount
  success: (block) => {
      // {
      //     "hash": "D16FF348B634CDB3DF8...9D6F5B180CCD3B93F99A4D15203",
      //     "address": "PAYEE_NANO_ADDRESS",
      //     "username": "PAYEE_USERNAME",
      //      ...
      // }
      console.log("Hello:", block.username || block.address)
  }
})

HTML Content Paywall

NanoPay includes an easy way to monetize any website client-side.

Please note, this method does not require a back-end, as such it's not for keeping secrets from public. Google bots can still crawl content.

<style>
/* This css prevents flash of content on page load */
.premium { display: none; } 
</style>
<article class="premium">
  Premium content is hidden until the payment is completed.
</article>
NanoPay.wall({ 
    element: '.premium',
    title: 'Read Story',
    button: 'Unlock Story', 
    free: true, // Allow free reading
    amount: 0.001, // Cost of Article
    address: '@development', // Your Nano Address or Username
    success: (block) => {
      // do stuff like render highlight.js
      console.log("Thanks for reading:", block.username || block.address)
    }
})

Premium content protected by the paywall.

I work on Nano part-time. Please consider helping fund this kind of work. Anything helps.

Protecting Premium Content with a Backend

The client-side wall above is useful for demos and simple donations, but it is not content protection. The article HTML is already in the browser, so a reader can inspect the source without paying.

The source repository is part of the security boundary too. Do not keep paid Markdown in a public repository or public build context. Use a private repository, a private content store, or an encrypted content workflow whose decryption key is available only to the server-side build/runtime. Removing the body from docs/ does not protect a source file that anyone can read on GitHub.

For real premium content, keep the article body on the server and use NanoPay only as the payment interface. A secure flow has four parts:

  1. The backend selects the article and its fixed price. Never accept the price or article body from the browser.
  2. The backend creates a checkout at https://rpc.nano.to and returns the checkout object plus a short-lived, signed capability tied to that checkout and article.
  3. The frontend opens NanoPay with the returned checkout. Its success callback is only a signal to ask the backend for access; it is not proof of payment.
  4. The backend independently checks the checkout and returns the article only when the response is confirmed complete and contains a block.

The checkout endpoint should use server-owned values and metadata:

const checkout = await fetch('https://rpc.nano.to', {
  method: 'POST',
  headers: { 'content-type': 'application/json' },
  body: JSON.stringify({
    action: 'checkout',
    address: env.PREMIUM_ADDRESS,
    amount: article.amount,
    payment_monitor: true,
    metadata: { type: 'article', slug: article.slug }
  })
})

Do not put PREMIUM_SECRET, a private key, or an unredacted checkout token in frontend code, Markdown, screenshots, or source control. Store signing keys in the platform secret manager. On Cloudflare Pages, for example:

npx wrangler pages secret put PREMIUM_SECRET --project-name your-project

The content endpoint verifies the signed capability, asks the checkout API for the current payment state, and rejects pending or detected payments:

const capability = verifyCapability(request, env.PREMIUM_SECRET)
if (!capability || capability.slug !== params.slug) return new Response(null, { status: 401 })

const payment = await fetch(`https://rpc.nano.to/check/${capability.checkoutId}`)
  .then(response => response.json())

if (payment.status !== 'complete' || !payment.block) {
  return Response.json({ status: payment.status || 'pending' }, { status: 402 })
}

return Response.json({ content: await loadPrivateArticle(params.slug) }, {
  headers: { 'cache-control': 'no-store' }
})

The browser can then combine that backend with NanoPay without treating local storage, a success callback, a block hash supplied by the browser, or a hidden HTML element as an entitlement:

fetch(`/api/articles/${slug}/checkout`, { method: 'POST' })
  .then(response => response.json())
  .then(({ checkout, token }) => NanoPay.open({
    title: 'Unlock Article',
    checkout,
    success: () => fetch(`/api/articles/${slug}/content`, {
      headers: { authorization: `Bearer ${token}` }
    })
  }))

Use cache-control: no-store for checkout capabilities and unlocked content. Add platform rate limiting to checkout creation, keep premium source files out of public assets and public source repositories, and make every fulfillment check idempotent. A public checkout URL is not an entitlement by itself; only the server-side confirmed payment check should grant access.

Nano.to Support

Bugs, Questions & Installation Support: