Use this file to discover all available pages before exploring further.
Card elements let you embed secure card input fields in your own page. Each field runs in a cross-origin iframe — raw card data never exists in your JavaScript or reaches your server.
OzVault.create() is the async static factory — call it once when your checkout loads. It resolves once the session is initialized; mount elements immediately after. The vault is ready to tokenize once onReady fires (or vault.isReady === true). Gate your submit button on both vault readiness and field readiness.
import { OzVault, OzError } from '@ozura/elements';let vault;try { vault = await OzVault.create({ pubKey: 'YOUR_PUB_KEY', sessionUrl: '/api/oz-session', });} catch (err) { // Pub key blocked, session endpoint failed, or backend unreachable console.error('Vault init failed:', err instanceof OzError ? err.message : err); // Show fallback UI — do not proceed return;}const cardNumber = vault.createElement('cardNumber');const expiry = vault.createElement('expirationDate');const cvv = vault.createElement('cvv');// Mount by CSS selector or HTMLElement referencecardNumber.mount('#card-number');expiry.mount('#expiry');cvv.mount('#cvv');
You can also pass element options at creation time:
Call createToken() after the user has filled all fields. The SDK transmits the field values directly to the Ozura Vault API — raw data never passes through your code.
{ token: string; // vault token cvcSession: string; // CVC session ID — always present on success; pass to cardSale card?: { last4: string; // last 4 digits of card number brand: string; // 'visa' | 'mastercard' | 'amex' | 'discover' | … expMonth: string; // '01'–'12' expYear: string; // '2026', '2027', … }; billing?: BillingDetails; // echoed back (validated + normalized) if you passed billing}
cvcSession is always present on a successful tokenization. The SDK validates this field before resolving createToken() — if it is absent the promise rejects with an OzError (errorCode: 'server'), which would indicate a vault misconfiguration. Always forward both token and cvcSession to your charge endpoint:
cvcSession lifecycle. The cvcSession is a short-lived vault credential that allows your server to retrieve the CVC value during a cardSale call. It shares the parent session TTL (default 30 minutes). It is safe to briefly persist it server-side — e.g. in a Redis key with a short TTL, or in a signed server-session — between the tokenization call and the charge call (seconds to minutes is fine). Do not store it long-term or log it. The vault invalidates the CVC credential after it is consumed by cardSale, so it is single-use.
The SDK normalizes state to its standard 2-letter abbreviation for US and Canadian addresses. Full names (e.g. "California", "British Columbia") and 2-letter codes are both accepted. For country: 'US', the state must be a valid US state, territory (PR, GU, VI, AS, MP), or military address code (AE, AP, AA) — other values are rejected. For country: 'CA', the state must be a valid Canadian province code.
cardSale requires more billing fields than tokenize-only. The Pay API enforces minLength: 1 on billing fields — passing an empty string "" causes a validation error even if the field is technically optional. The SDK strips absent optional fields automatically (omits the key rather than sending ""). However, in practice cardSale typically requires email, phone, and a full address to process the charge. If any of these are missing, the Pay API will return a validation error.Recommendation: For charge flows, collect email, phone, and full address from the customer and pass them in BillingDetails. For tokenize-only flows (no charging), firstName and lastName are the only fields required by the vault.
Each element emits events you can listen to with .on():
cardNumber.on('change', (event) => { // event.empty — boolean: true when the field has no input // event.complete — boolean: field has reached the required length/format (may still be invalid — check `valid`) // event.valid — boolean: value passes full validation (e.g. Luhn check, expiry in future) // event.error — string | undefined: human-readable error message // event.cardBrand — string: 'visa' | 'mastercard' | 'amex' | … (cardNumber only)});expiry.on('change', (event) => { // event.month — string: parsed month '01'–'12' (expirationDate only) // event.year — string: parsed 2-digit year e.g. '27' (expirationDate only)});
The expiry onChange event includes month and year values. These are parsed from the masked expiry input and delivered to your handler for display purposes (e.g. showing the detected expiry date). If your PCI scope requires zero cardholder data on the merchant page, do not read or log these fields.
cardNumber.on('focus', () => { /* field received focus */ });cardNumber.on('blur', (data) => { /* field lost focus; data = { empty, complete, valid, error } */ });cardNumber.on('ready', () => { /* iframe loaded and is interactive */ });cardNumber.on('loaderror', ({ elementType, error }) => { /* iframe failed to load */ });
When the checkout component unmounts, call vault.destroy() to remove all iframes and listeners. In vanilla JS use a cancel flag to handle the async nature of OzVault.create():
let cancelled = false;let vault: OzVault | null = null;OzVault.create({ pubKey: 'YOUR_PUB_KEY', sessionUrl: '/api/oz-session',}).then(v => { if (cancelled) { v.destroy(); return; } vault = v; // mount elements...});// On unmount:function cleanup() { cancelled = true; vault?.destroy();}
// Focus a field programmaticallycardNumber.focus();// Clear a fieldcardNumber.clear();// Check if the iframe is readyif (cardNumber.isReady) { /* safe to proceed */ }// Get the element typecardNumber.type; // 'cardNumber'// Tear down the iframe and remove listenerscardNumber.destroy();
onReady fires before OzVault.create() resolves. Vault initialization runs multiple steps in parallel. The onReady callback can fire while await OzVault.create(...) is still pending — meaning vault is undefined inside the callback. Never reference vault inside onReady. Declare your readiness flags before calling create() so they exist when the callback runs. See the example below for the correct pattern.
A complete working example: HTML page with card fields, Express backend with /api/oz-session and /api/charge routes, and the full tokenize → charge flow.
// server.jsimport express from 'express';import { Ozura, OzuraError, getClientIp, createSessionMiddleware } from '@ozura/elements/server';const app = express();app.use(express.json());// Serve the SDK from the same origin for localhost CDN-free loadingapp.get('/oz-elements.esm.js', (_, res) => res.sendFile('node_modules/@ozura/elements/dist/oz-elements.esm.js', { root: '.' }));const ozura = new Ozura({ merchantId: process.env.MERCHANT_ID, apiKey: process.env.MERCHANT_API_KEY, vaultKey: process.env.VAULT_API_KEY,});// Session route — called automatically by the frontend SDKapp.post('/api/oz-session', createSessionMiddleware(ozura));// Charge route — called after tokenizationapp.post('/api/charge', async (req, res) => { try { const { token, cvcSession, billing } = req.body; // Always fetch the authoritative amount from your own database const amount = '49.00'; const result = await ozura.cardSale({ token, cvcSession, amount, currency: 'USD', billing, clientIpAddress: getClientIp(req), }); // Store result.transactionId — required for refunds res.json({ success: true, transactionId: result.transactionId }); } catch (err) { const status = err instanceof OzuraError ? (err.statusCode || 500) : 500; const message = err instanceof OzuraError ? err.message : 'Internal server error'; res.status(status).json({ error: message }); }});app.listen(3000, () => console.log('Listening on http://localhost:3000'));
This example serves the SDK locally to avoid CDN CORS issues on localhost. In production, use the CDN URL or your bundler — remove the /oz-elements.esm.js static route. See Installation → CDN for details.