@ozura/elements 1.3.1-next.68 → 1.3.1-next.70

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.
@@ -1114,7 +1114,7 @@ class OzVault {
1114
1114
  this.loadErrorTimeoutId = setTimeout(() => {
1115
1115
  this.loadErrorTimeoutId = null;
1116
1116
  if (!this._destroyed && !this.tokenizerReady) {
1117
- options.onLoadError();
1117
+ options.onLoadError({ source: 'tokenizer' });
1118
1118
  }
1119
1119
  }, timeout);
1120
1120
  }
@@ -1247,6 +1247,39 @@ class OzVault {
1247
1247
  get tokenizeCount() {
1248
1248
  return this._tokenizeSuccessCount;
1249
1249
  }
1250
+ /**
1251
+ * `true` when every mounted field has reported `complete && valid` via its
1252
+ * last `change` event. `false` if no fields have been created, or if any
1253
+ * field is incomplete or invalid.
1254
+ *
1255
+ * Use this to gate the pay button in vanilla JS integrations without having
1256
+ * to wire up individual `change` event listeners:
1257
+ *
1258
+ * @example
1259
+ * vault.getElement('cardNumber')!.on('change', () => {
1260
+ * payBtn.disabled = !vault.isComplete;
1261
+ * });
1262
+ */
1263
+ get isComplete() {
1264
+ if (this.elements.size === 0)
1265
+ return false;
1266
+ for (const frameId of this.elements.keys()) {
1267
+ if (this.completionState.get(frameId) !== true)
1268
+ return false;
1269
+ }
1270
+ return true;
1271
+ }
1272
+ /**
1273
+ * `true` while a `createToken()` or `createBankToken()` call is in progress
1274
+ * (including the transparent wax-key refresh phase). Use this to keep the pay
1275
+ * button disabled during tokenization to prevent double-submission.
1276
+ *
1277
+ * @example
1278
+ * payBtn.disabled = vault.isTokenizing;
1279
+ */
1280
+ get isTokenizing() {
1281
+ return this._tokenizing !== null;
1282
+ }
1250
1283
  /**
1251
1284
  * Creates a new OzElement of the given type. Call `.mount(selector)` on the
1252
1285
  * returned element to attach it to the DOM.
@@ -1329,17 +1362,29 @@ class OzVault {
1329
1362
  ? 'A card tokenization is already in progress. Wait for it to complete before calling createBankToken().'
1330
1363
  : 'A bank tokenization is already in progress. Wait for it to complete before calling createBankToken() again.');
1331
1364
  }
1332
- if (!((_a = options.firstName) === null || _a === void 0 ? void 0 : _a.trim())) {
1333
- throw new OzError('firstName is required for bank account tokenization.');
1334
- }
1335
- if (!((_b = options.lastName) === null || _b === void 0 ? void 0 : _b.trim())) {
1336
- throw new OzError('lastName is required for bank account tokenization.');
1337
- }
1338
- if (options.firstName.trim().length > 50) {
1339
- throw new OzError('firstName must be 50 characters or fewer.');
1365
+ // Validate billing details if provided billing.firstName/lastName take
1366
+ // precedence over the top-level params (mirrors createToken() behaviour).
1367
+ let normalizedBankBilling;
1368
+ let bankFirstName = ((_a = options.firstName) !== null && _a !== void 0 ? _a : '').trim();
1369
+ let bankLastName = ((_b = options.lastName) !== null && _b !== void 0 ? _b : '').trim();
1370
+ if (options.billing) {
1371
+ const result = validateBilling(options.billing);
1372
+ if (!result.valid) {
1373
+ throw new OzError(`Invalid billing details: ${result.errors.join('; ')}`);
1374
+ }
1375
+ normalizedBankBilling = result.normalized;
1376
+ bankFirstName = normalizedBankBilling.firstName;
1377
+ bankLastName = normalizedBankBilling.lastName;
1340
1378
  }
1341
- if (options.lastName.trim().length > 50) {
1342
- throw new OzError('lastName must be 50 characters or fewer.');
1379
+ else {
1380
+ if (!bankFirstName)
1381
+ throw new OzError('firstName is required for bank account tokenization.');
1382
+ if (!bankLastName)
1383
+ throw new OzError('lastName is required for bank account tokenization.');
1384
+ if (bankFirstName.length > 50)
1385
+ throw new OzError('firstName must be 50 characters or fewer.');
1386
+ if (bankLastName.length > 50)
1387
+ throw new OzError('lastName must be 50 characters or fewer.');
1343
1388
  }
1344
1389
  const accountEl = this.bankElementsByType.get('accountNumber');
1345
1390
  const routingEl = this.bankElementsByType.get('routingNumber');
@@ -1365,14 +1410,7 @@ class OzVault {
1365
1410
  if (this._resetCount === resetCountAtStart)
1366
1411
  this._tokenizing = null;
1367
1412
  };
1368
- this.bankTokenizeResolvers.set(requestId, {
1369
- resolve: (v) => { cleanup(); resolve(v); },
1370
- reject: (e) => { cleanup(); reject(e); },
1371
- firstName: options.firstName.trim(),
1372
- lastName: options.lastName.trim(),
1373
- readyElements: readyBankElements,
1374
- fieldCount: readyBankElements.length,
1375
- });
1413
+ 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 }));
1376
1414
  try {
1377
1415
  const bankChannels = readyBankElements.map(() => new MessageChannel());
1378
1416
  const bankTokenizeStartMs = Date.now();
@@ -1381,8 +1419,8 @@ class OzVault {
1381
1419
  requestId,
1382
1420
  tokenizationSessionId: this.tokenizationSessionId,
1383
1421
  pubKey: (_a = this.pubKey) !== null && _a !== void 0 ? _a : '',
1384
- firstName: options.firstName.trim(),
1385
- lastName: options.lastName.trim(),
1422
+ firstName: bankFirstName,
1423
+ lastName: bankLastName,
1386
1424
  fieldCount: readyBankElements.length,
1387
1425
  }, bankChannels.map(ch => ch.port1));
1388
1426
  this.log('OZ_BANK_TOKENIZE sent', { requestIdPrefix: `${requestId.slice(0, 12)}...`, fieldCount: readyBankElements.length });
@@ -2104,7 +2142,7 @@ class OzVault {
2104
2142
  break;
2105
2143
  }
2106
2144
  const bank = isBankAccountMetadata(msg.bank) ? msg.bank : undefined;
2107
- pending.resolve(Object.assign({ token }, (bank ? { bank } : {})));
2145
+ pending.resolve(Object.assign(Object.assign({ token }, (bank ? { bank } : {})), (pending.billing ? { billing: pending.billing } : {})));
2108
2146
  this.log('bank token received', {
2109
2147
  elapsedMs: pending.tokenizeStartMs != null ? Date.now() - pending.tokenizeStartMs : null,
2110
2148
  tokenPresent: true,
@@ -2271,6 +2309,7 @@ const OzElements = defineComponent({
2271
2309
  debug: { type: Boolean, default: undefined },
2272
2310
  onSessionRefresh: { type: Function, default: undefined },
2273
2311
  onReady: { type: Function, default: undefined },
2312
+ onLoadError: { type: Function, default: undefined },
2274
2313
  sessionLimit: { type: Number, default: undefined },
2275
2314
  maxTokenizeCalls: { type: Number, default: undefined },
2276
2315
  },
@@ -2316,7 +2355,20 @@ const OzElements = defineComponent({
2316
2355
  onMounted(() => {
2317
2356
  const ac = new AbortController();
2318
2357
  abortController = ac;
2319
- OzVault.create(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ pubKey: props.pubKey }, (props.sessionUrl ? { sessionUrl: props.sessionUrl } : {})), (props.getSessionKey ? { getSessionKey: props.getSessionKey } : {})), (props.frameBaseUrl ? { frameBaseUrl: props.frameBaseUrl } : {})), (props.fonts ? { fonts: props.fonts } : {})), (props.appearance ? { appearance: props.appearance } : {})), (props.loadTimeoutMs !== undefined ? { loadTimeoutMs: props.loadTimeoutMs } : {})), (props.debug ? { debug: props.debug } : {})), {
2358
+ // Guard: onLoadError must fire at most once per mount cycle. It can be
2359
+ // triggered by two independent paths — the vault's iframe load timeout
2360
+ // (inside OzVault constructor) and the .catch below when create() rejects
2361
+ // after the timeout has already fired. Without this flag both paths would
2362
+ // call the callback, mirroring the same guard used in the React provider.
2363
+ let loadErrorFired = false;
2364
+ const fireLoadError = () => {
2365
+ var _a;
2366
+ if (loadErrorFired)
2367
+ return;
2368
+ loadErrorFired = true;
2369
+ (_a = props.onLoadError) === null || _a === void 0 ? void 0 : _a.call(props, { source: 'tokenizer' });
2370
+ };
2371
+ OzVault.create(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign(Object.assign({ pubKey: props.pubKey }, (props.sessionUrl ? { sessionUrl: props.sessionUrl } : {})), (props.getSessionKey ? { getSessionKey: props.getSessionKey } : {})), (props.frameBaseUrl ? { frameBaseUrl: props.frameBaseUrl } : {})), (props.fonts ? { fonts: props.fonts } : {})), (props.appearance ? { appearance: props.appearance } : {})), (props.loadTimeoutMs !== undefined ? { loadTimeoutMs: props.loadTimeoutMs } : {})), (props.onLoadError ? { onLoadError: fireLoadError } : {})), (props.debug ? { debug: props.debug } : {})), {
2320
2372
  // Session lifecycle — wire refresh callback and reset tokenizeCount so the
2321
2373
  // counter stays accurate across proactive key refreshes (mirrors React provider).
2322
2374
  // Deferred by one microtask for the same reason as React: notifyTokenize fires
@@ -2338,6 +2390,12 @@ const OzElements = defineComponent({
2338
2390
  if (ac.signal.aborted)
2339
2391
  return;
2340
2392
  initError.value = err instanceof Error ? err : new Error('OzVault.create() failed.');
2393
+ if (props.onLoadError) {
2394
+ fireLoadError();
2395
+ }
2396
+ else {
2397
+ console.error('[OzElements] OzVault.create() failed. Provide an `onLoadError` prop to handle this gracefully.', err);
2398
+ }
2341
2399
  });
2342
2400
  });
2343
2401
  onUnmounted(() => {