@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.
- package/README.md +167 -21
- package/dist/frame/element-frame.js +56 -15
- package/dist/frame/element-frame.js.map +1 -1
- package/dist/frame/tokenizer-frame.js +4 -1
- package/dist/frame/tokenizer-frame.js.map +1 -1
- package/dist/oz-elements.esm.js +201 -14
- package/dist/oz-elements.esm.js.map +1 -1
- package/dist/oz-elements.umd.js +201 -14
- package/dist/oz-elements.umd.js.map +1 -1
- package/dist/react/frame/elementFrame.d.ts +9 -0
- package/dist/react/index.cjs.js +210 -20
- package/dist/react/index.cjs.js.map +1 -1
- package/dist/react/index.esm.js +210 -20
- package/dist/react/index.esm.js.map +1 -1
- package/dist/react/react/index.d.ts +28 -1
- package/dist/react/sdk/OzVault.d.ts +49 -0
- package/dist/react/types/index.d.ts +18 -0
- package/dist/server/frame/elementFrame.d.ts +9 -0
- package/dist/server/sdk/OzVault.d.ts +49 -0
- package/dist/server/types/index.d.ts +18 -0
- package/dist/types/frame/elementFrame.d.ts +9 -0
- package/dist/types/sdk/OzVault.d.ts +49 -0
- package/dist/types/types/index.d.ts +18 -0
- package/package.json +1 -1
package/dist/oz-elements.esm.js
CHANGED
|
@@ -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 ${
|
|
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 = (
|
|
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 = (
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
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 (
|
|
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;
|