@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
@@ -1,5 +1,5 @@
1
1
  import { jsx, jsxs } from 'react/jsx-runtime';
2
- import { createContext, useContext, useRef, useEffect, useState, useCallback, useMemo } from 'react';
2
+ import { createContext, useContext, useRef, useCallback, useState, useMemo, useEffect } from 'react';
3
3
 
4
4
  const THEME_DEFAULT = {
5
5
  base: {
@@ -355,7 +355,7 @@ class OzElement {
355
355
  * (useful when integrating with React refs).
356
356
  */
357
357
  mount(target) {
358
- var _a;
358
+ var _a, _b;
359
359
  if (this._destroyed)
360
360
  throw new OzError('Cannot mount a destroyed element.');
361
361
  if (this.iframe)
@@ -372,7 +372,13 @@ class OzElement {
372
372
  iframe.setAttribute('scrolling', 'no');
373
373
  iframe.setAttribute('allowtransparency', 'true');
374
374
  iframe.style.cssText = 'border:none;width:100%;height:46px;display:block;overflow:hidden;';
375
- iframe.title = `Secure ${this.elementType} input`;
375
+ iframe.title = `Secure ${(_a = {
376
+ cardNumber: 'card number',
377
+ expirationDate: 'expiration date',
378
+ cvv: 'CVV',
379
+ accountNumber: 'account number',
380
+ routingNumber: 'routing number',
381
+ }[this.elementType]) !== null && _a !== void 0 ? _a : this.elementType} input`;
376
382
  // Note: the `sandbox` attribute is intentionally NOT set. Field values are
377
383
  // delivered to the tokenizer iframe via a MessageChannel port (transferred
378
384
  // in OZ_BEGIN_COLLECT), so no window.parent named-frame lookup is needed.
@@ -386,7 +392,7 @@ class OzElement {
386
392
  container.appendChild(iframe);
387
393
  this.iframe = iframe;
388
394
  this._frameWindow = iframe.contentWindow;
389
- const timeout = (_a = this.options.loadTimeoutMs) !== null && _a !== void 0 ? _a : 10000;
395
+ const timeout = (_b = this.options.loadTimeoutMs) !== null && _b !== void 0 ? _b : 10000;
390
396
  this._loadTimer = setTimeout(() => {
391
397
  if (!this._ready && !this._destroyed) {
392
398
  this.emit('loaderror', { elementType: this.elementType, error: `${this.elementType} iframe failed to load within ${timeout}ms` });
@@ -819,13 +825,105 @@ function validateBilling(billing) {
819
825
  return { valid: errors.length === 0, errors, normalized };
820
826
  }
821
827
 
828
+ /**
829
+ * Shared postMessage protocol constants.
830
+ *
831
+ * PROTOCOL_VERSION must be incremented any time a breaking change is made to
832
+ * the postMessage message shape (new required fields, renamed types, removed
833
+ * fields, changed semantics). The SDK reads this value from OZ_FRAME_READY
834
+ * messages and warns when the frame and SDK are out of sync.
835
+ *
836
+ * Non-breaking additions (new optional fields, new message types that old
837
+ * frames can safely ignore) do NOT require a version bump.
838
+ */
839
+ const PROTOCOL_VERSION = 1;
840
+
841
+ /**
842
+ * Creates a `getSessionKey` callback for `OzVault.create()` and `<OzElements>`.
843
+ *
844
+ * This is the recommended way to wire the SDK to your backend session endpoint.
845
+ * If you don't need custom headers or auth logic, pass `sessionUrl` directly to
846
+ * `OzVault.create()` or `<OzElements>` — it calls this helper internally.
847
+ *
848
+ * The callback POSTs `{ sessionId }` to `url` and reads `sessionKey` (or the
849
+ * legacy `waxKey`) from the JSON response, so it is compatible with both the
850
+ * new `createSessionMiddleware` and the old `createMintWaxMiddleware` backends.
851
+ *
852
+ * Each call enforces a **10-second timeout**. On pure network failures
853
+ * (offline, DNS, connection refused) the request is retried **once after 750ms**.
854
+ * HTTP 4xx/5xx errors are never retried — they indicate misconfiguration.
855
+ *
856
+ * @param url - Absolute or relative URL of your session endpoint, e.g. `'/api/oz-session'`.
857
+ *
858
+ * @example
859
+ * // Simplest — just pass sessionUrl, no need to call this directly
860
+ * const vault = await OzVault.create({ pubKey: 'pk_live_...', sessionUrl: '/api/oz-session' });
861
+ *
862
+ * @example
863
+ * // Manual — use when you need custom headers
864
+ * const vault = await OzVault.create({
865
+ * pubKey: 'pk_live_...',
866
+ * getSessionKey: createSessionFetcher('/api/oz-session'),
867
+ * });
868
+ */
869
+ function createSessionFetcher(url) {
870
+ const TIMEOUT_MS = 10000;
871
+ // Each attempt gets its own AbortController so a timeout on attempt 1 does
872
+ // not bleed into the retry.
873
+ const attemptFetch = (sessionId) => {
874
+ const controller = new AbortController();
875
+ const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
876
+ return fetch(url, {
877
+ method: 'POST',
878
+ headers: { 'Content-Type': 'application/json' },
879
+ body: JSON.stringify({ sessionId }),
880
+ signal: controller.signal,
881
+ }).finally(() => clearTimeout(timer));
882
+ };
883
+ return async (sessionId) => {
884
+ let res;
885
+ try {
886
+ res = await attemptFetch(sessionId);
887
+ }
888
+ catch (firstErr) {
889
+ // Timeout/abort — don't retry, we already waited the full duration.
890
+ if (firstErr instanceof Error && (firstErr.name === 'AbortError' || firstErr.name === 'TimeoutError')) {
891
+ throw new OzError(`Session endpoint timed out after ${TIMEOUT_MS / 1000}s (${url})`, undefined, 'timeout');
892
+ }
893
+ // Pure network error — retry once after a short pause.
894
+ await new Promise(resolve => setTimeout(resolve, 750));
895
+ try {
896
+ res = await attemptFetch(sessionId);
897
+ }
898
+ catch (retryErr) {
899
+ const msg = retryErr instanceof Error ? retryErr.message : 'Network error';
900
+ throw new OzError(`Could not reach session endpoint (${url}): ${msg}`, undefined, 'network');
901
+ }
902
+ }
903
+ const data = await res.json().catch(() => ({}));
904
+ if (!res.ok) {
905
+ throw new OzError(typeof data.error === 'string' && data.error
906
+ ? data.error
907
+ : `Session endpoint returned HTTP ${res.status}`, undefined, res.status >= 500 ? 'server' : res.status === 401 || res.status === 403 ? 'auth' : 'validation');
908
+ }
909
+ // Accept both new `sessionKey` and legacy `waxKey` for backward compatibility
910
+ // with backends that haven't migrated to createSessionMiddleware yet.
911
+ const key = (typeof data.sessionKey === 'string' ? data.sessionKey : '') ||
912
+ (typeof data.waxKey === 'string' ? data.waxKey : '');
913
+ if (!key.trim()) {
914
+ throw new OzError('Session endpoint response is missing sessionKey. Check your /api/oz-session implementation.', undefined, 'validation');
915
+ }
916
+ return key;
917
+ };
918
+ }
919
+
822
920
  function isCardMetadata(v) {
823
921
  return !!v && typeof v === 'object' && typeof v.last4 === 'string';
824
922
  }
825
923
  function isBankAccountMetadata(v) {
826
924
  return !!v && typeof v === 'object' && typeof v.last4 === 'string';
827
925
  }
828
- const DEFAULT_FRAME_BASE_URL = "https://elements.ozura.com";
926
+ const DEFAULT_FRAME_BASE_URL = "https://lively-hill-097170c0f.4.azurestaticapps.net";
829
927
  /**
830
928
  * The main entry point for OzElements. Creates and manages iframe-based
831
929
  * card input elements that keep raw card data isolated from the merchant page.
@@ -858,7 +956,7 @@ class OzVault {
858
956
  * @internal
859
957
  */
860
958
  constructor(options, waxKey, tokenizationSessionId) {
861
- var _a, _b, _c;
959
+ var _a, _b, _c, _d, _e, _f;
862
960
  this.elements = new Map();
863
961
  this.elementsByType = new Map();
864
962
  this.bankElementsByType = new Map();
@@ -871,6 +969,9 @@ class OzVault {
871
969
  this.tokenizerReady = false;
872
970
  this._tokenizing = null;
873
971
  this._destroyed = false;
972
+ // Incremented every time reset() cancels an active tokenization so that
973
+ // any in-flight wax-key refresh retry can detect it was superseded.
974
+ this._resetCount = 0;
874
975
  // Tracks successful tokenizations against the per-key call budget so the SDK
875
976
  // can proactively refresh the wax key after it has been consumed rather than
876
977
  // waiting for the next createToken() call to fail.
@@ -889,14 +990,16 @@ class OzVault {
889
990
  this.fonts = (_a = options.fonts) !== null && _a !== void 0 ? _a : [];
890
991
  this.resolvedAppearance = resolveAppearance(options.appearance);
891
992
  this.vaultId = `vault-${uuid()}`;
892
- this._maxTokenizeCalls = (_b = options.maxTokenizeCalls) !== null && _b !== void 0 ? _b : 3;
993
+ // sessionLimit takes precedence over legacy maxTokenizeCalls
994
+ this._maxTokenizeCalls = (_c = (_b = options.sessionLimit) !== null && _b !== void 0 ? _b : options.maxTokenizeCalls) !== null && _c !== void 0 ? _c : 3;
995
+ this._debug = (_d = options.debug) !== null && _d !== void 0 ? _d : false;
893
996
  this.boundHandleMessage = this.handleMessage.bind(this);
894
997
  window.addEventListener('message', this.boundHandleMessage);
895
998
  this.boundHandleVisibility = this.handleVisibilityChange.bind(this);
896
999
  document.addEventListener('visibilitychange', this.boundHandleVisibility);
897
1000
  this.mountTokenizerFrame();
898
1001
  if (options.onLoadError) {
899
- const timeout = (_c = options.loadTimeoutMs) !== null && _c !== void 0 ? _c : 10000;
1002
+ const timeout = (_e = options.loadTimeoutMs) !== null && _e !== void 0 ? _e : 10000;
900
1003
  this.loadErrorTimeoutId = setTimeout(() => {
901
1004
  this.loadErrorTimeoutId = null;
902
1005
  if (!this._destroyed && !this.tokenizerReady) {
@@ -904,54 +1007,68 @@ class OzVault {
904
1007
  }
905
1008
  }, timeout);
906
1009
  }
907
- this._onWaxRefresh = options.onWaxRefresh;
1010
+ // onSessionRefresh takes precedence over legacy onWaxRefresh
1011
+ this._onWaxRefresh = (_f = options.onSessionRefresh) !== null && _f !== void 0 ? _f : options.onWaxRefresh;
908
1012
  this._onReady = options.onReady;
1013
+ this.log('vault created', { vaultId: this.vaultId, frameBaseUrl: this.frameBaseUrl, maxTokenizeCalls: this._maxTokenizeCalls });
909
1014
  }
910
1015
  /**
911
1016
  * Creates and returns a ready `OzVault` instance.
912
1017
  *
913
1018
  * Internally this:
914
- * 1. Generates a `tokenizationSessionId` (UUID).
1019
+ * 1. Generates a session UUID.
915
1020
  * 2. Starts loading the hidden tokenizer iframe immediately.
916
- * 3. Calls `options.fetchWaxKey(tokenizationSessionId)` concurrently — your
917
- * backend mints a session-bound wax key from the vault and returns it.
918
- * 4. Resolves with the vault instance once the wax key is stored. The iframe
1021
+ * 3. Fetches a session key from your backend concurrently — either via
1022
+ * `sessionUrl` (simplest), `getSessionKey` (custom headers/auth), or the
1023
+ * deprecated `fetchWaxKey` callback.
1024
+ * 4. Resolves with the vault instance once the session key is stored. The iframe
919
1025
  * has been loading the whole time, so `isReady` may already be true or
920
1026
  * will fire shortly after.
921
1027
  *
922
1028
  * The returned vault is ready to create elements immediately. `createToken()`
923
1029
  * additionally requires `vault.isReady` (tokenizer iframe loaded).
924
1030
  *
925
- * @throws {OzError} if `fetchWaxKey` throws, returns a non-string value, or returns an empty/whitespace-only string.
1031
+ * @throws {OzError} if the session fetch fails, times out, or returns an empty string.
926
1032
  */
927
1033
  static async create(options, signal) {
928
1034
  if (!options.pubKey || !options.pubKey.trim()) {
929
1035
  throw new OzError('pubKey is required in options. Obtain your public key from the Ozura admin.');
930
1036
  }
931
- if (typeof options.fetchWaxKey !== 'function') {
932
- throw new OzError('fetchWaxKey must be a function. See OzVault.create() docs for the expected signature.');
1037
+ // Normalize the session callback. Priority: sessionUrl > getSessionKey > fetchWaxKey (deprecated).
1038
+ // This allows merchants to use the clean new API without touching legacy code.
1039
+ let resolvedFetchKey;
1040
+ if (options.sessionUrl) {
1041
+ resolvedFetchKey = createSessionFetcher(options.sessionUrl);
1042
+ }
1043
+ else if (typeof options.getSessionKey === 'function') {
1044
+ resolvedFetchKey = options.getSessionKey;
1045
+ }
1046
+ else if (typeof options.fetchWaxKey === 'function') {
1047
+ resolvedFetchKey = options.fetchWaxKey;
1048
+ }
1049
+ else {
1050
+ throw new OzError('A session URL or callback is required. Pass sessionUrl, getSessionKey, or fetchWaxKey to OzVault.create().');
933
1051
  }
934
1052
  const tokenizationSessionId = uuid();
935
1053
  // Construct the vault immediately — this mounts the tokenizer iframe so it
936
- // starts loading while fetchWaxKey is in flight. The waxKey field starts
937
- // empty and is set below before create() returns.
1054
+ // starts loading while the session fetch is in flight.
938
1055
  const vault = new OzVault(options, '', tokenizationSessionId);
939
1056
  // If the caller provides an AbortSignal (e.g. React useEffect cleanup),
940
1057
  // destroy the vault immediately on abort so the tokenizer iframe and message
941
- // listener are removed synchronously rather than waiting for fetchWaxKey to
942
- // settle. This eliminates the brief double-iframe window in React StrictMode.
1058
+ // listener are removed synchronously rather than waiting for the session fetch
1059
+ // to settle. This eliminates the brief double-iframe window in React StrictMode.
943
1060
  const onAbort = () => vault.destroy();
944
1061
  signal === null || signal === void 0 ? void 0 : signal.addEventListener('abort', onAbort, { once: true });
945
1062
  let waxKey;
946
1063
  try {
947
- waxKey = await options.fetchWaxKey(tokenizationSessionId);
1064
+ waxKey = await resolvedFetchKey(tokenizationSessionId);
948
1065
  }
949
1066
  catch (err) {
950
1067
  signal === null || signal === void 0 ? void 0 : signal.removeEventListener('abort', onAbort);
951
1068
  vault.destroy();
952
1069
  if (signal === null || signal === void 0 ? void 0 : signal.aborted)
953
1070
  throw new OzError('OzVault.create() was cancelled.');
954
- // Preserve errorCode/retryable from OzError (e.g. timeout/network from createFetchWaxKey)
1071
+ // Preserve errorCode/retryable from OzError (e.g. timeout/network from createSessionFetcher)
955
1072
  // so callers can distinguish transient failures from config errors.
956
1073
  const originalCode = err instanceof OzError ? err.errorCode : undefined;
957
1074
  const msg = err instanceof Error ? err.message : 'Unknown error';
@@ -968,13 +1085,14 @@ class OzVault {
968
1085
  }
969
1086
  // Static methods can access private fields of instances of the same class.
970
1087
  vault.waxKey = waxKey;
971
- vault._storedFetchWaxKey = options.fetchWaxKey;
1088
+ vault._storedFetchWaxKey = resolvedFetchKey;
972
1089
  // If the tokenizer iframe fired OZ_FRAME_READY before fetchWaxKey resolved,
973
1090
  // the OZ_INIT sent at that point had an empty waxKey. Send a follow-up now
974
1091
  // so the tokenizer has the key stored before any createToken() call.
975
1092
  if (vault.tokenizerReady) {
976
1093
  vault.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey });
977
1094
  }
1095
+ vault.log('wax key received — vault ready');
978
1096
  return vault;
979
1097
  }
980
1098
  /**
@@ -1116,8 +1234,13 @@ class OzVault {
1116
1234
  const readyBankElements = [accountEl, routingEl];
1117
1235
  this._tokenizing = 'bank';
1118
1236
  const requestId = `req-${uuid()}`;
1237
+ this.log('createBankToken() called');
1119
1238
  return new Promise((resolve, reject) => {
1120
- const cleanup = () => { this._tokenizing = null; };
1239
+ const resetCountAtStart = this._resetCount;
1240
+ const cleanup = () => {
1241
+ if (this._resetCount === resetCountAtStart)
1242
+ this._tokenizing = null;
1243
+ };
1121
1244
  this.bankTokenizeResolvers.set(requestId, {
1122
1245
  resolve: (v) => { cleanup(); resolve(v); },
1123
1246
  reject: (e) => { cleanup(); reject(e); },
@@ -1128,6 +1251,7 @@ class OzVault {
1128
1251
  });
1129
1252
  try {
1130
1253
  const bankChannels = readyBankElements.map(() => new MessageChannel());
1254
+ const bankTokenizeStartMs = Date.now();
1131
1255
  this.sendToTokenizer({
1132
1256
  type: 'OZ_BANK_TOKENIZE',
1133
1257
  requestId,
@@ -1138,6 +1262,7 @@ class OzVault {
1138
1262
  lastName: options.lastName.trim(),
1139
1263
  fieldCount: readyBankElements.length,
1140
1264
  }, bankChannels.map(ch => ch.port1));
1265
+ this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
1141
1266
  readyBankElements.forEach((el, i) => el.beginCollect(requestId, bankChannels[i].port2));
1142
1267
  const bankTimeoutId = setTimeout(() => {
1143
1268
  if (this.bankTokenizeResolvers.has(requestId)) {
@@ -1148,8 +1273,10 @@ class OzVault {
1148
1273
  }
1149
1274
  }, 30000);
1150
1275
  const bankPendingEntry = this.bankTokenizeResolvers.get(requestId);
1151
- if (bankPendingEntry)
1276
+ if (bankPendingEntry) {
1152
1277
  bankPendingEntry.timeoutId = bankTimeoutId;
1278
+ bankPendingEntry.tokenizeStartMs = bankTokenizeStartMs;
1279
+ }
1153
1280
  }
1154
1281
  catch (err) {
1155
1282
  this.bankTokenizeResolvers.delete(requestId);
@@ -1220,8 +1347,15 @@ class OzVault {
1220
1347
  }
1221
1348
  this._tokenizing = 'card';
1222
1349
  const requestId = `req-${uuid()}`;
1350
+ this.log('createToken() called');
1223
1351
  return new Promise((resolve, reject) => {
1224
- const cleanup = () => { this._tokenizing = null; };
1352
+ // Capture the reset generation so cleanup() only zeros _tokenizing when it
1353
+ // still belongs to this invocation — not a newer one that started after a reset.
1354
+ const resetCountAtStart = this._resetCount;
1355
+ const cleanup = () => {
1356
+ if (this._resetCount === resetCountAtStart)
1357
+ this._tokenizing = null;
1358
+ };
1225
1359
  this.tokenizeResolvers.set(requestId, {
1226
1360
  resolve: (v) => { cleanup(); resolve(v); },
1227
1361
  reject: (e) => { cleanup(); reject(e); },
@@ -1234,6 +1368,7 @@ class OzVault {
1234
1368
  try {
1235
1369
  // Tell tokenizer frame to expect N field values, then tokenize
1236
1370
  const cardChannels = readyElements.map(() => new MessageChannel());
1371
+ const tokenizeStartMs = Date.now();
1237
1372
  this.sendToTokenizer({
1238
1373
  type: 'OZ_TOKENIZE',
1239
1374
  requestId,
@@ -1244,6 +1379,11 @@ class OzVault {
1244
1379
  lastName,
1245
1380
  fieldCount: readyElements.length,
1246
1381
  }, cardChannels.map(ch => ch.port1));
1382
+ this.log('OZ_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyElements.length });
1383
+ // Store start time for elapsed-ms logging on result
1384
+ const cardEntry = this.tokenizeResolvers.get(requestId);
1385
+ if (cardEntry)
1386
+ cardEntry.tokenizeStartMs = tokenizeStartMs;
1247
1387
  // Tell each ready element frame to send its raw value to the tokenizer
1248
1388
  readyElements.forEach((el, i) => el.beginCollect(requestId, cardChannels[i].port2));
1249
1389
  const cardTimeoutId = setTimeout(() => {
@@ -1265,6 +1405,63 @@ class OzVault {
1265
1405
  }
1266
1406
  });
1267
1407
  }
1408
+ /**
1409
+ * Clears all mounted element fields without tearing down the vault.
1410
+ *
1411
+ * Call this after a failed payment (e.g. card declined) to let the customer
1412
+ * re-enter their details. The vault instance, tokenizer iframe, wax key, and
1413
+ * tokenization budget counter are all preserved — no network calls are made.
1414
+ *
1415
+ * **Wax key session model:** by design, one wax key covers the full checkout
1416
+ * session. The default `max_tokenize_calls: 3` supports two declined attempts
1417
+ * and one final attempt on the same key. Do not call `vault.destroy()` and
1418
+ * recreate the vault between declines — that unnecessarily re-mints the key
1419
+ * and discards the remaining budget.
1420
+ *
1421
+ * @example
1422
+ * try {
1423
+ * const { token, cvcSession } = await vault.createToken({ billing });
1424
+ * await chargeCard(token, cvcSession);
1425
+ * } catch (err) {
1426
+ * vault.reset(); // clear fields; let customer re-enter
1427
+ * showError(err.message);
1428
+ * }
1429
+ */
1430
+ reset() {
1431
+ if (this._destroyed)
1432
+ return;
1433
+ const cancelling = Boolean(this._tokenizing);
1434
+ this.log('reset() called', { tokenizing: this._tokenizing, cancelling });
1435
+ if (this._tokenizing) {
1436
+ this._tokenizing = null;
1437
+ this._resetCount++;
1438
+ this.tokenizeResolvers.forEach(({ reject, timeoutId }, requestId) => {
1439
+ if (timeoutId != null)
1440
+ clearTimeout(timeoutId);
1441
+ this.sendToTokenizer({ type: 'OZ_TOKENIZE_CANCEL', requestId });
1442
+ reject(new OzError('Vault was reset while tokenization was in progress.'));
1443
+ });
1444
+ this.tokenizeResolvers.clear();
1445
+ this.bankTokenizeResolvers.forEach(({ reject, timeoutId }, requestId) => {
1446
+ if (timeoutId != null)
1447
+ clearTimeout(timeoutId);
1448
+ this.sendToTokenizer({ type: 'OZ_TOKENIZE_CANCEL', requestId });
1449
+ reject(new OzError('Vault was reset while tokenization was in progress.'));
1450
+ });
1451
+ this.bankTokenizeResolvers.clear();
1452
+ }
1453
+ // Clear field values in all mounted element iframes
1454
+ this.elementsByType.forEach(el => el.clear());
1455
+ this.bankElementsByType.forEach(el => el.clear());
1456
+ // Reset per-element completion state so auto-advance starts fresh on re-entry
1457
+ for (const frameId of this.completionState.keys()) {
1458
+ this.completionState.set(frameId, false);
1459
+ }
1460
+ // NOTE: _tokenizeSuccessCount is intentionally NOT reset.
1461
+ // It reflects real server-side wax key budget consumption. Zeroing it
1462
+ // would desync the proactive refresh logic from the vault's state and
1463
+ // risk triggering a mid-session re-mint on what should be a clean retry.
1464
+ }
1268
1465
  /**
1269
1466
  * Tears down the vault: removes all element iframes, the tokenizer iframe,
1270
1467
  * and the global message listener. Call this when the checkout component
@@ -1275,6 +1472,7 @@ class OzVault {
1275
1472
  if (this._destroyed)
1276
1473
  return;
1277
1474
  this._destroyed = true;
1475
+ this.log('destroy() called');
1278
1476
  window.removeEventListener('message', this.boundHandleMessage);
1279
1477
  document.removeEventListener('visibilitychange', this.boundHandleVisibility);
1280
1478
  if (this._pendingMount) {
@@ -1329,13 +1527,17 @@ class OzVault {
1329
1527
  const REFRESH_THRESHOLD_MS = 20 * 60 * 1000; // 20 minutes
1330
1528
  if (document.hidden) {
1331
1529
  this._hiddenAt = Date.now();
1530
+ this.log('tab hidden');
1332
1531
  }
1333
1532
  else {
1334
- if (this._hiddenAt !== null &&
1335
- Date.now() - this._hiddenAt >= REFRESH_THRESHOLD_MS &&
1336
- this._storedFetchWaxKey &&
1533
+ const hiddenMs = this._hiddenAt !== null ? Date.now() - this._hiddenAt : 0;
1534
+ const willRefresh = (this._hiddenAt !== null &&
1535
+ hiddenMs >= REFRESH_THRESHOLD_MS &&
1536
+ Boolean(this._storedFetchWaxKey) &&
1337
1537
  !this._tokenizing &&
1338
- !this._waxRefreshing) {
1538
+ !this._waxRefreshing);
1539
+ this.log('tab visible', { hiddenMs, willRefresh });
1540
+ if (willRefresh) {
1339
1541
  this.refreshWaxKey().catch((err) => {
1340
1542
  // Proactive refresh failure is non-fatal — the reactive path on the
1341
1543
  // next createToken() call will handle it, including the auth retry.
@@ -1345,6 +1547,56 @@ class OzVault {
1345
1547
  this._hiddenAt = null;
1346
1548
  }
1347
1549
  }
1550
+ // ─── Debug ───────────────────────────────────────────────────────────────
1551
+ /**
1552
+ * Emits a `[OzVault]`-prefixed entry to `console.log`. No-op when `debug` is
1553
+ * not set. Never called with sensitive values — callers use presence flags only.
1554
+ */
1555
+ log(message, data) {
1556
+ if (!this._debug)
1557
+ return;
1558
+ if (data !== undefined) {
1559
+ console.log(`[OzVault] ${message}`, data);
1560
+ }
1561
+ else {
1562
+ console.log(`[OzVault] ${message}`);
1563
+ }
1564
+ }
1565
+ /**
1566
+ * Returns a plain-object snapshot of the vault's current internal state.
1567
+ * Safe to attach to bug reports — no wax keys, tokens, or billing data included.
1568
+ *
1569
+ * Available on all vault instances regardless of whether `debug` was enabled.
1570
+ *
1571
+ * @example
1572
+ * console.log(vault.debugState());
1573
+ * // {
1574
+ * // vaultId: 'vault-abc123',
1575
+ * // isReady: true,
1576
+ * // tokenizing: null,
1577
+ * // destroyed: false,
1578
+ * // waxKeyPresent: true,
1579
+ * // elements: ['cardNumber', 'expirationDate', 'cvv'],
1580
+ * // ...
1581
+ * // }
1582
+ */
1583
+ debugState() {
1584
+ return {
1585
+ vaultId: this.vaultId,
1586
+ isReady: this.tokenizerReady,
1587
+ tokenizing: this._tokenizing,
1588
+ destroyed: this._destroyed,
1589
+ waxKeyPresent: Boolean(this.waxKey),
1590
+ tokenizeSuccessCount: this._tokenizeSuccessCount,
1591
+ maxTokenizeCalls: this._maxTokenizeCalls,
1592
+ resetCount: this._resetCount,
1593
+ elements: [...this.elementsByType.keys()],
1594
+ bankElements: [...this.bankElementsByType.keys()],
1595
+ completionState: Object.fromEntries([...this.completionState.entries()].map(([id, v]) => [id.slice(0, 8), v])),
1596
+ pendingTokenizations: this.tokenizeResolvers.size,
1597
+ pendingBankTokenizations: this.bankTokenizeResolvers.size,
1598
+ };
1599
+ }
1348
1600
  mountTokenizerFrame() {
1349
1601
  const mount = () => {
1350
1602
  this._pendingMount = null;
@@ -1356,6 +1608,7 @@ class OzVault {
1356
1608
  iframe.src = `${this.frameBaseUrl}/frame/tokenizer-frame.html#vaultId=${encodeURIComponent(this.vaultId)}${parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : ''}`;
1357
1609
  document.body.appendChild(iframe);
1358
1610
  this.tokenizerFrame = iframe;
1611
+ this.log('mounting tokenizer iframe');
1359
1612
  };
1360
1613
  if (document.readyState === 'loading') {
1361
1614
  this._pendingMount = mount;
@@ -1391,6 +1644,12 @@ class OzVault {
1391
1644
  // the previous session and justCompleted never fires, breaking auto-advance.
1392
1645
  if (msg.type === 'OZ_FRAME_READY') {
1393
1646
  this.completionState.set(frameId, false);
1647
+ if (msg.__ozVersion !== PROTOCOL_VERSION) {
1648
+ console.warn(`[OzVault] Protocol version mismatch on element frame "${frameId}" — ` +
1649
+ `SDK expects v${PROTOCOL_VERSION}, frame reported v${typeof msg.__ozVersion === 'number' ? msg.__ozVersion : '(none)'}. ` +
1650
+ 'Deploy the matching frame assets to elements.ozura.com and purge the Azure CDN cache.');
1651
+ }
1652
+ this.log('element iframe ready', { type: el.type, frameIdPrefix: frameId.slice(0, 8) });
1394
1653
  }
1395
1654
  // Intercept OZ_CHANGE before forwarding — handle auto-advance and CVV sync
1396
1655
  if (msg.type === 'OZ_CHANGE') {
@@ -1414,6 +1673,7 @@ class OzVault {
1414
1673
  // Require valid too — avoids advancing at 13 digits for unknown-brand cards
1415
1674
  // where isComplete() fires before the user has finished typing.
1416
1675
  const justCompleted = complete && valid && !wasComplete;
1676
+ this.log('field changed', { type: el.type, complete, valid, justCompleted });
1417
1677
  // Sync CVV length when card brand changes
1418
1678
  if (el.type === 'cardNumber') {
1419
1679
  const brand = msg.cardBrand;
@@ -1425,17 +1685,25 @@ class OzVault {
1425
1685
  // Auto-advance focus on completion
1426
1686
  if (justCompleted) {
1427
1687
  if (el.type === 'cardNumber') {
1688
+ this.log('auto-advance', { from: 'cardNumber', to: 'expirationDate' });
1428
1689
  (_b = this.elementsByType.get('expirationDate')) === null || _b === void 0 ? void 0 : _b.focus();
1429
1690
  }
1430
1691
  else if (el.type === 'expirationDate') {
1692
+ this.log('auto-advance', { from: 'expirationDate', to: 'cvv' });
1431
1693
  (_c = this.elementsByType.get('cvv')) === null || _c === void 0 ? void 0 : _c.focus();
1432
1694
  }
1433
1695
  }
1434
1696
  }
1435
1697
  handleTokenizerMessage(msg) {
1436
- var _a, _b, _c;
1698
+ var _a, _b, _c, _d;
1437
1699
  switch (msg.type) {
1438
1700
  case 'OZ_FRAME_READY':
1701
+ if (msg.__ozVersion !== PROTOCOL_VERSION) {
1702
+ console.warn(`[OzVault] Protocol version mismatch — SDK expects v${PROTOCOL_VERSION}, ` +
1703
+ `tokenizer frame reported v${typeof msg.__ozVersion === 'number' ? msg.__ozVersion : '(none)'}. ` +
1704
+ 'This usually means the deployed frame files are stale. ' +
1705
+ 'Deploy the matching frame assets to elements.ozura.com and purge the Azure CDN cache.');
1706
+ }
1439
1707
  this.tokenizerReady = true;
1440
1708
  if (this.loadErrorTimeoutId != null) {
1441
1709
  clearTimeout(this.loadErrorTimeoutId);
@@ -1447,6 +1715,7 @@ class OzVault {
1447
1715
  // sent again from create() once the key is available.
1448
1716
  this.sendToTokenizer(Object.assign({ type: 'OZ_INIT', frameId: '__tokenizer__' }, (this.waxKey ? { waxKey: this.waxKey } : {})));
1449
1717
  (_c = this._onReady) === null || _c === void 0 ? void 0 : _c.call(this);
1718
+ this.log('tokenizer iframe ready', { protocolVersion: (_d = msg.__ozVersion) !== null && _d !== void 0 ? _d : null });
1450
1719
  break;
1451
1720
  case 'OZ_TOKEN_RESULT': {
1452
1721
  if (typeof msg.requestId !== 'string' || !msg.requestId) {
@@ -1471,11 +1740,18 @@ class OzVault {
1471
1740
  }
1472
1741
  pending.resolve(Object.assign(Object.assign({ token,
1473
1742
  cvcSession }, (card ? { card } : {})), (pending.billing ? { billing: pending.billing } : {})));
1743
+ this.log('token received', {
1744
+ elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
1745
+ tokenPresent: true,
1746
+ cvcSessionPresent: true,
1747
+ cardMetadataPresent: Boolean(card),
1748
+ });
1474
1749
  // Increment the per-key success counter and proactively refresh once
1475
1750
  // the budget is exhausted so the next createToken() call uses a fresh
1476
1751
  // key without waiting for a vault rejection.
1477
1752
  this._tokenizeSuccessCount++;
1478
1753
  if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
1754
+ this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1479
1755
  this.refreshWaxKey().catch((err) => {
1480
1756
  console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
1481
1757
  });
@@ -1495,14 +1771,25 @@ class OzVault {
1495
1771
  this.tokenizeResolvers.delete(msg.requestId);
1496
1772
  if (pending.timeoutId != null)
1497
1773
  clearTimeout(pending.timeoutId);
1774
+ const willRefresh = this.isRefreshableAuthError(errorCode, raw) && !pending.retried && Boolean(this._storedFetchWaxKey);
1775
+ this.log('token error', { errorCode, willRefresh });
1498
1776
  // Auto-refresh: if the wax key expired or was consumed and we haven't
1499
1777
  // already retried for this request, transparently re-mint and retry.
1500
- if (this.isRefreshableAuthError(errorCode, raw) && !pending.retried && this._storedFetchWaxKey) {
1778
+ if (willRefresh) {
1779
+ const resetCountAtRetry = this._resetCount;
1501
1780
  this.refreshWaxKey().then(() => {
1502
1781
  if (this._destroyed) {
1503
1782
  pending.reject(new OzError('Vault destroyed during wax key refresh.'));
1504
1783
  return;
1505
1784
  }
1785
+ if (this._resetCount !== resetCountAtRetry) {
1786
+ // reset() was called while the wax key was refreshing — the fields
1787
+ // have been cleared and _tokenizing was zeroed. Reject the original
1788
+ // promise so it doesn't stay pending, and bail out without starting
1789
+ // a new retry (which would tokenize against empty fields).
1790
+ pending.reject(new OzError('Vault was reset while tokenization was in progress.'));
1791
+ return;
1792
+ }
1506
1793
  const newRequestId = `req-${uuid()}`;
1507
1794
  // _tokenizing is still 'card' (cleanup() hasn't been called yet)
1508
1795
  this.tokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, pending), { retried: true }));
@@ -1549,11 +1836,16 @@ class OzVault {
1549
1836
  if (bankPending.timeoutId != null)
1550
1837
  clearTimeout(bankPending.timeoutId);
1551
1838
  if (this.isRefreshableAuthError(errorCode, raw) && !bankPending.retried && this._storedFetchWaxKey) {
1839
+ const resetCountAtRetry = this._resetCount;
1552
1840
  this.refreshWaxKey().then(() => {
1553
1841
  if (this._destroyed) {
1554
1842
  bankPending.reject(new OzError('Vault destroyed during wax key refresh.'));
1555
1843
  return;
1556
1844
  }
1845
+ if (this._resetCount !== resetCountAtRetry) {
1846
+ bankPending.reject(new OzError('Vault was reset while tokenization was in progress.'));
1847
+ return;
1848
+ }
1557
1849
  const newRequestId = `req-${uuid()}`;
1558
1850
  this.bankTokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, bankPending), { retried: true }));
1559
1851
  try {
@@ -1611,9 +1903,15 @@ class OzVault {
1611
1903
  }
1612
1904
  const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
1613
1905
  pending.resolve(Object.assign({ token }, (bank ? { bank } : {})));
1906
+ this.log('bank token received', {
1907
+ elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
1908
+ tokenPresent: true,
1909
+ bankMetadataPresent: Boolean(bank),
1910
+ });
1614
1911
  // Same proactive refresh logic as card tokenization.
1615
1912
  this._tokenizeSuccessCount++;
1616
1913
  if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
1914
+ this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1617
1915
  this.refreshWaxKey().catch((err) => {
1618
1916
  console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
1619
1917
  });
@@ -1669,6 +1967,7 @@ class OzVault {
1669
1967
  }
1670
1968
  const newSessionId = uuid();
1671
1969
  (_a = this._onWaxRefresh) === null || _a === void 0 ? void 0 : _a.call(this);
1970
+ this.log('wax key refresh started');
1672
1971
  this._waxRefreshing = this._storedFetchWaxKey(newSessionId)
1673
1972
  .then(newWaxKey => {
1674
1973
  if (typeof newWaxKey !== 'string' || !newWaxKey.trim()) {
@@ -1682,6 +1981,11 @@ class OzVault {
1682
1981
  if (!this._destroyed && this.tokenizerReady) {
1683
1982
  this.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey: newWaxKey });
1684
1983
  }
1984
+ this.log('wax key refresh succeeded');
1985
+ })
1986
+ .catch((err) => {
1987
+ this.log('wax key refresh failed', { error: err instanceof Error ? err.message : String(err) });
1988
+ throw err;
1685
1989
  })
1686
1990
  .finally(() => {
1687
1991
  this._waxRefreshing = null;
@@ -1695,84 +1999,6 @@ class OzVault {
1695
1999
  }
1696
2000
  }
1697
2001
 
1698
- /**
1699
- * Creates a ready-to-use `fetchWaxKey` callback for `OzVault.create()` and `<OzElements>`.
1700
- *
1701
- * Calls your backend mint endpoint with `{ sessionId }` and returns the wax key string.
1702
- * Throws on non-OK responses or a missing `waxKey` field so the vault can surface the
1703
- * error through its normal error path.
1704
- *
1705
- * Each call enforces a 10-second per-attempt timeout. On a pure network-level
1706
- * failure (connection refused, DNS failure, etc.) the call is retried once after
1707
- * 750ms before throwing. HTTP errors (4xx/5xx) are never retried — they indicate
1708
- * an endpoint misconfiguration or an invalid key, not a transient failure.
1709
- *
1710
- * The mint endpoint is typically the one-line `createMintWaxHandler` / `createMintWaxMiddleware`
1711
- * from `@ozura/elements/server`.
1712
- *
1713
- * @param mintUrl - Absolute or relative URL of your wax-key mint endpoint, e.g. `'/api/mint-wax'`.
1714
- *
1715
- * @example
1716
- * // Vanilla JS
1717
- * const vault = await OzVault.create({
1718
- * pubKey: 'pk_live_...',
1719
- * fetchWaxKey: createFetchWaxKey('/api/mint-wax'),
1720
- * });
1721
- *
1722
- * @example
1723
- * // React
1724
- * <OzElements pubKey="pk_live_..." fetchWaxKey={createFetchWaxKey('/api/mint-wax')}>
1725
- */
1726
- function createFetchWaxKey(mintUrl) {
1727
- const TIMEOUT_MS = 10000;
1728
- // Each attempt gets its own AbortController so a timeout on attempt 1 does
1729
- // not bleed into the retry. Uses AbortController + setTimeout instead of
1730
- // AbortSignal.timeout() to support environments without that API.
1731
- const attemptFetch = (sessionId) => {
1732
- const controller = new AbortController();
1733
- const timer = setTimeout(() => controller.abort(), TIMEOUT_MS);
1734
- return fetch(mintUrl, {
1735
- method: 'POST',
1736
- headers: { 'Content-Type': 'application/json' },
1737
- body: JSON.stringify({ sessionId }),
1738
- signal: controller.signal,
1739
- }).finally(() => clearTimeout(timer));
1740
- };
1741
- return async (sessionId) => {
1742
- let res;
1743
- try {
1744
- res = await attemptFetch(sessionId);
1745
- }
1746
- catch (firstErr) {
1747
- // Abort/timeout should not be retried — the server received nothing or
1748
- // we already waited the full timeout duration.
1749
- if (firstErr instanceof Error && (firstErr.name === 'AbortError' || firstErr.name === 'TimeoutError')) {
1750
- throw new OzError(`Wax key mint timed out after ${TIMEOUT_MS / 1000}s (${mintUrl})`, undefined, 'timeout');
1751
- }
1752
- // Pure network error (offline, DNS, connection refused) — retry once
1753
- // after a short pause in case of a transient blip.
1754
- await new Promise(resolve => setTimeout(resolve, 750));
1755
- try {
1756
- res = await attemptFetch(sessionId);
1757
- }
1758
- catch (retryErr) {
1759
- const msg = retryErr instanceof Error ? retryErr.message : 'Network error';
1760
- throw new OzError(`Could not reach wax key mint endpoint (${mintUrl}): ${msg}`, undefined, 'network');
1761
- }
1762
- }
1763
- const data = await res.json().catch(() => ({}));
1764
- if (!res.ok) {
1765
- throw new OzError(typeof data.error === 'string' && data.error
1766
- ? data.error
1767
- : `Wax key mint failed (HTTP ${res.status})`, undefined, res.status >= 500 ? 'server' : res.status === 401 || res.status === 403 ? 'auth' : 'validation');
1768
- }
1769
- if (typeof data.waxKey !== 'string' || !data.waxKey.trim()) {
1770
- throw new OzError('Mint endpoint response is missing waxKey. Check your /api/mint-wax implementation.', undefined, 'validation');
1771
- }
1772
- return data.waxKey;
1773
- };
1774
- }
1775
-
1776
2002
  const OzContext = createContext({
1777
2003
  vault: null,
1778
2004
  initError: null,
@@ -1789,7 +2015,7 @@ const OzContext = createContext({
1789
2015
  * All `<OzCardNumber />`, `<OzExpiry />`, and `<OzCvv />` children must be
1790
2016
  * rendered inside this provider.
1791
2017
  */
1792
- function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loadTimeoutMs, onWaxRefresh, onReady, appearance, maxTokenizeCalls, children }) {
2018
+ function OzElements({ sessionUrl, getSessionKey, fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loadTimeoutMs, onSessionRefresh, onWaxRefresh, onReady, appearance, sessionLimit, maxTokenizeCalls, debug, children }) {
1793
2019
  const [vault, setVault] = useState(null);
1794
2020
  const [initError, setInitError] = useState(null);
1795
2021
  const [mountedCount, setMountedCount] = useState(0);
@@ -1797,13 +2023,14 @@ function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loa
1797
2023
  const [tokenizeCount, setTokenizeCount] = useState(0);
1798
2024
  const onLoadErrorRef = useRef(onLoadError);
1799
2025
  onLoadErrorRef.current = onLoadError;
1800
- const onWaxRefreshRef = useRef(onWaxRefresh);
1801
- onWaxRefreshRef.current = onWaxRefresh;
2026
+ const onWaxRefreshRef = useRef(onSessionRefresh !== null && onSessionRefresh !== void 0 ? onSessionRefresh : onWaxRefresh);
2027
+ onWaxRefreshRef.current = onSessionRefresh !== null && onSessionRefresh !== void 0 ? onSessionRefresh : onWaxRefresh;
1802
2028
  const onReadyRef = useRef(onReady);
1803
2029
  onReadyRef.current = onReady;
1804
- // Keep a ref to fetchWaxKey so changes don't trigger vault recreation
1805
- const fetchWaxKeyRef = useRef(fetchWaxKey);
1806
- fetchWaxKeyRef.current = fetchWaxKey;
2030
+ // Keep a ref to the session callback so changes don't trigger vault recreation.
2031
+ // Priority mirrors OzVault.create(): sessionUrl > getSessionKey > fetchWaxKey.
2032
+ const getSessionKeyRef = useRef(getSessionKey !== null && getSessionKey !== void 0 ? getSessionKey : fetchWaxKey);
2033
+ getSessionKeyRef.current = getSessionKey !== null && getSessionKey !== void 0 ? getSessionKey : fetchWaxKey;
1807
2034
  const appearanceKey = useMemo(() => appearance ? JSON.stringify(appearance) : '', [appearance]);
1808
2035
  const fontsKey = useMemo(() => fonts ? JSON.stringify(fonts) : '', [fonts]);
1809
2036
  useEffect(() => {
@@ -1830,7 +2057,9 @@ function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loa
1830
2057
  // synchronously rather than waiting for the promise to settle. Without this,
1831
2058
  // two hidden iframes and two window listeners briefly coexist.
1832
2059
  const abortController = new AbortController();
1833
- OzVault.create(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ pubKey, fetchWaxKey: (sessionId) => fetchWaxKeyRef.current(sessionId) }, (frameBaseUrl ? { frameBaseUrl } : {})), (parsedFonts ? { fonts: parsedFonts } : {})), (parsedAppearance ? { appearance: parsedAppearance } : {})), (onLoadErrorRef.current ? { onLoadError: fireLoadError, loadTimeoutMs } : {})), {
2060
+ OzVault.create(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ pubKey }, (sessionUrl
2061
+ ? { sessionUrl }
2062
+ : { getSessionKey: (sessionId) => getSessionKeyRef.current(sessionId) })), (frameBaseUrl ? { frameBaseUrl } : {})), (parsedFonts ? { fonts: parsedFonts } : {})), (parsedAppearance ? { appearance: parsedAppearance } : {})), (onLoadErrorRef.current ? { onLoadError: fireLoadError, loadTimeoutMs } : {})), {
1834
2063
  // Always install onWaxRefresh internally so we can reset tokenizeCount
1835
2064
  // when any wax key refresh occurs (reactive TTL expiry, post-budget
1836
2065
  // proactive, or visibility-change proactive). Without this the React
@@ -1852,11 +2081,12 @@ function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loa
1852
2081
  // the retry tokenization completes (a full fetchWaxKey + tokenize round-
1853
2082
  // trip separates them), so the count correctly resets to 0 then rises to
1854
2083
  // 1 after the retry notifyTokenize fires.
1855
- onWaxRefresh: () => {
2084
+ onSessionRefresh: () => {
1856
2085
  var _a;
1857
2086
  Promise.resolve().then(() => setTokenizeCount(0));
1858
2087
  (_a = onWaxRefreshRef.current) === null || _a === void 0 ? void 0 : _a.call(onWaxRefreshRef);
1859
- } }), (onReadyRef.current ? { onReady: () => { var _a; return (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef); } } : {})), (maxTokenizeCalls !== undefined ? { maxTokenizeCalls } : {})), abortController.signal).then(v => {
2088
+ }, onReady: () => { var _a; return (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef); } }), (sessionLimit !== undefined ? { sessionLimit }
2089
+ : maxTokenizeCalls !== undefined ? { maxTokenizeCalls } : {})), (debug ? { debug: true } : {})), abortController.signal).then(v => {
1860
2090
  if (cancelled) {
1861
2091
  v.destroy();
1862
2092
  return;
@@ -1889,7 +2119,7 @@ function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loa
1889
2119
  setVault(null);
1890
2120
  setInitError(null);
1891
2121
  };
1892
- }, [pubKey, frameBaseUrl, loadTimeoutMs, appearanceKey, fontsKey, maxTokenizeCalls]);
2122
+ }, [pubKey, sessionUrl, frameBaseUrl, loadTimeoutMs, appearanceKey, fontsKey, sessionLimit, maxTokenizeCalls, debug]);
1893
2123
  const notifyMount = useCallback(() => setMountedCount(n => n + 1), []);
1894
2124
  const notifyReady = useCallback(() => setReadyCount(n => n + 1), []);
1895
2125
  const notifyUnmount = useCallback(() => {
@@ -1922,8 +2152,11 @@ function useOzElements() {
1922
2152
  notifyTokenize();
1923
2153
  return result;
1924
2154
  }, [vault, notifyTokenize]);
2155
+ const reset = useCallback(() => {
2156
+ vault === null || vault === void 0 ? void 0 : vault.reset();
2157
+ }, [vault]);
1925
2158
  const ready = vault !== null && vault.isReady && mountedCount > 0 && readyCount >= mountedCount;
1926
- return { createToken, createBankToken, ready, initError, tokenizeCount };
2159
+ return { createToken, createBankToken, reset, ready, initError, tokenizeCount };
1927
2160
  }
1928
2161
  const SKELETON_STYLE = {
1929
2162
  height: 46,
@@ -2018,6 +2251,71 @@ const OzCardNumber = (props) => jsx(OzFieldBase, Object.assign({ type: "cardNumb
2018
2251
  const OzExpiry = (props) => jsx(OzFieldBase, Object.assign({ type: "expirationDate", variant: "card" }, props));
2019
2252
  /** Renders a PCI-isolated CVV input inside an Ozura iframe. */
2020
2253
  const OzCvv = (props) => jsx(OzFieldBase, Object.assign({ type: "cvv", variant: "card" }, props));
2254
+ // ─── Shared composite-component hook ─────────────────────────────────────────
2255
+ /**
2256
+ * Shared plumbing for OzCard and OzBankCard.
2257
+ *
2258
+ * Manages:
2259
+ * - Callback refs (onChange, onReady, onFocus, onBlur) kept in sync on every render
2260
+ * - Vault-change detection: resets `readyFieldTypes` and `onReadyFiredRef` when the
2261
+ * vault instance is replaced (e.g. after fetchWaxKey changes or the provider remounts)
2262
+ * - Per-field ready tracking: creates one stable handler per named field; fires the
2263
+ * `onReady` callback once all `fieldNames.length` fields have reported ready
2264
+ * - Error state
2265
+ * - Layout helpers: `gapStr`, `renderLabel`
2266
+ *
2267
+ * @internal — not exported; used only by OzCard and OzBankCard.
2268
+ */
2269
+ function useCardBase({ vault, fieldNames, onChange, onReady, onFocus, onBlur, gap = 8, labelStyle, labelClassName, }) {
2270
+ const totalFields = fieldNames.length;
2271
+ const readyFieldTypes = useRef(new Set());
2272
+ const onReadyFiredRef = useRef(false);
2273
+ const vaultRef = useRef(vault);
2274
+ const onChangeRef = useRef(onChange);
2275
+ const onReadyRef = useRef(onReady);
2276
+ const onFocusRef = useRef(onFocus);
2277
+ const onBlurRef = useRef(onBlur);
2278
+ useEffect(() => { onChangeRef.current = onChange; }, [onChange]);
2279
+ useEffect(() => { onReadyRef.current = onReady; }, [onReady]);
2280
+ useEffect(() => { onFocusRef.current = onFocus; }, [onFocus]);
2281
+ useEffect(() => { onBlurRef.current = onBlur; }, [onBlur]);
2282
+ useEffect(() => {
2283
+ if (vault !== vaultRef.current) {
2284
+ vaultRef.current = vault;
2285
+ readyFieldTypes.current = new Set();
2286
+ onReadyFiredRef.current = false;
2287
+ }
2288
+ return () => {
2289
+ readyFieldTypes.current = new Set();
2290
+ onReadyFiredRef.current = false;
2291
+ };
2292
+ }, [vault]);
2293
+ // One stable handler per named field — recreated only when total field count changes.
2294
+ // Field names are static (card = 3 fields, bank = 2 fields) so `totalFields` alone
2295
+ // is a sufficient dependency; a JSON dep would create a new map on every render.
2296
+ // CONTRACT: `fieldNames` must be a static literal — callers must not pass a dynamic
2297
+ // array that changes length without also changing field count.
2298
+ // eslint-disable-next-line react-hooks/exhaustive-deps
2299
+ const readyHandlers = useMemo(() => {
2300
+ const handlers = {};
2301
+ for (const name of fieldNames) {
2302
+ handlers[name] = () => {
2303
+ var _a;
2304
+ readyFieldTypes.current.add(name);
2305
+ if (readyFieldTypes.current.size >= totalFields && !onReadyFiredRef.current) {
2306
+ onReadyFiredRef.current = true;
2307
+ (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
2308
+ }
2309
+ };
2310
+ }
2311
+ return handlers;
2312
+ }, [totalFields]); // totalFields captures fieldNames.length; field names are static
2313
+ const [error, setError] = useState();
2314
+ const gapStr = typeof gap === 'string' ? gap : `${gap}px`;
2315
+ const resolvedLabelStyle = useMemo(() => (labelStyle ? Object.assign(Object.assign({}, DEFAULT_LABEL_STYLE), labelStyle) : DEFAULT_LABEL_STYLE), [labelStyle]);
2316
+ const renderLabel = useCallback((text) => renderFieldLabel(text, labelClassName, resolvedLabelStyle), [labelClassName, resolvedLabelStyle]);
2317
+ return { onChangeRef, onFocusRef, onBlurRef, readyHandlers, error, setError, gapStr, renderLabel };
2318
+ }
2021
2319
  const DEFAULT_ERROR_STYLE = {
2022
2320
  color: '#dc2626',
2023
2321
  fontSize: 13,
@@ -2060,62 +2358,22 @@ function mergeStyles(base, override) {
2060
2358
  function OzCard({ style, styles, classNames, labels, labelStyle, labelClassName, layout = 'default', gap = 8, hideErrors = false, errorStyle, errorClassName, renderError, onChange, onReady, onFocus, onBlur, disabled, className, placeholders, }) {
2061
2359
  var _a, _b, _c;
2062
2360
  const { vault } = useContext(OzContext);
2361
+ const { onChangeRef, onFocusRef, onBlurRef, readyHandlers, error, setError, gapStr, renderLabel, } = useCardBase({
2362
+ vault,
2363
+ fieldNames: ['cardNumber', 'expiry', 'cvv'],
2364
+ onChange,
2365
+ onReady,
2366
+ onFocus,
2367
+ onBlur,
2368
+ gap,
2369
+ labelStyle,
2370
+ labelClassName,
2371
+ });
2063
2372
  const fieldState = useRef({
2064
2373
  cardNumber: null,
2065
2374
  expiry: null,
2066
2375
  cvv: null,
2067
2376
  });
2068
- const readyFieldTypes = useRef(new Set());
2069
- const onReadyFiredRef = useRef(false);
2070
- const vaultRef = useRef(vault);
2071
- const onChangeRef = useRef(onChange);
2072
- const onReadyRef = useRef(onReady);
2073
- const onFocusRef = useRef(onFocus);
2074
- const onBlurRef = useRef(onBlur);
2075
- useEffect(() => { onChangeRef.current = onChange; }, [onChange]);
2076
- useEffect(() => { onReadyRef.current = onReady; }, [onReady]);
2077
- useEffect(() => { onFocusRef.current = onFocus; }, [onFocus]);
2078
- useEffect(() => { onBlurRef.current = onBlur; }, [onBlur]);
2079
- // When the vault is recreated (e.g. appearance/fonts props change on OzElements),
2080
- // context readyCount is reset but this ref is not. Reset so onReady fires once when all 3 are ready.
2081
- // The cleanup resets readyFieldTypes when the component unmounts (covers React StrictMode double-invoke
2082
- // and SPA scenarios where the parent re-mounts this component).
2083
- useEffect(() => {
2084
- if (vault !== vaultRef.current) {
2085
- vaultRef.current = vault;
2086
- readyFieldTypes.current = new Set();
2087
- onReadyFiredRef.current = false;
2088
- }
2089
- return () => {
2090
- readyFieldTypes.current = new Set();
2091
- onReadyFiredRef.current = false;
2092
- };
2093
- }, [vault]);
2094
- const [error, setError] = useState();
2095
- const handleCardNumberReady = useCallback(() => {
2096
- var _a;
2097
- readyFieldTypes.current.add('cardNumber');
2098
- if (readyFieldTypes.current.size >= 3 && !onReadyFiredRef.current) {
2099
- onReadyFiredRef.current = true;
2100
- (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
2101
- }
2102
- }, []);
2103
- const handleExpiryReady = useCallback(() => {
2104
- var _a;
2105
- readyFieldTypes.current.add('expiry');
2106
- if (readyFieldTypes.current.size >= 3 && !onReadyFiredRef.current) {
2107
- onReadyFiredRef.current = true;
2108
- (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
2109
- }
2110
- }, []);
2111
- const handleCvvReady = useCallback(() => {
2112
- var _a;
2113
- readyFieldTypes.current.add('cvv');
2114
- if (readyFieldTypes.current.size >= 3 && !onReadyFiredRef.current) {
2115
- onReadyFiredRef.current = true;
2116
- (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
2117
- }
2118
- }, []);
2119
2377
  const emitChange = useCallback(() => {
2120
2378
  var _a;
2121
2379
  const { cardNumber, expiry, cvv } = fieldState.current;
@@ -2130,20 +2388,16 @@ function OzCard({ style, styles, classNames, labels, labelStyle, labelClassName,
2130
2388
  error: err,
2131
2389
  fields: Object.assign({}, fieldState.current),
2132
2390
  });
2133
- }, []);
2134
- const gapStr = typeof gap === 'string' ? gap : `${gap}px`;
2135
- const resolvedLabelStyle = labelStyle
2136
- ? Object.assign(Object.assign({}, DEFAULT_LABEL_STYLE), labelStyle) : DEFAULT_LABEL_STYLE;
2137
- const renderLabel = (text) => renderFieldLabel(text, labelClassName, resolvedLabelStyle);
2391
+ }, [setError, onChangeRef]);
2138
2392
  const showError = !hideErrors && error;
2139
2393
  const errorNode = showError
2140
2394
  ? renderError
2141
2395
  ? renderError(error)
2142
2396
  : (jsx("div", { role: "alert", className: errorClassName, style: errorStyle ? Object.assign(Object.assign({}, DEFAULT_ERROR_STYLE), errorStyle) : DEFAULT_ERROR_STYLE, children: error }))
2143
2397
  : null;
2144
- const cardNumberField = (jsxs("div", { children: [renderLabel(labels === null || labels === void 0 ? void 0 : labels.cardNumber), jsx(OzCardNumber, { style: mergeStyles(style, styles === null || styles === void 0 ? void 0 : styles.cardNumber), className: classNames === null || classNames === void 0 ? void 0 : classNames.cardNumber, placeholder: (_a = placeholders === null || placeholders === void 0 ? void 0 : placeholders.cardNumber) !== null && _a !== void 0 ? _a : 'Card number', disabled: disabled, onChange: (e) => { fieldState.current.cardNumber = e; emitChange(); }, onFocus: () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef, 'cardNumber'); }, onBlur: () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, 'cardNumber'); }, onReady: handleCardNumberReady })] }));
2145
- const expiryField = (jsxs("div", { style: layout === 'default' ? { flex: 1 } : undefined, children: [renderLabel(labels === null || labels === void 0 ? void 0 : labels.expiry), jsx(OzExpiry, { style: mergeStyles(style, styles === null || styles === void 0 ? void 0 : styles.expiry), className: classNames === null || classNames === void 0 ? void 0 : classNames.expiry, placeholder: (_b = placeholders === null || placeholders === void 0 ? void 0 : placeholders.expiry) !== null && _b !== void 0 ? _b : 'MM / YY', disabled: disabled, onChange: (e) => { fieldState.current.expiry = e; emitChange(); }, onFocus: () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef, 'expiry'); }, onBlur: () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, 'expiry'); }, onReady: handleExpiryReady })] }));
2146
- const cvvField = (jsxs("div", { style: layout === 'default' ? { flex: 1 } : undefined, children: [renderLabel(labels === null || labels === void 0 ? void 0 : labels.cvv), jsx(OzCvv, { style: mergeStyles(style, styles === null || styles === void 0 ? void 0 : styles.cvv), className: classNames === null || classNames === void 0 ? void 0 : classNames.cvv, placeholder: (_c = placeholders === null || placeholders === void 0 ? void 0 : placeholders.cvv) !== null && _c !== void 0 ? _c : 'CVV', disabled: disabled, onChange: (e) => { fieldState.current.cvv = e; emitChange(); }, onFocus: () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef, 'cvv'); }, onBlur: () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, 'cvv'); }, onReady: handleCvvReady })] }));
2398
+ const cardNumberField = (jsxs("div", { children: [renderLabel(labels === null || labels === void 0 ? void 0 : labels.cardNumber), jsx(OzCardNumber, { style: mergeStyles(style, styles === null || styles === void 0 ? void 0 : styles.cardNumber), className: classNames === null || classNames === void 0 ? void 0 : classNames.cardNumber, placeholder: (_a = placeholders === null || placeholders === void 0 ? void 0 : placeholders.cardNumber) !== null && _a !== void 0 ? _a : 'Card number', disabled: disabled, onChange: (e) => { fieldState.current.cardNumber = e; emitChange(); }, onFocus: () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef, 'cardNumber'); }, onBlur: () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, 'cardNumber'); }, onReady: readyHandlers['cardNumber'] })] }));
2399
+ const expiryField = (jsxs("div", { style: layout === 'default' ? { flex: 1 } : undefined, children: [renderLabel(labels === null || labels === void 0 ? void 0 : labels.expiry), jsx(OzExpiry, { style: mergeStyles(style, styles === null || styles === void 0 ? void 0 : styles.expiry), className: classNames === null || classNames === void 0 ? void 0 : classNames.expiry, placeholder: (_b = placeholders === null || placeholders === void 0 ? void 0 : placeholders.expiry) !== null && _b !== void 0 ? _b : 'MM / YY', disabled: disabled, onChange: (e) => { fieldState.current.expiry = e; emitChange(); }, onFocus: () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef, 'expiry'); }, onBlur: () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, 'expiry'); }, onReady: readyHandlers['expiry'] })] }));
2400
+ const cvvField = (jsxs("div", { style: layout === 'default' ? { flex: 1 } : undefined, children: [renderLabel(labels === null || labels === void 0 ? void 0 : labels.cvv), jsx(OzCvv, { style: mergeStyles(style, styles === null || styles === void 0 ? void 0 : styles.cvv), className: classNames === null || classNames === void 0 ? void 0 : classNames.cvv, placeholder: (_c = placeholders === null || placeholders === void 0 ? void 0 : placeholders.cvv) !== null && _c !== void 0 ? _c : 'CVV', disabled: disabled, onChange: (e) => { fieldState.current.cvv = e; emitChange(); }, onFocus: () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef, 'cvv'); }, onBlur: () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, 'cvv'); }, onReady: readyHandlers['cvv'] })] }));
2147
2401
  if (layout === 'rows') {
2148
2402
  return (jsxs("div", { className: className, style: { width: '100%', display: 'flex', flexDirection: 'column', gap: gapStr }, children: [cardNumberField, expiryField, cvvField, errorNode] }));
2149
2403
  }
@@ -2164,49 +2418,21 @@ const OzBankRoutingNumber = (props) => jsx(OzFieldBase, Object.assign({ type: "r
2164
2418
  function OzBankCard({ style, styles, classNames, labels, labelStyle, labelClassName, gap = 8, hideErrors = false, errorStyle, errorClassName, renderError, onChange, onReady, onFocus, onBlur, disabled, className, placeholders, }) {
2165
2419
  var _a, _b;
2166
2420
  const { vault } = useContext(OzContext);
2421
+ const { onChangeRef, onFocusRef, onBlurRef, readyHandlers, error, setError, gapStr, renderLabel, } = useCardBase({
2422
+ vault,
2423
+ fieldNames: ['accountNumber', 'routingNumber'],
2424
+ onChange,
2425
+ onReady,
2426
+ onFocus,
2427
+ onBlur,
2428
+ gap,
2429
+ labelStyle,
2430
+ labelClassName,
2431
+ });
2167
2432
  const fieldState = useRef({
2168
2433
  accountNumber: null,
2169
2434
  routingNumber: null,
2170
2435
  });
2171
- const readyFieldTypes = useRef(new Set());
2172
- const onReadyFiredRef = useRef(false);
2173
- const vaultRef = useRef(vault);
2174
- const onChangeRef = useRef(onChange);
2175
- const onReadyRef = useRef(onReady);
2176
- const onFocusRef = useRef(onFocus);
2177
- const onBlurRef = useRef(onBlur);
2178
- useEffect(() => { onChangeRef.current = onChange; }, [onChange]);
2179
- useEffect(() => { onReadyRef.current = onReady; }, [onReady]);
2180
- useEffect(() => { onFocusRef.current = onFocus; }, [onFocus]);
2181
- useEffect(() => { onBlurRef.current = onBlur; }, [onBlur]);
2182
- useEffect(() => {
2183
- if (vault !== vaultRef.current) {
2184
- vaultRef.current = vault;
2185
- readyFieldTypes.current = new Set();
2186
- onReadyFiredRef.current = false;
2187
- }
2188
- return () => {
2189
- readyFieldTypes.current = new Set();
2190
- onReadyFiredRef.current = false;
2191
- };
2192
- }, [vault]);
2193
- const [error, setError] = useState();
2194
- const handleAccountReady = useCallback(() => {
2195
- var _a;
2196
- readyFieldTypes.current.add('accountNumber');
2197
- if (readyFieldTypes.current.size >= 2 && !onReadyFiredRef.current) {
2198
- onReadyFiredRef.current = true;
2199
- (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
2200
- }
2201
- }, []);
2202
- const handleRoutingReady = useCallback(() => {
2203
- var _a;
2204
- readyFieldTypes.current.add('routingNumber');
2205
- if (readyFieldTypes.current.size >= 2 && !onReadyFiredRef.current) {
2206
- onReadyFiredRef.current = true;
2207
- (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
2208
- }
2209
- }, []);
2210
2436
  const emitChange = useCallback(() => {
2211
2437
  var _a;
2212
2438
  const { accountNumber, routingNumber } = fieldState.current;
@@ -2219,19 +2445,15 @@ function OzBankCard({ style, styles, classNames, labels, labelStyle, labelClassN
2219
2445
  error: err,
2220
2446
  fields: Object.assign({}, fieldState.current),
2221
2447
  });
2222
- }, []);
2223
- const gapStr = typeof gap === 'string' ? gap : `${gap}px`;
2224
- const resolvedLabelStyle = labelStyle
2225
- ? Object.assign(Object.assign({}, DEFAULT_LABEL_STYLE), labelStyle) : DEFAULT_LABEL_STYLE;
2226
- const renderLabel = (text) => renderFieldLabel(text, labelClassName, resolvedLabelStyle);
2448
+ }, [setError, onChangeRef]);
2227
2449
  const showError = !hideErrors && error;
2228
2450
  const errorNode = showError
2229
2451
  ? renderError
2230
2452
  ? renderError(error)
2231
2453
  : (jsx("div", { role: "alert", className: errorClassName, style: errorStyle ? Object.assign(Object.assign({}, DEFAULT_ERROR_STYLE), errorStyle) : DEFAULT_ERROR_STYLE, children: error }))
2232
2454
  : null;
2233
- return (jsxs("div", { className: className, style: { width: '100%', display: 'flex', flexDirection: 'column', gap: gapStr }, children: [jsxs("div", { children: [renderLabel(labels === null || labels === void 0 ? void 0 : labels.accountNumber), jsx(OzBankAccountNumber, { style: mergeStyles(style, styles === null || styles === void 0 ? void 0 : styles.accountNumber), className: classNames === null || classNames === void 0 ? void 0 : classNames.accountNumber, placeholder: (_a = placeholders === null || placeholders === void 0 ? void 0 : placeholders.accountNumber) !== null && _a !== void 0 ? _a : 'Account number', disabled: disabled, onChange: (e) => { fieldState.current.accountNumber = e; emitChange(); }, onFocus: () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef, 'accountNumber'); }, onBlur: () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, 'accountNumber'); }, onReady: handleAccountReady })] }), jsxs("div", { children: [renderLabel(labels === null || labels === void 0 ? void 0 : labels.routingNumber), jsx(OzBankRoutingNumber, { style: mergeStyles(style, styles === null || styles === void 0 ? void 0 : styles.routingNumber), className: classNames === null || classNames === void 0 ? void 0 : classNames.routingNumber, placeholder: (_b = placeholders === null || placeholders === void 0 ? void 0 : placeholders.routingNumber) !== null && _b !== void 0 ? _b : 'Routing number', disabled: disabled, onChange: (e) => { fieldState.current.routingNumber = e; emitChange(); }, onFocus: () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef, 'routingNumber'); }, onBlur: () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, 'routingNumber'); }, onReady: handleRoutingReady })] }), errorNode] }));
2455
+ return (jsxs("div", { className: className, style: { width: '100%', display: 'flex', flexDirection: 'column', gap: gapStr }, children: [jsxs("div", { children: [renderLabel(labels === null || labels === void 0 ? void 0 : labels.accountNumber), jsx(OzBankAccountNumber, { style: mergeStyles(style, styles === null || styles === void 0 ? void 0 : styles.accountNumber), className: classNames === null || classNames === void 0 ? void 0 : classNames.accountNumber, placeholder: (_a = placeholders === null || placeholders === void 0 ? void 0 : placeholders.accountNumber) !== null && _a !== void 0 ? _a : 'Account number', disabled: disabled, onChange: (e) => { fieldState.current.accountNumber = e; emitChange(); }, onFocus: () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef, 'accountNumber'); }, onBlur: () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, 'accountNumber'); }, onReady: readyHandlers['accountNumber'] })] }), jsxs("div", { children: [renderLabel(labels === null || labels === void 0 ? void 0 : labels.routingNumber), jsx(OzBankRoutingNumber, { style: mergeStyles(style, styles === null || styles === void 0 ? void 0 : styles.routingNumber), className: classNames === null || classNames === void 0 ? void 0 : classNames.routingNumber, placeholder: (_b = placeholders === null || placeholders === void 0 ? void 0 : placeholders.routingNumber) !== null && _b !== void 0 ? _b : 'Routing number', disabled: disabled, onChange: (e) => { fieldState.current.routingNumber = e; emitChange(); }, onFocus: () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef, 'routingNumber'); }, onBlur: () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef, 'routingNumber'); }, onReady: readyHandlers['routingNumber'] })] }), errorNode] }));
2234
2456
  }
2235
2457
 
2236
- export { OzBankAccountNumber, OzBankCard, OzBankRoutingNumber, OzCard, OzCardNumber, OzCvv, OzElements, OzExpiry, createFetchWaxKey, useOzElements };
2458
+ export { OzBankAccountNumber, OzBankCard, OzBankRoutingNumber, OzCard, OzCardNumber, OzCvv, OzElements, OzExpiry, createSessionFetcher as createFetchWaxKey, useOzElements };
2237
2459
  //# sourceMappingURL=index.esm.js.map