@visa/cli 4.1.0-rc.229 → 4.1.0-rc.230
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 +17 -9
- package/dist/checkout-engine/adapters/shopify.d.ts +27 -2
- package/dist/checkout-engine/adapters/shopify.js +194 -20
- package/dist/checkout-engine/cli-engine.d.ts +35 -2
- package/dist/checkout-engine/cli-engine.js +146 -12
- package/dist/checkout-engine/executor.d.ts +17 -0
- package/dist/checkout-engine/executor.js +212 -40
- package/dist/checkout-engine/hosted-approval.js +4 -1
- package/dist/checkout-engine/index.d.ts +3 -2
- package/dist/checkout-engine/index.js +1 -0
- package/dist/checkout-engine/receipt.d.ts +14 -0
- package/dist/checkout-engine/receipt.js +13 -3
- package/dist/checkout-engine/unresolved-charges.js +9 -0
- package/dist/checkout-engine/web-bot-auth.d.ts +7 -1
- package/dist/checkout-engine/web-bot-auth.js +60 -1
- package/dist/cli.js +540 -474
- package/dist/mcp-server/index.js +451 -396
- package/dist/skills/pair-visa-agent/RUNTIMES.md +9 -1
- package/dist/skills/pair-visa-agent/scripts/__tests__/setup.test.mjs +407 -0
- package/dist/skills/pair-visa-agent/scripts/setup.mjs +310 -30
- package/install.ps1 +5 -4
- package/install.sh +1 -1
- package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
- package/package.json +8 -10
- package/server.json +2 -2
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
// Every browser/network primitive is injectable (CliEngineDeps) so the session/
|
|
12
12
|
// timer/store lifecycle is unit-testable without launching Chromium.
|
|
13
13
|
import { readFile } from 'node:fs/promises';
|
|
14
|
+
import { randomUUID } from 'node:crypto';
|
|
14
15
|
import { launchCheckoutBrowser } from './browser-launch.js';
|
|
15
16
|
import { RECEIPT_DIR } from './receipt-dir.js';
|
|
16
17
|
import { prepareCheckout as realPrepareCheckout, submitApprovedCheckout as realSubmitApprovedCheckout, InMemoryPreparedCheckoutStore, } from './executor.js';
|
|
@@ -19,7 +20,7 @@ import { VgsLiveInstrument, decimalToMinor, minorToDecimal, } from './vgs-live-i
|
|
|
19
20
|
import { serverCreateIntent, serverFetchCryptogram, serverPostConfirmation, } from './vgs-gateway/server-mint-client.js';
|
|
20
21
|
import { createCardMandate, DEFAULT_MANDATE_MAX_DRAWS, drawFromMandate, MandateDrawDeclinedError, } from './mandate/card-mandate.js';
|
|
21
22
|
import { MandateLedger } from './mandate/mandate-ledger.js';
|
|
22
|
-
import { buildReceipt, writeReceipt as realWriteReceipt } from './receipt.js';
|
|
23
|
+
import { buildReceipt, writeReceipt as realWriteReceipt, } from './receipt.js';
|
|
23
24
|
import { reportVicOutcome as realReportVicOutcome, } from './vic-confirmation.js';
|
|
24
25
|
/**
|
|
25
26
|
* A card-mandate draw failed transiently (retryable) rather than definitively.
|
|
@@ -60,6 +61,31 @@ export function isTransientDrawFailure(err) {
|
|
|
60
61
|
// card-decline reason) matches nothing here → the mandate is correctly disabled.
|
|
61
62
|
return /\(last:\s*(?:5\d\d|429)\b|\bETIMEDOUT\b|\bECONNRESET\b|\bECONNREFUSED\b|\bEAI_AGAIN\b|socket hang up|tim(?:e|ed)[ -]?out/i.test(msgs.join(' '));
|
|
62
63
|
}
|
|
64
|
+
/**
|
|
65
|
+
* Refusals that are about the OUTER card GRANT's remaining capacity, not about
|
|
66
|
+
* this mandate and not about the card network (#7491).
|
|
67
|
+
*
|
|
68
|
+
* The atomic reserve (#7233) charges the grant's shared `agent_grant_usage`
|
|
69
|
+
* epoch, so an exhausted or replaced grant refuses BEFORE any credential is
|
|
70
|
+
* minted: no cryptogram is issued, no processor intent is created, no money
|
|
71
|
+
* moves. Treating that as a mandate decline burned the budget the human had
|
|
72
|
+
* just approved and told the caller to create another mandate — which, over the
|
|
73
|
+
* same exhausted grant, is guaranteed to fail identically. The honest remedy is
|
|
74
|
+
* a new/replacement card grant, and the mandate must stay honored so it can
|
|
75
|
+
* draw once the owner supplies one.
|
|
76
|
+
*/
|
|
77
|
+
// Deliberately NARROW. `grant_aggregate_no_mandate` is NOT here: that refusal
|
|
78
|
+
// means the mandate row itself vanished between the read and the reserve, which
|
|
79
|
+
// is a statement about the mandate, not about the grant's capacity.
|
|
80
|
+
const GRANT_CAPACITY_REASONS = new Set([
|
|
81
|
+
'over_grant_aggregate',
|
|
82
|
+
'no_active_grant',
|
|
83
|
+
'no_active_grant_or_card_scope',
|
|
84
|
+
'card_grant_exhausted',
|
|
85
|
+
]);
|
|
86
|
+
function isGrantCapacityReason(reason) {
|
|
87
|
+
return GRANT_CAPACITY_REASONS.has(reason);
|
|
88
|
+
}
|
|
63
89
|
/**
|
|
64
90
|
* A verdict refusal may be wrapped by drawFromMandate after its local
|
|
65
91
|
* reservation is released. Walk the cause chain so the original auth status +
|
|
@@ -80,10 +106,17 @@ export function classifyCardDrawVerdictFailure(err) {
|
|
|
80
106
|
? candidate.reasons.filter((reason) => typeof reason === 'string')
|
|
81
107
|
: [];
|
|
82
108
|
if (status !== 0 || reasons.length > 0) {
|
|
109
|
+
const transient = status === 503 ||
|
|
110
|
+
reasons.length === 0 ||
|
|
111
|
+
reasons.every((reason) => transientReasons.has(reason));
|
|
83
112
|
return {
|
|
84
|
-
transient
|
|
85
|
-
|
|
86
|
-
|
|
113
|
+
transient,
|
|
114
|
+
// A capacity refusal is NEITHER transient nor a decline. Retrying now
|
|
115
|
+
// fails identically (so it is not transient), but nothing about the
|
|
116
|
+
// MANDATE was refused (so it is not a decline). Only a refusal that is
|
|
117
|
+
// exclusively about the outer grant qualifies: a mixed reason list
|
|
118
|
+
// still carries a real mandate/network refusal and stays a decline.
|
|
119
|
+
grantCapacity: !transient && reasons.length > 0 && reasons.every(isGrantCapacityReason),
|
|
87
120
|
reasons,
|
|
88
121
|
};
|
|
89
122
|
}
|
|
@@ -130,8 +163,9 @@ export class CheckoutReviewRefusedError extends Error {
|
|
|
130
163
|
failureCode;
|
|
131
164
|
requiresAdapter;
|
|
132
165
|
detectedRoles;
|
|
166
|
+
receiptWrite;
|
|
133
167
|
detail;
|
|
134
|
-
constructor(result) {
|
|
168
|
+
constructor(result, receiptWrite) {
|
|
135
169
|
super(result.detail
|
|
136
170
|
? `review refused: ${result.outcome} — ${result.detail}`
|
|
137
171
|
: `review refused: ${result.outcome}`);
|
|
@@ -140,12 +174,14 @@ export class CheckoutReviewRefusedError extends Error {
|
|
|
140
174
|
this.failureCode = result.failureCode;
|
|
141
175
|
this.requiresAdapter = [...result.requiresAdapter];
|
|
142
176
|
this.detectedRoles = Object.keys(result.fields);
|
|
177
|
+
this.receiptWrite = receiptWrite;
|
|
143
178
|
this.detail = result.detail;
|
|
144
179
|
}
|
|
145
180
|
}
|
|
146
181
|
function payAttemptFingerprint(input) {
|
|
147
182
|
return JSON.stringify([
|
|
148
183
|
input.url,
|
|
184
|
+
input.checkoutRoute,
|
|
149
185
|
input.amount,
|
|
150
186
|
input.currency,
|
|
151
187
|
input.credentialPath,
|
|
@@ -171,6 +207,9 @@ function failedPay(detail) {
|
|
|
171
207
|
credentialDisclosed: false,
|
|
172
208
|
};
|
|
173
209
|
}
|
|
210
|
+
function boundedReceiptWriteErrorCode(reason) {
|
|
211
|
+
return reason.match(/\b(?:EACCES|EEXIST|ENOSPC|ENOTDIR|EPERM|EROFS)\b/)?.[0] ?? 'UNKNOWN';
|
|
212
|
+
}
|
|
174
213
|
// Must match the prepared-checkout store TTL so a session and its store entry
|
|
175
214
|
// expire together — an abandoned review can't leak the browser + state.
|
|
176
215
|
const PREPARED_TTL_MS = 5 * 60 * 1000;
|
|
@@ -194,6 +233,39 @@ export function createCliCheckoutEngine(deps = {}) {
|
|
|
194
233
|
const now = deps.now ?? (() => new Date());
|
|
195
234
|
const fetchMandateCryptogram = deps.serverFetchCryptogram ?? serverFetchCryptogram;
|
|
196
235
|
const postServerConfirmation = deps.serverPostConfirmation ?? serverPostConfirmation;
|
|
236
|
+
const createObservationId = deps.createObservationId ?? randomUUID;
|
|
237
|
+
async function writeObservedReceipt(mode, receipt) {
|
|
238
|
+
let report;
|
|
239
|
+
try {
|
|
240
|
+
report = await writeReceipt(RECEIPT_DIR, receipt);
|
|
241
|
+
}
|
|
242
|
+
catch (err) {
|
|
243
|
+
// The real writer is fail-open already. Keep that invariant even for an
|
|
244
|
+
// injected writer so a filesystem or test-double failure cannot replace
|
|
245
|
+
// the checkout result after a charge may have completed.
|
|
246
|
+
report = { written: false, reason: err instanceof Error ? err.message : String(err) };
|
|
247
|
+
}
|
|
248
|
+
const event = {
|
|
249
|
+
event: 'checkout_receipt_write',
|
|
250
|
+
status: report.written ? 'written' : 'failed',
|
|
251
|
+
mode,
|
|
252
|
+
checkoutOutcome: receipt.outcome,
|
|
253
|
+
merchantHost: receipt.merchant.host,
|
|
254
|
+
observationId: receipt.observationId ?? receipt.receiptId ?? createObservationId(),
|
|
255
|
+
failureCode: receipt.capability?.failureCode ?? null,
|
|
256
|
+
errorCode: report.written ? null : boundedReceiptWriteErrorCode(report.reason),
|
|
257
|
+
panRedactions: report.written ? report.panRedactions : null,
|
|
258
|
+
};
|
|
259
|
+
try {
|
|
260
|
+
const observerResult = deps.onReceiptWrite?.(event);
|
|
261
|
+
if (observerResult)
|
|
262
|
+
void Promise.resolve(observerResult).catch(() => undefined);
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// Observability is non-authoritative and must not affect payment state.
|
|
266
|
+
}
|
|
267
|
+
return { report, event };
|
|
268
|
+
}
|
|
197
269
|
const cardDrawVerdict = deps.cardDrawVerdict ?? null;
|
|
198
270
|
const cardMandateRegister = deps.cardMandateRegister ?? null;
|
|
199
271
|
async function closeSession(reviewId) {
|
|
@@ -504,6 +576,7 @@ export function createCliCheckoutEngine(deps = {}) {
|
|
|
504
576
|
try {
|
|
505
577
|
const prep = await prepareCheckout({
|
|
506
578
|
url: input.url,
|
|
579
|
+
checkoutRoute: input.checkoutRoute,
|
|
507
580
|
mandate: {
|
|
508
581
|
maxAmountMinor: amountMinor,
|
|
509
582
|
currency: input.currency,
|
|
@@ -514,18 +587,56 @@ export function createCliCheckoutEngine(deps = {}) {
|
|
|
514
587
|
currency: input.currency,
|
|
515
588
|
browser,
|
|
516
589
|
contact: input.contact,
|
|
590
|
+
...(input.trustedMerchantIdentity
|
|
591
|
+
? { trustedMerchantIdentity: input.trustedMerchantIdentity }
|
|
592
|
+
: {}),
|
|
517
593
|
}, store);
|
|
518
594
|
if (prep.status !== 'ready') {
|
|
519
|
-
|
|
595
|
+
// A terminal review is still a capability observation. Persist the
|
|
596
|
+
// same compact, owner-only artifact as a pay attempt so the local
|
|
597
|
+
// ledger records unsupported merchant surfaces and the safe card
|
|
598
|
+
// display identity instead of retaining successes only.
|
|
599
|
+
const observationId = createObservationId();
|
|
600
|
+
const receiptWrite = await writeObservedReceipt('dry-run', buildReceipt({
|
|
601
|
+
mode: 'dry-run',
|
|
602
|
+
reviewId: null,
|
|
603
|
+
observationId,
|
|
604
|
+
merchant: {
|
|
605
|
+
name: input.merchantName ?? host,
|
|
606
|
+
host,
|
|
607
|
+
url: input.url,
|
|
608
|
+
},
|
|
609
|
+
transaction: {
|
|
610
|
+
amount: input.amount,
|
|
611
|
+
amountMinor,
|
|
612
|
+
currency: input.currency,
|
|
613
|
+
},
|
|
614
|
+
result: prep.result,
|
|
615
|
+
vicConfirmation: null,
|
|
616
|
+
agentName: input.agentName,
|
|
617
|
+
cardLast4: input.cardLast4,
|
|
618
|
+
recordedAt: now(),
|
|
619
|
+
}));
|
|
620
|
+
throw new CheckoutReviewRefusedError(prep.result, receiptWrite.event);
|
|
520
621
|
}
|
|
521
622
|
const r = prep.checkout.review;
|
|
522
623
|
// Expire the companion session on the SAME schedule as the store entry
|
|
523
624
|
// (unref'd so a pending timer never keeps the process alive).
|
|
524
625
|
const cleanupTimer = setTimeout(() => void closeSession(r.id), ttlMs);
|
|
525
626
|
cleanupTimer.unref?.();
|
|
627
|
+
const target = buildTarget(input);
|
|
628
|
+
if (r.merchantOrigin) {
|
|
629
|
+
// The credential and mandate bind to the exact origin the browser
|
|
630
|
+
// actually reviewed, not the Shopify service continuation that led
|
|
631
|
+
// there. Keep the latter separately for review/pay replay binding.
|
|
632
|
+
target.merchantUrl = r.merchantOrigin;
|
|
633
|
+
target.merchantName = input.merchantName ?? r.merchantHost;
|
|
634
|
+
}
|
|
526
635
|
sessions.set(r.id, {
|
|
527
636
|
browser,
|
|
528
|
-
|
|
637
|
+
requestUrl: new URL(input.url).toString(),
|
|
638
|
+
checkoutRoute: input.checkoutRoute,
|
|
639
|
+
target,
|
|
529
640
|
amountMinor,
|
|
530
641
|
currency: input.currency,
|
|
531
642
|
contact: input.contact,
|
|
@@ -604,7 +715,7 @@ export function createCliCheckoutEngine(deps = {}) {
|
|
|
604
715
|
let reviewedUrl;
|
|
605
716
|
try {
|
|
606
717
|
payUrl = new URL(input.url).toString();
|
|
607
|
-
reviewedUrl =
|
|
718
|
+
reviewedUrl = session.requestUrl;
|
|
608
719
|
}
|
|
609
720
|
catch {
|
|
610
721
|
await closeSession(input.reviewId);
|
|
@@ -658,6 +769,19 @@ export function createCliCheckoutEngine(deps = {}) {
|
|
|
658
769
|
...noCredentialFacts,
|
|
659
770
|
};
|
|
660
771
|
}
|
|
772
|
+
if (input.checkoutRoute !== session.checkoutRoute) {
|
|
773
|
+
await closeSession(input.reviewId);
|
|
774
|
+
return {
|
|
775
|
+
outcome: 'failed',
|
|
776
|
+
confirmationRef: null,
|
|
777
|
+
receiptPath: null,
|
|
778
|
+
detail: 'pay checkout route does not match the reviewed guest-card route — start a fresh review',
|
|
779
|
+
vicConfirmation: null,
|
|
780
|
+
source: null,
|
|
781
|
+
remainingMinor: null,
|
|
782
|
+
...noCredentialFacts,
|
|
783
|
+
};
|
|
784
|
+
}
|
|
661
785
|
// Amount-bind the confirmation: the pay-call amount must match the
|
|
662
786
|
// reviewed amount. The reviewId already locks the immutable mandate, but
|
|
663
787
|
// re-checking here makes the confirmation explicitly amount-bound so a
|
|
@@ -856,9 +980,18 @@ export function createCliCheckoutEngine(deps = {}) {
|
|
|
856
980
|
if (err instanceof MandateDrawDeclinedError) {
|
|
857
981
|
const verdictFailure = classifyCardDrawVerdictFailure(err);
|
|
858
982
|
if (verdictFailure) {
|
|
859
|
-
|
|
983
|
+
// #7491: a GRANT-capacity refusal is pre-credential and says
|
|
984
|
+
// nothing about this mandate, so it must NOT disable it. The
|
|
985
|
+
// mandate is healthy and becomes drawable again the moment the
|
|
986
|
+
// owner supplies a grant with room; disabling it here threw
|
|
987
|
+
// away a just-approved budget and pointed the caller at a new
|
|
988
|
+
// mandate that would die on the same wall.
|
|
989
|
+
if (!verdictFailure.transient && !verdictFailure.grantCapacity) {
|
|
860
990
|
await ledger.markUnhonored(mandateId, now()).catch(() => { });
|
|
861
991
|
}
|
|
992
|
+
if (verdictFailure.grantCapacity) {
|
|
993
|
+
throw new Error(`this agent's card grant has no remaining capacity (${verdictFailure.reasons.join(', ') || 'grant exhausted'}) — nothing was charged and the mandate is still valid; ask the owner for a new or replenished card grant (visa agent grant-card) before retrying this checkout`, { cause: err });
|
|
994
|
+
}
|
|
862
995
|
throw new Error(verdictFailure.transient
|
|
863
996
|
? `the card-mandate draw could not be authorized right now (${verdictFailure.reasons.join(', ') || 'temporary error'}) — retry shortly`
|
|
864
997
|
: `this card mandate can no longer be drawn (${verdictFailure.reasons.join(', ') || 'refused'}); it has been disabled — create and claim a new mandate before retrying this checkout`, { cause: err });
|
|
@@ -938,7 +1071,7 @@ export function createCliCheckoutEngine(deps = {}) {
|
|
|
938
1071
|
});
|
|
939
1072
|
}
|
|
940
1073
|
let receiptPath = null;
|
|
941
|
-
const
|
|
1074
|
+
const receiptWrite = await writeObservedReceipt(mode, buildReceipt({
|
|
942
1075
|
mode,
|
|
943
1076
|
reviewId: input.reviewId,
|
|
944
1077
|
// Derive BOTH name and host from the reviewed session target (not
|
|
@@ -959,8 +1092,8 @@ export function createCliCheckoutEngine(deps = {}) {
|
|
|
959
1092
|
agentName: input.agentName,
|
|
960
1093
|
cardLast4: input.cardLast4,
|
|
961
1094
|
}));
|
|
962
|
-
if (report.written)
|
|
963
|
-
receiptPath = report.path;
|
|
1095
|
+
if (receiptWrite.report.written)
|
|
1096
|
+
receiptPath = receiptWrite.report.path;
|
|
964
1097
|
return finishAttempt({
|
|
965
1098
|
outcome: result.outcome,
|
|
966
1099
|
confirmationRef: result.confirmationRef ?? null,
|
|
@@ -973,6 +1106,7 @@ export function createCliCheckoutEngine(deps = {}) {
|
|
|
973
1106
|
credentialIssued: result.credentialLifecycle !== 'not-requested',
|
|
974
1107
|
credentialDisclosed: result.credentialLifecycle === 'partially-exposed' ||
|
|
975
1108
|
result.credentialLifecycle === 'fully-filled',
|
|
1109
|
+
receiptWrite: receiptWrite.event,
|
|
976
1110
|
});
|
|
977
1111
|
}
|
|
978
1112
|
catch (error) {
|
|
@@ -8,6 +8,7 @@ import { type ObservedOutcome } from './outcome.js';
|
|
|
8
8
|
import { type WebBotAuthConfig } from './web-bot-auth.js';
|
|
9
9
|
export { minorFromDecimal, pageCurrency } from './amount.js';
|
|
10
10
|
export type CheckoutMode = 'dry-run' | 'submit';
|
|
11
|
+
export type CheckoutRoute = 'guest-card';
|
|
11
12
|
export type CheckoutOutcome = 'reviewed-dry-run'
|
|
12
13
|
/** Historical receipt value from the credential-disclosing dry-run. */
|
|
13
14
|
| 'filled-dry-run' | 'partial-fill' | 'adapter-required' | 'confirmed' | 'declined' | 'action-required' | 'cancelled' | 'blocked-by-mandate'
|
|
@@ -56,6 +57,7 @@ export type CheckoutResult = {
|
|
|
56
57
|
};
|
|
57
58
|
export type PrepareCheckoutOptions = {
|
|
58
59
|
url: string;
|
|
60
|
+
checkoutRoute: CheckoutRoute;
|
|
59
61
|
mandate: Mandate;
|
|
60
62
|
browser: Browser;
|
|
61
63
|
contact?: Contact;
|
|
@@ -63,6 +65,16 @@ export type PrepareCheckoutOptions = {
|
|
|
63
65
|
currency?: string;
|
|
64
66
|
debugShotsDir?: string;
|
|
65
67
|
webBotAuth?: WebBotAuthConfig | null;
|
|
68
|
+
/**
|
|
69
|
+
* Process-internal UCP provenance. Public callers may relay only handoffId;
|
|
70
|
+
* this origin set is recovered by the CLI from its single-use registry.
|
|
71
|
+
*/
|
|
72
|
+
trustedMerchantIdentity?: Readonly<{
|
|
73
|
+
handoffId: string;
|
|
74
|
+
checkoutId: string;
|
|
75
|
+
allowedOrigins: readonly string[];
|
|
76
|
+
expiresAt: string;
|
|
77
|
+
}>;
|
|
66
78
|
};
|
|
67
79
|
export type RunCheckoutOptions = PrepareCheckoutOptions & {
|
|
68
80
|
instrument: Instrument;
|
|
@@ -75,6 +87,8 @@ export type CheckoutReview = {
|
|
|
75
87
|
id: string;
|
|
76
88
|
url: string;
|
|
77
89
|
merchantHost: string;
|
|
90
|
+
/** Exact origin frozen for a trusted UCP checkout; omitted for generic flows. */
|
|
91
|
+
merchantOrigin?: string;
|
|
78
92
|
amountMinor: number;
|
|
79
93
|
currency: string;
|
|
80
94
|
mandateMaxAmountMinor: number;
|
|
@@ -82,6 +96,7 @@ export type CheckoutReview = {
|
|
|
82
96
|
submitTarget: string | null;
|
|
83
97
|
submitTargetFingerprint: Readonly<CheckoutSubmitTargetFingerprint> | null;
|
|
84
98
|
detectedRoles: readonly string[];
|
|
99
|
+
checkoutRoute: CheckoutRoute;
|
|
85
100
|
};
|
|
86
101
|
export type CheckoutSubmitTargetFingerprint = {
|
|
87
102
|
kind: 'submit-control' | 'text-button';
|
|
@@ -99,6 +114,7 @@ export type PreparedCheckout = {
|
|
|
99
114
|
readonly fields: Readonly<FieldMap>;
|
|
100
115
|
readonly evidence: EvidenceLog;
|
|
101
116
|
readonly requiresAdapter: readonly string[];
|
|
117
|
+
readonly checkoutRoute: CheckoutRoute;
|
|
102
118
|
};
|
|
103
119
|
export type PrepareCheckoutResult = {
|
|
104
120
|
status: 'ready';
|
|
@@ -174,6 +190,7 @@ export declare class InMemoryPreparedCheckoutStore implements PreparedCheckoutSe
|
|
|
174
190
|
private scheduleReaper;
|
|
175
191
|
private closeState;
|
|
176
192
|
}
|
|
193
|
+
export declare function trustedMerchantOriginRefusal(options: PrepareCheckoutOptions, pageUrl: string, expectedOrigin?: string): string | null;
|
|
177
194
|
export declare function reconcileHeldOutcome(original: ObservedOutcome, held: ObservedOutcome): ObservedOutcome;
|
|
178
195
|
export declare function debugShotMaskPlan(fields: FieldMap): {
|
|
179
196
|
skipReason: string | null;
|