@ozura/elements 1.0.2 → 1.1.0
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 +92 -20
- package/dist/frame/element-frame.js.map +1 -1
- package/dist/frame/tokenizer-frame.js +18 -2
- package/dist/frame/tokenizer-frame.js.map +1 -1
- package/dist/oz-elements.esm.js +225 -14
- package/dist/oz-elements.esm.js.map +1 -1
- package/dist/oz-elements.umd.js +225 -14
- package/dist/oz-elements.umd.js.map +1 -1
- package/dist/react/frame/elementFrame.d.ts +70 -1
- package/dist/react/frame/protocol.d.ts +12 -0
- package/dist/react/index.cjs.js +325 -122
- package/dist/react/index.cjs.js.map +1 -1
- package/dist/react/index.esm.js +326 -123
- 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 +20 -0
- package/dist/server/frame/elementFrame.d.ts +70 -1
- package/dist/server/frame/protocol.d.ts +12 -0
- package/dist/server/sdk/OzVault.d.ts +49 -0
- package/dist/server/types/index.d.ts +20 -0
- package/dist/types/frame/elementFrame.d.ts +70 -1
- package/dist/types/frame/protocol.d.ts +12 -0
- package/dist/types/sdk/OzVault.d.ts +49 -0
- package/dist/types/types/index.d.ts +20 -0
- package/package.json +1 -1
package/dist/react/index.cjs.js
CHANGED
|
@@ -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 ${
|
|
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 = (
|
|
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,6 +827,19 @@ 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
|
+
|
|
824
843
|
function isCardMetadata(v) {
|
|
825
844
|
return !!v && typeof v === 'object' && typeof v.last4 === 'string';
|
|
826
845
|
}
|
|
@@ -860,7 +879,7 @@ class OzVault {
|
|
|
860
879
|
* @internal
|
|
861
880
|
*/
|
|
862
881
|
constructor(options, waxKey, tokenizationSessionId) {
|
|
863
|
-
var _a, _b, _c;
|
|
882
|
+
var _a, _b, _c, _d;
|
|
864
883
|
this.elements = new Map();
|
|
865
884
|
this.elementsByType = new Map();
|
|
866
885
|
this.bankElementsByType = new Map();
|
|
@@ -873,6 +892,9 @@ class OzVault {
|
|
|
873
892
|
this.tokenizerReady = false;
|
|
874
893
|
this._tokenizing = null;
|
|
875
894
|
this._destroyed = false;
|
|
895
|
+
// Incremented every time reset() cancels an active tokenization so that
|
|
896
|
+
// any in-flight wax-key refresh retry can detect it was superseded.
|
|
897
|
+
this._resetCount = 0;
|
|
876
898
|
// Tracks successful tokenizations against the per-key call budget so the SDK
|
|
877
899
|
// can proactively refresh the wax key after it has been consumed rather than
|
|
878
900
|
// waiting for the next createToken() call to fail.
|
|
@@ -892,13 +914,14 @@ class OzVault {
|
|
|
892
914
|
this.resolvedAppearance = resolveAppearance(options.appearance);
|
|
893
915
|
this.vaultId = `vault-${uuid()}`;
|
|
894
916
|
this._maxTokenizeCalls = (_b = options.maxTokenizeCalls) !== null && _b !== void 0 ? _b : 3;
|
|
917
|
+
this._debug = (_c = options.debug) !== null && _c !== void 0 ? _c : false;
|
|
895
918
|
this.boundHandleMessage = this.handleMessage.bind(this);
|
|
896
919
|
window.addEventListener('message', this.boundHandleMessage);
|
|
897
920
|
this.boundHandleVisibility = this.handleVisibilityChange.bind(this);
|
|
898
921
|
document.addEventListener('visibilitychange', this.boundHandleVisibility);
|
|
899
922
|
this.mountTokenizerFrame();
|
|
900
923
|
if (options.onLoadError) {
|
|
901
|
-
const timeout = (
|
|
924
|
+
const timeout = (_d = options.loadTimeoutMs) !== null && _d !== void 0 ? _d : 10000;
|
|
902
925
|
this.loadErrorTimeoutId = setTimeout(() => {
|
|
903
926
|
this.loadErrorTimeoutId = null;
|
|
904
927
|
if (!this._destroyed && !this.tokenizerReady) {
|
|
@@ -908,6 +931,7 @@ class OzVault {
|
|
|
908
931
|
}
|
|
909
932
|
this._onWaxRefresh = options.onWaxRefresh;
|
|
910
933
|
this._onReady = options.onReady;
|
|
934
|
+
this.log('vault created', { vaultId: this.vaultId, frameBaseUrl: this.frameBaseUrl, maxTokenizeCalls: this._maxTokenizeCalls });
|
|
911
935
|
}
|
|
912
936
|
/**
|
|
913
937
|
* Creates and returns a ready `OzVault` instance.
|
|
@@ -977,6 +1001,7 @@ class OzVault {
|
|
|
977
1001
|
if (vault.tokenizerReady) {
|
|
978
1002
|
vault.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey });
|
|
979
1003
|
}
|
|
1004
|
+
vault.log('wax key received — vault ready');
|
|
980
1005
|
return vault;
|
|
981
1006
|
}
|
|
982
1007
|
/**
|
|
@@ -1118,8 +1143,13 @@ class OzVault {
|
|
|
1118
1143
|
const readyBankElements = [accountEl, routingEl];
|
|
1119
1144
|
this._tokenizing = 'bank';
|
|
1120
1145
|
const requestId = `req-${uuid()}`;
|
|
1146
|
+
this.log('createBankToken() called');
|
|
1121
1147
|
return new Promise((resolve, reject) => {
|
|
1122
|
-
const
|
|
1148
|
+
const resetCountAtStart = this._resetCount;
|
|
1149
|
+
const cleanup = () => {
|
|
1150
|
+
if (this._resetCount === resetCountAtStart)
|
|
1151
|
+
this._tokenizing = null;
|
|
1152
|
+
};
|
|
1123
1153
|
this.bankTokenizeResolvers.set(requestId, {
|
|
1124
1154
|
resolve: (v) => { cleanup(); resolve(v); },
|
|
1125
1155
|
reject: (e) => { cleanup(); reject(e); },
|
|
@@ -1130,6 +1160,7 @@ class OzVault {
|
|
|
1130
1160
|
});
|
|
1131
1161
|
try {
|
|
1132
1162
|
const bankChannels = readyBankElements.map(() => new MessageChannel());
|
|
1163
|
+
const bankTokenizeStartMs = Date.now();
|
|
1133
1164
|
this.sendToTokenizer({
|
|
1134
1165
|
type: 'OZ_BANK_TOKENIZE',
|
|
1135
1166
|
requestId,
|
|
@@ -1140,6 +1171,7 @@ class OzVault {
|
|
|
1140
1171
|
lastName: options.lastName.trim(),
|
|
1141
1172
|
fieldCount: readyBankElements.length,
|
|
1142
1173
|
}, bankChannels.map(ch => ch.port1));
|
|
1174
|
+
this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
|
|
1143
1175
|
readyBankElements.forEach((el, i) => el.beginCollect(requestId, bankChannels[i].port2));
|
|
1144
1176
|
const bankTimeoutId = setTimeout(() => {
|
|
1145
1177
|
if (this.bankTokenizeResolvers.has(requestId)) {
|
|
@@ -1150,8 +1182,10 @@ class OzVault {
|
|
|
1150
1182
|
}
|
|
1151
1183
|
}, 30000);
|
|
1152
1184
|
const bankPendingEntry = this.bankTokenizeResolvers.get(requestId);
|
|
1153
|
-
if (bankPendingEntry)
|
|
1185
|
+
if (bankPendingEntry) {
|
|
1154
1186
|
bankPendingEntry.timeoutId = bankTimeoutId;
|
|
1187
|
+
bankPendingEntry.tokenizeStartMs = bankTokenizeStartMs;
|
|
1188
|
+
}
|
|
1155
1189
|
}
|
|
1156
1190
|
catch (err) {
|
|
1157
1191
|
this.bankTokenizeResolvers.delete(requestId);
|
|
@@ -1222,8 +1256,15 @@ class OzVault {
|
|
|
1222
1256
|
}
|
|
1223
1257
|
this._tokenizing = 'card';
|
|
1224
1258
|
const requestId = `req-${uuid()}`;
|
|
1259
|
+
this.log('createToken() called');
|
|
1225
1260
|
return new Promise((resolve, reject) => {
|
|
1226
|
-
|
|
1261
|
+
// Capture the reset generation so cleanup() only zeros _tokenizing when it
|
|
1262
|
+
// still belongs to this invocation — not a newer one that started after a reset.
|
|
1263
|
+
const resetCountAtStart = this._resetCount;
|
|
1264
|
+
const cleanup = () => {
|
|
1265
|
+
if (this._resetCount === resetCountAtStart)
|
|
1266
|
+
this._tokenizing = null;
|
|
1267
|
+
};
|
|
1227
1268
|
this.tokenizeResolvers.set(requestId, {
|
|
1228
1269
|
resolve: (v) => { cleanup(); resolve(v); },
|
|
1229
1270
|
reject: (e) => { cleanup(); reject(e); },
|
|
@@ -1236,6 +1277,7 @@ class OzVault {
|
|
|
1236
1277
|
try {
|
|
1237
1278
|
// Tell tokenizer frame to expect N field values, then tokenize
|
|
1238
1279
|
const cardChannels = readyElements.map(() => new MessageChannel());
|
|
1280
|
+
const tokenizeStartMs = Date.now();
|
|
1239
1281
|
this.sendToTokenizer({
|
|
1240
1282
|
type: 'OZ_TOKENIZE',
|
|
1241
1283
|
requestId,
|
|
@@ -1246,6 +1288,11 @@ class OzVault {
|
|
|
1246
1288
|
lastName,
|
|
1247
1289
|
fieldCount: readyElements.length,
|
|
1248
1290
|
}, cardChannels.map(ch => ch.port1));
|
|
1291
|
+
this.log('OZ_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyElements.length });
|
|
1292
|
+
// Store start time for elapsed-ms logging on result
|
|
1293
|
+
const cardEntry = this.tokenizeResolvers.get(requestId);
|
|
1294
|
+
if (cardEntry)
|
|
1295
|
+
cardEntry.tokenizeStartMs = tokenizeStartMs;
|
|
1249
1296
|
// Tell each ready element frame to send its raw value to the tokenizer
|
|
1250
1297
|
readyElements.forEach((el, i) => el.beginCollect(requestId, cardChannels[i].port2));
|
|
1251
1298
|
const cardTimeoutId = setTimeout(() => {
|
|
@@ -1267,6 +1314,63 @@ class OzVault {
|
|
|
1267
1314
|
}
|
|
1268
1315
|
});
|
|
1269
1316
|
}
|
|
1317
|
+
/**
|
|
1318
|
+
* Clears all mounted element fields without tearing down the vault.
|
|
1319
|
+
*
|
|
1320
|
+
* Call this after a failed payment (e.g. card declined) to let the customer
|
|
1321
|
+
* re-enter their details. The vault instance, tokenizer iframe, wax key, and
|
|
1322
|
+
* tokenization budget counter are all preserved — no network calls are made.
|
|
1323
|
+
*
|
|
1324
|
+
* **Wax key session model:** by design, one wax key covers the full checkout
|
|
1325
|
+
* session. The default `max_tokenize_calls: 3` supports two declined attempts
|
|
1326
|
+
* and one final attempt on the same key. Do not call `vault.destroy()` and
|
|
1327
|
+
* recreate the vault between declines — that unnecessarily re-mints the key
|
|
1328
|
+
* and discards the remaining budget.
|
|
1329
|
+
*
|
|
1330
|
+
* @example
|
|
1331
|
+
* try {
|
|
1332
|
+
* const { token, cvcSession } = await vault.createToken({ billing });
|
|
1333
|
+
* await chargeCard(token, cvcSession);
|
|
1334
|
+
* } catch (err) {
|
|
1335
|
+
* vault.reset(); // clear fields; let customer re-enter
|
|
1336
|
+
* showError(err.message);
|
|
1337
|
+
* }
|
|
1338
|
+
*/
|
|
1339
|
+
reset() {
|
|
1340
|
+
if (this._destroyed)
|
|
1341
|
+
return;
|
|
1342
|
+
const cancelling = Boolean(this._tokenizing);
|
|
1343
|
+
this.log('reset() called', { tokenizing: this._tokenizing, cancelling });
|
|
1344
|
+
if (this._tokenizing) {
|
|
1345
|
+
this._tokenizing = null;
|
|
1346
|
+
this._resetCount++;
|
|
1347
|
+
this.tokenizeResolvers.forEach(({ reject, timeoutId }, requestId) => {
|
|
1348
|
+
if (timeoutId != null)
|
|
1349
|
+
clearTimeout(timeoutId);
|
|
1350
|
+
this.sendToTokenizer({ type: 'OZ_TOKENIZE_CANCEL', requestId });
|
|
1351
|
+
reject(new OzError('Vault was reset while tokenization was in progress.'));
|
|
1352
|
+
});
|
|
1353
|
+
this.tokenizeResolvers.clear();
|
|
1354
|
+
this.bankTokenizeResolvers.forEach(({ reject, timeoutId }, requestId) => {
|
|
1355
|
+
if (timeoutId != null)
|
|
1356
|
+
clearTimeout(timeoutId);
|
|
1357
|
+
this.sendToTokenizer({ type: 'OZ_TOKENIZE_CANCEL', requestId });
|
|
1358
|
+
reject(new OzError('Vault was reset while tokenization was in progress.'));
|
|
1359
|
+
});
|
|
1360
|
+
this.bankTokenizeResolvers.clear();
|
|
1361
|
+
}
|
|
1362
|
+
// Clear field values in all mounted element iframes
|
|
1363
|
+
this.elementsByType.forEach(el => el.clear());
|
|
1364
|
+
this.bankElementsByType.forEach(el => el.clear());
|
|
1365
|
+
// Reset per-element completion state so auto-advance starts fresh on re-entry
|
|
1366
|
+
for (const frameId of this.completionState.keys()) {
|
|
1367
|
+
this.completionState.set(frameId, false);
|
|
1368
|
+
}
|
|
1369
|
+
// NOTE: _tokenizeSuccessCount is intentionally NOT reset.
|
|
1370
|
+
// It reflects real server-side wax key budget consumption. Zeroing it
|
|
1371
|
+
// would desync the proactive refresh logic from the vault's state and
|
|
1372
|
+
// risk triggering a mid-session re-mint on what should be a clean retry.
|
|
1373
|
+
}
|
|
1270
1374
|
/**
|
|
1271
1375
|
* Tears down the vault: removes all element iframes, the tokenizer iframe,
|
|
1272
1376
|
* and the global message listener. Call this when the checkout component
|
|
@@ -1277,6 +1381,7 @@ class OzVault {
|
|
|
1277
1381
|
if (this._destroyed)
|
|
1278
1382
|
return;
|
|
1279
1383
|
this._destroyed = true;
|
|
1384
|
+
this.log('destroy() called');
|
|
1280
1385
|
window.removeEventListener('message', this.boundHandleMessage);
|
|
1281
1386
|
document.removeEventListener('visibilitychange', this.boundHandleVisibility);
|
|
1282
1387
|
if (this._pendingMount) {
|
|
@@ -1331,13 +1436,17 @@ class OzVault {
|
|
|
1331
1436
|
const REFRESH_THRESHOLD_MS = 20 * 60 * 1000; // 20 minutes
|
|
1332
1437
|
if (document.hidden) {
|
|
1333
1438
|
this._hiddenAt = Date.now();
|
|
1439
|
+
this.log('tab hidden');
|
|
1334
1440
|
}
|
|
1335
1441
|
else {
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1442
|
+
const hiddenMs = this._hiddenAt !== null ? Date.now() - this._hiddenAt : 0;
|
|
1443
|
+
const willRefresh = (this._hiddenAt !== null &&
|
|
1444
|
+
hiddenMs >= REFRESH_THRESHOLD_MS &&
|
|
1445
|
+
Boolean(this._storedFetchWaxKey) &&
|
|
1339
1446
|
!this._tokenizing &&
|
|
1340
|
-
!this._waxRefreshing)
|
|
1447
|
+
!this._waxRefreshing);
|
|
1448
|
+
this.log('tab visible', { hiddenMs, willRefresh });
|
|
1449
|
+
if (willRefresh) {
|
|
1341
1450
|
this.refreshWaxKey().catch((err) => {
|
|
1342
1451
|
// Proactive refresh failure is non-fatal — the reactive path on the
|
|
1343
1452
|
// next createToken() call will handle it, including the auth retry.
|
|
@@ -1347,6 +1456,56 @@ class OzVault {
|
|
|
1347
1456
|
this._hiddenAt = null;
|
|
1348
1457
|
}
|
|
1349
1458
|
}
|
|
1459
|
+
// ─── Debug ───────────────────────────────────────────────────────────────
|
|
1460
|
+
/**
|
|
1461
|
+
* Emits a `[OzVault]`-prefixed entry to `console.log`. No-op when `debug` is
|
|
1462
|
+
* not set. Never called with sensitive values — callers use presence flags only.
|
|
1463
|
+
*/
|
|
1464
|
+
log(message, data) {
|
|
1465
|
+
if (!this._debug)
|
|
1466
|
+
return;
|
|
1467
|
+
if (data !== undefined) {
|
|
1468
|
+
console.log(`[OzVault] ${message}`, data);
|
|
1469
|
+
}
|
|
1470
|
+
else {
|
|
1471
|
+
console.log(`[OzVault] ${message}`);
|
|
1472
|
+
}
|
|
1473
|
+
}
|
|
1474
|
+
/**
|
|
1475
|
+
* Returns a plain-object snapshot of the vault's current internal state.
|
|
1476
|
+
* Safe to attach to bug reports — no wax keys, tokens, or billing data included.
|
|
1477
|
+
*
|
|
1478
|
+
* Available on all vault instances regardless of whether `debug` was enabled.
|
|
1479
|
+
*
|
|
1480
|
+
* @example
|
|
1481
|
+
* console.log(vault.debugState());
|
|
1482
|
+
* // {
|
|
1483
|
+
* // vaultId: 'vault-abc123',
|
|
1484
|
+
* // isReady: true,
|
|
1485
|
+
* // tokenizing: null,
|
|
1486
|
+
* // destroyed: false,
|
|
1487
|
+
* // waxKeyPresent: true,
|
|
1488
|
+
* // elements: ['cardNumber', 'expirationDate', 'cvv'],
|
|
1489
|
+
* // ...
|
|
1490
|
+
* // }
|
|
1491
|
+
*/
|
|
1492
|
+
debugState() {
|
|
1493
|
+
return {
|
|
1494
|
+
vaultId: this.vaultId,
|
|
1495
|
+
isReady: this.tokenizerReady,
|
|
1496
|
+
tokenizing: this._tokenizing,
|
|
1497
|
+
destroyed: this._destroyed,
|
|
1498
|
+
waxKeyPresent: Boolean(this.waxKey),
|
|
1499
|
+
tokenizeSuccessCount: this._tokenizeSuccessCount,
|
|
1500
|
+
maxTokenizeCalls: this._maxTokenizeCalls,
|
|
1501
|
+
resetCount: this._resetCount,
|
|
1502
|
+
elements: [...this.elementsByType.keys()],
|
|
1503
|
+
bankElements: [...this.bankElementsByType.keys()],
|
|
1504
|
+
completionState: Object.fromEntries([...this.completionState.entries()].map(([id, v]) => [id.slice(0, 8), v])),
|
|
1505
|
+
pendingTokenizations: this.tokenizeResolvers.size,
|
|
1506
|
+
pendingBankTokenizations: this.bankTokenizeResolvers.size,
|
|
1507
|
+
};
|
|
1508
|
+
}
|
|
1350
1509
|
mountTokenizerFrame() {
|
|
1351
1510
|
const mount = () => {
|
|
1352
1511
|
this._pendingMount = null;
|
|
@@ -1358,6 +1517,7 @@ class OzVault {
|
|
|
1358
1517
|
iframe.src = `${this.frameBaseUrl}/frame/tokenizer-frame.html#vaultId=${encodeURIComponent(this.vaultId)}${parentOrigin ? `&parentOrigin=${encodeURIComponent(parentOrigin)}` : ''}`;
|
|
1359
1518
|
document.body.appendChild(iframe);
|
|
1360
1519
|
this.tokenizerFrame = iframe;
|
|
1520
|
+
this.log('mounting tokenizer iframe');
|
|
1361
1521
|
};
|
|
1362
1522
|
if (document.readyState === 'loading') {
|
|
1363
1523
|
this._pendingMount = mount;
|
|
@@ -1393,6 +1553,12 @@ class OzVault {
|
|
|
1393
1553
|
// the previous session and justCompleted never fires, breaking auto-advance.
|
|
1394
1554
|
if (msg.type === 'OZ_FRAME_READY') {
|
|
1395
1555
|
this.completionState.set(frameId, false);
|
|
1556
|
+
if (msg.__ozVersion !== PROTOCOL_VERSION) {
|
|
1557
|
+
console.warn(`[OzVault] Protocol version mismatch on element frame "${frameId}" — ` +
|
|
1558
|
+
`SDK expects v${PROTOCOL_VERSION}, frame reported v${typeof msg.__ozVersion === 'number' ? msg.__ozVersion : '(none)'}. ` +
|
|
1559
|
+
'Deploy the matching frame assets to elements.ozura.com and purge the Azure CDN cache.');
|
|
1560
|
+
}
|
|
1561
|
+
this.log('element iframe ready', { type: el.type, frameIdPrefix: frameId.slice(0, 8) });
|
|
1396
1562
|
}
|
|
1397
1563
|
// Intercept OZ_CHANGE before forwarding — handle auto-advance and CVV sync
|
|
1398
1564
|
if (msg.type === 'OZ_CHANGE') {
|
|
@@ -1416,6 +1582,7 @@ class OzVault {
|
|
|
1416
1582
|
// Require valid too — avoids advancing at 13 digits for unknown-brand cards
|
|
1417
1583
|
// where isComplete() fires before the user has finished typing.
|
|
1418
1584
|
const justCompleted = complete && valid && !wasComplete;
|
|
1585
|
+
this.log('field changed', { type: el.type, complete, valid, justCompleted });
|
|
1419
1586
|
// Sync CVV length when card brand changes
|
|
1420
1587
|
if (el.type === 'cardNumber') {
|
|
1421
1588
|
const brand = msg.cardBrand;
|
|
@@ -1427,17 +1594,25 @@ class OzVault {
|
|
|
1427
1594
|
// Auto-advance focus on completion
|
|
1428
1595
|
if (justCompleted) {
|
|
1429
1596
|
if (el.type === 'cardNumber') {
|
|
1597
|
+
this.log('auto-advance', { from: 'cardNumber', to: 'expirationDate' });
|
|
1430
1598
|
(_b = this.elementsByType.get('expirationDate')) === null || _b === void 0 ? void 0 : _b.focus();
|
|
1431
1599
|
}
|
|
1432
1600
|
else if (el.type === 'expirationDate') {
|
|
1601
|
+
this.log('auto-advance', { from: 'expirationDate', to: 'cvv' });
|
|
1433
1602
|
(_c = this.elementsByType.get('cvv')) === null || _c === void 0 ? void 0 : _c.focus();
|
|
1434
1603
|
}
|
|
1435
1604
|
}
|
|
1436
1605
|
}
|
|
1437
1606
|
handleTokenizerMessage(msg) {
|
|
1438
|
-
var _a, _b, _c;
|
|
1607
|
+
var _a, _b, _c, _d;
|
|
1439
1608
|
switch (msg.type) {
|
|
1440
1609
|
case 'OZ_FRAME_READY':
|
|
1610
|
+
if (msg.__ozVersion !== PROTOCOL_VERSION) {
|
|
1611
|
+
console.warn(`[OzVault] Protocol version mismatch — SDK expects v${PROTOCOL_VERSION}, ` +
|
|
1612
|
+
`tokenizer frame reported v${typeof msg.__ozVersion === 'number' ? msg.__ozVersion : '(none)'}. ` +
|
|
1613
|
+
'This usually means the deployed frame files are stale. ' +
|
|
1614
|
+
'Deploy the matching frame assets to elements.ozura.com and purge the Azure CDN cache.');
|
|
1615
|
+
}
|
|
1441
1616
|
this.tokenizerReady = true;
|
|
1442
1617
|
if (this.loadErrorTimeoutId != null) {
|
|
1443
1618
|
clearTimeout(this.loadErrorTimeoutId);
|
|
@@ -1449,6 +1624,7 @@ class OzVault {
|
|
|
1449
1624
|
// sent again from create() once the key is available.
|
|
1450
1625
|
this.sendToTokenizer(Object.assign({ type: 'OZ_INIT', frameId: '__tokenizer__' }, (this.waxKey ? { waxKey: this.waxKey } : {})));
|
|
1451
1626
|
(_c = this._onReady) === null || _c === void 0 ? void 0 : _c.call(this);
|
|
1627
|
+
this.log('tokenizer iframe ready', { protocolVersion: (_d = msg.__ozVersion) !== null && _d !== void 0 ? _d : null });
|
|
1452
1628
|
break;
|
|
1453
1629
|
case 'OZ_TOKEN_RESULT': {
|
|
1454
1630
|
if (typeof msg.requestId !== 'string' || !msg.requestId) {
|
|
@@ -1473,11 +1649,18 @@ class OzVault {
|
|
|
1473
1649
|
}
|
|
1474
1650
|
pending.resolve(Object.assign(Object.assign({ token,
|
|
1475
1651
|
cvcSession }, (card ? { card } : {})), (pending.billing ? { billing: pending.billing } : {})));
|
|
1652
|
+
this.log('token received', {
|
|
1653
|
+
elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
|
|
1654
|
+
tokenPresent: true,
|
|
1655
|
+
cvcSessionPresent: true,
|
|
1656
|
+
cardMetadataPresent: Boolean(card),
|
|
1657
|
+
});
|
|
1476
1658
|
// Increment the per-key success counter and proactively refresh once
|
|
1477
1659
|
// the budget is exhausted so the next createToken() call uses a fresh
|
|
1478
1660
|
// key without waiting for a vault rejection.
|
|
1479
1661
|
this._tokenizeSuccessCount++;
|
|
1480
1662
|
if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
|
|
1663
|
+
this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
|
|
1481
1664
|
this.refreshWaxKey().catch((err) => {
|
|
1482
1665
|
console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
|
|
1483
1666
|
});
|
|
@@ -1497,14 +1680,25 @@ class OzVault {
|
|
|
1497
1680
|
this.tokenizeResolvers.delete(msg.requestId);
|
|
1498
1681
|
if (pending.timeoutId != null)
|
|
1499
1682
|
clearTimeout(pending.timeoutId);
|
|
1683
|
+
const willRefresh = this.isRefreshableAuthError(errorCode, raw) && !pending.retried && Boolean(this._storedFetchWaxKey);
|
|
1684
|
+
this.log('token error', { errorCode, willRefresh });
|
|
1500
1685
|
// Auto-refresh: if the wax key expired or was consumed and we haven't
|
|
1501
1686
|
// already retried for this request, transparently re-mint and retry.
|
|
1502
|
-
if (
|
|
1687
|
+
if (willRefresh) {
|
|
1688
|
+
const resetCountAtRetry = this._resetCount;
|
|
1503
1689
|
this.refreshWaxKey().then(() => {
|
|
1504
1690
|
if (this._destroyed) {
|
|
1505
1691
|
pending.reject(new OzError('Vault destroyed during wax key refresh.'));
|
|
1506
1692
|
return;
|
|
1507
1693
|
}
|
|
1694
|
+
if (this._resetCount !== resetCountAtRetry) {
|
|
1695
|
+
// reset() was called while the wax key was refreshing — the fields
|
|
1696
|
+
// have been cleared and _tokenizing was zeroed. Reject the original
|
|
1697
|
+
// promise so it doesn't stay pending, and bail out without starting
|
|
1698
|
+
// a new retry (which would tokenize against empty fields).
|
|
1699
|
+
pending.reject(new OzError('Vault was reset while tokenization was in progress.'));
|
|
1700
|
+
return;
|
|
1701
|
+
}
|
|
1508
1702
|
const newRequestId = `req-${uuid()}`;
|
|
1509
1703
|
// _tokenizing is still 'card' (cleanup() hasn't been called yet)
|
|
1510
1704
|
this.tokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, pending), { retried: true }));
|
|
@@ -1551,11 +1745,16 @@ class OzVault {
|
|
|
1551
1745
|
if (bankPending.timeoutId != null)
|
|
1552
1746
|
clearTimeout(bankPending.timeoutId);
|
|
1553
1747
|
if (this.isRefreshableAuthError(errorCode, raw) && !bankPending.retried && this._storedFetchWaxKey) {
|
|
1748
|
+
const resetCountAtRetry = this._resetCount;
|
|
1554
1749
|
this.refreshWaxKey().then(() => {
|
|
1555
1750
|
if (this._destroyed) {
|
|
1556
1751
|
bankPending.reject(new OzError('Vault destroyed during wax key refresh.'));
|
|
1557
1752
|
return;
|
|
1558
1753
|
}
|
|
1754
|
+
if (this._resetCount !== resetCountAtRetry) {
|
|
1755
|
+
bankPending.reject(new OzError('Vault was reset while tokenization was in progress.'));
|
|
1756
|
+
return;
|
|
1757
|
+
}
|
|
1559
1758
|
const newRequestId = `req-${uuid()}`;
|
|
1560
1759
|
this.bankTokenizeResolvers.set(newRequestId, Object.assign(Object.assign({}, bankPending), { retried: true }));
|
|
1561
1760
|
try {
|
|
@@ -1613,9 +1812,15 @@ class OzVault {
|
|
|
1613
1812
|
}
|
|
1614
1813
|
const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
|
|
1615
1814
|
pending.resolve(Object.assign({ token }, (bank ? { bank } : {})));
|
|
1815
|
+
this.log('bank token received', {
|
|
1816
|
+
elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
|
|
1817
|
+
tokenPresent: true,
|
|
1818
|
+
bankMetadataPresent: Boolean(bank),
|
|
1819
|
+
});
|
|
1616
1820
|
// Same proactive refresh logic as card tokenization.
|
|
1617
1821
|
this._tokenizeSuccessCount++;
|
|
1618
1822
|
if (this._tokenizeSuccessCount >= this._maxTokenizeCalls) {
|
|
1823
|
+
this.log('proactive wax key refresh triggered', { tokenizeSuccessCount: this._tokenizeSuccessCount, maxTokenizeCalls: this._maxTokenizeCalls });
|
|
1619
1824
|
this.refreshWaxKey().catch((err) => {
|
|
1620
1825
|
console.warn('[OzVault] Post-budget wax key refresh failed:', err instanceof Error ? err.message : err);
|
|
1621
1826
|
});
|
|
@@ -1671,6 +1876,7 @@ class OzVault {
|
|
|
1671
1876
|
}
|
|
1672
1877
|
const newSessionId = uuid();
|
|
1673
1878
|
(_a = this._onWaxRefresh) === null || _a === void 0 ? void 0 : _a.call(this);
|
|
1879
|
+
this.log('wax key refresh started');
|
|
1674
1880
|
this._waxRefreshing = this._storedFetchWaxKey(newSessionId)
|
|
1675
1881
|
.then(newWaxKey => {
|
|
1676
1882
|
if (typeof newWaxKey !== 'string' || !newWaxKey.trim()) {
|
|
@@ -1684,6 +1890,11 @@ class OzVault {
|
|
|
1684
1890
|
if (!this._destroyed && this.tokenizerReady) {
|
|
1685
1891
|
this.sendToTokenizer({ type: 'OZ_INIT', frameId: '__tokenizer__', waxKey: newWaxKey });
|
|
1686
1892
|
}
|
|
1893
|
+
this.log('wax key refresh succeeded');
|
|
1894
|
+
})
|
|
1895
|
+
.catch((err) => {
|
|
1896
|
+
this.log('wax key refresh failed', { error: err instanceof Error ? err.message : String(err) });
|
|
1897
|
+
throw err;
|
|
1687
1898
|
})
|
|
1688
1899
|
.finally(() => {
|
|
1689
1900
|
this._waxRefreshing = null;
|
|
@@ -1791,7 +2002,7 @@ const OzContext = react.createContext({
|
|
|
1791
2002
|
* All `<OzCardNumber />`, `<OzExpiry />`, and `<OzCvv />` children must be
|
|
1792
2003
|
* rendered inside this provider.
|
|
1793
2004
|
*/
|
|
1794
|
-
function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loadTimeoutMs, onWaxRefresh, onReady, appearance, maxTokenizeCalls, children }) {
|
|
2005
|
+
function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loadTimeoutMs, onWaxRefresh, onReady, appearance, maxTokenizeCalls, debug, children }) {
|
|
1795
2006
|
const [vault, setVault] = react.useState(null);
|
|
1796
2007
|
const [initError, setInitError] = react.useState(null);
|
|
1797
2008
|
const [mountedCount, setMountedCount] = react.useState(0);
|
|
@@ -1858,7 +2069,7 @@ function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loa
|
|
|
1858
2069
|
var _a;
|
|
1859
2070
|
Promise.resolve().then(() => setTokenizeCount(0));
|
|
1860
2071
|
(_a = onWaxRefreshRef.current) === null || _a === void 0 ? void 0 : _a.call(onWaxRefreshRef);
|
|
1861
|
-
}
|
|
2072
|
+
}, onReady: () => { var _a; return (_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef); } }), (maxTokenizeCalls !== undefined ? { maxTokenizeCalls } : {})), (debug ? { debug: true } : {})), abortController.signal).then(v => {
|
|
1862
2073
|
if (cancelled) {
|
|
1863
2074
|
v.destroy();
|
|
1864
2075
|
return;
|
|
@@ -1891,7 +2102,7 @@ function OzElements({ fetchWaxKey, pubKey, frameBaseUrl, fonts, onLoadError, loa
|
|
|
1891
2102
|
setVault(null);
|
|
1892
2103
|
setInitError(null);
|
|
1893
2104
|
};
|
|
1894
|
-
}, [pubKey, frameBaseUrl, loadTimeoutMs, appearanceKey, fontsKey, maxTokenizeCalls]);
|
|
2105
|
+
}, [pubKey, frameBaseUrl, loadTimeoutMs, appearanceKey, fontsKey, maxTokenizeCalls, debug]);
|
|
1895
2106
|
const notifyMount = react.useCallback(() => setMountedCount(n => n + 1), []);
|
|
1896
2107
|
const notifyReady = react.useCallback(() => setReadyCount(n => n + 1), []);
|
|
1897
2108
|
const notifyUnmount = react.useCallback(() => {
|
|
@@ -1924,8 +2135,11 @@ function useOzElements() {
|
|
|
1924
2135
|
notifyTokenize();
|
|
1925
2136
|
return result;
|
|
1926
2137
|
}, [vault, notifyTokenize]);
|
|
2138
|
+
const reset = react.useCallback(() => {
|
|
2139
|
+
vault === null || vault === void 0 ? void 0 : vault.reset();
|
|
2140
|
+
}, [vault]);
|
|
1927
2141
|
const ready = vault !== null && vault.isReady && mountedCount > 0 && readyCount >= mountedCount;
|
|
1928
|
-
return { createToken, createBankToken, ready, initError, tokenizeCount };
|
|
2142
|
+
return { createToken, createBankToken, reset, ready, initError, tokenizeCount };
|
|
1929
2143
|
}
|
|
1930
2144
|
const SKELETON_STYLE = {
|
|
1931
2145
|
height: 46,
|
|
@@ -2020,6 +2234,71 @@ const OzCardNumber = (props) => jsxRuntime.jsx(OzFieldBase, Object.assign({ type
|
|
|
2020
2234
|
const OzExpiry = (props) => jsxRuntime.jsx(OzFieldBase, Object.assign({ type: "expirationDate", variant: "card" }, props));
|
|
2021
2235
|
/** Renders a PCI-isolated CVV input inside an Ozura iframe. */
|
|
2022
2236
|
const OzCvv = (props) => jsxRuntime.jsx(OzFieldBase, Object.assign({ type: "cvv", variant: "card" }, props));
|
|
2237
|
+
// ─── Shared composite-component hook ─────────────────────────────────────────
|
|
2238
|
+
/**
|
|
2239
|
+
* Shared plumbing for OzCard and OzBankCard.
|
|
2240
|
+
*
|
|
2241
|
+
* Manages:
|
|
2242
|
+
* - Callback refs (onChange, onReady, onFocus, onBlur) kept in sync on every render
|
|
2243
|
+
* - Vault-change detection: resets `readyFieldTypes` and `onReadyFiredRef` when the
|
|
2244
|
+
* vault instance is replaced (e.g. after fetchWaxKey changes or the provider remounts)
|
|
2245
|
+
* - Per-field ready tracking: creates one stable handler per named field; fires the
|
|
2246
|
+
* `onReady` callback once all `fieldNames.length` fields have reported ready
|
|
2247
|
+
* - Error state
|
|
2248
|
+
* - Layout helpers: `gapStr`, `renderLabel`
|
|
2249
|
+
*
|
|
2250
|
+
* @internal — not exported; used only by OzCard and OzBankCard.
|
|
2251
|
+
*/
|
|
2252
|
+
function useCardBase({ vault, fieldNames, onChange, onReady, onFocus, onBlur, gap = 8, labelStyle, labelClassName, }) {
|
|
2253
|
+
const totalFields = fieldNames.length;
|
|
2254
|
+
const readyFieldTypes = react.useRef(new Set());
|
|
2255
|
+
const onReadyFiredRef = react.useRef(false);
|
|
2256
|
+
const vaultRef = react.useRef(vault);
|
|
2257
|
+
const onChangeRef = react.useRef(onChange);
|
|
2258
|
+
const onReadyRef = react.useRef(onReady);
|
|
2259
|
+
const onFocusRef = react.useRef(onFocus);
|
|
2260
|
+
const onBlurRef = react.useRef(onBlur);
|
|
2261
|
+
react.useEffect(() => { onChangeRef.current = onChange; }, [onChange]);
|
|
2262
|
+
react.useEffect(() => { onReadyRef.current = onReady; }, [onReady]);
|
|
2263
|
+
react.useEffect(() => { onFocusRef.current = onFocus; }, [onFocus]);
|
|
2264
|
+
react.useEffect(() => { onBlurRef.current = onBlur; }, [onBlur]);
|
|
2265
|
+
react.useEffect(() => {
|
|
2266
|
+
if (vault !== vaultRef.current) {
|
|
2267
|
+
vaultRef.current = vault;
|
|
2268
|
+
readyFieldTypes.current = new Set();
|
|
2269
|
+
onReadyFiredRef.current = false;
|
|
2270
|
+
}
|
|
2271
|
+
return () => {
|
|
2272
|
+
readyFieldTypes.current = new Set();
|
|
2273
|
+
onReadyFiredRef.current = false;
|
|
2274
|
+
};
|
|
2275
|
+
}, [vault]);
|
|
2276
|
+
// One stable handler per named field — recreated only when total field count changes.
|
|
2277
|
+
// Field names are static (card = 3 fields, bank = 2 fields) so `totalFields` alone
|
|
2278
|
+
// is a sufficient dependency; a JSON dep would create a new map on every render.
|
|
2279
|
+
// CONTRACT: `fieldNames` must be a static literal — callers must not pass a dynamic
|
|
2280
|
+
// array that changes length without also changing field count.
|
|
2281
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
2282
|
+
const readyHandlers = react.useMemo(() => {
|
|
2283
|
+
const handlers = {};
|
|
2284
|
+
for (const name of fieldNames) {
|
|
2285
|
+
handlers[name] = () => {
|
|
2286
|
+
var _a;
|
|
2287
|
+
readyFieldTypes.current.add(name);
|
|
2288
|
+
if (readyFieldTypes.current.size >= totalFields && !onReadyFiredRef.current) {
|
|
2289
|
+
onReadyFiredRef.current = true;
|
|
2290
|
+
(_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
|
|
2291
|
+
}
|
|
2292
|
+
};
|
|
2293
|
+
}
|
|
2294
|
+
return handlers;
|
|
2295
|
+
}, [totalFields]); // totalFields captures fieldNames.length; field names are static
|
|
2296
|
+
const [error, setError] = react.useState();
|
|
2297
|
+
const gapStr = typeof gap === 'string' ? gap : `${gap}px`;
|
|
2298
|
+
const resolvedLabelStyle = react.useMemo(() => (labelStyle ? Object.assign(Object.assign({}, DEFAULT_LABEL_STYLE), labelStyle) : DEFAULT_LABEL_STYLE), [labelStyle]);
|
|
2299
|
+
const renderLabel = react.useCallback((text) => renderFieldLabel(text, labelClassName, resolvedLabelStyle), [labelClassName, resolvedLabelStyle]);
|
|
2300
|
+
return { onChangeRef, onFocusRef, onBlurRef, readyHandlers, error, setError, gapStr, renderLabel };
|
|
2301
|
+
}
|
|
2023
2302
|
const DEFAULT_ERROR_STYLE = {
|
|
2024
2303
|
color: '#dc2626',
|
|
2025
2304
|
fontSize: 13,
|
|
@@ -2062,62 +2341,22 @@ function mergeStyles(base, override) {
|
|
|
2062
2341
|
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
2342
|
var _a, _b, _c;
|
|
2064
2343
|
const { vault } = react.useContext(OzContext);
|
|
2344
|
+
const { onChangeRef, onFocusRef, onBlurRef, readyHandlers, error, setError, gapStr, renderLabel, } = useCardBase({
|
|
2345
|
+
vault,
|
|
2346
|
+
fieldNames: ['cardNumber', 'expiry', 'cvv'],
|
|
2347
|
+
onChange,
|
|
2348
|
+
onReady,
|
|
2349
|
+
onFocus,
|
|
2350
|
+
onBlur,
|
|
2351
|
+
gap,
|
|
2352
|
+
labelStyle,
|
|
2353
|
+
labelClassName,
|
|
2354
|
+
});
|
|
2065
2355
|
const fieldState = react.useRef({
|
|
2066
2356
|
cardNumber: null,
|
|
2067
2357
|
expiry: null,
|
|
2068
2358
|
cvv: null,
|
|
2069
2359
|
});
|
|
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
2360
|
const emitChange = react.useCallback(() => {
|
|
2122
2361
|
var _a;
|
|
2123
2362
|
const { cardNumber, expiry, cvv } = fieldState.current;
|
|
@@ -2132,20 +2371,16 @@ function OzCard({ style, styles, classNames, labels, labelStyle, labelClassName,
|
|
|
2132
2371
|
error: err,
|
|
2133
2372
|
fields: Object.assign({}, fieldState.current),
|
|
2134
2373
|
});
|
|
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);
|
|
2374
|
+
}, [setError, onChangeRef]);
|
|
2140
2375
|
const showError = !hideErrors && error;
|
|
2141
2376
|
const errorNode = showError
|
|
2142
2377
|
? renderError
|
|
2143
2378
|
? renderError(error)
|
|
2144
2379
|
: (jsxRuntime.jsx("div", { role: "alert", className: errorClassName, style: errorStyle ? Object.assign(Object.assign({}, DEFAULT_ERROR_STYLE), errorStyle) : DEFAULT_ERROR_STYLE, children: error }))
|
|
2145
2380
|
: 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:
|
|
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:
|
|
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:
|
|
2381
|
+
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'] })] }));
|
|
2382
|
+
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'] })] }));
|
|
2383
|
+
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
2384
|
if (layout === 'rows') {
|
|
2150
2385
|
return (jsxRuntime.jsxs("div", { className: className, style: { width: '100%', display: 'flex', flexDirection: 'column', gap: gapStr }, children: [cardNumberField, expiryField, cvvField, errorNode] }));
|
|
2151
2386
|
}
|
|
@@ -2166,49 +2401,21 @@ const OzBankRoutingNumber = (props) => jsxRuntime.jsx(OzFieldBase, Object.assign
|
|
|
2166
2401
|
function OzBankCard({ style, styles, classNames, labels, labelStyle, labelClassName, gap = 8, hideErrors = false, errorStyle, errorClassName, renderError, onChange, onReady, onFocus, onBlur, disabled, className, placeholders, }) {
|
|
2167
2402
|
var _a, _b;
|
|
2168
2403
|
const { vault } = react.useContext(OzContext);
|
|
2404
|
+
const { onChangeRef, onFocusRef, onBlurRef, readyHandlers, error, setError, gapStr, renderLabel, } = useCardBase({
|
|
2405
|
+
vault,
|
|
2406
|
+
fieldNames: ['accountNumber', 'routingNumber'],
|
|
2407
|
+
onChange,
|
|
2408
|
+
onReady,
|
|
2409
|
+
onFocus,
|
|
2410
|
+
onBlur,
|
|
2411
|
+
gap,
|
|
2412
|
+
labelStyle,
|
|
2413
|
+
labelClassName,
|
|
2414
|
+
});
|
|
2169
2415
|
const fieldState = react.useRef({
|
|
2170
2416
|
accountNumber: null,
|
|
2171
2417
|
routingNumber: null,
|
|
2172
2418
|
});
|
|
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
2419
|
const emitChange = react.useCallback(() => {
|
|
2213
2420
|
var _a;
|
|
2214
2421
|
const { accountNumber, routingNumber } = fieldState.current;
|
|
@@ -2221,18 +2428,14 @@ function OzBankCard({ style, styles, classNames, labels, labelStyle, labelClassN
|
|
|
2221
2428
|
error: err,
|
|
2222
2429
|
fields: Object.assign({}, fieldState.current),
|
|
2223
2430
|
});
|
|
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);
|
|
2431
|
+
}, [setError, onChangeRef]);
|
|
2229
2432
|
const showError = !hideErrors && error;
|
|
2230
2433
|
const errorNode = showError
|
|
2231
2434
|
? renderError
|
|
2232
2435
|
? renderError(error)
|
|
2233
2436
|
: (jsxRuntime.jsx("div", { role: "alert", className: errorClassName, style: errorStyle ? Object.assign(Object.assign({}, DEFAULT_ERROR_STYLE), errorStyle) : DEFAULT_ERROR_STYLE, children: error }))
|
|
2234
2437
|
: 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:
|
|
2438
|
+
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
2439
|
}
|
|
2237
2440
|
|
|
2238
2441
|
exports.OzBankAccountNumber = OzBankAccountNumber;
|