@visa/cli 4.1.0-rc.99 → 5.0.0-rc.335
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 +282 -156
- package/dist/cli.js +333 -532
- 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 +234 -387
- package/dist/merchant-ucp-mcp/index.js +7 -0
- package/dist/skills/pair-visa-agent/RUNTIMES.md +56 -26
- package/dist/skills/pair-visa-agent/SKILL.md +319 -320
- 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/install.ps1 +10 -6
- package/install.sh +7 -2
- 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 +23 -31
- package/server.json +3 -3
- package/dist/checkout-engine/adapters/generic.d.ts +0 -23
- package/dist/checkout-engine/adapters/generic.js +0 -216
- package/dist/checkout-engine/adapters/index.d.ts +0 -10
- package/dist/checkout-engine/adapters/index.js +0 -24
- package/dist/checkout-engine/adapters/shopify.d.ts +0 -31
- package/dist/checkout-engine/adapters/shopify.js +0 -423
- 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/amount.d.ts +0 -15
- package/dist/checkout-engine/amount.js +0 -72
- 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 -227
- package/dist/checkout-engine/cli-engine.js +0 -779
- package/dist/checkout-engine/detect.d.ts +0 -61
- package/dist/checkout-engine/detect.js +0 -398
- 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 -176
- package/dist/checkout-engine/executor.js +0 -1325
- package/dist/checkout-engine/hosted-approval.d.ts +0 -187
- package/dist/checkout-engine/hosted-approval.js +0 -478
- 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 -61
- 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 -121
- package/dist/checkout-engine/mandate/card-mandate.js +0 -227
- package/dist/checkout-engine/mandate/mandate-ledger.d.ts +0 -142
- package/dist/checkout-engine/mandate/mandate-ledger.js +0 -338
- 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/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/trace-handles.d.ts +0 -8
- package/dist/checkout-engine/trace-handles.js +0 -12
- package/dist/checkout-engine/types.d.ts +0 -44
- 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 -180
- package/dist/checkout-engine/vgs-live-instrument.d.ts +0 -179
- package/dist/checkout-engine/vgs-live-instrument.js +0 -296
- package/dist/checkout-engine/vic-confirmation.d.ts +0 -34
- package/dist/checkout-engine/vic-confirmation.js +0 -39
package/install.sh
CHANGED
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
_visa_install() {
|
|
11
11
|
set -euo pipefail
|
|
12
12
|
|
|
13
|
-
REQUIRED_NODE_MAJOR=
|
|
13
|
+
REQUIRED_NODE_MAJOR=20
|
|
14
14
|
PACKAGE="@visa/cli@rc"
|
|
15
15
|
|
|
16
16
|
# ── colours (disabled when piped) ─────────────────────────────────────────────
|
|
@@ -109,7 +109,12 @@ 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
|
-
|
|
112
|
+
# The door, named as the shell sees it. This line used to name the retired
|
|
113
|
+
# setup ceremony, so an owner who followed it to the letter got
|
|
114
|
+
# legacy_door_removed at the end of a clean install (owner report,
|
|
115
|
+
# 2026-09-15). `pnpm lint:docs-sync` now reads this file.
|
|
116
|
+
info "Then create your agent with: visa agent enroll"
|
|
117
|
+
info "Or ask your agent to call agent_enroll. You approve it in the browser."
|
|
113
118
|
else
|
|
114
119
|
warn "Installed, but 'visa-cli' was not found on PATH."
|
|
115
120
|
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": "
|
|
3
|
+
"version": "5.0.0-rc.335",
|
|
4
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/shared build && pnpm --filter @visa/observability build && pnpm --filter @visa/money build && pnpm --filter @visa/crypto build && pnpm --filter
|
|
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 @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,8 +21,10 @@
|
|
|
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",
|
|
27
|
+
"coinbase:onramp": "pnpm exec tsx tests/e2e/coinbase-onramp-harness/run.ts",
|
|
25
28
|
"test:all": "npm run test:unit && npm run test:integration && npm run test:e2e",
|
|
26
29
|
"prepublishOnly": "npm run build && npm test",
|
|
27
30
|
"lint": "eslint src/**/*.ts",
|
|
@@ -41,51 +44,40 @@
|
|
|
41
44
|
"author": "Visa Crypto Labs",
|
|
42
45
|
"license": "SEE LICENSE IN LICENSE",
|
|
43
46
|
"dependencies": {
|
|
44
|
-
"@modelcontextprotocol/
|
|
45
|
-
"@
|
|
47
|
+
"@modelcontextprotocol/ext-apps": "1.7.5",
|
|
48
|
+
"@modelcontextprotocol/server": "2.0.0",
|
|
49
|
+
"@ucp-js/sdk": "0.4.4",
|
|
46
50
|
"commander": "^12.1.0",
|
|
47
51
|
"punycode": "^2.3.1",
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
"libp2p": "^3.3.8",
|
|
52
|
-
"@libp2p/tcp": "^11.0.26",
|
|
53
|
-
"@libp2p/crypto": "^5.1.22",
|
|
54
|
-
"@chainsafe/libp2p-noise": "^17.0.0",
|
|
55
|
-
"@chainsafe/libp2p-yamux": "^8.0.1",
|
|
56
|
-
"@multiformats/multiaddr": "^13.0.3",
|
|
57
|
-
"uint8arrays": "^6.1.1",
|
|
58
|
-
"playwright-core": "^1.48.2"
|
|
52
|
+
"toml": "^4.3.0",
|
|
53
|
+
"yaml": "^2.9.0",
|
|
54
|
+
"zod": "^3.25.76"
|
|
59
55
|
},
|
|
60
56
|
"devDependencies": {
|
|
57
|
+
"@types/express": "^5.0.0",
|
|
58
|
+
"@types/jest": "^30.0.0",
|
|
59
|
+
"@types/node": "^26.1.0",
|
|
60
|
+
"@typescript-eslint/eslint-plugin": "^8.68.0",
|
|
61
|
+
"@typescript-eslint/parser": "^8.68.0",
|
|
62
|
+
"@visa-cli/tools": "workspace:*",
|
|
61
63
|
"@visa/agent-mail": "workspace:*",
|
|
62
|
-
"@visa/checkout-engine": "workspace:*",
|
|
63
64
|
"@visa/crypto": "workspace:*",
|
|
64
65
|
"@visa/identity": "workspace:*",
|
|
65
|
-
"subway-sdk": "workspace:*",
|
|
66
66
|
"@visa/money": "workspace:*",
|
|
67
|
+
"@visa/observability": "workspace:*",
|
|
68
|
+
"@visa/shared": "workspace:*",
|
|
67
69
|
"@visa/wallet": "workspace:*",
|
|
68
70
|
"@visa/wallet-tools": "workspace:*",
|
|
69
|
-
"@visa/observability": "workspace:*",
|
|
70
|
-
"@visa-cli/tools": "workspace:*",
|
|
71
|
-
"@changesets/changelog-git": "^0.2.1",
|
|
72
|
-
"@changesets/cli": "^2.31.0",
|
|
73
|
-
"@types/jest": "^30.0.0",
|
|
74
|
-
"@types/node": "^26.1.0",
|
|
75
|
-
"@typescript-eslint/eslint-plugin": "^8.65.0",
|
|
76
|
-
"@typescript-eslint/parser": "^8.65.0",
|
|
77
|
-
"@types/express": "^5.0.0",
|
|
78
71
|
"esbuild": "^0.28.1",
|
|
79
|
-
"express": "^4.21.0",
|
|
80
72
|
"eslint": "^10.0.2",
|
|
81
|
-
"
|
|
73
|
+
"express": "^4.21.0",
|
|
82
74
|
"jest": "^29.7.0",
|
|
83
75
|
"prettier": "^3.9.6",
|
|
84
76
|
"ts-jest": "^29.4.12",
|
|
85
77
|
"typescript": "^5.7.0"
|
|
86
78
|
},
|
|
87
79
|
"engines": {
|
|
88
|
-
"node": ">=
|
|
80
|
+
"node": ">=20.0.0"
|
|
89
81
|
},
|
|
90
82
|
"mcpName": "io.github.visa-crypto-labs/visa-cli",
|
|
91
83
|
"files": [
|
|
@@ -99,6 +91,6 @@
|
|
|
99
91
|
"native/bin/win32-x64/visa-keychain-win.exe",
|
|
100
92
|
"server.json",
|
|
101
93
|
"README.md",
|
|
102
|
-
"
|
|
94
|
+
"native/bin/*/visa-runtime-signer*"
|
|
103
95
|
]
|
|
104
96
|
}
|
package/server.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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
|
+
"version": "5.0.0-rc.335",
|
|
5
5
|
"title": "Visa CLI",
|
|
6
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",
|
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
{
|
|
10
10
|
"registryType": "npm",
|
|
11
11
|
"identifier": "@visa/cli",
|
|
12
|
-
"version": "
|
|
12
|
+
"version": "5.0.0-rc.335",
|
|
13
13
|
"transport": {
|
|
14
14
|
"type": "stdio"
|
|
15
15
|
},
|
|
@@ -1,23 +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
|
-
prepareContact?: (page: Page, contact: Contact) => Promise<FillResult>;
|
|
9
|
-
fill(page: Page, fields: FieldMap, credential: CardCredential, contact: Contact): Promise<FillResult>;
|
|
10
|
-
}
|
|
11
|
-
export declare function resolveLocator(page: Page, entry: FieldEntry): Locator;
|
|
12
|
-
export declare function scrubFillErrorMessage(message: string, value: string): string;
|
|
13
|
-
export declare function fillContactFieldMap(page: Page, fields: FieldMap, contact: Contact, opts?: {
|
|
14
|
-
fillTimeoutMs?: number;
|
|
15
|
-
}): Promise<FilledField[]>;
|
|
16
|
-
export declare function fillFieldMap(page: Page, fields: FieldMap, credential: CardCredential, contact: Contact, opts?: {
|
|
17
|
-
fillTimeoutMs?: number;
|
|
18
|
-
}): Promise<FilledField[]>;
|
|
19
|
-
export declare class GenericAdapter implements CheckoutAdapter {
|
|
20
|
-
name: string;
|
|
21
|
-
matches(_detected: DetectResult): boolean;
|
|
22
|
-
fill(page: Page, fields: FieldMap, credential: CardCredential, contact: Contact): Promise<FillResult>;
|
|
23
|
-
}
|
|
@@ -1,216 +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
|
-
async function fillFields(page, fields, credential, contact, opts = {}) {
|
|
101
|
-
const fillTimeoutMs = opts.fillTimeoutMs ?? DEFAULT_FILL_TIMEOUT_MS;
|
|
102
|
-
const filled = [];
|
|
103
|
-
const fullName = contact.fullName ?? credential?.cardholderName;
|
|
104
|
-
const first = contact.firstName ?? fullName?.split(/\s+/)[0];
|
|
105
|
-
const last = contact.lastName ?? fullName?.split(/\s+/).slice(1).join(' ');
|
|
106
|
-
// Order matters a little: contact/name before card is harmless, but we fill
|
|
107
|
-
// card fields explicitly per role so order is not load-bearing.
|
|
108
|
-
const jobs = [];
|
|
109
|
-
const add = (role, entry, value, display) => {
|
|
110
|
-
if (!entry)
|
|
111
|
-
return;
|
|
112
|
-
// Skip fields that are not currently visible (e.g. a collapsed accordion
|
|
113
|
-
// panel or a not-yet-reached step). The executor reveals them and re-fills.
|
|
114
|
-
if (entry.visible === false)
|
|
115
|
-
return;
|
|
116
|
-
jobs.push({
|
|
117
|
-
role,
|
|
118
|
-
entry,
|
|
119
|
-
run: async () => fillOne(page, role, entry, value(), display(), fillTimeoutMs),
|
|
120
|
-
});
|
|
121
|
-
};
|
|
122
|
-
if (credential) {
|
|
123
|
-
add('number', fields.number, () => credential.pan, () => maskPan(credential.pan));
|
|
124
|
-
add('cvc', fields.cvc, () => credential.cvc, () => maskCvc(credential.cvc));
|
|
125
|
-
}
|
|
126
|
-
if (fullName)
|
|
127
|
-
add('name', fields.name, () => fullName, () => redactContact('name', fullName));
|
|
128
|
-
if (first)
|
|
129
|
-
add('nameFirst', fields.nameFirst, () => first, () => redactContact('nameFirst', first));
|
|
130
|
-
if (last)
|
|
131
|
-
add('nameLast', fields.nameLast, () => last, () => redactContact('nameLast', last));
|
|
132
|
-
// Expiry display values are always redacted: the expiry is part of the
|
|
133
|
-
// keyable credential (DPAN + expiry + DAVV) and never enters the log.
|
|
134
|
-
if (credential && fields.expCombined) {
|
|
135
|
-
const e = fields.expCombined;
|
|
136
|
-
const v = expCombinedValue(e, credential.expMonth, credential.expYear);
|
|
137
|
-
add('expCombined', e, () => v, () => maskExpiry());
|
|
138
|
-
}
|
|
139
|
-
if (credential && fields.expMonth) {
|
|
140
|
-
const e = fields.expMonth;
|
|
141
|
-
const value = e.tag === 'select'
|
|
142
|
-
? (monthOptionValue(e.options ?? [], credential.expMonth) ?? pad2(credential.expMonth))
|
|
143
|
-
: pad2(credential.expMonth);
|
|
144
|
-
add('expMonth', e, () => value, () => maskExpiry());
|
|
145
|
-
}
|
|
146
|
-
if (credential && fields.expYear) {
|
|
147
|
-
const e = fields.expYear;
|
|
148
|
-
let value;
|
|
149
|
-
if (e.tag === 'select') {
|
|
150
|
-
value = yearOptionValue(e.options ?? [], credential.expYear) ?? String(credential.expYear);
|
|
151
|
-
}
|
|
152
|
-
else if ((e.maxlength ?? 0) === 2) {
|
|
153
|
-
value = pad2(credential.expYear % 100);
|
|
154
|
-
}
|
|
155
|
-
else {
|
|
156
|
-
value = String(credential.expYear);
|
|
157
|
-
}
|
|
158
|
-
add('expYear', e, () => value, () => maskExpiry());
|
|
159
|
-
}
|
|
160
|
-
// Contact / shipping. Display values are always redacted: these are PII and
|
|
161
|
-
// the evidence log is built to be persistable.
|
|
162
|
-
if (contact.email)
|
|
163
|
-
add('email', fields.email, () => contact.email, () => redactContact('email', contact.email));
|
|
164
|
-
if (contact.phone)
|
|
165
|
-
add('phone', fields.phone, () => contact.phone, () => redactContact('phone', contact.phone));
|
|
166
|
-
if (contact.addressLine1)
|
|
167
|
-
add('addressLine1', fields.addressLine1, () => contact.addressLine1, () => redactContact('addressLine1', contact.addressLine1));
|
|
168
|
-
if (contact.addressLine2)
|
|
169
|
-
add('addressLine2', fields.addressLine2, () => contact.addressLine2, () => redactContact('addressLine2', contact.addressLine2));
|
|
170
|
-
if (contact.city)
|
|
171
|
-
add('city', fields.city, () => contact.city, () => redactContact('city', contact.city));
|
|
172
|
-
if (contact.postalCode)
|
|
173
|
-
add('postalCode', fields.postalCode, () => contact.postalCode, () => redactContact('postalCode', contact.postalCode));
|
|
174
|
-
if (fields.state && contact.state) {
|
|
175
|
-
const e = fields.state;
|
|
176
|
-
const wanted = contact.state;
|
|
177
|
-
const value = e.tag === 'select' ? (matchOption(e.options ?? [], [wanted]) ?? wanted) : wanted;
|
|
178
|
-
add('state', e, () => value, () => redactContact('state', value));
|
|
179
|
-
}
|
|
180
|
-
if (fields.country && contact.country) {
|
|
181
|
-
const e = fields.country;
|
|
182
|
-
const value = e.tag === 'select'
|
|
183
|
-
? (matchOption(e.options ?? [], [contact.country]) ?? contact.country)
|
|
184
|
-
: contact.country;
|
|
185
|
-
add('country', e, () => value, () => redactContact('country', value));
|
|
186
|
-
}
|
|
187
|
-
for (const j of jobs) {
|
|
188
|
-
const r = await j.run();
|
|
189
|
-
if (r)
|
|
190
|
-
filled.push(r);
|
|
191
|
-
}
|
|
192
|
-
return filled;
|
|
193
|
-
}
|
|
194
|
-
// Credential-free contact prefill for merchants that must calculate shipping,
|
|
195
|
-
// tax, and the final total before a human reviews the payment.
|
|
196
|
-
export async function fillContactFieldMap(page, fields, contact, opts = {}) {
|
|
197
|
-
return fillFields(page, fields, null, contact, opts);
|
|
198
|
-
}
|
|
199
|
-
// The shared post-approval fill routine used by every adapter.
|
|
200
|
-
export async function fillFieldMap(page, fields, credential, contact, opts = {}) {
|
|
201
|
-
return fillFields(page, fields, credential, contact, opts);
|
|
202
|
-
}
|
|
203
|
-
export class GenericAdapter {
|
|
204
|
-
name = 'generic';
|
|
205
|
-
matches(_detected) {
|
|
206
|
-
// The generic adapter is the universal fallback; it always matches.
|
|
207
|
-
return true;
|
|
208
|
-
}
|
|
209
|
-
async fill(page, fields, credential, contact) {
|
|
210
|
-
const filled = await fillFieldMap(page, fields, credential, contact);
|
|
211
|
-
return {
|
|
212
|
-
ok: filled.some((f) => f.role === 'number' && f.ok),
|
|
213
|
-
filled,
|
|
214
|
-
};
|
|
215
|
-
}
|
|
216
|
-
}
|
|
@@ -1,10 +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 { ShopifyAdapter } from './shopify.js';
|
|
6
|
-
export { StripeLikeAdapter } from './stripe-like.js';
|
|
7
|
-
export { fillFieldMap, resolveLocator } from './generic.js';
|
|
8
|
-
export declare function selectAdapter(detected: DetectResult, pageKind?: {
|
|
9
|
-
shopify?: boolean;
|
|
10
|
-
}): CheckoutAdapter;
|
|
@@ -1,24 +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 { ShopifyAdapter } from './shopify.js';
|
|
6
|
-
import { StripeLikeAdapter } from './stripe-like.js';
|
|
7
|
-
export { GenericAdapter } from './generic.js';
|
|
8
|
-
export { ShopifyAdapter } from './shopify.js';
|
|
9
|
-
export { StripeLikeAdapter } from './stripe-like.js';
|
|
10
|
-
export { fillFieldMap, resolveLocator } from './generic.js';
|
|
11
|
-
const FALLBACK = new GenericAdapter();
|
|
12
|
-
export function selectAdapter(detected, pageKind = {}) {
|
|
13
|
-
// Shopify owns the outer checkout form and can still delegate card fields to
|
|
14
|
-
// an iframe, so it gets first crack and reuses fillFieldMap for both.
|
|
15
|
-
const shopify = new ShopifyAdapter(detected);
|
|
16
|
-
if (pageKind.shopify === true)
|
|
17
|
-
return shopify;
|
|
18
|
-
const specific = [shopify, new StripeLikeAdapter()];
|
|
19
|
-
for (const a of specific) {
|
|
20
|
-
if (a.matches(detected))
|
|
21
|
-
return a;
|
|
22
|
-
}
|
|
23
|
-
return FALLBACK;
|
|
24
|
-
}
|
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
import type { Page } from 'playwright-core';
|
|
2
|
-
import { type PageAmountRead } from '../amount.js';
|
|
3
|
-
import { type DetectResult, type FieldMap } from '../detect.js';
|
|
4
|
-
import type { CardCredential } from '../instrument.js';
|
|
5
|
-
import type { Contact, FillResult } from '../types.js';
|
|
6
|
-
import { type CheckoutAdapter } from './generic.js';
|
|
7
|
-
type ShopifySummary = {
|
|
8
|
-
subtotalMinor: number | null;
|
|
9
|
-
shippingMinor: number | null;
|
|
10
|
-
taxMinor: number | null;
|
|
11
|
-
discountMinor: number;
|
|
12
|
-
totalMinor: number | null;
|
|
13
|
-
currency: string | null;
|
|
14
|
-
verified: boolean;
|
|
15
|
-
};
|
|
16
|
-
export declare function parseShopifySummary(text: string): ShopifySummary;
|
|
17
|
-
export declare function readShopifyAmount(page: Page, requireVerified: boolean): Promise<PageAmountRead>;
|
|
18
|
-
export declare function readStableShopifyAmount(page: Page, timeoutMs?: number): Promise<PageAmountRead>;
|
|
19
|
-
export declare function isShopifyCheckoutPage(page: Page): Promise<boolean>;
|
|
20
|
-
export declare function detectShopifyChallenge(page: Page): Promise<{
|
|
21
|
-
signal: string;
|
|
22
|
-
} | null>;
|
|
23
|
-
export declare class ShopifyAdapter implements CheckoutAdapter {
|
|
24
|
-
private readonly detected;
|
|
25
|
-
name: string;
|
|
26
|
-
constructor(detected: DetectResult);
|
|
27
|
-
matches(detected: DetectResult): boolean;
|
|
28
|
-
prepareContact(page: Page, contact: Contact): Promise<FillResult>;
|
|
29
|
-
fill(page: Page, _fields: FieldMap, credential: CardCredential, _contact: Contact): Promise<FillResult>;
|
|
30
|
-
}
|
|
31
|
-
export {};
|