@ozura/elements 1.0.2-next.9 → 1.1.0-next.21

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.
@@ -407,7 +407,7 @@ class OzElement {
407
407
  * (useful when integrating with React refs).
408
408
  */
409
409
  mount(target) {
410
- var _a;
410
+ var _a, _b;
411
411
  if (this._destroyed)
412
412
  throw new OzError('Cannot mount a destroyed element.');
413
413
  if (this.iframe)
@@ -424,7 +424,13 @@ class OzElement {
424
424
  iframe.setAttribute('scrolling', 'no');
425
425
  iframe.setAttribute('allowtransparency', 'true');
426
426
  iframe.style.cssText = 'border:none;width:100%;height:46px;display:block;overflow:hidden;';
427
- iframe.title = `Secure ${this.elementType} input`;
427
+ iframe.title = `Secure ${(_a = {
428
+ cardNumber: 'card number',
429
+ expirationDate: 'expiration date',
430
+ cvv: 'CVV',
431
+ accountNumber: 'account number',
432
+ routingNumber: 'routing number',
433
+ }[this.elementType]) !== null && _a !== void 0 ? _a : this.elementType} input`;
428
434
  // Note: the `sandbox` attribute is intentionally NOT set. Field values are
429
435
  // delivered to the tokenizer iframe via a MessageChannel port (transferred
430
436
  // in OZ_BEGIN_COLLECT), so no window.parent named-frame lookup is needed.
@@ -438,7 +444,7 @@ class OzElement {
438
444
  container.appendChild(iframe);
439
445
  this.iframe = iframe;
440
446
  this._frameWindow = iframe.contentWindow;
441
- const timeout = (_a = this.options.loadTimeoutMs) !== null && _a !== void 0 ? _a : 10000;
447
+ const timeout = (_b = this.options.loadTimeoutMs) !== null && _b !== void 0 ? _b : 10000;
442
448
  this._loadTimer = setTimeout(() => {
443
449
  if (!this._ready && !this._destroyed) {
444
450
  this.emit('loaderror', { elementType: this.elementType, error: `${this.elementType} iframe failed to load within ${timeout}ms` });
@@ -923,7 +929,7 @@ class OzVault {
923
929
  * @internal
924
930
  */
925
931
  constructor(options, waxKey, tokenizationSessionId) {
926
- var _a, _b, _c;
932
+ var _a, _b, _c, _d;
927
933
  this.elements = new Map();
928
934
  this.elementsByType = new Map();
929
935
  this.bankElementsByType = new Map();
@@ -936,6 +942,9 @@ class OzVault {
936
942
  this.tokenizerReady = false;
937
943
  this._tokenizing = null;
938
944
  this._destroyed = false;
945
+ // Incremented every time reset() cancels an active tokenization so that
946
+ // any in-flight wax-key refresh retry can detect it was superseded.
947
+ this._resetCount = 0;
939
948
  // Tracks successful tokenizations against the per-key call budget so the SDK
940
949
  // can proactively refresh the wax key after it has been consumed rather than
941
950
  // waiting for the next createToken() call to fail.
@@ -955,13 +964,14 @@ class OzVault {
955
964
  this.resolvedAppearance = resolveAppearance(options.appearance);
956
965
  this.vaultId = `vault-${uuid()}`;
957
966
  this._maxTokenizeCalls = (_b = options.maxTokenizeCalls) !== null && _b !== void 0 ? _b : 3;
967
+ this._debug = (_c = options.debug) !== null && _c !== void 0 ? _c : false;
958
968
  this.boundHandleMessage = this.handleMessage.bind(this);
959
969
  window.addEventListener('message', this.boundHandleMessage);
960
970
  this.boundHandleVisibility = this.handleVisibilityChange.bind(this);
961
971
  document.addEventListener('visibilitychange', this.boundHandleVisibility);
962
972
  this.mountTokenizerFrame();
963
973
  if (options.onLoadError) {
964
- const timeout = (_c = options.loadTimeoutMs) !== null && _c !== void 0 ? _c : 10000;
974
+ const timeout = (_d = options.loadTimeoutMs) !== null && _d !== void 0 ? _d : 10000;
965
975
  this.loadErrorTimeoutId = setTimeout(() => {
966
976
  this.loadErrorTimeoutId = null;
967
977
  if (!this._destroyed && !this.tokenizerReady) {
@@ -971,6 +981,7 @@ class OzVault {
971
981
  }
972
982
  this._onWaxRefresh = options.onWaxRefresh;
973
983
  this._onReady = options.onReady;
984
+ this.log('vault created', { vaultId: this.vaultId, frameBaseUrl: this.frameBaseUrl, maxTokenizeCalls: this._maxTokenizeCalls });
974
985
  }
975
986
  /**
976
987
  * Creates and returns a ready `OzVault` instance.
@@ -1040,6 +1051,7 @@ class OzVault {
1040
1051
  if (vault.tokenizerReady) {
1041
1052
  vault.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey });
1042
1053
  }
1054
+ vault.log('wax key received — vault ready');
1043
1055
  return vault;
1044
1056
  }
1045
1057
  /**
@@ -1181,8 +1193,13 @@ class OzVault {
1181
1193
  const readyBankElements = [accountEl, routingEl];
1182
1194
  this._tokenizing = 'bank';
1183
1195
  const requestId = `req-${uuid()}`;
1196
+ this.log('createBankToken() called');
1184
1197
  return new Promise((resolve, reject) => {
1185
- const cleanup = () => { this._tokenizing = null; };
1198
+ const resetCountAtStart = this._resetCount;
1199
+ const cleanup = () => {
1200
+ if (this._resetCount === resetCountAtStart)
1201
+ this._tokenizing = null;
1202
+ };
1186
1203
  this.bankTokenizeResolvers.set(requestId, {
1187
1204
  resolve: (v) => { cleanup(); resolve(v); },
1188
1205
  reject: (e) => { cleanup(); reject(e); },
@@ -1193,6 +1210,7 @@ class OzVault {
1193
1210
  });
1194
1211
  try {
1195
1212
  const bankChannels = readyBankElements.map(() => new MessageChannel());
1213
+ const bankTokenizeStartMs = Date.now();
1196
1214
  this.sendToTokenizer({
1197
1215
  type: 'OZ_BANK_TOKENIZE',
1198
1216
  requestId,
@@ -1203,6 +1221,7 @@ class OzVault {
1203
1221
  lastName: options.lastName.trim(),
1204
1222
  fieldCount: readyBankElements.length,
1205
1223
  }, bankChannels.map(ch => ch.port1));
1224
+ this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
1206
1225
  readyBankElements.forEach((el, i) => el.beginCollect(requestId, bankChannels[i].port2));
1207
1226
  const bankTimeoutId = setTimeout(() => {
1208
1227
  if (this.bankTokenizeResolvers.has(requestId)) {
@@ -1213,8 +1232,10 @@ class OzVault {
1213
1232
  }
1214
1233
  }, 30000);
1215
1234
  const bankPendingEntry = this.bankTokenizeResolvers.get(requestId);
1216
- if (bankPendingEntry)
1235
+ if (bankPendingEntry) {
1217
1236
  bankPendingEntry.timeoutId = bankTimeoutId;
1237
+ bankPendingEntry.tokenizeStartMs = bankTokenizeStartMs;
1238
+ }
1218
1239
  }
1219
1240
  catch (err) {
1220
1241
  this.bankTokenizeResolvers.delete(requestId);
@@ -1285,8 +1306,15 @@ class OzVault {
1285
1306
  }
1286
1307
  this._tokenizing = 'card';
1287
1308
  const requestId = `req-${uuid()}`;
1309
+ this.log('createToken() called');
1288
1310
  return new Promise((resolve, reject) => {
1289
- const cleanup = () => { this._tokenizing = null; };
1311
+ // Capture the reset generation so cleanup() only zeros _tokenizing when it
1312
+ // still belongs to this invocation — not a newer one that started after a reset.
1313
+ const resetCountAtStart = this._resetCount;
1314
+ const cleanup = () => {
1315
+ if (this._resetCount === resetCountAtStart)
1316
+ this._tokenizing = null;
1317
+ };
1290
1318
  this.tokenizeResolvers.set(requestId, {
1291
1319
  resolve: (v) => { cleanup(); resolve(v); },
1292
1320
  reject: (e) => { cleanup(); reject(e); },
@@ -1299,6 +1327,7 @@ class OzVault {
1299
1327
  try {
1300
1328
  // Tell tokenizer frame to expect N field values, then tokenize
1301
1329
  const cardChannels = readyElements.map(() => new MessageChannel());
1330
+ const tokenizeStartMs = Date.now();
1302
1331
  this.sendToTokenizer({
1303
1332
  type: 'OZ_TOKENIZE',
1304
1333
  requestId,
@@ -1309,6 +1338,11 @@ class OzVault {
1309
1338
  lastName,
1310
1339
  fieldCount: readyElements.length,
1311
1340
  }, cardChannels.map(ch => ch.port1));
1341
+ this.log('OZ_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyElements.length });
1342
+ // Store start time for elapsed-ms logging on result
1343
+ const cardEntry = this.tokenizeResolvers.get(requestId);
1344
+ if (cardEntry)
1345
+ cardEntry.tokenizeStartMs = tokenizeStartMs;
1312
1346
  // Tell each ready element frame to send its raw value to the tokenizer
1313
1347
  readyElements.forEach((el, i) => el.beginCollect(requestId, cardChannels[i].port2));
1314
1348
  const cardTimeoutId = setTimeout(() => {
@@ -1330,6 +1364,63 @@ class OzVault {
1330
1364
  }
1331
1365
  });
1332
1366
  }
1367
+ /**
1368
+ * Clears all mounted element fields without tearing down the vault.
1369
+ *
1370
+ * Call this after a failed payment (e.g. card declined) to let the customer
1371
+ * re-enter their details. The vault instance, tokenizer iframe, wax key, and
1372
+ * tokenization budget counter are all preserved — no network calls are made.
1373
+ *
1374
+ * **Wax key session model:** by design, one wax key covers the full checkout
1375
+ * session. The default `max_tokenize_calls: 3` supports two declined attempts
1376
+ * and one final attempt on the same key. Do not call `vault.destroy()` and
1377
+ * recreate the vault between declines — that unnecessarily re-mints the key
1378
+ * and discards the remaining budget.
1379
+ *
1380
+ * @example
1381
+ * try {
1382
+ * const { token, cvcSession } = await vault.createToken({ billing });
1383
+ * await chargeCard(token, cvcSession);
1384
+ * } catch (err) {
1385
+ * vault.reset(); // clear fields; let customer re-enter
1386
+ * showError(err.message);
1387
+ * }
1388
+ */
1389
+ reset() {
1390
+ if (this._destroyed)
1391
+ return;
1392
+ const cancelling = Boolean(this._tokenizing);
1393
+ this.log('reset() called', { tokenizing: this._tokenizing, cancelling });
1394
+ if (this._tokenizing) {
1395
+ this._tokenizing = null;
1396
+ this._resetCount++;
1397
+ this.tokenizeResolvers.forEach(({ reject, timeoutId }, requestId) => {
1398
+ if (timeoutId != null)
1399
+ clearTimeout(timeoutId);
1400
+ this.sendToTokenizer({ type: 'OZ_TOKENIZE_CANCEL', requestId });
1401
+ reject(new OzError('Vault was reset while tokenization was in progress.'));
1402
+ });
1403
+ this.tokenizeResolvers.clear();
1404
+ this.bankTokenizeResolvers.forEach(({ reject, timeoutId }, requestId) => {
1405
+ if (timeoutId != null)
1406
+ clearTimeout(timeoutId);
1407
+ this.sendToTokenizer({ type: 'OZ_TOKENIZE_CANCEL', requestId });
1408
+ reject(new OzError('Vault was reset while tokenization was in progress.'));
1409
+ });
1410
+ this.bankTokenizeResolvers.clear();
1411
+ }
1412
+ // Clear field values in all mounted element iframes
1413
+ this.elementsByType.forEach(el => el.clear());
1414
+ this.bankElementsByType.forEach(el => el.clear());
1415
+ // Reset per-element completion state so auto-advance starts fresh on re-entry
1416
+ for (const frameId of this.completionState.keys()) {
1417
+ this.completionState.set(frameId, false);
1418
+ }
1419
+ // NOTE: _tokenizeSuccessCount is intentionally NOT reset.
1420
+ // It reflects real server-side wax key budget consumption. Zeroing it
1421
+ // would desync the proactive refresh logic from the vault's state and
1422
+ // risk triggering a mid-session re-mint on what should be a clean retry.
1423
+ }
1333
1424
  /**
1334
1425
  * Tears down the vault: removes all element iframes, the tokenizer iframe,
1335
1426
  * and the global message listener. Call this when the checkout component
@@ -1340,6 +1431,7 @@ class OzVault {
1340
1431
  if (this._destroyed)
1341
1432
  return;
1342
1433
  this._destroyed = true;
1434
+ this.log('destroy() called');
1343
1435
  window.removeEventListener('message', this.boundHandleMessage);
1344
1436
  document.removeEventListener('visibilitychange', this.boundHandleVisibility);
1345
1437
  if (this._pendingMount) {
@@ -1394,13 +1486,17 @@ class OzVault {
1394
1486
  const REFRESH_THRESHOLD_MS = 20 * 60 * 1000; // 20 minutes
1395
1487
  if (document.hidden) {
1396
1488
  this._hiddenAt = Date.now();
1489
+ this.log('tab hidden');
1397
1490
  }
1398
1491
  else {
1399
- if (this._hiddenAt !== null &&
1400
- Date.now() - this._hiddenAt >= REFRESH_THRESHOLD_MS &&
1401
- this._storedFetchWaxKey &&
1492
+ const hiddenMs = this._hiddenAt !== null ? Date.now() - this._hiddenAt : 0;
1493
+ const willRefresh = (this._hiddenAt !== null &&
1494
+ hiddenMs >= REFRESH_THRESHOLD_MS &&
1495
+ Boolean(this._storedFetchWaxKey) &&
1402
1496
  !this._tokenizing &&
1403
- !this._waxRefreshing) {
1497
+ !this._waxRefreshing);
1498
+ this.log('tab visible', { hiddenMs, willRefresh });
1499
+ if (willRefresh) {
1404
1500
  this.refreshWaxKey().catch((err) => {
1405
1501
  // Proactive refresh failure is non-fatal — the reactive path on the
1406
1502
  // next createToken() call will handle it, including the auth retry.
@@ -1410,6 +1506,56 @@ class OzVault {
1410
1506
  this._hiddenAt = null;
1411
1507
  }
1412
1508
  }
1509
+ // ─── Debug ───────────────────────────────────────────────────────────────
1510
+ /**
1511
+ * Emits a `[OzVault]`-prefixed entry to `console.log`. No-op when `debug` is
1512
+ * not set. Never called with sensitive values — callers use presence flags only.
1513
+ */
1514
+ log(message, data) {
1515
+ if (!this._debug)
1516
+ return;
1517
+ if (data !== undefined) {
1518
+ console.log(`[OzVault] ${message}`, data);
1519
+ }
1520
+ else {
1521
+ console.log(`[OzVault] ${message}`);
1522
+ }
1523
+ }
1524
+ /**
1525
+ * Returns a plain-object snapshot of the vault's current internal state.
1526
+ * Safe to attach to bug reports — no wax keys, tokens, or billing data included.
1527
+ *
1528
+ * Available on all vault instances regardless of whether `debug` was enabled.
1529
+ *
1530
+ * @example
1531
+ * console.log(vault.debugState());
1532
+ * // {
1533
+ * // vaultId: 'vault-abc123',
1534
+ * // isReady: true,
1535
+ * // tokenizing: null,
1536
+ * // destroyed: false,
1537
+ * // waxKeyPresent: true,
1538
+ * // elements: ['cardNumber', 'expirationDate', 'cvv'],
1539
+ * // ...
1540
+ * // }
1541
+ */
1542
+ debugState() {
1543
+ return {
1544
+ vaultId: this.vaultId,
1545
+ isReady: this.tokenizerReady,
1546
+ tokenizing: this._tokenizing,
1547
+ destroyed: this._destroyed,
1548
+ waxKeyPresent: Boolean(this.waxKey),
1549
+ tokenizeSuccessCount: this._tokenizeSuccessCount,
1550
+ maxTokenizeCalls: this._maxTokenizeCalls,
1551
+ resetCount: this._resetCount,
1552
+ elements: [...this.elementsByType.keys()],
1553
+ bankElements: [...this.bankElementsByType.keys()],
1554
+ completionState: Object.fromEntries([...this.completionState.entries()].map(([id, v]) => [id.slice(0, 8), v])),
1555
+ pendingTokenizations: this.tokenizeResolvers.size,
1556
+ pendingBankTokenizations: this.bankTokenizeResolvers.size,
1557
+ };
1558
+ }
1413
1559
  mountTokenizerFrame() {
1414
1560
  const mount = () => {
1415
1561
  this._pendingMount = null;
@@ -1421,6 +1567,7 @@ class OzVault {
1421
1567
  iframe.src = `${this.frameBaseUrl}/frame/tokenizer-frame.html#vaultId=${encodeURIComponent(this.vaultId)}${parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : ''}`;
1422
1568
  document.body.appendChild(iframe);
1423
1569
  this.tokenizerFrame = iframe;
1570
+ this.log('mounting tokenizer iframe');
1424
1571
  };
1425
1572
  if (document.readyState === 'loading') {
1426
1573
  this._pendingMount = mount;
@@ -1461,6 +1608,7 @@ class OzVault {
1461
1608
  `SDK expects v${PROTOCOL_VERSION}, frame reported v${typeof msg.__ozVersion === 'number' ? msg.__ozVersion : '(none)'}. ` +
1462
1609
  'Deploy the matching frame assets to elements.ozura.com and purge the Azure CDN cache.');
1463
1610
  }
1611
+ this.log('element iframe ready', { type: el.type, frameIdPrefix: frameId.slice(0, 8) });
1464
1612
  }
1465
1613
  // Intercept OZ_CHANGE before forwarding — handle auto-advance and CVV sync
1466
1614
  if (msg.type === 'OZ_CHANGE') {
@@ -1484,6 +1632,7 @@ class OzVault {
1484
1632
  // Require valid too — avoids advancing at 13 digits for unknown-brand cards
1485
1633
  // where isComplete() fires before the user has finished typing.
1486
1634
  const justCompleted = complete && valid && !wasComplete;
1635
+ this.log('field changed', { type: el.type, complete, valid, justCompleted });
1487
1636
  // Sync CVV length when card brand changes
1488
1637
  if (el.type === 'cardNumber') {
1489
1638
  const brand = msg.cardBrand;
@@ -1495,15 +1644,17 @@ class OzVault {
1495
1644
  // Auto-advance focus on completion
1496
1645
  if (justCompleted) {
1497
1646
  if (el.type === 'cardNumber') {
1647
+ this.log('auto-advance', { from: 'cardNumber', to: 'expirationDate' });
1498
1648
  (_b = this.elementsByType.get('expirationDate')) === null || _b === void 0 ? void 0 : _b.focus();
1499
1649
  }
1500
1650
  else if (el.type === 'expirationDate') {
1651
+ this.log('auto-advance', { from: 'expirationDate', to: 'cvv' });
1501
1652
  (_c = this.elementsByType.get('cvv')) === null || _c === void 0 ? void 0 : _c.focus();
1502
1653
  }
1503
1654
  }
1504
1655
  }
1505
1656
  handleTokenizerMessage(msg) {
1506
- var _a, _b, _c;
1657
+ var _a, _b, _c, _d;
1507
1658
  switch (msg.type) {
1508
1659
  case 'OZ_FRAME_READY':
1509
1660
  if (msg.__ozVersion !== PROTOCOL_VERSION) {
@@ -1523,6 +1674,7 @@ class OzVault {
1523
1674
  // sent again from create() once the key is available.
1524
1675
  this.sendToTokenizer(Object.assign({ type: 'OZ_INIT', frameId: '__tokenizer__' }, (this.waxKey ? { waxKey: this.waxKey } : {})));
1525
1676
  (_c = this._onReady) === null || _c === void 0 ? void 0 : _c.call(this);
1677
+ this.log('tokenizer iframe ready', { protocolVersion: (_d = msg.__ozVersion) !== null && _d !== void 0 ? _d : null });
1526
1678
  break;
1527
1679
  case 'OZ_TOKEN_RESULT': {
1528
1680
  if (typeof msg.requestId !== 'string' || !msg.requestId) {
@@ -1547,11 +1699,18 @@ class OzVault {
1547
1699
  }
1548
1700
  pending.resolve(Object.assign(Object.assign({ token,
1549
1701
  cvcSession }, (card ? { card } : {})), (pending.billing ? { billing: pending.billing } : {})));
1702
+ this.log('token received', {
1703
+ elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
1704
+ tokenPresent: true,
1705
+ cvcSessionPresent: true,
1706
+ cardMetadataPresent: Boolean(card),
1707
+ });
1550
1708
  // Increment the per-key success counter and proactively refresh once
1551
1709
  // the budget is exhausted so the next createToken() call uses a fresh
1552
1710
  // key without waiting for a vault rejection.
1553
1711
  this._tokenizeSuccessCount++;
1554
1712
  if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
1713
+ this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1555
1714
  this.refreshWaxKey().catch((err) => {
1556
1715
  console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
1557
1716
  });
@@ -1571,14 +1730,25 @@ class OzVault {
1571
1730
  this.tokenizeResolvers.delete(msg.requestId);
1572
1731
  if (pending.timeoutId != null)
1573
1732
  clearTimeout(pending.timeoutId);
1733
+ const willRefresh = this.isRefreshableAuthError(errorCode, raw) && !pending.retried && Boolean(this._storedFetchWaxKey);
1734
+ this.log('token error', { errorCode, willRefresh });
1574
1735
  // Auto-refresh: if the wax key expired or was consumed and we haven't
1575
1736
  // already retried for this request, transparently re-mint and retry.
1576
- if (this.isRefreshableAuthError(errorCode, raw) && !pending.retried && this._storedFetchWaxKey) {
1737
+ if (willRefresh) {
1738
+ const resetCountAtRetry = this._resetCount;
1577
1739
  this.refreshWaxKey().then(() => {
1578
1740
  if (this._destroyed) {
1579
1741
  pending.reject(new OzError('Vault destroyed during wax key refresh.'));
1580
1742
  return;
1581
1743
  }
1744
+ if (this._resetCount !== resetCountAtRetry) {
1745
+ // reset() was called while the wax key was refreshing — the fields
1746
+ // have been cleared and _tokenizing was zeroed. Reject the original
1747
+ // promise so it doesn't stay pending, and bail out without starting
1748
+ // a new retry (which would tokenize against empty fields).
1749
+ pending.reject(new OzError('Vault was reset while tokenization was in progress.'));
1750
+ return;
1751
+ }
1582
1752
  const newRequestId = `req-${uuid()}`;
1583
1753
  // _tokenizing is still 'card' (cleanup() hasn't been called yet)
1584
1754
  this.tokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, pending), { retried: true }));
@@ -1625,11 +1795,16 @@ class OzVault {
1625
1795
  if (bankPending.timeoutId != null)
1626
1796
  clearTimeout(bankPending.timeoutId);
1627
1797
  if (this.isRefreshableAuthError(errorCode, raw) && !bankPending.retried && this._storedFetchWaxKey) {
1798
+ const resetCountAtRetry = this._resetCount;
1628
1799
  this.refreshWaxKey().then(() => {
1629
1800
  if (this._destroyed) {
1630
1801
  bankPending.reject(new OzError('Vault destroyed during wax key refresh.'));
1631
1802
  return;
1632
1803
  }
1804
+ if (this._resetCount !== resetCountAtRetry) {
1805
+ bankPending.reject(new OzError('Vault was reset while tokenization was in progress.'));
1806
+ return;
1807
+ }
1633
1808
  const newRequestId = `req-${uuid()}`;
1634
1809
  this.bankTokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, bankPending), { retried: true }));
1635
1810
  try {
@@ -1687,9 +1862,15 @@ class OzVault {
1687
1862
  }
1688
1863
  const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
1689
1864
  pending.resolve(Object.assign({ token }, (bank ? { bank } : {})));
1865
+ this.log('bank token received', {
1866
+ elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
1867
+ tokenPresent: true,
1868
+ bankMetadataPresent: Boolean(bank),
1869
+ });
1690
1870
  // Same proactive refresh logic as card tokenization.
1691
1871
  this._tokenizeSuccessCount++;
1692
1872
  if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
1873
+ this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
1693
1874
  this.refreshWaxKey().catch((err) => {
1694
1875
  console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
1695
1876
  });
@@ -1745,6 +1926,7 @@ class OzVault {
1745
1926
  }
1746
1927
  const newSessionId = uuid();
1747
1928
  (_a = this._onWaxRefresh) === null || _a === void 0 ? void 0 : _a.call(this);
1929
+ this.log('wax key refresh started');
1748
1930
  this._waxRefreshing = this._storedFetchWaxKey(newSessionId)
1749
1931
  .then(newWaxKey => {
1750
1932
  if (typeof newWaxKey !== 'string' || !newWaxKey.trim()) {
@@ -1758,6 +1940,11 @@ class OzVault {
1758
1940
  if (!this._destroyed && this.tokenizerReady) {
1759
1941
  this.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey: newWaxKey });
1760
1942
  }
1943
+ this.log('wax key refresh succeeded');
1944
+ })
1945
+ .catch((err) => {
1946
+ this.log('wax key refresh failed', { error: err instanceof Error ? err.message : String(err) });
1947
+ throw err;
1761
1948
  })
1762
1949
  .finally(() => {
1763
1950
  this._waxRefreshing = null;