@visa/cli 4.1.0-rc.31 → 4.1.0-rc.311
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 +284 -46
- package/dist/cli.js +618 -338
- package/dist/managed-runtime/resolve-and-update.mjs +268 -0
- package/dist/managed-runtime/runtime-readiness.mjs +126 -0
- package/dist/managed-runtime/update-and-restart.mjs +1079 -0
- package/dist/mcp-apps/ucp-checkout.html +280 -0
- package/dist/mcp-server/index.js +513 -258
- package/dist/merchant-ucp-mcp/index.js +7 -0
- package/dist/skills/pair-visa-agent/RUNTIMES.md +122 -79
- package/dist/skills/pair-visa-agent/SKILL.md +390 -333
- 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/dist/skills/visa-shopify-checkout/SKILL.md +122 -0
- package/dist/skills/visa-shopify-checkout/references/evidence-and-states.md +37 -0
- package/dist/skills/visa-ucp-shopping/SKILL.md +86 -0
- package/dist/subway-direct.mjs +1 -0
- package/install.ps1 +7 -6
- package/install.sh +3 -3
- package/native/bin/darwin-arm64/visa-runtime-signer +0 -0
- package/native/bin/darwin-x64/visa-runtime-signer +0 -0
- package/native/bin/linux-arm64/visa-runtime-signer +0 -0
- package/native/bin/linux-x64/visa-runtime-signer +0 -0
- package/native/bin/win32-arm64/visa-runtime-signer.exe +0 -0
- package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
- package/native/bin/win32-x64/visa-runtime-signer.exe +0 -0
- package/package.json +32 -30
- package/server.json +4 -4
- package/dist/checkout-engine/adapters/generic.d.ts +0 -19
- package/dist/checkout-engine/adapters/generic.js +0 -201
- package/dist/checkout-engine/adapters/index.d.ts +0 -7
- package/dist/checkout-engine/adapters/index.js +0 -17
- package/dist/checkout-engine/adapters/stripe-like.d.ts +0 -10
- package/dist/checkout-engine/adapters/stripe-like.js +0 -21
- package/dist/checkout-engine/browser-launch.d.ts +0 -46
- package/dist/checkout-engine/browser-launch.js +0 -81
- package/dist/checkout-engine/ceremony.d.ts +0 -64
- package/dist/checkout-engine/ceremony.js +0 -261
- package/dist/checkout-engine/cli-engine.d.ts +0 -208
- package/dist/checkout-engine/cli-engine.js +0 -584
- package/dist/checkout-engine/detect.d.ts +0 -61
- package/dist/checkout-engine/detect.js +0 -392
- package/dist/checkout-engine/evidence.d.ts +0 -25
- package/dist/checkout-engine/evidence.js +0 -104
- package/dist/checkout-engine/executor.d.ts +0 -174
- package/dist/checkout-engine/executor.js +0 -1306
- package/dist/checkout-engine/hosted-approval.d.ts +0 -135
- package/dist/checkout-engine/hosted-approval.js +0 -311
- package/dist/checkout-engine/index.d.ts +0 -6
- package/dist/checkout-engine/index.js +0 -8
- package/dist/checkout-engine/inline-target.d.ts +0 -13
- package/dist/checkout-engine/inline-target.js +0 -37
- package/dist/checkout-engine/instrument.d.ts +0 -55
- package/dist/checkout-engine/instrument.js +0 -87
- package/dist/checkout-engine/live-fill-approval.d.ts +0 -43
- package/dist/checkout-engine/live-fill-approval.js +0 -90
- package/dist/checkout-engine/mandate/card-mandate.d.ts +0 -117
- package/dist/checkout-engine/mandate/card-mandate.js +0 -221
- package/dist/checkout-engine/mandate/mandate-ledger.d.ts +0 -135
- package/dist/checkout-engine/mandate/mandate-ledger.js +0 -318
- package/dist/checkout-engine/mandate.d.ts +0 -25
- package/dist/checkout-engine/mandate.js +0 -100
- package/dist/checkout-engine/outcome.d.ts +0 -30
- package/dist/checkout-engine/outcome.js +0 -225
- package/dist/checkout-engine/owner-only-file.d.ts +0 -19
- package/dist/checkout-engine/owner-only-file.js +0 -41
- package/dist/checkout-engine/package.json +0 -3
- 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/receipt.d.ts +0 -81
- package/dist/checkout-engine/receipt.js +0 -109
- 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 -493
- package/dist/checkout-engine/types.d.ts +0 -39
- package/dist/checkout-engine/types.js +0 -2
- package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +0 -74
- package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +0 -248
- package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +0 -82
- package/dist/checkout-engine/vgs-gateway/server-mint-client.js +0 -178
- package/dist/checkout-engine/vgs-live-instrument.d.ts +0 -168
- package/dist/checkout-engine/vgs-live-instrument.js +0 -289
- package/dist/checkout-engine/vic-confirmation.d.ts +0 -34
- package/dist/checkout-engine/vic-confirmation.js +0 -39
package/install.ps1
CHANGED
|
@@ -108,8 +108,8 @@ try {
|
|
|
108
108
|
}
|
|
109
109
|
|
|
110
110
|
$major = [int]$nodeMatch.Groups['major'].Value
|
|
111
|
-
if ($major -lt
|
|
112
|
-
Write-Host " Node.js $nodeVersion is too old. Version
|
|
111
|
+
if ($major -lt 20) {
|
|
112
|
+
Write-Host " Node.js $nodeVersion is too old. Version 20+ required." -ForegroundColor Red
|
|
113
113
|
Write-Host " Install from https://nodejs.org/ and try again." -ForegroundColor Yellow
|
|
114
114
|
Wait-BeforeExit
|
|
115
115
|
exit 1
|
|
@@ -137,8 +137,9 @@ try {
|
|
|
137
137
|
}
|
|
138
138
|
|
|
139
139
|
# Install via npm
|
|
140
|
-
|
|
141
|
-
|
|
140
|
+
$Package = '@visa/cli@rc'
|
|
141
|
+
Write-Host " Running: npm install -g $Package" -ForegroundColor Gray
|
|
142
|
+
$npmOutput = & npm install -g $Package 2>&1
|
|
142
143
|
$npmExitCode = $LASTEXITCODE
|
|
143
144
|
$npmText = ($npmOutput | Out-String)
|
|
144
145
|
$npmOutput | Out-Host
|
|
@@ -158,7 +159,7 @@ if ($npmExitCode -ne 0) {
|
|
|
158
159
|
Write-Host " Do not disable TLS verification globally." -ForegroundColor Yellow
|
|
159
160
|
} else {
|
|
160
161
|
Write-Host " Run manually for the full error, then retry:" -ForegroundColor Yellow
|
|
161
|
-
Write-Host " npm install -g
|
|
162
|
+
Write-Host " npm install -g $Package" -ForegroundColor Yellow
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
Wait-BeforeExit
|
|
@@ -188,7 +189,7 @@ Write-Host ""
|
|
|
188
189
|
if ($verifiedCommand) {
|
|
189
190
|
Write-Host " Visa CLI $visaVersion installed." -ForegroundColor Green
|
|
190
191
|
Write-Host " Connect an AI client with: $verifiedCommand connect <client>" -ForegroundColor Cyan
|
|
191
|
-
Write-Host " Then ask your agent to call
|
|
192
|
+
Write-Host " Then ask your agent to call setup_start." -ForegroundColor Cyan
|
|
192
193
|
Write-Host ""
|
|
193
194
|
} else {
|
|
194
195
|
$primaryCommand = $cliCommandNames | Select-Object -First 1
|
package/install.sh
CHANGED
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
_visa_install() {
|
|
11
11
|
set -euo pipefail
|
|
12
12
|
|
|
13
|
-
REQUIRED_NODE_MAJOR=
|
|
14
|
-
PACKAGE="@visa/cli"
|
|
13
|
+
REQUIRED_NODE_MAJOR=20
|
|
14
|
+
PACKAGE="@visa/cli@rc"
|
|
15
15
|
|
|
16
16
|
# ── colours (disabled when piped) ─────────────────────────────────────────────
|
|
17
17
|
if [ -t 1 ]; then
|
|
@@ -109,7 +109,7 @@ echo ""
|
|
|
109
109
|
if [ -n "$VISA_VERSION" ]; then
|
|
110
110
|
ok "Visa CLI ${VISA_VERSION} installed."
|
|
111
111
|
info "Connect an AI client with: visa-cli connect <client>"
|
|
112
|
-
info "Then ask your agent to call
|
|
112
|
+
info "Then ask your agent to call setup_start."
|
|
113
113
|
else
|
|
114
114
|
warn "Installed, but 'visa-cli' was not found on PATH."
|
|
115
115
|
warn "Restart your shell, then run: visa-cli connect <client>"
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@visa/cli",
|
|
3
|
-
"version": "4.1.0-rc.
|
|
4
|
-
"description": "
|
|
3
|
+
"version": "4.1.0-rc.311",
|
|
4
|
+
"description": "Visa CLI runtime for stable agent identity and separately authorized payment capabilities",
|
|
5
5
|
"bin": {
|
|
6
6
|
"visa-cli": "./bin/visa-cli.js",
|
|
7
|
-
"visa": "./bin/visa-cli.js"
|
|
7
|
+
"visa": "./bin/visa-cli.js",
|
|
8
|
+
"visa-runtime-converge": "./dist/managed-runtime/resolve-and-update.mjs"
|
|
8
9
|
},
|
|
9
10
|
"scripts": {
|
|
10
11
|
"sync:server-json": "node scripts/sync-server-json.mjs",
|
|
11
12
|
"check:server-json": "node scripts/sync-server-json.mjs --check",
|
|
12
|
-
"prebuild": "node scripts/sync-server-json.mjs && pnpm --filter @visa/money build && pnpm --filter @visa/crypto build && pnpm --filter subway-sdk build && pnpm --filter @visa-cli/tools build && pnpm --filter @visa/identity build && pnpm --filter @visa/wallet build && pnpm --filter @visa/agent-mail build && pnpm --filter @visa/wallet-tools build
|
|
13
|
+
"prebuild": "node scripts/sync-server-json.mjs && pnpm --filter @visa/shared build && pnpm --filter @visa/observability build && pnpm --filter @visa/money build && pnpm --filter @visa/crypto build && pnpm --filter subway-sdk build && pnpm --filter @visa-cli/tools build && pnpm --filter @visa/identity build && pnpm --filter @visa/wallet build && pnpm --filter @visa/agent-mail build && pnpm --filter @visa/wallet-tools build",
|
|
13
14
|
"build": "tsc --noEmit && node esbuild.config.js",
|
|
14
15
|
"prepack": "node scripts/sync-server-json.mjs --check",
|
|
15
16
|
"dev": "tsc --watch",
|
|
@@ -20,9 +21,9 @@
|
|
|
20
21
|
"test:unit:watch": "jest --config jest.config.js --watch",
|
|
21
22
|
"test:unit:coverage": "jest --config jest.config.js --coverage",
|
|
22
23
|
"test:smoke": "VISA_AUTH_URL=https://auth.visacli.sh jest --config jest.smoke.config.js",
|
|
24
|
+
"test:packed": "node scripts/packed-cli-smoke.mjs",
|
|
23
25
|
"test:integration": "jest --config jest.integration.config.js",
|
|
24
26
|
"test:e2e": "jest --config jest.e2e.config.js",
|
|
25
|
-
"test:catalog-e2e": "jest --config jest.catalog-e2e.config.js",
|
|
26
27
|
"test:all": "npm run test:unit && npm run test:integration && npm run test:e2e",
|
|
27
28
|
"prepublishOnly": "npm run build && npm test",
|
|
28
29
|
"lint": "eslint src/**/*.ts",
|
|
@@ -42,49 +43,50 @@
|
|
|
42
43
|
"author": "Visa Crypto Labs",
|
|
43
44
|
"license": "SEE LICENSE IN LICENSE",
|
|
44
45
|
"dependencies": {
|
|
45
|
-
"@modelcontextprotocol/
|
|
46
|
-
"@
|
|
46
|
+
"@modelcontextprotocol/ext-apps": "1.7.5",
|
|
47
|
+
"@modelcontextprotocol/server": "2.0.0",
|
|
48
|
+
"@ucp-js/sdk": "0.4.4",
|
|
47
49
|
"commander": "^12.1.0",
|
|
48
|
-
"
|
|
50
|
+
"punycode": "^2.3.1",
|
|
51
|
+
"toml": "^4.3.0",
|
|
52
|
+
"yaml": "^2.9.0",
|
|
53
|
+
"zod": "^3.25.76"
|
|
49
54
|
},
|
|
50
55
|
"optionalDependencies": {
|
|
51
|
-
"libp2p": "^3.3.4",
|
|
52
|
-
"@libp2p/tcp": "^11.0.22",
|
|
53
|
-
"@libp2p/crypto": "^5.1.20",
|
|
54
56
|
"@chainsafe/libp2p-noise": "^17.0.0",
|
|
55
57
|
"@chainsafe/libp2p-yamux": "^8.0.1",
|
|
58
|
+
"@libp2p/crypto": "^5.1.23",
|
|
59
|
+
"@libp2p/tcp": "^11.0.26",
|
|
56
60
|
"@multiformats/multiaddr": "^13.0.3",
|
|
57
|
-
"
|
|
58
|
-
"
|
|
61
|
+
"libp2p": "^3.3.9",
|
|
62
|
+
"uint8arrays": "^6.1.1"
|
|
59
63
|
},
|
|
60
64
|
"devDependencies": {
|
|
61
|
-
"@
|
|
65
|
+
"@types/express": "^5.0.0",
|
|
66
|
+
"@types/jest": "^30.0.0",
|
|
67
|
+
"@types/node": "^26.1.0",
|
|
68
|
+
"@typescript-eslint/eslint-plugin": "^8.68.0",
|
|
69
|
+
"@typescript-eslint/parser": "^8.68.0",
|
|
70
|
+
"@visa-cli/tools": "workspace:*",
|
|
71
|
+
"@visa/agent-mail": "workspace:*",
|
|
62
72
|
"@visa/crypto": "workspace:*",
|
|
63
73
|
"@visa/identity": "workspace:*",
|
|
64
|
-
"subway-sdk": "workspace:*",
|
|
65
74
|
"@visa/money": "workspace:*",
|
|
75
|
+
"@visa/observability": "workspace:*",
|
|
76
|
+
"@visa/shared": "workspace:*",
|
|
66
77
|
"@visa/wallet": "workspace:*",
|
|
67
78
|
"@visa/wallet-tools": "workspace:*",
|
|
68
|
-
"@visa/observability": "workspace:*",
|
|
69
|
-
"@visa-cli/tools": "workspace:*",
|
|
70
|
-
"@changesets/changelog-git": "^0.2.1",
|
|
71
|
-
"@changesets/cli": "^2.31.0",
|
|
72
|
-
"@types/jest": "^30.0.0",
|
|
73
|
-
"@types/node": "^26.1.0",
|
|
74
|
-
"@typescript-eslint/eslint-plugin": "^8.65.0",
|
|
75
|
-
"@typescript-eslint/parser": "^8.65.0",
|
|
76
|
-
"@types/express": "^5.0.0",
|
|
77
79
|
"esbuild": "^0.28.1",
|
|
78
|
-
"express": "^4.21.0",
|
|
79
80
|
"eslint": "^10.0.2",
|
|
80
|
-
"
|
|
81
|
+
"express": "^4.21.0",
|
|
81
82
|
"jest": "^29.7.0",
|
|
82
|
-
"prettier": "^3.9.
|
|
83
|
-
"
|
|
83
|
+
"prettier": "^3.9.6",
|
|
84
|
+
"subway-sdk": "workspace:*",
|
|
85
|
+
"ts-jest": "^29.4.12",
|
|
84
86
|
"typescript": "^5.7.0"
|
|
85
87
|
},
|
|
86
88
|
"engines": {
|
|
87
|
-
"node": ">=
|
|
89
|
+
"node": ">=20.0.0"
|
|
88
90
|
},
|
|
89
91
|
"mcpName": "io.github.visa-crypto-labs/visa-cli",
|
|
90
92
|
"files": [
|
|
@@ -98,6 +100,6 @@
|
|
|
98
100
|
"native/bin/win32-x64/visa-keychain-win.exe",
|
|
99
101
|
"server.json",
|
|
100
102
|
"README.md",
|
|
101
|
-
"
|
|
103
|
+
"native/bin/*/visa-runtime-signer*"
|
|
102
104
|
]
|
|
103
105
|
}
|
package/server.json
CHANGED
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
{
|
|
2
|
-
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-
|
|
2
|
+
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
|
|
3
3
|
"name": "io.github.visa-crypto-labs/visa-cli",
|
|
4
|
-
"version": "4.1.0-rc.
|
|
4
|
+
"version": "4.1.0-rc.311",
|
|
5
5
|
"title": "Visa CLI",
|
|
6
|
-
"description": "
|
|
6
|
+
"description": "Pair a human-approved agent identity, configure payment capabilities separately, and discover and pay x402 services from your AI coding assistant.",
|
|
7
7
|
"websiteUrl": "https://github.com/Visa-Crypto-Labs/Visa-mono/tree/main/packages/cli#readme",
|
|
8
8
|
"packages": [
|
|
9
9
|
{
|
|
10
10
|
"registryType": "npm",
|
|
11
11
|
"identifier": "@visa/cli",
|
|
12
|
-
"version": "4.1.0-rc.
|
|
12
|
+
"version": "4.1.0-rc.311",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
15
15
|
},
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
import type { Page, Locator } from 'playwright-core';
|
|
2
|
-
import type { DetectResult, FieldEntry, FieldMap } from '../detect.js';
|
|
3
|
-
import type { CardCredential } from '../instrument.js';
|
|
4
|
-
import type { Contact, FillResult, FilledField } from '../types.js';
|
|
5
|
-
export interface CheckoutAdapter {
|
|
6
|
-
name: string;
|
|
7
|
-
matches(detected: DetectResult): boolean;
|
|
8
|
-
fill(page: Page, fields: FieldMap, credential: CardCredential, contact: Contact): Promise<FillResult>;
|
|
9
|
-
}
|
|
10
|
-
export declare function resolveLocator(page: Page, entry: FieldEntry): Locator;
|
|
11
|
-
export declare function scrubFillErrorMessage(message: string, value: string): string;
|
|
12
|
-
export declare function fillFieldMap(page: Page, fields: FieldMap, credential: CardCredential, contact: Contact, opts?: {
|
|
13
|
-
fillTimeoutMs?: number;
|
|
14
|
-
}): Promise<FilledField[]>;
|
|
15
|
-
export declare class GenericAdapter implements CheckoutAdapter {
|
|
16
|
-
name: string;
|
|
17
|
-
matches(_detected: DetectResult): boolean;
|
|
18
|
-
fill(page: Page, fields: FieldMap, credential: CardCredential, contact: Contact): Promise<FillResult>;
|
|
19
|
-
}
|
|
@@ -1,201 +0,0 @@
|
|
|
1
|
-
// Generic adapter. Drives the detect.ts field map: fills text inputs and
|
|
2
|
-
// <select> dropdowns, handles split vs combined expiry, two- vs four-digit
|
|
3
|
-
// years, and split first/last name. It is the fallback that should beat any
|
|
4
|
-
// well-behaved guest checkout on its own.
|
|
5
|
-
import { maskCvc, maskExpiry, maskPan, redactContact } from '../evidence.js';
|
|
6
|
-
function pad2(n) {
|
|
7
|
-
return String(n).padStart(2, '0');
|
|
8
|
-
}
|
|
9
|
-
export function resolveLocator(page, entry) {
|
|
10
|
-
if (entry.frame)
|
|
11
|
-
return page.frameLocator(entry.frame).locator(entry.locator);
|
|
12
|
-
return page.locator(entry.locator);
|
|
13
|
-
}
|
|
14
|
-
function matchOption(options, wanted) {
|
|
15
|
-
for (const w of wanted) {
|
|
16
|
-
const lc = w.toLowerCase();
|
|
17
|
-
for (const o of options) {
|
|
18
|
-
if (o.value === w)
|
|
19
|
-
return o.value;
|
|
20
|
-
if (o.value.toLowerCase() === lc)
|
|
21
|
-
return o.value;
|
|
22
|
-
if (o.text.toLowerCase() === lc)
|
|
23
|
-
return o.value;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
// looser: contains
|
|
27
|
-
for (const w of wanted) {
|
|
28
|
-
const lc = w.toLowerCase();
|
|
29
|
-
for (const o of options)
|
|
30
|
-
if (o.text.toLowerCase().includes(lc))
|
|
31
|
-
return o.value;
|
|
32
|
-
}
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
35
|
-
const MONTH_NAMES = [
|
|
36
|
-
'january',
|
|
37
|
-
'february',
|
|
38
|
-
'march',
|
|
39
|
-
'april',
|
|
40
|
-
'may',
|
|
41
|
-
'june',
|
|
42
|
-
'july',
|
|
43
|
-
'august',
|
|
44
|
-
'september',
|
|
45
|
-
'october',
|
|
46
|
-
'november',
|
|
47
|
-
'december',
|
|
48
|
-
];
|
|
49
|
-
function monthOptionValue(options, month) {
|
|
50
|
-
return matchOption(options, [pad2(month), String(month), MONTH_NAMES[month - 1] ?? '']);
|
|
51
|
-
}
|
|
52
|
-
function yearOptionValue(options, year) {
|
|
53
|
-
return matchOption(options, [String(year), pad2(year % 100), String(year % 100)]);
|
|
54
|
-
}
|
|
55
|
-
// value chosen for a select-style role, or the typed string for inputs.
|
|
56
|
-
function expCombinedValue(entry, month, year) {
|
|
57
|
-
const mm = pad2(month);
|
|
58
|
-
const yy = pad2(year % 100);
|
|
59
|
-
const max = entry.maxlength ?? 0;
|
|
60
|
-
// A maxlength of exactly 4 means MMYY with no separator.
|
|
61
|
-
if (max === 4)
|
|
62
|
-
return `${mm}${yy}`;
|
|
63
|
-
return `${mm}/${yy}`;
|
|
64
|
-
}
|
|
65
|
-
// Playwright failure messages embed the attempted value in their call log
|
|
66
|
-
// (`- fill("4242…")`, `- selectOption("123")`), so an unscrubbed error from a
|
|
67
|
-
// FAILED fill would write the exact secret it was typing (cvc/DAVV, expiry,
|
|
68
|
-
// contact PII) into evidence — and from there into the persisted receipt.
|
|
69
|
-
// Scrub the call-log payloads and every literal occurrence of the value.
|
|
70
|
-
export function scrubFillErrorMessage(message, value) {
|
|
71
|
-
let scrubbed = message.replace(/\b(fill|selectOption)\("(?:[^"\\]|\\.)*"\)/g, '$1("<redacted>")');
|
|
72
|
-
if (value)
|
|
73
|
-
scrubbed = scrubbed.split(value).join('<redacted>');
|
|
74
|
-
return scrubbed;
|
|
75
|
-
}
|
|
76
|
-
const DEFAULT_FILL_TIMEOUT_MS = 5000;
|
|
77
|
-
async function fillOne(page, role, entry, value, displayValue, fillTimeoutMs) {
|
|
78
|
-
const base = {
|
|
79
|
-
role,
|
|
80
|
-
locator: entry.locator,
|
|
81
|
-
confidence: entry.confidence,
|
|
82
|
-
source: entry.source,
|
|
83
|
-
frame: entry.frame,
|
|
84
|
-
value: displayValue,
|
|
85
|
-
};
|
|
86
|
-
try {
|
|
87
|
-
const loc = resolveLocator(page, entry);
|
|
88
|
-
if (entry.tag === 'select') {
|
|
89
|
-
await loc.selectOption(value, { timeout: fillTimeoutMs });
|
|
90
|
-
}
|
|
91
|
-
else {
|
|
92
|
-
await loc.fill(value, { timeout: fillTimeoutMs });
|
|
93
|
-
}
|
|
94
|
-
return { ...base, ok: true };
|
|
95
|
-
}
|
|
96
|
-
catch (err) {
|
|
97
|
-
return { ...base, ok: false, error: scrubFillErrorMessage(err.message, value) };
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
// The shared fill routine used by every adapter. Takes an already-detected
|
|
101
|
-
// FieldMap so the executor controls when detection runs.
|
|
102
|
-
export async function fillFieldMap(page, fields, credential, contact, opts = {}) {
|
|
103
|
-
const fillTimeoutMs = opts.fillTimeoutMs ?? DEFAULT_FILL_TIMEOUT_MS;
|
|
104
|
-
const filled = [];
|
|
105
|
-
const first = contact.firstName ?? credential.cardholderName.split(/\s+/)[0] ?? credential.cardholderName;
|
|
106
|
-
const last = contact.lastName ?? credential.cardholderName.split(/\s+/).slice(1).join(' ') ?? '';
|
|
107
|
-
// Order matters a little: contact/name before card is harmless, but we fill
|
|
108
|
-
// card fields explicitly per role so order is not load-bearing.
|
|
109
|
-
const jobs = [];
|
|
110
|
-
const add = (role, entry, value, display) => {
|
|
111
|
-
if (!entry)
|
|
112
|
-
return;
|
|
113
|
-
// Skip fields that are not currently visible (e.g. a collapsed accordion
|
|
114
|
-
// panel or a not-yet-reached step). The executor reveals them and re-fills.
|
|
115
|
-
if (entry.visible === false)
|
|
116
|
-
return;
|
|
117
|
-
jobs.push({
|
|
118
|
-
role,
|
|
119
|
-
entry,
|
|
120
|
-
run: async () => fillOne(page, role, entry, value(), display(), fillTimeoutMs),
|
|
121
|
-
});
|
|
122
|
-
};
|
|
123
|
-
add('number', fields.number, () => credential.pan, () => maskPan(credential.pan));
|
|
124
|
-
add('cvc', fields.cvc, () => credential.cvc, () => maskCvc(credential.cvc));
|
|
125
|
-
add('name', fields.name, () => credential.cardholderName, () => redactContact('name', credential.cardholderName));
|
|
126
|
-
add('nameFirst', fields.nameFirst, () => first, () => redactContact('nameFirst', first));
|
|
127
|
-
add('nameLast', fields.nameLast, () => last, () => redactContact('nameLast', last));
|
|
128
|
-
// Expiry display values are always redacted: the expiry is part of the
|
|
129
|
-
// keyable credential (DPAN + expiry + DAVV) and never enters the log.
|
|
130
|
-
if (fields.expCombined) {
|
|
131
|
-
const e = fields.expCombined;
|
|
132
|
-
const v = expCombinedValue(e, credential.expMonth, credential.expYear);
|
|
133
|
-
add('expCombined', e, () => v, () => maskExpiry());
|
|
134
|
-
}
|
|
135
|
-
if (fields.expMonth) {
|
|
136
|
-
const e = fields.expMonth;
|
|
137
|
-
const value = e.tag === 'select'
|
|
138
|
-
? (monthOptionValue(e.options ?? [], credential.expMonth) ?? pad2(credential.expMonth))
|
|
139
|
-
: pad2(credential.expMonth);
|
|
140
|
-
add('expMonth', e, () => value, () => maskExpiry());
|
|
141
|
-
}
|
|
142
|
-
if (fields.expYear) {
|
|
143
|
-
const e = fields.expYear;
|
|
144
|
-
let value;
|
|
145
|
-
if (e.tag === 'select') {
|
|
146
|
-
value = yearOptionValue(e.options ?? [], credential.expYear) ?? String(credential.expYear);
|
|
147
|
-
}
|
|
148
|
-
else if ((e.maxlength ?? 0) === 2) {
|
|
149
|
-
value = pad2(credential.expYear % 100);
|
|
150
|
-
}
|
|
151
|
-
else {
|
|
152
|
-
value = String(credential.expYear);
|
|
153
|
-
}
|
|
154
|
-
add('expYear', e, () => value, () => maskExpiry());
|
|
155
|
-
}
|
|
156
|
-
// Contact / shipping. Display values are always redacted: these are PII and
|
|
157
|
-
// the evidence log is built to be persistable.
|
|
158
|
-
if (contact.email)
|
|
159
|
-
add('email', fields.email, () => contact.email, () => redactContact('email', contact.email));
|
|
160
|
-
if (contact.addressLine1)
|
|
161
|
-
add('addressLine1', fields.addressLine1, () => contact.addressLine1, () => redactContact('addressLine1', contact.addressLine1));
|
|
162
|
-
if (contact.addressLine2)
|
|
163
|
-
add('addressLine2', fields.addressLine2, () => contact.addressLine2, () => redactContact('addressLine2', contact.addressLine2));
|
|
164
|
-
if (contact.city)
|
|
165
|
-
add('city', fields.city, () => contact.city, () => redactContact('city', contact.city));
|
|
166
|
-
if (contact.postalCode)
|
|
167
|
-
add('postalCode', fields.postalCode, () => contact.postalCode, () => redactContact('postalCode', contact.postalCode));
|
|
168
|
-
if (fields.state && contact.state) {
|
|
169
|
-
const e = fields.state;
|
|
170
|
-
const wanted = contact.state;
|
|
171
|
-
const value = e.tag === 'select' ? (matchOption(e.options ?? [], [wanted]) ?? wanted) : wanted;
|
|
172
|
-
add('state', e, () => value, () => redactContact('state', value));
|
|
173
|
-
}
|
|
174
|
-
if (fields.country && contact.country) {
|
|
175
|
-
const e = fields.country;
|
|
176
|
-
const value = e.tag === 'select'
|
|
177
|
-
? (matchOption(e.options ?? [], [contact.country]) ?? contact.country)
|
|
178
|
-
: contact.country;
|
|
179
|
-
add('country', e, () => value, () => redactContact('country', value));
|
|
180
|
-
}
|
|
181
|
-
for (const j of jobs) {
|
|
182
|
-
const r = await j.run();
|
|
183
|
-
if (r)
|
|
184
|
-
filled.push(r);
|
|
185
|
-
}
|
|
186
|
-
return filled;
|
|
187
|
-
}
|
|
188
|
-
export class GenericAdapter {
|
|
189
|
-
name = 'generic';
|
|
190
|
-
matches(_detected) {
|
|
191
|
-
// The generic adapter is the universal fallback; it always matches.
|
|
192
|
-
return true;
|
|
193
|
-
}
|
|
194
|
-
async fill(page, fields, credential, contact) {
|
|
195
|
-
const filled = await fillFieldMap(page, fields, credential, contact);
|
|
196
|
-
return {
|
|
197
|
-
ok: filled.some((f) => f.role === 'number' && f.ok),
|
|
198
|
-
filled,
|
|
199
|
-
};
|
|
200
|
-
}
|
|
201
|
-
}
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import type { DetectResult } from '../detect.js';
|
|
2
|
-
import type { CheckoutAdapter } from './generic.js';
|
|
3
|
-
export type { CheckoutAdapter } from './generic.js';
|
|
4
|
-
export { GenericAdapter } from './generic.js';
|
|
5
|
-
export { StripeLikeAdapter } from './stripe-like.js';
|
|
6
|
-
export { fillFieldMap, resolveLocator } from './generic.js';
|
|
7
|
-
export declare function selectAdapter(detected: DetectResult): CheckoutAdapter;
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
// Adapter registry. Specific adapters get first crack; the generic adapter is
|
|
2
|
-
// the universal fallback and always matches last. Selection is a pure
|
|
3
|
-
// function of an already-run detection — adapters never re-detect.
|
|
4
|
-
import { GenericAdapter } from './generic.js';
|
|
5
|
-
import { StripeLikeAdapter } from './stripe-like.js';
|
|
6
|
-
export { GenericAdapter } from './generic.js';
|
|
7
|
-
export { StripeLikeAdapter } from './stripe-like.js';
|
|
8
|
-
export { fillFieldMap, resolveLocator } from './generic.js';
|
|
9
|
-
const SPECIFIC = [new StripeLikeAdapter()];
|
|
10
|
-
const FALLBACK = new GenericAdapter();
|
|
11
|
-
export function selectAdapter(detected) {
|
|
12
|
-
for (const a of SPECIFIC) {
|
|
13
|
-
if (a.matches(detected))
|
|
14
|
-
return a;
|
|
15
|
-
}
|
|
16
|
-
return FALLBACK;
|
|
17
|
-
}
|
|
@@ -1,10 +0,0 @@
|
|
|
1
|
-
import type { Page } from 'playwright-core';
|
|
2
|
-
import type { DetectResult, FieldMap } from '../detect.js';
|
|
3
|
-
import type { CardCredential } from '../instrument.js';
|
|
4
|
-
import type { Contact, FillResult } from '../types.js';
|
|
5
|
-
import { type CheckoutAdapter } from './generic.js';
|
|
6
|
-
export declare class StripeLikeAdapter implements CheckoutAdapter {
|
|
7
|
-
name: string;
|
|
8
|
-
matches(detected: DetectResult): boolean;
|
|
9
|
-
fill(page: Page, fields: FieldMap, credential: CardCredential, contact: Contact): Promise<FillResult>;
|
|
10
|
-
}
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
// Stripe-like adapter. Matches checkouts whose card fields live inside a
|
|
2
|
-
// same-origin iframe laid out like Stripe Elements (a card-number frame, an
|
|
3
|
-
// expiry frame, a cvc frame, or one combined frame). It fills through
|
|
4
|
-
// Playwright frameLocator. For M0 only same-origin test iframes are fillable;
|
|
5
|
-
// a real cross-origin PSP iframe is detected upstream and reported as
|
|
6
|
-
// requiresAdapter instead.
|
|
7
|
-
import { fillFieldMap } from './generic.js';
|
|
8
|
-
export class StripeLikeAdapter {
|
|
9
|
-
name = 'stripe-like';
|
|
10
|
-
matches(detected) {
|
|
11
|
-
// We take this adapter when the card number was found inside a frame.
|
|
12
|
-
return Boolean(detected.fields.number?.frame);
|
|
13
|
-
}
|
|
14
|
-
async fill(page, fields, credential, contact) {
|
|
15
|
-
const filled = await fillFieldMap(page, fields, credential, contact);
|
|
16
|
-
return {
|
|
17
|
-
ok: filled.some((f) => f.role === 'number' && f.ok),
|
|
18
|
-
filled,
|
|
19
|
-
};
|
|
20
|
-
}
|
|
21
|
-
}
|
|
@@ -1,46 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser provisioning for the checkout engine.
|
|
3
|
-
*
|
|
4
|
-
* @purpose The engine depends on `playwright-core` (NOT `playwright`) so that
|
|
5
|
-
* installing @visa/cli never triggers a postinstall Chromium download. A real
|
|
6
|
-
* browser is resolved lazily, on the first checkout, in this order:
|
|
7
|
-
*
|
|
8
|
-
* 1. VISA_CHECKOUT_BROWSER — explicit executable path override (deterministic
|
|
9
|
-
* for CI / pinned environments; wins over everything). If it is set but
|
|
10
|
-
* FAILS to launch, we ABORT loudly rather than fall through — a pinned
|
|
11
|
-
* money flow must not run in a browser the operator did not choose.
|
|
12
|
-
* 2. channel: 'chrome' — a system-installed Google Chrome (no download).
|
|
13
|
-
* 3. channel: 'msedge' — a system-installed Microsoft Edge (no download).
|
|
14
|
-
* 4. default chromium — a playwright-managed browser, IF one was already
|
|
15
|
-
* installed via `npx playwright-core install chromium` (still no download
|
|
16
|
-
* here — it only USES an existing one; launch throws if absent).
|
|
17
|
-
*
|
|
18
|
-
* If none launch, we throw one actionable error listing every attempt and the
|
|
19
|
-
* three ways to fix it. We never silently download hundreds of MB mid-command;
|
|
20
|
-
* provisioning stays an explicit user choice (install Chrome, run the documented
|
|
21
|
-
* `playwright-core install`, or point VISA_CHECKOUT_BROWSER at a binary).
|
|
22
|
-
*
|
|
23
|
-
* The install command is `playwright-core`, NOT `playwright`: this package no
|
|
24
|
-
* longer depends on the full `playwright`, so `npx playwright …` would fetch an
|
|
25
|
-
* unrelated latest `playwright` and can install a browser revision that does not
|
|
26
|
-
* match the pinned `playwright-core` — the very launch this is meant to fix.
|
|
27
|
-
* `npx playwright-core install` uses the installed, version-matched package.
|
|
28
|
-
*/
|
|
29
|
-
import { type Browser, type LaunchOptions } from 'playwright-core';
|
|
30
|
-
export type LaunchCheckoutBrowserDeps = {
|
|
31
|
-
env?: NodeJS.ProcessEnv;
|
|
32
|
-
/** Injectable launcher (default: playwright-core chromium.launch) — tests
|
|
33
|
-
* pass a fake to assert the fallback order without a real browser. */
|
|
34
|
-
launch?: (opts: LaunchOptions) => Promise<Browser>;
|
|
35
|
-
log?: (msg: string) => void;
|
|
36
|
-
};
|
|
37
|
-
type Attempt = {
|
|
38
|
-
label: string;
|
|
39
|
-
opts: LaunchOptions;
|
|
40
|
-
override?: boolean;
|
|
41
|
-
};
|
|
42
|
-
/** Build the ordered launch attempts for the current environment. Exported for
|
|
43
|
-
* unit testing the resolution order without launching anything. */
|
|
44
|
-
export declare function buildLaunchAttempts(env: NodeJS.ProcessEnv): Attempt[];
|
|
45
|
-
export declare function launchCheckoutBrowser(deps?: LaunchCheckoutBrowserDeps): Promise<Browser>;
|
|
46
|
-
export {};
|
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browser provisioning for the checkout engine.
|
|
3
|
-
*
|
|
4
|
-
* @purpose The engine depends on `playwright-core` (NOT `playwright`) so that
|
|
5
|
-
* installing @visa/cli never triggers a postinstall Chromium download. A real
|
|
6
|
-
* browser is resolved lazily, on the first checkout, in this order:
|
|
7
|
-
*
|
|
8
|
-
* 1. VISA_CHECKOUT_BROWSER — explicit executable path override (deterministic
|
|
9
|
-
* for CI / pinned environments; wins over everything). If it is set but
|
|
10
|
-
* FAILS to launch, we ABORT loudly rather than fall through — a pinned
|
|
11
|
-
* money flow must not run in a browser the operator did not choose.
|
|
12
|
-
* 2. channel: 'chrome' — a system-installed Google Chrome (no download).
|
|
13
|
-
* 3. channel: 'msedge' — a system-installed Microsoft Edge (no download).
|
|
14
|
-
* 4. default chromium — a playwright-managed browser, IF one was already
|
|
15
|
-
* installed via `npx playwright-core install chromium` (still no download
|
|
16
|
-
* here — it only USES an existing one; launch throws if absent).
|
|
17
|
-
*
|
|
18
|
-
* If none launch, we throw one actionable error listing every attempt and the
|
|
19
|
-
* three ways to fix it. We never silently download hundreds of MB mid-command;
|
|
20
|
-
* provisioning stays an explicit user choice (install Chrome, run the documented
|
|
21
|
-
* `playwright-core install`, or point VISA_CHECKOUT_BROWSER at a binary).
|
|
22
|
-
*
|
|
23
|
-
* The install command is `playwright-core`, NOT `playwright`: this package no
|
|
24
|
-
* longer depends on the full `playwright`, so `npx playwright …` would fetch an
|
|
25
|
-
* unrelated latest `playwright` and can install a browser revision that does not
|
|
26
|
-
* match the pinned `playwright-core` — the very launch this is meant to fix.
|
|
27
|
-
* `npx playwright-core install` uses the installed, version-matched package.
|
|
28
|
-
*/
|
|
29
|
-
import { chromium } from 'playwright-core';
|
|
30
|
-
const HEADFUL = { headless: false };
|
|
31
|
-
/** Build the ordered launch attempts for the current environment. Exported for
|
|
32
|
-
* unit testing the resolution order without launching anything. */
|
|
33
|
-
export function buildLaunchAttempts(env) {
|
|
34
|
-
const attempts = [];
|
|
35
|
-
const override = env.VISA_CHECKOUT_BROWSER?.trim();
|
|
36
|
-
if (override) {
|
|
37
|
-
attempts.push({
|
|
38
|
-
label: `VISA_CHECKOUT_BROWSER (${override})`,
|
|
39
|
-
opts: { ...HEADFUL, executablePath: override },
|
|
40
|
-
override: true,
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
attempts.push({
|
|
44
|
-
label: 'system Chrome (channel=chrome)',
|
|
45
|
-
opts: { ...HEADFUL, channel: 'chrome' },
|
|
46
|
-
});
|
|
47
|
-
attempts.push({ label: 'system Edge (channel=msedge)', opts: { ...HEADFUL, channel: 'msedge' } });
|
|
48
|
-
attempts.push({ label: 'playwright-managed chromium', opts: { ...HEADFUL } });
|
|
49
|
-
return attempts;
|
|
50
|
-
}
|
|
51
|
-
export async function launchCheckoutBrowser(deps = {}) {
|
|
52
|
-
const env = deps.env ?? process.env;
|
|
53
|
-
const launch = deps.launch ?? ((opts) => chromium.launch(opts));
|
|
54
|
-
const log = deps.log ?? (() => { });
|
|
55
|
-
const errors = [];
|
|
56
|
-
for (const attempt of buildLaunchAttempts(env)) {
|
|
57
|
-
try {
|
|
58
|
-
const browser = await launch(attempt.opts);
|
|
59
|
-
log(`checkout browser: launched via ${attempt.label}`);
|
|
60
|
-
return browser;
|
|
61
|
-
}
|
|
62
|
-
catch (err) {
|
|
63
|
-
const first = err.message.split('\n')[0];
|
|
64
|
-
if (attempt.override) {
|
|
65
|
-
// An explicit VISA_CHECKOUT_BROWSER is a deterministic, pinned choice
|
|
66
|
-
// (CI / a locked-down operator env). If it fails to launch, ABORT loudly
|
|
67
|
-
// rather than silently substituting a system browser the operator did
|
|
68
|
-
// not pin — this is a money flow. Unsetting the var opts back into the
|
|
69
|
-
// system-browser fallback below.
|
|
70
|
-
throw new Error(`VISA_CHECKOUT_BROWSER="${env.VISA_CHECKOUT_BROWSER?.trim()}" failed to launch: ${first}. ` +
|
|
71
|
-
'Fix the path/binary, or unset VISA_CHECKOUT_BROWSER to allow a system-browser ' +
|
|
72
|
-
'fallback — refusing to substitute an unpinned browser for a checkout.');
|
|
73
|
-
}
|
|
74
|
-
errors.push(`${attempt.label}: ${first}`);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
throw new Error('Could not launch a browser for checkout. Tried — ' +
|
|
78
|
-
errors.join(' | ') +
|
|
79
|
-
'. Install Google Chrome, or run `npx playwright-core install chromium` ' +
|
|
80
|
-
'(matches the pinned version), or set VISA_CHECKOUT_BROWSER to a browser executable path.');
|
|
81
|
-
}
|