@visa/cli 4.1.0-rc.83 → 4.1.0-rc.84
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/checkout-engine/hosted-approval.js +12 -3
- package/dist/checkout-engine/vgs-live-instrument.d.ts +9 -0
- package/dist/checkout-engine/vgs-live-instrument.js +4 -1
- package/dist/cli.js +8 -8
- package/dist/mcp-server/index.js +5 -5
- package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
- package/package.json +1 -1
- package/server.json +2 -2
|
@@ -236,7 +236,13 @@ export async function runHostedApproval(opts) {
|
|
|
236
236
|
if (doc?.status === 'declined')
|
|
237
237
|
throw new HostedApprovalDeclinedError();
|
|
238
238
|
if (doc?.status === 'completed') {
|
|
239
|
-
|
|
239
|
+
// A passkey-exempt token ('otp' cardholder ID&V / 'none') runs no
|
|
240
|
+
// ceremony and produces no assurance — the approval server marks the
|
|
241
|
+
// claim explicitly (derived from the owner's verified card record at
|
|
242
|
+
// complete-time). Only that marker excuses a null assuranceData; a
|
|
243
|
+
// silently dropped payload still fails exactly as before.
|
|
244
|
+
const assuranceExempt = doc.cardholderVerification === 'otp' || doc.cardholderVerification === 'none';
|
|
245
|
+
if ((doc.assuranceData === undefined || doc.assuranceData === null) && !assuranceExempt) {
|
|
240
246
|
throw new Error('hosted approval completed but carried no assuranceData');
|
|
241
247
|
}
|
|
242
248
|
// The context the approval was COMPLETED against must be exactly this
|
|
@@ -304,9 +310,12 @@ export async function runHostedApproval(opts) {
|
|
|
304
310
|
else if (doc.validUntil !== undefined) {
|
|
305
311
|
throw new Error('hosted one-purchase approval unexpectedly carried a budget expiry');
|
|
306
312
|
}
|
|
307
|
-
log(
|
|
313
|
+
log(assuranceExempt
|
|
314
|
+
? 'approval received from the hosted page (code-verified card, no passkey).'
|
|
315
|
+
: 'passkey approval received from the hosted page.');
|
|
308
316
|
return {
|
|
309
|
-
...assuranceFromCeremony(target, doc.assuranceData),
|
|
317
|
+
...assuranceFromCeremony(target, doc.assuranceData ?? null),
|
|
318
|
+
...(assuranceExempt ? { assuranceExempt: true } : {}),
|
|
310
319
|
...(typeof doc.mintToken === 'string' && doc.mintToken
|
|
311
320
|
? { mintToken: doc.mintToken }
|
|
312
321
|
: {}),
|
|
@@ -42,6 +42,15 @@ export type PurchaseAssurance = {
|
|
|
42
42
|
transactionAmount: string;
|
|
43
43
|
/** ISO 4217 currency the ceremony was scoped to. */
|
|
44
44
|
transactionCurrencyCode: string;
|
|
45
|
+
/**
|
|
46
|
+
* True iff the approval server marked this token passkey-exempt ('otp'
|
|
47
|
+
* cardholder ID&V / 'none' — verified against the owner's account card
|
|
48
|
+
* record at complete-time): no ceremony ran, `assuranceData` is null by
|
|
49
|
+
* design, and the intent is minted without it (the signed mint token
|
|
50
|
+
* carries the same exemption for the server's own check). Absent/false →
|
|
51
|
+
* assuranceData is REQUIRED, exactly the historical contract.
|
|
52
|
+
*/
|
|
53
|
+
assuranceExempt?: boolean;
|
|
45
54
|
};
|
|
46
55
|
/**
|
|
47
56
|
* Our fail-closed freshness bound, matching the runner's 15-minute checkout
|
|
@@ -106,7 +106,10 @@ export function validatePurchaseAssurance(value, target, now = new Date()) {
|
|
|
106
106
|
if (value == null || typeof value !== 'object') {
|
|
107
107
|
throw new Error(`purchase assurance is missing — ${remedy}`);
|
|
108
108
|
}
|
|
109
|
-
|
|
109
|
+
// A passkey-exempt approval (marked by the approval SERVER, never assumed
|
|
110
|
+
// locally) carries no assurance by design — every scope/freshness check
|
|
111
|
+
// below still applies to it unchanged.
|
|
112
|
+
if (value.assuranceData == null && value.assuranceExempt !== true) {
|
|
110
113
|
throw new Error(`purchase assurance requires assuranceData — ${remedy}`);
|
|
111
114
|
}
|
|
112
115
|
const mintedAtMs = typeof value.mintedAt === 'string' ? Date.parse(value.mintedAt) : NaN;
|