@visa/cli 4.1.0-rc.8 → 4.1.0-rc.81

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 (70) hide show
  1. package/README.md +178 -231
  2. package/dist/checkout-engine/adapters/generic.d.ts +23 -0
  3. package/dist/checkout-engine/adapters/generic.js +216 -0
  4. package/dist/checkout-engine/adapters/index.d.ts +8 -0
  5. package/dist/checkout-engine/adapters/index.js +21 -0
  6. package/dist/checkout-engine/adapters/shopify.d.ts +31 -0
  7. package/dist/checkout-engine/adapters/shopify.js +423 -0
  8. package/dist/checkout-engine/adapters/stripe-like.d.ts +10 -0
  9. package/dist/checkout-engine/adapters/stripe-like.js +21 -0
  10. package/dist/checkout-engine/amount.d.ts +15 -0
  11. package/dist/checkout-engine/amount.js +72 -0
  12. package/dist/checkout-engine/browser-launch.d.ts +46 -0
  13. package/dist/checkout-engine/browser-launch.js +81 -0
  14. package/dist/checkout-engine/ceremony.d.ts +64 -0
  15. package/dist/checkout-engine/ceremony.js +261 -0
  16. package/dist/checkout-engine/cli-engine.d.ts +214 -0
  17. package/dist/checkout-engine/cli-engine.js +701 -0
  18. package/dist/checkout-engine/detect.d.ts +61 -0
  19. package/dist/checkout-engine/detect.js +398 -0
  20. package/dist/checkout-engine/evidence.d.ts +25 -0
  21. package/dist/checkout-engine/evidence.js +104 -0
  22. package/dist/checkout-engine/executor.d.ts +176 -0
  23. package/dist/checkout-engine/executor.js +1322 -0
  24. package/dist/checkout-engine/hosted-approval.d.ts +142 -0
  25. package/dist/checkout-engine/hosted-approval.js +339 -0
  26. package/dist/checkout-engine/index.d.ts +6 -0
  27. package/dist/checkout-engine/index.js +8 -0
  28. package/dist/checkout-engine/inline-target.d.ts +13 -0
  29. package/dist/checkout-engine/inline-target.js +37 -0
  30. package/dist/checkout-engine/instrument.d.ts +61 -0
  31. package/dist/checkout-engine/instrument.js +87 -0
  32. package/dist/checkout-engine/live-fill-approval.d.ts +43 -0
  33. package/dist/checkout-engine/live-fill-approval.js +90 -0
  34. package/dist/checkout-engine/mandate/card-mandate.d.ts +121 -0
  35. package/dist/checkout-engine/mandate/card-mandate.js +227 -0
  36. package/dist/checkout-engine/mandate/mandate-ledger.d.ts +142 -0
  37. package/dist/checkout-engine/mandate/mandate-ledger.js +338 -0
  38. package/dist/checkout-engine/mandate.d.ts +25 -0
  39. package/dist/checkout-engine/mandate.js +100 -0
  40. package/dist/checkout-engine/outcome.d.ts +30 -0
  41. package/dist/checkout-engine/outcome.js +225 -0
  42. package/dist/checkout-engine/owner-only-file.d.ts +19 -0
  43. package/dist/checkout-engine/owner-only-file.js +41 -0
  44. package/dist/checkout-engine/package.json +3 -0
  45. package/dist/checkout-engine/receipt.d.ts +81 -0
  46. package/dist/checkout-engine/receipt.js +109 -0
  47. package/dist/checkout-engine/repo-env.d.ts +11 -0
  48. package/dist/checkout-engine/repo-env.js +23 -0
  49. package/dist/checkout-engine/trace-handles.d.ts +8 -0
  50. package/dist/checkout-engine/trace-handles.js +12 -0
  51. package/dist/checkout-engine/types.d.ts +44 -0
  52. package/dist/checkout-engine/types.js +2 -0
  53. package/dist/checkout-engine/vgs-gateway/fetch-credential.d.mts +74 -0
  54. package/dist/checkout-engine/vgs-gateway/fetch-credential.mjs +248 -0
  55. package/dist/checkout-engine/vgs-gateway/server-mint-client.d.ts +82 -0
  56. package/dist/checkout-engine/vgs-gateway/server-mint-client.js +180 -0
  57. package/dist/checkout-engine/vgs-live-instrument.d.ts +170 -0
  58. package/dist/checkout-engine/vgs-live-instrument.js +293 -0
  59. package/dist/checkout-engine/vic-confirmation.d.ts +34 -0
  60. package/dist/checkout-engine/vic-confirmation.js +39 -0
  61. package/dist/cli.js +442 -433
  62. package/dist/mcp-server/index.js +360 -170
  63. package/dist/skills/pair-visa-agent/RUNTIMES.md +92 -0
  64. package/dist/skills/pair-visa-agent/SKILL.md +447 -0
  65. package/dist/skills/pair-visa-agent/scripts/setup.mjs +48 -0
  66. package/install.ps1 +3 -41
  67. package/install.sh +3 -35
  68. package/native/bin/win32-x64/visa-keychain-win.exe +0 -0
  69. package/package.json +16 -12
  70. package/server.json +3 -3
@@ -0,0 +1,216 @@
1
+ // Generic adapter. Drives the detect.ts field map: fills text inputs and
2
+ // <select> dropdowns, handles split vs combined expiry, two- vs four-digit
3
+ // years, and split first/last name. It is the fallback that should beat any
4
+ // well-behaved guest checkout on its own.
5
+ import { maskCvc, maskExpiry, maskPan, redactContact } from '../evidence.js';
6
+ function pad2(n) {
7
+ return String(n).padStart(2, '0');
8
+ }
9
+ export function resolveLocator(page, entry) {
10
+ if (entry.frame)
11
+ return page.frameLocator(entry.frame).locator(entry.locator);
12
+ return page.locator(entry.locator);
13
+ }
14
+ function matchOption(options, wanted) {
15
+ for (const w of wanted) {
16
+ const lc = w.toLowerCase();
17
+ for (const o of options) {
18
+ if (o.value === w)
19
+ return o.value;
20
+ if (o.value.toLowerCase() === lc)
21
+ return o.value;
22
+ if (o.text.toLowerCase() === lc)
23
+ return o.value;
24
+ }
25
+ }
26
+ // looser: contains
27
+ for (const w of wanted) {
28
+ const lc = w.toLowerCase();
29
+ for (const o of options)
30
+ if (o.text.toLowerCase().includes(lc))
31
+ return o.value;
32
+ }
33
+ return null;
34
+ }
35
+ const MONTH_NAMES = [
36
+ 'january',
37
+ 'february',
38
+ 'march',
39
+ 'april',
40
+ 'may',
41
+ 'june',
42
+ 'july',
43
+ 'august',
44
+ 'september',
45
+ 'october',
46
+ 'november',
47
+ 'december',
48
+ ];
49
+ function monthOptionValue(options, month) {
50
+ return matchOption(options, [pad2(month), String(month), MONTH_NAMES[month - 1] ?? '']);
51
+ }
52
+ function yearOptionValue(options, year) {
53
+ return matchOption(options, [String(year), pad2(year % 100), String(year % 100)]);
54
+ }
55
+ // value chosen for a select-style role, or the typed string for inputs.
56
+ function expCombinedValue(entry, month, year) {
57
+ const mm = pad2(month);
58
+ const yy = pad2(year % 100);
59
+ const max = entry.maxlength ?? 0;
60
+ // A maxlength of exactly 4 means MMYY with no separator.
61
+ if (max === 4)
62
+ return `${mm}${yy}`;
63
+ return `${mm}/${yy}`;
64
+ }
65
+ // Playwright failure messages embed the attempted value in their call log
66
+ // (`- fill("4242…")`, `- selectOption("123")`), so an unscrubbed error from a
67
+ // FAILED fill would write the exact secret it was typing (cvc/DAVV, expiry,
68
+ // contact PII) into evidence — and from there into the persisted receipt.
69
+ // Scrub the call-log payloads and every literal occurrence of the value.
70
+ export function scrubFillErrorMessage(message, value) {
71
+ let scrubbed = message.replace(/\b(fill|selectOption)\("(?:[^"\\]|\\.)*"\)/g, '$1("<redacted>")');
72
+ if (value)
73
+ scrubbed = scrubbed.split(value).join('<redacted>');
74
+ return scrubbed;
75
+ }
76
+ const DEFAULT_FILL_TIMEOUT_MS = 5000;
77
+ async function fillOne(page, role, entry, value, displayValue, fillTimeoutMs) {
78
+ const base = {
79
+ role,
80
+ locator: entry.locator,
81
+ confidence: entry.confidence,
82
+ source: entry.source,
83
+ frame: entry.frame,
84
+ value: displayValue,
85
+ };
86
+ try {
87
+ const loc = resolveLocator(page, entry);
88
+ if (entry.tag === 'select') {
89
+ await loc.selectOption(value, { timeout: fillTimeoutMs });
90
+ }
91
+ else {
92
+ await loc.fill(value, { timeout: fillTimeoutMs });
93
+ }
94
+ return { ...base, ok: true };
95
+ }
96
+ catch (err) {
97
+ return { ...base, ok: false, error: scrubFillErrorMessage(err.message, value) };
98
+ }
99
+ }
100
+ async function fillFields(page, fields, credential, contact, opts = {}) {
101
+ const fillTimeoutMs = opts.fillTimeoutMs ?? DEFAULT_FILL_TIMEOUT_MS;
102
+ const filled = [];
103
+ const fullName = contact.fullName ?? credential?.cardholderName;
104
+ const first = contact.firstName ?? fullName?.split(/\s+/)[0];
105
+ const last = contact.lastName ?? fullName?.split(/\s+/).slice(1).join(' ');
106
+ // Order matters a little: contact/name before card is harmless, but we fill
107
+ // card fields explicitly per role so order is not load-bearing.
108
+ const jobs = [];
109
+ const add = (role, entry, value, display) => {
110
+ if (!entry)
111
+ return;
112
+ // Skip fields that are not currently visible (e.g. a collapsed accordion
113
+ // panel or a not-yet-reached step). The executor reveals them and re-fills.
114
+ if (entry.visible === false)
115
+ return;
116
+ jobs.push({
117
+ role,
118
+ entry,
119
+ run: async () => fillOne(page, role, entry, value(), display(), fillTimeoutMs),
120
+ });
121
+ };
122
+ if (credential) {
123
+ add('number', fields.number, () => credential.pan, () => maskPan(credential.pan));
124
+ add('cvc', fields.cvc, () => credential.cvc, () => maskCvc(credential.cvc));
125
+ }
126
+ if (fullName)
127
+ add('name', fields.name, () => fullName, () => redactContact('name', fullName));
128
+ if (first)
129
+ add('nameFirst', fields.nameFirst, () => first, () => redactContact('nameFirst', first));
130
+ if (last)
131
+ add('nameLast', fields.nameLast, () => last, () => redactContact('nameLast', last));
132
+ // Expiry display values are always redacted: the expiry is part of the
133
+ // keyable credential (DPAN + expiry + DAVV) and never enters the log.
134
+ if (credential && fields.expCombined) {
135
+ const e = fields.expCombined;
136
+ const v = expCombinedValue(e, credential.expMonth, credential.expYear);
137
+ add('expCombined', e, () => v, () => maskExpiry());
138
+ }
139
+ if (credential && fields.expMonth) {
140
+ const e = fields.expMonth;
141
+ const value = e.tag === 'select'
142
+ ? (monthOptionValue(e.options ?? [], credential.expMonth) ?? pad2(credential.expMonth))
143
+ : pad2(credential.expMonth);
144
+ add('expMonth', e, () => value, () => maskExpiry());
145
+ }
146
+ if (credential && fields.expYear) {
147
+ const e = fields.expYear;
148
+ let value;
149
+ if (e.tag === 'select') {
150
+ value = yearOptionValue(e.options ?? [], credential.expYear) ?? String(credential.expYear);
151
+ }
152
+ else if ((e.maxlength ?? 0) === 2) {
153
+ value = pad2(credential.expYear % 100);
154
+ }
155
+ else {
156
+ value = String(credential.expYear);
157
+ }
158
+ add('expYear', e, () => value, () => maskExpiry());
159
+ }
160
+ // Contact / shipping. Display values are always redacted: these are PII and
161
+ // the evidence log is built to be persistable.
162
+ if (contact.email)
163
+ add('email', fields.email, () => contact.email, () => redactContact('email', contact.email));
164
+ if (contact.phone)
165
+ add('phone', fields.phone, () => contact.phone, () => redactContact('phone', contact.phone));
166
+ if (contact.addressLine1)
167
+ add('addressLine1', fields.addressLine1, () => contact.addressLine1, () => redactContact('addressLine1', contact.addressLine1));
168
+ if (contact.addressLine2)
169
+ add('addressLine2', fields.addressLine2, () => contact.addressLine2, () => redactContact('addressLine2', contact.addressLine2));
170
+ if (contact.city)
171
+ add('city', fields.city, () => contact.city, () => redactContact('city', contact.city));
172
+ if (contact.postalCode)
173
+ add('postalCode', fields.postalCode, () => contact.postalCode, () => redactContact('postalCode', contact.postalCode));
174
+ if (fields.state && contact.state) {
175
+ const e = fields.state;
176
+ const wanted = contact.state;
177
+ const value = e.tag === 'select' ? (matchOption(e.options ?? [], [wanted]) ?? wanted) : wanted;
178
+ add('state', e, () => value, () => redactContact('state', value));
179
+ }
180
+ if (fields.country && contact.country) {
181
+ const e = fields.country;
182
+ const value = e.tag === 'select'
183
+ ? (matchOption(e.options ?? [], [contact.country]) ?? contact.country)
184
+ : contact.country;
185
+ add('country', e, () => value, () => redactContact('country', value));
186
+ }
187
+ for (const j of jobs) {
188
+ const r = await j.run();
189
+ if (r)
190
+ filled.push(r);
191
+ }
192
+ return filled;
193
+ }
194
+ // Credential-free contact prefill for merchants that must calculate shipping,
195
+ // tax, and the final total before a human reviews the payment.
196
+ export async function fillContactFieldMap(page, fields, contact, opts = {}) {
197
+ return fillFields(page, fields, null, contact, opts);
198
+ }
199
+ // The shared post-approval fill routine used by every adapter.
200
+ export async function fillFieldMap(page, fields, credential, contact, opts = {}) {
201
+ return fillFields(page, fields, credential, contact, opts);
202
+ }
203
+ export class GenericAdapter {
204
+ name = 'generic';
205
+ matches(_detected) {
206
+ // The generic adapter is the universal fallback; it always matches.
207
+ return true;
208
+ }
209
+ async fill(page, fields, credential, contact) {
210
+ const filled = await fillFieldMap(page, fields, credential, contact);
211
+ return {
212
+ ok: filled.some((f) => f.role === 'number' && f.ok),
213
+ filled,
214
+ };
215
+ }
216
+ }
@@ -0,0 +1,8 @@
1
+ import type { DetectResult } from '../detect.js';
2
+ import type { CheckoutAdapter } from './generic.js';
3
+ export type { CheckoutAdapter } from './generic.js';
4
+ export { GenericAdapter } from './generic.js';
5
+ export { ShopifyAdapter } from './shopify.js';
6
+ export { StripeLikeAdapter } from './stripe-like.js';
7
+ export { fillFieldMap, resolveLocator } from './generic.js';
8
+ export declare function selectAdapter(detected: DetectResult): CheckoutAdapter;
@@ -0,0 +1,21 @@
1
+ // Adapter registry. Specific adapters get first crack; the generic adapter is
2
+ // the universal fallback and always matches last. Selection is a pure
3
+ // function of an already-run detection — adapters never re-detect.
4
+ import { GenericAdapter } from './generic.js';
5
+ import { ShopifyAdapter } from './shopify.js';
6
+ import { StripeLikeAdapter } from './stripe-like.js';
7
+ export { GenericAdapter } from './generic.js';
8
+ export { ShopifyAdapter } from './shopify.js';
9
+ export { StripeLikeAdapter } from './stripe-like.js';
10
+ export { fillFieldMap, resolveLocator } from './generic.js';
11
+ const FALLBACK = new GenericAdapter();
12
+ export function selectAdapter(detected) {
13
+ // Shopify owns the outer checkout form and can still delegate card fields to
14
+ // an iframe, so it gets first crack and reuses fillFieldMap for both.
15
+ const specific = [new ShopifyAdapter(detected), new StripeLikeAdapter()];
16
+ for (const a of specific) {
17
+ if (a.matches(detected))
18
+ return a;
19
+ }
20
+ return FALLBACK;
21
+ }
@@ -0,0 +1,31 @@
1
+ import type { Page } from 'playwright-core';
2
+ import { type PageAmountRead } from '../amount.js';
3
+ import { type DetectResult, type FieldMap } from '../detect.js';
4
+ import type { CardCredential } from '../instrument.js';
5
+ import type { Contact, FillResult } from '../types.js';
6
+ import { type CheckoutAdapter } from './generic.js';
7
+ type ShopifySummary = {
8
+ subtotalMinor: number | null;
9
+ shippingMinor: number | null;
10
+ taxMinor: number | null;
11
+ discountMinor: number;
12
+ totalMinor: number | null;
13
+ currency: string | null;
14
+ verified: boolean;
15
+ };
16
+ export declare function parseShopifySummary(text: string): ShopifySummary;
17
+ export declare function readShopifyAmount(page: Page, requireVerified: boolean): Promise<PageAmountRead>;
18
+ export declare function readStableShopifyAmount(page: Page, timeoutMs?: number): Promise<PageAmountRead>;
19
+ export declare function isShopifyCheckoutPage(page: Page): Promise<boolean>;
20
+ export declare function detectShopifyChallenge(page: Page): Promise<{
21
+ signal: string;
22
+ } | null>;
23
+ export declare class ShopifyAdapter implements CheckoutAdapter {
24
+ private readonly detected;
25
+ name: string;
26
+ constructor(detected: DetectResult);
27
+ matches(detected: DetectResult): boolean;
28
+ prepareContact(page: Page, contact: Contact): Promise<FillResult>;
29
+ fill(page: Page, _fields: FieldMap, credential: CardCredential, _contact: Contact): Promise<FillResult>;
30
+ }
31
+ export {};