@ozura/elements 1.0.2 → 1.1.0-next.22
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +234 -86
- package/dist/frame/element-frame.js +92 -20
- package/dist/frame/element-frame.js.map +1 -1
- package/dist/frame/tokenizer-frame.html +1 -1
- package/dist/frame/tokenizer-frame.js +20 -4
- package/dist/frame/tokenizer-frame.js.map +1 -1
- package/dist/oz-elements.esm.js +477 -251
- package/dist/oz-elements.esm.js.map +1 -1
- package/dist/oz-elements.umd.js +478 -251
- package/dist/oz-elements.umd.js.map +1 -1
- package/dist/react/frame/elementFrame.d.ts +70 -1
- package/dist/react/frame/protocol.d.ts +12 -0
- package/dist/react/index.cjs.js +447 -225
- package/dist/react/index.cjs.js.map +1 -1
- package/dist/react/index.esm.js +448 -226
- package/dist/react/index.esm.js.map +1 -1
- package/dist/react/react/index.d.ts +70 -26
- package/dist/react/sdk/OzVault.d.ts +55 -5
- package/dist/react/sdk/createSessionFetcher.d.ts +29 -0
- package/dist/react/sdk/index.d.ts +6 -26
- package/dist/react/server/index.d.ts +126 -74
- package/dist/react/types/index.d.ts +72 -31
- package/dist/server/frame/elementFrame.d.ts +70 -1
- package/dist/server/frame/protocol.d.ts +12 -0
- package/dist/server/index.cjs.js +167 -78
- package/dist/server/index.cjs.js.map +1 -1
- package/dist/server/index.esm.js +166 -79
- package/dist/server/index.esm.js.map +1 -1
- package/dist/server/sdk/OzVault.d.ts +55 -5
- package/dist/server/sdk/createSessionFetcher.d.ts +29 -0
- package/dist/server/sdk/index.d.ts +6 -26
- package/dist/server/server/index.d.ts +126 -74
- package/dist/server/types/index.d.ts +72 -31
- package/dist/types/frame/elementFrame.d.ts +70 -1
- package/dist/types/frame/protocol.d.ts +12 -0
- package/dist/types/sdk/OzVault.d.ts +55 -5
- package/dist/types/sdk/createSessionFetcher.d.ts +29 -0
- package/dist/types/sdk/index.d.ts +6 -26
- package/dist/types/server/index.d.ts +126 -74
- package/dist/types/types/index.d.ts +72 -31
- package/package.json +1 -1
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2025 Ozura
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 Ozura
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@ Card data is collected inside Ozura-hosted iframes so raw numbers never touch yo
|
|
|
18
18
|
- [Quick start — React](#quick-start--react)
|
|
19
19
|
- [Quick start — Vanilla JS](#quick-start--vanilla-js)
|
|
20
20
|
- [Server setup](#server-setup)
|
|
21
|
-
- [
|
|
21
|
+
- [Session endpoint](#session-endpoint)
|
|
22
22
|
- [Card sale endpoint](#card-sale-endpoint)
|
|
23
23
|
- [Vanilla JS API](#vanilla-js-api)
|
|
24
24
|
- [OzVault.create()](#ozvaultcreate)
|
|
@@ -26,7 +26,9 @@ Card data is collected inside Ozura-hosted iframes so raw numbers never touch yo
|
|
|
26
26
|
- [vault.createToken()](#vaultcreatetokenoptions)
|
|
27
27
|
- [vault.createBankElement()](#vaultcreatebankelement)
|
|
28
28
|
- [vault.createBankToken()](#vaultcreatebanktokenoptions)
|
|
29
|
+
- [vault.reset()](#vaultreset)
|
|
29
30
|
- [vault.destroy()](#vaultdestroy)
|
|
31
|
+
- [vault.debugState()](#vaultdebugstate)
|
|
30
32
|
- [OzElement events](#ozelement-events)
|
|
31
33
|
- [React API](#react-api)
|
|
32
34
|
- [OzElements provider](#ozelements-provider)
|
|
@@ -40,6 +42,7 @@ Card data is collected inside Ozura-hosted iframes so raw numbers never touch yo
|
|
|
40
42
|
- [Custom fonts](#custom-fonts)
|
|
41
43
|
- [Billing details](#billing-details)
|
|
42
44
|
- [Error handling](#error-handling)
|
|
45
|
+
- [Debug mode](#debug-mode)
|
|
43
46
|
- [Server utilities](#server-utilities)
|
|
44
47
|
- [Ozura class](#ozura-class)
|
|
45
48
|
- [Route handler factories](#route-handler-factories)
|
|
@@ -78,7 +81,7 @@ Merchant page
|
|
|
78
81
|
└── [visible] element-frame.html ← CVV ─┘
|
|
79
82
|
```
|
|
80
83
|
|
|
81
|
-
1. `OzVault.create()` mounts a hidden tokenizer iframe and fetches a short-lived **
|
|
84
|
+
1. `OzVault.create()` mounts a hidden tokenizer iframe and fetches a short-lived **session key** from your server.
|
|
82
85
|
2. Calling `vault.createElement()` mounts a visible input iframe for each field.
|
|
83
86
|
3. `vault.createToken()` opens a direct `MessageChannel` between each element iframe and the tokenizer iframe. Raw values travel over those ports — they never pass through your JavaScript.
|
|
84
87
|
4. The tokenizer POSTs directly to the vault API over HTTPS and returns a token to your page.
|
|
@@ -92,7 +95,7 @@ Your server only ever sees a token, never card data.
|
|
|
92
95
|
| Credential | Format | Where it lives | Required for |
|
|
93
96
|
|---|---|---|---|
|
|
94
97
|
| **Vault pub key** | `pk_live_…` or `pk_prod_…` | Frontend env var (safe to expose) | All integrations |
|
|
95
|
-
| **Vault API key** | `key_…` | Server env var — **never in the browser** |
|
|
98
|
+
| **Vault API key** | `key_…` | Server env var — **never in the browser** | Creating sessions (all integrations) |
|
|
96
99
|
| **Pay API key** | `ak_…` | Server env var only | OzuraPay merchants (card charging) |
|
|
97
100
|
| **Merchant ID** | `ozu_…` | Server env var only | OzuraPay merchants (card charging) |
|
|
98
101
|
|
|
@@ -124,13 +127,13 @@ npm install react react-dom # if not already installed
|
|
|
124
127
|
|
|
125
128
|
```tsx
|
|
126
129
|
// 1. Wrap your checkout in <OzElements>
|
|
127
|
-
import { OzElements, OzCard, useOzElements
|
|
130
|
+
import { OzElements, OzCard, useOzElements } from '@ozura/elements/react';
|
|
128
131
|
|
|
129
132
|
function CheckoutPage() {
|
|
130
133
|
return (
|
|
131
134
|
<OzElements
|
|
132
135
|
pubKey="pk_live_..."
|
|
133
|
-
|
|
136
|
+
sessionUrl="/api/oz-session"
|
|
134
137
|
>
|
|
135
138
|
<CheckoutForm />
|
|
136
139
|
</OzElements>
|
|
@@ -139,25 +142,30 @@ function CheckoutPage() {
|
|
|
139
142
|
|
|
140
143
|
// 2. Collect card data and tokenize
|
|
141
144
|
function CheckoutForm() {
|
|
142
|
-
const { createToken, ready } = useOzElements();
|
|
145
|
+
const { createToken, reset, ready } = useOzElements();
|
|
143
146
|
|
|
144
147
|
const handleSubmit = async (e: React.FormEvent) => {
|
|
145
148
|
e.preventDefault();
|
|
146
|
-
|
|
147
|
-
billing
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
149
|
+
try {
|
|
150
|
+
const { token, cvcSession, billing } = await createToken({
|
|
151
|
+
billing: {
|
|
152
|
+
firstName: 'Jane',
|
|
153
|
+
lastName: 'Smith',
|
|
154
|
+
email: 'jane@example.com',
|
|
155
|
+
address: { line1: '123 Main St', city: 'Austin', state: 'TX', zip: '78701', country: 'US' },
|
|
156
|
+
},
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
// Send token to your server
|
|
160
|
+
await fetch('/api/charge', {
|
|
161
|
+
method: 'POST',
|
|
162
|
+
headers: { 'Content-Type': 'application/json' },
|
|
163
|
+
body: JSON.stringify({ token, cvcSession, billing }),
|
|
164
|
+
});
|
|
165
|
+
} catch (err) {
|
|
166
|
+
reset(); // clear fields so the customer can re-enter
|
|
167
|
+
console.error(err);
|
|
168
|
+
}
|
|
161
169
|
};
|
|
162
170
|
|
|
163
171
|
return (
|
|
@@ -176,7 +184,7 @@ function CheckoutForm() {
|
|
|
176
184
|
> 📖 [Card elements guide](https://docs.ozura.com/sdks/elements/card-elements) — full end-to-end example, field events, submit-button gating, and auto-advance behaviour.
|
|
177
185
|
|
|
178
186
|
```ts
|
|
179
|
-
import { OzVault
|
|
187
|
+
import { OzVault } from '@ozura/elements';
|
|
180
188
|
|
|
181
189
|
// Declare state BEFORE OzVault.create(). The onReady callback fires when the
|
|
182
190
|
// tokenizer iframe loads — this can happen before create() resolves because the
|
|
@@ -191,7 +199,7 @@ function checkReady() {
|
|
|
191
199
|
|
|
192
200
|
const vault = await OzVault.create({
|
|
193
201
|
pubKey: 'pk_live_...',
|
|
194
|
-
|
|
202
|
+
sessionUrl: '/api/oz-session',
|
|
195
203
|
onReady: () => { tokenizerIsReady = true; checkReady(); }, // tokenizer iframe loaded
|
|
196
204
|
});
|
|
197
205
|
|
|
@@ -208,10 +216,15 @@ cvvEl.mount('#cvv');
|
|
|
208
216
|
});
|
|
209
217
|
|
|
210
218
|
async function pay() {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
219
|
+
try {
|
|
220
|
+
const { token, cvcSession, card } = await vault.createToken({
|
|
221
|
+
billing: { firstName: 'Jane', lastName: 'Smith' },
|
|
222
|
+
});
|
|
223
|
+
// POST { token, cvcSession } to your server
|
|
224
|
+
} catch (err) {
|
|
225
|
+
vault.reset(); // clear fields; let customer re-enter
|
|
226
|
+
console.error(err);
|
|
227
|
+
}
|
|
215
228
|
}
|
|
216
229
|
|
|
217
230
|
// Clean up when done
|
|
@@ -224,50 +237,43 @@ vault.destroy();
|
|
|
224
237
|
|
|
225
238
|
## Server setup
|
|
226
239
|
|
|
227
|
-
> 📖 [Server SDK guide](https://docs.ozura.com/sdks/elements/server) —
|
|
240
|
+
> 📖 [Server SDK guide](https://docs.ozura.com/sdks/elements/server) — session creation, card sale handler factories, IP extraction, and manual implementation patterns.
|
|
228
241
|
|
|
229
|
-
###
|
|
242
|
+
### Session endpoint
|
|
230
243
|
|
|
231
|
-
The SDK calls your
|
|
244
|
+
The SDK calls your session endpoint whenever it needs to start or refresh a payment session. Your backend creates a short-lived session key from the vault using your vault API key — the key never touches the browser.
|
|
232
245
|
|
|
233
246
|
**Next.js App Router (recommended)**
|
|
234
247
|
|
|
235
248
|
```ts
|
|
236
|
-
// app/api/
|
|
237
|
-
import { Ozura,
|
|
249
|
+
// app/api/oz-session/route.ts
|
|
250
|
+
import { Ozura, createSessionHandler } from '@ozura/elements/server';
|
|
238
251
|
|
|
239
|
-
const ozura = new Ozura({
|
|
240
|
-
merchantId: process.env.MERCHANT_ID!,
|
|
241
|
-
apiKey: process.env.MERCHANT_API_KEY!,
|
|
242
|
-
vaultKey: process.env.VAULT_API_KEY!,
|
|
243
|
-
});
|
|
252
|
+
const ozura = new Ozura({ vaultKey: process.env.VAULT_API_KEY! });
|
|
244
253
|
|
|
245
|
-
export const POST =
|
|
254
|
+
export const POST = createSessionHandler(ozura);
|
|
246
255
|
```
|
|
247
256
|
|
|
248
257
|
**Express**
|
|
249
258
|
|
|
250
259
|
```ts
|
|
251
260
|
import express from 'express';
|
|
252
|
-
import { Ozura,
|
|
261
|
+
import { Ozura, createSessionMiddleware } from '@ozura/elements/server';
|
|
253
262
|
|
|
254
|
-
const ozura = new Ozura({
|
|
263
|
+
const ozura = new Ozura({ vaultKey: process.env.VAULT_API_KEY! });
|
|
255
264
|
const app = express();
|
|
256
265
|
|
|
257
266
|
app.use(express.json());
|
|
258
|
-
app.post('/api/
|
|
267
|
+
app.post('/api/oz-session', createSessionMiddleware(ozura));
|
|
259
268
|
```
|
|
260
269
|
|
|
261
|
-
**Manual implementation**
|
|
270
|
+
**Manual implementation** (for custom logic or auth checks)
|
|
262
271
|
|
|
263
272
|
```ts
|
|
264
|
-
// POST /api/
|
|
273
|
+
// POST /api/oz-session
|
|
265
274
|
const { sessionId } = await req.json();
|
|
266
|
-
const {
|
|
267
|
-
|
|
268
|
-
maxTokenizeCalls: 3, // vault enforces this limit; must match VaultOptions.maxTokenizeCalls on the client
|
|
269
|
-
});
|
|
270
|
-
return Response.json({ waxKey });
|
|
275
|
+
const { sessionKey } = await ozura.createSession({ sessionId });
|
|
276
|
+
return Response.json({ sessionKey });
|
|
271
277
|
```
|
|
272
278
|
|
|
273
279
|
### Card sale endpoint
|
|
@@ -336,26 +342,29 @@ const result = await ozura.cardSale({
|
|
|
336
342
|
const vault = await OzVault.create(options: VaultOptions): Promise<OzVault>
|
|
337
343
|
```
|
|
338
344
|
|
|
339
|
-
Mounts the hidden tokenizer iframe and fetches
|
|
345
|
+
Mounts the hidden tokenizer iframe and fetches a session key concurrently. Both happen in parallel — by the time `create()` resolves, the iframe may already be ready.
|
|
340
346
|
|
|
341
347
|
| Option | Type | Required | Description |
|
|
342
348
|
|---|---|---|---|
|
|
343
349
|
| `pubKey` | `string` | ✓ | Your public key from the Ozura admin. |
|
|
344
|
-
| `
|
|
350
|
+
| `sessionUrl` | `string` | ✓ ¹ | URL of your session endpoint. The simplest option — pass the path and the SDK handles everything. |
|
|
351
|
+
| `getSessionKey` | `(sessionId: string) => Promise<string>` | ✓ ¹ | Custom async callback for obtaining the session key. Use when you need custom headers or auth logic. |
|
|
352
|
+
| `fetchWaxKey` | `(sessionId: string) => Promise<string>` | ✓ ¹ | **Deprecated.** Use `sessionUrl` or `getSessionKey` instead. |
|
|
345
353
|
| `frameBaseUrl` | `string` | — | Base URL for iframe assets. Defaults to production CDN. Override for local dev (see [Local development](#local-development)). |
|
|
346
354
|
| `fonts` | `FontSource[]` | — | Custom fonts to inject into all element iframes. |
|
|
347
355
|
| `appearance` | `Appearance` | — | Global theme and variable overrides. |
|
|
348
356
|
| `loadTimeoutMs` | `number` | — | Tokenizer iframe load timeout in ms. Default: `10000`. Only takes effect when `onLoadError` is also provided. |
|
|
349
357
|
| `onLoadError` | `() => void` | — | Called if the tokenizer iframe fails to load within `loadTimeoutMs`. |
|
|
350
|
-
| `
|
|
351
|
-
| `onReady` | `() => void` | — | Called once when the tokenizer iframe has loaded and is ready. Use in vanilla JS to re-check submit-button readiness
|
|
352
|
-
| `
|
|
358
|
+
| `onSessionRefresh` | `() => void` | — | Called when the SDK silently refreshes the session mid-tokenization (key expired or consumed). |
|
|
359
|
+
| `onReady` | `() => void` | — | Called once when the tokenizer iframe has loaded and is ready. Use in vanilla JS to re-check submit-button readiness. In React, `useOzElements().ready` handles this automatically. |
|
|
360
|
+
| `sessionLimit` | `number` | — | Card submissions allowed per session before the SDK refreshes automatically. Default: `3`. Must match `sessionLimit` in your server-side `createSession` call. |
|
|
361
|
+
| `debug` | `boolean` | — | Enables structured `[OzVault]`-prefixed `console.log` output at every lifecycle event. Safe to use in production — no sensitive data is ever logged. Default: `false`. See [Debug mode](#debug-mode) for details. |
|
|
353
362
|
|
|
354
|
-
|
|
363
|
+
¹ Exactly one of `sessionUrl`, `getSessionKey`, or `fetchWaxKey` is required.
|
|
355
364
|
|
|
356
|
-
|
|
365
|
+
Throws `OzError` if the session fetch rejects, returns an empty string, or returns a non-string value.
|
|
357
366
|
|
|
358
|
-
>
|
|
367
|
+
> **`sessionUrl` retry behavior:** The SDK enforces a **10-second per-attempt timeout** and retries **once after 750 ms on pure network failures** (connection refused, DNS failure, offline). HTTP 4xx/5xx errors are never retried — they signal endpoint misconfiguration or invalid credentials and require developer action. Errors are thrown as `OzError` instances so you can inspect `err.errorCode`.
|
|
359
368
|
|
|
360
369
|
---
|
|
361
370
|
|
|
@@ -456,7 +465,7 @@ Throws `OzError` if:
|
|
|
456
465
|
vault.tokenizeCount: number // read-only getter
|
|
457
466
|
```
|
|
458
467
|
|
|
459
|
-
Returns the number of successful `createToken()` / `createBankToken()` calls made
|
|
468
|
+
Returns the number of successful `createToken()` / `createBankToken()` calls made in the current session. Resets to `0` each time the session is refreshed (proactively or reactively). Use this in vanilla JS to display "attempts remaining" feedback or gate the submit button:
|
|
460
469
|
|
|
461
470
|
```ts
|
|
462
471
|
const MAX = 3; // matches maxTokenizeCalls
|
|
@@ -541,6 +550,65 @@ useEffect(() => {
|
|
|
541
550
|
|
|
542
551
|
---
|
|
543
552
|
|
|
553
|
+
### vault.reset()
|
|
554
|
+
|
|
555
|
+
```ts
|
|
556
|
+
vault.reset(): void
|
|
557
|
+
```
|
|
558
|
+
|
|
559
|
+
Clears all mounted card and bank element fields without destroying the vault, refreshing the session, or resetting the tokenization budget. Call this after a declined payment to let the customer re-enter their card details on the same checkout screen.
|
|
560
|
+
|
|
561
|
+
The session key, its remaining budget, and all iframes are fully preserved — no network calls are made.
|
|
562
|
+
|
|
563
|
+
**Session model:** One session covers the full checkout. The default `sessionLimit: 3` is enough for two declines and a final attempt. Use `vault.reset()` between declines — not `vault.destroy()` + recreate, which would waste the remaining budget and cause iframe flicker.
|
|
564
|
+
|
|
565
|
+
```ts
|
|
566
|
+
try {
|
|
567
|
+
const { token, cvcSession } = await vault.createToken({ billing });
|
|
568
|
+
await fetch('/api/charge', {
|
|
569
|
+
method: 'POST',
|
|
570
|
+
headers: { 'Content-Type': 'application/json' },
|
|
571
|
+
body: JSON.stringify({ token, cvcSession }),
|
|
572
|
+
});
|
|
573
|
+
} catch (err) {
|
|
574
|
+
vault.reset(); // clear fields; let customer re-enter
|
|
575
|
+
showError(err instanceof OzError ? err.message : 'Payment failed.');
|
|
576
|
+
}
|
|
577
|
+
```
|
|
578
|
+
|
|
579
|
+
---
|
|
580
|
+
|
|
581
|
+
### vault.debugState()
|
|
582
|
+
|
|
583
|
+
```ts
|
|
584
|
+
vault.debugState(): Record<string, unknown>
|
|
585
|
+
```
|
|
586
|
+
|
|
587
|
+
Returns a structured snapshot of the vault's internal state. Always available regardless of whether `debug: true` is set. Useful for attaching to support tickets or dumping on error.
|
|
588
|
+
|
|
589
|
+
```ts
|
|
590
|
+
console.log(vault.debugState());
|
|
591
|
+
// {
|
|
592
|
+
// vaultId: 'vault_abc12...',
|
|
593
|
+
// isReady: true,
|
|
594
|
+
// tokenizing: null,
|
|
595
|
+
// destroyed: false,
|
|
596
|
+
// waxKeyPresent: true,
|
|
597
|
+
// tokenizeSuccessCount: 1,
|
|
598
|
+
// maxTokenizeCalls: 3,
|
|
599
|
+
// resetCount: 0,
|
|
600
|
+
// elements: ['cardNumber', 'expirationDate', 'cvv'],
|
|
601
|
+
// bankElements: [],
|
|
602
|
+
// completionState: { 'a1b2c3d4': true, 'e5f6a7b8': true, '...' : false },
|
|
603
|
+
// pendingTokenizations: 0,
|
|
604
|
+
// pendingBankTokenizations: 0,
|
|
605
|
+
// }
|
|
606
|
+
```
|
|
607
|
+
|
|
608
|
+
No sensitive data is returned: wax keys, tokens, CVC sessions, and billing fields are never included.
|
|
609
|
+
|
|
610
|
+
---
|
|
611
|
+
|
|
544
612
|
### OzElement events
|
|
545
613
|
|
|
546
614
|
```ts
|
|
@@ -576,11 +644,11 @@ Auto-advance is built in: the vault automatically moves focus from card number
|
|
|
576
644
|
### OzElements provider
|
|
577
645
|
|
|
578
646
|
```tsx
|
|
579
|
-
import { OzElements
|
|
647
|
+
import { OzElements } from '@ozura/elements/react';
|
|
580
648
|
|
|
581
649
|
<OzElements
|
|
582
650
|
pubKey="pk_live_..."
|
|
583
|
-
|
|
651
|
+
sessionUrl="/api/oz-session"
|
|
584
652
|
appearance={{ theme: 'flat', variables: { colorPrimary: '#6366f1' } }}
|
|
585
653
|
onLoadError={() => setPaymentUnavailable(true)}
|
|
586
654
|
>
|
|
@@ -590,9 +658,9 @@ import { OzElements, createFetchWaxKey } from '@ozura/elements/react';
|
|
|
590
658
|
|
|
591
659
|
All `VaultOptions` are accepted as props. The provider creates a single `OzVault` instance and destroys it on unmount.
|
|
592
660
|
|
|
593
|
-
> **Prop changes and vault lifecycle:** Changing `pubKey`, `frameBaseUrl`, `loadTimeoutMs`, `appearance`, `fonts`, or `
|
|
661
|
+
> **Prop changes and vault lifecycle:** Changing `pubKey`, `sessionUrl`, `frameBaseUrl`, `loadTimeoutMs`, `appearance`, `fonts`, or `sessionLimit` destroys the current vault and creates a new one — all field iframes will remount. Changing `getSessionKey`, `fetchWaxKey`, `onLoadError`, `onSessionRefresh`, or `onReady` updates the callback in place via refs without recreating the vault.
|
|
594
662
|
|
|
595
|
-
> **One card form per provider:** A vault holds one element per field type (`cardNumber`, `expiry`, `cvv`, etc.). Rendering two `<OzCard>` components under the same `<OzElements>` provider will cause the second to silently replace the first's iframes, breaking the first form. If you genuinely need two independent card forms on the same page, wrap each in its own `<OzElements>` provider with separate `pubKey` / `
|
|
663
|
+
> **One card form per provider:** A vault holds one element per field type (`cardNumber`, `expiry`, `cvv`, etc.). Rendering two `<OzCard>` components under the same `<OzElements>` provider will cause the second to silently replace the first's iframes, breaking the first form. If you genuinely need two independent card forms on the same page, wrap each in its own `<OzElements>` provider with separate `pubKey` / `sessionUrl` configurations.
|
|
596
664
|
|
|
597
665
|
---
|
|
598
666
|
|
|
@@ -700,7 +768,7 @@ import { OzBankAccountNumber, OzBankRoutingNumber } from '@ozura/elements/react'
|
|
|
700
768
|
### useOzElements()
|
|
701
769
|
|
|
702
770
|
```ts
|
|
703
|
-
const { createToken, createBankToken, ready, initError, tokenizeCount } = useOzElements();
|
|
771
|
+
const { createToken, createBankToken, reset, ready, initError, tokenizeCount } = useOzElements();
|
|
704
772
|
```
|
|
705
773
|
|
|
706
774
|
Must be called from inside an `<OzElements>` provider tree.
|
|
@@ -709,9 +777,10 @@ Must be called from inside an `<OzElements>` provider tree.
|
|
|
709
777
|
|---|---|---|
|
|
710
778
|
| `createToken` | `(options?: TokenizeOptions) => Promise<TokenResponse>` | Tokenize mounted card elements. |
|
|
711
779
|
| `createBankToken` | `(options: BankTokenizeOptions) => Promise<BankTokenResponse>` | Tokenize mounted bank elements. |
|
|
780
|
+
| `reset` | `() => void` | Clear all mounted element fields without destroying the vault or refreshing the session. Call after a declined payment so the customer can re-enter their details. |
|
|
712
781
|
| `ready` | `boolean` | `true` when the tokenizer **and** all mounted element iframes are ready. Gate your submit button on this. See note below. |
|
|
713
|
-
| `initError` | `Error \| null` | Non-null if `OzVault.create()` failed (e.g.
|
|
714
|
-
| `tokenizeCount` | `number` | Number of successful tokenizations
|
|
782
|
+
| `initError` | `Error \| null` | Non-null if `OzVault.create()` failed (e.g. session endpoint unreachable). Render a fallback UI. |
|
|
783
|
+
| `tokenizeCount` | `number` | Number of successful tokenizations in the current session. Resets on session refresh or provider re-init. Useful for tracking calls against `sessionLimit`. |
|
|
715
784
|
|
|
716
785
|
> **`ready` vs `vault.isReady`:** `ready` from `useOzElements()` is a composite — it combines `vault.isReady` (tokenizer loaded) with element readiness (all mounted input iframes loaded). `vault.isReady` alone is insufficient for gating a submit button. Always use `ready` from `useOzElements()` in React.
|
|
717
786
|
|
|
@@ -764,7 +833,7 @@ Apply a preset theme and/or variable overrides to all elements at once:
|
|
|
764
833
|
// OzVault.create
|
|
765
834
|
const vault = await OzVault.create({
|
|
766
835
|
pubKey: '...',
|
|
767
|
-
|
|
836
|
+
sessionUrl: '/api/oz-session',
|
|
768
837
|
appearance: {
|
|
769
838
|
theme: 'flat', // 'default' | 'night' | 'flat'
|
|
770
839
|
variables: {
|
|
@@ -784,7 +853,7 @@ const vault = await OzVault.create({
|
|
|
784
853
|
});
|
|
785
854
|
|
|
786
855
|
// React provider
|
|
787
|
-
<OzElements pubKey="..."
|
|
856
|
+
<OzElements pubKey="..." sessionUrl="..." appearance={{ theme: 'night' }}>
|
|
788
857
|
```
|
|
789
858
|
|
|
790
859
|
Per-element `style` takes precedence over `appearance` variables.
|
|
@@ -847,7 +916,7 @@ Billing is validated and normalized by both `vault.createToken()` and the server
|
|
|
847
916
|
|
|
848
917
|
## Error handling
|
|
849
918
|
|
|
850
|
-
> 📖 [Error handling guide](https://docs.ozura.com/sdks/elements/error-handling) — `OzError` fields, every `errorCode` value, retry guidance, and
|
|
919
|
+
> 📖 [Error handling guide](https://docs.ozura.com/sdks/elements/error-handling) — `OzError` fields, every `errorCode` value, retry guidance, and session expiry behaviour.
|
|
851
920
|
|
|
852
921
|
All SDK errors are instances of `OzError`:
|
|
853
922
|
|
|
@@ -885,7 +954,7 @@ try {
|
|
|
885
954
|
| `raw` | `string` | Raw error string from the vault API, if available. |
|
|
886
955
|
| `retryable` | `boolean` | `true` for `network`, `timeout`, `server`. `false` for `auth`, `validation`, `config`, `unknown`. |
|
|
887
956
|
|
|
888
|
-
> **
|
|
957
|
+
> **Session expiry is handled automatically.** When a session expires or is consumed between initialization and the user clicking Pay, the SDK silently fetches a fresh session and retries the tokenization once. You will only receive an `auth` error if the session refresh itself fails — for example, if your `/api/oz-session` backend endpoint is unreachable. A healthy `auth` error in production means your session endpoint needs attention, not that the user's card is bad.
|
|
889
958
|
|
|
890
959
|
**Error normalisation helpers** (for displaying errors from `cardSale` to users):
|
|
891
960
|
|
|
@@ -900,6 +969,78 @@ const display = normalizeCardSaleError(err.message); // cardSale API errors
|
|
|
900
969
|
|
|
901
970
|
---
|
|
902
971
|
|
|
972
|
+
## Debug mode
|
|
973
|
+
|
|
974
|
+
Pass `debug: true` in `VaultOptions` (or as a prop on `<OzElements>`) to activate structured console logging at every SDK lifecycle event.
|
|
975
|
+
|
|
976
|
+
```ts
|
|
977
|
+
const vault = await OzVault.create({
|
|
978
|
+
pubKey: 'pk_live_...',
|
|
979
|
+
sessionUrl: '/api/oz-session',
|
|
980
|
+
debug: true, // enables [OzVault] console.log output
|
|
981
|
+
});
|
|
982
|
+
```
|
|
983
|
+
|
|
984
|
+
```tsx
|
|
985
|
+
// React
|
|
986
|
+
<OzElements pubKey="pk_live_..." sessionUrl="/api/oz-session" debug>
|
|
987
|
+
...
|
|
988
|
+
</OzElements>
|
|
989
|
+
```
|
|
990
|
+
|
|
991
|
+
Each log entry is a `[OzVault] <message>` prefixed `console.log` call. Events logged include:
|
|
992
|
+
|
|
993
|
+
| Event | When it fires |
|
|
994
|
+
|---|---|
|
|
995
|
+
| `vault created` | Constructor completes |
|
|
996
|
+
| `wax key received` | `fetchWaxKey` resolves |
|
|
997
|
+
| `mounting tokenizer iframe` | Tokenizer iframe creation begins |
|
|
998
|
+
| `tokenizer iframe ready` | Tokenizer iframe handshake complete |
|
|
999
|
+
| `element iframe ready` | Each card/bank input iframe loads |
|
|
1000
|
+
| `field changed` | Per-field `change` event (empty/complete/valid/auto-advance state) |
|
|
1001
|
+
| `auto-advance` | Focus moves automatically between card fields |
|
|
1002
|
+
| `createToken() called` | Entry to `createToken()` |
|
|
1003
|
+
| `OZ_TOKENIZE sent` | Tokenize request dispatched to iframe |
|
|
1004
|
+
| `token received` | Token result returned (with elapsed ms) |
|
|
1005
|
+
| `token error` | Vault or network error during tokenize |
|
|
1006
|
+
| `proactive wax key refresh triggered` | Budget exhausted; refresh starting |
|
|
1007
|
+
| `wax key refresh started/succeeded/failed` | Refresh lifecycle |
|
|
1008
|
+
| `tab hidden` / `tab visible` | `visibilitychange` events |
|
|
1009
|
+
| `reset() called` | `vault.reset()` entry |
|
|
1010
|
+
| `destroy() called` | `vault.destroy()` entry |
|
|
1011
|
+
|
|
1012
|
+
**Security:** No sensitive data is ever logged. Wax keys, tokens, CVC sessions, and billing fields appear only as boolean presence flags (`waxKeyPresent: true`). Frame IDs and request IDs are truncated.
|
|
1013
|
+
|
|
1014
|
+
### vault.debugState()
|
|
1015
|
+
|
|
1016
|
+
`vault.debugState()` is always available — regardless of whether `debug: true` was set — and returns a one-time snapshot for attaching to bug reports:
|
|
1017
|
+
|
|
1018
|
+
```ts
|
|
1019
|
+
console.log(vault.debugState());
|
|
1020
|
+
```
|
|
1021
|
+
|
|
1022
|
+
Sample output:
|
|
1023
|
+
|
|
1024
|
+
```json
|
|
1025
|
+
{
|
|
1026
|
+
"vaultId": "vault_abc12...",
|
|
1027
|
+
"isReady": true,
|
|
1028
|
+
"tokenizing": null,
|
|
1029
|
+
"destroyed": false,
|
|
1030
|
+
"waxKeyPresent": true,
|
|
1031
|
+
"tokenizeSuccessCount": 1,
|
|
1032
|
+
"maxTokenizeCalls": 3,
|
|
1033
|
+
"resetCount": 0,
|
|
1034
|
+
"elements": ["cardNumber", "expirationDate", "cvv"],
|
|
1035
|
+
"bankElements": [],
|
|
1036
|
+
"completionState": { "a1b2c3d4": true, "e5f6a7b8": true, "c9d0e1f2": false },
|
|
1037
|
+
"pendingTokenizations": 0,
|
|
1038
|
+
"pendingBankTokenizations": 0
|
|
1039
|
+
}
|
|
1040
|
+
```
|
|
1041
|
+
|
|
1042
|
+
---
|
|
1043
|
+
|
|
903
1044
|
## Server utilities
|
|
904
1045
|
|
|
905
1046
|
> 📖 [Server SDK guide](https://docs.ozura.com/sdks/elements/server) — `Ozura` class methods, route handler factories, `getClientIp`, error types, and rate limits.
|
|
@@ -920,7 +1061,7 @@ const ozura = new Ozura({
|
|
|
920
1061
|
});
|
|
921
1062
|
```
|
|
922
1063
|
|
|
923
|
-
> **Tokenize-only integrations** (
|
|
1064
|
+
> **Tokenize-only integrations** (session creation + tokenize cards, no charging) only need `vaultKey`. The `merchantId` and `apiKey` fields are optional — they are validated lazily and only required when `cardSale()` is called.
|
|
924
1065
|
>
|
|
925
1066
|
> ```ts
|
|
926
1067
|
> const ozura = new Ozura({ vaultKey: process.env.VAULT_API_KEY! });
|
|
@@ -944,25 +1085,32 @@ const result = await ozura.cardSale({
|
|
|
944
1085
|
const surcharge = result.surchargeAmount ?? '0.00';
|
|
945
1086
|
const tip = result.tipAmount ?? '0.00';
|
|
946
1087
|
|
|
947
|
-
//
|
|
948
|
-
const {
|
|
949
|
-
|
|
950
|
-
|
|
1088
|
+
// Create a session key (for custom session endpoint implementations)
|
|
1089
|
+
const { sessionKey, expiresInSeconds } = await ozura.createSession({
|
|
1090
|
+
sessionId,
|
|
1091
|
+
sessionLimit: 3, // must match VaultOptions.sessionLimit on the client (default: 3)
|
|
1092
|
+
// pass null to remove the cap (vault default = unlimited)
|
|
1093
|
+
// Optional — stored in vault audit log for correlation with your own records:
|
|
1094
|
+
// orderId: order.id,
|
|
1095
|
+
// customerId: user.id,
|
|
1096
|
+
// cartId: cart.id,
|
|
1097
|
+
// metadata: { source: 'web' },
|
|
1098
|
+
// ttlSeconds: 600, // shorter TTL for quicker checkouts (default: 1800)
|
|
951
1099
|
});
|
|
952
1100
|
|
|
953
|
-
// Revoke a
|
|
1101
|
+
// Revoke a session — call on all three session-end paths
|
|
954
1102
|
// Best-effort — never throws. Shortens the exposure window before the vault's ~30 min TTL.
|
|
955
|
-
await ozura.
|
|
1103
|
+
await ozura.revokeSession(sessionKey);
|
|
956
1104
|
|
|
957
1105
|
// Suggested pattern — wire all three exit paths:
|
|
958
1106
|
// 1. Payment success
|
|
959
1107
|
const result = await ozura.cardSale({ ... });
|
|
960
|
-
await ozura.
|
|
1108
|
+
await ozura.revokeSession(sessionKey); // session is spent; close the window immediately
|
|
961
1109
|
|
|
962
1110
|
// 2. User cancels checkout
|
|
963
1111
|
router.post('/api/cancel', async (req) => {
|
|
964
|
-
const {
|
|
965
|
-
await ozura.
|
|
1112
|
+
const { sessionKey } = await db.session.get(req.sessionId);
|
|
1113
|
+
await ozura.revokeSession(sessionKey);
|
|
966
1114
|
return Response.json({ ok: true });
|
|
967
1115
|
});
|
|
968
1116
|
|
|
@@ -1001,18 +1149,18 @@ try {
|
|
|
1001
1149
|
|
|
1002
1150
|
Rate limits: `cardSale` — 100 req/min per merchant. `listTransactions` — 200 req/min per merchant.
|
|
1003
1151
|
|
|
1004
|
-
> **Retry behavior:** `
|
|
1152
|
+
> **Retry behavior:** `createSession` and `listTransactions` retry on 5xx and network errors using exponential backoff (1 s / 2 s / 4 s…) up to `retries + 1` total attempts. **`cardSale` is never retried** — it is a non-idempotent financial operation and the result of a duplicate charge cannot be predicted.
|
|
1005
1153
|
|
|
1006
1154
|
---
|
|
1007
1155
|
|
|
1008
1156
|
### Route handler factories
|
|
1009
1157
|
|
|
1010
|
-
The server package exports
|
|
1158
|
+
The server package exports factory functions covering two runtimes × two endpoints:
|
|
1011
1159
|
|
|
1012
1160
|
| Function | Runtime | Endpoint |
|
|
1013
1161
|
|---|---|---|
|
|
1014
|
-
| `
|
|
1015
|
-
| `
|
|
1162
|
+
| `createSessionHandler` | Fetch API (Next.js App Router, Cloudflare, Vercel Edge) | `POST /api/oz-session` |
|
|
1163
|
+
| `createSessionMiddleware` | Express / Connect | `POST /api/oz-session` |
|
|
1016
1164
|
| `createCardSaleHandler` | Fetch API | `POST /api/charge` |
|
|
1017
1165
|
| `createCardSaleMiddleware` | Express / Connect | `POST /api/charge` |
|
|
1018
1166
|
|
|
@@ -1050,7 +1198,7 @@ Set `frameBaseUrl` to point your vault at the local server:
|
|
|
1050
1198
|
```ts
|
|
1051
1199
|
const vault = await OzVault.create({
|
|
1052
1200
|
pubKey: 'pk_test_...',
|
|
1053
|
-
|
|
1201
|
+
sessionUrl: '/api/oz-session',
|
|
1054
1202
|
frameBaseUrl: 'http://localhost:4242', // local dev only
|
|
1055
1203
|
});
|
|
1056
1204
|
```
|
|
@@ -1060,7 +1208,7 @@ Or in React:
|
|
|
1060
1208
|
```tsx
|
|
1061
1209
|
<OzElements
|
|
1062
1210
|
pubKey="pk_test_..."
|
|
1063
|
-
|
|
1211
|
+
sessionUrl="/api/oz-session"
|
|
1064
1212
|
frameBaseUrl="http://localhost:4242"
|
|
1065
1213
|
>
|
|
1066
1214
|
```
|
|
@@ -1160,8 +1308,8 @@ Server-specific types are exported from `@ozura/elements/server`:
|
|
|
1160
1308
|
import type {
|
|
1161
1309
|
OzuraConfig,
|
|
1162
1310
|
CardSaleInput,
|
|
1163
|
-
|
|
1164
|
-
|
|
1311
|
+
CreateSessionOptions,
|
|
1312
|
+
CreateSessionResult,
|
|
1165
1313
|
ListTransactionsInput,
|
|
1166
1314
|
} from '@ozura/elements/server';
|
|
1167
1315
|
```
|