@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.
Files changed (86) hide show
  1. package/README.md +284 -46
  2. package/dist/cli.js +618 -338
  3. package/dist/managed-runtime/resolve-and-update.mjs +268 -0
  4. package/dist/managed-runtime/runtime-readiness.mjs +126 -0
  5. package/dist/managed-runtime/update-and-restart.mjs +1079 -0
  6. package/dist/mcp-apps/ucp-checkout.html +280 -0
  7. package/dist/mcp-server/index.js +513 -258
  8. package/dist/merchant-ucp-mcp/index.js +7 -0
  9. package/dist/skills/pair-visa-agent/RUNTIMES.md +122 -79
  10. package/dist/skills/pair-visa-agent/SKILL.md +390 -333
  11. package/dist/skills/pair-visa-agent/scripts/__tests__/setup.test.mjs +407 -0
  12. package/dist/skills/pair-visa-agent/scripts/setup.mjs +310 -30
  13. package/dist/skills/visa-shopify-checkout/SKILL.md +122 -0
  14. package/dist/skills/visa-shopify-checkout/references/evidence-and-states.md +37 -0
  15. package/dist/skills/visa-ucp-shopping/SKILL.md +86 -0
  16. package/dist/subway-direct.mjs +1 -0
  17. package/install.ps1 +7 -6
  18. package/install.sh +3 -3
  19. package/native/bin/darwin-arm64/visa-runtime-signer +0 -0
  20. package/native/bin/darwin-x64/visa-runtime-signer +0 -0
  21. package/native/bin/linux-arm64/visa-runtime-signer +0 -0
  22. package/native/bin/linux-x64/visa-runtime-signer +0 -0
  23. package/native/bin/win32-arm64/visa-runtime-signer.exe +0 -0
  24. package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
  25. package/native/bin/win32-x64/visa-runtime-signer.exe +0 -0
  26. package/package.json +32 -30
  27. package/server.json +4 -4
  28. package/dist/checkout-engine/adapters/generic.d.ts +0 -19
  29. package/dist/checkout-engine/adapters/generic.js +0 -201
  30. package/dist/checkout-engine/adapters/index.d.ts +0 -7
  31. package/dist/checkout-engine/adapters/index.js +0 -17
  32. package/dist/checkout-engine/adapters/stripe-like.d.ts +0 -10
  33. package/dist/checkout-engine/adapters/stripe-like.js +0 -21
  34. package/dist/checkout-engine/browser-launch.d.ts +0 -46
  35. package/dist/checkout-engine/browser-launch.js +0 -81
  36. package/dist/checkout-engine/ceremony.d.ts +0 -64
  37. package/dist/checkout-engine/ceremony.js +0 -261
  38. package/dist/checkout-engine/cli-engine.d.ts +0 -208
  39. package/dist/checkout-engine/cli-engine.js +0 -584
  40. package/dist/checkout-engine/detect.d.ts +0 -61
  41. package/dist/checkout-engine/detect.js +0 -392
  42. package/dist/checkout-engine/evidence.d.ts +0 -25
  43. package/dist/checkout-engine/evidence.js +0 -104
  44. package/dist/checkout-engine/executor.d.ts +0 -174
  45. package/dist/checkout-engine/executor.js +0 -1306
  46. package/dist/checkout-engine/hosted-approval.d.ts +0 -135
  47. package/dist/checkout-engine/hosted-approval.js +0 -311
  48. package/dist/checkout-engine/index.d.ts +0 -6
  49. package/dist/checkout-engine/index.js +0 -8
  50. package/dist/checkout-engine/inline-target.d.ts +0 -13
  51. package/dist/checkout-engine/inline-target.js +0 -37
  52. package/dist/checkout-engine/instrument.d.ts +0 -55
  53. package/dist/checkout-engine/instrument.js +0 -87
  54. package/dist/checkout-engine/live-fill-approval.d.ts +0 -43
  55. package/dist/checkout-engine/live-fill-approval.js +0 -90
  56. package/dist/checkout-engine/mandate/card-mandate.d.ts +0 -117
  57. package/dist/checkout-engine/mandate/card-mandate.js +0 -221
  58. package/dist/checkout-engine/mandate/mandate-ledger.d.ts +0 -135
  59. package/dist/checkout-engine/mandate/mandate-ledger.js +0 -318
  60. package/dist/checkout-engine/mandate.d.ts +0 -25
  61. package/dist/checkout-engine/mandate.js +0 -100
  62. package/dist/checkout-engine/outcome.d.ts +0 -30
  63. package/dist/checkout-engine/outcome.js +0 -225
  64. package/dist/checkout-engine/owner-only-file.d.ts +0 -19
  65. package/dist/checkout-engine/owner-only-file.js +0 -41
  66. package/dist/checkout-engine/package.json +0 -3
  67. package/dist/checkout-engine/pay-args.d.ts +0 -14
  68. package/dist/checkout-engine/pay-args.js +0 -44
  69. package/dist/checkout-engine/pay.d.ts +0 -1
  70. package/dist/checkout-engine/pay.js +0 -13
  71. package/dist/checkout-engine/receipt.d.ts +0 -81
  72. package/dist/checkout-engine/receipt.js +0 -109
  73. package/dist/checkout-engine/repo-env.d.ts +0 -11
  74. package/dist/checkout-engine/repo-env.js +0 -23
  75. package/dist/checkout-engine/run-live-fill.d.ts +0 -1
  76. package/dist/checkout-engine/run-live-fill.js +0 -493
  77. package/dist/checkout-engine/types.d.ts +0 -39
  78. package/dist/checkout-engine/types.js +0 -2
  79. package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +0 -74
  80. package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +0 -248
  81. package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +0 -82
  82. package/dist/checkout-engine/vgs-gateway/server-mint-client.js +0 -178
  83. package/dist/checkout-engine/vgs-live-instrument.d.ts +0 -168
  84. package/dist/checkout-engine/vgs-live-instrument.js +0 -289
  85. package/dist/checkout-engine/vic-confirmation.d.ts +0 -34
  86. 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 18) {
112
- Write-Host " Node.js $nodeVersion is too old. Version 18+ required." -ForegroundColor Red
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
- Write-Host " Running: npm install -g @visa/cli" -ForegroundColor Gray
141
- $npmOutput = & npm install -g @visa/cli 2>&1
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 @visa/cli" -ForegroundColor Yellow
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 enroll_agent." -ForegroundColor Cyan
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=18
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 enroll_agent."
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>"
package/package.json CHANGED
@@ -1,15 +1,16 @@
1
1
  {
2
2
  "name": "@visa/cli",
3
- "version": "4.1.0-rc.31",
4
- "description": "AI-powered payments for Claude Code",
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 && pnpm --filter @visa/checkout-engine 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/sdk": "^1.0.0",
46
- "@ucp-js/sdk": "0.4.1",
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
- "zod": "^3.23.0"
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
- "uint8arrays": "^6.1.1",
58
- "playwright-core": "^1.48.2"
61
+ "libp2p": "^3.3.9",
62
+ "uint8arrays": "^6.1.1"
59
63
  },
60
64
  "devDependencies": {
61
- "@visa/checkout-engine": "workspace:*",
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
- "eslint-config-prettier": "^10.1.8",
81
+ "express": "^4.21.0",
81
82
  "jest": "^29.7.0",
82
- "prettier": "^3.9.4",
83
- "ts-jest": "^29.4.11",
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": ">=18.0.0"
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
- "LICENSE"
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-10-17/server.schema.json",
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.31",
4
+ "version": "4.1.0-rc.311",
5
5
  "title": "Visa CLI",
6
- "description": "Visa Verified Agent payments for coding agents. Enroll a .visa identity, pair a delegated self-custodial wallet with human-approved spend caps, and discover and pay x402 services — all from your AI coding assistant.",
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.31",
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
- }