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/agentic-payment-approvals-js

v0.0.4

Published

Zero-dependency browser SDK to surface an Atoa payment-approval — open the hosted approval page (popup, modal, or redirect) and resolve the human decision. The decision credential (OTP, passkey) is only ever collected on the Atoa origin; host apps forward

Readme

@atoapayments/agentic-payment-approvals-js

Keep the human approval of a gated payment inside your own web UI, instead of sending the user off to a redirect. When an Atoa Agent Pay payment.send(...) or payment.collect(...) needs Strong Customer Authentication, it returns a nextAction with a clientSecret. Hand that secret to confirmApproval and this SDK mounts Atoa's hosted approval page as an iframe inside a container you provide — the approver enters their one-time code or passkey right there, and you get back the decision.

npm version dependencies types license

This is the browser companion to the nextAction that Atoa Agent Pay returns on a gated payment. It is not a payments SDK and is separate from @atoapayments/agent-pay: its one job is to surface the approval UI. It is zero-dependency and framework-agnostic (React is an optional peer, not required).

There is exactly one surface: the mounted iframe. You own the chrome around it — render the container inline, inside your own modal, a bottom sheet, wherever — and the iframe fills it 100% x 100%. No popup windows, no full-page redirects.

The decision credential (OTP, passkey) is only ever collected on the Atoa origin. Your app provides a container and forwards the clientSecret; it never re-skins the approval or sees the code. That boundary is the security model — don't work around it.

Install

npm i @atoapayments/agentic-payment-approvals-js

Ships ESM + CJS + a .d.ts, plus a unpkg global build for a plain <script> tag.

Quickstart

import { confirmApproval } from '@atoapayments/agentic-payment-approvals-js';

// `payments` came from a gated `atoa.payment.send(...)` (or `.collect(...)`) in @atoapayments/agent-pay
if (payments.nextAction) {
  const approval = confirmApproval({
    container: '#approval',                      // a selector or an HTMLElement you render + size
    clientSecret: payments.nextAction.clientSecret,
    colorScheme: 'light',                        // 'light' (default) | 'dark'
    onEvent: (e) => console.log(e.type),
  });

  const { status } = await approval.result;
  // status: 'APPROVED' | 'DECLINED' | 'EXPIRED' | 'SUPERSEDED' | 'CANCELLED'

  // ...and from YOUR close affordance (backdrop click, sheet swipe-away, route change):
  approval.destroy();                            // cancels if still pending (-> CANCELLED), removes the iframe
}

You decide where #approval lives and how big it is; the iframe is 100% x 100% of it. Wrap it in your own modal or bottom sheet and the "Approve GBP X" card + code entry render inside — you never draw the approval itself.

Documentation

  • Approvals SDK — https://docs.paywithatoa.co.uk/agent-pay/approvals · Theming · Events
  • Where nextAction comes fromSCA on a payout · SCA on a charge
  • Agent Pay overview — https://docs.paywithatoa.co.uk/agent-pay/overview

confirmApproval(options) → ApprovalHandle

| Option | Default | Notes | |---|---|---| | container | required | A CSS selector or an HTMLElement. You render + size it; the iframe fills it. Throws synchronously if it doesn't resolve to an element. | | clientSecret | required | From nextAction. Env (live/sandbox) is read from its prefix (ap_live_... / ap_test_...) — no publishable key; a malformed secret throws before any iframe is created. | | colorScheme | light | 'light' or 'dark'. Anything else (incl. undefined) normalizes to light. Independent of theme. | | theme | — | Bounded branding tokens (see Theming) — hex/length-validated and contrast-clamped. Amounts, warnings, and the decline button are never restyled. | | labels | — | Enumerated approve-CTA text only (see Labels). | | onEvent | — | Fires for every lifecycle event (same stream as handle.on(...)). | | onResult | — | Terminal-only convenience — called once with the final { status }. | | apiUrl | — | Non-prod override for the hosted-page origin (e.g. http://localhost:3001). Ignored in normal use — the origin is derived from the secret's env prefix. |

ApprovalHandle — the live handle you get back:

  • result: Promise<{ status }> — resolves exactly once with the terminal decision. Never rejects.
  • on(listener) => unsubscribe — subscribe to the lifecycle stream (same events as onEvent); returns an unsubscribe function.
  • destroy() — tear down: cancel a still-pending approval (-> CANCELLED), drop listeners, remove the iframe from the DOM.

Labels

labels restyles only the approve button's verb, and only from a fixed enum — freetext is rejected (relabeling the decision away from "you are paying" is the dark-pattern vector this guards against). The page always appends the amount, so PAY renders as "Pay GBP 42.00". The decline label is fixed and never configurable.

labels: { approve: 'PAY' } // 'APPROVE' (default) | 'PAY' | 'CONFIRM' | 'AUTHORIZE'

Anything outside the enum (or a non-object) is dropped and the default APPROVE is used. Branding tokens (theme) work the same way — see Theming.

Events

Every event flows through both onEvent and every on(...) listener, as the same { type, ...payload } object — filter on e.type. Payloads are enumerated/masked only: never an OTP, a full contact, or an account number. A listener that throws is swallowed and never breaks the stream.

| type | Emitted when | Payload | Terminal? | |---|---|---|---| | opened | The instant the handle is created (iframe appended, channel listening). SDK-emitted. | — | no | | loaded | The page finished loading and resolved which CTA it will show. | method?: 'PASSKEY' \| 'SETUP_OFFERED' \| 'OTP_ONLY' | no | | approved | The human approved. | decidedBy?: string | yes -> APPROVED | | declined | The human declined. | decidedBy?: string | yes -> DECLINED | | expired | The approval window lapsed before a decision. | — | yes -> EXPIRED | | superseded | A newer approval for the same action replaced this one. | — | yes -> SUPERSEDED | | error | A recoverable page error. Non-terminal — the page may recover. | reason: string | no | | closed | You called destroy() (or it was auto-cancelled). Immediately precedes a CANCELLED result. SDK-emitted. | — | no |

error does not settle result — the promise stays pending, and it's up to you to destroy() if you want to give up. closed is not itself terminal: the terminal CANCELLED status arrives on result/onResult, never as an event type.

Result & terminal statuses

result (and onResult) resolve exactly once, with { status }. Five statuses — the first four come from the page's terminal events, the fifth only from you:

| Status | Source | |---|---| | APPROVED | approved event | | DECLINED | declined event | | EXPIRED | expired event | | SUPERSEDED | superseded event | | CANCELLED | You called destroy() before any of the above. |

result never rejects — a page error leaves it pending rather than throwing, so always await a terminal status (or drive teardown yourself via destroy()).

When to call destroy()

destroy() cancels a still-pending approval (resolving result as CANCELLED and emitting closed), drops all listeners, and removes the iframe. It is a no-op once a decision has landed. Call it from your own close affordance — the SDK draws no chrome, so nothing else can cancel for you:

  • a backdrop / overlay click, or the close button on your modal;
  • a bottom-sheet swipe-away or dismiss;
  • a route change or component unmount (useEffect cleanup, onUnmounted, etc.) — so an abandoned approval doesn't leak an iframe and a live postMessage listener.

You do not need to call it after a terminal decision — the SDK self-cleans on resolve. It's purely for the "user walked away without deciding" path.

Browser support, bundle size & CSP

  • Zero runtime dependencies, framework-agnostic. React is an optional peer (>=17) — used only for the ambient types; the SDK is plain DOM.
  • Works in any modern browser with iframe + postMessage. Passkey approval uses WebAuthn delegated to the cross-origin frame (publickey-credentials-get / publickey-credentials-create); where the browser doesn't support cross-origin passkey creation, the page falls back to a one-time code, so "Set up passkey" never dead-ends.
  • The hosting origin must be in the approval page's server-side frame-ancestors allowlist (per-business registered — never a wildcard). The postMessage handshake is origin-pinned: the page requests the secret and the SDK answers pinned to the page origin, so the secret never rides a URL.

Relationship to Agent Pay

This package is the browser front-end for the SCA approval that @atoapayments/agent-pay surfaces via nextAction. It is optional and independent: the same approval can be completed by handing the approver the raw approvalUrl from nextAction instead (and the Python SDK does exactly that, since this browser SDK is TypeScript-only). Use this package when you want the approval to happen inside your own web UI rather than via a redirect.

License

License. MIT. This covers the code in this package.

Service terms. Use of the Atoa API is governed by the Atoa Services Agreement: https://paywithatoa.co.uk/terms/. The MIT license applies to this SDK only and grants no rights to the Atoa service.

Trademarks. "Atoa" and the Atoa logo are trademarks of Atoa Payments Limited. The MIT license grants rights in the code, not in our names or marks — a modified or redistributed copy must not be presented as an Atoa product.

Security. Report vulnerabilities to [email protected] — please do not open a public issue.