Embedded Checkout

By default, when you start a Neon checkout, the user is redirected to a Neon-hosted page. This helps ensure that you can remain PCI-compliant, since no sensitive data is entered on your site.

Alternatively, you can embed a Neon checkout in your website using an iframe and our JavaScript wrapper, @neonpay/js. This lets your users complete their purchase without ever leaving your site (except in cases where a full-page redirect is required—more on that later).

1. Overview of the Flow

  1. Create a checkout on your server using your secret API key.
  2. Initialize the checkout on the client using your public client key.
  3. Mount the checkout into your page with a CSS selector.
  4. Listen for events such as a successful purchase.
  5. Handle return redirects from payment providers like PayPal.

2. Server: Create a Checkout

Use your server to create a checkout with your Neon API key by calling POST /checkout:

import fetch from 'node-fetch';

export async function createCheckout() {
  const YOUR_DOMAIN = 'http://localhost:3000';
  
  const response = await fetch('https://api.neonpay.com/checkout', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-Api-Key': process.env.NEON_API_KEY,
    },
    body: JSON.stringify({
      items: [
        {
          sku: "BIG_SWORD",
          name: "A big sword",
          quantity: 1,
          imageUrl: "https://placehold.co/600x400",
          price: 299
        }
      ],
      returnUrl: "${YOUR_DOMAIN}/checkout",
      accountId: "account-id",
      languageLocale: "en-US",
      playerCountry: "US",
      currency: "USD"
    }),
  });

  const data = await response.json();
  return data.checkoutId;
}

items contains the list of Item objects the user wants to purchase:

    • sku is a unique code you can use to fulfill the purchase to the user's account. See Updating Your Game for more on the fulfillment process.
    • name is the display name of the item, localized to the player's preferred language.
    • quantity is the number of this item they want to purchase.
    • price is the price of the checkout, set to 100x the base unit of the currency. See Currencies for more details.
    • Optionally, you can set imageUrl to a link to an image that we can use to display the item during checkout.
    • Optionally, you can set subtitle and highlightedSubtitle to display additional information about the item.
  • returnUrl is the URL you want the user to return to after completing a redirect-based payment flow. (More on this below.)
  • languageLocale and playerCountry are used to geo-locate the user and offer the appropriate translations and payment methods.
    • languageLocale is an IETF BCP 47 tag, e.g. en-US, that's used to show the correct language for all text in the checkout form.
    • playerCountry is an ISO 3166-1 country code, e.g. US, that's used to show the correct payment methods for the user, and also to ensure that the user's input payment method originates from the same country they are physically in.
  • accountId is a unique account ID that's used to fulfill the purchase to the correct account. See Updating Your Game for more on the fulfillment process.
  • Optionally, you can set externalReferenceId and externalMetadata for use in the fulfillment process. Neon includes it in the purchase completion and in analytics events, but otherwise doesn't make use of it.

3. Client: Embed the Checkout

Add an empty mount point in your HTML:

<div id="checkout-container"></div>

Initialize and mount the checkout in your JavaScript, and listen for events:

import { init } from '@neonpay/js';

const client = init({ clientKey: 'your_client_key' });

function startCheckout(checkoutId, fromRedirect = false) {
  const checkout = client.startEmbeddedCheckout({ checkoutId, fromRedirect });
  checkout.mount('#checkout-container');

  checkout.on('purchase.completed', ({ purchaseId }) => {
    console.log('Checkout completed:', purchaseId);
    // redirect to success page, show confirmation, etc.
  });
}

const response = await fetch('/api/create-checkout');
const { checkoutId } = await response.json();
startCheckout(checkoutId);

Including @neonpay/js

You can include @neonpay/js in one of two ways:

npm (Recommended)

The recommended approach to including Neon.js in your project is by using the module loader provided as an npm package. The package makes it easy to load the script tag in your application and includes TypeScript definitions. See the package documentation for details.

# npm
npm install --save @neonpay/js

# yarn
yarn add @neonpay/js

<script> Tag

Alternatively, you can include the script tag directly on your site.

<script src="https://js.poweredbyneon.com/v1/neon.js"></script>
<script>
  const neon = new window.Neon("{{client key}}");
  console.log(neon);
</script>

In general, we recommend loading neon.js asynchronously, by adding async or defer to the <script> tag. However, note that doing so will mean that the Neon global may not be immediately available (since it's loaded only after Neon.js fully loads), so your scripts will need to handle the possibility that it's not yet defined.


4. Client: Handle Checkout Events

We emit two events that you can listen to, and optionally action on.

purchase.completed

On payment success, we fire the purchase.completed event with the purchase ID of the completed checkout. You can use this ID to call the GET /purchase endpoint to show the user an order summary, or you can redirect back to the game or your store.

checkout.amounts_updated

Whenever any of the checkout amounts change, we fire the checkout.amount_updated event with the following data:

  • itemTotal: the total of the items in the checkout (item.price x item.quantity for each item in the cart)
  • subtotalAmount: the total amount minus taxes
  • taxAmount: the sales taxes on this transaction
  • totalAmount: the total amount the user will be charged

5. Client: Handle Redirect-Based Payment Methods

Some payment methods (like PayPal) require a full-page redirect. Neon handles this automatically when the user selects such a method. Here's how it works:

  1. We redirect the user from your site to the payment provider.
  2. After payment, the user is returned to the returnUrl you supplied.
  3. On that return page, you'll receive the checkout ID as ?checkout_id=... in the query string.
  4. Pass that ID to startEmbeddedCheckout with fromRedirect: true.

We’ll read the status of the checkout and either:

  • Emit the same purchase.completed event as before, or
  • Show an error state inside the iframe.

(There’s no need to create a new checkout in this case.)

Here's an example integration, modifying checkout.js above to handle both flows:

import { init } from '@neonpay/js';

const client = init({ clientKey: 'your_client_key' });

function startCheckout(checkoutId, fromRedirect = false) {
  const checkout = client.startEmbeddedCheckout({ checkoutId, fromRedirect });
  checkout.mount('#checkout-container');

  checkout.on('purchase.completed', ({ purchaseId }) => {
    console.log('Checkout completed:', purchaseId);
    // redirect to success page, show confirmation, etc.
  });
}

// Look for a checkout ID in the URL (e.g., after a redirect)
const urlParams = new URLSearchParams(window.location.search);
const redirectedCheckoutId = urlParams.get('checkoutId');

if (redirectedCheckoutId) {
  startCheckout(redirectedCheckoutId, true);
} else {
  const response = await fetch('/api/create-checkout');
  const { checkoutId } = await response.json();
  startCheckout(checkoutId);
}

Note that in this case, returnUrl should be set to the same URL as the page that hosts the checkout iframe. You can also set it to a different, more specific URL; however, you'll need to handle the checkout the same way as described above.