> ## Documentation Index
> Fetch the complete documentation index at: https://docs.ozura.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Bank Payments — Using Elements

> Embed secure bank account fields in your own form and tokenize in the browser. Account data never touches your server.

# Bank Payments — Using Elements

**Ozura Elements** lets you embed account number and routing number fields directly in your own page. Input fields run inside isolated iframes served from Ozura's domain — raw bank data never leaves those iframes. Tokenization happens in the browser; you receive a vault token that your backend can use to process an ACH payment with your processor.

<Warning>
  **OzuraPay does not currently support bank account payments.** The token you receive is for use with your own ACH-capable payment processor via the [Proxy](/guides/vault/proxy/overview).
</Warning>

<Info>
  **Prefer a hosted page?** See [Checkout](/guides/vault/bank-payments/checkout) (merchants only). Need to tokenize from your backend? See [Using the API](/guides/vault/bank-payments/using-the-api).
</Info>

***

## How It Works

```mermaid theme={null}
flowchart LR
  A["1. INSTALL\nAdd @ozura/elements\nto your page"] --> B["2. RENDER\nMount bank fields\nin your form"]
  B --> C["3. SUBMIT\nSDK tokenizes\nin the browser"]
  C --> D["4. PROCESS\nBackend uses token\nwith your ACH processor"]
```

Account and routing numbers travel directly from the browser to the Ozura Vault — your server only ever sees a token.

***

## Install the SDK

<CodeGroup>
  ```bash npm theme={null}
  npm install @ozura/elements
  ```

  ```bash yarn theme={null}
  yarn add @ozura/elements
  ```

  ```html Script tag (CDN) theme={null}
  <script src="https://elements.ozura.com/oz-elements.umd.js"></script>
  ```
</CodeGroup>

***

## Quick Start

### 1. Get your credentials

Get your **Vault API Key** from [ozuravault.com](https://ozuravault.com) → your project → Applications.

Your first vault project is a **test project** by default. To create a production project, use the project dropdown in the top-left → **Create new project** → enable the **Production** switch.

| Credential        | Required        | Where                                                                                                 |
| ----------------- | --------------- | ----------------------------------------------------------------------------------------------------- |
| **Vault API Key** | Always          | ozuravault.com → Applications                                                                         |
| **Vault Pub Key** | Production only | Provided when you set up a production project — contact **[ammar@ozura.com](mailto:ammar@ozura.com)** |

<Note>
  **Test/sandbox vault API key?** No pub key needed — you can omit `pubKey` entirely. **Production vault API key?** Pass your production pub key as `pubKey`. Do not hardcode credentials in source code; use environment variables.

  See [Sandbox](/guides/sandbox) for how to create a Test project at ozuravault.com and grab a test vault key.
</Note>

### 2. Initialize and mount bank fields

```html theme={null}
<!DOCTYPE html>
<html>
<head>
  <script type="module">
    import { OzVault } from 'https://elements.ozura.com/oz-elements.esm.js';

    // pubKey is only required for production vault API keys.
    // Omit it entirely when using a test/sandbox vault API key.
    let vault;
    try {
      vault = await OzVault.create({
        pubKey:     'YOUR_PUB_KEY',         // production vault keys only — remove this line for test vault keys
        sessionUrl: '/api/oz-session',
      });
    } catch (err) {
      document.getElementById('submit-btn').textContent = 'Payment unavailable';
      document.getElementById('submit-btn').disabled = true;
      throw err;
    }

    // Create bank elements
    const accountEl = vault.createBankElement('accountNumber');
    const routingEl = vault.createBankElement('routingNumber');

    // Mount into your DOM
    accountEl.mount('#account-number');
    routingEl.mount('#routing-number');

    // Tokenize on submit
    document.getElementById('submit-btn').addEventListener('click', async () => {
      const firstName = document.getElementById('first-name').value;
      const lastName  = document.getElementById('last-name').value;

      const { token, bank } = await vault.createBankToken({ firstName, lastName });

      // bank.last4              — last 4 digits of account number (safe to display)
      // bank.routingNumberLast4 — last 4 digits of routing number (safe to display)

      // Send token to your backend
      await fetch('/api/ach-charge', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ token }),
      });
    });
  </script>
</head>
<body>
  <input id="first-name" placeholder="First name" />
  <input id="last-name"  placeholder="Last name" />
  <div id="account-number"></div>
  <div id="routing-number"></div>
  <button id="submit-btn">Submit</button>
</body>
</html>
```

### 3. Process the payment on your backend

Your backend receives the `token` and forwards it to your ACH processor via the Proxy:

```bash theme={null}
curl -X POST https://api.ozuravault.com/proxy/transaction \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_VAULT_API_KEY" \
  -d '{
    "token": "tok_...",
    "proxy_url": "https://api.yourachprocessor.com/payments",
    "request_data": {
      "account_number": "${accountNumber}",
      "routing_number": "${routingNumber}",
      "first_name":     "${first_name}",
      "last_name":      "${last_name}",
      "amount":         5000
    }
  }'
```

***

## Token Response

`createBankToken()` resolves with:

```ts theme={null}
{
  token: string;        // vault token — pass to your backend
  bank?: {
    last4:              string; // last 4 digits of account number (safe to display)
    routingNumberLast4: string; // last 4 digits of routing number (safe to display)
  };
}
```

***

## Enabling the Submit Button

Use `change` events to gate your submit button until both fields are complete and valid:

```js theme={null}
const state = { accountReady: false, routingReady: false };

function updateButton() {
  document.getElementById('submit-btn').disabled =
    !(state.accountReady && state.routingReady);
}

accountEl.on('change', ({ complete, valid }) => {
  state.accountReady = complete && valid;
  updateButton();
});

routingEl.on('change', ({ complete, valid }) => {
  state.routingReady = complete && valid;
  updateButton();
});
```

Elements validate in real-time:

* **Account number** — 4–17 digits; masked like a password field to prevent shoulder surfing
* **Routing number** — Exactly 9 digits, validated against the standard ABA checksum algorithm

***

## Field Validation & Error Handling

```js theme={null}
accountEl.on('change', ({ complete, valid, error }) => {
  const wrap   = document.getElementById('account-wrap');
  const status = document.getElementById('account-error');

  wrap.classList.toggle('complete', complete && valid);
  wrap.classList.toggle('invalid',  !valid && error != null);
  status.textContent = error ?? '';
});
```

Error messages returned in the `error` field:

* `"Invalid account number"` — fewer than 4 digits or not all digits
* `"Invalid routing number"` — not 9 digits or fails ABA checksum

***

## React Integration

For most integrations, use the pre-built `OzBankCard` component (or the individual `OzBankAccountNumber` / `OzBankRoutingNumber` components). See the [React SDK reference](/sdks/elements/react) for the full prop API.

```tsx theme={null}
import { OzElements, OzBankCard, useOzElements } from '@ozura/elements/react';

function BankForm() {
  const { createBankToken, ready } = useOzElements();
  const handleSubmit = async () => {
    const { token } = await createBankToken({ firstName: 'Jane', lastName: 'Doe' });
    // send token to backend
  };
  return (
    <>
      <OzBankCard />
      <button disabled={!ready} onClick={handleSubmit}>Link account</button>
    </>
  );
}

// Wrap in the provider. Omit `pubKey` for test vault keys.
export function BankPage() {
  return (
    <OzElements pubKey="YOUR_PUB_KEY" sessionUrl="/api/oz-session">
      <BankForm />
    </OzElements>
  );
}
```

For custom layouts where the pre-built components don't fit, use the vanilla `OzVault` API inside a `useEffect` to create and mount bank elements manually:

```tsx theme={null}
import { useEffect, useRef } from 'react';
import { OzVault } from '@ozura/elements';

export function BankForm() {
  const vaultRef       = useRef<OzVault | null>(null);
  const accountWrapRef = useRef<HTMLDivElement>(null);
  const routingWrapRef = useRef<HTMLDivElement>(null);

  useEffect(() => {
    let vault: OzVault | null = null;
    let cancelled = false;

    // Omit `pubKey` when using a test vault key from a Test project at ozuravault.com.
    OzVault.create({
      pubKey:     'YOUR_PUB_KEY',
      sessionUrl: '/api/oz-session',
    }).then(v => {
      if (cancelled) { v.destroy(); return; }
      vault = v;
      vaultRef.current = v;

      const accountEl = v.createBankElement('accountNumber');
      const routingEl = v.createBankElement('routingNumber');

      if (accountWrapRef.current) accountEl.mount(accountWrapRef.current);
      if (routingWrapRef.current) routingEl.mount(routingWrapRef.current);
    });

    return () => { cancelled = true; vault?.destroy(); };
  }, []);

  const handleSubmit = async (firstName: string, lastName: string) => {
    if (!vaultRef.current) return;
    const { token } = await vaultRef.current.createBankToken({ firstName, lastName });
    // send token to backend
  };

  return (
    <div>
      <div ref={accountWrapRef} />
      <div ref={routingWrapRef} />
    </div>
  );
}
```

***

## Styling

Bank elements accept the same `style` config as card elements:

```js theme={null}
const style = {
  base:        { color: '#1a1a2e', fontSize: '15px', padding: '12px 14px', backgroundColor: '#f8fafc' },
  focus:       { backgroundColor: '#ffffff', caretColor: '#6366f1' },
  invalid:     { color: '#ef4444' },
  complete:    { color: '#22c55e' },
  placeholder: { color: '#94a3b8' },
};

const accountEl = vault.createBankElement('accountNumber', { style });
const routingEl = vault.createBankElement('routingNumber', { style });
```

See the [Styling guide](/sdks/elements/styling) for the full property reference.

***

## Next Steps

<CardGroup cols={2}>
  <Card title="Elements SDK Reference" icon="code" href="/sdks/elements/overview">
    Complete API reference — all methods, options, events, and TypeScript types.
  </Card>

  <Card title="Styling & Appearance" icon="palette" href="/sdks/elements/styling">
    Customize fonts, colors, and states to match your brand.
  </Card>

  <Card title="Proxy" icon="arrow-right-left" href="/guides/vault/proxy/overview">
    Forward the bank token to your ACH processor.
  </Card>

  <Card title="API Reference — Tokenize" icon="terminal" href="/api-reference/vault/tokenize">
    Token response shape and all tokenize options.
  </Card>
</CardGroup>
