@atoapayments/agentic-payment-approvals-js embeds that hosted page inside a container you provide and resolves to the
decision, so the approver stays in your own web UI instead of following a link away. It’s the browser companion to the
nextAction you get back from payment.send / payment.collect — an alternative to delivering the raw approvalUrl.
Browser/TypeScript only, zero dependencies. If you’re on the Python SDK — or have no web layer at all — deliver the
approvalUrl from nextAction instead (see SCA on a payout); this SDK is just a
nicer surface for the same approval, not a separate one.Install
Use
When a gated call returns anextAction, pass its clientSecret to confirmApproval along with a container you
render. You get back an ApprovalHandle: await handle.result for the decision, handle.on(…) to follow the
lifecycle, and handle.destroy() to close it.
Options
confirmApproval(options) → ApprovalHandle.
Theming
theme accepts a small, bounded set of branding tokens applied as CSS custom properties — not freeform CSS. Each
token is hex/length-validated; malformed tokens are dropped. The two critical contrast pairs (button-text vs primary,
text vs background) are contrast-clamped to ~4.5:1 — a failing pair falls back to the accessible default, so you can
never render an invisible decline button or white-on-white amount.
Events
Every event flows through bothonEvent 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.
opened and closed are emitted by the SDK; everything else originates on the Atoa page. error does not settle
result — the page may recover — so it’s up to you to destroy() if you want to give up.
Result & terminal statuses
result (and onResult) resolve exactly once, with { status }:
A declined or expired approval also comes back on the
Payment itself — failureReason: APPROVAL_DECLINED /
APPROVAL_EXPIRED — so a purely server-side integration behaves identically. Use onEvent / on(…) for everything
between opened and the terminal status.
When to call destroy()
Call destroy() when the approver closes your UI without deciding — a backdrop tap, a swipe-away, or the component
unmounting. It removes the iframe and resolves result as CANCELLED.
After a real decision you don’t need it: the SDK cleans up on its own, and calling destroy() then does nothing.