npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@comergent/checkout-js

v0.0.6

Published

Loads the Comergent checkout wrapper, so a Buy now button opens the source merchant's checkout without leaving your page.

Readme

@comergent/checkout-js

Opens the source merchant's checkout from your Buy now button, without the buyer leaving your page.

npm install @comergent/checkout-js
import { loadComergent } from "@comergent/checkout-js";

const comergent = await loadComergent({ publishableKey: "cpk_…" });

buyNowButton.addEventListener("click", () => {
  comergent.buyNow({
    store: "storeedt",
    brand: "brand-storeedt",
    sourceVariantId: "43750038667363",
  });
});

All three are already in the product payload you fetch for the PDP, and each is sent exactly as it is written:

| Option | Read from | | ----------------- | ------------------------------------ | | store | custom.source_shop (product) | | brand | custom.brand_handle (product) | | sourceVariantId | custom.source_variant_id (variant) |

store names the merchant who fulfils; brand names what you displayed. On a marketplace they differ — one merchant carries many brands — and neither can be derived from the other, because brand-<shop>-<vendor> cannot be split back into a shop when both halves may contain hyphens.

brand is optional: omit it rather than guess. store also accepts the older brand-<slug> spelling.

Two steps, if you want the click to be instant

buyNow above does two things: it mints a checkout link on the merchant's store, then opens it. The first is a network round trip the buyer spends watching a holding page. Split it and that wait disappears:

// On page load, or on hover — anywhere but inside the click.
const checkout = await comergent.createCheckout({
  store: "storeedt",
  brand: "brand-storeedt",
  sourceVariantId: "43750038667363",
  quantity: 1,
});

buyNowButton.addEventListener("click", () => {
  comergent.buyNow({ checkout });
});

Pass the whole checkout object, not checkout.checkoutUrl. It carries the session id that completion events are keyed by, and its URL must reach Shopify byte-for-byte — a rebuilt one is served a clone of the cart.

Both shapes work forever. Use the one-step form when a click that waits is fine, and the two-step form when it is not.

Requirements

The popup is Shopify's Checkout Kit, which the wrapper loads at runtime — not something you install. Its constraints are yours:

  • Browsers: evergreen Chromium and Firefox, Safari 16.4+. It needs <dialog>, customElements and AbortController.
  • Anything older: use display: "redirect", which needs none of them.
  • While a checkout is open your page is covered by Checkout Kit's scrim and nothing on it is clickable. Released on both completion and abandonment.

Checkout Kit is an alpha on a next dist-tag and Shopify say breaking changes may land in any release. We pin an exact version and verify a bump against a real purchase — which is the other reason the wrapper is not in this package.

Popup size

Defaults to 480 x 780. Set Shopify's documented custom properties on the element, or pass dimensions explicitly — the explicit ones win.

shopify-checkout {
  --shopify-checkout-dialog-width: 700;
  --shopify-checkout-dialog-height: 500;
}
await loadComergent({ publishableKey: "cpk_…", popup: { width: 640, height: 900 } });

The one rule

Do not await anything between the click and buyNow.

// WRONG — the popup will be blocked
button.addEventListener("click", async () => {
  await trackAnalytics();
  comergent.buyNow({ store, sourceVariantId });
});

A browser only allows a popup to open during a user gesture, and an await spends it. Do the loading up front, as above, and keep the handler synchronous. buyNow returns nothing precisely so there is nothing to await.

createCheckout does return a promise, and the same rule covers it — await it before the click, never inside:

// WRONG — same blocked popup, one step further back
button.addEventListener("click", async () => {
  const checkout = await comergent.createCheckout({ store, sourceVariantId });
  comergent.buyNow({ checkout });
});

React

const [comergent, setComergent] = useState(null);

useEffect(() => {
  loadComergent({ publishableKey: "cpk_…" }).then(setComergent);
}, []);

// Disabled until it is ready, so the handler never has to await.
<button
  disabled={!comergent}
  onClick={() => comergent.buyNow({ store, sourceVariantId, quantity })}
>
  Buy now
</button>;

Calling loadComergent repeatedly is free: one script tag, one shared promise. StrictMode double-invoking your effect changes nothing.

createCheckout(options)

Mints the checkout link. Resolves with { sessionId, provider, checkoutUrl, clientReference? }.

| Option | Default | What it does | | ----------------- | ------- | ------------------------------------------------------ | | store | — | Required. The product's custom.source_shop. | | brand | none | The product's custom.brand_handle. | | sourceVariantId | — | Required. The variant's custom.source_variant_id. | | quantity | 1 | 1 to 100. | | clientReference | none | Your own id for the cart, echoed back to you. | | discount | none | A coupon code, applied at the checkout. See below. | | buyer | none | Prefills the merchant's checkout. See below. |

A link stays valid, so there is no need to mint one per click. One the buyer never uses costs nothing but an abandoned cart on the merchant's store.

buyNow(options)

Takes a checkout from above, or everything createCheckout takes and mints one itself.

| Option | Default | What it does | | ------------ | --------- | ------------------------------------------------------------- | | checkout | none | What createCheckout resolved with. Pass it whole. | | display | "popup" | "redirect" sends the buyer to the checkout in the same tab. | | onComplete | none | Fired after the buyer pays. Cosmetic — see below. | | onClose | none | The checkout window is no longer in front of the buyer. |

Plus every createCheckout option, when you are not passing a checkout.

Prefilling the buyer

comergent.createCheckout({
  store,
  sourceVariantId,
  buyer: {
    email: "[email protected]",
    phone: "+91 98765 43210",
    address: { countryCode: "IN", firstName: "Priya", city: "New Delhi", zip: "110016" },
  },
});

Everything is optional and only what you send is filled in — except countryCode, which is required whenever you send an address and must be the two-letter ISO code in capitals ("IN", never "India" or "in"). It selects the market and so the prices, which is why we never guess one: without it the address is dropped and the buyer types it.

Anything Shopify would refuse — a blank string, an unparseable email — is dropped rather than sent, so bad details cost you the prefill and never the sale. These are never stored, logged, or put in a URL.

Coupons

comergent.buyNow({ store, sourceVariantId, discount: "HT500" });

The code rides on Shopify's own cart permalink, so the source merchant's store decides whether it is honoured — exactly as if the buyer had typed it. One code, no comma: Shopify separates multiple codes with one, and whether two combine is a setting in that merchant's admin.

A code that store does not recognise is ignored, not refused. The checkout opens and the buyer pays full price, and nothing tells you it happened — so a coupon you show must also exist in that merchant's Shopify admin, not only in whatever you read it from.

onClose is not "the buyer gave up"

Checkout Kit fires it when the popup is dismissed by the buyer, by our code, or on focus loss. It means the checkout window is no longer in front of them, which is not the same as abandonment.

onComplete is not proof of an order

It arrives over the browser, which means anyone with a developer console can fire it. Never fulfil, count, bill or credit anything from it. Use it to refresh your own UI and nothing else. Order truth lives in the source merchant's Shopify admin.

loadComergent(options)

Returns a promise for the wrapper, or null when there is no window — so it is safe to call from code that also runs during server-side rendering.

| Option | Default | What it does | | ---------------- | -------------------- | -------------------------------------- | | publishableKey | — | Required. Identifies you to Comergent. | | scriptSrc | Comergent production | Point at a staging deployment. |

What this package is not

It does not contain the checkout wrapper. It inserts the script tag that fetches it, and resolves once that wrapper has installed itself — the same shape as @stripe/stripe-js, which does not contain Stripe.js either.

That means you do not reinstall this package to get our updates. Fixes and improvements to the wrapper reach your users on their next page load. You would only upgrade this package if the loader's own API changed, which should be close to never.

It also means you must not vendor, bundle or self-host the wrapper. Doing so freezes it into your build, so a fix on our side would need a release on yours — and the checkout protocol underneath is Shopify's, which moves on their schedule.

Content Security Policy

Two origins, if you send one — ours for the wrapper, and jsDelivr, which is where the wrapper pulls Shopify's Checkout Kit at runtime:

script-src 'self' https://ht-tech-shop-now.comergent.ai https://cdn.jsdelivr.net;

The checkout runs in a popup window rather than in your document, so no frame-src is needed.