@visa/cli 4.1.0-rc.13 → 4.1.0-rc.131
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 +188 -232
- package/dist/checkout-engine/adapters/generic.d.ts +4 -0
- package/dist/checkout-engine/adapters/generic.js +28 -13
- package/dist/checkout-engine/adapters/index.d.ts +4 -1
- package/dist/checkout-engine/adapters/index.js +10 -3
- package/dist/checkout-engine/adapters/shopify.d.ts +31 -0
- package/dist/checkout-engine/adapters/shopify.js +423 -0
- package/dist/checkout-engine/amount.d.ts +15 -0
- package/dist/checkout-engine/amount.js +72 -0
- package/dist/checkout-engine/cli-engine.d.ts +207 -2
- package/dist/checkout-engine/cli-engine.js +677 -27
- package/dist/checkout-engine/detect.d.ts +1 -1
- package/dist/checkout-engine/detect.js +26 -0
- package/dist/checkout-engine/evidence.d.ts +4 -1
- package/dist/checkout-engine/evidence.js +51 -6
- package/dist/checkout-engine/executor.d.ts +34 -4
- package/dist/checkout-engine/executor.js +266 -115
- package/dist/checkout-engine/hosted-approval.d.ts +133 -8
- package/dist/checkout-engine/hosted-approval.js +400 -49
- package/dist/checkout-engine/index.d.ts +4 -1
- package/dist/checkout-engine/index.js +3 -0
- package/dist/checkout-engine/instrument.d.ts +7 -0
- package/dist/checkout-engine/instrument.js +4 -0
- package/dist/checkout-engine/live-fill-approval.d.ts +0 -20
- package/dist/checkout-engine/live-fill-approval.js +15 -51
- package/dist/checkout-engine/mandate/card-mandate.d.ts +121 -0
- package/dist/checkout-engine/mandate/card-mandate.js +227 -0
- package/dist/checkout-engine/mandate/mandate-ledger.d.ts +165 -0
- package/dist/checkout-engine/mandate/mandate-ledger.js +373 -0
- package/dist/checkout-engine/outcome.d.ts +2 -2
- package/dist/checkout-engine/outcome.js +36 -1
- package/dist/checkout-engine/owner-only-file.d.ts +9 -0
- package/dist/checkout-engine/owner-only-file.js +20 -1
- package/dist/checkout-engine/trace-handles.d.ts +8 -0
- package/dist/checkout-engine/trace-handles.js +12 -0
- package/dist/checkout-engine/types.d.ts +20 -2
- package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +82 -0
- package/dist/checkout-engine/vgs-gateway/server-mint-client.js +180 -0
- package/dist/checkout-engine/vgs-live-instrument.d.ts +38 -0
- package/dist/checkout-engine/vgs-live-instrument.js +52 -8
- package/dist/checkout-engine/vic-confirmation.js +2 -2
- package/dist/cli.js +580 -494
- package/dist/mcp-server/index.js +442 -176
- package/dist/skills/pair-visa-agent/RUNTIMES.md +92 -0
- package/dist/skills/pair-visa-agent/SKILL.md +467 -0
- package/dist/skills/pair-visa-agent/scripts/setup.mjs +48 -0
- package/install.ps1 +3 -41
- package/install.sh +4 -36
- package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
- package/package.json +16 -12
- package/server.json +3 -3
- package/dist/checkout-engine/inline-target.d.ts +0 -13
- package/dist/checkout-engine/inline-target.js +0 -37
- package/dist/checkout-engine/pay-args.d.ts +0 -14
- package/dist/checkout-engine/pay-args.js +0 -44
- package/dist/checkout-engine/pay.d.ts +0 -1
- package/dist/checkout-engine/pay.js +0 -13
- package/dist/checkout-engine/repo-env.d.ts +0 -11
- package/dist/checkout-engine/repo-env.js +0 -23
- package/dist/checkout-engine/run-live-fill.d.ts +0 -1
- package/dist/checkout-engine/run-live-fill.js +0 -443
- package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +0 -74
- package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +0 -240
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Page } from 'playwright-core';
|
|
2
2
|
export type FieldSource = 'autocomplete' | 'attr-heuristic' | 'label-text' | 'iframe-psp' | 'type-inference';
|
|
3
|
-
export type FieldRole = 'number' | 'expMonth' | 'expYear' | 'expCombined' | 'cvc' | 'name' | 'nameFirst' | 'nameLast' | 'email' | 'addressLine1' | 'addressLine2' | 'city' | 'state' | 'postalCode' | 'country';
|
|
3
|
+
export type FieldRole = 'number' | 'expMonth' | 'expYear' | 'expCombined' | 'cvc' | 'name' | 'nameFirst' | 'nameLast' | 'email' | 'phone' | 'oneTimeCode' | 'addressLine1' | 'addressLine2' | 'city' | 'state' | 'postalCode' | 'country';
|
|
4
4
|
export declare const ALL_ROLES: FieldRole[];
|
|
5
5
|
export type SelectOption = {
|
|
6
6
|
value: string;
|
|
@@ -21,6 +21,8 @@ export const ALL_ROLES = [
|
|
|
21
21
|
'nameFirst',
|
|
22
22
|
'nameLast',
|
|
23
23
|
'email',
|
|
24
|
+
'phone',
|
|
25
|
+
'oneTimeCode',
|
|
24
26
|
'addressLine1',
|
|
25
27
|
'addressLine2',
|
|
26
28
|
'city',
|
|
@@ -37,6 +39,18 @@ const expMonthRe = /(exp.?month|expmonth|exp_mm|(^|[^a-z])mm([^a-z]|$)|(^|[^a-z]
|
|
|
37
39
|
const expYearRe = /(exp.?year|expyear|exp_yy|(^|[^a-z])yy(yy)?([^a-z]|$)|(^|[^a-z])year([^a-z]|$)|jahr|annee|(^|[^a-z])ano([^a-z]|$))/;
|
|
38
40
|
const expCombinedRe = /(exp(iry|iration)?(.?date)?|mm.?\/.?yy|mm.?jj|valid.?thru|ablauf|caducidad)/;
|
|
39
41
|
const emailRe = /(e-?mail|correo|courriel)/;
|
|
42
|
+
const phoneRe = /(phone|telephone|mobile|(^|[^a-z])tel([^a-z]|$)|telefono|téléphone)/;
|
|
43
|
+
// One-time verification code (merchant email/SMS OTP). Ordered AFTER cvcRe in
|
|
44
|
+
// attrClassify so a card CVC ("security code"/"card code") still wins — this
|
|
45
|
+
// regex deliberately omits those card phrasings. It also OMITS a bare "code":
|
|
46
|
+
// `postal_code`/`zip_code`/`country_code`/`promo_code` all contain "code" and
|
|
47
|
+
// would otherwise misclassify as an OTP field and starve the address fill. A
|
|
48
|
+
// bare "code" is matched LAST (bareCodeRe), after every address role.
|
|
49
|
+
const oneTimeCodeRe = /(one[-_ ]?time|(^|[^a-z])otp([^a-z]|$)|passcode|(^|[^a-z])pin([^a-z]|$)|verif(y|ication)?.?code|auth.?code)/;
|
|
50
|
+
// Standalone verification "code" — a last-resort OTP match tried only after the
|
|
51
|
+
// specific address roles (postal/country/state/city/line) have had their say,
|
|
52
|
+
// so a compound `*_code` address/product field is never stolen by the OTP role.
|
|
53
|
+
const bareCodeRe = /(^|[^a-z])code([^a-z]|$)/;
|
|
40
54
|
const postalRe = /(zip|postal|postcode|(^|[^a-z])plz([^a-z]|$)|(^|[^a-z])cep([^a-z]|$)|codigo.?postal)/;
|
|
41
55
|
const countryRe = /(country|(^|[^a-z])land([^a-z]|$)|(^|[^a-z])pais)/;
|
|
42
56
|
const stateRe = /((^|[^a-z])state([^a-z]|$)|province|region|bundesland|provincia)/;
|
|
@@ -55,6 +69,9 @@ const AUTOCOMPLETE_MAP = {
|
|
|
55
69
|
'cc-name': 'name',
|
|
56
70
|
name: 'name',
|
|
57
71
|
email: 'email',
|
|
72
|
+
tel: 'phone',
|
|
73
|
+
'tel-national': 'phone',
|
|
74
|
+
'one-time-code': 'oneTimeCode',
|
|
58
75
|
'given-name': 'nameFirst',
|
|
59
76
|
'family-name': 'nameLast',
|
|
60
77
|
'address-line1': 'addressLine1',
|
|
@@ -151,6 +168,11 @@ function attrClassify(text, m) {
|
|
|
151
168
|
return 'expCombined';
|
|
152
169
|
if (emailRe.test(text))
|
|
153
170
|
return 'email';
|
|
171
|
+
// OTP comes after cvc (checked above) so card CVC still classifies as 'cvc'.
|
|
172
|
+
if (oneTimeCodeRe.test(text))
|
|
173
|
+
return 'oneTimeCode';
|
|
174
|
+
if (phoneRe.test(text))
|
|
175
|
+
return 'phone';
|
|
154
176
|
if (postalRe.test(text))
|
|
155
177
|
return 'postalCode';
|
|
156
178
|
if (countryRe.test(text))
|
|
@@ -163,6 +185,10 @@ function attrClassify(text, m) {
|
|
|
163
185
|
return 'addressLine2';
|
|
164
186
|
if (addr1Re.test(text))
|
|
165
187
|
return 'addressLine1';
|
|
188
|
+
// Last-resort bare "code" → OTP, only once every address role has been ruled
|
|
189
|
+
// out, so `postal_code`/`country_code`/etc. keep their own role above.
|
|
190
|
+
if (bareCodeRe.test(text))
|
|
191
|
+
return 'oneTimeCode';
|
|
166
192
|
if (nameFirstRe.test(text))
|
|
167
193
|
return 'nameFirst';
|
|
168
194
|
if (nameLastRe.test(text))
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export type EvidenceStepType = 'navigation' | 'dom-stable' | 'review' | 'approval' | 'adapter-selected' | 'reveal' | 'detect' | 'field-fill' | 'amount-fill' | 'credential-minted' | 'fill-complete' | 'psp-detected' | 'mandate-verdict' | 'submit' | 'challenge-hold' | 'outcome' | 'note';
|
|
1
|
+
export type EvidenceStepType = 'navigation' | 'dom-stable' | 'review' | 'approval' | 'adapter-selected' | 'reveal' | 'detect' | 'field-fill' | 'amount-fill' | 'contact-prefill' | 'credential-minted' | 'credential-skipped' | 'credential-expiry-check' | 'fill-complete' | 'psp-detected' | 'mandate-verdict' | 'submit' | 'challenge-hold' | 'outcome' | 'note';
|
|
2
2
|
export type EvidenceStep = {
|
|
3
3
|
ts: string;
|
|
4
4
|
type: EvidenceStepType;
|
|
@@ -7,6 +7,9 @@ export type EvidenceStep = {
|
|
|
7
7
|
export declare function maskPan(pan: string): string;
|
|
8
8
|
export declare function maskCvc(_cvc: string): string;
|
|
9
9
|
export declare function maskExpiry(): string;
|
|
10
|
+
export declare function maskOtp(): string;
|
|
11
|
+
export declare function hostOf(hostOrEmail: string): string;
|
|
12
|
+
export declare function verificationLinkAllowed(link: string, messageFrom: string): boolean;
|
|
10
13
|
export declare function redactContact(role: string, value: string): string;
|
|
11
14
|
export declare class EvidenceLog {
|
|
12
15
|
private steps;
|
|
@@ -3,12 +3,15 @@
|
|
|
3
3
|
// the mandate verdict, the submit, and the final outcome, plus a compact
|
|
4
4
|
// accessibility snapshot of the end state.
|
|
5
5
|
//
|
|
6
|
-
// Money-adjacent hygiene rule: the full PAN, the cvc,
|
|
7
|
-
// NEVER enter the log. PANs are masked to last-4;
|
|
8
|
-
// entirely. maskPan/maskCvc/maskExpiry
|
|
9
|
-
//
|
|
10
|
-
//
|
|
11
|
-
//
|
|
6
|
+
// Money-adjacent hygiene rule: the full PAN, the cvc, the card expiry, and any
|
|
7
|
+
// one-time verification code NEVER enter the log. PANs are masked to last-4;
|
|
8
|
+
// cvc, expiry, and OTP codes are redacted entirely. maskPan/maskCvc/maskExpiry/
|
|
9
|
+
// maskOtp are the only sanctioned way to put such a value anywhere near a log
|
|
10
|
+
// line. (receipt.ts redactPanLikeDigits only catches 12-19-digit PANs, so a
|
|
11
|
+
// 4-8-digit OTP would slip straight through — maskOtp at the fill site is the
|
|
12
|
+
// load-bearing guarantee it never reaches the evidence log.) Contact PII
|
|
13
|
+
// (cardholder name, email, address, ...) is redacted via redactContact: the
|
|
14
|
+
// log records THAT a field was filled, never the personal value.
|
|
12
15
|
// Mask a PAN to its last four digits. Non-digits are stripped for the count.
|
|
13
16
|
export function maskPan(pan) {
|
|
14
17
|
const digits = (pan || '').replace(/\D/g, '');
|
|
@@ -25,6 +28,48 @@ export function maskCvc(_cvc) {
|
|
|
25
28
|
export function maskExpiry() {
|
|
26
29
|
return 'redacted';
|
|
27
30
|
}
|
|
31
|
+
// A merchant one-time verification code is single-use secret material — it
|
|
32
|
+
// never enters the log, in any format. Used at the oneTimeCode fill site so
|
|
33
|
+
// the code is masked exactly as cvc/PAN are (see the header note above).
|
|
34
|
+
export function maskOtp() {
|
|
35
|
+
return 'redacted';
|
|
36
|
+
}
|
|
37
|
+
// The registrable domain (approx eTLD+1) of an email address or hostname, for
|
|
38
|
+
// the caller-side from-domain guard on verification links: before navigating a
|
|
39
|
+
// link parsed from an untrusted OTP email, require the link host to match the
|
|
40
|
+
// message sender's domain. extract.ts guarantees mechanical extraction, NOT
|
|
41
|
+
// navigation safety — this guard is the anti-injection backstop.
|
|
42
|
+
export function hostOf(hostOrEmail) {
|
|
43
|
+
return (hostOrEmail.split('@').pop() ?? '').trim().toLowerCase().replace(/\.$/, '');
|
|
44
|
+
}
|
|
45
|
+
// Strict bidirectional host-suffix match (exact / child / parent). Deliberately
|
|
46
|
+
// NOT approximate eTLD+1 — that collapses shared-tenant hosts (`x.myshopify.com`,
|
|
47
|
+
// `x.co.uk`) to a common suffix and lets a sibling tenant match; siblings never
|
|
48
|
+
// match here. Fails SAFE: an unrelated-but-legitimate sender is skipped, not
|
|
49
|
+
// trusted.
|
|
50
|
+
function hostsRelated(a, b) {
|
|
51
|
+
const x = hostOf(a);
|
|
52
|
+
const y = hostOf(b);
|
|
53
|
+
if (!x || !y)
|
|
54
|
+
return false;
|
|
55
|
+
return x === y || x.endsWith('.' + y) || y.endsWith('.' + x);
|
|
56
|
+
}
|
|
57
|
+
// Whether a verification LINK is safe to navigate: the sender must NOT be the
|
|
58
|
+
// unauthenticated inbound variant (AgentMail marks spoofable inbound with an
|
|
59
|
+
// `.unauthenticated` sub-label), and the link host must belong to the sender's
|
|
60
|
+
// domain. Returns false to SKIP a link that fails either check.
|
|
61
|
+
export function verificationLinkAllowed(link, messageFrom) {
|
|
62
|
+
if (/\.unauthenticated\b/i.test(messageFrom))
|
|
63
|
+
return false;
|
|
64
|
+
let linkHost;
|
|
65
|
+
try {
|
|
66
|
+
linkHost = new URL(link).hostname;
|
|
67
|
+
}
|
|
68
|
+
catch {
|
|
69
|
+
return false;
|
|
70
|
+
}
|
|
71
|
+
return hostsRelated(linkHost, messageFrom);
|
|
72
|
+
}
|
|
28
73
|
// Contact values (cardholder name, email, address, ...) are PII and never
|
|
29
74
|
// enter the log raw either. Email keeps its domain for debuggability;
|
|
30
75
|
// everything else becomes a bare presence marker.
|
|
@@ -2,11 +2,14 @@ import type { Browser, BrowserContext, Page } from 'playwright-core';
|
|
|
2
2
|
import { type FieldMap } from './detect.js';
|
|
3
3
|
import { type Mandate } from './mandate.js';
|
|
4
4
|
import type { Instrument } from './instrument.js';
|
|
5
|
-
import type { Contact } from './types.js';
|
|
5
|
+
import type { Contact, OtpResolver } from './types.js';
|
|
6
6
|
import { EvidenceLog } from './evidence.js';
|
|
7
7
|
import { type ObservedOutcome } from './outcome.js';
|
|
8
|
+
export { minorFromDecimal, pageCurrency } from './amount.js';
|
|
8
9
|
export type CheckoutMode = 'dry-run' | 'submit';
|
|
9
|
-
export type CheckoutOutcome = '
|
|
10
|
+
export type CheckoutOutcome = 'reviewed-dry-run'
|
|
11
|
+
/** Historical receipt value from the credential-disclosing dry-run. */
|
|
12
|
+
| 'filled-dry-run' | 'partial-fill' | 'adapter-required' | 'confirmed' | 'declined' | 'action-required' | 'cancelled' | 'blocked-by-mandate' | 'failed';
|
|
10
13
|
export type CredentialLifecycle = 'not-requested' | 'minted-not-exposed' | 'partially-exposed' | 'fully-filled';
|
|
11
14
|
export type CredentialTiming = {
|
|
12
15
|
approvedAt?: string;
|
|
@@ -28,6 +31,7 @@ export type PrepareCheckoutOptions = {
|
|
|
28
31
|
url: string;
|
|
29
32
|
mandate: Mandate;
|
|
30
33
|
browser: Browser;
|
|
34
|
+
contact?: Contact;
|
|
31
35
|
amountMinor?: number;
|
|
32
36
|
currency?: string;
|
|
33
37
|
debugShotsDir?: string;
|
|
@@ -37,6 +41,7 @@ export type RunCheckoutOptions = PrepareCheckoutOptions & {
|
|
|
37
41
|
contact: Contact;
|
|
38
42
|
mode: CheckoutMode;
|
|
39
43
|
outcomeDeadlineMs?: number;
|
|
44
|
+
resolveEmailOtp?: OtpResolver;
|
|
40
45
|
};
|
|
41
46
|
export type CheckoutReview = {
|
|
42
47
|
id: string;
|
|
@@ -86,9 +91,9 @@ export type SubmitApprovedCheckoutOptions = {
|
|
|
86
91
|
outcomeDeadlineMs?: number;
|
|
87
92
|
challengeHoldMs?: number;
|
|
88
93
|
onChallengeHold?: (signal: string | null) => void;
|
|
94
|
+
resolveEmailOtp?: OtpResolver;
|
|
89
95
|
};
|
|
90
|
-
export declare function
|
|
91
|
-
export declare function pageCurrency(text: string): string | null;
|
|
96
|
+
export declare function snapshotOrigin(rawUrl: string): string;
|
|
92
97
|
export type PreparedCheckoutSession = {
|
|
93
98
|
checkout: PreparedCheckout;
|
|
94
99
|
context: BrowserContext;
|
|
@@ -141,6 +146,31 @@ export declare function debugShotMaskPlan(fields: FieldMap): {
|
|
|
141
146
|
}[];
|
|
142
147
|
};
|
|
143
148
|
export declare function isStripeLinkConsumerRequest(url: string): boolean;
|
|
149
|
+
export declare function suppressStripeLink(page: Page, evidence: EvidenceLog): Promise<void>;
|
|
150
|
+
export type LinkQuietResult = {
|
|
151
|
+
fired: boolean;
|
|
152
|
+
waitedMs: number;
|
|
153
|
+
};
|
|
154
|
+
/**
|
|
155
|
+
* Wait for the suppressed Stripe Link lookup to fire and settle BEFORE the
|
|
156
|
+
* submit click. Stripe debounces its consumer-session lookup ~300ms after the
|
|
157
|
+
* email input changes; our fill→click gap is single-digit ms, so the (aborted)
|
|
158
|
+
* lookup used to land INSIDE Stripe's in-flight submit chain and kill it
|
|
159
|
+
* silently — the click looked accepted but tokenization never ran and the page
|
|
160
|
+
* sat on the form until the outcome deadline (#5879: three identical live
|
|
161
|
+
* stalls at donate.stripe.com). Verified live A/B on that page: instant click →
|
|
162
|
+
* dead submit, no /v1/payment_methods; lookup settled first → tokenization and
|
|
163
|
+
* the confirm step both reached.
|
|
164
|
+
*
|
|
165
|
+
* If the lookup already fired, only the short settle applies (lets Stripe's
|
|
166
|
+
* abort handling unwind). If it never fires — non-Link page variants, no email
|
|
167
|
+
* field — the bound expires and the click proceeds as before.
|
|
168
|
+
*/
|
|
169
|
+
export declare function waitForLinkLookupQuiet(page: Page, opts?: {
|
|
170
|
+
boundMs?: number;
|
|
171
|
+
settleMs?: number;
|
|
172
|
+
delay?: (ms: number) => Promise<void>;
|
|
173
|
+
}): Promise<LinkQuietResult>;
|
|
144
174
|
export declare function prepareCheckout(opts: PrepareCheckoutOptions, store?: PreparedCheckoutSessionStore): Promise<PrepareCheckoutResult>;
|
|
145
175
|
export declare function submitApprovedCheckout(reviewId: string, opts: SubmitApprovedCheckoutOptions, store?: PreparedCheckoutSessionStore): Promise<CheckoutResult>;
|
|
146
176
|
export declare function cancelPreparedCheckout(reviewId: string, detail?: string, store?: PreparedCheckoutSessionStore): Promise<CheckoutResult>;
|