@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.
Files changed (42) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +234 -86
  3. package/dist/frame/element-frame.js +92 -20
  4. package/dist/frame/element-frame.js.map +1 -1
  5. package/dist/frame/tokenizer-frame.html +1 -1
  6. package/dist/frame/tokenizer-frame.js +20 -4
  7. package/dist/frame/tokenizer-frame.js.map +1 -1
  8. package/dist/oz-elements.esm.js +477 -251
  9. package/dist/oz-elements.esm.js.map +1 -1
  10. package/dist/oz-elements.umd.js +478 -251
  11. package/dist/oz-elements.umd.js.map +1 -1
  12. package/dist/react/frame/elementFrame.d.ts +70 -1
  13. package/dist/react/frame/protocol.d.ts +12 -0
  14. package/dist/react/index.cjs.js +447 -225
  15. package/dist/react/index.cjs.js.map +1 -1
  16. package/dist/react/index.esm.js +448 -226
  17. package/dist/react/index.esm.js.map +1 -1
  18. package/dist/react/react/index.d.ts +70 -26
  19. package/dist/react/sdk/OzVault.d.ts +55 -5
  20. package/dist/react/sdk/createSessionFetcher.d.ts +29 -0
  21. package/dist/react/sdk/index.d.ts +6 -26
  22. package/dist/react/server/index.d.ts +126 -74
  23. package/dist/react/types/index.d.ts +72 -31
  24. package/dist/server/frame/elementFrame.d.ts +70 -1
  25. package/dist/server/frame/protocol.d.ts +12 -0
  26. package/dist/server/index.cjs.js +167 -78
  27. package/dist/server/index.cjs.js.map +1 -1
  28. package/dist/server/index.esm.js +166 -79
  29. package/dist/server/index.esm.js.map +1 -1
  30. package/dist/server/sdk/OzVault.d.ts +55 -5
  31. package/dist/server/sdk/createSessionFetcher.d.ts +29 -0
  32. package/dist/server/sdk/index.d.ts +6 -26
  33. package/dist/server/server/index.d.ts +126 -74
  34. package/dist/server/types/index.d.ts +72 -31
  35. package/dist/types/frame/elementFrame.d.ts +70 -1
  36. package/dist/types/frame/protocol.d.ts +12 -0
  37. package/dist/types/sdk/OzVault.d.ts +55 -5
  38. package/dist/types/sdk/createSessionFetcher.d.ts +29 -0
  39. package/dist/types/sdk/index.d.ts +6 -26
  40. package/dist/types/server/index.d.ts +126 -74
  41. package/dist/types/types/index.d.ts +72 -31
  42. 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
- - [Wax key endpoint](#wax-key-endpoint)
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 **wax key** from your server.
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** | Minting wax keys (all integrations) |
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, createFetchWaxKey } from '@ozura/elements/react';
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
- fetchWaxKey={createFetchWaxKey('/api/mint-wax')}
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
- const { token, cvcSession, billing } = await createToken({
147
- billing: {
148
- firstName: 'Jane',
149
- lastName: 'Smith',
150
- email: 'jane@example.com',
151
- address: { line1: '123 Main St', city: 'Austin', state: 'TX', zip: '78701', country: 'US' },
152
- },
153
- });
154
-
155
- // Send token to your server
156
- await fetch('/api/charge', {
157
- method: 'POST',
158
- headers: { 'Content-Type': 'application/json' },
159
- body: JSON.stringify({ token, cvcSession, billing }),
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, createFetchWaxKey } from '@ozura/elements';
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
- fetchWaxKey: createFetchWaxKey('/api/mint-wax'),
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
- const { token, cvcSession, card } = await vault.createToken({
212
- billing: { firstName: 'Jane', lastName: 'Smith' },
213
- });
214
- // POST { token, cvcSession } to your server
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) — wax key minting, card sale handler factories, IP extraction, and manual implementation patterns.
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
- ### Wax key endpoint
242
+ ### Session endpoint
230
243
 
231
- The SDK calls your `fetchWaxKey` function with a `tokenizationSessionId` UUID. Your backend must exchange it for a wax key from the vault.
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/mint-wax/route.ts
237
- import { Ozura, createMintWaxHandler } from '@ozura/elements/server';
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 = createMintWaxHandler(ozura);
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, createMintWaxMiddleware } from '@ozura/elements/server';
261
+ import { Ozura, createSessionMiddleware } from '@ozura/elements/server';
253
262
 
254
- const ozura = new Ozura({ merchantId: '...', apiKey: '...', vaultKey: '...' });
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/mint-wax', createMintWaxMiddleware(ozura));
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/mint-wax
273
+ // POST /api/oz-session
265
274
  const { sessionId } = await req.json();
266
- const { waxKey } = await ozura.mintWaxKey({
267
- tokenizationSessionId: sessionId,
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 the wax key concurrently. Both happen in parallel — by the time `create()` resolves, the iframe may already be ready.
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
- | `fetchWaxKey` | `(sessionId: string) => Promise<string>` | ✓ | Called with the session ID; must return a wax key string from your server. Use `createFetchWaxKey('/api/mint-wax')` for the common case. |
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
- | `onWaxRefresh` | `() => void` | — | Called when the SDK silently re-mints an expired wax key mid-tokenization. |
351
- | `onReady` | `() => void` | — | Called once when the tokenizer iframe has loaded and is ready. Use in vanilla JS to re-check submit-button readiness when the tokenizer becomes ready after all element iframes have already fired. In React, `useOzElements().ready` handles this automatically. |
352
- | `maxTokenizeCalls` | `number` | — | Maximum successful `createToken` calls per wax key before the key is considered consumed. Default: `3`. Must match `maxTokenizeCalls` in your server-side `mintWaxKey` call. |
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
- Throws `OzError` if `fetchWaxKey` rejects, returns an empty string, or returns a non-string value.
363
+ ¹ Exactly one of `sessionUrl`, `getSessionKey`, or `fetchWaxKey` is required.
355
364
 
356
- > **`createFetchWaxKey` retry behavior:** The built-in helper 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` and `err.retryable`.
365
+ Throws `OzError` if the session fetch rejects, returns an empty string, or returns a non-string value.
357
366
 
358
- > **Re-export identity:** `createFetchWaxKey` is exported from both `@ozura/elements` and `@ozura/elements/react`. They are identicalthe same function. Use whichever matches your import context.
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 against the current wax key. Resets to `0` each time the wax key is refreshed (proactively or reactively). Use this in vanilla JS to display "attempts remaining" feedback or gate the submit button:
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, createFetchWaxKey } from '@ozura/elements/react';
647
+ import { OzElements } from '@ozura/elements/react';
580
648
 
581
649
  <OzElements
582
650
  pubKey="pk_live_..."
583
- fetchWaxKey={createFetchWaxKey('/api/mint-wax')}
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 `maxTokenizeCalls` destroys the current vault and creates a new one — all field iframes will remount. Changing `fetchWaxKey`, `onLoadError`, `onWaxRefresh`, or `onReady` updates the callback in place via refs without recreating the vault.
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` / `fetchWaxKey` configurations.
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. `fetchWaxKey` threw). Render a fallback UI. |
714
- | `tokenizeCount` | `number` | Number of successful tokenizations since the last wax key was minted. Resets on wax refresh or provider re-init. Useful for tracking calls against `maxTokenizeCalls`. |
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
- fetchWaxKey: createFetchWaxKey('/api/mint-wax'),
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="..." fetchWaxKey={...} appearance={{ theme: 'night' }}>
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 wax key expiry behaviour.
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
- > **Wax key expiry is handled automatically.** When a wax key expires or is consumed between initialization and the user clicking Pay, the SDK silently re-mints a fresh key and retries the tokenization once. You will only receive an `auth` error if the re-mint itself fails — for example, if your `/api/mint-wax` backend endpoint is unreachable. A healthy `auth` error in production means your mint endpoint needs attention, not that the user's card is bad.
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** (mint wax keys + 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.
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
- // Mint a wax key (for custom fetchWaxKey implementations)
948
- const { waxKey, expiresInSeconds } = await ozura.mintWaxKey({
949
- tokenizationSessionId: sessionId,
950
- maxTokenizeCalls: 3, // must match VaultOptions.maxTokenizeCalls on the client (default: 3)
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 wax key — call on all three session-end paths
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.revokeWaxKey(waxKey);
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.revokeWaxKey(waxKey); // key is spent; close the window immediately
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 { waxKey } = await db.session.get(req.sessionId);
965
- await ozura.revokeWaxKey(waxKey);
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:** `mintWaxKey` 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.
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 four factory functions covering two runtimes × two endpoints:
1158
+ The server package exports factory functions covering two runtimes × two endpoints:
1011
1159
 
1012
1160
  | Function | Runtime | Endpoint |
1013
1161
  |---|---|---|
1014
- | `createMintWaxHandler` | Fetch API (Next.js App Router, Cloudflare, Vercel Edge) | `POST /api/mint-wax` |
1015
- | `createMintWaxMiddleware` | Express / Connect | `POST /api/mint-wax` |
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
- fetchWaxKey: createFetchWaxKey('/api/mint-wax'),
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
- fetchWaxKey={createFetchWaxKey('/api/mint-wax')}
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
- MintWaxKeyOptions,
1164
- MintWaxKeyResult,
1311
+ CreateSessionOptions,
1312
+ CreateSessionResult,
1165
1313
  ListTransactionsInput,
1166
1314
  } from '@ozura/elements/server';
1167
1315
  ```