Orchestr.js reference
Orchestr.js is the browser SDK behind card payments. It mounts the secure card fields, exchanges the entered card for a token, confirms the payment, and renders 3-D Secure — while your page never sees a card number.
Install
Always load the SDK from our CDN — never bundle or self-host it. The pinned-major URL means a security fix reaches your payment page without you redeploying, and PCI DSS v4's script-integrity duties (6.4.3, 11.6.1) remain on Orchestr's side:
<script src="https://js.upprove.com/v1/orchestr.js"></script>
For bundlers, @orchestr/card-js is a tiny loader that injects that same tag and gives you full
TypeScript types:
npm install @orchestr/card-js
import {loadOrchestr} from '@orchestr/card-js';
const Orchestr = await loadOrchestr();
Orchestr(publishableKey)
Creates a client. The key's prefix selects the environment — a pk_test_ key can never reach
production:
| Key prefix | Environment |
|---|---|
pk_test_ | Sandbox — test cards only. |
pk_live_ | Live. |
Passing a secret key (sk_…) throws immediately: secret keys must never reach the browser.
| Method | Description |
|---|---|
orchestr.elements(options?) | A new element group. Accepts {locale}. |
orchestr.collectCard(clientSecret) | Collect the mounted card, resolve with a token. |
orchestr.confirmCardPayment({orderId, token}) | Confirm the payment; renders 3-D Secure if required. |
The card element
const card = orchestr.elements().create('card');
card.mount('#card-element');
await card.ready; // resolves once the secure fields have loaded
One combined element renders the number, expiry, and CVC fields. It lives in an iframe on Orchestr's origin, sized automatically to its content.
| Member | Description |
|---|---|
mount(target) | Mount into a selector or element. Throws if the target is missing. |
unmount() | Detach; the element can be mounted again. |
destroy() | Terminal teardown — release the element and all listeners. |
ready | Promise resolving when the fields are interactive. Await before enabling Pay. |
update({locale}) | Change display options in place. |
clear() / focus() / blur() | Field utilities. |
on(event, handler) / off(event, handler) | Subscribe / unsubscribe. |
Events
| Event | Payload | Fires when… |
|---|---|---|
ready | — | The secure fields have loaded and are interactive. |
change | {complete, error, fields} | Validity changes. Enable your Pay button on complete. |
focus / blur | {field} | A field gains or loses focus. |
submitting | — | A collection has started (UX hint — show a spinner). |
loaderror | {error} | The fields failed to load (usually a CSP issue — see below). |
collectCard(clientSecret)
Submits the card from inside the secure fields and exchanges it for a durable token. The
clientSecret is the next_action.client_secret from your server's POST /v1/payments call — a
short-lived grant scoped to that one payment.
The same method also serves the collection leg of a card transfer, where the
client secret comes from POST /v1/transfers and the token it mints is payout-scoped rather than
charge-scoped. Don't call confirmCardPayment() on that flow — a transfer is settled by your server
with POST /v1/transfers/{orderId}/execute.
const {card, error} = await orchestr.collectCard(clientSecret);
Resolves with either card or error, never throws on a decline:
card field | Description |
|---|---|
token | The card token (tok_…). Opaque, non-sensitive — safe to store and send to your server. |
brand | Card scheme (e.g. visa) — null until identified. |
last4 / bin | Display-safe digits. |
expMonth / expYear | Expiry. |
On failure the fields keep their contents so the customer can correct and retry. collectCard is
never retried automatically.
confirmCardPayment({orderId, token})
Confirms the payment with the collected token. When the issuer requires 3-D Secure, the SDK renders the challenge in your page and completes the flow — you write no 3DS code.
const {payment, error} = await orchestr.confirmCardPayment({orderId, token});
payment.status mirrors the payment status and can be
any of completed, failed, authorized (a manual-capture hold),
settling, canceled, or requires_action. Don't branch on completed alone — treat anything
that isn't failed as "not yet refused" and wait for the webhook.
Treat the value as a display hint only — the signed
payment.* webhooks are the authoritative result, and the
only safe trigger for fulfilment.
How the challenge is presented
When 3-D Secure is required, how the SDK mounts the challenge is a per-account setting, surfaced to
the SDK as a display_mode of inline, popup, or redirect. redirect navigates the whole
window away from your page, so don't rely on your page's JavaScript state surviving the challenge
— re-read the payment on return.
display_mode and challenge_url are browser-SDK concerns and never appear on the Merchant API. If
you want to render 3-D Secure yourself from your own server, see
server-to-server card payments instead.
Errors
Every failure resolves with one shape:
{
"type": "validation_error",
"code": "CLIENT_SECRET_EXPIRED",
"message": "…",
"correlationId": "…"
}
type | Meaning |
|---|---|
validation_error | Field-level problems, raised before anything leaves the browser. |
authentication_error | The client_secret was rejected or has expired. |
api_error | Orchestr failed to process the request. |
network_error | The request never reached Orchestr. |
integration_error | Developer misuse — these throw instead of resolving, and always mean a bug in the integration. |
Branch on type, key your customer-facing copy off code, and quote correlationId in support
tickets. A client_secret lives 15 minutes; an expired one fails fast with
CLIENT_SECRET_EXPIRED — create a new payment to mint a fresh one.
Content-Security-Policy
If your page sets a CSP, allow the SDK script, the secure-fields frame, and the confirm call:
| Directive | Live | Sandbox |
|---|---|---|
script-src | https://js.upprove.com | https://js.upprove.com |
frame-src | https://fields.card.upprove.com | https://fields.sandbox.card.upprove.com |
connect-src | https://api.upprove.com | https://api.sandbox.upprove.com |
A missing frame-src is the usual cause of a loaderror with code IFRAME_BLOCKED.
Using with React
No bindings package is needed — mount in an effect and destroy on unmount:
function CardField({orchestr, onReady}) {
const ref = useRef(null);
useEffect(() => {
const card = orchestr.elements().create('card');
card.mount(ref.current);
card.ready.then(onReady, () => {});
return () => card.destroy(); // required: React 18 StrictMode mounts twice
}, []);
return <div ref={ref} />;
}
Next steps
- Card payments — the end-to-end integration guide.
- Webhooks — fulfil on
payment.succeeded, never on the browser result.