@visa/cli 4.1.0-rc.298 → 4.1.0-rc.299
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 +29 -45
- package/dist/cli.js +556 -786
- package/dist/mcp-server/index.js +408 -622
- package/dist/merchant-ucp-mcp/index.js +6 -6
- package/dist/skills/pair-visa-agent/SKILL.md +175 -240
- 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 +2 -4
- package/server.json +2 -2
- package/dist/checkout-engine/adapters/generic.d.ts +0 -88
- package/dist/checkout-engine/adapters/generic.js +0 -526
- 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 -98
- package/dist/checkout-engine/adapters/shopify.js +0 -744
- 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 -17
- package/dist/checkout-engine/amount.js +0 -72
- package/dist/checkout-engine/browser-launch.d.ts +0 -51
- package/dist/checkout-engine/browser-launch.js +0 -96
- package/dist/checkout-engine/browserbase-browser.d.ts +0 -24
- package/dist/checkout-engine/browserbase-browser.js +0 -186
- 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 -417
- package/dist/checkout-engine/cli-engine.js +0 -1331
- package/dist/checkout-engine/confirmed-merchants.d.ts +0 -31
- package/dist/checkout-engine/confirmed-merchants.js +0 -165
- 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 -262
- package/dist/checkout-engine/executor.js +0 -1837
- package/dist/checkout-engine/hosted-approval.d.ts +0 -195
- package/dist/checkout-engine/hosted-approval.js +0 -501
- package/dist/checkout-engine/index.d.ts +0 -12
- package/dist/checkout-engine/index.js +0 -13
- package/dist/checkout-engine/instrument.d.ts +0 -61
- package/dist/checkout-engine/instrument.js +0 -87
- package/dist/checkout-engine/known-merchants.d.ts +0 -10
- package/dist/checkout-engine/known-merchants.js +0 -38
- package/dist/checkout-engine/live-fill-approval.d.ts +0 -37
- package/dist/checkout-engine/live-fill-approval.js +0 -76
- package/dist/checkout-engine/mandate/card-mandate.d.ts +0 -121
- package/dist/checkout-engine/mandate/card-mandate.js +0 -226
- package/dist/checkout-engine/mandate/mandate-ledger.d.ts +0 -175
- package/dist/checkout-engine/mandate/mandate-ledger.js +0 -425
- package/dist/checkout-engine/mandate.d.ts +0 -33
- package/dist/checkout-engine/mandate.js +0 -135
- 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-dir.d.ts +0 -6
- package/dist/checkout-engine/receipt-dir.js +0 -8
- package/dist/checkout-engine/receipt.d.ts +0 -135
- package/dist/checkout-engine/receipt.js +0 -148
- package/dist/checkout-engine/shopify-primary-domain.d.ts +0 -25
- package/dist/checkout-engine/shopify-primary-domain.js +0 -96
- 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 -52
- package/dist/checkout-engine/types.js +0 -2
- package/dist/checkout-engine/unresolved-charges.d.ts +0 -34
- package/dist/checkout-engine/unresolved-charges.js +0 -134
- package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +0 -155
- package/dist/checkout-engine/vgs-gateway/server-mint-client.js +0 -493
- package/dist/checkout-engine/vgs-live-instrument.d.ts +0 -144
- package/dist/checkout-engine/vgs-live-instrument.js +0 -229
- package/dist/checkout-engine/vic-confirmation.d.ts +0 -52
- package/dist/checkout-engine/vic-confirmation.js +0 -45
- package/dist/checkout-engine/web-bot-auth.d.ts +0 -98
- package/dist/checkout-engine/web-bot-auth.js +0 -218
|
@@ -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,17 +0,0 @@
|
|
|
1
|
-
import type { Page } from 'playwright-core';
|
|
2
|
-
export declare function minorFromDecimal(text: string): number | null;
|
|
3
|
-
export declare function pageCurrency(text: string): string | null;
|
|
4
|
-
export type PageAmountRead = {
|
|
5
|
-
kind: 'none';
|
|
6
|
-
} | {
|
|
7
|
-
kind: 'unreadable';
|
|
8
|
-
reason?: string;
|
|
9
|
-
} | {
|
|
10
|
-
kind: 'ok';
|
|
11
|
-
amountMinor: number;
|
|
12
|
-
currency: string | null;
|
|
13
|
-
source: 'page-attr' | 'page-text' | 'shopify-summary';
|
|
14
|
-
/** Shopify rows counted as zero because they never rendered (#8669). */
|
|
15
|
-
impliedZeroRows?: Array<'tax' | 'shipping'>;
|
|
16
|
-
};
|
|
17
|
-
export declare function readGenericPageAmount(page: Page): Promise<PageAmountRead>;
|
|
@@ -1,72 +0,0 @@
|
|
|
1
|
-
// Convert a human decimal like "$50.00" to integer minor units without
|
|
2
|
-
// floats. Fail closed on separator ambiguity. Only 2-decimal currencies are
|
|
3
|
-
// supported (matching pageCurrency's allowlist).
|
|
4
|
-
export function minorFromDecimal(text) {
|
|
5
|
-
const match = text.match(/\d[\d.,]*/);
|
|
6
|
-
if (!match)
|
|
7
|
-
return null;
|
|
8
|
-
const token = match[0].replace(/[.,]+$/, '');
|
|
9
|
-
if (/^\d+$/.test(token))
|
|
10
|
-
return Number.parseInt(token, 10) * 100;
|
|
11
|
-
if (/^\d{1,3}(,\d{3})+\.\d{2}$/.test(token)) {
|
|
12
|
-
const [whole, fraction] = token.replace(/,/g, '').split('.');
|
|
13
|
-
return Number.parseInt(whole, 10) * 100 + Number.parseInt(fraction, 10);
|
|
14
|
-
}
|
|
15
|
-
if (/^\d{1,3}(\.\d{3})+,\d{2}$/.test(token)) {
|
|
16
|
-
const [whole, fraction] = token.replace(/\./g, '').split(',');
|
|
17
|
-
return Number.parseInt(whole, 10) * 100 + Number.parseInt(fraction, 10);
|
|
18
|
-
}
|
|
19
|
-
if (/^\d+\.\d{1,2}$/.test(token)) {
|
|
20
|
-
const [whole, fraction] = token.split('.');
|
|
21
|
-
return Number.parseInt(whole, 10) * 100 + Number.parseInt(fraction.padEnd(2, '0'), 10);
|
|
22
|
-
}
|
|
23
|
-
if (/^\d+,\d{1,2}$/.test(token)) {
|
|
24
|
-
const [whole, fraction] = token.split(',');
|
|
25
|
-
return Number.parseInt(whole, 10) * 100 + Number.parseInt(fraction.padEnd(2, '0'), 10);
|
|
26
|
-
}
|
|
27
|
-
return null;
|
|
28
|
-
}
|
|
29
|
-
const ISO_CURRENCIES = ['USD', 'EUR', 'GBP', 'CAD', 'AUD', 'CHF', 'NZD'];
|
|
30
|
-
export function pageCurrency(text) {
|
|
31
|
-
const iso = text.match(/\b([A-Z]{3})\b/);
|
|
32
|
-
if (iso && ISO_CURRENCIES.includes(iso[1]))
|
|
33
|
-
return iso[1];
|
|
34
|
-
if (text.includes('€'))
|
|
35
|
-
return 'EUR';
|
|
36
|
-
if (text.includes('£'))
|
|
37
|
-
return 'GBP';
|
|
38
|
-
return null;
|
|
39
|
-
}
|
|
40
|
-
export async function readGenericPageAmount(page) {
|
|
41
|
-
const explicitLocator = page.locator('[data-total-minor]').first();
|
|
42
|
-
if ((await explicitLocator.count().catch(() => 0)) > 0) {
|
|
43
|
-
const explicit = await explicitLocator.getAttribute('data-total-minor').catch(() => null);
|
|
44
|
-
if (explicit && /^\d+$/.test(explicit)) {
|
|
45
|
-
const text = (await explicitLocator.textContent().catch(() => null)) ?? '';
|
|
46
|
-
return {
|
|
47
|
-
kind: 'ok',
|
|
48
|
-
amountMinor: Number.parseInt(explicit, 10),
|
|
49
|
-
currency: pageCurrency(text),
|
|
50
|
-
source: 'page-attr',
|
|
51
|
-
};
|
|
52
|
-
}
|
|
53
|
-
}
|
|
54
|
-
const totalLocator = page
|
|
55
|
-
.locator('#order-total, .order-total, [data-testid="order-total"]')
|
|
56
|
-
.first();
|
|
57
|
-
if ((await totalLocator.count().catch(() => 0)) > 0) {
|
|
58
|
-
const totalText = await totalLocator.textContent().catch(() => null);
|
|
59
|
-
if (totalText && /\d/.test(totalText)) {
|
|
60
|
-
const amountMinor = minorFromDecimal(totalText);
|
|
61
|
-
if (amountMinor == null)
|
|
62
|
-
return { kind: 'unreadable' };
|
|
63
|
-
return {
|
|
64
|
-
kind: 'ok',
|
|
65
|
-
amountMinor,
|
|
66
|
-
currency: pageCurrency(totalText),
|
|
67
|
-
source: 'page-text',
|
|
68
|
-
};
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
return { kind: 'none' };
|
|
72
|
-
}
|
|
@@ -1,51 +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_BROWSERBASE_ENABLED=1 — explicit remote Browserbase
|
|
9
|
-
* session for a headless runtime. It is mutually exclusive with a local
|
|
10
|
-
* executable override and never falls back after provider failure.
|
|
11
|
-
* 2. VISA_CHECKOUT_BROWSER — explicit executable path override (deterministic
|
|
12
|
-
* for CI / pinned environments; wins over everything). If it is set but
|
|
13
|
-
* FAILS to launch, we ABORT loudly rather than fall through — a pinned
|
|
14
|
-
* money flow must not run in a browser the operator did not choose.
|
|
15
|
-
* 3. channel: 'chrome' — a system-installed Google Chrome (no download).
|
|
16
|
-
* 4. channel: 'msedge' — a system-installed Microsoft Edge (no download).
|
|
17
|
-
* 5. default chromium — a playwright-managed browser, IF one was already
|
|
18
|
-
* installed via `npx playwright-core install chromium` (still no download
|
|
19
|
-
* here — it only USES an existing one; launch throws if absent).
|
|
20
|
-
*
|
|
21
|
-
* If none launch, we throw one actionable error listing every attempt and the
|
|
22
|
-
* three ways to fix it. We never silently download hundreds of MB mid-command;
|
|
23
|
-
* provisioning stays an explicit user choice (install Chrome, run the documented
|
|
24
|
-
* `playwright-core install`, or point VISA_CHECKOUT_BROWSER at a binary).
|
|
25
|
-
*
|
|
26
|
-
* The install command is `playwright-core`, NOT `playwright`: this package no
|
|
27
|
-
* longer depends on the full `playwright`, so `npx playwright …` would fetch an
|
|
28
|
-
* unrelated latest `playwright` and can install a browser revision that does not
|
|
29
|
-
* match the pinned `playwright-core` — the very launch this is meant to fix.
|
|
30
|
-
* `npx playwright-core install` uses the installed, version-matched package.
|
|
31
|
-
*/
|
|
32
|
-
import { type Browser, type LaunchOptions } from 'playwright-core';
|
|
33
|
-
import { launchBrowserbaseCheckoutBrowser } from './browserbase-browser.js';
|
|
34
|
-
export type LaunchCheckoutBrowserDeps = {
|
|
35
|
-
env?: NodeJS.ProcessEnv;
|
|
36
|
-
/** Injectable launcher (default: playwright-core chromium.launch) — tests
|
|
37
|
-
* pass a fake to assert the fallback order without a real browser. */
|
|
38
|
-
launch?: (opts: LaunchOptions) => Promise<Browser>;
|
|
39
|
-
launchBrowserbase?: typeof launchBrowserbaseCheckoutBrowser;
|
|
40
|
-
log?: (msg: string) => void;
|
|
41
|
-
};
|
|
42
|
-
type Attempt = {
|
|
43
|
-
label: string;
|
|
44
|
-
opts: LaunchOptions;
|
|
45
|
-
override?: boolean;
|
|
46
|
-
};
|
|
47
|
-
/** Build the ordered launch attempts for the current environment. Exported for
|
|
48
|
-
* unit testing the resolution order without launching anything. */
|
|
49
|
-
export declare function buildLaunchAttempts(env: NodeJS.ProcessEnv): Attempt[];
|
|
50
|
-
export declare function launchCheckoutBrowser(deps?: LaunchCheckoutBrowserDeps): Promise<Browser>;
|
|
51
|
-
export {};
|
|
@@ -1,96 +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_BROWSERBASE_ENABLED=1 — explicit remote Browserbase
|
|
9
|
-
* session for a headless runtime. It is mutually exclusive with a local
|
|
10
|
-
* executable override and never falls back after provider failure.
|
|
11
|
-
* 2. VISA_CHECKOUT_BROWSER — explicit executable path override (deterministic
|
|
12
|
-
* for CI / pinned environments; wins over everything). If it is set but
|
|
13
|
-
* FAILS to launch, we ABORT loudly rather than fall through — a pinned
|
|
14
|
-
* money flow must not run in a browser the operator did not choose.
|
|
15
|
-
* 3. channel: 'chrome' — a system-installed Google Chrome (no download).
|
|
16
|
-
* 4. channel: 'msedge' — a system-installed Microsoft Edge (no download).
|
|
17
|
-
* 5. default chromium — a playwright-managed browser, IF one was already
|
|
18
|
-
* installed via `npx playwright-core install chromium` (still no download
|
|
19
|
-
* here — it only USES an existing one; launch throws if absent).
|
|
20
|
-
*
|
|
21
|
-
* If none launch, we throw one actionable error listing every attempt and the
|
|
22
|
-
* three ways to fix it. We never silently download hundreds of MB mid-command;
|
|
23
|
-
* provisioning stays an explicit user choice (install Chrome, run the documented
|
|
24
|
-
* `playwright-core install`, or point VISA_CHECKOUT_BROWSER at a binary).
|
|
25
|
-
*
|
|
26
|
-
* The install command is `playwright-core`, NOT `playwright`: this package no
|
|
27
|
-
* longer depends on the full `playwright`, so `npx playwright …` would fetch an
|
|
28
|
-
* unrelated latest `playwright` and can install a browser revision that does not
|
|
29
|
-
* match the pinned `playwright-core` — the very launch this is meant to fix.
|
|
30
|
-
* `npx playwright-core install` uses the installed, version-matched package.
|
|
31
|
-
*/
|
|
32
|
-
import { chromium } from 'playwright-core';
|
|
33
|
-
import { launchBrowserbaseCheckoutBrowser } from './browserbase-browser.js';
|
|
34
|
-
const HEADFUL = { headless: false };
|
|
35
|
-
/** Build the ordered launch attempts for the current environment. Exported for
|
|
36
|
-
* unit testing the resolution order without launching anything. */
|
|
37
|
-
export function buildLaunchAttempts(env) {
|
|
38
|
-
const attempts = [];
|
|
39
|
-
const override = env.VISA_CHECKOUT_BROWSER?.trim();
|
|
40
|
-
if (override) {
|
|
41
|
-
attempts.push({
|
|
42
|
-
label: `VISA_CHECKOUT_BROWSER (${override})`,
|
|
43
|
-
opts: { ...HEADFUL, executablePath: override },
|
|
44
|
-
override: true,
|
|
45
|
-
});
|
|
46
|
-
}
|
|
47
|
-
attempts.push({
|
|
48
|
-
label: 'system Chrome (channel=chrome)',
|
|
49
|
-
opts: { ...HEADFUL, channel: 'chrome' },
|
|
50
|
-
});
|
|
51
|
-
attempts.push({ label: 'system Edge (channel=msedge)', opts: { ...HEADFUL, channel: 'msedge' } });
|
|
52
|
-
attempts.push({ label: 'playwright-managed chromium', opts: { ...HEADFUL } });
|
|
53
|
-
return attempts;
|
|
54
|
-
}
|
|
55
|
-
export async function launchCheckoutBrowser(deps = {}) {
|
|
56
|
-
const env = deps.env ?? process.env;
|
|
57
|
-
const launch = deps.launch ?? ((opts) => chromium.launch(opts));
|
|
58
|
-
const log = deps.log ?? (() => { });
|
|
59
|
-
const browserbaseFlag = env.VISA_CHECKOUT_BROWSERBASE_ENABLED?.trim();
|
|
60
|
-
if (browserbaseFlag && browserbaseFlag !== '0' && browserbaseFlag !== '1') {
|
|
61
|
-
throw new Error('VISA_CHECKOUT_BROWSERBASE_ENABLED must be 1 or 0 when set');
|
|
62
|
-
}
|
|
63
|
-
if (browserbaseFlag === '1') {
|
|
64
|
-
if (env.VISA_CHECKOUT_BROWSER?.trim()) {
|
|
65
|
-
throw new Error('VISA_CHECKOUT_BROWSERBASE_ENABLED and VISA_CHECKOUT_BROWSER cannot both be set');
|
|
66
|
-
}
|
|
67
|
-
const launchBrowserbase = deps.launchBrowserbase ?? launchBrowserbaseCheckoutBrowser;
|
|
68
|
-
return launchBrowserbase({ env, log });
|
|
69
|
-
}
|
|
70
|
-
const errors = [];
|
|
71
|
-
for (const attempt of buildLaunchAttempts(env)) {
|
|
72
|
-
try {
|
|
73
|
-
const browser = await launch(attempt.opts);
|
|
74
|
-
log(`checkout browser: launched via ${attempt.label}`);
|
|
75
|
-
return browser;
|
|
76
|
-
}
|
|
77
|
-
catch (err) {
|
|
78
|
-
const first = err.message.split('\n')[0];
|
|
79
|
-
if (attempt.override) {
|
|
80
|
-
// An explicit VISA_CHECKOUT_BROWSER is a deterministic, pinned choice
|
|
81
|
-
// (CI / a locked-down operator env). If it fails to launch, ABORT loudly
|
|
82
|
-
// rather than silently substituting a system browser the operator did
|
|
83
|
-
// not pin — this is a money flow. Unsetting the var opts back into the
|
|
84
|
-
// system-browser fallback below.
|
|
85
|
-
throw new Error(`VISA_CHECKOUT_BROWSER="${env.VISA_CHECKOUT_BROWSER?.trim()}" failed to launch: ${first}. ` +
|
|
86
|
-
'Fix the path/binary, or unset VISA_CHECKOUT_BROWSER to allow a system-browser ' +
|
|
87
|
-
'fallback — refusing to substitute an unpinned browser for a checkout.');
|
|
88
|
-
}
|
|
89
|
-
errors.push(`${attempt.label}: ${first}`);
|
|
90
|
-
}
|
|
91
|
-
}
|
|
92
|
-
throw new Error('Could not launch a browser for checkout. Tried — ' +
|
|
93
|
-
errors.join(' | ') +
|
|
94
|
-
'. Install Google Chrome, or run `npx playwright-core install chromium` ' +
|
|
95
|
-
'(matches the pinned version), or set VISA_CHECKOUT_BROWSER to a browser executable path.');
|
|
96
|
-
}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browserbase provisioning for the checkout engine.
|
|
3
|
-
*
|
|
4
|
-
* Browserbase exposes one pre-created Chromium context over CDP. The checkout
|
|
5
|
-
* engine normally asks a locally launched Browser for a fresh context, so this
|
|
6
|
-
* module adapts that one remote context to the narrow Browser surface the
|
|
7
|
-
* engine uses. It deliberately refuses unsupported context options instead of
|
|
8
|
-
* silently weakening a future checkout isolation setting.
|
|
9
|
-
*/
|
|
10
|
-
import { type Browser, type BrowserContext } from 'playwright-core';
|
|
11
|
-
type FetchLike = typeof fetch;
|
|
12
|
-
export type LaunchBrowserbaseCheckoutBrowserDeps = {
|
|
13
|
-
env?: NodeJS.ProcessEnv;
|
|
14
|
-
fetch?: FetchLike;
|
|
15
|
-
connectOverCDP?: (connectUrl: string) => Promise<Browser>;
|
|
16
|
-
log?: (message: string) => void;
|
|
17
|
-
};
|
|
18
|
-
/**
|
|
19
|
-
* Adapt Browserbase's one recorded default context to Browser.newContext().
|
|
20
|
-
* One launch serves one checkout review, so the context is leased exactly once.
|
|
21
|
-
*/
|
|
22
|
-
export declare function adaptBrowserbaseBrowser(browser: Browser, context: BrowserContext): Browser;
|
|
23
|
-
export declare function launchBrowserbaseCheckoutBrowser(deps?: LaunchBrowserbaseCheckoutBrowserDeps): Promise<Browser>;
|
|
24
|
-
export {};
|
|
@@ -1,186 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Browserbase provisioning for the checkout engine.
|
|
3
|
-
*
|
|
4
|
-
* Browserbase exposes one pre-created Chromium context over CDP. The checkout
|
|
5
|
-
* engine normally asks a locally launched Browser for a fresh context, so this
|
|
6
|
-
* module adapts that one remote context to the narrow Browser surface the
|
|
7
|
-
* engine uses. It deliberately refuses unsupported context options instead of
|
|
8
|
-
* silently weakening a future checkout isolation setting.
|
|
9
|
-
*/
|
|
10
|
-
import { chromium, } from 'playwright-core';
|
|
11
|
-
const BROWSERBASE_SESSIONS_URL = 'https://api.browserbase.com/v1/sessions';
|
|
12
|
-
const BROWSERBASE_SESSION_TIMEOUT_SECONDS = 600;
|
|
13
|
-
const BROWSERBASE_API_TIMEOUT_MS = 15_000;
|
|
14
|
-
function requiredSecret(env, name) {
|
|
15
|
-
const value = env[name]?.trim();
|
|
16
|
-
if (!value) {
|
|
17
|
-
throw new Error(`${name} is required when VISA_CHECKOUT_BROWSERBASE_ENABLED=1. ` +
|
|
18
|
-
'Set it in the runtime environment; never pass it through an agent prompt.');
|
|
19
|
-
}
|
|
20
|
-
return value;
|
|
21
|
-
}
|
|
22
|
-
function parseSession(value) {
|
|
23
|
-
if (!value || typeof value !== 'object') {
|
|
24
|
-
throw new Error('Browserbase session response was not an object');
|
|
25
|
-
}
|
|
26
|
-
const record = value;
|
|
27
|
-
if (typeof record.id !== 'string' || record.id.length === 0) {
|
|
28
|
-
throw new Error('Browserbase session response did not include an id');
|
|
29
|
-
}
|
|
30
|
-
if (typeof record.connectUrl !== 'string') {
|
|
31
|
-
throw new Error('Browserbase session response did not include a connect URL');
|
|
32
|
-
}
|
|
33
|
-
let connectUrl;
|
|
34
|
-
try {
|
|
35
|
-
connectUrl = new URL(record.connectUrl);
|
|
36
|
-
}
|
|
37
|
-
catch {
|
|
38
|
-
throw new Error('Browserbase session response included an invalid connect URL');
|
|
39
|
-
}
|
|
40
|
-
if (connectUrl.protocol !== 'wss:') {
|
|
41
|
-
throw new Error('Browserbase session response included a non-WSS connect URL');
|
|
42
|
-
}
|
|
43
|
-
return { id: record.id, connectUrl: connectUrl.toString() };
|
|
44
|
-
}
|
|
45
|
-
async function releaseBrowserbaseSession(fetchImpl, apiKey, sessionId, projectId, log) {
|
|
46
|
-
const requestStartedAt = Date.now();
|
|
47
|
-
try {
|
|
48
|
-
const response = await fetchImpl(`${BROWSERBASE_SESSIONS_URL}/${encodeURIComponent(sessionId)}`, {
|
|
49
|
-
method: 'POST',
|
|
50
|
-
headers: {
|
|
51
|
-
'Content-Type': 'application/json',
|
|
52
|
-
'X-BB-API-Key': apiKey,
|
|
53
|
-
},
|
|
54
|
-
body: JSON.stringify({
|
|
55
|
-
status: 'REQUEST_RELEASE',
|
|
56
|
-
...(projectId ? { projectId } : {}),
|
|
57
|
-
}),
|
|
58
|
-
signal: AbortSignal.timeout(BROWSERBASE_API_TIMEOUT_MS),
|
|
59
|
-
});
|
|
60
|
-
logBoundary(log, `checkout browser boundary: provider=browserbase operation=release_session status=${response.status} duration_ms=${Date.now() - requestStartedAt}`);
|
|
61
|
-
}
|
|
62
|
-
catch (error) {
|
|
63
|
-
const outcome = error instanceof Error && error.name === 'TimeoutError' ? 'timed_out' : 'failed';
|
|
64
|
-
logBoundary(log, `checkout browser boundary: provider=browserbase operation=release_session outcome=${outcome} duration_ms=${Date.now() - requestStartedAt}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function unsupportedContextOptions(options) {
|
|
68
|
-
return Object.keys(options).filter((key) => key !== 'locale' &&
|
|
69
|
-
key !== 'extraHTTPHeaders' &&
|
|
70
|
-
options[key] !== undefined);
|
|
71
|
-
}
|
|
72
|
-
function checkoutHeaders(headers = {}) {
|
|
73
|
-
const withoutAcceptLanguage = Object.fromEntries(Object.entries(headers).filter(([name]) => name.toLowerCase() !== 'accept-language'));
|
|
74
|
-
return { ...withoutAcceptLanguage, 'Accept-Language': 'en-US' };
|
|
75
|
-
}
|
|
76
|
-
function logBoundary(log, message) {
|
|
77
|
-
try {
|
|
78
|
-
log(message);
|
|
79
|
-
}
|
|
80
|
-
catch {
|
|
81
|
-
// Observability must never break checkout or expose the provider payload.
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
/**
|
|
85
|
-
* Adapt Browserbase's one recorded default context to Browser.newContext().
|
|
86
|
-
* One launch serves one checkout review, so the context is leased exactly once.
|
|
87
|
-
*/
|
|
88
|
-
export function adaptBrowserbaseBrowser(browser, context) {
|
|
89
|
-
let leased = false;
|
|
90
|
-
return new Proxy(browser, {
|
|
91
|
-
get(target, property, receiver) {
|
|
92
|
-
if (property === 'newContext') {
|
|
93
|
-
return async (options = {}) => {
|
|
94
|
-
if (leased)
|
|
95
|
-
throw new Error('Browserbase checkout context has already been leased');
|
|
96
|
-
const unsupported = unsupportedContextOptions(options);
|
|
97
|
-
if (unsupported.length > 0) {
|
|
98
|
-
throw new Error(`Browserbase checkout does not support context options: ${unsupported.join(', ')}`);
|
|
99
|
-
}
|
|
100
|
-
if (options.locale !== undefined && options.locale !== 'en-US') {
|
|
101
|
-
throw new Error('Browserbase checkout requires the en-US locale');
|
|
102
|
-
}
|
|
103
|
-
leased = true;
|
|
104
|
-
try {
|
|
105
|
-
await context.setExtraHTTPHeaders(checkoutHeaders(options.extraHTTPHeaders));
|
|
106
|
-
}
|
|
107
|
-
catch {
|
|
108
|
-
throw new Error('Browserbase checkout context headers could not be configured');
|
|
109
|
-
}
|
|
110
|
-
return context;
|
|
111
|
-
};
|
|
112
|
-
}
|
|
113
|
-
const value = Reflect.get(target, property, target);
|
|
114
|
-
return typeof value === 'function' ? value.bind(target) : value;
|
|
115
|
-
},
|
|
116
|
-
});
|
|
117
|
-
}
|
|
118
|
-
export async function launchBrowserbaseCheckoutBrowser(deps = {}) {
|
|
119
|
-
const env = deps.env ?? process.env;
|
|
120
|
-
const fetchImpl = deps.fetch ?? fetch;
|
|
121
|
-
const connectOverCDP = deps.connectOverCDP ?? ((url) => chromium.connectOverCDP(url));
|
|
122
|
-
const log = deps.log ?? (() => { });
|
|
123
|
-
const apiKey = requiredSecret(env, 'BROWSERBASE_API_KEY');
|
|
124
|
-
const projectId = env.BROWSERBASE_PROJECT_ID?.trim() || undefined;
|
|
125
|
-
const requestStartedAt = Date.now();
|
|
126
|
-
let response;
|
|
127
|
-
try {
|
|
128
|
-
response = await fetchImpl(BROWSERBASE_SESSIONS_URL, {
|
|
129
|
-
method: 'POST',
|
|
130
|
-
headers: {
|
|
131
|
-
'Content-Type': 'application/json',
|
|
132
|
-
'X-BB-API-Key': apiKey,
|
|
133
|
-
},
|
|
134
|
-
body: JSON.stringify({
|
|
135
|
-
...(projectId ? { projectId } : {}),
|
|
136
|
-
timeout: BROWSERBASE_SESSION_TIMEOUT_SECONDS,
|
|
137
|
-
keepAlive: false,
|
|
138
|
-
browserSettings: { viewport: { width: 1280, height: 900 } },
|
|
139
|
-
userMetadata: { surface: 'visa-checkout-engine' },
|
|
140
|
-
}),
|
|
141
|
-
signal: AbortSignal.timeout(BROWSERBASE_API_TIMEOUT_MS),
|
|
142
|
-
});
|
|
143
|
-
}
|
|
144
|
-
catch (error) {
|
|
145
|
-
const reason = error instanceof Error && error.name === 'TimeoutError' ? 'timed out' : 'failed';
|
|
146
|
-
logBoundary(log, `checkout browser boundary: provider=browserbase operation=create_session outcome=${reason.replace(' ', '_')} duration_ms=${Date.now() - requestStartedAt}`);
|
|
147
|
-
throw new Error(`Browserbase session creation ${reason}`);
|
|
148
|
-
}
|
|
149
|
-
logBoundary(log, `checkout browser boundary: provider=browserbase operation=create_session status=${response.status} duration_ms=${Date.now() - requestStartedAt}`);
|
|
150
|
-
if (!response.ok) {
|
|
151
|
-
throw new Error(`Browserbase session creation failed with HTTP ${response.status}`);
|
|
152
|
-
}
|
|
153
|
-
const sessionPayload = await response.json().catch(() => null);
|
|
154
|
-
let session;
|
|
155
|
-
try {
|
|
156
|
-
session = parseSession(sessionPayload);
|
|
157
|
-
}
|
|
158
|
-
catch (error) {
|
|
159
|
-
const sessionId = sessionPayload &&
|
|
160
|
-
typeof sessionPayload === 'object' &&
|
|
161
|
-
typeof sessionPayload.id === 'string'
|
|
162
|
-
? String(sessionPayload.id)
|
|
163
|
-
: null;
|
|
164
|
-
if (sessionId) {
|
|
165
|
-
await releaseBrowserbaseSession(fetchImpl, apiKey, sessionId, projectId, log);
|
|
166
|
-
}
|
|
167
|
-
throw error;
|
|
168
|
-
}
|
|
169
|
-
let browser;
|
|
170
|
-
try {
|
|
171
|
-
browser = await connectOverCDP(session.connectUrl);
|
|
172
|
-
}
|
|
173
|
-
catch {
|
|
174
|
-
await releaseBrowserbaseSession(fetchImpl, apiKey, session.id, projectId, log);
|
|
175
|
-
throw new Error('Browserbase session was created but the secure CDP connection failed');
|
|
176
|
-
}
|
|
177
|
-
const context = browser.contexts()[0];
|
|
178
|
-
if (!context) {
|
|
179
|
-
await browser.close().catch(() => undefined);
|
|
180
|
-
await releaseBrowserbaseSession(fetchImpl, apiKey, session.id, projectId, log);
|
|
181
|
-
throw new Error('Browserbase CDP connection did not expose its default browser context');
|
|
182
|
-
}
|
|
183
|
-
await Promise.all(context.pages().map((page) => page.close().catch(() => undefined)));
|
|
184
|
-
logBoundary(log, 'checkout browser: connected to Browserbase');
|
|
185
|
-
return adaptBrowserbaseBrowser(browser, context);
|
|
186
|
-
}
|
|
@@ -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>;
|