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

@atoapayments/pay-embed

v0.0.1

Published

Zero-dependency browser loader for Atoa's chat-native checkout. Mount an existing payment into any container as a cross-origin, origin-pinned iframe that announces its own height; map a nextAction to a surface; degrade an unknown action to a sentence and

Readme

@atoapayments/pay-embed

Mount an existing Atoa payment into any container — a chat bubble, a card, a sheet — as a cross-origin iframe that announces its own height.

Zero dependencies. ~8 KB brotli for the <script>-tag build, less when a bundler minifies the ESM entry. It is on the host's critical path, so it buys nothing it does not need.

Install

With a bundler:

npm i @atoapayments/pay-embed
import { AtoaUI } from '@atoapayments/pay-embed';

Or no install at all — one script tag, one mount() call, events back. @0 floats within the 0.x line so docs and demos stay current; in production pin an exact version (@atoapayments/[email protected]) — a payment surface must not change under you between deploys.

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

<script src="https://unpkg.com/@atoapayments/pay-embed@0/dist/index.global.js"></script>
<script>
  const { AtoaUI } = window.AtoaUI; // the tag exposes the package's exports as `window.AtoaUI`

  AtoaUI.mount({ paymentRequestId: 'pr_…' }, {
    container: document.getElementById('checkout'),
    environment: 'sandbox',            // or 'production'
    onPaymentCompleted: (e) => console.log('completed:', e.data),  // UX signal — your server polling is the truth
    onEvent: (e) => console.log('event:', e.type),                 // every domain event, known or future
  });
</script>

A result carrying only a paymentRequestId and no nextAction is a first-class boot path, not a degenerate one — pass the bare id (as above, or as paymentRequestId in the options) and the payment surface mounts.

Framing is open by design. Any page may frame the checkout — production, localhost, a notebook, a sandboxed widget — and there is nothing to register. clientId is optional: a publishable identifier that says who is integrating and authorises nothing on its own — omit it until Atoa issues you one. What authorises is the server-minted paymentRequestId/clientSecret your backend created with its API key, and the customer's own OTP or passkey — a ceremony that runs inside the frame on Atoa's origin, where the credentials are bound. A page that frames the embed without a payment it minted has framed an empty surface. The merchant API key must never reach the browser. Approval secrets travel only in the URL fragment, never the query string.

import { AtoaUI } from '@atoapayments/pay-embed';

const ui = AtoaUI.mount(result, {
  container: bubbleEl,
  clientId,
  environment: 'sandbox',
  theme: { mode: 'auto' },
  onPaymentCompleted: (e) => confirmBooking(e.data),
  onPaymentFailed: (e) => showRetry(e.data.reason),
  onUiState: (s) => chip.set(s),
});

result is whatever an Atoa tool or SDK method returned, in either casing. mount reads result.nextAction, picks the surface, escalates to a popup when must_escalate, and degrades to a link when the action is unknown.

What it refuses to do

  • It cannot create a payment. There is no create verb in this package. mount() takes an existing paymentRequestId — that is what makes double-spend impossible at the UI layer: every re-render, double-tap and chat reload resolves to the same payment.
  • One live mount per order. Pass orderId and a second mount() returns the existing handle instead of a second iframe.
  • It never opens a bank with window.open(). The hop is an anchor, rendered inside the frame, always. An anchor click is a navigation, so popup blockers, sandbox flags and in-app browsers have nothing to block.
  • It never posts to '*'. The child's atoa:hello is the single exception, it carries a frame id and a version, and everything after the handshake is pinned to the exact origin that replied.
  • It never trusts a claimed origin. MessageEvent.origin, compared as an exact string. No prefix match, no endsWith('.atoa.me').
  • It does not gate on who is framing. Framing is open; the security boundary is the server-minted payment capability and the customer's SCA on Atoa's origin, not the identity of the page around the frame.

The child owns its height

This is the one deliberate divergence from @atoapayments/agentic-payment-approvals-js, which is otherwise the direct precedent for this package. There, the caller sizes the container and the iframe fills 100%×100%. Here the child announces resize and the host applies it, because a chat bubble cannot be pre-sized.

Three levels of control

Level 1 — automatic. AtoaUI.mount(result, { container, environment }).

Level 2 — explicit placement. Switch on nextAction.type yourself and use componentFor() to decide the surface. Components take the whole action, never spread props, so adding a field to data never changes a signature.

Level 3 — your own UI over the exported primitives (watchStatus, parseNextAction, hostedUrl). What your own UI can never hold is card fields and the approval credential — that is the surface law, not a product decision: a secret entered outside an Atoa origin changes our PCI scope and yours.

nextAction → surface

| type | Component | Surface | |---|---|---| | PAY | AtoaCheckout | inline iframe | | AUTHORIZE_BANK | AtoaCheckout | inline iframe | | COLLECT_CARD | AtoaCard | sheet — 590 px floor, never inline | | APPROVE / APPROVAL | AtoaApproval | inline iframe | | AUTHORIZE_CONTRACT | AtoaContract | inline iframe | | VERIFY_CONTACT | AtoaApproval | inline iframe | | ENROL_CREDENTIAL | AtoaApproval | popup, always — Safari cannot credentials.create() cross-origin | | AWAIT | AtoaStatusChip | native | | NONE | AtoaReceipt | native | | anything else | AtoaFallback | native — fallback.text + fallback.url. Never throws |

must_escalate: true overrides the table and forces a popup: the SDK does not try an iframe first and fail.

AtoaFallback is the entire forward-compatibility contract. A server can ship a new action type before any SDK knows about it. APPROVAL stays a valid alias for APPROVE forever.

The fixtures are the contract

test/fixtures/next-action/*.json — one per catalogue type, plus unknown-type.json, plus approval-legacy-alias.json (exactly what agentic-payments-service emits today, with none of the envelope). Each records the wire payload and the parsed camelCase form.

Sprint 2's service work and the Python to_render_spec() parity suite are verified against these exact files. Changing a fixture is changing the wire contract.

Theming

theme: { mode }'auto' | 'light' | 'dark'. That is the whole surface.

Colours, fonts, radius and density are not settable, by design. A payment surface a host can restyle is one a host can make look like something it is not, and the trust the frame carries is the product. Brand treatment, when it ships, will be issued per business from the backend against a verified merchant — never accepted from the embedding page.

Anything else passed in theme is dropped, so an older host degrades to the default rather than breaking.

Clients must not strip the iframe attributes

allow="publickey-credentials-get; otp-credentials; payment"
sandbox="allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox
         allow-same-origin allow-top-navigation-by-user-activation"

Removing allow-popups kills the hand-off; removing allow kills passkeys and OTP autofill. The loader logs a warning if it detects either was altered after mount.

Events

Outcomes arrive as resource.event domain events — one dictionary (the same one webhooks will share when they arrive in a later phase). Each named callback gets a versioned envelope; onEvent fires for every domain event after its named callback, including types newer than this SDK — that is the forward-compatibility contract on the event surface.

AtoaUI.mount(result, {
  container,
  environment: 'sandbox',
  onPaymentCompleted: (e) => confirmBooking(e.data),      // { status, reason?, … }
  onPaymentExpired:   (e) => offerNewLink(e.resourceId),
  onContractActivated:(e) => enableAutopay(e.resourceId),
  onEvent:            (e) => log(e.type, e),              // the catch-all — always fires
});

The envelope:

{
  specVersion: 1,                    // bumps only on a breaking shape change
  type: 'payment.completed',         // payment.* | contract.* | approval.* | future types
  resource: 'payment',
  resourceId: 'pr_…',                // null when the SDK holds only a secret (approvals)
  occurredAt: '2026-08-23T12:00:00.000Z',
  livemode: false,                   // true only against production
  data: { status: 'COMPLETED', … }
}

Named callbacks exist for payment.completed/failed/cancelled/expired, contract.activated/declined and approval.approved/declined/expired. Everything else (approval.superseded, contract.revoked, whatever ships next) reaches onEvent only.

UI signals live apart from the domain dictionary: onUiState(phase) reports the frame's own phase machine, onError the API/bridge failures. Neither is a settlement fact.

These events are UX signals. Your server polling is the truth — confirm with payment.get / awaitSettled before fulfilling an order, never from a client event. Webhooks arrive in a later phase.