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