@visa/cli 4.1.0-rc.25 → 4.1.0-rc.4

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 (63) hide show
  1. package/dist/cli.js +329 -364
  2. package/dist/mcp-server/index.js +148 -148
  3. package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
  4. package/package.json +3 -5
  5. package/server.json +2 -2
  6. package/dist/checkout-engine/adapters/generic.d.ts +0 -19
  7. package/dist/checkout-engine/adapters/generic.js +0 -201
  8. package/dist/checkout-engine/adapters/index.d.ts +0 -7
  9. package/dist/checkout-engine/adapters/index.js +0 -17
  10. package/dist/checkout-engine/adapters/stripe-like.d.ts +0 -10
  11. package/dist/checkout-engine/adapters/stripe-like.js +0 -21
  12. package/dist/checkout-engine/browser-launch.d.ts +0 -46
  13. package/dist/checkout-engine/browser-launch.js +0 -81
  14. package/dist/checkout-engine/ceremony.d.ts +0 -64
  15. package/dist/checkout-engine/ceremony.js +0 -261
  16. package/dist/checkout-engine/cli-engine.d.ts +0 -60
  17. package/dist/checkout-engine/cli-engine.js +0 -242
  18. package/dist/checkout-engine/detect.d.ts +0 -61
  19. package/dist/checkout-engine/detect.js +0 -372
  20. package/dist/checkout-engine/evidence.d.ts +0 -22
  21. package/dist/checkout-engine/evidence.js +0 -59
  22. package/dist/checkout-engine/executor.d.ts +0 -172
  23. package/dist/checkout-engine/executor.js +0 -1233
  24. package/dist/checkout-engine/hosted-approval.d.ts +0 -78
  25. package/dist/checkout-engine/hosted-approval.js +0 -171
  26. package/dist/checkout-engine/index.d.ts +0 -3
  27. package/dist/checkout-engine/index.js +0 -5
  28. package/dist/checkout-engine/inline-target.d.ts +0 -13
  29. package/dist/checkout-engine/inline-target.js +0 -37
  30. package/dist/checkout-engine/instrument.d.ts +0 -54
  31. package/dist/checkout-engine/instrument.js +0 -83
  32. package/dist/checkout-engine/live-fill-approval.d.ts +0 -52
  33. package/dist/checkout-engine/live-fill-approval.js +0 -107
  34. package/dist/checkout-engine/mandate.d.ts +0 -25
  35. package/dist/checkout-engine/mandate.js +0 -100
  36. package/dist/checkout-engine/outcome.d.ts +0 -30
  37. package/dist/checkout-engine/outcome.js +0 -190
  38. package/dist/checkout-engine/owner-only-file.d.ts +0 -10
  39. package/dist/checkout-engine/owner-only-file.js +0 -22
  40. package/dist/checkout-engine/package.json +0 -3
  41. package/dist/checkout-engine/pay-args.d.ts +0 -14
  42. package/dist/checkout-engine/pay-args.js +0 -44
  43. package/dist/checkout-engine/pay.d.ts +0 -1
  44. package/dist/checkout-engine/pay.js +0 -13
  45. package/dist/checkout-engine/receipt.d.ts +0 -81
  46. package/dist/checkout-engine/receipt.js +0 -109
  47. package/dist/checkout-engine/repo-env.d.ts +0 -11
  48. package/dist/checkout-engine/repo-env.js +0 -23
  49. package/dist/checkout-engine/run-live-fill.d.ts +0 -1
  50. package/dist/checkout-engine/run-live-fill.js +0 -443
  51. package/dist/checkout-engine/types.d.ts +0 -26
  52. package/dist/checkout-engine/types.js +0 -2
  53. package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +0 -74
  54. package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +0 -248
  55. package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +0 -49
  56. package/dist/checkout-engine/vgs-gateway/server-mint-client.js +0 -150
  57. package/dist/checkout-engine/vgs-live-instrument.d.ts +0 -141
  58. package/dist/checkout-engine/vgs-live-instrument.js +0 -252
  59. package/dist/checkout-engine/vic-confirmation.d.ts +0 -34
  60. package/dist/checkout-engine/vic-confirmation.js +0 -39
  61. package/dist/skills/pair-visa-agent/RUNTIMES.md +0 -79
  62. package/dist/skills/pair-visa-agent/SKILL.md +0 -360
  63. package/dist/skills/pair-visa-agent/scripts/setup.mjs +0 -48
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@visa/cli",
3
- "version": "4.1.0-rc.25",
3
+ "version": "4.1.0-rc.4",
4
4
  "description": "AI-powered payments for Claude Code",
5
5
  "bin": {
6
6
  "visa-cli": "./bin/visa-cli.js",
@@ -9,7 +9,7 @@
9
9
  "scripts": {
10
10
  "sync:server-json": "node scripts/sync-server-json.mjs",
11
11
  "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/wallet-tools build && pnpm --filter @visa/checkout-engine build",
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/wallet-tools build",
13
13
  "build": "tsc --noEmit && node esbuild.config.js",
14
14
  "prepack": "node scripts/sync-server-json.mjs --check",
15
15
  "dev": "tsc --watch",
@@ -53,11 +53,9 @@
53
53
  "@chainsafe/libp2p-noise": "^17.0.0",
54
54
  "@chainsafe/libp2p-yamux": "^8.0.1",
55
55
  "@multiformats/multiaddr": "^13.0.3",
56
- "uint8arrays": "^6.1.1",
57
- "playwright-core": "^1.48.2"
56
+ "uint8arrays": "^6.1.1"
58
57
  },
59
58
  "devDependencies": {
60
- "@visa/checkout-engine": "workspace:*",
61
59
  "@visa/crypto": "workspace:*",
62
60
  "subway-sdk": "workspace:*",
63
61
  "@visa/money": "workspace:*",
package/server.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "$schema": "https://static.modelcontextprotocol.io/schemas/2025-10-17/server.schema.json",
3
3
  "name": "io.github.visa-crypto-labs/visa-cli",
4
- "version": "4.1.0-rc.25",
4
+ "version": "4.1.0-rc.4",
5
5
  "title": "Visa CLI",
6
6
  "description": "AI-powered payments and creative tools for coding agents. Generate images, music, video, query crypto prices, and make purchases — all 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": "4.1.0-rc.25",
12
+ "version": "4.1.0-rc.4",
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
- }
@@ -1,64 +0,0 @@
1
- import type { PurchaseAssurance, VgsCheckoutTarget } from './vgs-live-instrument.js';
2
- /** ISO-4217 numeric codes for the consent screen — mirrors the dev-harness map. */
3
- export declare const CURRENCY_NUMERIC: Record<string, string>;
4
- export declare function currencyNumeric(code: string): string | null;
5
- /**
6
- * Build the runner-side PurchaseAssurance from a completed ceremony. Scope
7
- * fields come verbatim from the checkout target, so the instrument's
8
- * scope-alignment validation (#5709) is satisfied by construction; `now` is
9
- * injectable for tests.
10
- */
11
- export declare function assuranceFromCeremony(target: VgsCheckoutTarget, assuranceData: unknown, now?: Date): PurchaseAssurance;
12
- export type CeremonyPageConfig = {
13
- tokenId: string;
14
- environment: 'live' | 'sandbox';
15
- consumerEmail: string;
16
- merchantName: string;
17
- /** Decimal major-unit amount, e.g. "1.00" — drives the consent screen. */
18
- amount: string;
19
- /** Display currency, e.g. "USD". */
20
- currency: string;
21
- /** ISO-4217 numeric code for the SDK, e.g. "840". */
22
- currencyNumericCode: string;
23
- };
24
- /**
25
- * The single-purpose approval page. Pure string builder so tests can assert
26
- * the config embed is intact and escape-safe. The config is embedded as JSON
27
- * with `<` escaped, so a hostile merchant name cannot break out of the script
28
- * element.
29
- */
30
- export declare function ceremonyPageHtml(cfg: CeremonyPageConfig): string;
31
- export type RunCeremonyOptions = {
32
- page: CeremonyPageConfig;
33
- target: VgsCheckoutTarget;
34
- /** Contents of the vendored VgsAgenticAuth module, served to the page. */
35
- vendorSdkJs: string;
36
- /** Short-lived VGS bearer for the ceremony SDK (client-credentials mint). */
37
- mintAccessToken: () => Promise<string>;
38
- /**
39
- * TLS material for the loopback server. The Visa device-binding iframe
40
- * requires an HTTPS parent page, so real callers must pass this; `null`
41
- * serves plain HTTP and exists ONLY so tests can exercise the server
42
- * without certificates.
43
- */
44
- tls: {
45
- key: string | Buffer;
46
- cert: string | Buffer;
47
- } | null;
48
- /** Default 4400 — the origin the operator's browser already trusts. */
49
- port?: number;
50
- /** Overall wait for the human to complete the ceremony. Default 4 minutes. */
51
- timeoutMs?: number;
52
- /** Open the approval page in the operator's browser. Default: macOS `open`. */
53
- openUrl?: (url: string) => void;
54
- /** Status output. Default: stderr. */
55
- log?: (line: string) => void;
56
- };
57
- export declare const CEREMONY_DEFAULT_PORT = 4400;
58
- export declare const CEREMONY_DEFAULT_TIMEOUT_MS: number;
59
- /**
60
- * Serve the approval page on loopback, open it in the operator's browser, and
61
- * resolve with the fresh PurchaseAssurance once the passkey completes. Rejects
62
- * on timeout, port conflict, or server failure; the server is always closed.
63
- */
64
- export declare function runInteractiveCeremony(opts: RunCeremonyOptions): Promise<PurchaseAssurance>;