{"_id":"@atoapayments/pay-embed","name":"@atoapayments/pay-embed","dist-tags":{"latest":"0.0.1"},"versions":{"0.0.1":{"name":"@atoapayments/pay-embed","version":"0.0.1","private":false,"type":"module","description":"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 ","keywords":["atoa","payments","embed","checkout","chat","iframe","nextaction","sdk","browser"],"homepage":"https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/tree/main/packages/pay-embed#readme","repository":{"type":"git","url":"git+https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework.git","directory":"packages/pay-embed"},"bugs":{"url":"https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/issues"},"author":{"name":"Atoa and contributors"},"license":"MIT","sideEffects":false,"main":"./dist/index.cjs","module":"./dist/index.js","types":"./dist/index.d.ts","unpkg":"./dist/index.global.js","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","require":"./dist/index.cjs"},"./types":{"types":"./dist/types.d.ts","import":"./dist/types.js","require":"./dist/types.cjs"},"./package.json":"./package.json"},"publishConfig":{"access":"public"},"scripts":{"build":"tsup","test":"node --test test/*.test.ts","typecheck":"tsc --noEmit"},"devDependencies":{"@types/node":"^22.0.0","tsup":"^8.0.0","typescript":"^5.5.0"},"gitHead":"7b2d5d12976b92eea65eb45932badc2729ed5936","_id":"@atoapayments/pay-embed@0.0.1","_nodeVersion":"24.19.0","_npmVersion":"11.17.0","dist":{"integrity":"sha512-9loXZ0djkPrq9O48I5YUJIb3RjYdny/svIkGoeUJXyIMjjBqO47DEDQxVnQ4D7xsERu0gIRpTfkrKnpbwLrslg==","shasum":"b253d38a4a7ac6ee350dfe5a8f6fe0b646fcbdc5","tarball":"https://registry.npmjs.org/@atoapayments/pay-embed/-/pay-embed-0.0.1.tgz","fileCount":15,"unpackedSize":170741,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQDzFTKDIBGoj+OB18k9450rWTB0aIsS0cwJCiU9QvOAwQIhALsKyqaPTRcM/T21V9sfAXPQhHcFLzm/2Rh52itVpzYF"}]},"_npmUser":{"name":"atoalicence","email":"licence@paywithatoa.co.uk"},"directories":{},"maintainers":[{"name":"shariqueatoa","email":"sharique@paywithatoa.co.uk"},{"name":"rvkrish","email":"vamsi@paywithatoa.co.uk"},{"name":"tushargupta224","email":"tushar@paywithatoa.co.uk"},{"name":"atoalicence","email":"licence@paywithatoa.co.uk"},{"name":"anandtanu","email":"tanushree@paywithatoa.co.uk"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/pay-embed_0.0.1_1787654008050_0.8475600911202381"},"_hasShrinkwrap":false}},"time":{"created":"2026-08-25T10:33:27.849Z","0.0.1":"2026-08-25T10:33:28.197Z","modified":"2026-08-25T10:33:28.711Z"},"maintainers":[{"name":"shariqueatoa","email":"sharique@paywithatoa.co.uk"},{"name":"rvkrish","email":"vamsi@paywithatoa.co.uk"},{"name":"tushargupta224","email":"tushar@paywithatoa.co.uk"},{"name":"atoalicence","email":"licence@paywithatoa.co.uk"},{"name":"anandtanu","email":"tanushree@paywithatoa.co.uk"}],"description":"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 ","homepage":"https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/tree/main/packages/pay-embed#readme","keywords":["atoa","payments","embed","checkout","chat","iframe","nextaction","sdk","browser"],"repository":{"type":"git","url":"git+https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework.git","directory":"packages/pay-embed"},"author":{"name":"Atoa and contributors"},"bugs":{"url":"https://github.com/ATOAPaymentsLimited/AtoaAgenticFramework/issues"},"license":"MIT","readme":"# `@atoapayments/pay-embed`\n\nMount an **existing** Atoa payment into any container — a chat bubble, a card, a sheet —\nas a cross-origin iframe that announces its own height.\n\n**Zero dependencies.** ~8 KB brotli for the `<script>`-tag build, less when a bundler\nminifies the ESM entry. It is on the host's critical path, so it buys nothing it does\nnot need.\n\n## Install\n\nWith a bundler:\n\n```bash\nnpm i @atoapayments/pay-embed\n```\n\n```ts\nimport { AtoaUI } from '@atoapayments/pay-embed';\n```\n\nOr no install at all — one script tag, one `mount()` call, events back. `@0` floats within the 0.x line\nso docs and demos stay current; **in production pin an exact version** (`@atoapayments/pay-embed@0.1.0`)\n— a payment surface must not change under you between deploys.\n\n```html\n<div id=\"checkout\"></div>\n\n<script src=\"https://unpkg.com/@atoapayments/pay-embed@0/dist/index.global.js\"></script>\n<script>\n  const { AtoaUI } = window.AtoaUI; // the tag exposes the package's exports as `window.AtoaUI`\n\n  AtoaUI.mount({ paymentRequestId: 'pr_…' }, {\n    container: document.getElementById('checkout'),\n    environment: 'sandbox',            // or 'production'\n    onPaymentCompleted: (e) => console.log('completed:', e.data),  // UX signal — your server polling is the truth\n    onEvent: (e) => console.log('event:', e.type),                 // every domain event, known or future\n  });\n</script>\n```\n\nA result carrying only a `paymentRequestId` and no `nextAction` is a first-class boot path,\nnot a degenerate one — pass the bare id (as above, or as `paymentRequestId` in the options)\nand the payment surface mounts.\n\n**Framing is open by design.** Any page may frame the checkout — production, localhost, a\nnotebook, a sandboxed widget — and there is nothing to register. `clientId` is **optional**:\na publishable identifier that says *who* is integrating and authorises nothing on its own —\nomit it until Atoa issues you one.\nWhat authorises is the server-minted `paymentRequestId`/`clientSecret` your backend created\nwith its API key, and the customer's own OTP or passkey — a ceremony that runs inside the\nframe on Atoa's origin, where the credentials are bound. A page that frames the embed\nwithout a payment it minted has framed an empty surface. The merchant **API key must never\nreach the browser**. Approval secrets travel only in the URL **fragment**, never the query\nstring.\n\n```ts\nimport { AtoaUI } from '@atoapayments/pay-embed';\n\nconst ui = AtoaUI.mount(result, {\n  container: bubbleEl,\n  clientId,\n  environment: 'sandbox',\n  theme: { mode: 'auto' },\n  onPaymentCompleted: (e) => confirmBooking(e.data),\n  onPaymentFailed: (e) => showRetry(e.data.reason),\n  onUiState: (s) => chip.set(s),\n});\n```\n\n`result` is whatever an Atoa tool or SDK method returned, in either casing. `mount` reads\n`result.nextAction`, picks the surface, escalates to a popup when `must_escalate`, and\ndegrades to a link when the action is unknown.\n\n## What it refuses to do\n\n- **It cannot create a payment.** There is no create verb in this package. `mount()`\n  takes an existing `paymentRequestId` — that is what makes double-spend impossible at\n  the UI layer: every re-render, double-tap and chat reload resolves to the same payment.\n- **One live mount per order.** Pass `orderId` and a second `mount()` returns the\n  existing handle instead of a second iframe.\n- **It never opens a bank with `window.open()`.** The hop is an anchor, rendered inside\n  the frame, always. An anchor click *is* a navigation, so popup blockers, sandbox flags\n  and in-app browsers have nothing to block.\n- **It never posts to `'*'`.** The child's `atoa:hello` is the single exception, it\n  carries a frame id and a version, and everything after the handshake is pinned to the\n  exact origin that replied.\n- **It never trusts a claimed origin.** `MessageEvent.origin`, compared as an exact\n  string. No prefix match, no `endsWith('.atoa.me')`.\n- **It does not gate on who is framing.** Framing is open; the security boundary is the\n  server-minted payment capability and the customer's SCA on Atoa's origin, not the\n  identity of the page around the frame.\n\n## The child owns its height\n\nThis is the one deliberate divergence from `@atoapayments/agentic-payment-approvals-js`, which is\notherwise the direct precedent for this package. There, the *caller* sizes the container\nand the iframe fills 100%×100%. Here the child announces `resize` and the host applies it,\nbecause a chat bubble cannot be pre-sized.\n\n## Three levels of control\n\n**Level 1 — automatic.** `AtoaUI.mount(result, { container, environment })`.\n\n**Level 2 — explicit placement.** Switch on `nextAction.type` yourself and use\n`componentFor()` to decide the surface. Components take the whole action, never spread\nprops, so adding a field to `data` never changes a signature.\n\n**Level 3 — your own UI** over the exported primitives (`watchStatus`, `parseNextAction`,\n`hostedUrl`). What your own UI can never hold is **card fields and the approval\ncredential** — that is the surface law, not a product decision: a secret entered outside\nan Atoa origin changes our PCI scope and yours.\n\n## `nextAction` → surface\n\n| `type` | Component | Surface |\n|---|---|---|\n| `PAY` | `AtoaCheckout` | inline iframe |\n| `AUTHORIZE_BANK` | `AtoaCheckout` | inline iframe |\n| `COLLECT_CARD` | `AtoaCard` | **sheet** — 590 px floor, never inline |\n| `APPROVE` / `APPROVAL` | `AtoaApproval` | inline iframe |\n| `AUTHORIZE_CONTRACT` | `AtoaContract` | inline iframe |\n| `VERIFY_CONTACT` | `AtoaApproval` | inline iframe |\n| `ENROL_CREDENTIAL` | `AtoaApproval` | **popup, always** — Safari cannot `credentials.create()` cross-origin |\n| `AWAIT` | `AtoaStatusChip` | native |\n| `NONE` | `AtoaReceipt` | native |\n| *anything else* | **`AtoaFallback`** | native — `fallback.text` + `fallback.url`. Never throws |\n\n`must_escalate: true` overrides the table and forces a popup: the SDK does not try an\niframe first and fail.\n\n**`AtoaFallback` is the entire forward-compatibility contract.** A server can ship a new\naction type before any SDK knows about it. `APPROVAL` stays a valid alias for `APPROVE`\nforever.\n\n## The fixtures are the contract\n\n`test/fixtures/next-action/*.json` — one per catalogue type, plus `unknown-type.json`,\nplus `approval-legacy-alias.json` (exactly what `agentic-payments-service` emits **today**,\nwith none of the envelope). Each records the wire payload and the parsed camelCase form.\n\nSprint 2's service work and the Python `to_render_spec()` parity suite are verified\nagainst these exact files. **Changing a fixture is changing the wire contract.**\n\n## Theming\n\n`theme: { mode }` — `'auto' | 'light' | 'dark'`. That is the whole surface.\n\nColours, fonts, radius and density are not settable, by design. A payment surface a host\ncan restyle is one a host can make look like something it is not, and the trust the frame\ncarries is the product. Brand treatment, when it ships, will be issued per business from\nthe backend against a verified merchant — never accepted from the embedding page.\n\nAnything else passed in `theme` is dropped, so an older host degrades to the default\nrather than breaking.\n\n## Clients must not strip the iframe attributes\n\n```\nallow=\"publickey-credentials-get; otp-credentials; payment\"\nsandbox=\"allow-scripts allow-forms allow-popups allow-popups-to-escape-sandbox\n         allow-same-origin allow-top-navigation-by-user-activation\"\n```\n\nRemoving `allow-popups` kills the hand-off; removing `allow` kills passkeys and OTP\nautofill. The loader logs a warning if it detects either was altered after mount.\n\n## Events\n\nOutcomes arrive as `resource.event` domain events — one dictionary (the same one\nwebhooks will share when they arrive in a later phase). Each named callback gets a versioned envelope; `onEvent` fires for **every**\ndomain event after its named callback, including types newer than this SDK — that is the\nforward-compatibility contract on the event surface.\n\n```ts\nAtoaUI.mount(result, {\n  container,\n  environment: 'sandbox',\n  onPaymentCompleted: (e) => confirmBooking(e.data),      // { status, reason?, … }\n  onPaymentExpired:   (e) => offerNewLink(e.resourceId),\n  onContractActivated:(e) => enableAutopay(e.resourceId),\n  onEvent:            (e) => log(e.type, e),              // the catch-all — always fires\n});\n```\n\nThe envelope:\n\n```ts\n{\n  specVersion: 1,                    // bumps only on a breaking shape change\n  type: 'payment.completed',         // payment.* | contract.* | approval.* | future types\n  resource: 'payment',\n  resourceId: 'pr_…',                // null when the SDK holds only a secret (approvals)\n  occurredAt: '2026-08-23T12:00:00.000Z',\n  livemode: false,                   // true only against production\n  data: { status: 'COMPLETED', … }\n}\n```\n\nNamed callbacks exist for `payment.completed/failed/cancelled/expired`,\n`contract.activated/declined` and `approval.approved/declined/expired`. Everything else\n(`approval.superseded`, `contract.revoked`, whatever ships next) reaches `onEvent` only.\n\nUI signals live apart from the domain dictionary: `onUiState(phase)` reports the frame's\nown phase machine, `onError` the API/bridge failures. Neither is a settlement fact.\n\nThese events are UX signals. **Your server polling is the truth** — confirm with\n`payment.get` / `awaitSettled` before fulfilling an order, never from a client event.\nWebhooks arrive in a later phase.\n","readmeFilename":"README.md","_rev":"1-9885728e43ef50f5bb942168475dae65"}