@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.umd.js
CHANGED
|
@@ -413,7 +413,7 @@
|
|
|
413
413
|
* (useful when integrating with React refs).
|
|
414
414
|
*/
|
|
415
415
|
mount(target) {
|
|
416
|
-
var _a;
|
|
416
|
+
var _a, _b;
|
|
417
417
|
if (this._destroyed)
|
|
418
418
|
throw new OzError('Cannot mount a destroyed element.');
|
|
419
419
|
if (this.iframe)
|
|
@@ -430,7 +430,13 @@
|
|
|
430
430
|
iframe.setAttribute('scrolling', 'no');
|
|
431
431
|
iframe.setAttribute('allowtransparency', 'true');
|
|
432
432
|
iframe.style.cssText = 'border:none;width:100%;height:46px;display:block;overflow:hidden;';
|
|
433
|
-
iframe.title = `Secure ${
|
|
433
|
+
iframe.title = `Secure ${(_a = {
|
|
434
|
+
cardNumber: 'card number',
|
|
435
|
+
expirationDate: 'expiration date',
|
|
436
|
+
cvv: 'CVV',
|
|
437
|
+
accountNumber: 'account number',
|
|
438
|
+
routingNumber: 'routing number',
|
|
439
|
+
}[this.elementType]) !== null && _a !== void 0 ? _a : this.elementType} input`;
|
|
434
440
|
// Note: the `sandbox` attribute is intentionally NOT set. Field values are
|
|
435
441
|
// delivered to the tokenizer iframe via a MessageChannel port (transferred
|
|
436
442
|
// in OZ_BEGIN_COLLECT), so no window.parent named-frame lookup is needed.
|
|
@@ -444,7 +450,7 @@
|
|
|
444
450
|
container.appendChild(iframe);
|
|
445
451
|
this.iframe = iframe;
|
|
446
452
|
this._frameWindow = iframe.contentWindow;
|
|
447
|
-
const timeout = (
|
|
453
|
+
const timeout = (_b = this.options.loadTimeoutMs) !== null && _b !== void 0 ? _b : 10000;
|
|
448
454
|
this._loadTimer = setTimeout(() => {
|
|
449
455
|
if (!this._ready && !this._destroyed) {
|
|
450
456
|
this.emit('loaderror', { elementType: this.elementType, error: `${this.elementType} iframe failed to load within ${timeout}ms` });
|
|
@@ -929,7 +935,7 @@
|
|
|
929
935
|
* @internal
|
|
930
936
|
*/
|
|
931
937
|
constructor(options, waxKey, tokenizationSessionId) {
|
|
932
|
-
var _a, _b, _c;
|
|
938
|
+
var _a, _b, _c, _d;
|
|
933
939
|
this.elements = new Map();
|
|
934
940
|
this.elementsByType = new Map();
|
|
935
941
|
this.bankElementsByType = new Map();
|
|
@@ -942,6 +948,9 @@
|
|
|
942
948
|
this.tokenizerReady = false;
|
|
943
949
|
this._tokenizing = null;
|
|
944
950
|
this._destroyed = false;
|
|
951
|
+
// Incremented every time reset() cancels an active tokenization so that
|
|
952
|
+
// any in-flight wax-key refresh retry can detect it was superseded.
|
|
953
|
+
this._resetCount = 0;
|
|
945
954
|
// Tracks successful tokenizations against the per-key call budget so the SDK
|
|
946
955
|
// can proactively refresh the wax key after it has been consumed rather than
|
|
947
956
|
// waiting for the next createToken() call to fail.
|
|
@@ -961,13 +970,14 @@
|
|
|
961
970
|
this.resolvedAppearance = resolveAppearance(options.appearance);
|
|
962
971
|
this.vaultId = `vault-${uuid()}`;
|
|
963
972
|
this._maxTokenizeCalls = (_b = options.maxTokenizeCalls) !== null && _b !== void 0 ? _b : 3;
|
|
973
|
+
this._debug = (_c = options.debug) !== null && _c !== void 0 ? _c : false;
|
|
964
974
|
this.boundHandleMessage = this.handleMessage.bind(this);
|
|
965
975
|
window.addEventListener('message', this.boundHandleMessage);
|
|
966
976
|
this.boundHandleVisibility = this.handleVisibilityChange.bind(this);
|
|
967
977
|
document.addEventListener('visibilitychange', this.boundHandleVisibility);
|
|
968
978
|
this.mountTokenizerFrame();
|
|
969
979
|
if (options.onLoadError) {
|
|
970
|
-
const timeout = (
|
|
980
|
+
const timeout = (_d = options.loadTimeoutMs) !== null && _d !== void 0 ? _d : 10000;
|
|
971
981
|
this.loadErrorTimeoutId = setTimeout(() => {
|
|
972
982
|
this.loadErrorTimeoutId = null;
|
|
973
983
|
if (!this._destroyed && !this.tokenizerReady) {
|
|
@@ -977,6 +987,7 @@
|
|
|
977
987
|
}
|
|
978
988
|
this._onWaxRefresh = options.onWaxRefresh;
|
|
979
989
|
this._onReady = options.onReady;
|
|
990
|
+
this.log('vault created', { vaultId: this.vaultId, frameBaseUrl: this.frameBaseUrl, maxTokenizeCalls: this._maxTokenizeCalls });
|
|
980
991
|
}
|
|
981
992
|
/**
|
|
982
993
|
* Creates and returns a ready `OzVault` instance.
|
|
@@ -1046,6 +1057,7 @@
|
|
|
1046
1057
|
if (vault.tokenizerReady) {
|
|
1047
1058
|
vault.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey });
|
|
1048
1059
|
}
|
|
1060
|
+
vault.log('wax key received — vault ready');
|
|
1049
1061
|
return vault;
|
|
1050
1062
|
}
|
|
1051
1063
|
/**
|
|
@@ -1187,8 +1199,13 @@
|
|
|
1187
1199
|
const readyBankElements = [accountEl, routingEl];
|
|
1188
1200
|
this._tokenizing = 'bank';
|
|
1189
1201
|
const requestId = `req-${uuid()}`;
|
|
1202
|
+
this.log('createBankToken() called');
|
|
1190
1203
|
return new Promise((resolve, reject) => {
|
|
1191
|
-
const
|
|
1204
|
+
const resetCountAtStart = this._resetCount;
|
|
1205
|
+
const cleanup = () => {
|
|
1206
|
+
if (this._resetCount === resetCountAtStart)
|
|
1207
|
+
this._tokenizing = null;
|
|
1208
|
+
};
|
|
1192
1209
|
this.bankTokenizeResolvers.set(requestId, {
|
|
1193
1210
|
resolve: (v) => { cleanup(); resolve(v); },
|
|
1194
1211
|
reject: (e) => { cleanup(); reject(e); },
|
|
@@ -1199,6 +1216,7 @@
|
|
|
1199
1216
|
});
|
|
1200
1217
|
try {
|
|
1201
1218
|
const bankChannels = readyBankElements.map(() => new MessageChannel());
|
|
1219
|
+
const bankTokenizeStartMs = Date.now();
|
|
1202
1220
|
this.sendToTokenizer({
|
|
1203
1221
|
type: 'OZ_BANK_TOKENIZE',
|
|
1204
1222
|
requestId,
|
|
@@ -1209,6 +1227,7 @@
|
|
|
1209
1227
|
lastName: options.lastName.trim(),
|
|
1210
1228
|
fieldCount: readyBankElements.length,
|
|
1211
1229
|
}, bankChannels.map(ch => ch.port1));
|
|
1230
|
+
this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
|
|
1212
1231
|
readyBankElements.forEach((el, i) => el.beginCollect(requestId, bankChannels[i].port2));
|
|
1213
1232
|
const bankTimeoutId = setTimeout(() => {
|
|
1214
1233
|
if (this.bankTokenizeResolvers.has(requestId)) {
|
|
@@ -1219,8 +1238,10 @@
|
|
|
1219
1238
|
}
|
|
1220
1239
|
}, 30000);
|
|
1221
1240
|
const bankPendingEntry = this.bankTokenizeResolvers.get(requestId);
|
|
1222
|
-
if (bankPendingEntry)
|
|
1241
|
+
if (bankPendingEntry) {
|
|
1223
1242
|
bankPendingEntry.timeoutId = bankTimeoutId;
|
|
1243
|
+
bankPendingEntry.tokenizeStartMs = bankTokenizeStartMs;
|
|
1244
|
+
}
|
|
1224
1245
|
}
|
|
1225
1246
|
catch (err) {
|
|
1226
1247
|
this.bankTokenizeResolvers.delete(requestId);
|
|
@@ -1291,8 +1312,15 @@
|
|
|
1291
1312
|
}
|
|
1292
1313
|
this._tokenizing = 'card';
|
|
1293
1314
|
const requestId = `req-${uuid()}`;
|
|
1315
|
+
this.log('createToken() called');
|
|
1294
1316
|
return new Promise((resolve, reject) => {
|
|
1295
|
-
|
|
1317
|
+
// Capture the reset generation so cleanup() only zeros _tokenizing when it
|
|
1318
|
+
// still belongs to this invocation — not a newer one that started after a reset.
|
|
1319
|
+
const resetCountAtStart = this._resetCount;
|
|
1320
|
+
const cleanup = () => {
|
|
1321
|
+
if (this._resetCount === resetCountAtStart)
|
|
1322
|
+
this._tokenizing = null;
|
|
1323
|
+
};
|
|
1296
1324
|
this.tokenizeResolvers.set(requestId, {
|
|
1297
1325
|
resolve: (v) => { cleanup(); resolve(v); },
|
|
1298
1326
|
reject: (e) => { cleanup(); reject(e); },
|
|
@@ -1305,6 +1333,7 @@
|
|
|
1305
1333
|
try {
|
|
1306
1334
|
// Tell tokenizer frame to expect N field values, then tokenize
|
|
1307
1335
|
const cardChannels = readyElements.map(() => new MessageChannel());
|
|
1336
|
+
const tokenizeStartMs = Date.now();
|
|
1308
1337
|
this.sendToTokenizer({
|
|
1309
1338
|
type: 'OZ_TOKENIZE',
|
|
1310
1339
|
requestId,
|
|
@@ -1315,6 +1344,11 @@
|
|
|
1315
1344
|
lastName,
|
|
1316
1345
|
fieldCount: readyElements.length,
|
|
1317
1346
|
}, cardChannels.map(ch => ch.port1));
|
|
1347
|
+
this.log('OZ_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyElements.length });
|
|
1348
|
+
// Store start time for elapsed-ms logging on result
|
|
1349
|
+
const cardEntry = this.tokenizeResolvers.get(requestId);
|
|
1350
|
+
if (cardEntry)
|
|
1351
|
+
cardEntry.tokenizeStartMs = tokenizeStartMs;
|
|
1318
1352
|
// Tell each ready element frame to send its raw value to the tokenizer
|
|
1319
1353
|
readyElements.forEach((el, i) => el.beginCollect(requestId, cardChannels[i].port2));
|
|
1320
1354
|
const cardTimeoutId = setTimeout(() => {
|
|
@@ -1336,6 +1370,63 @@
|
|
|
1336
1370
|
}
|
|
1337
1371
|
});
|
|
1338
1372
|
}
|
|
1373
|
+
/**
|
|
1374
|
+
* Clears all mounted element fields without tearing down the vault.
|
|
1375
|
+
*
|
|
1376
|
+
* Call this after a failed payment (e.g. card declined) to let the customer
|
|
1377
|
+
* re-enter their details. The vault instance, tokenizer iframe, wax key, and
|
|
1378
|
+
* tokenization budget counter are all preserved — no network calls are made.
|
|
1379
|
+
*
|
|
1380
|
+
* **Wax key session model:** by design, one wax key covers the full checkout
|
|
1381
|
+
* session. The default `max_tokenize_calls: 3` supports two declined attempts
|
|
1382
|
+
* and one final attempt on the same key. Do not call `vault.destroy()` and
|
|
1383
|
+
* recreate the vault between declines — that unnecessarily re-mints the key
|
|
1384
|
+
* and discards the remaining budget.
|
|
1385
|
+
*
|
|
1386
|
+
* @example
|
|
1387
|
+
* try {
|
|
1388
|
+
* const { token, cvcSession } = await vault.createToken({ billing });
|
|
1389
|
+
* await chargeCard(token, cvcSession);
|
|
1390
|
+
* } catch (err) {
|
|
1391
|
+
* vault.reset(); // clear fields; let customer re-enter
|
|
1392
|
+
* showError(err.message);
|
|
1393
|
+
* }
|
|
1394
|
+
*/
|
|
1395
|
+
reset() {
|
|
1396
|
+
if (this._destroyed)
|
|
1397
|
+
return;
|
|
1398
|
+
const cancelling = Boolean(this._tokenizing);
|
|
1399
|
+
this.log('reset() called', { tokenizing: this._tokenizing, cancelling });
|
|
1400
|
+
if (this._tokenizing) {
|
|
1401
|
+
this._tokenizing = null;
|
|
1402
|
+
this._resetCount++;
|
|
1403
|
+
this.tokenizeResolvers.forEach(({ reject, timeoutId }, requestId) => {
|
|
1404
|
+
if (timeoutId != null)
|
|
1405
|
+
clearTimeout(timeoutId);
|
|
1406
|
+
this.sendToTokenizer({ type: 'OZ_TOKENIZE_CANCEL', requestId });
|
|
1407
|
+
reject(new OzError('Vault was reset while tokenization was in progress.'));
|
|
1408
|
+
});
|
|
1409
|
+
this.tokenizeResolvers.clear();
|
|
1410
|
+
this.bankTokenizeResolvers.forEach(({ reject, timeoutId }, requestId) => {
|
|
1411
|
+
if (timeoutId != null)
|
|
1412
|
+
clearTimeout(timeoutId);
|
|
1413
|
+
this.sendToTokenizer({ type: 'OZ_TOKENIZE_CANCEL', requestId });
|
|
1414
|
+
reject(new OzError('Vault was reset while tokenization was in progress.'));
|
|
1415
|
+
});
|
|
1416
|
+
this.bankTokenizeResolvers.clear();
|
|
1417
|
+
}
|
|
1418
|
+
// Clear field values in all mounted element iframes
|
|
1419
|
+
this.elementsByType.forEach(el => el.clear());
|
|
1420
|
+
this.bankElementsByType.forEach(el => el.clear());
|
|
1421
|
+
// Reset per-element completion state so auto-advance starts fresh on re-entry
|
|
1422
|
+
for (const frameId of this.completionState.keys()) {
|
|
1423
|
+
this.completionState.set(frameId, false);
|
|
1424
|
+
}
|
|
1425
|
+
// NOTE: _tokenizeSuccessCount is intentionally NOT reset.
|
|
1426
|
+
// It reflects real server-side wax key budget consumption. Zeroing it
|
|
1427
|
+
// would desync the proactive refresh logic from the vault's state and
|
|
1428
|
+
// risk triggering a mid-session re-mint on what should be a clean retry.
|
|
1429
|
+
}
|
|
1339
1430
|
/**
|
|
1340
1431
|
* Tears down the vault: removes all element iframes, the tokenizer iframe,
|
|
1341
1432
|
* and the global message listener. Call this when the checkout component
|
|
@@ -1346,6 +1437,7 @@
|
|
|
1346
1437
|
if (this._destroyed)
|
|
1347
1438
|
return;
|
|
1348
1439
|
this._destroyed = true;
|
|
1440
|
+
this.log('destroy() called');
|
|
1349
1441
|
window.removeEventListener('message', this.boundHandleMessage);
|
|
1350
1442
|
document.removeEventListener('visibilitychange', this.boundHandleVisibility);
|
|
1351
1443
|
if (this._pendingMount) {
|
|
@@ -1400,13 +1492,17 @@
|
|
|
1400
1492
|
const REFRESH_THRESHOLD_MS = 20 * 60 * 1000; // 20 minutes
|
|
1401
1493
|
if (document.hidden) {
|
|
1402
1494
|
this._hiddenAt = Date.now();
|
|
1495
|
+
this.log('tab hidden');
|
|
1403
1496
|
}
|
|
1404
1497
|
else {
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1498
|
+
const hiddenMs = this._hiddenAt !== null ? Date.now() - this._hiddenAt : 0;
|
|
1499
|
+
const willRefresh = (this._hiddenAt !== null &&
|
|
1500
|
+
hiddenMs >= REFRESH_THRESHOLD_MS &&
|
|
1501
|
+
Boolean(this._storedFetchWaxKey) &&
|
|
1408
1502
|
!this._tokenizing &&
|
|
1409
|
-
!this._waxRefreshing)
|
|
1503
|
+
!this._waxRefreshing);
|
|
1504
|
+
this.log('tab visible', { hiddenMs, willRefresh });
|
|
1505
|
+
if (willRefresh) {
|
|
1410
1506
|
this.refreshWaxKey().catch((err) => {
|
|
1411
1507
|
// Proactive refresh failure is non-fatal — the reactive path on the
|
|
1412
1508
|
// next createToken() call will handle it, including the auth retry.
|
|
@@ -1416,6 +1512,56 @@
|
|
|
1416
1512
|
this._hiddenAt = null;
|
|
1417
1513
|
}
|
|
1418
1514
|
}
|
|
1515
|
+
// ─── Debug ───────────────────────────────────────────────────────────────
|
|
1516
|
+
/**
|
|
1517
|
+
* Emits a `[OzVault]`-prefixed entry to `console.log`. No-op when `debug` is
|
|
1518
|
+
* not set. Never called with sensitive values — callers use presence flags only.
|
|
1519
|
+
*/
|
|
1520
|
+
log(message, data) {
|
|
1521
|
+
if (!this._debug)
|
|
1522
|
+
return;
|
|
1523
|
+
if (data !== undefined) {
|
|
1524
|
+
console.log(`[OzVault] ${message}`, data);
|
|
1525
|
+
}
|
|
1526
|
+
else {
|
|
1527
|
+
console.log(`[OzVault] ${message}`);
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
/**
|
|
1531
|
+
* Returns a plain-object snapshot of the vault's current internal state.
|
|
1532
|
+
* Safe to attach to bug reports — no wax keys, tokens, or billing data included.
|
|
1533
|
+
*
|
|
1534
|
+
* Available on all vault instances regardless of whether `debug` was enabled.
|
|
1535
|
+
*
|
|
1536
|
+
* @example
|
|
1537
|
+
* console.log(vault.debugState());
|
|
1538
|
+
* // {
|
|
1539
|
+
* // vaultId: 'vault-abc123',
|
|
1540
|
+
* // isReady: true,
|
|
1541
|
+
* // tokenizing: null,
|
|
1542
|
+
* // destroyed: false,
|
|
1543
|
+
* // waxKeyPresent: true,
|
|
1544
|
+
* // elements: ['cardNumber', 'expirationDate', 'cvv'],
|
|
1545
|
+
* // ...
|
|
1546
|
+
* // }
|
|
1547
|
+
*/
|
|
1548
|
+
debugState() {
|
|
1549
|
+
return {
|
|
1550
|
+
vaultId: this.vaultId,
|
|
1551
|
+
isReady: this.tokenizerReady,
|
|
1552
|
+
tokenizing: this._tokenizing,
|
|
1553
|
+
destroyed: this._destroyed,
|
|
1554
|
+
waxKeyPresent: Boolean(this.waxKey),
|
|
1555
|
+
tokenizeSuccessCount: this._tokenizeSuccessCount,
|
|
1556
|
+
maxTokenizeCalls: this._maxTokenizeCalls,
|
|
1557
|
+
resetCount: this._resetCount,
|
|
1558
|
+
elements: [...this.elementsByType.keys()],
|
|
1559
|
+
bankElements: [...this.bankElementsByType.keys()],
|
|
1560
|
+
completionState: Object.fromEntries([...this.completionState.entries()].map(([id, v]) => [id.slice(0, 8), v])),
|
|
1561
|
+
pendingTokenizations: this.tokenizeResolvers.size,
|
|
1562
|
+
pendingBankTokenizations: this.bankTokenizeResolvers.size,
|
|
1563
|
+
};
|
|
1564
|
+
}
|
|
1419
1565
|
mountTokenizerFrame() {
|
|
1420
1566
|
const mount = () => {
|
|
1421
1567
|
this._pendingMount = null;
|
|
@@ -1427,6 +1573,7 @@
|
|
|
1427
1573
|
iframe.src = `${this.frameBaseUrl}/frame/tokenizer-frame.html#vaultId=${encodeURIComponent(this.vaultId)}${parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : ''}`;
|
|
1428
1574
|
document.body.appendChild(iframe);
|
|
1429
1575
|
this.tokenizerFrame = iframe;
|
|
1576
|
+
this.log('mounting tokenizer iframe');
|
|
1430
1577
|
};
|
|
1431
1578
|
if (document.readyState === 'loading') {
|
|
1432
1579
|
this._pendingMount = mount;
|
|
@@ -1467,6 +1614,7 @@
|
|
|
1467
1614
|
`SDK expects v${PROTOCOL_VERSION}, frame reported v${typeof msg.__ozVersion === 'number' ? msg.__ozVersion : '(none)'}. ` +
|
|
1468
1615
|
'Deploy the matching frame assets to elements.ozura.com and purge the Azure CDN cache.');
|
|
1469
1616
|
}
|
|
1617
|
+
this.log('element iframe ready', { type: el.type, frameIdPrefix: frameId.slice(0, 8) });
|
|
1470
1618
|
}
|
|
1471
1619
|
// Intercept OZ_CHANGE before forwarding — handle auto-advance and CVV sync
|
|
1472
1620
|
if (msg.type === 'OZ_CHANGE') {
|
|
@@ -1490,6 +1638,7 @@
|
|
|
1490
1638
|
// Require valid too — avoids advancing at 13 digits for unknown-brand cards
|
|
1491
1639
|
// where isComplete() fires before the user has finished typing.
|
|
1492
1640
|
const justCompleted = complete && valid && !wasComplete;
|
|
1641
|
+
this.log('field changed', { type: el.type, complete, valid, justCompleted });
|
|
1493
1642
|
// Sync CVV length when card brand changes
|
|
1494
1643
|
if (el.type === 'cardNumber') {
|
|
1495
1644
|
const brand = msg.cardBrand;
|
|
@@ -1501,15 +1650,17 @@
|
|
|
1501
1650
|
// Auto-advance focus on completion
|
|
1502
1651
|
if (justCompleted) {
|
|
1503
1652
|
if (el.type === 'cardNumber') {
|
|
1653
|
+
this.log('auto-advance', { from: 'cardNumber', to: 'expirationDate' });
|
|
1504
1654
|
(_b = this.elementsByType.get('expirationDate')) === null || _b === void 0 ? void 0 : _b.focus();
|
|
1505
1655
|
}
|
|
1506
1656
|
else if (el.type === 'expirationDate') {
|
|
1657
|
+
this.log('auto-advance', { from: 'expirationDate', to: 'cvv' });
|
|
1507
1658
|
(_c = this.elementsByType.get('cvv')) === null || _c === void 0 ? void 0 : _c.focus();
|
|
1508
1659
|
}
|
|
1509
1660
|
}
|
|
1510
1661
|
}
|
|
1511
1662
|
handleTokenizerMessage(msg) {
|
|
1512
|
-
var _a, _b, _c;
|
|
1663
|
+
var _a, _b, _c, _d;
|
|
1513
1664
|
switch (msg.type) {
|
|
1514
1665
|
case 'OZ_FRAME_READY':
|
|
1515
1666
|
if (msg.__ozVersion !== PROTOCOL_VERSION) {
|
|
@@ -1529,6 +1680,7 @@
|
|
|
1529
1680
|
// sent again from create() once the key is available.
|
|
1530
1681
|
this.sendToTokenizer(Object.assign({ type: 'OZ_INIT', frameId: '__tokenizer__' }, (this.waxKey ? { waxKey: this.waxKey } : {})));
|
|
1531
1682
|
(_c = this._onReady) === null || _c === void 0 ? void 0 : _c.call(this);
|
|
1683
|
+
this.log('tokenizer iframe ready', { protocolVersion: (_d = msg.__ozVersion) !== null && _d !== void 0 ? _d : null });
|
|
1532
1684
|
break;
|
|
1533
1685
|
case 'OZ_TOKEN_RESULT': {
|
|
1534
1686
|
if (typeof msg.requestId !== 'string' || !msg.requestId) {
|
|
@@ -1553,11 +1705,18 @@
|
|
|
1553
1705
|
}
|
|
1554
1706
|
pending.resolve(Object.assign(Object.assign({ token,
|
|
1555
1707
|
cvcSession }, (card ? { card } : {})), (pending.billing ? { billing: pending.billing } : {})));
|
|
1708
|
+
this.log('token received', {
|
|
1709
|
+
elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
|
|
1710
|
+
tokenPresent: true,
|
|
1711
|
+
cvcSessionPresent: true,
|
|
1712
|
+
cardMetadataPresent: Boolean(card),
|
|
1713
|
+
});
|
|
1556
1714
|
// Increment the per-key success counter and proactively refresh once
|
|
1557
1715
|
// the budget is exhausted so the next createToken() call uses a fresh
|
|
1558
1716
|
// key without waiting for a vault rejection.
|
|
1559
1717
|
this._tokenizeSuccessCount++;
|
|
1560
1718
|
if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
|
|
1719
|
+
this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
|
|
1561
1720
|
this.refreshWaxKey().catch((err) => {
|
|
1562
1721
|
console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
|
|
1563
1722
|
});
|
|
@@ -1577,14 +1736,25 @@
|
|
|
1577
1736
|
this.tokenizeResolvers.delete(msg.requestId);
|
|
1578
1737
|
if (pending.timeoutId != null)
|
|
1579
1738
|
clearTimeout(pending.timeoutId);
|
|
1739
|
+
const willRefresh = this.isRefreshableAuthError(errorCode, raw) && !pending.retried && Boolean(this._storedFetchWaxKey);
|
|
1740
|
+
this.log('token error', { errorCode, willRefresh });
|
|
1580
1741
|
// Auto-refresh: if the wax key expired or was consumed and we haven't
|
|
1581
1742
|
// already retried for this request, transparently re-mint and retry.
|
|
1582
|
-
if (
|
|
1743
|
+
if (willRefresh) {
|
|
1744
|
+
const resetCountAtRetry = this._resetCount;
|
|
1583
1745
|
this.refreshWaxKey().then(() => {
|
|
1584
1746
|
if (this._destroyed) {
|
|
1585
1747
|
pending.reject(new OzError('Vault destroyed during wax key refresh.'));
|
|
1586
1748
|
return;
|
|
1587
1749
|
}
|
|
1750
|
+
if (this._resetCount !== resetCountAtRetry) {
|
|
1751
|
+
// reset() was called while the wax key was refreshing — the fields
|
|
1752
|
+
// have been cleared and _tokenizing was zeroed. Reject the original
|
|
1753
|
+
// promise so it doesn't stay pending, and bail out without starting
|
|
1754
|
+
// a new retry (which would tokenize against empty fields).
|
|
1755
|
+
pending.reject(new OzError('Vault was reset while tokenization was in progress.'));
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1588
1758
|
const newRequestId = `req-${uuid()}`;
|
|
1589
1759
|
// _tokenizing is still 'card' (cleanup() hasn't been called yet)
|
|
1590
1760
|
this.tokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, pending), { retried: true }));
|
|
@@ -1631,11 +1801,16 @@
|
|
|
1631
1801
|
if (bankPending.timeoutId != null)
|
|
1632
1802
|
clearTimeout(bankPending.timeoutId);
|
|
1633
1803
|
if (this.isRefreshableAuthError(errorCode, raw) && !bankPending.retried && this._storedFetchWaxKey) {
|
|
1804
|
+
const resetCountAtRetry = this._resetCount;
|
|
1634
1805
|
this.refreshWaxKey().then(() => {
|
|
1635
1806
|
if (this._destroyed) {
|
|
1636
1807
|
bankPending.reject(new OzError('Vault destroyed during wax key refresh.'));
|
|
1637
1808
|
return;
|
|
1638
1809
|
}
|
|
1810
|
+
if (this._resetCount !== resetCountAtRetry) {
|
|
1811
|
+
bankPending.reject(new OzError('Vault was reset while tokenization was in progress.'));
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1639
1814
|
const newRequestId = `req-${uuid()}`;
|
|
1640
1815
|
this.bankTokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, bankPending), { retried: true }));
|
|
1641
1816
|
try {
|
|
@@ -1693,9 +1868,15 @@
|
|
|
1693
1868
|
}
|
|
1694
1869
|
const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
|
|
1695
1870
|
pending.resolve(Object.assign({ token }, (bank ? { bank } : {})));
|
|
1871
|
+
this.log('bank token received', {
|
|
1872
|
+
elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
|
|
1873
|
+
tokenPresent: true,
|
|
1874
|
+
bankMetadataPresent: Boolean(bank),
|
|
1875
|
+
});
|
|
1696
1876
|
// Same proactive refresh logic as card tokenization.
|
|
1697
1877
|
this._tokenizeSuccessCount++;
|
|
1698
1878
|
if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
|
|
1879
|
+
this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
|
|
1699
1880
|
this.refreshWaxKey().catch((err) => {
|
|
1700
1881
|
console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
|
|
1701
1882
|
});
|
|
@@ -1751,6 +1932,7 @@
|
|
|
1751
1932
|
}
|
|
1752
1933
|
const newSessionId = uuid();
|
|
1753
1934
|
(_a = this._onWaxRefresh) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1935
|
+
this.log('wax key refresh started');
|
|
1754
1936
|
this._waxRefreshing = this._storedFetchWaxKey(newSessionId)
|
|
1755
1937
|
.then(newWaxKey => {
|
|
1756
1938
|
if (typeof newWaxKey !== 'string' || !newWaxKey.trim()) {
|
|
@@ -1764,6 +1946,11 @@
|
|
|
1764
1946
|
if (!this._destroyed && this.tokenizerReady) {
|
|
1765
1947
|
this.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey: newWaxKey });
|
|
1766
1948
|
}
|
|
1949
|
+
this.log('wax key refresh succeeded');
|
|
1950
|
+
})
|
|
1951
|
+
.catch((err) => {
|
|
1952
|
+
this.log('wax key refresh failed', { error: err instanceof Error ? err.message : String(err) });
|
|
1953
|
+
throw err;
|
|
1767
1954
|
})
|
|
1768
1955
|
.finally(() => {
|
|
1769
1956
|
this._waxRefreshing = null;
|