@ozura/elements 1.3.1 → 1.4.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/dist/frame/element-frame.html +1 -1
- package/dist/frame/element-frame.js +7 -4
- package/dist/frame/tokenizer-frame.html +1 -1
- package/dist/frame/tokenizer-frame.js +5 -6
- package/dist/oz-elements.esm.js +70 -22
- package/dist/oz-elements.umd.js +70 -22
- package/dist/react/index.cjs.js +114 -38
- package/dist/react/index.esm.js +114 -38
- package/dist/react/react/index.d.ts +29 -1
- package/dist/react/sdk/OzVault.d.ts +33 -0
- package/dist/react/types/index.d.ts +30 -5
- package/dist/react/vue/index.d.ts +56 -1
- package/dist/server/sdk/OzVault.d.ts +33 -0
- package/dist/server/types/index.d.ts +30 -5
- package/dist/server/vue/index.d.ts +56 -1
- package/dist/types/sdk/OzVault.d.ts +33 -0
- package/dist/types/types/index.d.ts +30 -5
- package/dist/types/vue/index.d.ts +56 -1
- package/dist/vue/index.cjs.js +135 -35
- package/dist/vue/index.esm.js +135 -35
- package/dist/vue/sdk/OzVault.d.ts +33 -0
- package/dist/vue/types/index.d.ts +30 -5
- package/dist/vue/vue/index.d.ts +56 -1
- package/package.json +1 -1
- package/dist/react/index.cjs.js.map +0 -1
- package/dist/react/index.esm.js.map +0 -1
- package/dist/vue/index.cjs.js.map +0 -1
- package/dist/vue/index.esm.js.map +0 -1
|
@@ -17,6 +17,6 @@
|
|
|
17
17
|
</style>
|
|
18
18
|
</head>
|
|
19
19
|
<body>
|
|
20
|
-
<script src="./element-frame.js" integrity="sha384-
|
|
20
|
+
<script src="./element-frame.js" integrity="sha384-cIulRhFy1VmJOkAhGUL37LXgMI2dlGiy0XbrrqeZ/IvsXlcq7U+Z1+KLIKb4Ty1G" crossorigin="anonymous"></script>
|
|
21
21
|
</body>
|
|
22
22
|
</html>
|
|
@@ -588,10 +588,13 @@ var _OzElementFrame = (function (exports) {
|
|
|
588
588
|
const msg = event.data;
|
|
589
589
|
if (!msg || msg.__oz !== true || msg.vaultId !== this.vaultId)
|
|
590
590
|
return;
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
591
|
+
// Reject messages when parentOrigin was absent/invalid, and messages from
|
|
592
|
+
// any origin that doesn't match the validated parentOrigin. Never adopt an
|
|
593
|
+
// unknown sender as the host — that would let any page that guesses the
|
|
594
|
+
// vaultId hijack the frame's postMessage target. The SDK always supplies
|
|
595
|
+
// parentOrigin in the hash; if it's missing, the frame is non-functional
|
|
596
|
+
// by design rather than open to the first sender.
|
|
597
|
+
if (!this.hostOrigin || event.origin !== this.hostOrigin) {
|
|
595
598
|
return;
|
|
596
599
|
}
|
|
597
600
|
switch (msg.type) {
|
|
@@ -6,6 +6,6 @@
|
|
|
6
6
|
<title>Ozura Tokenizer</title>
|
|
7
7
|
</head>
|
|
8
8
|
<body>
|
|
9
|
-
<script src="./tokenizer-frame.js" integrity="sha384-
|
|
9
|
+
<script src="./tokenizer-frame.js" integrity="sha384-n3WeN859wcdlVjR0zCrcbTWEtfZyxw682z21vn2OGkjLOtPfrDa9IqywSkLhNh3e" crossorigin="anonymous"></script>
|
|
10
10
|
</body>
|
|
11
11
|
</html>
|
|
@@ -143,15 +143,14 @@ var _OzTokenizerFrame = (function (exports) {
|
|
|
143
143
|
return;
|
|
144
144
|
switch (msg.type) {
|
|
145
145
|
case 'OZ_INIT':
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
146
|
+
// Never adopt an unknown sender as the host. If parentOrigin was absent
|
|
147
|
+
// or failed safeParentOrigin() validation, hostOrigin is "" and this
|
|
148
|
+
// frame is non-functional by design — rejecting all messages is safer
|
|
149
|
+
// than trusting the first sender. The SDK always supplies parentOrigin.
|
|
149
150
|
// Origin-validated wax key store. Accepts both the initial delivery and
|
|
150
151
|
// subsequent refresh deliveries (sent by OzVault.refreshWaxKey() after
|
|
151
152
|
// minting a new key). Origin validation is the security boundary —
|
|
152
|
-
// messages from any origin other than
|
|
153
|
-
// OZ_INIT are rejected. The previous write-once guard blocked refreshes
|
|
154
|
-
// and made the auto-refresh feature non-functional.
|
|
153
|
+
// messages from any origin other than hostOrigin are silently dropped.
|
|
155
154
|
if (event.origin === this.hostOrigin && typeof msg.waxKey === 'string' && msg.waxKey) {
|
|
156
155
|
this.waxKey = msg.waxKey;
|
|
157
156
|
}
|
package/dist/oz-elements.esm.js
CHANGED
|
@@ -1167,7 +1167,7 @@ class OzVault {
|
|
|
1167
1167
|
this.loadErrorTimeoutId = setTimeout(() => {
|
|
1168
1168
|
this.loadErrorTimeoutId = null;
|
|
1169
1169
|
if (!this._destroyed && !this.tokenizerReady) {
|
|
1170
|
-
options.onLoadError();
|
|
1170
|
+
options.onLoadError({ source: 'tokenizer' });
|
|
1171
1171
|
}
|
|
1172
1172
|
}, timeout);
|
|
1173
1173
|
}
|
|
@@ -1300,6 +1300,49 @@ class OzVault {
|
|
|
1300
1300
|
get tokenizeCount() {
|
|
1301
1301
|
return this._tokenizeSuccessCount;
|
|
1302
1302
|
}
|
|
1303
|
+
/**
|
|
1304
|
+
* `true` when every mounted field has reported `complete && valid` via its
|
|
1305
|
+
* last `change` event. `false` if no fields have been created, or if any
|
|
1306
|
+
* field is incomplete or invalid.
|
|
1307
|
+
*
|
|
1308
|
+
* Use this to gate the pay button in vanilla JS integrations without having
|
|
1309
|
+
* to wire up individual `change` event listeners:
|
|
1310
|
+
*
|
|
1311
|
+
* @example
|
|
1312
|
+
* vault.getElement('cardNumber')!.on('change', () => {
|
|
1313
|
+
* payBtn.disabled = !vault.isComplete;
|
|
1314
|
+
* });
|
|
1315
|
+
*/
|
|
1316
|
+
get isComplete() {
|
|
1317
|
+
return this.allComplete([...this.elementsByType.values()]);
|
|
1318
|
+
}
|
|
1319
|
+
/**
|
|
1320
|
+
* Like {@link isComplete}, but for bank-account elements created via
|
|
1321
|
+
* {@link createBankElement}. Card and bank fields are tracked separately so a
|
|
1322
|
+
* card-only checkout is never gated on bank fields (and vice versa), matching
|
|
1323
|
+
* the `createToken()` / `createBankToken()` split. A vault with both card and
|
|
1324
|
+
* bank elements exposes each completion state independently.
|
|
1325
|
+
*/
|
|
1326
|
+
get isBankComplete() {
|
|
1327
|
+
return this.allComplete([...this.bankElementsByType.values()]);
|
|
1328
|
+
}
|
|
1329
|
+
/** True iff the set is non-empty and every element has reported complete-and-valid. */
|
|
1330
|
+
allComplete(els) {
|
|
1331
|
+
if (els.length === 0)
|
|
1332
|
+
return false;
|
|
1333
|
+
return els.every(el => this.completionState.get(el.frameId) === true);
|
|
1334
|
+
}
|
|
1335
|
+
/**
|
|
1336
|
+
* `true` while a `createToken()` or `createBankToken()` call is in progress
|
|
1337
|
+
* (including the transparent wax-key refresh phase). Use this to keep the pay
|
|
1338
|
+
* button disabled during tokenization to prevent double-submission.
|
|
1339
|
+
*
|
|
1340
|
+
* @example
|
|
1341
|
+
* payBtn.disabled = vault.isTokenizing;
|
|
1342
|
+
*/
|
|
1343
|
+
get isTokenizing() {
|
|
1344
|
+
return this._tokenizing !== null;
|
|
1345
|
+
}
|
|
1303
1346
|
/**
|
|
1304
1347
|
* Creates a new OzElement of the given type. Call `.mount(selector)` on the
|
|
1305
1348
|
* returned element to attach it to the DOM.
|
|
@@ -1382,17 +1425,29 @@ class OzVault {
|
|
|
1382
1425
|
? 'A card tokenization is already in progress. Wait for it to complete before calling createBankToken().'
|
|
1383
1426
|
: 'A bank tokenization is already in progress. Wait for it to complete before calling createBankToken() again.');
|
|
1384
1427
|
}
|
|
1385
|
-
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
|
|
1389
|
-
|
|
1390
|
-
|
|
1391
|
-
|
|
1392
|
-
|
|
1428
|
+
// Validate billing details if provided — billing.firstName/lastName take
|
|
1429
|
+
// precedence over the top-level params (mirrors createToken() behaviour).
|
|
1430
|
+
let normalizedBankBilling;
|
|
1431
|
+
let bankFirstName = ((_a = options.firstName) !== null && _a !== void 0 ? _a : '').trim();
|
|
1432
|
+
let bankLastName = ((_b = options.lastName) !== null && _b !== void 0 ? _b : '').trim();
|
|
1433
|
+
if (options.billing) {
|
|
1434
|
+
const result = validateBilling(options.billing);
|
|
1435
|
+
if (!result.valid) {
|
|
1436
|
+
throw new OzError(`Invalid billing details: ${result.errors.join('; ')}`);
|
|
1437
|
+
}
|
|
1438
|
+
normalizedBankBilling = result.normalized;
|
|
1439
|
+
bankFirstName = normalizedBankBilling.firstName;
|
|
1440
|
+
bankLastName = normalizedBankBilling.lastName;
|
|
1393
1441
|
}
|
|
1394
|
-
|
|
1395
|
-
|
|
1442
|
+
else {
|
|
1443
|
+
if (!bankFirstName)
|
|
1444
|
+
throw new OzError('firstName is required for bank account tokenization.');
|
|
1445
|
+
if (!bankLastName)
|
|
1446
|
+
throw new OzError('lastName is required for bank account tokenization.');
|
|
1447
|
+
if (bankFirstName.length > 50)
|
|
1448
|
+
throw new OzError('firstName must be 50 characters or fewer.');
|
|
1449
|
+
if (bankLastName.length > 50)
|
|
1450
|
+
throw new OzError('lastName must be 50 characters or fewer.');
|
|
1396
1451
|
}
|
|
1397
1452
|
const accountEl = this.bankElementsByType.get('accountNumber');
|
|
1398
1453
|
const routingEl = this.bankElementsByType.get('routingNumber');
|
|
@@ -1418,14 +1473,7 @@ class OzVault {
|
|
|
1418
1473
|
if (this._resetCount === resetCountAtStart)
|
|
1419
1474
|
this._tokenizing = null;
|
|
1420
1475
|
};
|
|
1421
|
-
this.bankTokenizeResolvers.set(requestId, {
|
|
1422
|
-
resolve: (v) => { cleanup(); resolve(v); },
|
|
1423
|
-
reject: (e) => { cleanup(); reject(e); },
|
|
1424
|
-
firstName: options.firstName.trim(),
|
|
1425
|
-
lastName: options.lastName.trim(),
|
|
1426
|
-
readyElements: readyBankElements,
|
|
1427
|
-
fieldCount: readyBankElements.length,
|
|
1428
|
-
});
|
|
1476
|
+
this.bankTokenizeResolvers.set(requestId, Object.assign(Object.assign({ resolve: (v) => { cleanup(); resolve(v); }, reject: (e) => { cleanup(); reject(e); }, firstName: bankFirstName, lastName: bankLastName }, (normalizedBankBilling ? { billing: normalizedBankBilling } : {})), { readyElements: readyBankElements, fieldCount: readyBankElements.length }));
|
|
1429
1477
|
try {
|
|
1430
1478
|
const bankChannels = readyBankElements.map(() => new MessageChannel());
|
|
1431
1479
|
const bankTokenizeStartMs = Date.now();
|
|
@@ -1434,8 +1482,8 @@ class OzVault {
|
|
|
1434
1482
|
requestId,
|
|
1435
1483
|
tokenizationSessionId: this.tokenizationSessionId,
|
|
1436
1484
|
pubKey: (_a = this.pubKey) !== null && _a !== void 0 ? _a : '',
|
|
1437
|
-
firstName:
|
|
1438
|
-
lastName:
|
|
1485
|
+
firstName: bankFirstName,
|
|
1486
|
+
lastName: bankLastName,
|
|
1439
1487
|
fieldCount: readyBankElements.length,
|
|
1440
1488
|
}, bankChannels.map(ch => ch.port1));
|
|
1441
1489
|
this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
|
|
@@ -2157,7 +2205,7 @@ class OzVault {
|
|
|
2157
2205
|
break;
|
|
2158
2206
|
}
|
|
2159
2207
|
const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
|
|
2160
|
-
pending.resolve(Object.assign({ token }, (bank ? { bank } : {})));
|
|
2208
|
+
pending.resolve(Object.assign(Object.assign({ token }, (bank ? { bank } : {})), (pending.billing ? { billing: pending.billing } : {})));
|
|
2161
2209
|
this.log('bank token received', {
|
|
2162
2210
|
elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
|
|
2163
2211
|
tokenPresent: true,
|
package/dist/oz-elements.umd.js
CHANGED
|
@@ -1173,7 +1173,7 @@
|
|
|
1173
1173
|
this.loadErrorTimeoutId = setTimeout(() => {
|
|
1174
1174
|
this.loadErrorTimeoutId = null;
|
|
1175
1175
|
if (!this._destroyed && !this.tokenizerReady) {
|
|
1176
|
-
options.onLoadError();
|
|
1176
|
+
options.onLoadError({ source: 'tokenizer' });
|
|
1177
1177
|
}
|
|
1178
1178
|
}, timeout);
|
|
1179
1179
|
}
|
|
@@ -1306,6 +1306,49 @@
|
|
|
1306
1306
|
get tokenizeCount() {
|
|
1307
1307
|
return this._tokenizeSuccessCount;
|
|
1308
1308
|
}
|
|
1309
|
+
/**
|
|
1310
|
+
* `true` when every mounted field has reported `complete && valid` via its
|
|
1311
|
+
* last `change` event. `false` if no fields have been created, or if any
|
|
1312
|
+
* field is incomplete or invalid.
|
|
1313
|
+
*
|
|
1314
|
+
* Use this to gate the pay button in vanilla JS integrations without having
|
|
1315
|
+
* to wire up individual `change` event listeners:
|
|
1316
|
+
*
|
|
1317
|
+
* @example
|
|
1318
|
+
* vault.getElement('cardNumber')!.on('change', () => {
|
|
1319
|
+
* payBtn.disabled = !vault.isComplete;
|
|
1320
|
+
* });
|
|
1321
|
+
*/
|
|
1322
|
+
get isComplete() {
|
|
1323
|
+
return this.allComplete([...this.elementsByType.values()]);
|
|
1324
|
+
}
|
|
1325
|
+
/**
|
|
1326
|
+
* Like {@link isComplete}, but for bank-account elements created via
|
|
1327
|
+
* {@link createBankElement}. Card and bank fields are tracked separately so a
|
|
1328
|
+
* card-only checkout is never gated on bank fields (and vice versa), matching
|
|
1329
|
+
* the `createToken()` / `createBankToken()` split. A vault with both card and
|
|
1330
|
+
* bank elements exposes each completion state independently.
|
|
1331
|
+
*/
|
|
1332
|
+
get isBankComplete() {
|
|
1333
|
+
return this.allComplete([...this.bankElementsByType.values()]);
|
|
1334
|
+
}
|
|
1335
|
+
/** True iff the set is non-empty and every element has reported complete-and-valid. */
|
|
1336
|
+
allComplete(els) {
|
|
1337
|
+
if (els.length === 0)
|
|
1338
|
+
return false;
|
|
1339
|
+
return els.every(el => this.completionState.get(el.frameId) === true);
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* `true` while a `createToken()` or `createBankToken()` call is in progress
|
|
1343
|
+
* (including the transparent wax-key refresh phase). Use this to keep the pay
|
|
1344
|
+
* button disabled during tokenization to prevent double-submission.
|
|
1345
|
+
*
|
|
1346
|
+
* @example
|
|
1347
|
+
* payBtn.disabled = vault.isTokenizing;
|
|
1348
|
+
*/
|
|
1349
|
+
get isTokenizing() {
|
|
1350
|
+
return this._tokenizing !== null;
|
|
1351
|
+
}
|
|
1309
1352
|
/**
|
|
1310
1353
|
* Creates a new OzElement of the given type. Call `.mount(selector)` on the
|
|
1311
1354
|
* returned element to attach it to the DOM.
|
|
@@ -1388,17 +1431,29 @@
|
|
|
1388
1431
|
? 'A card tokenization is already in progress. Wait for it to complete before calling createBankToken().'
|
|
1389
1432
|
: 'A bank tokenization is already in progress. Wait for it to complete before calling createBankToken() again.');
|
|
1390
1433
|
}
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1398
|
-
|
|
1434
|
+
// Validate billing details if provided — billing.firstName/lastName take
|
|
1435
|
+
// precedence over the top-level params (mirrors createToken() behaviour).
|
|
1436
|
+
let normalizedBankBilling;
|
|
1437
|
+
let bankFirstName = ((_a = options.firstName) !== null && _a !== void 0 ? _a : '').trim();
|
|
1438
|
+
let bankLastName = ((_b = options.lastName) !== null && _b !== void 0 ? _b : '').trim();
|
|
1439
|
+
if (options.billing) {
|
|
1440
|
+
const result = validateBilling(options.billing);
|
|
1441
|
+
if (!result.valid) {
|
|
1442
|
+
throw new OzError(`Invalid billing details: ${result.errors.join('; ')}`);
|
|
1443
|
+
}
|
|
1444
|
+
normalizedBankBilling = result.normalized;
|
|
1445
|
+
bankFirstName = normalizedBankBilling.firstName;
|
|
1446
|
+
bankLastName = normalizedBankBilling.lastName;
|
|
1399
1447
|
}
|
|
1400
|
-
|
|
1401
|
-
|
|
1448
|
+
else {
|
|
1449
|
+
if (!bankFirstName)
|
|
1450
|
+
throw new OzError('firstName is required for bank account tokenization.');
|
|
1451
|
+
if (!bankLastName)
|
|
1452
|
+
throw new OzError('lastName is required for bank account tokenization.');
|
|
1453
|
+
if (bankFirstName.length > 50)
|
|
1454
|
+
throw new OzError('firstName must be 50 characters or fewer.');
|
|
1455
|
+
if (bankLastName.length > 50)
|
|
1456
|
+
throw new OzError('lastName must be 50 characters or fewer.');
|
|
1402
1457
|
}
|
|
1403
1458
|
const accountEl = this.bankElementsByType.get('accountNumber');
|
|
1404
1459
|
const routingEl = this.bankElementsByType.get('routingNumber');
|
|
@@ -1424,14 +1479,7 @@
|
|
|
1424
1479
|
if (this._resetCount === resetCountAtStart)
|
|
1425
1480
|
this._tokenizing = null;
|
|
1426
1481
|
};
|
|
1427
|
-
this.bankTokenizeResolvers.set(requestId, {
|
|
1428
|
-
resolve: (v) => { cleanup(); resolve(v); },
|
|
1429
|
-
reject: (e) => { cleanup(); reject(e); },
|
|
1430
|
-
firstName: options.firstName.trim(),
|
|
1431
|
-
lastName: options.lastName.trim(),
|
|
1432
|
-
readyElements: readyBankElements,
|
|
1433
|
-
fieldCount: readyBankElements.length,
|
|
1434
|
-
});
|
|
1482
|
+
this.bankTokenizeResolvers.set(requestId, Object.assign(Object.assign({ resolve: (v) => { cleanup(); resolve(v); }, reject: (e) => { cleanup(); reject(e); }, firstName: bankFirstName, lastName: bankLastName }, (normalizedBankBilling ? { billing: normalizedBankBilling } : {})), { readyElements: readyBankElements, fieldCount: readyBankElements.length }));
|
|
1435
1483
|
try {
|
|
1436
1484
|
const bankChannels = readyBankElements.map(() => new MessageChannel());
|
|
1437
1485
|
const bankTokenizeStartMs = Date.now();
|
|
@@ -1440,8 +1488,8 @@
|
|
|
1440
1488
|
requestId,
|
|
1441
1489
|
tokenizationSessionId: this.tokenizationSessionId,
|
|
1442
1490
|
pubKey: (_a = this.pubKey) !== null && _a !== void 0 ? _a : '',
|
|
1443
|
-
firstName:
|
|
1444
|
-
lastName:
|
|
1491
|
+
firstName: bankFirstName,
|
|
1492
|
+
lastName: bankLastName,
|
|
1445
1493
|
fieldCount: readyBankElements.length,
|
|
1446
1494
|
}, bankChannels.map(ch => ch.port1));
|
|
1447
1495
|
this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
|
|
@@ -2163,7 +2211,7 @@
|
|
|
2163
2211
|
break;
|
|
2164
2212
|
}
|
|
2165
2213
|
const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
|
|
2166
|
-
pending.resolve(Object.assign({ token }, (bank ? { bank } : {})));
|
|
2214
|
+
pending.resolve(Object.assign(Object.assign({ token }, (bank ? { bank } : {})), (pending.billing ? { billing: pending.billing } : {})));
|
|
2167
2215
|
this.log('bank token received', {
|
|
2168
2216
|
elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
|
|
2169
2217
|
tokenPresent: true,
|
package/dist/react/index.cjs.js
CHANGED
|
@@ -1117,7 +1117,7 @@ class OzVault {
|
|
|
1117
1117
|
this.loadErrorTimeoutId = setTimeout(() => {
|
|
1118
1118
|
this.loadErrorTimeoutId = null;
|
|
1119
1119
|
if (!this._destroyed && !this.tokenizerReady) {
|
|
1120
|
-
options.onLoadError();
|
|
1120
|
+
options.onLoadError({ source: 'tokenizer' });
|
|
1121
1121
|
}
|
|
1122
1122
|
}, timeout);
|
|
1123
1123
|
}
|
|
@@ -1250,6 +1250,49 @@ class OzVault {
|
|
|
1250
1250
|
get tokenizeCount() {
|
|
1251
1251
|
return this._tokenizeSuccessCount;
|
|
1252
1252
|
}
|
|
1253
|
+
/**
|
|
1254
|
+
* `true` when every mounted field has reported `complete && valid` via its
|
|
1255
|
+
* last `change` event. `false` if no fields have been created, or if any
|
|
1256
|
+
* field is incomplete or invalid.
|
|
1257
|
+
*
|
|
1258
|
+
* Use this to gate the pay button in vanilla JS integrations without having
|
|
1259
|
+
* to wire up individual `change` event listeners:
|
|
1260
|
+
*
|
|
1261
|
+
* @example
|
|
1262
|
+
* vault.getElement('cardNumber')!.on('change', () => {
|
|
1263
|
+
* payBtn.disabled = !vault.isComplete;
|
|
1264
|
+
* });
|
|
1265
|
+
*/
|
|
1266
|
+
get isComplete() {
|
|
1267
|
+
return this.allComplete([...this.elementsByType.values()]);
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Like {@link isComplete}, but for bank-account elements created via
|
|
1271
|
+
* {@link createBankElement}. Card and bank fields are tracked separately so a
|
|
1272
|
+
* card-only checkout is never gated on bank fields (and vice versa), matching
|
|
1273
|
+
* the `createToken()` / `createBankToken()` split. A vault with both card and
|
|
1274
|
+
* bank elements exposes each completion state independently.
|
|
1275
|
+
*/
|
|
1276
|
+
get isBankComplete() {
|
|
1277
|
+
return this.allComplete([...this.bankElementsByType.values()]);
|
|
1278
|
+
}
|
|
1279
|
+
/** True iff the set is non-empty and every element has reported complete-and-valid. */
|
|
1280
|
+
allComplete(els) {
|
|
1281
|
+
if (els.length === 0)
|
|
1282
|
+
return false;
|
|
1283
|
+
return els.every(el => this.completionState.get(el.frameId) === true);
|
|
1284
|
+
}
|
|
1285
|
+
/**
|
|
1286
|
+
* `true` while a `createToken()` or `createBankToken()` call is in progress
|
|
1287
|
+
* (including the transparent wax-key refresh phase). Use this to keep the pay
|
|
1288
|
+
* button disabled during tokenization to prevent double-submission.
|
|
1289
|
+
*
|
|
1290
|
+
* @example
|
|
1291
|
+
* payBtn.disabled = vault.isTokenizing;
|
|
1292
|
+
*/
|
|
1293
|
+
get isTokenizing() {
|
|
1294
|
+
return this._tokenizing !== null;
|
|
1295
|
+
}
|
|
1253
1296
|
/**
|
|
1254
1297
|
* Creates a new OzElement of the given type. Call `.mount(selector)` on the
|
|
1255
1298
|
* returned element to attach it to the DOM.
|
|
@@ -1332,17 +1375,29 @@ class OzVault {
|
|
|
1332
1375
|
? 'A card tokenization is already in progress. Wait for it to complete before calling createBankToken().'
|
|
1333
1376
|
: 'A bank tokenization is already in progress. Wait for it to complete before calling createBankToken() again.');
|
|
1334
1377
|
}
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1378
|
+
// Validate billing details if provided — billing.firstName/lastName take
|
|
1379
|
+
// precedence over the top-level params (mirrors createToken() behaviour).
|
|
1380
|
+
let normalizedBankBilling;
|
|
1381
|
+
let bankFirstName = ((_a = options.firstName) !== null && _a !== void 0 ? _a : '').trim();
|
|
1382
|
+
let bankLastName = ((_b = options.lastName) !== null && _b !== void 0 ? _b : '').trim();
|
|
1383
|
+
if (options.billing) {
|
|
1384
|
+
const result = validateBilling(options.billing);
|
|
1385
|
+
if (!result.valid) {
|
|
1386
|
+
throw new OzError(`Invalid billing details: ${result.errors.join('; ')}`);
|
|
1387
|
+
}
|
|
1388
|
+
normalizedBankBilling = result.normalized;
|
|
1389
|
+
bankFirstName = normalizedBankBilling.firstName;
|
|
1390
|
+
bankLastName = normalizedBankBilling.lastName;
|
|
1343
1391
|
}
|
|
1344
|
-
|
|
1345
|
-
|
|
1392
|
+
else {
|
|
1393
|
+
if (!bankFirstName)
|
|
1394
|
+
throw new OzError('firstName is required for bank account tokenization.');
|
|
1395
|
+
if (!bankLastName)
|
|
1396
|
+
throw new OzError('lastName is required for bank account tokenization.');
|
|
1397
|
+
if (bankFirstName.length > 50)
|
|
1398
|
+
throw new OzError('firstName must be 50 characters or fewer.');
|
|
1399
|
+
if (bankLastName.length > 50)
|
|
1400
|
+
throw new OzError('lastName must be 50 characters or fewer.');
|
|
1346
1401
|
}
|
|
1347
1402
|
const accountEl = this.bankElementsByType.get('accountNumber');
|
|
1348
1403
|
const routingEl = this.bankElementsByType.get('routingNumber');
|
|
@@ -1368,14 +1423,7 @@ class OzVault {
|
|
|
1368
1423
|
if (this._resetCount === resetCountAtStart)
|
|
1369
1424
|
this._tokenizing = null;
|
|
1370
1425
|
};
|
|
1371
|
-
this.bankTokenizeResolvers.set(requestId, {
|
|
1372
|
-
resolve: (v) => { cleanup(); resolve(v); },
|
|
1373
|
-
reject: (e) => { cleanup(); reject(e); },
|
|
1374
|
-
firstName: options.firstName.trim(),
|
|
1375
|
-
lastName: options.lastName.trim(),
|
|
1376
|
-
readyElements: readyBankElements,
|
|
1377
|
-
fieldCount: readyBankElements.length,
|
|
1378
|
-
});
|
|
1426
|
+
this.bankTokenizeResolvers.set(requestId, Object.assign(Object.assign({ resolve: (v) => { cleanup(); resolve(v); }, reject: (e) => { cleanup(); reject(e); }, firstName: bankFirstName, lastName: bankLastName }, (normalizedBankBilling ? { billing: normalizedBankBilling } : {})), { readyElements: readyBankElements, fieldCount: readyBankElements.length }));
|
|
1379
1427
|
try {
|
|
1380
1428
|
const bankChannels = readyBankElements.map(() => new MessageChannel());
|
|
1381
1429
|
const bankTokenizeStartMs = Date.now();
|
|
@@ -1384,8 +1432,8 @@ class OzVault {
|
|
|
1384
1432
|
requestId,
|
|
1385
1433
|
tokenizationSessionId: this.tokenizationSessionId,
|
|
1386
1434
|
pubKey: (_a = this.pubKey) !== null && _a !== void 0 ? _a : '',
|
|
1387
|
-
firstName:
|
|
1388
|
-
lastName:
|
|
1435
|
+
firstName: bankFirstName,
|
|
1436
|
+
lastName: bankLastName,
|
|
1389
1437
|
fieldCount: readyBankElements.length,
|
|
1390
1438
|
}, bankChannels.map(ch => ch.port1));
|
|
1391
1439
|
this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
|
|
@@ -2107,7 +2155,7 @@ class OzVault {
|
|
|
2107
2155
|
break;
|
|
2108
2156
|
}
|
|
2109
2157
|
const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
|
|
2110
|
-
pending.resolve(Object.assign({ token }, (bank ? { bank } : {})));
|
|
2158
|
+
pending.resolve(Object.assign(Object.assign({ token }, (bank ? { bank } : {})), (pending.billing ? { billing: pending.billing } : {})));
|
|
2111
2159
|
this.log('bank token received', {
|
|
2112
2160
|
elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
|
|
2113
2161
|
tokenPresent: true,
|
|
@@ -2237,9 +2285,11 @@ const OzContext = react.createContext({
|
|
|
2237
2285
|
notifyUnmount: () => { },
|
|
2238
2286
|
notifyMount: () => { },
|
|
2239
2287
|
notifyTokenize: () => { },
|
|
2288
|
+
notifyChange: () => { },
|
|
2240
2289
|
mountedCount: 0,
|
|
2241
2290
|
readyCount: 0,
|
|
2242
2291
|
tokenizeCount: 0,
|
|
2292
|
+
changeTick: 0,
|
|
2243
2293
|
});
|
|
2244
2294
|
/**
|
|
2245
2295
|
* Creates and owns an OzVault instance for the lifetime of this component.
|
|
@@ -2252,6 +2302,7 @@ function OzElements({ sessionUrl, getSessionKey, fetchWaxKey, pubKey, frameBaseU
|
|
|
2252
2302
|
const [mountedCount, setMountedCount] = react.useState(0);
|
|
2253
2303
|
const [readyCount, setReadyCount] = react.useState(0);
|
|
2254
2304
|
const [tokenizeCount, setTokenizeCount] = react.useState(0);
|
|
2305
|
+
const [changeTick, setChangeTick] = react.useState(0);
|
|
2255
2306
|
const onLoadErrorRef = react.useRef(onLoadError);
|
|
2256
2307
|
onLoadErrorRef.current = onLoadError;
|
|
2257
2308
|
const onWaxRefreshRef = react.useRef(onSessionRefresh !== null && onSessionRefresh !== void 0 ? onSessionRefresh : onWaxRefresh);
|
|
@@ -2290,7 +2341,7 @@ function OzElements({ sessionUrl, getSessionKey, fetchWaxKey, pubKey, frameBaseU
|
|
|
2290
2341
|
if (loadErrorFired)
|
|
2291
2342
|
return;
|
|
2292
2343
|
loadErrorFired = true;
|
|
2293
|
-
(_a = onLoadErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onLoadErrorRef);
|
|
2344
|
+
(_a = onLoadErrorRef.current) === null || _a === void 0 ? void 0 : _a.call(onLoadErrorRef, { source: 'tokenizer' });
|
|
2294
2345
|
};
|
|
2295
2346
|
// AbortController passed to create() so that if this effect's cleanup runs
|
|
2296
2347
|
// while fetchWaxKey is still in-flight (React StrictMode double-invoke or
|
|
@@ -2374,7 +2425,8 @@ function OzElements({ sessionUrl, getSessionKey, fetchWaxKey, pubKey, frameBaseU
|
|
|
2374
2425
|
setReadyCount(n => Math.max(0, n - 1));
|
|
2375
2426
|
}, []);
|
|
2376
2427
|
const notifyTokenize = react.useCallback(() => setTokenizeCount(n => n + 1), []);
|
|
2377
|
-
const
|
|
2428
|
+
const notifyChange = react.useCallback(() => setChangeTick(n => n + 1), []);
|
|
2429
|
+
const value = react.useMemo(() => ({ vault, initError, notifyMount, notifyReady, notifyUnmount, notifyTokenize, notifyChange, mountedCount, readyCount, tokenizeCount, changeTick }), [vault, initError, notifyMount, notifyReady, notifyUnmount, notifyTokenize, notifyChange, mountedCount, readyCount, tokenizeCount, changeTick]);
|
|
2378
2430
|
return jsxRuntime.jsx(OzContext.Provider, { value: value, children: children });
|
|
2379
2431
|
}
|
|
2380
2432
|
/**
|
|
@@ -2382,28 +2434,53 @@ function OzElements({ sessionUrl, getSessionKey, fetchWaxKey, pubKey, frameBaseU
|
|
|
2382
2434
|
* an `<OzElements>` provider tree.
|
|
2383
2435
|
*/
|
|
2384
2436
|
function useOzElements() {
|
|
2385
|
-
|
|
2437
|
+
var _a, _b, _c;
|
|
2438
|
+
const { vault, initError, mountedCount, readyCount, notifyTokenize, notifyChange, tokenizeCount } = react.useContext(OzContext);
|
|
2386
2439
|
const createToken = react.useCallback(async (options) => {
|
|
2387
2440
|
if (!vault) {
|
|
2388
2441
|
return Promise.reject(new OzError('useOzElements must be called inside an <OzElements> provider.'));
|
|
2389
2442
|
}
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
2443
|
+
// Start the call so vault._tokenizing flips synchronously, then notify so
|
|
2444
|
+
// `isTokenizing` re-renders as `true`. Notify again on settle so it returns
|
|
2445
|
+
// to `false`. (vault.isTokenizing is a plain getter — nothing else would
|
|
2446
|
+
// trigger a render while the call is in flight.)
|
|
2447
|
+
const promise = vault.createToken(options);
|
|
2448
|
+
notifyChange();
|
|
2449
|
+
try {
|
|
2450
|
+
const result = await promise;
|
|
2451
|
+
notifyTokenize();
|
|
2452
|
+
return result;
|
|
2453
|
+
}
|
|
2454
|
+
finally {
|
|
2455
|
+
notifyChange();
|
|
2456
|
+
}
|
|
2457
|
+
}, [vault, notifyTokenize, notifyChange]);
|
|
2394
2458
|
const createBankToken = react.useCallback(async (options) => {
|
|
2395
2459
|
if (!vault) {
|
|
2396
2460
|
return Promise.reject(new OzError('useOzElements must be called inside an <OzElements> provider.'));
|
|
2397
2461
|
}
|
|
2398
|
-
const
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2462
|
+
const promise = vault.createBankToken(options);
|
|
2463
|
+
notifyChange();
|
|
2464
|
+
try {
|
|
2465
|
+
const result = await promise;
|
|
2466
|
+
notifyTokenize();
|
|
2467
|
+
return result;
|
|
2468
|
+
}
|
|
2469
|
+
finally {
|
|
2470
|
+
notifyChange();
|
|
2471
|
+
}
|
|
2472
|
+
}, [vault, notifyTokenize, notifyChange]);
|
|
2402
2473
|
const reset = react.useCallback(() => {
|
|
2403
2474
|
vault === null || vault === void 0 ? void 0 : vault.reset();
|
|
2404
|
-
|
|
2475
|
+
// reset() clears completion state and cancels any in-flight tokenization, so
|
|
2476
|
+
// re-render to refresh the derived isComplete / isTokenizing getters below.
|
|
2477
|
+
notifyChange();
|
|
2478
|
+
}, [vault, notifyChange]);
|
|
2405
2479
|
const ready = vault !== null && vault.isReady && mountedCount > 0 && readyCount >= mountedCount;
|
|
2406
|
-
|
|
2480
|
+
const isComplete = (_a = vault === null || vault === void 0 ? void 0 : vault.isComplete) !== null && _a !== void 0 ? _a : false;
|
|
2481
|
+
const isBankComplete = (_b = vault === null || vault === void 0 ? void 0 : vault.isBankComplete) !== null && _b !== void 0 ? _b : false;
|
|
2482
|
+
const isTokenizing = (_c = vault === null || vault === void 0 ? void 0 : vault.isTokenizing) !== null && _c !== void 0 ? _c : false;
|
|
2483
|
+
return { createToken, createBankToken, reset, ready, initError, tokenizeCount, isComplete, isBankComplete, isTokenizing };
|
|
2407
2484
|
}
|
|
2408
2485
|
const SKELETON_STYLE = {
|
|
2409
2486
|
height: 46,
|
|
@@ -2438,7 +2515,7 @@ function OzFieldBase({ type, variant, style, placeholder, disabled, loadTimeoutM
|
|
|
2438
2515
|
const elementRef = react.useRef(null);
|
|
2439
2516
|
const [loaded, setLoaded] = react.useState(false);
|
|
2440
2517
|
const [loadError, setLoadError] = react.useState(null);
|
|
2441
|
-
const { vault, notifyMount, notifyReady, notifyUnmount } = react.useContext(OzContext);
|
|
2518
|
+
const { vault, notifyMount, notifyReady, notifyUnmount, notifyChange } = react.useContext(OzContext);
|
|
2442
2519
|
const onChangeRef = react.useRef(onChange);
|
|
2443
2520
|
const onFocusRef = react.useRef(onFocus);
|
|
2444
2521
|
const onBlurRef = react.useRef(onBlur);
|
|
@@ -2470,7 +2547,7 @@ function OzFieldBase({ type, variant, style, placeholder, disabled, loadTimeoutM
|
|
|
2470
2547
|
notifyReady();
|
|
2471
2548
|
(_a = onReadyRef.current) === null || _a === void 0 ? void 0 : _a.call(onReadyRef);
|
|
2472
2549
|
});
|
|
2473
|
-
element.on('change', (e) => { var _a;
|
|
2550
|
+
element.on('change', (e) => { var _a; (_a = onChangeRef.current) === null || _a === void 0 ? void 0 : _a.call(onChangeRef, e); notifyChange(); });
|
|
2474
2551
|
element.on('focus', () => { var _a; return (_a = onFocusRef.current) === null || _a === void 0 ? void 0 : _a.call(onFocusRef); });
|
|
2475
2552
|
element.on('blur', () => { var _a; return (_a = onBlurRef.current) === null || _a === void 0 ? void 0 : _a.call(onBlurRef); });
|
|
2476
2553
|
element.on('loaderror', (e) => {
|
|
@@ -2713,4 +2790,3 @@ exports.OzExpiry = OzExpiry;
|
|
|
2713
2790
|
exports.createFetchWaxKey = createSessionFetcher;
|
|
2714
2791
|
exports.createSessionFetcher = createSessionFetcher;
|
|
2715
2792
|
exports.useOzElements = useOzElements;
|
|
2716
|
-
//# sourceMappingURL=index.cjs.js.map
|