@ozura/elements 0.1.0-beta.6 → 1.0.0

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 (50) hide show
  1. package/README.md +1121 -720
  2. package/dist/frame/element-frame.js +77 -57
  3. package/dist/frame/element-frame.js.map +1 -1
  4. package/dist/frame/tokenizer-frame.html +1 -1
  5. package/dist/frame/tokenizer-frame.js +221 -74
  6. package/dist/frame/tokenizer-frame.js.map +1 -1
  7. package/dist/oz-elements.esm.js +870 -231
  8. package/dist/oz-elements.esm.js.map +1 -1
  9. package/dist/oz-elements.umd.js +870 -230
  10. package/dist/oz-elements.umd.js.map +1 -1
  11. package/dist/react/frame/tokenizerFrame.d.ts +32 -0
  12. package/dist/react/index.cjs.js +1045 -220
  13. package/dist/react/index.cjs.js.map +1 -1
  14. package/dist/react/index.esm.js +1042 -221
  15. package/dist/react/index.esm.js.map +1 -1
  16. package/dist/react/react/index.d.ts +165 -8
  17. package/dist/react/sdk/OzElement.d.ts +34 -3
  18. package/dist/react/sdk/OzVault.d.ts +104 -4
  19. package/dist/react/sdk/errors.d.ts +9 -0
  20. package/dist/react/sdk/index.d.ts +29 -0
  21. package/dist/react/server/index.d.ts +266 -2
  22. package/dist/react/types/index.d.ts +94 -16
  23. package/dist/react/utils/appearance.d.ts +9 -0
  24. package/dist/react/utils/cardUtils.d.ts +14 -0
  25. package/dist/react/utils/uuid.d.ts +12 -0
  26. package/dist/server/frame/tokenizerFrame.d.ts +32 -0
  27. package/dist/server/index.cjs.js +761 -30
  28. package/dist/server/index.cjs.js.map +1 -1
  29. package/dist/server/index.esm.js +757 -31
  30. package/dist/server/index.esm.js.map +1 -1
  31. package/dist/server/sdk/OzElement.d.ts +34 -3
  32. package/dist/server/sdk/OzVault.d.ts +104 -4
  33. package/dist/server/sdk/errors.d.ts +9 -0
  34. package/dist/server/sdk/index.d.ts +29 -0
  35. package/dist/server/server/index.d.ts +266 -2
  36. package/dist/server/types/index.d.ts +94 -16
  37. package/dist/server/utils/appearance.d.ts +9 -0
  38. package/dist/server/utils/cardUtils.d.ts +14 -0
  39. package/dist/server/utils/uuid.d.ts +12 -0
  40. package/dist/types/frame/tokenizerFrame.d.ts +32 -0
  41. package/dist/types/sdk/OzElement.d.ts +34 -3
  42. package/dist/types/sdk/OzVault.d.ts +104 -4
  43. package/dist/types/sdk/errors.d.ts +9 -0
  44. package/dist/types/sdk/index.d.ts +29 -0
  45. package/dist/types/server/index.d.ts +266 -2
  46. package/dist/types/types/index.d.ts +94 -16
  47. package/dist/types/utils/appearance.d.ts +9 -0
  48. package/dist/types/utils/cardUtils.d.ts +14 -0
  49. package/dist/types/utils/uuid.d.ts +12 -0
  50. package/package.json +7 -4
@@ -5,6 +5,9 @@ export interface ElementStyle {
5
5
  fontWeight?: string;
6
6
  fontStyle?: string;
7
7
  fontVariant?: string;
8
+ fontSmoothing?: string;
9
+ webkitFontSmoothing?: string;
10
+ mozOsxFontSmoothing?: string;
8
11
  letterSpacing?: string;
9
12
  lineHeight?: string;
10
13
  textAlign?: string;
@@ -55,8 +58,6 @@ export interface ElementStyle {
55
58
  minHeight?: string;
56
59
  maxHeight?: string;
57
60
  transition?: string;
58
- /** Index signature for forward-compatibility with future CSS properties. */
59
- [key: string]: string | undefined;
60
61
  }
61
62
  export interface ElementStyleConfig {
62
63
  /** Default styles applied to the input. */
@@ -74,7 +75,8 @@ export interface ElementOptions {
74
75
  style?: ElementStyleConfig;
75
76
  placeholder?: string;
76
77
  disabled?: boolean;
77
- /** How long to wait (ms) for the iframe to load before emitting `loaderror`. Default: 10 000. */
78
+ /** How long to wait (ms) for the iframe to load before emitting `loaderror`. Default: 10 000.
79
+ * Only takes effect when this element's `onLoadError` option is provided or when `VaultOptions.onLoadError` is set. */
78
80
  loadTimeoutMs?: number;
79
81
  }
80
82
  export interface ElementChangeEvent {
@@ -118,7 +120,7 @@ export interface AppearanceVariables {
118
120
  colorText?: string;
119
121
  /** Input background color. Maps to `base.backgroundColor`. */
120
122
  colorBackground?: string;
121
- /** Primary accent color. Maps to `focus.caretColor` and `focus.color`. */
123
+ /** Primary accent color. Maps to `focus.caretColor` and `base.caretColor`. */
122
124
  colorPrimary?: string;
123
125
  /** Error/invalid state text color. Maps to `invalid.color`. */
124
126
  colorDanger?: string;
@@ -153,6 +155,36 @@ export interface VaultOptions {
153
155
  /** System pub key required for tokenization. Obtain from Ozura admin.
154
156
  * Sent as the `X-Pub-Key` header on tokenize requests. */
155
157
  pubKey: string;
158
+ /**
159
+ * Called by the SDK with a SDK-generated `tokenizationSessionId` UUID during
160
+ * `OzVault.create()`. Implement this to call your backend, which should mint a
161
+ * wax key from the vault using your vault secret and return it here.
162
+ *
163
+ * The wax key is a short-lived, session-scoped credential (TTL ~30 min).
164
+ * It replaces the vault secret on every browser tokenize call — the secret
165
+ * never leaves your server.
166
+ *
167
+ * **Server-side (recommended):** Use `ozura.mintWaxKey()` from `@ozura/elements/server`:
168
+ * @example
169
+ * // Your API route (e.g. Next.js App Router):
170
+ * // POST /api/mint-wax
171
+ * // const { sessionId } = await req.json();
172
+ * // const { waxKey } = await ozura.mintWaxKey({ tokenizationSessionId: sessionId });
173
+ * // return Response.json({ waxKey });
174
+ *
175
+ * **Client-side (fetchWaxKey callback):**
176
+ * @example
177
+ * fetchWaxKey: async (sessionId) => {
178
+ * const res = await fetch('/api/mint-wax', {
179
+ * method: 'POST',
180
+ * headers: { 'Content-Type': 'application/json' },
181
+ * body: JSON.stringify({ sessionId }),
182
+ * });
183
+ * const { waxKey } = await res.json();
184
+ * return waxKey;
185
+ * }
186
+ */
187
+ fetchWaxKey: (tokenizationSessionId: string) => Promise<string>;
156
188
  /** Base URL where the Ozura frame HTML/JS assets are served from.
157
189
  * Defaults to the production CDN. Override for local development. */
158
190
  frameBaseUrl?: string;
@@ -175,8 +207,40 @@ export interface VaultOptions {
175
207
  * Use this to show a fallback UI (e.g. "Unable to load payment fields").
176
208
  */
177
209
  onLoadError?: () => void;
178
- /** How long to wait (ms) for the tokenizer iframe before calling `onLoadError`. Default: 10 000. */
210
+ /** How long to wait (ms) for the tokenizer iframe before calling `onLoadError`. Default: 10 000.
211
+ * Only takes effect when `onLoadError` is also provided — setting this without `onLoadError` has no effect. */
179
212
  loadTimeoutMs?: number;
213
+ /**
214
+ * Called when the SDK silently re-mints the wax key during a tokenization
215
+ * attempt (expiry or consumption). The `createToken()` / `createBankToken()`
216
+ * promise stays pending until the refresh completes and the retry resolves.
217
+ * Use this to show a loading indicator while the re-mint round trip is in flight.
218
+ */
219
+ onWaxRefresh?: () => void;
220
+ /**
221
+ * Maximum number of tokenize calls each minted wax key accepts before the
222
+ * vault marks it as consumed. Should match the `maxTokenizeCalls` passed to
223
+ * `ozura.mintWaxKey()` on your server (both default to `3`).
224
+ *
225
+ * The SDK uses this value to proactively refresh the wax key after it has been
226
+ * fully consumed — before the next `createToken()` call is made — so users
227
+ * never experience a delay from a reactive re-mint. If the values are out of
228
+ * sync the reactive refresh path still catches consumption errors; this is
229
+ * purely an optimisation.
230
+ *
231
+ * @default 3
232
+ */
233
+ maxTokenizeCalls?: number;
234
+ /**
235
+ * Called once when the tokenizer iframe has loaded and is ready to accept
236
+ * tokenization requests. Useful in vanilla JS to re-evaluate submit-button
237
+ * readiness when the tokenizer becomes ready after all element iframes have
238
+ * already loaded.
239
+ *
240
+ * In React, use `useOzElements().ready` instead — it combines both tokenizer
241
+ * and element readiness automatically.
242
+ */
243
+ onReady?: () => void;
180
244
  /**
181
245
  * Global appearance configuration. Applies preset themes and/or variable
182
246
  * overrides to all elements created by this vault. Per-element `style`
@@ -248,8 +312,8 @@ export interface BankTokenizeOptions {
248
312
  export interface BankAccountMetadata {
249
313
  /** Last 4 digits of the account number. */
250
314
  last4: string;
251
- /** US routing number (9 digits). */
252
- routingNumber: string;
315
+ /** Last 4 digits of the routing number sufficient for display and reconciliation. */
316
+ routingNumberLast4: string;
253
317
  }
254
318
  /** Non-sensitive card metadata returned alongside the token. */
255
319
  export interface CardMetadata {
@@ -264,6 +328,16 @@ export interface CardMetadata {
264
328
  }
265
329
  export interface TokenResponse {
266
330
  token: string;
331
+ /**
332
+ * CVC session token returned by the vault alongside the card token.
333
+ *
334
+ * Required by the Ozura Pay API `cardSale` endpoint. If this is absent it
335
+ * means the vault is not configured to return CVC sessions — `createToken()`
336
+ * will reject with an `OZ_TOKEN_ERROR` before this response is ever returned
337
+ * to the caller. In practice, this field is always present on successful
338
+ * tokenization; the optional type reflects only that the field is not part
339
+ * of the tokenized card data itself.
340
+ */
267
341
  cvcSession?: string;
268
342
  /** Non-sensitive card metadata — available immediately without a cardSale call. */
269
343
  card?: CardMetadata;
@@ -290,7 +364,6 @@ export interface BankTokenResponse {
290
364
  *
291
365
  * Headers required:
292
366
  * x-api-key: <merchantApiKey>
293
- * vault-api-key: <vaultApiKey> (same key passed to OzVault)
294
367
  */
295
368
  export interface CardSaleRequest {
296
369
  /** Processor to use. If omitted, the Pay API auto-selects (Elavon → Nuvei → Worldpay). */
@@ -339,12 +412,12 @@ export interface CardSaleResponseData {
339
412
  amount: string;
340
413
  /** ISO 4217 currency code, e.g. "USD". */
341
414
  currency: string;
342
- /** Surcharge amount applied, e.g. "0.00". */
343
- surchargeAmount: string;
415
+ /** Surcharge amount applied, e.g. "1.50". Absent when no surcharge was configured. */
416
+ surchargeAmount?: string;
344
417
  /** Sales tax calculated by the Pay API based on billing address. */
345
418
  salesTaxAmount?: string;
346
- /** Tip amount applied, e.g. "0.00". */
347
- tipAmount: string;
419
+ /** Tip amount applied, e.g. "5.00". Absent when no tip was included in the request. */
420
+ tipAmount?: string;
348
421
  /** Last four digits of the card number. */
349
422
  cardLastFour: string;
350
423
  /** First six digits of the card number (BIN). */
@@ -379,12 +452,17 @@ export interface CardSaleResponseData {
379
452
  * Full response envelope from the Ozura Pay API cardSale endpoint.
380
453
  *
381
454
  * On success: `{ success: true, data: CardSaleResponseData }`
382
- * On failure: HTTP 4xx/5xx with `{ error: string }` — pass `error` to
383
- * `normalizeCardSaleError()` to get a user-facing message.
455
+ * On failure: HTTP 4xx/5xx with `{ success: false, error: string }` — pass
456
+ * `error` to `normalizeCardSaleError()` to get a user-facing message.
457
+ *
458
+ * Note: the server SDK `Ozura.cardSale()` throws `OzuraError` on failure rather
459
+ * than returning this shape. This type is only needed when calling the Pay API
460
+ * directly via `fetch`.
384
461
  */
385
462
  export interface CardSaleApiResponse {
386
463
  success: boolean;
387
- data: CardSaleResponseData;
464
+ data?: CardSaleResponseData;
465
+ error?: string;
388
466
  }
389
467
  /**
390
468
  * Transaction types returned by the Pay API transQuery endpoint.
@@ -510,7 +588,7 @@ export interface TransactionQueryResponse {
510
588
  data: TransactionData[];
511
589
  pagination: TransactionQueryPagination;
512
590
  }
513
- export type OzMessageType = 'OZ_FRAME_READY' | 'OZ_INIT' | 'OZ_UPDATE' | 'OZ_CLEAR' | 'OZ_CHANGE' | 'OZ_FOCUS' | 'OZ_BLUR' | 'OZ_RESIZE' | 'OZ_SET_TOKENIZER_NAME' | 'OZ_BEGIN_COLLECT' | 'OZ_FIELD_VALUE' | 'OZ_TOKENIZE' | 'OZ_TOKEN_RESULT' | 'OZ_TOKEN_ERROR' | 'OZ_FOCUS_REQUEST' | 'OZ_BLUR_REQUEST' | 'OZ_SET_CVV_LENGTH' | 'OZ_BANK_TOKENIZE' | 'OZ_BANK_TOKEN_RESULT';
591
+ export type OzMessageType = 'OZ_FRAME_READY' | 'OZ_INIT' | 'OZ_UPDATE' | 'OZ_CLEAR' | 'OZ_CHANGE' | 'OZ_FOCUS' | 'OZ_BLUR' | 'OZ_RESIZE' | 'OZ_BEGIN_COLLECT' | 'OZ_FIELD_VALUE' | 'OZ_TOKENIZE' | 'OZ_TOKEN_RESULT' | 'OZ_TOKEN_ERROR' | 'OZ_FOCUS_REQUEST' | 'OZ_BLUR_REQUEST' | 'OZ_SET_CVV_LENGTH' | 'OZ_BANK_TOKENIZE' | 'OZ_BANK_TOKEN_RESULT' | 'OZ_TOKENIZE_CANCEL';
514
592
  export interface OzMessage {
515
593
  __oz: true;
516
594
  vaultId: string;
@@ -4,6 +4,15 @@ import type { Appearance, ElementStyleConfig } from '../types';
4
4
  * Resolution order: theme defaults → variable overrides.
5
5
  * The returned config is then used as the "base appearance" that
6
6
  * per-element `style` overrides merge on top of.
7
+ *
8
+ * @remarks
9
+ * - `appearance: undefined` → no styles injected (element iframes use their
10
+ * own minimal built-in defaults).
11
+ * - `appearance: {}` or `appearance: { variables: {...} }` without an explicit
12
+ * `theme` → the `'default'` theme is used as the base. Omitting `theme`
13
+ * does NOT mean "no theme" — it means `theme: 'default'`. To opt out of
14
+ * the preset themes entirely, use per-element `style` overrides without
15
+ * passing an `appearance` prop at all.
7
16
  */
8
17
  export declare function resolveAppearance(appearance?: Appearance): ElementStyleConfig | undefined;
9
18
  /**
@@ -21,6 +21,20 @@ export interface CardValidationResult {
21
21
  valid: boolean;
22
22
  error?: string;
23
23
  }
24
+ /**
25
+ * Canonical card number validation: length check then Luhn.
26
+ *
27
+ * **Not called by `elementFrame.ts` at runtime.** The frame needs `complete`
28
+ * and `valid` as separate signals for field UX (show completion indicator
29
+ * before running Luhn), so it implements both checks inline via `isComplete()`
30
+ * and `isValid()`. Those methods MUST stay logically in sync with this
31
+ * function — any change to length bounds or error text here must be mirrored
32
+ * there, and vice-versa.
33
+ *
34
+ * Used by the unit test suite as the authoritative spec for the algorithm.
35
+ *
36
+ * @internal
37
+ */
24
38
  export declare function validateCardNumber(digits: string, brand: CardBrand | string): CardValidationResult;
25
39
  export interface ExpiryValidationResult {
26
40
  complete: boolean;
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Generates a RFC 4122 v4 UUID.
3
+ *
4
+ * Uses `crypto.randomUUID()` when available (Chrome 92+, Firefox 95+,
5
+ * Safari 15.4+, Node 14.17+) and falls back to `crypto.getRandomValues()`
6
+ * for older environments (Safari 14, some embedded WebViews, older Node).
7
+ *
8
+ * Both paths use the same CSPRNG — the difference is only in API surface.
9
+ *
10
+ * @internal
11
+ */
12
+ export declare function uuid(): string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ozura/elements",
3
- "version": "0.1.0-beta.6",
3
+ "version": "1.0.0",
4
4
  "description": "PCI-compliant card tokenization SDK for the Ozura Vault — collect card data in iframe-isolated fields and tokenize without PCI scope",
5
5
  "main": "dist/oz-elements.umd.js",
6
6
  "module": "dist/oz-elements.esm.js",
@@ -12,12 +12,12 @@
12
12
  "require": "./dist/oz-elements.umd.js"
13
13
  },
14
14
  "./react": {
15
- "types": "./dist/react/index.d.ts",
15
+ "types": "./dist/react/react/index.d.ts",
16
16
  "import": "./dist/react/index.esm.js",
17
17
  "require": "./dist/react/index.cjs.js"
18
18
  },
19
19
  "./server": {
20
- "types": "./dist/server/index.d.ts",
20
+ "types": "./dist/server/server/index.d.ts",
21
21
  "import": "./dist/server/index.esm.js",
22
22
  "require": "./dist/server/index.cjs.js"
23
23
  }
@@ -41,7 +41,8 @@
41
41
  "test:coverage": "vitest run --coverage",
42
42
  "test:e2e": "playwright test --project=mock",
43
43
  "test:e2e:live": "playwright test --project=live",
44
- "prepublishOnly": "npm run build"
44
+ "check:csp": "node scripts/check-csp.mjs",
45
+ "prepublishOnly": "cross-env NODE_ENV=production npm run build"
45
46
  },
46
47
  "peerDependencies": {
47
48
  "react": ">=17",
@@ -80,6 +81,8 @@
80
81
  "@rollup/plugin-node-resolve": "^16.0.1",
81
82
  "@rollup/plugin-replace": "^6.0.3",
82
83
  "@rollup/plugin-typescript": "^12.1.2",
84
+ "@testing-library/jest-dom": "^6.9.1",
85
+ "@testing-library/react": "^16.3.2",
83
86
  "@types/node": "^22.14.0",
84
87
  "@types/react": "^19.2.14",
85
88
  "@types/react-dom": "^19.2.3",